.atl / .at reworked

This commit is contained in:
Kwoth
2021-12-13 19:28:22 +01:00
parent fcc49dbbdb
commit 3c0768a372
15 changed files with 3207 additions and 137 deletions

View File

@@ -0,0 +1,13 @@
using System.Linq;
using System.Threading.Tasks;
using LinqToDB.EntityFrameworkCore;
using NadekoBot.Services.Database.Models;
namespace NadekoBot.Modules.Searches
{
public static class AtlExtensions
{
public static Task<AutoTranslateChannel> GetByChannelId(this IQueryable<AutoTranslateChannel> set, ulong channelId)
=> set.FirstOrDefaultAsyncLinqToDB(x => x.ChannelId == channelId);
}
}

View File

@@ -0,0 +1,14 @@
using System.Collections.Generic;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Searches
{
public interface ITranslateService
{
public Task<string> Translate(string source, string target, string text = null);
Task<bool> ToggleAtl(ulong guildId, ulong channelId, bool autoDelete);
IEnumerable<string> GetLanguages();
Task<bool?> RegisterUserAsync(ulong userId, ulong channelId, string @from, string to);
Task<bool> UnregisterUser(ulong channelId, ulong userId);
}
}

View File

@@ -1,10 +1,6 @@
using Discord;
using Discord.WebSocket;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Common;
using NadekoBot.Common;
using NadekoBot.Modules.Searches.Common;
using NadekoBot.Services;
using NadekoBot.Services.Database.Models;
using NadekoBot.Extensions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@@ -13,18 +9,14 @@ using SixLabors.ImageSharp.Drawing.Processing;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using AngleSharp.Html.Dom;
using AngleSharp.Html.Parser;
using NadekoBot.Db;
using NadekoBot.Modules.Administration;
using Serilog;
using HorizontalAlignment = SixLabors.Fonts.HorizontalAlignment;
using Image = SixLabors.ImageSharp.Image;
@@ -34,71 +26,31 @@ namespace NadekoBot.Modules.Searches.Services
public class SearchesService : INService
{
private readonly IHttpClientFactory _httpFactory;
private readonly DiscordSocketClient _client;
private readonly IGoogleApiService _google;
private readonly DbService _db;
private readonly IImageCache _imgs;
private readonly IDataCache _cache;
private readonly FontProvider _fonts;
private readonly IBotCredentials _creds;
private readonly IEmbedBuilderService _eb;
private readonly NadekoRandom _rng;
public ConcurrentDictionary<ulong, bool> TranslatedChannels { get; } = new ConcurrentDictionary<ulong, bool>();
// (userId, channelId)
public ConcurrentDictionary<(ulong UserId, ulong ChannelId), string> UserLanguages { get; } = new ConcurrentDictionary<(ulong, ulong), string>();
public List<WoWJoke> WowJokes { get; } = new List<WoWJoke>();
public List<MagicItem> MagicItems { get; } = new List<MagicItem>();
private readonly List<string> _yomamaJokes;
public SearchesService(DiscordSocketClient client, IGoogleApiService google,
DbService db, Bot bot, IDataCache cache, IHttpClientFactory factory,
FontProvider fonts, IBotCredentials creds, IEmbedBuilderService eb)
public SearchesService(IGoogleApiService google,
IDataCache cache,
IHttpClientFactory factory,
FontProvider fonts,
IBotCredentials creds)
{
_httpFactory = factory;
_client = client;
_google = google;
_db = db;
_imgs = cache.LocalImages;
_cache = cache;
_fonts = fonts;
_creds = creds;
_eb = eb;
_rng = new NadekoRandom();
//translate commands
_client.MessageReceived += (msg) =>
{
var _ = Task.Run(async () =>
{
try
{
if (!(msg is SocketUserMessage umsg))
return;
if (!TranslatedChannels.TryGetValue(umsg.Channel.Id, out var autoDelete))
return;
var key = (umsg.Author.Id, umsg.Channel.Id);
if (!UserLanguages.TryGetValue(key, out string langs))
return;
var text = await Translate(langs, umsg.Resolve(TagHandling.Ignore))
.ConfigureAwait(false);
if (autoDelete)
try { await umsg.DeleteAsync().ConfigureAwait(false); } catch { }
await umsg.Channel.SendConfirmAsync(_eb, $"{umsg.Author.Mention} `:` "
+ text.Replace("<@ ", "<@", StringComparison.InvariantCulture)
.Replace("<@! ", "<@!", StringComparison.InvariantCulture)).ConfigureAwait(false);
}
catch { }
});
return Task.CompletedTask;
};
//joke commands
if (File.Exists("data/wowjokes.json"))
{
@@ -340,19 +292,6 @@ namespace NadekoBot.Modules.Searches.Services
_rng.Next(1, max).ToString("000") + ".png";
}
public async Task<string> Translate(string langs, string text = null)
{
if (string.IsNullOrWhiteSpace(text))
throw new ArgumentException("Text is empty or null", nameof(text));
var langarr = langs.ToLowerInvariant().Split('>');
if (langarr.Length != 2)
throw new ArgumentException("Langs does not have 2 parts separated by a >", nameof(langs));
var from = langarr[0];
var to = langarr[1];
text = text?.Trim();
return (await _google.Translate(text, from, to).ConfigureAwait(false)).SanitizeMentions(true);
}
private readonly object yomamaLock = new object();
private int yomamaJokeIndex = 0;
public Task<string> GetYomamaJoke()

View File

@@ -0,0 +1,216 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Discord;
using Discord.Net;
using LinqToDB;
using LinqToDB.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Extensions;
using NadekoBot.Services;
namespace NadekoBot.Modules.Searches
{
public sealed class TranslateService : ITranslateService, ILateExecutor, IReadyExecutor, INService
{
private readonly IGoogleApiService _google;
private readonly DbService _db;
private readonly IEmbedBuilderService _eb;
private readonly Bot _bot;
private readonly ConcurrentDictionary<ulong, bool> _atcs = new();
private readonly ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, (string From, string To)>> _users = new();
public TranslateService(IGoogleApiService google,
DbService db,
IEmbedBuilderService eb,
Bot bot)
{
_google = google;
_db = db;
_eb = eb;
_bot = bot;
}
public async Task OnReadyAsync()
{
var ctx = _db.GetDbContext();
var guilds = _bot.AllGuildConfigs.Select(x => x.GuildId).ToList();
var cs = await ctx.AutoTranslateChannels
.Include(x => x.Users)
.Where(x => guilds.Contains(x.GuildId))
.ToListAsyncEF();
foreach (var c in cs)
{
_atcs[c.ChannelId] = c.AutoDelete;
_users[c.ChannelId] = new(c.Users.ToDictionary(x => x.UserId, x => (x.Source, x.Target)));
}
}
public async Task LateExecute(IGuild guild, IUserMessage msg)
{
if (msg is IUserMessage { Channel: ITextChannel tch } um)
{
if (!_atcs.TryGetValue(tch.Id, out var autoDelete))
return;
if (!_users.TryGetValue(tch.Id, out var users)
|| !users.TryGetValue(um.Author.Id, out var langs))
return;
var output = await _google.Translate(msg.Content, langs.From, langs.To);
if (string.IsNullOrWhiteSpace(output))
return;
var embed = _eb.Create()
.WithOkColor();
if (autoDelete)
{
embed
.WithAuthor(um.Author.ToString(), um.Author.GetAvatarUrl())
.AddField(langs.From, um.Content)
.AddField(langs.To, output);
await tch.EmbedAsync(embed);
try
{
await um.DeleteAsync();
}
catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.Forbidden)
{
_atcs.TryUpdate(tch.Id, false, true);
}
return;
}
await um.ReplyAsync(embed: embed
.AddField(langs.To, output)
.Build());
}
}
public async Task<string> Translate(string source, string target, string text = null)
{
if (string.IsNullOrWhiteSpace(text))
throw new ArgumentException("Text is empty or null", nameof(text));
var res = await _google.Translate(text, source, target).ConfigureAwait(false);
return res.SanitizeMentions(true);
}
public async Task<bool> ToggleAtl(ulong guildId, ulong channelId, bool autoDelete)
{
var ctx = _db.GetDbContext();
var old = await ctx.AutoTranslateChannels
.ToLinqToDBTable()
.FirstOrDefaultAsyncLinqToDB(x => x.ChannelId == channelId);
if (old is null)
{
ctx.AutoTranslateChannels
.Add(new()
{
GuildId = guildId,
ChannelId = channelId,
AutoDelete = autoDelete,
});
await ctx.SaveChangesAsync();
_atcs[channelId] = autoDelete;
_users[channelId] = new();
return true;
}
// if autodelete value is different, update the autodelete value
// instead of disabling
if (old.AutoDelete != autoDelete)
{
old.AutoDelete = autoDelete;
await ctx.SaveChangesAsync();
_atcs[channelId] = autoDelete;
return true;
}
await ctx.AutoTranslateChannels
.ToLinqToDBTable()
.DeleteAsync(x => x.ChannelId == channelId);
await ctx.SaveChangesAsync();
_atcs.TryRemove(channelId, out _);
_users.TryRemove(channelId, out _);
return false;
}
public async Task<bool?> RegisterUserAsync(ulong userId, ulong channelId, string from, string to)
{
var ctx = _db.GetDbContext();
var ch = await ctx.AutoTranslateChannels
.ToLinqToDBTable()
.GetByChannelId(channelId);
if (ch is null)
return null;
var user = ch.Users
.FirstOrDefault(x => x.UserId == userId);
if (user is null)
{
ch.Users.Add(user = new()
{
Source = from,
Target = to,
UserId = userId,
});
await ctx.SaveChangesAsync();
var dict = _users.GetOrAdd(channelId, new ConcurrentDictionary<ulong, (string, string)>());
dict[userId] = (from, to);
return true;
}
ctx.AutoTranslateUsers.Remove(user);
await ctx.SaveChangesAsync();
if (_users.TryGetValue(channelId, out var inner))
inner.TryRemove(userId, out _);
return true;
}
public async Task<bool> UnregisterUser(ulong channelId, ulong userId)
{
var ctx = _db.GetDbContext();
var rows = await ctx.AutoTranslateUsers
.ToLinqToDBTable()
.DeleteAsync(x => x.UserId == userId &&
x.Channel.ChannelId == channelId);
if (_users.TryGetValue(channelId, out var inner))
inner.TryRemove(userId, out _);
await ctx.SaveChangesAsync();
return rows > 0;
}
public IEnumerable<string> GetLanguages() => _google.Languages;
}
}

View File

@@ -2,35 +2,29 @@
using Discord.Commands;
using NadekoBot.Extensions;
using System.Threading.Tasks;
using System.Linq;
using NadekoBot.Common.Attributes;
using NadekoBot.Services;
using NadekoBot.Modules.Searches.Services;
namespace NadekoBot.Modules.Searches
{
public partial class Searches
{
[Group]
public class TranslateCommands : NadekoSubmodule
public class TranslateCommands : NadekoSubmodule<ITranslateService>
{
private readonly SearchesService _searches;
private readonly IGoogleApiService _google;
public TranslateCommands(SearchesService searches, IGoogleApiService google)
{
_searches = searches;
_google = google;
}
[NadekoCommand, Aliases]
public async Task Translate(string langs, [Leftover] string text = null)
public async Task Translate(string from, string to, [Leftover] string text = null)
{
try
{
await ctx.Channel.TriggerTypingAsync().ConfigureAwait(false);
var translation = await _searches.Translate(langs, text).ConfigureAwait(false);
await SendConfirmAsync(GetText(strs.translation) + " " + langs, translation).ConfigureAwait(false);
var translation = await _service.Translate(from, to, text).ConfigureAwait(false);
var embed = _eb.Create(ctx)
.WithOkColor()
.AddField(from, text, false)
.AddField(to, translation, false);
await ctx.Channel.EmbedAsync(embed);
}
catch
{
@@ -38,27 +32,6 @@ namespace NadekoBot.Modules.Searches
}
}
//[NadekoCommand, Usage, Description, Aliases]
//[OwnerOnly]
//public async Task Obfuscate([Leftover] string txt)
//{
// var lastItem = "en";
// foreach (var item in _google.Languages.Except(new[] { "en" }).Where(x => x.Length < 4))
// {
// var txt2 = await _searches.Translate(lastItem + ">" + item, txt);
// await ctx.Channel.EmbedAsync(_eb.Create()
// .WithOkColor()
// .WithTitle(lastItem + ">" + item)
// .AddField("Input", txt)
// .AddField("Output", txt2));
// txt = txt2;
// await Task.Delay(500);
// lastItem = item;
// }
// txt = await _searches.Translate(lastItem + ">en", txt);
// await SendConfirmAsync("Final output:\n\n" + txt);
//}
public enum AutoDeleteAutoTranslate
{
Del,
@@ -68,56 +41,49 @@ namespace NadekoBot.Modules.Searches
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[BotPerm(ChannelPerm.ManageMessages)]
[OwnerOnly]
public async Task AutoTranslate(AutoDeleteAutoTranslate autoDelete = AutoDeleteAutoTranslate.Nodel)
{
var channel = (ITextChannel)ctx.Channel;
if (autoDelete == AutoDeleteAutoTranslate.Del)
{
_searches.TranslatedChannels.AddOrUpdate(channel.Id, true, (key, val) => true);
await ReplyConfirmLocalizedAsync(strs.atl_ad_started).ConfigureAwait(false);
return;
}
if (_searches.TranslatedChannels.TryRemove(channel.Id, out _))
{
await ReplyConfirmLocalizedAsync(strs.atl_stopped).ConfigureAwait(false);
return;
}
if (_searches.TranslatedChannels.TryAdd(channel.Id, autoDelete == AutoDeleteAutoTranslate.Del))
var toggle = await _service.ToggleAtl(ctx.Guild.Id, ctx.Channel.Id, autoDelete == AutoDeleteAutoTranslate.Del);
if (toggle)
{
await ReplyConfirmLocalizedAsync(strs.atl_started).ConfigureAwait(false);
}
else
{
await ReplyConfirmLocalizedAsync(strs.atl_stopped).ConfigureAwait(false);
}
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public async Task AutoTransLang([Leftover] string langs = null)
public async Task AutoTransLang()
{
var ucp = (ctx.User.Id, ctx.Channel.Id);
if (string.IsNullOrWhiteSpace(langs))
if (await _service.UnregisterUser(ctx.Channel.Id, ctx.User.Id))
{
if (_searches.UserLanguages.TryRemove(ucp, out langs))
await ReplyConfirmLocalizedAsync(strs.atl_removed).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.atl_removed).ConfigureAwait(false);
}
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public async Task AutoTransLang(string from, string to)
{
var succ = await _service.RegisterUserAsync(ctx.User.Id, ctx.Channel.Id, from, to);
if (succ is null)
{
await ReplyErrorLocalizedAsync(strs.atl_not_enabled);
return;
}
var langarr = langs.ToLowerInvariant().Split('>');
if (langarr.Length != 2)
return;
var from = langarr[0];
var to = langarr[1];
if (!_google.Languages.Contains(from) || !_google.Languages.Contains(to))
if (succ is false)
{
await ReplyErrorLocalizedAsync(strs.invalid_lang).ConfigureAwait(false);
return;
}
_searches.UserLanguages.AddOrUpdate(ucp, langs, (key, val) => langs);
await ReplyConfirmLocalizedAsync(strs.atl_set(from, to));
}
@@ -125,7 +91,7 @@ namespace NadekoBot.Modules.Searches
[RequireContext(ContextType.Guild)]
public async Task Translangs()
{
await ctx.Channel.SendTableAsync(_google.Languages, str => $"{str,-15}", 3).ConfigureAwait(false);
await ctx.Channel.SendTableAsync(_service.GetLanguages(), str => $"{str,-15}", 3).ConfigureAwait(false);
}
}