re-added .xpex and .xpexl commands as there was no way to exclude users and roles from the xp system anymore
This commit is contained in:
parent
ca46786c5e
commit
1d667db598
19 changed files with 631 additions and 169 deletions
src/EllieBot
Db/Models/xp
Migrations
PostgreSql
20250318221943_xpexclusion.sql20250318222207_init.Designer.cs20250318222207_init.csPostgreSqlContextModelSnapshot.cs
Sqlite
Modules/Xp
strings
|
@ -1,14 +0,0 @@
|
||||||
namespace EllieBot.Db.Models;
|
|
||||||
|
|
||||||
public class ExcludedItem : DbEntity
|
|
||||||
{
|
|
||||||
public int? XpSettingsId { get; set; }
|
|
||||||
public ulong ItemId { get; set; }
|
|
||||||
public ExcludedItemType ItemType { get; set; }
|
|
||||||
|
|
||||||
public override int GetHashCode()
|
|
||||||
=> ItemId.GetHashCode() ^ ItemType.GetHashCode();
|
|
||||||
|
|
||||||
public override bool Equals(object? obj)
|
|
||||||
=> obj is ExcludedItem ei && ei.ItemId == ItemId && ei.ItemType == ItemType;
|
|
||||||
}
|
|
|
@ -1,3 +1,7 @@
|
||||||
namespace EllieBot.Db.Models;
|
namespace EllieBot.Db.Models;
|
||||||
|
|
||||||
public enum ExcludedItemType { Channel, Role }
|
public enum XpExcludedItemType
|
||||||
|
{
|
||||||
|
User,
|
||||||
|
Role
|
||||||
|
}
|
31
src/EllieBot/Db/Models/xp/XpExcludedItem.cs
Normal file
31
src/EllieBot/Db/Models/xp/XpExcludedItem.cs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
|
||||||
|
namespace EllieBot.Db.Models;
|
||||||
|
|
||||||
|
public class XpExcludedItem
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
public ulong GuildId { get; set; }
|
||||||
|
|
||||||
|
public XpExcludedItemType ItemType { get; set; }
|
||||||
|
public ulong ItemId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class XpExclusionEntityConfig : IEntityTypeConfiguration<XpExcludedItem>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<XpExcludedItem> builder)
|
||||||
|
{
|
||||||
|
builder.HasIndex(x => x.GuildId);
|
||||||
|
|
||||||
|
builder.HasAlternateKey(x => new
|
||||||
|
{
|
||||||
|
x.GuildId,
|
||||||
|
x.ItemType,
|
||||||
|
x.ItemId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
START TRANSACTION;
|
||||||
|
CREATE TABLE xpexcludeditem (
|
||||||
|
id integer GENERATED BY DEFAULT AS IDENTITY,
|
||||||
|
guildid numeric(20,0) NOT NULL,
|
||||||
|
itemtype integer NOT NULL,
|
||||||
|
itemid numeric(20,0) NOT NULL,
|
||||||
|
CONSTRAINT pk_xpexcludeditem PRIMARY KEY (id),
|
||||||
|
CONSTRAINT ak_xpexcludeditem_guildid_itemtype_itemid UNIQUE (guildid, itemtype, itemid)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX ix_xpexcludeditem_guildid ON xpexcludeditem (guildid);
|
||||||
|
|
||||||
|
INSERT INTO "__EFMigrationsHistory" (migrationid, productversion)
|
||||||
|
VALUES ('20250318221943_xpexclusion', '9.0.1');
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
|
|
|
@ -12,7 +12,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
namespace EllieBot.Migrations.PostgreSql
|
namespace EllieBot.Migrations.PostgreSql
|
||||||
{
|
{
|
||||||
[DbContext(typeof(PostgreSqlContext))]
|
[DbContext(typeof(PostgreSqlContext))]
|
||||||
[Migration("20250317063309_init")]
|
[Migration("20250318222207_init")]
|
||||||
partial class init
|
partial class init
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
@ -3263,6 +3263,39 @@ namespace EllieBot.Migrations.PostgreSql
|
||||||
b.ToTable("xpcurrencyreward", (string)null);
|
b.ToTable("xpcurrencyreward", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("EllieBot.Db.Models.XpExcludedItem", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<decimal>("GuildId")
|
||||||
|
.HasColumnType("numeric(20,0)")
|
||||||
|
.HasColumnName("guildid");
|
||||||
|
|
||||||
|
b.Property<decimal>("ItemId")
|
||||||
|
.HasColumnType("numeric(20,0)")
|
||||||
|
.HasColumnName("itemid");
|
||||||
|
|
||||||
|
b.Property<int>("ItemType")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("itemtype");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk_xpexcludeditem");
|
||||||
|
|
||||||
|
b.HasAlternateKey("GuildId", "ItemType", "ItemId")
|
||||||
|
.HasName("ak_xpexcludeditem_guildid_itemtype_itemid");
|
||||||
|
|
||||||
|
b.HasIndex("GuildId")
|
||||||
|
.HasDatabaseName("ix_xpexcludeditem_guildid");
|
||||||
|
|
||||||
|
b.ToTable("xpexcludeditem", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("EllieBot.Db.Models.XpRoleReward", b =>
|
modelBuilder.Entity("EllieBot.Db.Models.XpRoleReward", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
|
@ -1179,6 +1179,22 @@ namespace EllieBot.Migrations.PostgreSql
|
||||||
table.PrimaryKey("pk_warnings", x => x.id);
|
table.PrimaryKey("pk_warnings", x => x.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "xpexcludeditem",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
id = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
guildid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||||
|
itemtype = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
itemid = table.Column<decimal>(type: "numeric(20,0)", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk_xpexcludeditem", x => x.id);
|
||||||
|
table.UniqueConstraint("ak_xpexcludeditem_guildid_itemtype_itemid", x => new { x.guildid, x.itemtype, x.itemid });
|
||||||
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "xpsettings",
|
name: "xpsettings",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
|
@ -2280,6 +2296,11 @@ namespace EllieBot.Migrations.PostgreSql
|
||||||
table: "xpcurrencyreward",
|
table: "xpcurrencyreward",
|
||||||
column: "xpsettingsid");
|
column: "xpsettingsid");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_xpexcludeditem_guildid",
|
||||||
|
table: "xpexcludeditem",
|
||||||
|
column: "guildid");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "ix_xprolereward_xpsettingsid_level",
|
name: "ix_xprolereward_xpsettingsid_level",
|
||||||
table: "xprolereward",
|
table: "xprolereward",
|
||||||
|
@ -2574,6 +2595,9 @@ namespace EllieBot.Migrations.PostgreSql
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "xpcurrencyreward");
|
name: "xpcurrencyreward");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "xpexcludeditem");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "xprolereward");
|
name: "xprolereward");
|
||||||
|
|
|
@ -3260,6 +3260,39 @@ namespace EllieBot.Migrations.PostgreSql
|
||||||
b.ToTable("xpcurrencyreward", (string)null);
|
b.ToTable("xpcurrencyreward", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("EllieBot.Db.Models.XpExcludedItem", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<decimal>("GuildId")
|
||||||
|
.HasColumnType("numeric(20,0)")
|
||||||
|
.HasColumnName("guildid");
|
||||||
|
|
||||||
|
b.Property<decimal>("ItemId")
|
||||||
|
.HasColumnType("numeric(20,0)")
|
||||||
|
.HasColumnName("itemid");
|
||||||
|
|
||||||
|
b.Property<int>("ItemType")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("itemtype");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk_xpexcludeditem");
|
||||||
|
|
||||||
|
b.HasAlternateKey("GuildId", "ItemType", "ItemId")
|
||||||
|
.HasName("ak_xpexcludeditem_guildid_itemtype_itemid");
|
||||||
|
|
||||||
|
b.HasIndex("GuildId")
|
||||||
|
.HasDatabaseName("ix_xpexcludeditem_guildid");
|
||||||
|
|
||||||
|
b.ToTable("xpexcludeditem", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("EllieBot.Db.Models.XpRoleReward", b =>
|
modelBuilder.Entity("EllieBot.Db.Models.XpRoleReward", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
BEGIN TRANSACTION;
|
||||||
|
CREATE TABLE "XpExcludedItem" (
|
||||||
|
"Id" INTEGER NOT NULL CONSTRAINT "PK_XpExcludedItem" PRIMARY KEY AUTOINCREMENT,
|
||||||
|
"GuildId" INTEGER NOT NULL,
|
||||||
|
"ItemType" INTEGER NOT NULL,
|
||||||
|
"ItemId" INTEGER NOT NULL,
|
||||||
|
CONSTRAINT "AK_XpExcludedItem_GuildId_ItemType_ItemId" UNIQUE ("GuildId", "ItemType", "ItemId")
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX "IX_XpExcludedItem_GuildId" ON "XpExcludedItem" ("GuildId");
|
||||||
|
|
||||||
|
INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
|
||||||
|
VALUES ('20250318221922_xpexclusion', '9.0.1');
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
|
|
|
@ -11,7 +11,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
namespace EllieBot.Migrations.Sqlite
|
namespace EllieBot.Migrations.Sqlite
|
||||||
{
|
{
|
||||||
[DbContext(typeof(SqliteContext))]
|
[DbContext(typeof(SqliteContext))]
|
||||||
[Migration("20250317063300_init")]
|
[Migration("20250318222152_init")]
|
||||||
partial class init
|
partial class init
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
@ -2428,6 +2428,30 @@ namespace EllieBot.Migrations.Sqlite
|
||||||
b.ToTable("XpCurrencyReward");
|
b.ToTable("XpCurrencyReward");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("EllieBot.Db.Models.XpExcludedItem", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<ulong>("GuildId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<ulong>("ItemId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("ItemType")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasAlternateKey("GuildId", "ItemType", "ItemId");
|
||||||
|
|
||||||
|
b.HasIndex("GuildId");
|
||||||
|
|
||||||
|
b.ToTable("XpExcludedItem");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("EllieBot.Db.Models.XpRoleReward", b =>
|
modelBuilder.Entity("EllieBot.Db.Models.XpRoleReward", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
|
@ -1181,6 +1181,22 @@ namespace EllieBot.Migrations.Sqlite
|
||||||
table.PrimaryKey("PK_Warnings", x => x.Id);
|
table.PrimaryKey("PK_Warnings", x => x.Id);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "XpExcludedItem",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
GuildId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||||
|
ItemType = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
ItemId = table.Column<ulong>(type: "INTEGER", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_XpExcludedItem", x => x.Id);
|
||||||
|
table.UniqueConstraint("AK_XpExcludedItem_GuildId_ItemType_ItemId", x => new { x.GuildId, x.ItemType, x.ItemId });
|
||||||
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "XpSettings",
|
name: "XpSettings",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
|
@ -2282,6 +2298,11 @@ namespace EllieBot.Migrations.Sqlite
|
||||||
table: "XpCurrencyReward",
|
table: "XpCurrencyReward",
|
||||||
column: "XpSettingsId");
|
column: "XpSettingsId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_XpExcludedItem_GuildId",
|
||||||
|
table: "XpExcludedItem",
|
||||||
|
column: "GuildId");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_XpRoleReward_XpSettingsId_Level",
|
name: "IX_XpRoleReward_XpSettingsId_Level",
|
||||||
table: "XpRoleReward",
|
table: "XpRoleReward",
|
||||||
|
@ -2576,6 +2597,9 @@ namespace EllieBot.Migrations.Sqlite
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "XpCurrencyReward");
|
name: "XpCurrencyReward");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "XpExcludedItem");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "XpRoleReward");
|
name: "XpRoleReward");
|
||||||
|
|
|
@ -2425,6 +2425,30 @@ namespace EllieBot.Migrations.Sqlite
|
||||||
b.ToTable("XpCurrencyReward");
|
b.ToTable("XpCurrencyReward");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("EllieBot.Db.Models.XpExcludedItem", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<ulong>("GuildId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<ulong>("ItemId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("ItemType")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasAlternateKey("GuildId", "ItemType", "ItemId");
|
||||||
|
|
||||||
|
b.HasIndex("GuildId");
|
||||||
|
|
||||||
|
b.ToTable("XpExcludedItem");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("EllieBot.Db.Models.XpRoleReward", b =>
|
modelBuilder.Entity("EllieBot.Db.Models.XpRoleReward", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
|
|
84
src/EllieBot/Modules/Xp/XpExclusion/XpExclusionCommands.cs
Normal file
84
src/EllieBot/Modules/Xp/XpExclusion/XpExclusionCommands.cs
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
using EllieBot.Db.Models;
|
||||||
|
|
||||||
|
namespace EllieBot.Modules.Xp;
|
||||||
|
|
||||||
|
public partial class Xp
|
||||||
|
{
|
||||||
|
[RequireUserPermission(GuildPermission.Administrator)]
|
||||||
|
public class XpExclusionCommands : EllieModule<XpExclusionService>
|
||||||
|
{
|
||||||
|
[Cmd]
|
||||||
|
[RequireContext(ContextType.Guild)]
|
||||||
|
[UserPerm(GuildPerm.Administrator)]
|
||||||
|
public async Task XpExclusion()
|
||||||
|
{
|
||||||
|
var exclusions = await _service.GetExclusionsAsync(ctx.Guild.Id);
|
||||||
|
|
||||||
|
if (!exclusions.Any())
|
||||||
|
{
|
||||||
|
await Response().Pending(strs.xp_exclusion_none).SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Response()
|
||||||
|
.Paginated()
|
||||||
|
.Items(exclusions.OrderBy(x => x.ItemType).ToList())
|
||||||
|
.PageSize(10)
|
||||||
|
.Page((items, _) =>
|
||||||
|
{
|
||||||
|
var eb = CreateEmbed()
|
||||||
|
.WithOkColor()
|
||||||
|
.WithTitle(GetText(strs.xp_exclusion_title));
|
||||||
|
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
var itemType = item.ItemType;
|
||||||
|
var mention = GetMention(itemType, item.ItemId);
|
||||||
|
|
||||||
|
eb.AddField(itemType.ToString(), mention);
|
||||||
|
}
|
||||||
|
|
||||||
|
return eb;
|
||||||
|
})
|
||||||
|
.SendAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
[RequireContext(ContextType.Guild)]
|
||||||
|
[UserPerm(GuildPerm.Administrator)]
|
||||||
|
public async Task XpExclude([Leftover] IRole role)
|
||||||
|
=> await XpExclude(XpExcludedItemType.Role, role.Id);
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
[RequireContext(ContextType.Guild)]
|
||||||
|
[UserPerm(GuildPerm.Administrator)]
|
||||||
|
public async Task XpExclude([Leftover] IUser user)
|
||||||
|
=> await XpExclude(XpExcludedItemType.User, user.Id);
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
[RequireContext(ContextType.Guild)]
|
||||||
|
[UserPerm(GuildPerm.Administrator)]
|
||||||
|
public async Task XpExclude(XpExcludedItemType type, ulong itemId)
|
||||||
|
{
|
||||||
|
var isExcluded = await _service.ToggleExclusionAsync(ctx.Guild.Id, type, itemId);
|
||||||
|
|
||||||
|
if (isExcluded)
|
||||||
|
await Response()
|
||||||
|
.Confirm(strs.xp_exclude_added(type.ToString(), GetMention(type, itemId)))
|
||||||
|
.SendAsync();
|
||||||
|
else
|
||||||
|
await Response()
|
||||||
|
.Confirm(strs.xp_exclude_removed(type.ToString(), GetMention(type, itemId)))
|
||||||
|
.SendAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetMention(XpExcludedItemType itemType, ulong itemId)
|
||||||
|
=> itemType switch
|
||||||
|
{
|
||||||
|
XpExcludedItemType.Role => ctx.Guild.GetRole(itemId)?.ToString() ?? itemId.ToString(),
|
||||||
|
XpExcludedItemType.User => (ctx.Guild as SocketGuild)?.GetUser(itemId)?.ToString() ??
|
||||||
|
itemId.ToString(),
|
||||||
|
_ => itemId.ToString()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
99
src/EllieBot/Modules/Xp/XpExclusion/XpExclusionService.cs
Normal file
99
src/EllieBot/Modules/Xp/XpExclusion/XpExclusionService.cs
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
using LinqToDB.EntityFrameworkCore;
|
||||||
|
using EllieBot.Common.ModuleBehaviors;
|
||||||
|
using EllieBot.Db.Models;
|
||||||
|
using LinqToDB;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace EllieBot.Modules.Xp;
|
||||||
|
|
||||||
|
public class XpExclusionService(DbService db, ShardData shardData) : IReadyExecutor, IEService
|
||||||
|
{
|
||||||
|
private ConcurrentHashSet<(ulong GuildId, XpExcludedItemType ItemType, ulong ItemId)> _exclusions = new();
|
||||||
|
|
||||||
|
public async Task OnReadyAsync()
|
||||||
|
{
|
||||||
|
await using var uow = db.GetDbContext();
|
||||||
|
_exclusions = await uow.GetTable<XpExcludedItem>()
|
||||||
|
.Where(x => Queries.GuildOnShard(x.GuildId, shardData.TotalShards, shardData.ShardId))
|
||||||
|
.ToListAsyncLinqToDB()
|
||||||
|
.Fmap(x => x
|
||||||
|
.Select(x => (x.GuildId, x.ItemType, x.ItemId))
|
||||||
|
.ToHashSet()
|
||||||
|
.ToConcurrentSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Toggles exclusion for the specified item. If the item was excluded, it will be included
|
||||||
|
/// and vice versa.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="guildId">ID of the guild</param>
|
||||||
|
/// <param name="itemType">Type of the item to toggle exclusion for</param>
|
||||||
|
/// <param name="itemId">ID of the item to toggle exclusion for</param>
|
||||||
|
/// <returns>True if the item is now excluded, false if it's no longer excluded</returns>
|
||||||
|
public async Task<bool> ToggleExclusionAsync(ulong guildId, XpExcludedItemType itemType, ulong itemId)
|
||||||
|
{
|
||||||
|
var key = (guildId, itemType, itemId);
|
||||||
|
var isExcluded = false;
|
||||||
|
|
||||||
|
await using var uow = db.GetDbContext();
|
||||||
|
if (_exclusions.Contains(key))
|
||||||
|
{
|
||||||
|
isExcluded = false;
|
||||||
|
// item exists, remove it
|
||||||
|
await uow.GetTable<XpExcludedItem>()
|
||||||
|
.Where(x => x.GuildId == guildId
|
||||||
|
&& x.ItemType == itemType
|
||||||
|
&& x.ItemId == itemId)
|
||||||
|
.DeleteAsync();
|
||||||
|
|
||||||
|
_exclusions.TryRemove(key);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
isExcluded = true;
|
||||||
|
// item doesn't exist, add it
|
||||||
|
await uow.GetTable<XpExcludedItem>()
|
||||||
|
.InsertOrUpdateAsync(() => new XpExcludedItem
|
||||||
|
{
|
||||||
|
GuildId = guildId,
|
||||||
|
ItemType = itemType,
|
||||||
|
ItemId = itemId
|
||||||
|
},
|
||||||
|
_ => new(),
|
||||||
|
() => new XpExcludedItem
|
||||||
|
{
|
||||||
|
GuildId = guildId,
|
||||||
|
ItemType = itemType,
|
||||||
|
ItemId = itemId
|
||||||
|
});
|
||||||
|
|
||||||
|
_exclusions.Add(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
return isExcluded;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a list of all excluded items for a guild.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="guildId">ID of the guild</param>
|
||||||
|
/// <returns>List of excluded items in the guild</returns>
|
||||||
|
public async Task<IReadOnlyList<XpExcludedItem>> GetExclusionsAsync(ulong guildId)
|
||||||
|
{
|
||||||
|
await using var uow = db.GetDbContext();
|
||||||
|
return await uow.GetTable<XpExcludedItem>()
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(x => x.GuildId == guildId)
|
||||||
|
.ToListAsyncLinqToDB();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the specified item is excluded from XP gain.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="guildId">ID of the guild</param>
|
||||||
|
/// <param name="itemType">Type of the item</param>
|
||||||
|
/// <param name="itemId">ID of the item</param>
|
||||||
|
/// <returns>True if the item is excluded, otherwise false</returns>
|
||||||
|
public bool IsExcluded(ulong guildId, XpExcludedItemType itemType, ulong itemId)
|
||||||
|
=> _exclusions.Contains((guildId, itemType, itemId));
|
||||||
|
}
|
|
@ -9,7 +9,7 @@ namespace EllieBot.Modules.Xp;
|
||||||
|
|
||||||
using GuildXpRates = (IReadOnlyList<GuildXpConfig> GuildRates, IReadOnlyList<ChannelXpConfig> ChannelRates);
|
using GuildXpRates = (IReadOnlyList<GuildXpConfig> GuildRates, IReadOnlyList<ChannelXpConfig> ChannelRates);
|
||||||
|
|
||||||
public class GuildConfigXpService(DbService db, ShardData shardData, XpConfigService xcs) : IReadyExecutor, IEService
|
public class XpRateService(DbService db, ShardData shardData, XpConfigService xcs) : IReadyExecutor, IEService
|
||||||
{
|
{
|
||||||
private ConcurrentDictionary<(XpRateType RateType, ulong GuildId), XpRate> _guildRates = new();
|
private ConcurrentDictionary<(XpRateType RateType, ulong GuildId), XpRate> _guildRates = new();
|
||||||
private ConcurrentDictionary<ulong, ConcurrentDictionary<(XpRateType, ulong), XpRate>> _channelRates = new();
|
private ConcurrentDictionary<ulong, ConcurrentDictionary<(XpRateType, ulong), XpRate>> _channelRates = new();
|
||||||
|
|
|
@ -3,7 +3,7 @@ namespace EllieBot.Modules.Xp;
|
||||||
public partial class Xp
|
public partial class Xp
|
||||||
{
|
{
|
||||||
[RequireUserPermission(GuildPermission.ManageGuild)]
|
[RequireUserPermission(GuildPermission.ManageGuild)]
|
||||||
public class XpRateCommands : EllieModule<GuildConfigXpService>
|
public class XpRateCommands : EllieModule<XpRateService>
|
||||||
{
|
{
|
||||||
[Cmd]
|
[Cmd]
|
||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
|
|
|
@ -40,7 +40,8 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
||||||
private readonly INotifySubscriber _notifySub;
|
private readonly INotifySubscriber _notifySub;
|
||||||
private readonly IMemoryCache _memCache;
|
private readonly IMemoryCache _memCache;
|
||||||
private readonly XpTemplateService _templateService;
|
private readonly XpTemplateService _templateService;
|
||||||
private readonly GuildConfigXpService _xpRateService;
|
private readonly XpRateService _xpRateRateService;
|
||||||
|
private readonly XpExclusionService _xpExcl;
|
||||||
|
|
||||||
private readonly QueueRunner _levelUpQueue = new QueueRunner(0, 100);
|
private readonly QueueRunner _levelUpQueue = new QueueRunner(0, 100);
|
||||||
|
|
||||||
|
@ -60,7 +61,9 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
||||||
IMemoryCache memCache,
|
IMemoryCache memCache,
|
||||||
ShardData shardData,
|
ShardData shardData,
|
||||||
XpTemplateService templateService,
|
XpTemplateService templateService,
|
||||||
GuildConfigXpService xpRateService)
|
XpRateService xpRateRateService,
|
||||||
|
XpExclusionService xpExcl
|
||||||
|
)
|
||||||
{
|
{
|
||||||
_db = db;
|
_db = db;
|
||||||
_images = images;
|
_images = images;
|
||||||
|
@ -72,7 +75,8 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
||||||
_notifySub = notifySub;
|
_notifySub = notifySub;
|
||||||
_memCache = memCache;
|
_memCache = memCache;
|
||||||
_templateService = templateService;
|
_templateService = templateService;
|
||||||
_xpRateService = xpRateService;
|
_xpRateRateService = xpRateRateService;
|
||||||
|
_xpExcl = xpExcl;
|
||||||
_client = client;
|
_client = client;
|
||||||
_ps = ps;
|
_ps = ps;
|
||||||
_c = c;
|
_c = c;
|
||||||
|
@ -143,7 +147,7 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
||||||
if (!IsVoiceChannelActive(vc))
|
if (!IsVoiceChannelActive(vc))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var rate = _xpRateService.GetXpRate(XpRateType.Voice, g.Id, vc.Id);
|
var rate = _xpRateRateService.GetXpRate(XpRateType.Voice, g.Id, vc.Id);
|
||||||
|
|
||||||
if (rate.IsExcluded())
|
if (rate.IsExcluded())
|
||||||
continue;
|
continue;
|
||||||
|
@ -153,6 +157,9 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
||||||
if (!UserParticipatingInVoiceChannel(u))
|
if (!UserParticipatingInVoiceChannel(u))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
if (IsUserExcluded(g, u))
|
||||||
|
continue;
|
||||||
|
|
||||||
if (oldBatch.Contains(u))
|
if (oldBatch.Contains(u))
|
||||||
{
|
{
|
||||||
validUsers.Add(new(u, rate.Amount, vc.Id));
|
validUsers.Add(new(u, rate.Amount, vc.Id));
|
||||||
|
@ -509,15 +516,18 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
||||||
|
|
||||||
_ = Task.Run(async () =>
|
_ = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
|
if (IsUserExcluded(guild, user))
|
||||||
|
return;
|
||||||
|
|
||||||
var isImage = arg.Attachments.Any(a => a.Height >= 16 && a.Width >= 16);
|
var isImage = arg.Attachments.Any(a => a.Height >= 16 && a.Width >= 16);
|
||||||
var isText = arg.Content.Contains(' ') || arg.Content.Length >= 5;
|
var isText = arg.Content.Contains(' ') || arg.Content.Length >= 5;
|
||||||
|
|
||||||
var textRate = _xpRateService.GetXpRate(XpRateType.Text, guild.Id, gc.Id);
|
var textRate = _xpRateRateService.GetXpRate(XpRateType.Text, guild.Id, gc.Id);
|
||||||
|
|
||||||
XpRate rate;
|
XpRate rate;
|
||||||
if (isImage)
|
if (isImage)
|
||||||
{
|
{
|
||||||
var imageRate = _xpRateService.GetXpRate(XpRateType.Image, guild.Id, gc.Id);
|
var imageRate = _xpRateRateService.GetXpRate(XpRateType.Image, guild.Id, gc.Id);
|
||||||
if (imageRate.IsExcluded())
|
if (imageRate.IsExcluded())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -544,6 +554,20 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool IsUserExcluded(IGuild guild, SocketGuildUser user)
|
||||||
|
{
|
||||||
|
if (_xpExcl.IsExcluded(guild.Id, XpExcludedItemType.User, user.Id))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
foreach (var role in user.Roles)
|
||||||
|
{
|
||||||
|
if (_xpExcl.IsExcluded(guild.Id, XpExcludedItemType.Role, role.Id))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<int> AddXpToUsersAsync(ulong guildId, long amount, params ulong[] userIds)
|
public async Task<int> AddXpToUsersAsync(ulong guildId, long amount, params ulong[] userIds)
|
||||||
{
|
{
|
||||||
await using var ctx = _db.GetDbContext();
|
await using var ctx = _db.GetDbContext();
|
||||||
|
@ -614,49 +638,54 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
||||||
}
|
}
|
||||||
|
|
||||||
var avatarUrl = stats.User.RealAvatarUrl();
|
var avatarUrl = stats.User.RealAvatarUrl();
|
||||||
byte[] avatarImageData = null;
|
var avatarFetchTask = Task.Run(async () =>
|
||||||
|
|
||||||
if (avatarUrl is not null)
|
|
||||||
{
|
{
|
||||||
var result = await _c.GetImageDataAsync(avatarUrl);
|
try
|
||||||
if (!result.TryPickT0(out avatarImageData, out _))
|
|
||||||
{
|
{
|
||||||
using (var http = _httpFactory.CreateClient())
|
if (avatarUrl is null)
|
||||||
{
|
return null;
|
||||||
var avatarData = await http.GetByteArrayAsync(avatarUrl);
|
|
||||||
using (var tempDraw = Image.Load<Rgba32>(avatarData))
|
|
||||||
{
|
|
||||||
tempDraw.Mutate(x => x
|
|
||||||
.Resize(template.User.Icon.Size.X, template.User.Icon.Size.Y)
|
|
||||||
.ApplyRoundedCorners(Math.Max(template.User.Icon.Size.X,
|
|
||||||
template.User.Icon.Size.Y)
|
|
||||||
/ 2.0f));
|
|
||||||
await using (var stream = await tempDraw.ToStreamAsync())
|
|
||||||
{
|
|
||||||
avatarImageData = stream.ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await _c.SetImageDataAsync(avatarUrl, avatarImageData);
|
var result = await _c.GetImageDataAsync(avatarUrl);
|
||||||
|
if (result.TryPickT0(out var imgData, out _))
|
||||||
|
return imgData;
|
||||||
|
|
||||||
|
using var http = _httpFactory.CreateClient();
|
||||||
|
|
||||||
|
var avatarData = await http.GetByteArrayAsync(avatarUrl);
|
||||||
|
using var tempDraw = Image.Load<Rgba32>(avatarData);
|
||||||
|
|
||||||
|
tempDraw.Mutate(x => x
|
||||||
|
.Resize(template.User.Icon.Size.X, template.User.Icon.Size.Y)
|
||||||
|
.ApplyRoundedCorners(Math.Max(template.User.Icon.Size.X,
|
||||||
|
template.User.Icon.Size.Y)
|
||||||
|
/ 2.0f));
|
||||||
|
await using var stream = await tempDraw.ToStreamAsync();
|
||||||
|
var data = stream.ToArray();
|
||||||
|
await _c.SetImageDataAsync(avatarUrl, data);
|
||||||
|
return data;
|
||||||
}
|
}
|
||||||
}
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
using var img = Image.Load<Rgba32>(bgBytes);
|
using var img = Image.Load<Rgba32>(bgBytes);
|
||||||
|
|
||||||
if (template.User.Name.Show)
|
|
||||||
|
img.Mutate(x =>
|
||||||
{
|
{
|
||||||
var fontSize = (int)(template.User.Name.FontSize * 0.9);
|
if (template.User.Name.Show)
|
||||||
var username = stats.User.ToString();
|
|
||||||
var usernameFont = _fonts.NotoSans.CreateFont(fontSize, FontStyle.Bold);
|
|
||||||
|
|
||||||
var size = TextMeasurer.MeasureSize($"@{username}", new(usernameFont));
|
|
||||||
var scale = 400f / size.Width;
|
|
||||||
if (scale < 1)
|
|
||||||
usernameFont = _fonts.NotoSans.CreateFont(template.User.Name.FontSize * scale, FontStyle.Bold);
|
|
||||||
|
|
||||||
img.Mutate(x =>
|
|
||||||
{
|
{
|
||||||
|
var fontSize = (int)(template.User.Name.FontSize * 0.9);
|
||||||
|
var username = stats.User.ToString();
|
||||||
|
var usernameFont = _fonts.NotoSans.CreateFont(fontSize, FontStyle.Bold);
|
||||||
|
|
||||||
|
var size = TextMeasurer.MeasureSize($"@{username}", new(usernameFont));
|
||||||
|
var scale = 400f / size.Width;
|
||||||
|
if (scale < 1)
|
||||||
|
usernameFont = _fonts.NotoSans.CreateFont(template.User.Name.FontSize * scale, FontStyle.Bold);
|
||||||
|
|
||||||
x.DrawText(new RichTextOptions(usernameFont)
|
x.DrawText(new RichTextOptions(usernameFont)
|
||||||
{
|
{
|
||||||
HorizontalAlignment = HorizontalAlignment.Left,
|
HorizontalAlignment = HorizontalAlignment.Left,
|
||||||
|
@ -666,126 +695,129 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
||||||
},
|
},
|
||||||
"@" + username,
|
"@" + username,
|
||||||
Brushes.Solid(template.User.Name.Color));
|
Brushes.Solid(template.User.Name.Color));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//club name
|
//club name
|
||||||
|
|
||||||
if (template.Club.Name.Show)
|
if (template.Club.Name.Show)
|
||||||
|
{
|
||||||
|
var clubName = stats.User.Club?.ToString() ?? "-";
|
||||||
|
|
||||||
|
var clubFont = _fonts.NotoSans.CreateFont(template.Club.Name.FontSize, FontStyle.Regular);
|
||||||
|
|
||||||
|
x.DrawText(new RichTextOptions(clubFont)
|
||||||
{
|
{
|
||||||
var clubName = stats.User.Club?.ToString() ?? "-";
|
HorizontalAlignment = HorizontalAlignment.Right,
|
||||||
|
VerticalAlignment = VerticalAlignment.Top,
|
||||||
|
FallbackFontFamilies = _fonts.FallBackFonts,
|
||||||
|
Origin = new(template.Club.Name.Pos.X + 50, template.Club.Name.Pos.Y - 8)
|
||||||
|
},
|
||||||
|
clubName,
|
||||||
|
Brushes.Solid(template.Club.Name.Color));
|
||||||
|
}
|
||||||
|
|
||||||
var clubFont = _fonts.NotoSans.CreateFont(template.Club.Name.FontSize, FontStyle.Regular);
|
Font GetTruncatedFont(
|
||||||
|
FontFamily fontFamily,
|
||||||
|
int fontSize,
|
||||||
|
FontStyle style,
|
||||||
|
string text,
|
||||||
|
int maxSize)
|
||||||
|
{
|
||||||
|
var font = fontFamily.CreateFont(fontSize, style);
|
||||||
|
var size = TextMeasurer.MeasureSize(text, new(font));
|
||||||
|
var scale = maxSize / size.Width;
|
||||||
|
if (scale < 1)
|
||||||
|
font = fontFamily.CreateFont(fontSize * scale, style);
|
||||||
|
|
||||||
x.DrawText(new RichTextOptions(clubFont)
|
return font;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (template.User.Level.Show)
|
||||||
|
{
|
||||||
|
var guildLevelFont = GetTruncatedFont(
|
||||||
|
_fonts.NotoSans,
|
||||||
|
template.User.Level.FontSize,
|
||||||
|
FontStyle.Bold,
|
||||||
|
stats.Guild.Level.ToString(),
|
||||||
|
33);
|
||||||
|
|
||||||
|
|
||||||
|
x.DrawText(stats.Guild.Level.ToString(),
|
||||||
|
guildLevelFont,
|
||||||
|
template.User.Level.Color,
|
||||||
|
new(template.User.Level.Pos.X, template.User.Level.Pos.Y));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var guild = stats.Guild;
|
||||||
|
|
||||||
|
//xp bar
|
||||||
|
if (template.User.Xp.Bar.Show)
|
||||||
|
{
|
||||||
|
var xpPercent = guild.LevelXp / (float)guild.RequiredXp;
|
||||||
|
DrawXpBar(xpPercent, template.User.Xp.Bar.Guild, img);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (template.User.Xp.Guild.Show)
|
||||||
|
{
|
||||||
|
x.DrawText(
|
||||||
|
new RichTextOptions(_fonts.NotoSans.CreateFont(template.User.Xp.Guild.FontSize,
|
||||||
|
FontStyle.Bold))
|
||||||
{
|
{
|
||||||
HorizontalAlignment = HorizontalAlignment.Right,
|
HorizontalAlignment = HorizontalAlignment.Center,
|
||||||
VerticalAlignment = VerticalAlignment.Top,
|
VerticalAlignment = VerticalAlignment.Center,
|
||||||
FallbackFontFamilies = _fonts.FallBackFonts,
|
Origin = new(template.User.Xp.Guild.Pos.X, template.User.Xp.Guild.Pos.Y)
|
||||||
Origin = new(template.Club.Name.Pos.X + 50, template.Club.Name.Pos.Y - 8)
|
|
||||||
},
|
},
|
||||||
clubName,
|
$"{guild.LevelXp}/{guild.RequiredXp}",
|
||||||
Brushes.Solid(template.Club.Name.Color));
|
Brushes.Solid(template.User.Xp.Guild.Color));
|
||||||
}
|
}
|
||||||
|
|
||||||
Font GetTruncatedFont(
|
var rankPen = new SolidPen(Color.White, 1);
|
||||||
FontFamily fontFamily,
|
//ranking
|
||||||
int fontSize,
|
|
||||||
FontStyle style,
|
|
||||||
string text,
|
|
||||||
int maxSize)
|
|
||||||
{
|
|
||||||
var font = fontFamily.CreateFont(fontSize, style);
|
|
||||||
var size = TextMeasurer.MeasureSize(text, new(font));
|
|
||||||
var scale = maxSize / size.Width;
|
|
||||||
if (scale < 1)
|
|
||||||
font = fontFamily.CreateFont(fontSize * scale, style);
|
|
||||||
|
|
||||||
return font;
|
if (template.User.Rank.Show)
|
||||||
}
|
{
|
||||||
|
var guildRankStr = stats.GuildRanking.ToString();
|
||||||
|
|
||||||
|
var guildRankFont = GetTruncatedFont(
|
||||||
|
_fonts.NotoSans,
|
||||||
|
template.User.Rank.FontSize,
|
||||||
|
FontStyle.Bold,
|
||||||
|
guildRankStr,
|
||||||
|
22);
|
||||||
|
|
||||||
if (template.User.Level.Show)
|
x.DrawText(
|
||||||
{
|
new RichTextOptions(guildRankFont)
|
||||||
var guildLevelFont = GetTruncatedFont(
|
|
||||||
_fonts.NotoSans,
|
|
||||||
template.User.Level.FontSize,
|
|
||||||
FontStyle.Bold,
|
|
||||||
stats.Guild.Level.ToString(),
|
|
||||||
33);
|
|
||||||
|
|
||||||
|
|
||||||
x.DrawText(stats.Guild.Level.ToString(),
|
|
||||||
guildLevelFont,
|
|
||||||
template.User.Level.Color,
|
|
||||||
new(template.User.Level.Pos.X, template.User.Level.Pos.Y));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
var guild = stats.Guild;
|
|
||||||
|
|
||||||
//xp bar
|
|
||||||
if (template.User.Xp.Bar.Show)
|
|
||||||
{
|
|
||||||
var xpPercent = guild.LevelXp / (float)guild.RequiredXp;
|
|
||||||
DrawXpBar(xpPercent, template.User.Xp.Bar.Guild, img);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (template.User.Xp.Guild.Show)
|
|
||||||
{
|
|
||||||
x.DrawText(
|
|
||||||
new RichTextOptions(_fonts.NotoSans.CreateFont(template.User.Xp.Guild.FontSize,
|
|
||||||
FontStyle.Bold))
|
|
||||||
{
|
|
||||||
HorizontalAlignment = HorizontalAlignment.Center,
|
|
||||||
VerticalAlignment = VerticalAlignment.Center,
|
|
||||||
Origin = new(template.User.Xp.Guild.Pos.X, template.User.Xp.Guild.Pos.Y)
|
|
||||||
},
|
|
||||||
$"{guild.LevelXp}/{guild.RequiredXp}",
|
|
||||||
Brushes.Solid(template.User.Xp.Guild.Color));
|
|
||||||
}
|
|
||||||
|
|
||||||
var rankPen = new SolidPen(Color.White, 1);
|
|
||||||
//ranking
|
|
||||||
|
|
||||||
if (template.User.Rank.Show)
|
|
||||||
{
|
|
||||||
var guildRankStr = stats.GuildRanking.ToString();
|
|
||||||
|
|
||||||
var guildRankFont = GetTruncatedFont(
|
|
||||||
_fonts.NotoSans,
|
|
||||||
template.User.Rank.FontSize,
|
|
||||||
FontStyle.Bold,
|
|
||||||
guildRankStr,
|
|
||||||
22);
|
|
||||||
|
|
||||||
x.DrawText(
|
|
||||||
new RichTextOptions(guildRankFont)
|
|
||||||
{
|
|
||||||
Origin = new(template.User.Rank.Pos.X, template.User.Rank.Pos.Y)
|
|
||||||
},
|
|
||||||
guildRankStr,
|
|
||||||
Brushes.Solid(template.User.Rank.Color),
|
|
||||||
rankPen
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (template.User.Icon.Show)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
using var toDraw = Image.Load(avatarImageData);
|
Origin = new(template.User.Rank.Pos.X, template.User.Rank.Pos.Y)
|
||||||
if (toDraw.Size != new Size(template.User.Icon.Size.X, template.User.Icon.Size.Y))
|
},
|
||||||
toDraw.Mutate(x
|
guildRankStr,
|
||||||
=> x.Resize(template.User.Icon.Size.X, template.User.Icon.Size.Y));
|
Brushes.Solid(template.User.Rank.Color),
|
||||||
|
rankPen
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
x.DrawImage(toDraw,
|
if (template.User.Icon.Show)
|
||||||
new Point(template.User.Icon.Pos.X, template.User.Icon.Pos.Y),
|
{
|
||||||
1);
|
var avImageData = await avatarFetchTask;
|
||||||
}
|
img.Mutate(mut =>
|
||||||
catch (Exception ex)
|
{
|
||||||
{
|
try
|
||||||
Log.Warning(ex, "Error drawing avatar image");
|
{
|
||||||
}
|
using var toDraw = Image.Load(avImageData);
|
||||||
|
if (toDraw.Size != new Size(template.User.Icon.Size.X, template.User.Icon.Size.Y))
|
||||||
|
toDraw.Mutate(x => x.Resize(template.User.Icon.Size.X, template.User.Icon.Size.Y));
|
||||||
|
|
||||||
|
mut.DrawImage(toDraw,
|
||||||
|
new Point(template.User.Icon.Pos.X, template.User.Icon.Pos.Y),
|
||||||
|
1);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Warning(ex, "Error drawing avatar image");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1626,4 +1626,10 @@ scheduledelete:
|
||||||
scheduleadd:
|
scheduleadd:
|
||||||
- scheduleadd
|
- scheduleadd
|
||||||
- scha
|
- scha
|
||||||
- schadd
|
- schadd
|
||||||
|
xpexclusion:
|
||||||
|
- xpexclusion
|
||||||
|
- xpexl
|
||||||
|
xpexclude:
|
||||||
|
- xpexclude
|
||||||
|
- xpex
|
|
@ -1854,7 +1854,7 @@ playlistload:
|
||||||
- 5
|
- 5
|
||||||
params:
|
params:
|
||||||
- id:
|
- id:
|
||||||
desc: "The id of the playlist to be loaded."
|
desc: "The id of the playlist to be loaded."
|
||||||
playlistsave:
|
playlistsave:
|
||||||
desc: Saves a playlist under a certain name. Playlist name must be no longer than 20 characters and must not contain dashes.
|
desc: Saves a playlist under a certain name. Playlist name must be no longer than 20 characters and must not contain dashes.
|
||||||
ex:
|
ex:
|
||||||
|
@ -5100,4 +5100,25 @@ scheduleadd:
|
||||||
- time:
|
- time:
|
||||||
desc: "How long it takes for the command to execute. Example: 1h30m = 1 hour and 30 minutes"
|
desc: "How long it takes for the command to execute. Example: 1h30m = 1 hour and 30 minutes"
|
||||||
- command:
|
- command:
|
||||||
desc: "Command that will be executed after the specified time has elapsed"
|
desc: "Command that will be executed after the specified time has elapsed"
|
||||||
|
xpexclusion:
|
||||||
|
desc: |-
|
||||||
|
Shows a list of all XP exclusions in the server.
|
||||||
|
ex:
|
||||||
|
- ''
|
||||||
|
params:
|
||||||
|
- { }
|
||||||
|
xpexclude:
|
||||||
|
desc: |-
|
||||||
|
Toggles XP gain exclusion for a specified item.
|
||||||
|
Item types can be Role or User.
|
||||||
|
ex:
|
||||||
|
- '@CoolRole'
|
||||||
|
- '@User'
|
||||||
|
- 'role 123123123'
|
||||||
|
- 'user 123123123'
|
||||||
|
params:
|
||||||
|
- type:
|
||||||
|
desc: "Type of the item to exclude: role or user"
|
||||||
|
itemId:
|
||||||
|
desc: "ID or mention of the item to exclude."
|
|
@ -1217,5 +1217,9 @@
|
||||||
"schedule_command": "Command",
|
"schedule_command": "Command",
|
||||||
"schedule_when": "Executes At",
|
"schedule_when": "Executes At",
|
||||||
"schedule_add_success": "Scheduled command successfully added.",
|
"schedule_add_success": "Scheduled command successfully added.",
|
||||||
"schedule_add_error": "You already have 5 scheduled commands. Please delete some before adding more."
|
"schedule_add_error": "You already have 5 scheduled commands. Please delete some before adding more.",
|
||||||
|
"xp_exclusion_none": "There are no exclusions set for this server.",
|
||||||
|
"xp_exclusion_title": "XP Exclusions",
|
||||||
|
"xp_exclude_added": "{0}: {1} has been excluded from the XP system.",
|
||||||
|
"xp_exclude_removed": "{0}: {1} is no longer excluded from the XP system."
|
||||||
}
|
}
|
Loading…
Add table
Reference in a new issue