diff --git a/src/EllieBot/Db/Models/Notify.cs b/src/EllieBot/Db/Models/Notify.cs index caa8db3..f8b29c0 100644 --- a/src/EllieBot/Db/Models/Notify.cs +++ b/src/EllieBot/Db/Models/Notify.cs @@ -8,7 +8,7 @@ public class Notify public int Id { get; set; } public ulong GuildId { get; set; } - public ulong ChannelId { get; set; } + public ulong? ChannelId { get; set; } public NotifyType Type { get; set; } [MaxLength(10_000)] diff --git a/src/EllieBot/Migrations/PostgreSql/20250228044141_notify-allow-origin-channel.sql b/src/EllieBot/Migrations/PostgreSql/20250228044141_notify-allow-origin-channel.sql new file mode 100644 index 0000000..4abfb05 --- /dev/null +++ b/src/EllieBot/Migrations/PostgreSql/20250228044141_notify-allow-origin-channel.sql @@ -0,0 +1,7 @@ +START TRANSACTION; +ALTER TABLE notify ALTER COLUMN channelid DROP NOT NULL; + +INSERT INTO "__EFMigrationsHistory" (migrationid, productversion) +VALUES ('20250228044141_notify-allow-origin-channel', '9.0.1'); + +COMMIT; diff --git a/src/EllieBot/Migrations/PostgreSql/20250226215222_init.Designer.cs b/src/EllieBot/Migrations/PostgreSql/20250228044209_init.Designer.cs similarity index 99% rename from src/EllieBot/Migrations/PostgreSql/20250226215222_init.Designer.cs rename to src/EllieBot/Migrations/PostgreSql/20250228044209_init.Designer.cs index 4bc0e7f..02ad1c2 100644 --- a/src/EllieBot/Migrations/PostgreSql/20250226215222_init.Designer.cs +++ b/src/EllieBot/Migrations/PostgreSql/20250228044209_init.Designer.cs @@ -12,7 +12,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace EllieBot.Migrations.PostgreSql { [DbContext(typeof(PostgreSqlContext))] - [Migration("20250226215222_init")] + [Migration("20250228044209_init")] partial class init { /// <inheritdoc /> @@ -1809,7 +1809,7 @@ namespace EllieBot.Migrations.PostgreSql NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id")); - b.Property<decimal>("ChannelId") + b.Property<decimal?>("ChannelId") .HasColumnType("numeric(20,0)") .HasColumnName("channelid"); diff --git a/src/EllieBot/Migrations/PostgreSql/20250226215222_init.cs b/src/EllieBot/Migrations/PostgreSql/20250228044209_init.cs similarity index 99% rename from src/EllieBot/Migrations/PostgreSql/20250226215222_init.cs rename to src/EllieBot/Migrations/PostgreSql/20250228044209_init.cs index 9d717f0..a99bf07 100644 --- a/src/EllieBot/Migrations/PostgreSql/20250226215222_init.cs +++ b/src/EllieBot/Migrations/PostgreSql/20250228044209_init.cs @@ -658,7 +658,7 @@ namespace EllieBot.Migrations.PostgreSql 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), + channelid = table.Column<decimal>(type: "numeric(20,0)", nullable: true), type = table.Column<int>(type: "integer", nullable: false), message = table.Column<string>(type: "character varying(10000)", maxLength: 10000, nullable: false) }, diff --git a/src/EllieBot/Migrations/PostgreSql/PostgreSqlContextModelSnapshot.cs b/src/EllieBot/Migrations/PostgreSql/PostgreSqlContextModelSnapshot.cs index 5c0c910..e40eb15 100644 --- a/src/EllieBot/Migrations/PostgreSql/PostgreSqlContextModelSnapshot.cs +++ b/src/EllieBot/Migrations/PostgreSql/PostgreSqlContextModelSnapshot.cs @@ -1806,7 +1806,7 @@ namespace EllieBot.Migrations.PostgreSql NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id")); - b.Property<decimal>("ChannelId") + b.Property<decimal?>("ChannelId") .HasColumnType("numeric(20,0)") .HasColumnName("channelid"); diff --git a/src/EllieBot/Migrations/Sqlite/20250228044138_notify-allow-origin-channel.sql b/src/EllieBot/Migrations/Sqlite/20250228044138_notify-allow-origin-channel.sql new file mode 100644 index 0000000..c010e60 --- /dev/null +++ b/src/EllieBot/Migrations/Sqlite/20250228044138_notify-allow-origin-channel.sql @@ -0,0 +1,29 @@ +BEGIN TRANSACTION; +CREATE TABLE "ef_temp_Notify" ( + "Id" INTEGER NOT NULL CONSTRAINT "PK_Notify" PRIMARY KEY AUTOINCREMENT, + "ChannelId" INTEGER NULL, + "GuildId" INTEGER NOT NULL, + "Message" TEXT NOT NULL, + "Type" INTEGER NOT NULL, + CONSTRAINT "AK_Notify_GuildId_Type" UNIQUE ("GuildId", "Type") +); + +INSERT INTO "ef_temp_Notify" ("Id", "ChannelId", "GuildId", "Message", "Type") +SELECT "Id", "ChannelId", "GuildId", "Message", "Type" +FROM "Notify"; + +COMMIT; + +PRAGMA foreign_keys = 0; + +BEGIN TRANSACTION; +DROP TABLE "Notify"; + +ALTER TABLE "ef_temp_Notify" RENAME TO "Notify"; + +COMMIT; + +PRAGMA foreign_keys = 1; + +INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") +VALUES ('20250228044138_notify-allow-origin-channel', '9.0.1'); diff --git a/src/EllieBot/Migrations/Sqlite/20250226215220_init.Designer.cs b/src/EllieBot/Migrations/Sqlite/20250228044206_init.Designer.cs similarity index 99% rename from src/EllieBot/Migrations/Sqlite/20250226215220_init.Designer.cs rename to src/EllieBot/Migrations/Sqlite/20250228044206_init.Designer.cs index 18ebf28..f1d82e7 100644 --- a/src/EllieBot/Migrations/Sqlite/20250226215220_init.Designer.cs +++ b/src/EllieBot/Migrations/Sqlite/20250228044206_init.Designer.cs @@ -11,7 +11,7 @@ using EllieBot.Db; namespace EllieBot.Migrations.Sqlite { [DbContext(typeof(SqliteContext))] - [Migration("20250226215220_init")] + [Migration("20250228044206_init")] partial class init { /// <inheritdoc /> @@ -1351,7 +1351,7 @@ namespace EllieBot.Migrations.Sqlite .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.Property<ulong>("ChannelId") + b.Property<ulong?>("ChannelId") .HasColumnType("INTEGER"); b.Property<ulong>("GuildId") diff --git a/src/EllieBot/Migrations/Sqlite/20250226215220_init.cs b/src/EllieBot/Migrations/Sqlite/20250228044206_init.cs similarity index 99% rename from src/EllieBot/Migrations/Sqlite/20250226215220_init.cs rename to src/EllieBot/Migrations/Sqlite/20250228044206_init.cs index 566a6e2..7e5aeba 100644 --- a/src/EllieBot/Migrations/Sqlite/20250226215220_init.cs +++ b/src/EllieBot/Migrations/Sqlite/20250228044206_init.cs @@ -658,7 +658,7 @@ namespace EllieBot.Migrations.Sqlite 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), + ChannelId = table.Column<ulong>(type: "INTEGER", nullable: true), Type = table.Column<int>(type: "INTEGER", nullable: false), Message = table.Column<string>(type: "TEXT", maxLength: 10000, nullable: false) }, diff --git a/src/EllieBot/Migrations/Sqlite/SqliteContextModelSnapshot.cs b/src/EllieBot/Migrations/Sqlite/SqliteContextModelSnapshot.cs index 6f16565..77fa0b9 100644 --- a/src/EllieBot/Migrations/Sqlite/SqliteContextModelSnapshot.cs +++ b/src/EllieBot/Migrations/Sqlite/SqliteContextModelSnapshot.cs @@ -1348,7 +1348,7 @@ namespace EllieBot.Migrations.Sqlite .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.Property<ulong>("ChannelId") + b.Property<ulong?>("ChannelId") .HasColumnType("INTEGER"); b.Property<ulong>("GuildId") diff --git a/src/EllieBot/Modules/Administration/Notify/INotifyModel.cs b/src/EllieBot/Modules/Administration/Notify/INotifyModel.cs index bbfa70d..3d241df 100644 --- a/src/EllieBot/Modules/Administration/Notify/INotifyModel.cs +++ b/src/EllieBot/Modules/Administration/Notify/INotifyModel.cs @@ -3,15 +3,25 @@ namespace EllieBot.Modules.Administration; public interface INotifyModel<T> - where T: struct, INotifyModel<T> + where T : struct, INotifyModel<T> { static abstract string KeyName { get; } static abstract NotifyType NotifyType { get; } static abstract IReadOnlyList<NotifyModelPlaceholderData<T>> GetReplacements(); + static virtual bool SupportsOriginTarget + => false; + public virtual bool TryGetGuildId(out ulong guildId) { guildId = 0; + + return false; + } + + public virtual bool TryGetChannelId(out ulong channelId) + { + channelId = 0; return false; } diff --git a/src/EllieBot/Modules/Administration/Notify/INotifySubscriber.cs b/src/EllieBot/Modules/Administration/Notify/INotifySubscriber.cs index 23d4008..a1c902d 100644 --- a/src/EllieBot/Modules/Administration/Notify/INotifySubscriber.cs +++ b/src/EllieBot/Modules/Administration/Notify/INotifySubscriber.cs @@ -13,4 +13,7 @@ public interface INotifySubscriber NotifyModelData GetRegisteredModel(NotifyType nType); } -public readonly record struct NotifyModelData(NotifyType Type, IReadOnlyList<string> Replacements); \ No newline at end of file +public readonly record struct NotifyModelData( + NotifyType Type, + bool SupportsOriginTarget, + IReadOnlyList<string> Replacements); \ No newline at end of file diff --git a/src/EllieBot/Modules/Administration/Notify/Models/AddRoleRewardNotifyModel.cs b/src/EllieBot/Modules/Administration/Notify/Models/AddRoleRewardNotifyModel.cs index 60e263c..0e28d69 100644 --- a/src/EllieBot/Modules/Administration/Notify/Models/AddRoleRewardNotifyModel.cs +++ b/src/EllieBot/Modules/Administration/Notify/Models/AddRoleRewardNotifyModel.cs @@ -3,7 +3,12 @@ using EllieBot.Modules.Administration; namespace EllieBot.Modules.Xp.Services; -public record struct AddRoleRewardNotifyModel(ulong GuildId, ulong RoleId, ulong UserId, long Level) : INotifyModel<AddRoleRewardNotifyModel> +public record struct AddRoleRewardNotifyModel( + ulong GuildId, + ulong RoleId, + ulong UserId, + long Level) + : INotifyModel<AddRoleRewardNotifyModel> { public static string KeyName => "notify.reward.addrole"; @@ -18,9 +23,9 @@ public record struct AddRoleRewardNotifyModel(ulong GuildId, ulong RoleId, ulong public static IReadOnlyList<NotifyModelPlaceholderData<AddRoleRewardNotifyModel>> GetReplacements() => [ - new(PH_LEVEL, static (data, g) => data.Level.ToString() ), - new(PH_USER, static (data, g) => g.GetUser(data.UserId)?.ToString() ?? data.UserId.ToString() ), - new(PH_ROLE, static (data, g) => g.GetRole(data.RoleId)?.ToString() ?? data.RoleId.ToString() ) + new(PH_LEVEL, static (data, g) => data.Level.ToString()), + new(PH_USER, static (data, g) => g.GetUser(data.UserId)?.ToString() ?? data.UserId.ToString()), + new(PH_ROLE, static (data, g) => g.GetRole(data.RoleId)?.ToString() ?? data.RoleId.ToString()) ]; public bool TryGetUserId(out ulong userId) diff --git a/src/EllieBot/Modules/Administration/Notify/Models/LevelUpNotifyModel.cs b/src/EllieBot/Modules/Administration/Notify/Models/LevelUpNotifyModel.cs index 2643e22..cdbd89e 100644 --- a/src/EllieBot/Modules/Administration/Notify/Models/LevelUpNotifyModel.cs +++ b/src/EllieBot/Modules/Administration/Notify/Models/LevelUpNotifyModel.cs @@ -4,7 +4,7 @@ namespace EllieBot.Modules.Administration; public readonly record struct LevelUpNotifyModel( ulong GuildId, - ulong ChannelId, + ulong? ChannelId, ulong UserId, long Level) : INotifyModel<LevelUpNotifyModel> { @@ -21,17 +21,32 @@ public readonly record struct LevelUpNotifyModel( { return [ - new(PH_LEVEL, static (data, g) => data.Level.ToString() ), - new(PH_USER, static (data, g) => g.GetUser(data.UserId)?.ToString() ?? data.UserId.ToString() ) + new(PH_LEVEL, static (data, g) => data.Level.ToString()), + new(PH_USER, static (data, g) => g.GetUser(data.UserId)?.ToString() ?? data.UserId.ToString()) ]; } + public static bool SupportsOriginTarget + => true; + public readonly bool TryGetGuildId(out ulong guildId) { guildId = GuildId; return true; } + public readonly bool TryGetChannelId(out ulong channelId) + { + if (ChannelId is ulong cid) + { + channelId = cid; + return true; + } + + channelId = 0; + return false; + } + public readonly bool TryGetUserId(out ulong userId) { userId = UserId; diff --git a/src/EllieBot/Modules/Administration/Notify/NotifyCommands.cs b/src/EllieBot/Modules/Administration/Notify/NotifyCommands.cs index 8c7e2de..55dc1f9 100644 --- a/src/EllieBot/Modules/Administration/Notify/NotifyCommands.cs +++ b/src/EllieBot/Modules/Administration/Notify/NotifyCommands.cs @@ -12,23 +12,23 @@ public partial class Administration public async Task Notify() { await Response() - .Paginated() - .Items(Enum.GetValues<NotifyType>().DistinctBy(x => (int)x).ToList()) - .PageSize(5) - .Page((items, page) => - { - var eb = CreateEmbed() - .WithOkColor() - .WithTitle(GetText(strs.notify_available)); + .Paginated() + .Items(Enum.GetValues<NotifyType>().DistinctBy(x => (int)x).ToList()) + .PageSize(5) + .Page((items, page) => + { + var eb = CreateEmbed() + .WithOkColor() + .WithTitle(GetText(strs.notify_available)); - foreach (var item in items) - { - eb.AddField(item.ToString(), GetText(GetDescription(item)), false); - } + foreach (var item in items) + { + eb.AddField(item.ToString(), GetText(GetDescription(item)), false); + } - return eb; - }) - .SendAsync(); + return eb; + }) + .SendAsync(); } private LocStr GetDescription(NotifyType item) @@ -43,36 +43,56 @@ public partial class Administration [Cmd] [UserPerm(GuildPerm.Administrator)] - public async Task Notify(NotifyType nType, [Leftover] string? message = null) + public async Task Notify(NotifyType nType) { - if (string.IsNullOrWhiteSpace(message)) + // show msg + var conf = await _service.GetNotifyAsync(ctx.Guild.Id, nType); + if (conf is null) { - // show msg - var conf = await _service.GetNotifyAsync(ctx.Guild.Id, nType); - if (conf is null) - { - await Response().Confirm(strs.notify_msg_not_set).SendAsync(); - return; - } - - var eb = CreateEmbed() - .WithOkColor() - .WithTitle(GetText(strs.notify_msg)) - .WithDescription(conf.Message.TrimTo(2048)) - .AddField(GetText(strs.notify_type), conf.Type.ToString(), true) - .AddField(GetText(strs.channel), - $""" - <#{conf.ChannelId}> - `{conf.ChannelId}` - """, - true); - - await Response().Embed(eb).SendAsync(); + await Response().Confirm(strs.notify_msg_not_set).SendAsync(); return; } - await _service.EnableAsync(ctx.Guild.Id, ctx.Channel.Id, nType, message); - await Response().Confirm(strs.notify_on($"<#{ctx.Channel.Id}>", Format.Bold(nType.ToString()))).SendAsync(); + var outChannel = conf.ChannelId is null + ? """ + from which the event originated + `origin` + """ + : $""" + <#{conf.ChannelId}> + `{conf.ChannelId}` + """; + var eb = CreateEmbed() + .WithOkColor() + .WithTitle(GetText(strs.notify_msg)) + .WithDescription(conf.Message.TrimTo(2048)) + .AddField(GetText(strs.notify_type), conf.Type.ToString(), true) + .AddField(GetText(strs.channel), + outChannel, + true); + + await Response().Embed(eb).SendAsync(); + return; + } + + [Cmd] + [UserPerm(GuildPerm.Administrator)] + public async Task Notify(NotifyType nType, [Leftover] string message) + => await NotifyInternalAsync(nType, null, message); + + [Cmd] + [UserPerm(GuildPerm.Administrator)] + public async Task Notify(NotifyType nType, IMessageChannel channel, [Leftover] string message) + => await NotifyInternalAsync(nType, channel, message); + + private async Task NotifyInternalAsync(NotifyType nType, IMessageChannel? channel, [Leftover] string message) + { + var result = await _service.EnableAsync(ctx.Guild.Id, channel?.Id, nType, message); + + var outChannel = channel is null ? "origin" : $"<#{channel.Id}>"; + await Response() + .Confirm(strs.notify_on(outChannel, Format.Bold(nType.ToString()))) + .SendAsync(); } [Cmd] @@ -82,13 +102,12 @@ public partial class Administration var data = _service.GetRegisteredModel(nType); var eb = CreateEmbed() - .WithOkColor() - .WithTitle(GetText(strs.notify_placeholders(nType.ToString().ToLower()))); + .WithOkColor() + .WithTitle(GetText(strs.notify_placeholders(nType.ToString().ToLower()))); eb.WithDescription(data.Replacements.Join("\n---\n", x => $"`%event.{x}%`")); await Response().Embed(eb).SendAsync(); - } [Cmd] @@ -115,8 +134,8 @@ public partial class Administration sb.AppendLine(GetText(strs.notify_none)); await Response() - .Confirm(GetText(strs.notify_list), text: sb.ToString()) - .SendAsync(); + .Confirm(GetText(strs.notify_list), text: sb.ToString()) + .SendAsync(); } [Cmd] diff --git a/src/EllieBot/Modules/Administration/Notify/NotifyService.cs b/src/EllieBot/Modules/Administration/Notify/NotifyService.cs index 9fc7ea7..d23d71d 100644 --- a/src/EllieBot/Modules/Administration/Notify/NotifyService.cs +++ b/src/EllieBot/Modules/Administration/Notify/NotifyService.cs @@ -16,6 +16,7 @@ public sealed class NotifyService : IReadyExecutor, INotifySubscriber, IEService private readonly IBotCreds _creds; private readonly IReplacementService _repSvc; private readonly IPubSub _pubSub; + private ConcurrentDictionary<NotifyType, ConcurrentDictionary<ulong, Notify>> _events = new(); public NotifyService( @@ -36,12 +37,10 @@ public sealed class NotifyService : IReadyExecutor, INotifySubscriber, IEService private void RegisterModels() { - RegisterModel<LevelUpNotifyModel>(); RegisterModel<ProtectionNotifyModel>(); RegisterModel<AddRoleRewardNotifyModel>(); RegisterModel<RemoveRoleRewardNotifyModel>(); - } public async Task OnReadyAsync() @@ -84,7 +83,7 @@ public sealed class NotifyService : IReadyExecutor, INotifySubscriber, IEService catch (Exception ex) { Log.Warning(ex, - "Unknown error occurred while trying to triger {NotifyEvent} for {NotifyModel}", + "Unknown error occurred while trying to trigger {NotifyEvent} for {NotifyModel}", T.KeyName, data); } @@ -93,36 +92,39 @@ public sealed class NotifyService : IReadyExecutor, INotifySubscriber, IEService private async Task OnEvent<T>(T model) where T : struct, INotifyModel<T> { - if (_events.TryGetValue(T.NotifyType, out var subs)) + if (!_events.TryGetValue(T.NotifyType, out var subs)) + return; + + // make sure the event is consumed + // only in the guild it was meant for + if (model.TryGetGuildId(out var gid)) { - if (model.TryGetGuildId(out var gid)) - { - if (!subs.TryGetValue(gid, out var conf)) - return; - - await HandleNotifyEvent(conf, model); + if (!subs.TryGetValue(gid, out var conf)) return; - } - foreach (var key in subs.Keys.ToArray()) + await HandleNotifyEvent(conf, model); + return; + } + + // todo optimize this + foreach (var key in subs.Keys) + { + if (subs.TryGetValue(key, out var notif)) { - if (subs.TryGetValue(key, out var notif)) + try { - try - { - await HandleNotifyEvent(notif, model); - } - catch (Exception ex) - { - Log.Error(ex, - "Error occured while sending notification {NotifyEvent} to guild {GuildId}: {ErrorMessage}", - T.NotifyType, - key, - ex.Message); - } - - await Task.Delay(500); + await HandleNotifyEvent(notif, model); } + catch (Exception ex) + { + Log.Error(ex, + "Error occured while sending notification {NotifyEvent} to guild {GuildId}: {ErrorMessage}", + T.NotifyType, + key, + ex.Message); + } + + await Task.Delay(500); } } } @@ -131,9 +133,27 @@ public sealed class NotifyService : IReadyExecutor, INotifySubscriber, IEService where T : struct, INotifyModel<T> { var guild = _client.GetGuild(conf.GuildId); - var channel = guild?.GetTextChannel(conf.ChannelId); - if (guild is null || channel is null) + // bot probably left the guild, cleanup? + if (guild is null) + return; + + IMessageChannel? channel; + // if notify channel is specified for this event, send the event to that channel + if (conf.ChannelId is ulong confCid) + { + channel = guild.GetTextChannel(confCid); + } + else + { + // otherwise get the origin channel of the event + if (!model.TryGetChannelId(out var cid)) + return; + + channel = guild.GetChannel(cid) as IMessageChannel; + } + + if (channel is null) return; IUser? user = null; @@ -165,14 +185,24 @@ public sealed class NotifyService : IReadyExecutor, INotifySubscriber, IEService .SendAsync(); } - private static string GetPhToken(string name) => $"%event.{name}%"; + private static string GetPhToken(string name) + => $"%event.{name}%"; - public async Task EnableAsync( + public async Task<bool> EnableAsync( ulong guildId, - ulong channelId, + ulong? channelId, NotifyType nType, string message) { + // check if the notify type model supports null channel + if (channelId is null) + { + var model = GetRegisteredModel(nType); + if (!model.SupportsOriginTarget) + return false; + } + + await using var uow = _db.GetDbContext(); await uow.GetTable<Notify>() .InsertOrUpdateAsync(() => new() @@ -201,6 +231,8 @@ public sealed class NotifyService : IReadyExecutor, INotifySubscriber, IEService Type = nType, Message = message }; + + return true; } public async Task DisableAsync(ulong guildId, NotifyType nType) @@ -244,11 +276,15 @@ public sealed class NotifyService : IReadyExecutor, INotifySubscriber, IEService // messed up big time, it was supposed to be fully extensible, but it's stored as an enum in the database already... private readonly ConcurrentDictionary<NotifyType, NotifyModelData> _models = new(); + public void RegisterModel<T>() where T : struct, INotifyModel<T> { - var data = new NotifyModelData(T.NotifyType, T.GetReplacements().Map(x => x.Name)); + var data = new NotifyModelData(T.NotifyType, + T.SupportsOriginTarget, + T.GetReplacements().Map(x => x.Name)); _models[T.NotifyType] = data; } - public NotifyModelData GetRegisteredModel(NotifyType nType) => _models[nType]; -} + public NotifyModelData GetRegisteredModel(NotifyType nType) + => _models[nType]; +} \ No newline at end of file diff --git a/src/EllieBot/Modules/Xp/XpRate/GuildConfigXpService.cs b/src/EllieBot/Modules/Xp/XpRate/GuildConfigXpService.cs index acf7fbb..9b08527 100644 --- a/src/EllieBot/Modules/Xp/XpRate/GuildConfigXpService.cs +++ b/src/EllieBot/Modules/Xp/XpRate/GuildConfigXpService.cs @@ -85,6 +85,8 @@ public class GuildConfigXpService(DbService db, ShardData shardData, XpConfigSer GuildId = guildId, RateType = type, }); + + _guildRates[(type, guildId)] = new XpRate(type, amount, cooldown); } public async Task SetChannelXpRateAsync(ulong guildId, @@ -119,6 +121,9 @@ public class GuildConfigXpService(DbService db, ShardData shardData, XpConfigSer ChannelId = channelId, RateType = type, }); + + _channelRates.GetOrAdd(guildId, _ => new()) + [(type, channelId)] = new XpRate(type, amount, cooldown); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -137,6 +142,9 @@ public class GuildConfigXpService(DbService db, ShardData shardData, XpConfigSer var deleted = await uow.GetTable<GuildXpConfig>() .Where(x => x.GuildId == guildId) .DeleteAsync(); + + _guildRates.TryRemove((XpRateType.Text, guildId), out _); + return deleted > 0; } @@ -146,6 +154,10 @@ public class GuildConfigXpService(DbService db, ShardData shardData, XpConfigSer var deleted = await uow.GetTable<ChannelXpConfig>() .Where(x => x.GuildId == guildId && x.ChannelId == channelId) .DeleteAsync(); + + if (_channelRates.TryGetValue(guildId, out var channelRates)) + channelRates.TryRemove((XpRateType.Text, channelId), out _); + return deleted > 0; } diff --git a/src/EllieBot/Modules/Xp/XpService.cs b/src/EllieBot/Modules/Xp/XpService.cs index 5b0b6b5..431f92c 100644 --- a/src/EllieBot/Modules/Xp/XpService.cs +++ b/src/EllieBot/Modules/Xp/XpService.cs @@ -155,7 +155,7 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand if (oldBatch.Contains(u)) { - validUsers.Add(new(u, rate.Amount)); + validUsers.Add(new(u, rate.Amount, vc.Id)); } _voiceXpBatch.Add(u); @@ -218,13 +218,13 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand (batch, stats) => stats) .ToListAsyncLinqToDB(); - var userToXp = currentBatch.ToDictionary(x => x.User.Id, x => x.Xp); + var userToXp = currentBatch.ToDictionary(x => x.User.Id, x => x); foreach (var u in updated) { - if (!userToXp.TryGetValue(u.UserId, out var xpGained)) + if (!userToXp.TryGetValue(u.UserId, out var data)) continue; - var oldStats = new LevelStats(u.Xp - xpGained); + var oldStats = new LevelStats(u.Xp - data.Xp); var newStats = new LevelStats(u.Xp); Log.Information("User {User} xp updated from {OldLevel} to {NewLevel}", @@ -235,9 +235,8 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand if (oldStats.Level < newStats.Level) { await _levelUpQueue.EnqueueAsync(NotifyUser(u.GuildId, - 0, + data.ChannelId, u.UserId, - true, oldStats.Level, newStats.Level)); } @@ -246,19 +245,15 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand private Func<Task> NotifyUser( ulong guildId, - ulong channelId, + ulong? channelId, ulong userId, - bool isServer, long oldLevel, long newLevel) => async () => { - if (isServer) - { - await HandleRewardsInternalAsync(guildId, userId, oldLevel, newLevel); - } + await HandleRewardsInternalAsync(guildId, userId, oldLevel, newLevel); - await HandleNotifyInternalAsync(guildId, channelId, userId, isServer, newLevel); + await HandleNotifyInternalAsync(guildId, channelId, userId, newLevel); }; private async Task HandleRewardsInternalAsync( @@ -338,9 +333,8 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand private async Task HandleNotifyInternalAsync( ulong guildId, - ulong channelId, + ulong? channelId, ulong userId, - bool isServer, long newLevel) { var guild = _client.GetGuild(guildId); @@ -349,18 +343,15 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand if (guild is null || user is null) return; - if (isServer) + var model = new LevelUpNotifyModel() { - var model = new LevelUpNotifyModel() - { - GuildId = guildId, - UserId = userId, - ChannelId = channelId, - Level = newLevel - }; - await _notifySub.NotifyAsync(model, true); - return; - } + GuildId = guildId, + UserId = userId, + ChannelId = channelId, + Level = newLevel + }; + await _notifySub.NotifyAsync(model, true); + return; } public async Task SetCurrencyReward(ulong guildId, int level, int amount) @@ -552,7 +543,7 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand if (!await TryAddUserGainedXpAsync(user.Id, rate.Cooldown)) return; - _usersBatch.Add(new(user, rate.Amount)); + _usersBatch.Add(new(user, rate.Amount, gc.Id)); }); return Task.CompletedTask; @@ -1169,7 +1160,7 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand } } -public readonly record struct XpQueueEntry(IGuildUser User, long Xp) +public readonly record struct XpQueueEntry(IGuildUser User, long Xp, ulong? ChannelId) { public bool Equals(XpQueueEntry? other) => other?.User == User; diff --git a/src/EllieBot/data/patron.yml b/src/EllieBot/data/patron.yml index 632a3e6..2d2a3b9 100644 --- a/src/EllieBot/data/patron.yml +++ b/src/EllieBot/data/patron.yml @@ -4,16 +4,6 @@ version: 3 isEnabled: false # Who can do how much of what limits: - 100: - ChatBot: - quota: 50000000 - quotaPeriod: PerMonth - ReactionRole: - quota: -1 - quotaPeriod: Total - Prune: - quota: -1 - quotaPeriod: PerDay 50: ChatBot: quota: 20000000 @@ -24,16 +14,6 @@ limits: Prune: quota: -1 quotaPeriod: PerDay - 20: - ChatBot: - quota: 6500000 - quotaPeriod: PerMonth - ReactionRole: - quota: -1 - quotaPeriod: Total - Prune: - quota: 20 - quotaPeriod: PerDay 10: ChatBot: quota: 2500000 @@ -44,7 +24,7 @@ limits: Prune: quota: 5 quotaPeriod: PerDay - 5: + 2: ChatBot: quota: 1000000 quotaPeriod: PerMonth @@ -53,4 +33,4 @@ limits: quotaPeriod: Total Prune: quota: 2 - quotaPeriod: PerDay + quotaPeriod: PerDay \ 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 527679c..51077ef 100644 --- a/src/EllieBot/strings/commands/commands.en-US.yml +++ b/src/EllieBot/strings/commands/commands.en-US.yml @@ -4862,14 +4862,18 @@ minesweeper: desc: "The number of mines to create." notify: desc: |- - Sends a message to the current channel once the specified event occurs. + Sends a message to the specified channel once the specified event occurs. + + If no channel is specified, the message will be sent to the channel from which the event originated. + *note: this is only possible for events that have an origin channel (for example `levelup`)* + Provide no parameters to see all available events. ex: - 'levelup Congratulations to user %user.name% for reaching level %event.level%' params: - { } - event: - desc: "The event to notify on." + desc: "The event for which to see the current message." - event: desc: "The event to notify on." message: