added .notifyph

This commit is contained in:
Toastie 2025-02-08 17:01:15 +13:00
parent 37601286f5
commit 5a235b0565
Signed by: toastie_t0ast
GPG key ID: 0861BE54AD481DC7
13 changed files with 171 additions and 101 deletions

View file

@ -19,14 +19,12 @@ public class DiscordUser : DbEntity
public long CurrencyAmount { get; set; }
public override bool Equals(object obj)
public override bool Equals(object? obj)
=> obj is DiscordUser du ? du.UserId == UserId : false;
public override int GetHashCode()
=> UserId.GetHashCode();
public override string ToString()
{
return Username;
}
=> Username ?? DEFAULT_USERNAME;
}

View file

@ -1,13 +1,13 @@
using EllieBot.Db.Models;
using System.Collections;
namespace EllieBot.Modules.Administration;
public interface INotifyModel
public interface INotifyModel<T>
where T: struct, INotifyModel<T>
{
static abstract string KeyName { get; }
static abstract NotifyType NotifyType { get; }
IReadOnlyDictionary<string, Func<SocketGuild, string>> GetReplacements();
static abstract IReadOnlyList<NotifyModelPlaceholderData<T>> GetReplacements();
public virtual bool TryGetGuildId(out ulong guildId)
{
@ -20,4 +20,6 @@ public interface INotifyModel
userId = 0;
return false;
}
}
}
public readonly record struct NotifyModelPlaceholderData<T>(string Name, Func<T, SocketGuild, string> Func);

View file

@ -1,7 +1,16 @@
namespace EllieBot.Modules.Administration;
using EllieBot.Db.Models;
namespace EllieBot.Modules.Administration;
public interface INotifySubscriber
{
Task NotifyAsync<T>(T data, bool isShardLocal = false)
where T : struct, INotifyModel;
}
where T : struct, INotifyModel<T>;
void RegisterModel<T>()
where T : struct, INotifyModel<T>;
NotifyModelData GetRegisteredModel(NotifyType nType);
}
public readonly record struct NotifyModelData(NotifyType Type, IReadOnlyList<string> Replacements);

View file

@ -3,7 +3,7 @@ using EllieBot.Modules.Administration;
namespace EllieBot.Modules.Xp.Services;
public record struct AddRoleRewardNotifyModel(ulong GuildId, ulong RoleId, ulong UserId, long Level) : INotifyModel
public record struct AddRoleRewardNotifyModel(ulong GuildId, ulong RoleId, ulong UserId, long Level) : INotifyModel<AddRoleRewardNotifyModel>
{
public static string KeyName
=> "notify.reward.addrole";
@ -11,16 +11,17 @@ public record struct AddRoleRewardNotifyModel(ulong GuildId, ulong RoleId, ulong
public static NotifyType NotifyType
=> NotifyType.AddRoleReward;
public IReadOnlyDictionary<string, Func<SocketGuild, string>> GetReplacements()
{
var model = this;
return new Dictionary<string, Func<SocketGuild, string>>()
{
{ "%event.user%", g => g.GetUser(model.UserId)?.ToString() ?? model.UserId.ToString() },
{ "%event.role%", g => g.GetRole(model.RoleId)?.ToString() ?? model.RoleId.ToString() },
{ "%event.level%", g => model.Level.ToString() }
};
}
public const string PH_LEVEL = "level";
public const string PH_USER = "user";
public const string PH_ROLE = "role";
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() )
];
public bool TryGetUserId(out ulong userId)
{
@ -33,4 +34,4 @@ public record struct AddRoleRewardNotifyModel(ulong GuildId, ulong RoleId, ulong
guildId = GuildId;
return true;
}
}
}

View file

@ -2,11 +2,11 @@ using EllieBot.Db.Models;
namespace EllieBot.Modules.Administration;
public record struct LevelUpNotifyModel(
public readonly record struct LevelUpNotifyModel(
ulong GuildId,
ulong ChannelId,
ulong UserId,
long Level) : INotifyModel
long Level) : INotifyModel<LevelUpNotifyModel>
{
public static string KeyName
=> "notify.levelup";
@ -14,23 +14,25 @@ public record struct LevelUpNotifyModel(
public static NotifyType NotifyType
=> NotifyType.LevelUp;
public IReadOnlyDictionary<string, Func<SocketGuild, string>> GetReplacements()
public const string PH_USER = "user";
public const string PH_LEVEL = "level";
public static IReadOnlyList<NotifyModelPlaceholderData<LevelUpNotifyModel>> GetReplacements()
{
var data = this;
return new Dictionary<string, Func<SocketGuild, string>>()
{
{ "%event.level%", g => data.Level.ToString() },
{ "%event.user%", g => g.GetUser(data.UserId)?.ToString() ?? data.UserId.ToString() },
};
return
[
new(PH_LEVEL, static (data, g) => data.Level.ToString() ),
new(PH_USER, static (data, g) => g.GetUser(data.UserId)?.ToString() ?? data.UserId.ToString() )
];
}
public bool TryGetGuildId(out ulong guildId)
public readonly bool TryGetGuildId(out ulong guildId)
{
guildId = GuildId;
return true;
}
public bool TryGetUserId(out ulong userId)
public readonly bool TryGetUserId(out ulong userId)
{
userId = UserId;
return true;

View file

@ -3,7 +3,7 @@ using EllieBot.Db.Models;
namespace EllieBot.Modules.Administration.Services;
public record struct ProtectionNotifyModel(ulong GuildId, ProtectionType ProtType, ulong UserId) : INotifyModel
public record struct ProtectionNotifyModel(ulong GuildId, ProtectionType ProtType, ulong UserId) : INotifyModel<ProtectionNotifyModel>
{
public static string KeyName
=> "notify.protection";
@ -11,13 +11,13 @@ public record struct ProtectionNotifyModel(ulong GuildId, ProtectionType ProtTyp
public static NotifyType NotifyType
=> NotifyType.Protection;
public IReadOnlyDictionary<string, Func<SocketGuild, string>> GetReplacements()
public const string PH_TYPE = "type";
public static IReadOnlyList<NotifyModelPlaceholderData<ProtectionNotifyModel>> GetReplacements()
{
var data = this;
return new Dictionary<string, Func<SocketGuild, string>>()
{
{ "%event.type%", g => data.ProtType.ToString() },
};
return [
new(PH_TYPE, static (data, g) => data.ProtType.ToString() )
];
}
public bool TryGetUserId(out ulong userId)

View file

@ -3,7 +3,7 @@ using EllieBot.Modules.Administration;
namespace EllieBot.Modules.Xp.Services;
public record struct RemoveRoleRewardNotifyModel(ulong GuildId, ulong RoleId, ulong UserId, long Level) : INotifyModel
public record struct RemoveRoleRewardNotifyModel(ulong GuildId, ulong RoleId, ulong UserId, long Level) : INotifyModel<RemoveRoleRewardNotifyModel>
{
public static string KeyName
=> "notify.reward.removerole";
@ -11,17 +11,6 @@ public record struct RemoveRoleRewardNotifyModel(ulong GuildId, ulong RoleId, ul
public static NotifyType NotifyType
=> NotifyType.RemoveRoleReward;
public IReadOnlyDictionary<string, Func<SocketGuild, string>> GetReplacements()
{
var model = this;
return new Dictionary<string, Func<SocketGuild, string>>()
{
{ "%event.user%", g => g.GetUser(model.UserId)?.ToString() ?? model.UserId.ToString() },
{ "%event.role%", g => g.GetRole(model.RoleId)?.ToString() ?? model.RoleId.ToString() },
{ "%event.level%", g => model.Level.ToString() },
};
}
public bool TryGetUserId(out ulong userId)
{
userId = UserId;
@ -33,4 +22,17 @@ public record struct RemoveRoleRewardNotifyModel(ulong GuildId, ulong RoleId, ul
guildId = GuildId;
return true;
}
}
public const string PH_USER = "user";
public const string PH_ROLE = "role";
public const string PH_LEVEL = "level";
public static IReadOnlyList<NotifyModelPlaceholderData<RemoveRoleRewardNotifyModel>> GetReplacements()
{
return [
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() ),
];
}
}

View file

@ -13,7 +13,7 @@ public partial class Administration
{
await Response()
.Paginated()
.Items(Enum.GetValues<NotifyType>())
.Items(Enum.GetValues<NotifyType>().DistinctBy(x => (int)x).ToList())
.PageSize(5)
.Page((items, page) =>
{
@ -75,6 +75,22 @@ public partial class Administration
await Response().Confirm(strs.notify_on($"<#{ctx.Channel.Id}>", Format.Bold(nType.ToString()))).SendAsync();
}
[Cmd]
[UserPerm(GuildPerm.Administrator)]
public async Task NotifyPlaceholders(NotifyType nType)
{
var data = _service.GetRegisteredModel(nType);
var eb = CreateEmbed()
.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]
[UserPerm(GuildPerm.Administrator)]
public async Task NotifyList(int page = 1)

View file

@ -3,6 +3,6 @@
public static class NotifyModelExtensions
{
public static TypedKey<T> GetTypedKey<T>(this T model)
where T : struct, INotifyModel
where T : struct, INotifyModel<T>
=> new(T.KeyName);
}

View file

@ -3,6 +3,8 @@ using LinqToDB.EntityFrameworkCore;
using EllieBot.Common.ModuleBehaviors;
using EllieBot.Db.Models;
using EllieBot.Generators;
using EllieBot.Modules.Administration.Services;
using EllieBot.Modules.Xp.Services;
namespace EllieBot.Modules.Administration;
@ -32,30 +34,42 @@ public sealed class NotifyService : IReadyExecutor, INotifySubscriber, IEService
_pubSub = pubSub;
}
private void RegisterModels()
{
RegisterModel<LevelUpNotifyModel>();
RegisterModel<ProtectionNotifyModel>();
RegisterModel<AddRoleRewardNotifyModel>();
RegisterModel<RemoveRoleRewardNotifyModel>();
}
public async Task OnReadyAsync()
{
RegisterModels();
await using var uow = _db.GetDbContext();
_events = (await uow.GetTable<Notify>()
.Where(x => Queries.GuildOnShard(x.GuildId,
_creds.TotalShards,
_client.ShardId))
.ToListAsyncLinqToDB())
.GroupBy(x => x.Type)
.ToDictionary(x => x.Key, x => x.ToDictionary(x => x.GuildId).ToConcurrent())
.ToConcurrent();
.Where(x => Queries.GuildOnShard(x.GuildId,
_creds.TotalShards,
_client.ShardId))
.ToListAsyncLinqToDB())
.GroupBy(x => x.Type)
.ToDictionary(x => x.Key, x => x.ToDictionary(x => x.GuildId).ToConcurrent())
.ToConcurrent();
await SubscribeToEvent<LevelUpNotifyModel>();
}
private async Task SubscribeToEvent<T>()
where T : struct, INotifyModel
where T : struct, INotifyModel<T>
{
await _pubSub.Sub(new TypedKey<T>(T.KeyName), async (model) => await OnEvent(model));
}
public async Task NotifyAsync<T>(T data, bool isShardLocal = false)
where T : struct, INotifyModel
where T : struct, INotifyModel<T>
{
try
{
@ -77,7 +91,7 @@ public sealed class NotifyService : IReadyExecutor, INotifySubscriber, IEService
}
private async Task OnEvent<T>(T model)
where T : struct, INotifyModel
where T : struct, INotifyModel<T>
{
if (_events.TryGetValue(T.NotifyType, out var subs))
{
@ -113,7 +127,8 @@ public sealed class NotifyService : IReadyExecutor, INotifySubscriber, IEService
}
}
private async Task HandleNotifyEvent(Notify conf, INotifyModel model)
private async Task HandleNotifyEvent<T>(Notify conf, T model)
where T : struct, INotifyModel<T>
{
var guild = _client.GetGuild(conf.GuildId);
var channel = guild?.GetTextChannel(conf.ChannelId);
@ -130,26 +145,28 @@ public sealed class NotifyService : IReadyExecutor, INotifySubscriber, IEService
var rctx = new ReplacementContext(guild: guild, channel: channel, user: user);
var st = SmartText.CreateFrom(conf.Message);
foreach (var modelRep in model.GetReplacements())
foreach (var modelRep in T.GetReplacements())
{
rctx.WithOverride(modelRep.Key, () => modelRep.Value(guild));
rctx.WithOverride(GetPhToken(modelRep.Name), () => modelRep.Func(model, guild));
}
st = await _repSvc.ReplaceAsync(st, rctx);
if (st is SmartPlainText spt)
{
await _mss.Response(channel)
.Confirm(spt.Text)
.SendAsync();
.Confirm(spt.Text)
.SendAsync();
return;
}
await _mss.Response(channel)
.Text(st)
.Sanitize(false)
.SendAsync();
.Text(st)
.Sanitize(false)
.SendAsync();
}
private static string GetPhToken(string name) => $"%event.{name}%";
public async Task EnableAsync(
ulong guildId,
ulong channelId,
@ -158,23 +175,23 @@ public sealed class NotifyService : IReadyExecutor, INotifySubscriber, IEService
{
await using var uow = _db.GetDbContext();
await uow.GetTable<Notify>()
.InsertOrUpdateAsync(() => new()
{
GuildId = guildId,
ChannelId = channelId,
Type = nType,
Message = message,
},
(_) => new()
{
Message = message,
ChannelId = channelId
},
() => new()
{
GuildId = guildId,
Type = nType
});
.InsertOrUpdateAsync(() => new()
{
GuildId = guildId,
ChannelId = channelId,
Type = nType,
Message = message,
},
(_) => new()
{
Message = message,
ChannelId = channelId
},
() => new()
{
GuildId = guildId,
Type = nType
});
var eventDict = _events.GetOrAdd(nType, _ => new());
eventDict[guildId] = new()
@ -190,8 +207,8 @@ public sealed class NotifyService : IReadyExecutor, INotifySubscriber, IEService
{
await using var uow = _db.GetDbContext();
var deleted = await uow.GetTable<Notify>()
.Where(x => x.GuildId == guildId && x.Type == nType)
.DeleteAsync();
.Where(x => x.GuildId == guildId && x.Type == nType)
.DeleteAsync();
if (deleted == 0)
return;
@ -208,11 +225,11 @@ public sealed class NotifyService : IReadyExecutor, INotifySubscriber, IEService
await using var ctx = _db.GetDbContext();
var list = await ctx.GetTable<Notify>()
.Where(x => x.GuildId == guildId)
.OrderBy(x => x.Type)
.Skip(page * 10)
.Take(10)
.ToListAsyncLinqToDB();
.Where(x => x.GuildId == guildId)
.OrderBy(x => x.Type)
.Skip(page * 10)
.Take(10)
.ToListAsyncLinqToDB();
return list;
}
@ -221,7 +238,17 @@ public sealed class NotifyService : IReadyExecutor, INotifySubscriber, IEService
{
await using var ctx = _db.GetDbContext();
return await ctx.GetTable<Notify>()
.Where(x => x.GuildId == guildId && x.Type == nType)
.FirstOrDefaultAsyncLinqToDB();
.Where(x => x.GuildId == guildId && x.Type == nType)
.FirstOrDefaultAsyncLinqToDB();
}
}
// 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));
_models[T.NotifyType] = data;
}
public NotifyModelData GetRegisteredModel(NotifyType nType) => _models[nType];
}

View file

@ -1562,6 +1562,10 @@ notifyclear:
- notifyremove
- notifyrm
- notifclr
notifyphs:
- notifyphs
- notifyph
- notifyplaceholders
winlb:
- winlb
- wins

View file

@ -4911,6 +4911,14 @@ notifyclear:
params:
- event:
desc: "The notify event to clear."
notifyphs:
desc: |-
Lists the placeholders for a given notify event type
ex:
- 'levelup'
params:
- event:
desc: "The notify event to list placeholders for."
winlb:
desc: |-
Shows the biggest wins leaderboard

View file

@ -1158,6 +1158,7 @@
"notify_desc_addrolerew": "Triggers when a user gets a role as a reward for reaching a level (xprew).",
"notify_desc_removerolerew": "Triggers when a user loses a role as a reward for reaching a level (xprew).",
"notify_desc_not_found": "No description found for this notify event. Please report this.",
"notify_placeholders": "Placeholders for '{0}' notify event",
"winlb": "Biggest Wins Leaderboard",
"no_banner": "No banner set.",
"fish_nothing": "You caught nothing, try again.",