From 69a02e0e15a367800db85d70d2ee1660190b8536 Mon Sep 17 00:00:00 2001 From: Toastie <toastie@toastiet0ast.com> Date: Mon, 24 Mar 2025 14:06:49 +1300 Subject: [PATCH] .notify #channel nicecatch <message> fully implemented now notify fixes and improvements --- CHANGELOG.md | 13 +- .../Notify/Models/NiceCatchNotifyModel.cs | 42 ++++++ .../Administration/Notify/NotifyCommands.cs | 22 ++- .../Administration/Notify/NotifyService.cs | 13 +- .../Modules/Games/Fish/FishCommands.cs | 35 +---- src/EllieBot/Modules/Games/Fish/FishResult.cs | 6 + .../Modules/Games/Fish/FishService.cs | 125 ++++++++++++------ .../strings/responses/responses.en-US.json | 3 +- 8 files changed, 176 insertions(+), 83 deletions(-) create mode 100644 src/EllieBot/Modules/Administration/Notify/Models/NiceCatchNotifyModel.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ee62e6..06b3bc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,21 @@ *a,c,f,r,o* -## [6.0.14] - 24.03.2025 +## [6.0.14] + +### Added +- Added `.notify <channel> nicecatch <message>` event + - It will show all rare fish/trash and all max star fish caught on any server + - You can use `.notifyphs nicecatch` to see the list of placeholders you can use while setting a message + - Example: `.notify #fishfeed nicecatch %user% just caught a %event.fish.stars% %event.fish.name% %event.fish.emoji%` + +### Changed +- .notify commands now require Manage Messages permission +- .notify will now let you know if you can't set a notify message due to a missing channel ### Fixed - Fixed `.antispamignore` restart persistence +- Fixed `.notify` events. Only levelup used to work ## [6.0.13] - 23.03.2025 diff --git a/src/EllieBot/Modules/Administration/Notify/Models/NiceCatchNotifyModel.cs b/src/EllieBot/Modules/Administration/Notify/Models/NiceCatchNotifyModel.cs new file mode 100644 index 0000000..72400b2 --- /dev/null +++ b/src/EllieBot/Modules/Administration/Notify/Models/NiceCatchNotifyModel.cs @@ -0,0 +1,42 @@ +#nullable disable +using EllieBot.Db.Models; +using EllieBot.Modules.Games; + +namespace EllieBot.Modules.Administration.Services; + +public record struct NiceCatchNotifyModel( + ulong UserId, + FishData Fish, + string Stars +) : INotifyModel<NiceCatchNotifyModel> +{ + public static string KeyName + => "notify.nicecatch"; + + public static NotifyType NotifyType + => NotifyType.NiceCatch; + + public const string PH_EMOJI = "fish.emoji"; + public const string PH_IMAGE = "fish.image"; + public const string PH_NAME = "fish.name"; + public const string PH_STARS = "fish.stars"; + public const string PH_FLUFF = "fish.fluff"; + + public bool TryGetUserId(out ulong userId) + { + userId = UserId; + return true; + } + + public static IReadOnlyList<NotifyModelPlaceholderData<NiceCatchNotifyModel>> GetReplacements() + { + return + [ + new(PH_EMOJI, static (data, _) => data.Fish.Emoji), + new(PH_IMAGE, static (data, _) => data.Fish.Image), + new(PH_NAME, static (data, _) => data.Fish.Name), + new(PH_STARS, static (data, _) => data.Stars), + new(PH_FLUFF, static (data, _) => data.Fish.Fluff), + ]; + } +} \ No newline at end of file diff --git a/src/EllieBot/Modules/Administration/Notify/NotifyCommands.cs b/src/EllieBot/Modules/Administration/Notify/NotifyCommands.cs index 4399070..7d741d9 100644 --- a/src/EllieBot/Modules/Administration/Notify/NotifyCommands.cs +++ b/src/EllieBot/Modules/Administration/Notify/NotifyCommands.cs @@ -8,7 +8,7 @@ public partial class Administration public class NotifyCommands : EllieModule<NotifyService> { [Cmd] - [UserPerm(GuildPerm.Administrator)] + [UserPerm(GuildPerm.ManageRoles)] public async Task Notify() { await Response() @@ -43,7 +43,7 @@ public partial class Administration }; [Cmd] - [UserPerm(GuildPerm.Administrator)] + [UserPerm(GuildPerm.ManageRoles)] public async Task Notify(NotifyType nType) { // show msg @@ -77,12 +77,12 @@ public partial class Administration } [Cmd] - [UserPerm(GuildPerm.Administrator)] + [UserPerm(GuildPerm.ManageRoles)] public async Task Notify(NotifyType nType, [Leftover] string message) => await NotifyInternalAsync(nType, null, message); [Cmd] - [UserPerm(GuildPerm.Administrator)] + [UserPerm(GuildPerm.ManageRoles)] public async Task Notify(NotifyType nType, IMessageChannel channel, [Leftover] string message) => await NotifyInternalAsync(nType, channel, message); @@ -90,6 +90,14 @@ public partial class Administration { var result = await _service.EnableAsync(ctx.Guild.Id, channel?.Id, nType, message); + if(!result) + { + await Response() + .Error(strs.notify_cant_set) + .SendAsync(); + + return; + } var outChannel = channel is null ? "origin" : $"<#{channel.Id}>"; await Response() .Confirm(strs.notify_on(outChannel, Format.Bold(nType.ToString()))) @@ -97,7 +105,7 @@ public partial class Administration } [Cmd] - [UserPerm(GuildPerm.Administrator)] + [UserPerm(GuildPerm.ManageRoles)] public async Task NotifyPhs(NotifyType nType) { var data = _service.GetRegisteredModel(nType); @@ -112,7 +120,7 @@ public partial class Administration } [Cmd] - [UserPerm(GuildPerm.Administrator)] + [UserPerm(GuildPerm.ManageRoles)] public async Task NotifyList(int page = 1) { if (--page < 0) @@ -140,7 +148,7 @@ public partial class Administration } [Cmd] - [UserPerm(GuildPerm.Administrator)] + [UserPerm(GuildPerm.ManageRoles)] public async Task NotifyClear(NotifyType nType) { await _service.DisableAsync(ctx.Guild.Id, nType); diff --git a/src/EllieBot/Modules/Administration/Notify/NotifyService.cs b/src/EllieBot/Modules/Administration/Notify/NotifyService.cs index 9a1b8de..7ab9218 100644 --- a/src/EllieBot/Modules/Administration/Notify/NotifyService.cs +++ b/src/EllieBot/Modules/Administration/Notify/NotifyService.cs @@ -42,12 +42,11 @@ public sealed class NotifyService : IReadyExecutor, INotifySubscriber, IEService RegisterModel<ProtectionNotifyModel>(); RegisterModel<AddRoleRewardNotifyModel>(); RegisterModel<RemoveRoleRewardNotifyModel>(); + RegisterModel<NiceCatchNotifyModel>(); } public async Task OnReadyAsync() { - RegisterModels(); - await using var uow = _db.GetDbContext(); _events = (await uow.GetTable<Notify>() .Where(x => Queries.GuildOnShard(x.GuildId, @@ -57,9 +56,8 @@ public sealed class NotifyService : IReadyExecutor, INotifySubscriber, IEService .GroupBy(x => x.Type) .ToDictionary(x => x.Key, x => x.ToDictionary(x => x.GuildId).ToConcurrent()) .ToConcurrent(); - - - await SubscribeToEvent<LevelUpNotifyModel>(); + + RegisterModels(); } private async Task SubscribeToEvent<T>() @@ -75,7 +73,7 @@ public sealed class NotifyService : IReadyExecutor, INotifySubscriber, IEService { if (isShardLocal) { - await OnEvent(data); + _ = Task.Run(async () => await OnEvent(data)); return; } @@ -283,7 +281,10 @@ public sealed class NotifyService : IReadyExecutor, INotifySubscriber, IEService var data = new NotifyModelData(T.NotifyType, T.SupportsOriginTarget, T.GetReplacements().Map(x => x.Name)); + _models[T.NotifyType] = data; + + _pubSub.Sub<T>(new(T.KeyName), async (data) => await OnEvent(data)); } public NotifyModelData GetRegisteredModel(NotifyType nType) diff --git a/src/EllieBot/Modules/Games/Fish/FishCommands.cs b/src/EllieBot/Modules/Games/Fish/FishCommands.cs index bc20449..d8d0ece 100644 --- a/src/EllieBot/Modules/Games/Fish/FishCommands.cs +++ b/src/EllieBot/Modules/Games/Fish/FishCommands.cs @@ -105,7 +105,7 @@ public partial class Games .WithOkColor() .WithAuthor(ctx.User) .WithDescription(desc) - .AddField(GetText(strs.fish_quality), GetStarText(res.Stars, res.Fish.Stars), true) + .AddField(GetText(strs.fish_quality), fs.GetStarText(res.Stars, res.Fish.Stars), true) .AddField(GetText(strs.desc), res.Fish.Fluff, true) .WithThumbnailUrl(res.Fish.Image)) .SendAsync(); @@ -150,7 +150,7 @@ public partial class Games .Items(fishes) .PageSize(9) .CurrentPage(page) - .Page((fs, i) => + .Page((fishes, i) => { var eb = CreateEmbed() .WithDescription($"🧠 **Skill:** {skill} / {maxSkill}") @@ -158,7 +158,7 @@ public partial class Games .WithTitle(GetText(strs.fish_list_title)) .WithOkColor(); - foreach (var f in fs) + foreach (var f in fishes) { if (catchDict.TryGetValue(f.Id, out var c)) { @@ -169,14 +169,14 @@ public partial class Games + GetTodEmoji(f.Time) + GetWeatherEmoji(f.Weather) + "\n" - + GetStarText(c.MaxStars, f.Stars) + + fs.GetStarText(c.MaxStars, f.Stars) + "\n" + Format.Italics(f.Fluff), true); } else { - eb.AddField("?", GetFishEmoji(null, 0) + "\n" + GetStarText(0, f.Stars), true); + eb.AddField("?", GetFishEmoji(null, 0) + "\n" + fs.GetStarText(0, f.Stars), true); } } @@ -225,31 +225,8 @@ public partial class Games _ => "" }; - private string GetStarText(int resStars, int fishStars) - { - if (resStars == fishStars) - { - return MultiplyStars(fcs.Data.StarEmojis[^1], fishStars); - } + - var c = fcs.Data; - var starsp1 = MultiplyStars(c.StarEmojis[resStars], resStars); - var starsp2 = MultiplyStars(c.StarEmojis[0], fishStars - resStars); - - return starsp1 + starsp2; - } - - private string MultiplyStars(string starEmoji, int count) - { - var sb = new StringBuilder(); - - for (var i = 0; i < count; i++) - { - sb.Append(starEmoji); - } - - return sb.ToString(); - } } } diff --git a/src/EllieBot/Modules/Games/Fish/FishResult.cs b/src/EllieBot/Modules/Games/Fish/FishResult.cs index ad4edcc..a3451de 100644 --- a/src/EllieBot/Modules/Games/Fish/FishResult.cs +++ b/src/EllieBot/Modules/Games/Fish/FishResult.cs @@ -7,6 +7,12 @@ public sealed class FishResult public bool IsSkillUp { get; set; } public int Skill { get; set; } public int MaxSkill { get; set; } + + public bool IsMaxStar() + => Stars == Fish.Stars; + + public bool IsRare() + => Fish.Chance <= 15; } public readonly record struct AlreadyFishing; \ No newline at end of file diff --git a/src/EllieBot/Modules/Games/Fish/FishService.cs b/src/EllieBot/Modules/Games/Fish/FishService.cs index 43d8868..36dc20b 100644 --- a/src/EllieBot/Modules/Games/Fish/FishService.cs +++ b/src/EllieBot/Modules/Games/Fish/FishService.cs @@ -1,10 +1,19 @@ using System.Security.Cryptography; +using System.Text; using LinqToDB; using LinqToDB.EntityFrameworkCore; +using EllieBot.Modules.Administration; +using EllieBot.Modules.Administration.Services; namespace EllieBot.Modules.Games.Fish; -public sealed class FishService(FishConfigService fcs, IBotCache cache, DbService db) : IEService +public sealed class FishService( + FishConfigService fcs, + IBotCache cache, + DbService db, + INotifySubscriber notify +) + : IEService { private const double MAX_SKILL = 100; @@ -15,7 +24,7 @@ public sealed class FishService(FishConfigService fcs, IBotCache cache, DbServic public async Task<OneOf.OneOf<Task<FishResult?>, AlreadyFishing>> FishAsync(ulong userId, ulong channelId) { - var duration = _rng.Next(5, 9); + var duration = _rng.Next(3, 6); if (!await cache.AddAsync(FishingKey(userId), true, TimeSpan.FromSeconds(duration), overwrite: false)) { @@ -69,6 +78,18 @@ public sealed class FishService(FishConfigService fcs, IBotCache cache, DbServic } } + // notification system + if (result is not null) + { + if (result.IsMaxStar() || result.IsRare()) + { + await notify.NotifyAsync(new NiceCatchNotifyModel( + userId, + result.Fish, + GetStarText(result.Stars, result.Fish.Stars) + )); + } + } return result; } @@ -85,21 +106,21 @@ public sealed class FishService(FishConfigService fcs, IBotCache cache, DbServic var maxSkill = (int)MAX_SKILL; await ctx.GetTable<UserFishStats>() - .InsertOrUpdateAsync(() => new() - { - UserId = userId, - Skill = 1, - }, - (old) => new() - { - UserId = userId, - Skill = old.Skill > maxSkill ? maxSkill : old.Skill + 1 - }, - () => new() - { - UserId = userId, - Skill = playerSkill - }); + .InsertOrUpdateAsync(() => new() + { + UserId = userId, + Skill = 1, + }, + (old) => new() + { + UserId = userId, + Skill = old.Skill > maxSkill ? maxSkill : old.Skill + 1 + }, + () => new() + { + UserId = userId, + Skill = playerSkill + }); return true; } @@ -123,9 +144,9 @@ public sealed class FishService(FishConfigService fcs, IBotCache cache, DbServic await using var ctx = db.GetDbContext(); var skill = await ctx.GetTable<UserFishStats>() - .Where(x => x.UserId == userId) - .Select(x => x.Skill) - .FirstOrDefaultAsyncLinqToDB(); + .Where(x => x.UserId == userId) + .Select(x => x.Skill) + .FirstOrDefaultAsyncLinqToDB(); return (skill, (int)MAX_SKILL); } @@ -188,23 +209,23 @@ public sealed class FishService(FishConfigService fcs, IBotCache cache, DbServic await using var uow = db.GetDbContext(); await uow.GetTable<FishCatch>() - .InsertOrUpdateAsync(() => new FishCatch() - { - UserId = userId, - FishId = caught.Fish.Id, - MaxStars = caught.Stars, - Count = 1 - }, - (old) => new FishCatch() - { - Count = old.Count + 1, - MaxStars = Math.Max(old.MaxStars, caught.Stars), - }, - () => new() - { - FishId = caught.Fish.Id, - UserId = userId - }); + .InsertOrUpdateAsync(() => new FishCatch() + { + UserId = userId, + FishId = caught.Fish.Id, + MaxStars = caught.Stars, + Count = 1 + }, + (old) => new FishCatch() + { + Count = old.Count + 1, + MaxStars = Math.Max(old.MaxStars, caught.Stars), + }, + () => new() + { + FishId = caught.Fish.Id, + UserId = userId + }); return caught; } @@ -392,9 +413,35 @@ public sealed class FishService(FishConfigService fcs, IBotCache cache, DbServic await using var ctx = db.GetDbContext(); var catches = await ctx.GetTable<FishCatch>() - .Where(x => x.UserId == userId) - .ToListAsyncLinqToDB(); + .Where(x => x.UserId == userId) + .ToListAsyncLinqToDB(); return catches; } + + public string GetStarText(int resStars, int fishStars) + { + if (resStars == fishStars) + { + return MultiplyStars(fcs.Data.StarEmojis[^1], fishStars); + } + + var c = fcs.Data; + var starsp1 = MultiplyStars(c.StarEmojis[resStars], resStars); + var starsp2 = MultiplyStars(c.StarEmojis[0], fishStars - resStars); + + return starsp1 + starsp2; + } + + private string MultiplyStars(string starEmoji, int count) + { + var sb = new StringBuilder(); + + for (var i = 0; i < count; i++) + { + sb.Append(starEmoji); + } + + return sb.ToString(); + } } \ 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 92dd008..5f3957c 100644 --- a/src/EllieBot/strings/responses/responses.en-US.json +++ b/src/EllieBot/strings/responses/responses.en-US.json @@ -1239,5 +1239,6 @@ "linkfix_list_none": "No link fixes have been configured for this server.", "linkfix_list_title": "Link Fixes", "linkfix_removed": "Link fix for {0} has been removed.", - "linkfix_not_found": "No link fix found for {0}." + "linkfix_not_found": "No link fix found for {0}.", + "notify_cant_set": "This event doesn't support origin channel, Please specify a channel" } \ No newline at end of file