diff --git a/src/EllieBot/Migrations/PostgreSql/20250225212147_xp-excl-xp-rate.sql b/src/EllieBot/Migrations/PostgreSql/20250225212147_xp-excl-xp-rate.sql new file mode 100644 index 0000000..16e3c9f --- /dev/null +++ b/src/EllieBot/Migrations/PostgreSql/20250225212147_xp-excl-xp-rate.sql @@ -0,0 +1,27 @@ +START TRANSACTION; +ALTER TABLE userfishstats ADD bait integer; + +ALTER TABLE userfishstats ADD pole integer; + +CREATE TABLE channelxpconfig ( + id integer GENERATED BY DEFAULT AS IDENTITY, + guildid numeric(20,0) NOT NULL, + channelid numeric(20,0) NOT NULL, + xpamount integer NOT NULL, + cooldown real NOT NULL, + CONSTRAINT pk_channelxpconfig PRIMARY KEY (id), + CONSTRAINT ak_channelxpconfig_guildid_channelid UNIQUE (guildid, channelid) +); + +CREATE TABLE guildxpconfig ( + guildid numeric(20,0) NOT NULL, + xpamount integer NOT NULL, + cooldown integer NOT NULL, + xptemplateurl text, + CONSTRAINT pk_guildxpconfig PRIMARY KEY (guildid) +); + +INSERT INTO "__EFMigrationsHistory" (migrationid, productversion) +VALUES ('20250225212147_xp-excl-xp-rate', '9.0.1'); + +COMMIT; \ No newline at end of file diff --git a/src/EllieBot/Migrations/PostgreSql/20250202124905_init.Designer.cs b/src/EllieBot/Migrations/PostgreSql/20250225212209_init.Designer.cs similarity index 98% rename from src/EllieBot/Migrations/PostgreSql/20250202124905_init.Designer.cs rename to src/EllieBot/Migrations/PostgreSql/20250225212209_init.Designer.cs index b329303..66f7870 100644 --- a/src/EllieBot/Migrations/PostgreSql/20250202124905_init.Designer.cs +++ b/src/EllieBot/Migrations/PostgreSql/20250225212209_init.Designer.cs @@ -12,7 +12,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace EllieBot.Migrations.PostgreSql { [DbContext(typeof(PostgreSqlContext))] - [Migration("20250202124905_init")] + [Migration("20250225212209_init")] partial class init { /// <inheritdoc /> @@ -3456,6 +3456,14 @@ namespace EllieBot.Migrations.PostgreSql NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id")); + b.Property<int?>("Bait") + .HasColumnType("integer") + .HasColumnName("bait"); + + b.Property<int?>("Pole") + .HasColumnType("integer") + .HasColumnName("pole"); + b.Property<int>("Skill") .HasColumnType("integer") .HasColumnName("skill"); @@ -3474,6 +3482,65 @@ namespace EllieBot.Migrations.PostgreSql b.ToTable("userfishstats", (string)null); }); + modelBuilder.Entity("EllieBot.Modules.Xp.ChannelXpConfig", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id")); + + b.Property<decimal>("ChannelId") + .HasColumnType("numeric(20,0)") + .HasColumnName("channelid"); + + b.Property<float>("Cooldown") + .HasColumnType("real") + .HasColumnName("cooldown"); + + b.Property<decimal>("GuildId") + .HasColumnType("numeric(20,0)") + .HasColumnName("guildid"); + + b.Property<int>("XpAmount") + .HasColumnType("integer") + .HasColumnName("xpamount"); + + b.HasKey("Id") + .HasName("pk_channelxpconfig"); + + b.HasAlternateKey("GuildId", "ChannelId") + .HasName("ak_channelxpconfig_guildid_channelid"); + + b.ToTable("channelxpconfig", (string)null); + }); + + modelBuilder.Entity("EllieBot.Modules.Xp.GuildXpConfig", b => + { + b.Property<decimal>("GuildId") + .ValueGeneratedOnAdd() + .HasColumnType("numeric(20,0)") + .HasColumnName("guildid"); + + b.Property<int>("Cooldown") + .HasColumnType("integer") + .HasColumnName("cooldown"); + + b.Property<int>("XpAmount") + .HasColumnType("integer") + .HasColumnName("xpamount"); + + b.Property<string>("XpTemplateUrl") + .HasColumnType("text") + .HasColumnName("xptemplateurl"); + + b.HasKey("GuildId") + .HasName("pk_guildxpconfig"); + + b.ToTable("guildxpconfig", (string)null); + }); + modelBuilder.Entity("EllieBot.Services.GreetSettings", b => { b.Property<int>("Id") @@ -3978,4 +4045,4 @@ namespace EllieBot.Migrations.PostgreSql #pragma warning restore 612, 618 } } -} +} \ No newline at end of file diff --git a/src/EllieBot/Migrations/PostgreSql/20250202124905_init.cs b/src/EllieBot/Migrations/PostgreSql/20250225212209_init.cs similarity index 98% rename from src/EllieBot/Migrations/PostgreSql/20250202124905_init.cs rename to src/EllieBot/Migrations/PostgreSql/20250225212209_init.cs index 6e4032b..bf7ff89 100644 --- a/src/EllieBot/Migrations/PostgreSql/20250202124905_init.cs +++ b/src/EllieBot/Migrations/PostgreSql/20250225212209_init.cs @@ -187,6 +187,23 @@ namespace EllieBot.Migrations.PostgreSql table.UniqueConstraint("ak_buttonrole_roleid_messageid", x => new { x.roleid, x.messageid }); }); + migrationBuilder.CreateTable( + name: "channelxpconfig", + 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), + channelid = table.Column<decimal>(type: "numeric(20,0)", nullable: false), + xpamount = table.Column<int>(type: "integer", nullable: false), + cooldown = table.Column<float>(type: "real", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_channelxpconfig", x => x.id); + table.UniqueConstraint("ak_channelxpconfig_guildid_channelid", x => new { x.guildid, x.channelid }); + }); + migrationBuilder.CreateTable( name: "commandalias", columns: table => new @@ -487,6 +504,20 @@ namespace EllieBot.Migrations.PostgreSql table.PrimaryKey("pk_guildfilterconfig", x => x.id); }); + migrationBuilder.CreateTable( + name: "guildxpconfig", + columns: table => new + { + guildid = table.Column<decimal>(type: "numeric(20,0)", nullable: false), + xpamount = table.Column<int>(type: "integer", nullable: false), + cooldown = table.Column<int>(type: "integer", nullable: false), + xptemplateurl = table.Column<string>(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_guildxpconfig", x => x.guildid); + }); + migrationBuilder.CreateTable( name: "honeypotchannels", columns: table => new @@ -1033,7 +1064,9 @@ namespace EllieBot.Migrations.PostgreSql id = table.Column<int>(type: "integer", nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), userid = table.Column<decimal>(type: "numeric(20,0)", nullable: false), - skill = table.Column<int>(type: "integer", nullable: false) + skill = table.Column<int>(type: "integer", nullable: false), + pole = table.Column<int>(type: "integer", nullable: true), + bait = table.Column<int>(type: "integer", nullable: true) }, constraints: table => { @@ -2310,6 +2343,9 @@ namespace EllieBot.Migrations.PostgreSql migrationBuilder.DropTable( name: "buttonrole"); + migrationBuilder.DropTable( + name: "channelxpconfig"); + migrationBuilder.DropTable( name: "clubapplicants"); @@ -2376,6 +2412,9 @@ namespace EllieBot.Migrations.PostgreSql migrationBuilder.DropTable( name: "guildcolors"); + migrationBuilder.DropTable( + name: "guildxpconfig"); + migrationBuilder.DropTable( name: "honeypotchannels"); @@ -2551,4 +2590,4 @@ namespace EllieBot.Migrations.PostgreSql name: "discorduser"); } } -} +} \ No newline at end of file diff --git a/src/EllieBot/Migrations/PostgreSql/PostgreSqlContextModelSnapshot.cs b/src/EllieBot/Migrations/PostgreSql/PostgreSqlContextModelSnapshot.cs index 5d110ab..d58940a 100644 --- a/src/EllieBot/Migrations/PostgreSql/PostgreSqlContextModelSnapshot.cs +++ b/src/EllieBot/Migrations/PostgreSql/PostgreSqlContextModelSnapshot.cs @@ -3453,6 +3453,14 @@ namespace EllieBot.Migrations.PostgreSql NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id")); + b.Property<int?>("Bait") + .HasColumnType("integer") + .HasColumnName("bait"); + + b.Property<int?>("Pole") + .HasColumnType("integer") + .HasColumnName("pole"); + b.Property<int>("Skill") .HasColumnType("integer") .HasColumnName("skill"); @@ -3471,6 +3479,65 @@ namespace EllieBot.Migrations.PostgreSql b.ToTable("userfishstats", (string)null); }); + modelBuilder.Entity("EllieBot.Modules.Xp.ChannelXpConfig", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id")); + + b.Property<decimal>("ChannelId") + .HasColumnType("numeric(20,0)") + .HasColumnName("channelid"); + + b.Property<float>("Cooldown") + .HasColumnType("real") + .HasColumnName("cooldown"); + + b.Property<decimal>("GuildId") + .HasColumnType("numeric(20,0)") + .HasColumnName("guildid"); + + b.Property<int>("XpAmount") + .HasColumnType("integer") + .HasColumnName("xpamount"); + + b.HasKey("Id") + .HasName("pk_channelxpconfig"); + + b.HasAlternateKey("GuildId", "ChannelId") + .HasName("ak_channelxpconfig_guildid_channelid"); + + b.ToTable("channelxpconfig", (string)null); + }); + + modelBuilder.Entity("EllieBot.Modules.Xp.GuildXpConfig", b => + { + b.Property<decimal>("GuildId") + .ValueGeneratedOnAdd() + .HasColumnType("numeric(20,0)") + .HasColumnName("guildid"); + + b.Property<int>("Cooldown") + .HasColumnType("integer") + .HasColumnName("cooldown"); + + b.Property<int>("XpAmount") + .HasColumnType("integer") + .HasColumnName("xpamount"); + + b.Property<string>("XpTemplateUrl") + .HasColumnType("text") + .HasColumnName("xptemplateurl"); + + b.HasKey("GuildId") + .HasName("pk_guildxpconfig"); + + b.ToTable("guildxpconfig", (string)null); + }); + modelBuilder.Entity("EllieBot.Services.GreetSettings", b => { b.Property<int>("Id") @@ -3975,4 +4042,4 @@ namespace EllieBot.Migrations.PostgreSql #pragma warning restore 612, 618 } } -} +} \ No newline at end of file diff --git a/src/EllieBot/Migrations/Sqlite/20250225212144_xp-excl-xp-rate.sql b/src/EllieBot/Migrations/Sqlite/20250225212144_xp-excl-xp-rate.sql new file mode 100644 index 0000000..26ee2fa --- /dev/null +++ b/src/EllieBot/Migrations/Sqlite/20250225212144_xp-excl-xp-rate.sql @@ -0,0 +1,25 @@ +BEGIN TRANSACTION; +ALTER TABLE "UserFishStats" ADD "Bait" INTEGER NULL; + +ALTER TABLE "UserFishStats" ADD "Pole" INTEGER NULL; + +CREATE TABLE "ChannelXpConfig" ( + "Id" INTEGER NOT NULL CONSTRAINT "PK_ChannelXpConfig" PRIMARY KEY AUTOINCREMENT, + "GuildId" INTEGER NOT NULL, + "ChannelId" INTEGER NOT NULL, + "XpAmount" INTEGER NOT NULL, + "Cooldown" REAL NOT NULL, + CONSTRAINT "AK_ChannelXpConfig_GuildId_ChannelId" UNIQUE ("GuildId", "ChannelId") +); + +CREATE TABLE "GuildXpConfig" ( + "GuildId" INTEGER NOT NULL CONSTRAINT "PK_GuildXpConfig" PRIMARY KEY AUTOINCREMENT, + "XpAmount" INTEGER NOT NULL, + "Cooldown" INTEGER NOT NULL, + "XpTemplateUrl" TEXT NULL +); + +INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") +VALUES ('20250225212144_xp-excl-xp-rate', '9.0.1'); + +COMMIT; \ No newline at end of file diff --git a/src/EllieBot/Migrations/Sqlite/20250202124903_init.Designer.cs b/src/EllieBot/Migrations/Sqlite/20250225212206_init.Designer.cs similarity index 98% rename from src/EllieBot/Migrations/Sqlite/20250202124903_init.Designer.cs rename to src/EllieBot/Migrations/Sqlite/20250225212206_init.Designer.cs index ffb15cb..483367d 100644 --- a/src/EllieBot/Migrations/Sqlite/20250202124903_init.Designer.cs +++ b/src/EllieBot/Migrations/Sqlite/20250225212206_init.Designer.cs @@ -11,7 +11,7 @@ using EllieBot.Db; namespace EllieBot.Migrations.Sqlite { [DbContext(typeof(SqliteContext))] - [Migration("20250202124903_init")] + [Migration("20250225212206_init")] partial class init { /// <inheritdoc /> @@ -2571,6 +2571,12 @@ namespace EllieBot.Migrations.Sqlite .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); + b.Property<int?>("Bait") + .HasColumnType("INTEGER"); + + b.Property<int?>("Pole") + .HasColumnType("INTEGER"); + b.Property<int>("Skill") .HasColumnType("INTEGER"); @@ -2585,6 +2591,51 @@ namespace EllieBot.Migrations.Sqlite b.ToTable("UserFishStats"); }); + modelBuilder.Entity("EllieBot.Modules.Xp.ChannelXpConfig", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property<ulong>("ChannelId") + .HasColumnType("INTEGER"); + + b.Property<float>("Cooldown") + .HasColumnType("REAL"); + + b.Property<ulong>("GuildId") + .HasColumnType("INTEGER"); + + b.Property<int>("XpAmount") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasAlternateKey("GuildId", "ChannelId"); + + b.ToTable("ChannelXpConfig"); + }); + + modelBuilder.Entity("EllieBot.Modules.Xp.GuildXpConfig", b => + { + b.Property<ulong>("GuildId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property<int>("Cooldown") + .HasColumnType("INTEGER"); + + b.Property<int>("XpAmount") + .HasColumnType("INTEGER"); + + b.Property<string>("XpTemplateUrl") + .HasColumnType("TEXT"); + + b.HasKey("GuildId"); + + b.ToTable("GuildXpConfig"); + }); + modelBuilder.Entity("EllieBot.Services.GreetSettings", b => { b.Property<int>("Id") @@ -3030,4 +3081,4 @@ namespace EllieBot.Migrations.Sqlite #pragma warning restore 612, 618 } } -} +} \ No newline at end of file diff --git a/src/EllieBot/Migrations/Sqlite/20250202124903_init.cs b/src/EllieBot/Migrations/Sqlite/20250225212206_init.cs similarity index 98% rename from src/EllieBot/Migrations/Sqlite/20250202124903_init.cs rename to src/EllieBot/Migrations/Sqlite/20250225212206_init.cs index 1f7ecdc..c18b049 100644 --- a/src/EllieBot/Migrations/Sqlite/20250202124903_init.cs +++ b/src/EllieBot/Migrations/Sqlite/20250225212206_init.cs @@ -186,6 +186,23 @@ namespace EllieBot.Migrations.Sqlite table.UniqueConstraint("AK_ButtonRole_RoleId_MessageId", x => new { x.RoleId, x.MessageId }); }); + migrationBuilder.CreateTable( + name: "ChannelXpConfig", + columns: table => new + { + Id = table.Column<int>(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + GuildId = table.Column<ulong>(type: "INTEGER", nullable: false), + ChannelId = table.Column<ulong>(type: "INTEGER", nullable: false), + XpAmount = table.Column<int>(type: "INTEGER", nullable: false), + Cooldown = table.Column<float>(type: "REAL", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ChannelXpConfig", x => x.Id); + table.UniqueConstraint("AK_ChannelXpConfig_GuildId_ChannelId", x => new { x.GuildId, x.ChannelId }); + }); + migrationBuilder.CreateTable( name: "CommandAlias", columns: table => new @@ -486,6 +503,21 @@ namespace EllieBot.Migrations.Sqlite table.PrimaryKey("PK_GuildFilterConfig", x => x.Id); }); + migrationBuilder.CreateTable( + name: "GuildXpConfig", + columns: table => new + { + GuildId = table.Column<ulong>(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + XpAmount = table.Column<int>(type: "INTEGER", nullable: false), + Cooldown = table.Column<int>(type: "INTEGER", nullable: false), + XpTemplateUrl = table.Column<string>(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_GuildXpConfig", x => x.GuildId); + }); + migrationBuilder.CreateTable( name: "HoneyPotChannels", columns: table => new @@ -1035,7 +1067,9 @@ namespace EllieBot.Migrations.Sqlite Id = table.Column<int>(type: "INTEGER", nullable: false) .Annotation("Sqlite:Autoincrement", true), UserId = table.Column<ulong>(type: "INTEGER", nullable: false), - Skill = table.Column<int>(type: "INTEGER", nullable: false) + Skill = table.Column<int>(type: "INTEGER", nullable: false), + Pole = table.Column<int>(type: "INTEGER", nullable: true), + Bait = table.Column<int>(type: "INTEGER", nullable: true) }, constraints: table => { @@ -2312,6 +2346,9 @@ namespace EllieBot.Migrations.Sqlite migrationBuilder.DropTable( name: "ButtonRole"); + migrationBuilder.DropTable( + name: "ChannelXpConfig"); + migrationBuilder.DropTable( name: "ClubApplicants"); @@ -2378,6 +2415,9 @@ namespace EllieBot.Migrations.Sqlite migrationBuilder.DropTable( name: "GuildColors"); + migrationBuilder.DropTable( + name: "GuildXpConfig"); + migrationBuilder.DropTable( name: "HoneyPotChannels"); @@ -2553,4 +2593,4 @@ namespace EllieBot.Migrations.Sqlite name: "DiscordUser"); } } -} +} \ No newline at end of file diff --git a/src/EllieBot/Migrations/Sqlite/SqliteContextModelSnapshot.cs b/src/EllieBot/Migrations/Sqlite/SqliteContextModelSnapshot.cs index 084a0ee..1b4a98c 100644 --- a/src/EllieBot/Migrations/Sqlite/SqliteContextModelSnapshot.cs +++ b/src/EllieBot/Migrations/Sqlite/SqliteContextModelSnapshot.cs @@ -2568,6 +2568,12 @@ namespace EllieBot.Migrations.Sqlite .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); + b.Property<int?>("Bait") + .HasColumnType("INTEGER"); + + b.Property<int?>("Pole") + .HasColumnType("INTEGER"); + b.Property<int>("Skill") .HasColumnType("INTEGER"); @@ -2582,6 +2588,51 @@ namespace EllieBot.Migrations.Sqlite b.ToTable("UserFishStats"); }); + modelBuilder.Entity("EllieBot.Modules.Xp.ChannelXpConfig", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property<ulong>("ChannelId") + .HasColumnType("INTEGER"); + + b.Property<float>("Cooldown") + .HasColumnType("REAL"); + + b.Property<ulong>("GuildId") + .HasColumnType("INTEGER"); + + b.Property<int>("XpAmount") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasAlternateKey("GuildId", "ChannelId"); + + b.ToTable("ChannelXpConfig"); + }); + + modelBuilder.Entity("EllieBot.Modules.Xp.GuildXpConfig", b => + { + b.Property<ulong>("GuildId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property<int>("Cooldown") + .HasColumnType("INTEGER"); + + b.Property<int>("XpAmount") + .HasColumnType("INTEGER"); + + b.Property<string>("XpTemplateUrl") + .HasColumnType("TEXT"); + + b.HasKey("GuildId"); + + b.ToTable("GuildXpConfig"); + }); + modelBuilder.Entity("EllieBot.Services.GreetSettings", b => { b.Property<int>("Id") @@ -3027,4 +3078,4 @@ namespace EllieBot.Migrations.Sqlite #pragma warning restore 612, 618 } } -} +} \ No newline at end of file diff --git a/src/EllieBot/Modules/Xp/Xp.cs b/src/EllieBot/Modules/Xp/Xp.cs index 3e6801a..12b9ea5 100644 --- a/src/EllieBot/Modules/Xp/Xp.cs +++ b/src/EllieBot/Modules/Xp/Xp.cs @@ -53,91 +53,6 @@ public partial class Xp : EllieModule<XpService> } } - [Cmd] - [RequireContext(ContextType.Guild)] - [UserPerm(GuildPerm.Administrator)] - public async Task XpExclude(Server _) - { - var ex = await _service.ToggleExcludeServerAsync(ctx.Guild.Id); - - if (ex) - await Response().Confirm(strs.excluded(Format.Bold(ctx.Guild.ToString()))).SendAsync(); - else - await Response().Confirm(strs.not_excluded(Format.Bold(ctx.Guild.ToString()))).SendAsync(); - } - - [Cmd] - [UserPerm(GuildPerm.ManageRoles)] - [RequireContext(ContextType.Guild)] - public async Task XpExclude(Role _, [Leftover] IRole role) - { - var ex = await _service.ToggleExcludeRoleAsync(ctx.Guild.Id, role.Id); - - if (ex) - await Response().Confirm(strs.excluded(Format.Bold(role.ToString()))).SendAsync(); - else - await Response().Confirm(strs.not_excluded(Format.Bold(role.ToString()))).SendAsync(); - } - - [Cmd] - [UserPerm(GuildPerm.ManageChannels)] - [RequireContext(ContextType.Guild)] - public async Task XpExclude(Channel _, [Leftover] IChannel? channel = null) - { - if (channel is null) - channel = ctx.Channel; - - var ex = await _service.ToggleExcludeChannelAsync(ctx.Guild.Id, channel.Id); - - if (ex) - await Response().Confirm(strs.excluded(Format.Bold(channel.ToString()))).SendAsync(); - else - await Response().Confirm(strs.not_excluded(Format.Bold(channel.ToString()))).SendAsync(); - } - - [Cmd] - [RequireContext(ContextType.Guild)] - public async Task XpExclusionList() - { - var serverExcluded = _service.IsServerExcluded(ctx.Guild.Id); - var roles = _service.GetExcludedRoles(ctx.Guild.Id) - .Select(x => ctx.Guild.GetRole(x)) - .Where(x => x is not null) - .Select(x => $"`role` {x.Mention}") - .ToList(); - - var chans = (await _service.GetExcludedChannels(ctx.Guild.Id) - .Select(x => ctx.Guild.GetChannelAsync(x)) - .WhenAll()).Where(x => x is not null) - .Select(x => $"`channel` <#{x.Id}>") - .ToList(); - - var rolesStr = roles.Any() ? string.Join("\n", roles) + "\n" : string.Empty; - var chansStr = chans.Count > 0 ? string.Join("\n", chans) + "\n" : string.Empty; - var desc = Format.Code(serverExcluded - ? GetText(strs.server_is_excluded) - : GetText(strs.server_is_not_excluded)); - - desc += "\n\n" + rolesStr + chansStr; - - var lines = desc.Split('\n'); - await Response() - .Paginated() - .Items(lines) - .PageSize(15) - .CurrentPage(0) - .Page((items, _) => - { - var embed = CreateEmbed() - .WithTitle(GetText(strs.exclusion_list)) - .WithDescription(string.Join('\n', items)) - .WithOkColor(); - - return embed; - }) - .SendAsync(); - } - [Cmd] [EllieOptions<LbOpts>] [Priority(0)] diff --git a/src/EllieBot/Modules/Xp/XpRate/XpRateCommands.cs b/src/EllieBot/Modules/Xp/XpRate/XpRateCommands.cs new file mode 100644 index 0000000..c60e15e --- /dev/null +++ b/src/EllieBot/Modules/Xp/XpRate/XpRateCommands.cs @@ -0,0 +1,254 @@ +using LinqToDB; +using LinqToDB.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using EllieBot.Common.ModuleBehaviors; +using System.ComponentModel.DataAnnotations; + +namespace EllieBot.Modules.Xp; + +public partial class Xp +{ + [RequireUserPermission(GuildPermission.ManageGuild)] + public class XpRateCommands : EllieModule<GuildConfigXpService> + { + [Cmd] + [RequireContext(ContextType.Guild)] + public async Task XpRate() + { + var rates = await _service.GetGuildXpRatesAsync(ctx.Guild.Id); + if (rates.GuildConfig is null && !rates.ChannelRates.Any()) + { + await Response().Pending(strs.xp_rate_none).SendAsync(); + return; + } + + var eb = CreateEmbed() + .WithOkColor(); + if (rates.GuildConfig is not null) + { + eb.AddField(GetText(strs.xp_rate_server), + strs.xp_rate_amount_cooldown( + rates.GuildConfig.XpAmount, + rates.GuildConfig.Cooldown)); + } + + if (rates.ChannelRates.Any()) + { + var channelRates = rates.ChannelRates + .Select(c => $"<#{c.ChannelId}>: {GetRateString(c.XpAmount, c.Cooldown)}") + .Join('\n'); + + eb.AddField(GetText(strs.xp_rate_channels), channelRates); + } + + await Response().Embed(eb).SendAsync(); + } + + private string GetRateString(int argXpAmount, float cd) + { + if (argXpAmount == 0 || cd == 0) + return GetText(strs.xp_rate_no_gain); + + return GetText(strs.xp_rate_amount_cooldown(argXpAmount, Math.Round(cd, 1).ToString(Culture))); + } + + [Cmd] + [RequireContext(ContextType.Guild)] + public async Task XpRate(int amount, float minutes) + { + if (amount is < 0 or > 1000) + { + await Response().Error(strs.xp_rate_amount_invalid).SendAsync(); + return; + } + + if (minutes is < 0 or > 1440) + { + await Response().Error(strs.xp_rate_cooldown_invalid).SendAsync(); + return; + } + + await _service.SetGuildXpRateAsync(ctx.Guild.Id, amount, (int)Math.Ceiling(minutes)); + await Response().Confirm(strs.xp_rate_server_set(amount, minutes)).SendAsync(); + } + + [Cmd] + [RequireContext(ContextType.Guild)] + public async Task XpRate(IMessageChannel channel, int amount, float minutes) + { + if (amount is < 0 or > 1000) + { + await Response().Error(strs.xp_rate_amount_invalid).SendAsync(); + return; + } + + if (minutes is < 0 or > 1440) + { + await Response().Error(strs.xp_rate_cooldown_invalid).SendAsync(); + return; + } + + await _service.SetChannelXpRateAsync(ctx.Guild.Id, channel.Id, amount, (int)Math.Ceiling(minutes)); + await Response() + .Confirm(strs.xp_rate_channel_set(Format.Bold(channel.ToString()), amount, minutes)) + .SendAsync(); + } + + [Cmd] + [RequireContext(ContextType.Guild)] + public async Task XpRateReset() + { + await _service.ResetGuildXpRateAsync(ctx.Guild.Id); + await Response().Confirm(strs.xp_rate_server_reset).SendAsync(); + } + + [Cmd] + [RequireContext(ContextType.Guild)] + public async Task XpRateReset(IMessageChannel channel) + => await XpRateReset(channel.Id); + + [Cmd] + [RequireContext(ContextType.Guild)] + public async Task XpRateReset(ulong channelId) + { + await _service.ResetChannelXpRateAsync(ctx.Guild.Id, channelId); + await Response().Confirm(strs.xp_rate_channel_reset($"<#{channelId}>")).SendAsync(); + } + } +} + +public class GuildConfigXpService : IReadyExecutor, IEService +{ + private readonly DbService _db; + + public GuildConfigXpService(DbService db) + { + _db = db; + } + + public async Task<(GuildXpConfig? GuildConfig, List<ChannelXpConfig> ChannelRates)> GetGuildXpRatesAsync( + ulong guildId) + { + await using var uow = _db.GetDbContext(); + var guildConfig = + await AsyncExtensions.FirstOrDefaultAsync(uow.GetTable<GuildXpConfig>(), x => x.GuildId == guildId); + + var channelRates = await AsyncExtensions.ToListAsync(uow.GetTable<ChannelXpConfig>() + .Where(x => x.GuildId == guildId)); + + return (guildConfig, channelRates); + } + + public async Task SetGuildXpRateAsync(ulong guildId, int amount, int cooldown) + { + await using var uow = _db.GetDbContext(); + await uow.GetTable<GuildXpConfig>() + .InsertOrUpdateAsync(() => new() + { + GuildId = guildId, + XpAmount = amount, + Cooldown = cooldown + }, + (_) => new() + { + Cooldown = cooldown, + XpAmount = amount, + GuildId = guildId + }, + () => new() + { + GuildId = guildId + }); + } + + public async Task SetChannelXpRateAsync( + ulong guildId, + ulong channelId, + int amount, + int cooldown) + { + await using var uow = _db.GetDbContext(); + await uow.GetTable<ChannelXpConfig>() + .InsertOrUpdateAsync(() => new() + { + GuildId = guildId, + ChannelId = channelId, + XpAmount = amount, + Cooldown = cooldown + }, + (_) => new() + { + Cooldown = cooldown, + XpAmount = amount, + GuildId = guildId, + ChannelId = channelId + }, + () => new() + { + GuildId = guildId, + ChannelId = channelId + }); + } + + public async Task<bool> ResetGuildXpRateAsync(ulong guildId) + { + await using var uow = _db.GetDbContext(); + var deleted = await uow.GetTable<GuildXpConfig>() + .Where(x => x.GuildId == guildId) + .DeleteAsync(); + return deleted > 0; + } + + public async Task<bool> ResetChannelXpRateAsync(ulong guildId, ulong channelId) + { + await using var uow = _db.GetDbContext(); + var deleted = await uow.GetTable<ChannelXpConfig>() + .Where(x => x.GuildId == guildId && x.ChannelId == channelId) + .DeleteAsync(); + return deleted > 0; + } + + public Task OnReadyAsync() + => Task.CompletedTask; +} + +public class GuildXpConfig +{ + [Key] + public ulong GuildId { get; set; } + + public int XpAmount { get; set; } + public int Cooldown { get; set; } + public string? XpTemplateUrl { get; set; } +} + +public sealed class GuildXpConfigEntity : IEntityTypeConfiguration<GuildXpConfig> +{ + public void Configure(EntityTypeBuilder<GuildXpConfig> builder) + { + } +} + +public class ChannelXpConfig +{ + [Key] + public int Id { get; set; } + + public ulong GuildId { get; set; } + public ulong ChannelId { get; set; } + public int XpAmount { get; set; } + public float Cooldown { get; set; } +} + +public sealed class ChannelXpConfigEntity : IEntityTypeConfiguration<ChannelXpConfig> +{ + public void Configure(EntityTypeBuilder<ChannelXpConfig> builder) + { + builder.HasAlternateKey(x => new + { + x.GuildId, + x.ChannelId + }); + } +} \ No newline at end of file diff --git a/src/EllieBot/strings/aliases.yml b/src/EllieBot/strings/aliases.yml index 77e41e6..3e76803 100644 --- a/src/EllieBot/strings/aliases.yml +++ b/src/EllieBot/strings/aliases.yml @@ -1091,12 +1091,6 @@ experience: xptemplatereload: - xptempreload - xptr -xpexclusionlist: - - xpexclusionlist - - xpexl -xpexclude: - - xpexclude - - xpex xpleveluprewards: - xplvluprewards - xprews @@ -1581,4 +1575,8 @@ fishlist: fishspot: - fishspot - fisp - - fish? \ No newline at end of file + - fish? +xprate: + - xprate +xpratereset: + - xpratereset \ No newline at end of file diff --git a/src/EllieBot/strings/commands/commands.en-US.yml b/src/EllieBot/strings/commands/commands.en-US.yml index a390f53..cb691ca 100644 --- a/src/EllieBot/strings/commands/commands.en-US.yml +++ b/src/EllieBot/strings/commands/commands.en-US.yml @@ -3517,28 +3517,6 @@ xptemplatereload: - '' params: - { } -xpexclusionlist: - desc: Shows the roles and channels excluded from the XP system on this server, as well as whether the whole server is excluded. - ex: - - '' - params: - - { } -xpexclude: - desc: Exclude a channel, role or current server from the xp system. - ex: - - Role Excluded-Role - - Server - params: - - _: - desc: "The ID of the server to exclude from the XP system." - - _: - desc: "The role that should not receive XP rewards." - role: - desc: "The role that should not receive XP rewards." - - _: - desc: "The ID of the channel to exclude from XP tracking." - channel: - desc: "The ID of the channel to exclude from XP tracking." xpleveluprewards: desc: Shows currently set level up rewards. ex: @@ -4953,4 +4931,35 @@ fishspot: ex: - '' params: - - { } \ No newline at end of file + - { } +xprate: + desc: |- + Sets the xp rate for the server or the specified channel. + First specify the amount, and then the cooldown in minutes. + Provide no parameters to see the current rates. + ex: + - '' + - '3 5' + - '#channel 50 1' + params: + - { } + - amount: + desc: "The amount of xp to give per message." + minutes: + desc: "The cooldown in minutes. Allows decimal values." + - channel: + desc: "The channel to set the rate for." + amount: + desc: "The amount of xp to give per message." + minutes: + desc: "The cooldown in minutes. Allows decimal values." +xpratereset: + desc: |- + Resets the xp rate for the server or the specified channel. + ex: + - '' + - '#channel' + params: + - { } + - channel: + desc: "The channel to reset the rate for." \ No newline at end of file diff --git a/src/EllieBot/strings/responses/responses.en-US.json b/src/EllieBot/strings/responses/responses.en-US.json index 13c177e..e5dadaf 100644 --- a/src/EllieBot/strings/responses/responses.en-US.json +++ b/src/EllieBot/strings/responses/responses.en-US.json @@ -836,11 +836,6 @@ "xpn_notif_dm": "In a direct message channel.", "xpn_notif_disabled": "Nowhere.", "xprewsreset_confirm": "Are you sure you want to delete ALL xp level up rewards from this server? This action is irreversible.", - "excluded": "{0} has been excluded from the XP system on this server.", - "not_excluded": "{0} is no longer excluded from the XP system on this server.", - "exclusion_list": "Exclusion List", - "server_is_excluded": "This server is excluded.", - "server_is_not_excluded": "This server is not excluded.", "level_up_channel": "Congratulations {0}, You've reached level {1}!", "level_up_dm": "Congratulations {0}, You've reached level {1} on {2} server!", "level_up_global": "Congratulations {0}, You've reached global level {1}!", @@ -1172,5 +1167,16 @@ "fish_weather_forecast": "Forecast", "fish_tod": "Time of Day", "fish_skill_up": "Fishing skill increased to **{0} / {1}**", - "fish_list_title": "Fishing" + "fish_list_title": "Fishing", + "xp_rate_none": "No xp rate overrides on this server.", + "xp_rate_amount_invalid": "Amount must be between 0 and 1000.", + "xp_rate_cooldown_invalid": "Cooldown must be between 0 and 1440 minutes.", + "xp_rate_server": "Server xp Rate", + "xp_rate_amount_cooldown": "{0} xp per every {1} minutes", + "xp_rate_channels": "Channel XP Rates", + "xp_rate_server_set": "Server xp rate set to {0} xp per every {1} minutes.", + "xp_rate_channel_set": "Channel {0} xp rate set to {1} xp per every {2} minutes.", + "xp_rate_server_reset": "Server xp rate has been reset to global defaults.", + "xp_rate_channel_reset": "Channel {0} xp rate has been reset.", + "xp_rate_no_gain": "No xp gain" }