This repository has been archived on 2024-12-22. You can view files and clone it, but cannot push or open issues or pull requests.
elliebot/src/EllieBot/Modules/Administration/Notify/NotifyService.cs

202 lines
5.9 KiB
C#

using LinqToDB;
using LinqToDB.EntityFrameworkCore;
using EllieBot.Common.ModuleBehaviors;
using EllieBot.Db.Models;
namespace EllieBot.Modules.Administration;
public sealed class NotifyService : IReadyExecutor, INotifySubscriber, IEService
{
private readonly DbService _db;
private readonly IMessageSenderService _mss;
private readonly DiscordSocketClient _client;
private readonly IBotCreds _creds;
private readonly IReplacementService _repSvc;
private readonly IPubSub _pubSub;
private ConcurrentDictionary<NotifyType, ConcurrentDictionary<ulong, Notify>> _events = new();
public NotifyService(
DbService db,
IMessageSenderService mss,
DiscordSocketClient client,
IBotCreds creds,
IReplacementService repSvc,
IPubSub pubSub)
{
_db = db;
_mss = mss;
_client = client;
_creds = creds;
_repSvc = repSvc;
_pubSub = pubSub;
}
public async Task OnReadyAsync()
{
await using var uow = _db.GetDbContext();
_events = (await uow.GetTable<Notify>()
.Where(x => Linq2DbExpressions.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
{
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
{
try
{
if (isShardLocal)
{
await OnEvent(data);
return;
}
await _pubSub.Pub(data.GetTypedKey(), data);
}
catch (Exception ex)
{
Log.Warning(ex,
"Unknown error occurred while trying to triger {NotifyEvent} for {NotifyModel}",
T.KeyName,
data);
}
}
private async Task OnEvent<T>(T model)
where T : struct, INotifyModel
{
if (_events.TryGetValue(T.NotifyType, out var subs))
{
if (model.TryGetGuildId(out var gid))
{
if (!subs.TryGetValue(gid, out var conf))
return;
await HandleNotifyEvent(conf, model);
return;
}
foreach (var key in subs.Keys.ToArray())
{
if (subs.TryGetValue(key, out var notif))
{
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);
}
}
}
}
private async Task HandleNotifyEvent(Notify conf, INotifyModel model)
{
var guild = _client.GetGuild(conf.GuildId);
var channel = guild?.GetTextChannel(conf.ChannelId);
if (guild is null || channel is null)
return;
IUser? user = null;
if (model.TryGetUserId(out var userId))
{
user = guild.GetUser(userId) ?? _client.GetUser(userId);
}
var rctx = new ReplacementContext(guild: guild, channel: channel, user: user);
var st = SmartText.CreateFrom(conf.Message);
foreach (var modelRep in model.GetReplacements())
{
rctx.WithOverride(modelRep.Key, () => modelRep.Value(guild));
}
st = await _repSvc.ReplaceAsync(st, rctx);
if (st is SmartPlainText spt)
{
await _mss.Response(channel)
.Confirm(spt.Text)
.SendAsync();
return;
}
await _mss.Response(channel)
.Text(st)
.SendAsync();
}
public async Task EnableAsync(
ulong guildId,
ulong channelId,
NotifyType nType,
string message)
{
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
});
var eventDict = _events.GetOrAdd(nType, _ => new());
eventDict[guildId] = new()
{
GuildId = guildId,
ChannelId = channelId,
Type = nType,
Message = message
};
}
public async Task DisableAsync(ulong guildId, NotifyType nType)
{
await using var uow = _db.GetDbContext();
var deleted = await uow.GetTable<Notify>()
.Where(x => x.GuildId == guildId && x.Type == nType)
.DeleteAsync();
if (deleted == 0)
return;
if (!_events.TryGetValue(nType, out var guildsDict))
return;
guildsDict.TryRemove(guildId, out _);
}
}