#nullable disable using LinqToDB; using LinqToDB.EntityFrameworkCore; using EllieBot.Common.ModuleBehaviors; using EllieBot.Db.Models; using System.Collections.Frozen; namespace EllieBot.Modules.Searches; public sealed partial class FlagTranslateService : IReadyExecutor, IEService { private readonly IBotCreds _creds; private readonly DiscordSocketClient _client; private readonly TranslateService _ts; private readonly IMessageSenderService _sender; private IReadOnlyDictionary<string, string> _supportedFlags; private readonly DbService _db; private ConcurrentHashSet<ulong> _enabledChannels; private readonly IBotCache _cache; // disallow same message being translated multiple times to the same language private readonly ConcurrentHashSet<(ulong, string)> _msgLangs = new(); public FlagTranslateService( IBotCreds creds, DiscordSocketClient client, TranslateService ts, IMessageSenderService sender, DbService db, IBotCache cache) { _creds = creds; _client = client; _ts = ts; _sender = sender; _db = db; _cache = cache; } public async Task OnReadyAsync() { _supportedFlags = COUNTRIES .Split('\n') .Select(x => x.Split(' ')) .ToDictionary(x => x[0], x => x[1].TrimEnd()) .ToFrozenDictionary(); await using (var uow = _db.GetDbContext()) { _enabledChannels = (await uow.GetTable<FlagTranslateChannel>() .Where(x => Linq2DbExpressions.GuildOnShard(x.GuildId, _creds.TotalShards, _client.ShardId)) .Select(x => new { x.ChannelId, x.GuildId }) .ToListAsyncLinqToDB()) .Select(x => x.ChannelId) .ToHashSet() .ToConcurrentSet(); } _client.ReactionAdded += OnReactionAdded; var periodicCleanup = new PeriodicTimer(TimeSpan.FromHours(24)); while (await periodicCleanup.WaitForNextTickAsync()) { _msgLangs.Clear(); } } private const int FLAG_START = 127462; private static TypedKey<bool> CdKey(ulong userId) => new($"flagtranslate:{userId}"); private Task OnReactionAdded( Cacheable<IUserMessage, ulong> arg1, Cacheable<IMessageChannel, ulong> arg2, SocketReaction reaction) { if (!_enabledChannels.Contains(reaction.Channel.Id)) return Task.CompletedTask; var runes = reaction.Emote.Name.EnumerateRunes(); if (!runes.MoveNext() || runes.Current is not { Value: >= 127462 and <= 127487 } l1 || !runes.MoveNext() || runes.Current is not { Value: >= 127462 and <= 127487 } l2) { return Task.CompletedTask; } _ = Task.Run(async () => { if (reaction.Channel is not SocketTextChannel tc) return; var user = await ((IGuild)tc.Guild).GetUserAsync(reaction.UserId); if (user is null) return; if (!user.GetPermissions(tc).SendMessages) return; if (!tc.Guild.CurrentUser.GetPermissions(tc).SendMessages || !tc.Guild.CurrentUser.GetPermissions(tc).EmbedLinks) { await Disable(tc.Guild.Id, tc.Id); return; } var c1 = (char)(l1.Value - FLAG_START + 65); var c2 = (char)(l2.Value - FLAG_START + 65); var code = $"{c1}{c2}".ToUpper(); if (!_supportedFlags.TryGetValue(code, out var lang)) return; if (!_msgLangs.Add((reaction.MessageId, lang))) return; var result = await _cache.GetAsync(CdKey(reaction.UserId)); if (result.TryPickT0(out _, out _)) return; await _cache.AddAsync(CdKey(reaction.UserId), true, TimeSpan.FromSeconds(5)); var msg = await arg1.GetOrDownloadAsync(); var response = await _ts.Translate("", lang, msg.Content).ConfigureAwait(false); await msg.ReplyAsync(embed: _sender.CreateEmbed(tc.Guild?.Id) .WithOkColor() .WithFooter(user.ToString() ?? reaction.UserId.ToString(), user.RealAvatarUrl().ToString()) .WithDescription(response) .WithAuthor(reaction.Emote.ToString()) .Build(), allowedMentions: AllowedMentions.None ); }); return Task.CompletedTask; } public async Task Disable(ulong guildId, ulong tcId) { if (!_enabledChannels.TryRemove(tcId)) return; await using var uow = _db.GetDbContext(); await uow.GetTable<FlagTranslateChannel>() .Where(x => x.GuildId == guildId && x.ChannelId == tcId) .DeleteAsync(); } public async Task<bool> Toggle(ulong guildId, ulong tcId) { if (_enabledChannels.Contains(tcId)) { await Disable(guildId, tcId); return false; } await Enable(guildId, tcId); return true; } public async Task Enable(ulong guildId, ulong tcId) { if (!_enabledChannels.Add(tcId)) return; await using var uow = _db.GetDbContext(); await uow.GetTable<FlagTranslateChannel>() .InsertAsync(() => new FlagTranslateChannel { GuildId = guildId, ChannelId = tcId }); } }