.notify #channel nicecatch <message> fully implemented now

notify fixes and improvements
This commit is contained in:
Toastie 2025-03-24 14:06:49 +13:00
parent 8d08595a9f
commit 69a02e0e15
Signed by: toastie_t0ast
GPG key ID: 0861BE54AD481DC7
8 changed files with 176 additions and 83 deletions

View file

@ -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

View file

@ -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),
];
}
}

View file

@ -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);

View file

@ -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)

View file

@ -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();
}
}
}

View file

@ -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;

View file

@ -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();
}
}

View file

@ -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"
}