1
Fork 0
mirror of https://gitlab.com/Kwoth/nadekobot.git synced 2024-10-02 20:13:13 +00:00

.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

@ -9,6 +9,18 @@ Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.
- Added slots.currencyFontColor to gambling.yml
- Added `.qexport` and `.qimport` commands which allow you to export and import quotes just like `.crsexport`
### Changed
- `.at` and `.atl` commands reworked
- Persist restarts
- Will now only translate non-commands
- You can switch between `.at del` and `.at` without clearing the user language registrations
- Disabling `.at` will clear all user language registrations on that channel
- Users can't register languages if the `.at` is not enabled
- Looks much nicer
- Bot will now reply to user messages with a translation if `del` is disabled
- Bot will make an embed with original and translated text with user avatar and name if `del` is enabled
- If the bot is unable to delete messages while having `del` enabled, it will reset back to the no-del behavior for the current session
### Fixed
- `.crypto` now supports top 5000 coins

View file

@ -19,6 +19,7 @@ using NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Common.Configs;
using NadekoBot.Db;
using NadekoBot.Modules.Administration.Services;
using NadekoBot.Modules.Searches;
using Serilog;
namespace NadekoBot

View file

@ -0,0 +1,12 @@
using System.Collections.Generic;
namespace NadekoBot.Services.Database.Models
{
public class AutoTranslateChannel : DbEntity
{
public ulong GuildId { get; set; }
public ulong ChannelId { get; set; }
public bool AutoDelete { get; set; }
public IList<AutoTranslateUser> Users { get; set; } = new List<AutoTranslateUser>();
}
}

View file

@ -0,0 +1,11 @@
namespace NadekoBot.Services.Database.Models
{
public class AutoTranslateUser : DbEntity
{
public int ChannelId { get; set; }
public AutoTranslateChannel Channel { get; set; }
public ulong UserId { get; set; }
public string Source { get; set; }
public string Target { get; set; }
}
}

View file

@ -60,6 +60,8 @@ namespace NadekoBot.Services.Database
public DbSet<WaifuInfo> WaifuInfo { get; set; }
public DbSet<ImageOnlyChannel> ImageOnlyChannels { get; set; }
public DbSet<NsfwBlacklistedTag> NsfwBlacklistedTags { get; set; }
public DbSet<AutoTranslateChannel> AutoTranslateChannels { get; set; }
public DbSet<AutoTranslateUser> AutoTranslateUsers { get; set; }
public NadekoContext(DbContextOptions<NadekoContext> options) : base(options)
{
@ -368,6 +370,21 @@ namespace NadekoBot.Services.Database
modelBuilder.Entity<NsfwBlacklistedTag>(nbt => nbt
.HasIndex(x => x.GuildId)
.IsUnique(false));
var atch = modelBuilder.Entity<AutoTranslateChannel>();
atch.HasIndex(x => x.GuildId)
.IsUnique(false);
atch.HasIndex(x => x.ChannelId)
.IsUnique();
atch
.HasMany(x => x.Users)
.WithOne(x => x.Channel)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<AutoTranslateUser>(atu => atu
.HasAlternateKey(x => new { x.ChannelId, x.UserId }));
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,71 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace NadekoBot.Migrations
{
public partial class atlrework : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "AutoTranslateChannels",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
GuildId = table.Column<ulong>(type: "INTEGER", nullable: false),
ChannelId = table.Column<ulong>(type: "INTEGER", nullable: false),
AutoDelete = table.Column<bool>(type: "INTEGER", nullable: false),
DateAdded = table.Column<DateTime>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AutoTranslateChannels", x => x.Id);
});
migrationBuilder.CreateTable(
name: "AutoTranslateUsers",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
ChannelId = table.Column<int>(type: "INTEGER", nullable: false),
UserId = table.Column<ulong>(type: "INTEGER", nullable: false),
Source = table.Column<string>(type: "TEXT", nullable: true),
Target = table.Column<string>(type: "TEXT", nullable: true),
DateAdded = table.Column<DateTime>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AutoTranslateUsers", x => x.Id);
table.UniqueConstraint("AK_AutoTranslateUsers_ChannelId_UserId", x => new { x.ChannelId, x.UserId });
table.ForeignKey(
name: "FK_AutoTranslateUsers_AutoTranslateChannels_ChannelId",
column: x => x.ChannelId,
principalTable: "AutoTranslateChannels",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_AutoTranslateChannels_ChannelId",
table: "AutoTranslateChannels",
column: "ChannelId",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_AutoTranslateChannels_GuildId",
table: "AutoTranslateChannels",
column: "GuildId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "AutoTranslateUsers");
migrationBuilder.DropTable(
name: "AutoTranslateChannels");
}
}
}

View file

@ -340,6 +340,62 @@ namespace NadekoBot.Migrations
b.ToTable("AutoCommands");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.AutoTranslateChannel", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("AutoDelete")
.HasColumnType("INTEGER");
b.Property<ulong>("ChannelId")
.HasColumnType("INTEGER");
b.Property<DateTime?>("DateAdded")
.HasColumnType("TEXT");
b.Property<ulong>("GuildId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("ChannelId")
.IsUnique();
b.HasIndex("GuildId");
b.ToTable("AutoTranslateChannels");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.AutoTranslateUser", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("ChannelId")
.HasColumnType("INTEGER");
b.Property<DateTime?>("DateAdded")
.HasColumnType("TEXT");
b.Property<string>("Source")
.HasColumnType("TEXT");
b.Property<string>("Target")
.HasColumnType("TEXT");
b.Property<ulong>("UserId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasAlternateKey("ChannelId", "UserId");
b.ToTable("AutoTranslateUsers");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.BanTemplate", b =>
{
b.Property<int>("Id")
@ -2194,6 +2250,17 @@ namespace NadekoBot.Migrations
b.Navigation("GuildConfig");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.AutoTranslateUser", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.AutoTranslateChannel", "Channel")
.WithMany("Users")
.HasForeignKey("ChannelId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Channel");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null)
@ -2541,6 +2608,11 @@ namespace NadekoBot.Migrations
b.Navigation("IgnoredChannels");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.AutoTranslateChannel", b =>
{
b.Navigation("Users");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b =>
{
b.Navigation("AntiAltSetting");

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);
}
}
[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);
}
}

View file

@ -1306,7 +1306,7 @@ poll:
autotranslang:
desc: "Sets your source and target language to be used with `{0}at`. Specify no parameters to remove previously set value."
args:
- "en>fr"
- "en fr"
autotranslate:
desc: "Starts automatic translation of all messages by users who set their `{0}atl` in this channel. You can set \"del\" parameter to automatically delete all translated user messages."
args:

View file

@ -450,6 +450,7 @@
"atl_set": "Your auto-translate language has been set to {0}>{1}",
"atl_started": "Started automatic translation of messages on this channel.",
"atl_stopped": "Stopped automatic translation of messages on this channel.",
"atl_not_enabled": "Automatic translation is not enabled on this channel.",
"bad_input_format": "Bad input format, or something went wrong.",
"card_not_found": "Couldn't find that card.",
"catfact": "fact",