mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-10 17:28:27 -04:00
- Generator will now also add [NadekoDescription] attribute to commands
- CustomReactions module (and customreactions db table) renamed to Expressions. This was done to remove confusion about how it relates to discord Reactions (it doesn't, it was created and named before discord reactions existed) - Permissionv2 db table renamed to Permissions - Expression command now start with ex/expr and end with the name of the action or setting. For example .exd is expression delete - CommandStrings will now use methodname as the key, and not the command name (first entry in aliases.yml). In other words aliases.yml and commands.en-US.yml will use the same keys (once again).
This commit is contained in:
@@ -171,6 +171,7 @@ public class CmdAttribute : System.Attribute
|
||||
foreach (var method in model.Methods)
|
||||
{
|
||||
tw.WriteLine("[NadekoCommand]");
|
||||
tw.WriteLine("[NadekoDescription]");
|
||||
tw.WriteLine("[Aliases]");
|
||||
tw.WriteLine($"public partial {method.ReturnType} {method.MethodName}({string.Join(", ", method.Params)});");
|
||||
}
|
||||
|
@@ -14,6 +14,7 @@ namespace NadekoBot.Tests
|
||||
private const string responsesPath = "../../../../NadekoBot/data/strings/responses";
|
||||
private const string commandsPath = "../../../../NadekoBot/data/strings/commands";
|
||||
private const string aliasesPath = "../../../../NadekoBot/data/aliases.yml";
|
||||
|
||||
[Test]
|
||||
public void AllCommandNamesHaveStrings()
|
||||
{
|
||||
|
@@ -337,6 +337,7 @@ resharper_csharp_wrap_parameters_style = chop_if_long
|
||||
resharper_force_chop_compound_if_expression = false
|
||||
resharper_keep_existing_linebreaks = false
|
||||
resharper_max_formal_parameters_on_line = 3
|
||||
resharper_place_simple_embedded_statement_on_same_line = false
|
||||
resharper_wrap_chained_binary_expressions = chop_if_long
|
||||
resharper_wrap_chained_binary_patterns = chop_if_long
|
||||
resharper_wrap_chained_method_calls = chop_if_long
|
||||
@@ -345,4 +346,4 @@ resharper_csharp_wrap_before_first_type_parameter_constraint = true
|
||||
resharper_csharp_place_type_constraints_on_same_line = false
|
||||
resharper_csharp_wrap_before_extends_colon = true
|
||||
resharper_csharp_place_constructor_initializer_on_same_line = false
|
||||
resharper_force_attribute_style = separate
|
||||
resharper_force_attribute_style = separate
|
||||
|
@@ -1,3 +1,5 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace NadekoBot.Common.Attributes;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
@@ -7,4 +9,22 @@ internal sealed class NadekoModuleAttribute : GroupAttribute
|
||||
: base(moduleName)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
internal sealed class NadekoDescriptionAttribute : SummaryAttribute
|
||||
{
|
||||
public NadekoDescriptionAttribute([CallerMemberName] string name = "")
|
||||
: base(name)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
internal sealed class NadekoUsageAttribute : RemarksAttribute
|
||||
{
|
||||
public NadekoUsageAttribute([CallerMemberName] string name = "")
|
||||
: base(name)
|
||||
{
|
||||
}
|
||||
}
|
@@ -1,5 +1,5 @@
|
||||
#nullable disable
|
||||
using NadekoBot.Modules.CustomReactions;
|
||||
using NadekoBot.Modules.NadekoExpressions;
|
||||
|
||||
namespace NadekoBot.Common.TypeReaders;
|
||||
|
||||
@@ -35,12 +35,12 @@ public sealed class CommandOrCrTypeReader : NadekoTypeReader<CommandOrCrInfo>
|
||||
{
|
||||
private readonly CommandService _cmds;
|
||||
private readonly CommandHandler _commandHandler;
|
||||
private readonly CustomReactionsService _crs;
|
||||
private readonly NadekoExpressionsService _exprs;
|
||||
|
||||
public CommandOrCrTypeReader(CommandService cmds, CustomReactionsService crs, CommandHandler commandHandler)
|
||||
public CommandOrCrTypeReader(CommandService cmds, NadekoExpressionsService exprs, CommandHandler commandHandler)
|
||||
{
|
||||
_cmds = cmds;
|
||||
_crs = crs;
|
||||
_exprs = exprs;
|
||||
_commandHandler = commandHandler;
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ public sealed class CommandOrCrTypeReader : NadekoTypeReader<CommandOrCrInfo>
|
||||
{
|
||||
input = input.ToUpperInvariant();
|
||||
|
||||
if (_crs.ReactionExists(context.Guild?.Id, input))
|
||||
if (_exprs.ExpressionExists(context.Guild?.Id, input))
|
||||
return TypeReaderResult.FromSuccess(new CommandOrCrInfo(input, CommandOrCrInfo.Type.Custom));
|
||||
|
||||
var cmd = await new CommandTypeReader(_commandHandler, _cmds).ReadAsync(context, input);
|
||||
|
@@ -34,7 +34,7 @@ public sealed class ModuleOrCrTypeReader : NadekoTypeReader<ModuleOrCrInfo>
|
||||
var module = _cmds.Modules.GroupBy(m => m.GetTopLevelModule())
|
||||
.FirstOrDefault(m => m.Key.Name.ToUpperInvariant() == input)
|
||||
?.Key;
|
||||
if (module is null && input != "ACTUALCUSTOMREACTIONS")
|
||||
if (module is null && input != "ACTUALEXPRESSIONS")
|
||||
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "No such module found."));
|
||||
|
||||
return Task.FromResult(TypeReaderResult.FromSuccess(new ModuleOrCrInfo { Name = input }));
|
||||
|
@@ -1,18 +0,0 @@
|
||||
#nullable disable
|
||||
using LinqToDB;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Db;
|
||||
|
||||
public static class CustomReactionsExtensions
|
||||
{
|
||||
public static int ClearFromGuild(this DbSet<CustomReaction> crs, ulong guildId)
|
||||
=> crs.Delete(x => x.GuildId == guildId);
|
||||
|
||||
public static IEnumerable<CustomReaction> ForId(this DbSet<CustomReaction> crs, ulong id)
|
||||
=> crs.AsNoTracking().AsQueryable().Where(x => x.GuildId == id).ToList();
|
||||
|
||||
public static CustomReaction GetByGuildIdAndInput(this DbSet<CustomReaction> crs, ulong? guildId, string input)
|
||||
=> crs.FirstOrDefault(x => x.GuildId == guildId && x.Trigger.ToUpper() == input);
|
||||
}
|
18
src/NadekoBot/Db/Extensions/NadekoExpressionExtensions.cs
Normal file
18
src/NadekoBot/Db/Extensions/NadekoExpressionExtensions.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
#nullable disable
|
||||
using LinqToDB;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Db;
|
||||
|
||||
public static class NadekoExpressionExtensions
|
||||
{
|
||||
public static int ClearFromGuild(this DbSet<CustomReaction> exprs, ulong guildId)
|
||||
=> exprs.Delete(x => x.GuildId == guildId);
|
||||
|
||||
public static IEnumerable<CustomReaction> ForId(this DbSet<CustomReaction> exprs, ulong id)
|
||||
=> exprs.AsNoTracking().AsQueryable().Where(x => x.GuildId == id).ToList();
|
||||
|
||||
public static CustomReaction GetByGuildIdAndInput(this DbSet<CustomReaction> exprs, ulong? guildId, string input)
|
||||
=> exprs.FirstOrDefault(x => x.GuildId == guildId && x.Trigger.ToUpper() == input);
|
||||
}
|
@@ -34,7 +34,7 @@ public class NadekoContext : DbContext
|
||||
public DbSet<Reminder> Reminders { get; set; }
|
||||
public DbSet<SelfAssignedRole> SelfAssignableRoles { get; set; }
|
||||
public DbSet<MusicPlaylist> MusicPlaylists { get; set; }
|
||||
public DbSet<CustomReaction> CustomReactions { get; set; }
|
||||
public DbSet<CustomReaction> Expressions { get; set; }
|
||||
public DbSet<CurrencyTransaction> CurrencyTransactions { get; set; }
|
||||
public DbSet<WaifuUpdate> WaifuUpdates { get; set; }
|
||||
public DbSet<Warning> Warnings { get; set; }
|
||||
@@ -62,6 +62,8 @@ public class NadekoContext : DbContext
|
||||
public DbSet<NsfwBlacklistedTag> NsfwBlacklistedTags { get; set; }
|
||||
public DbSet<AutoTranslateChannel> AutoTranslateChannels { get; set; }
|
||||
public DbSet<AutoTranslateUser> AutoTranslateUsers { get; set; }
|
||||
|
||||
public DbSet<Permissionv2> Permissions { get; set; }
|
||||
|
||||
public NadekoContext(DbContextOptions<NadekoContext> options)
|
||||
: base(options)
|
||||
|
2726
src/NadekoBot/Migrations/20220102102344_crs-rename-to-expressions-perm-rename.Designer.cs
generated
Normal file
2726
src/NadekoBot/Migrations/20220102102344_crs-rename-to-expressions-perm-rename.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,103 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace NadekoBot.Migrations
|
||||
{
|
||||
public partial class crsrenametoexpressionspermrename : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Permissionv2_GuildConfigs_GuildConfigId",
|
||||
table: "Permissionv2");
|
||||
|
||||
migrationBuilder.DropPrimaryKey(
|
||||
name: "PK_Permissionv2",
|
||||
table: "Permissionv2");
|
||||
|
||||
migrationBuilder.DropPrimaryKey(
|
||||
name: "PK_CustomReactions",
|
||||
table: "CustomReactions");
|
||||
|
||||
migrationBuilder.RenameTable(
|
||||
name: "Permissionv2",
|
||||
newName: "Permissions");
|
||||
|
||||
migrationBuilder.RenameTable(
|
||||
name: "CustomReactions",
|
||||
newName: "Expressions");
|
||||
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "IX_Permissionv2_GuildConfigId",
|
||||
table: "Permissions",
|
||||
newName: "IX_Permissions_GuildConfigId");
|
||||
|
||||
migrationBuilder.AddPrimaryKey(
|
||||
name: "PK_Permissions",
|
||||
table: "Permissions",
|
||||
column: "Id");
|
||||
|
||||
migrationBuilder.AddPrimaryKey(
|
||||
name: "PK_Expressions",
|
||||
table: "Expressions",
|
||||
column: "Id");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Permissions_GuildConfigs_GuildConfigId",
|
||||
table: "Permissions",
|
||||
column: "GuildConfigId",
|
||||
principalTable: "GuildConfigs",
|
||||
principalColumn: "Id");
|
||||
|
||||
migrationBuilder.Sql(@"UPDATE Permissions
|
||||
SET SecondaryTargetName='ACTUALEXPRESSIONS'
|
||||
WHERE SecondaryTargetName='ActualCustomReactions' COLLATE NOCASE;");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Permissions_GuildConfigs_GuildConfigId",
|
||||
table: "Permissions");
|
||||
|
||||
migrationBuilder.DropPrimaryKey(
|
||||
name: "PK_Permissions",
|
||||
table: "Permissions");
|
||||
|
||||
migrationBuilder.DropPrimaryKey(
|
||||
name: "PK_Expressions",
|
||||
table: "Expressions");
|
||||
|
||||
migrationBuilder.RenameTable(
|
||||
name: "Permissions",
|
||||
newName: "Permissionv2");
|
||||
|
||||
migrationBuilder.RenameTable(
|
||||
name: "Expressions",
|
||||
newName: "CustomReactions");
|
||||
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "IX_Permissions_GuildConfigId",
|
||||
table: "Permissionv2",
|
||||
newName: "IX_Permissionv2_GuildConfigId");
|
||||
|
||||
migrationBuilder.AddPrimaryKey(
|
||||
name: "PK_Permissionv2",
|
||||
table: "Permissionv2",
|
||||
column: "Id");
|
||||
|
||||
migrationBuilder.AddPrimaryKey(
|
||||
name: "PK_CustomReactions",
|
||||
table: "CustomReactions",
|
||||
column: "Id");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Permissionv2_GuildConfigs_GuildConfigId",
|
||||
table: "Permissionv2",
|
||||
column: "GuildConfigId",
|
||||
principalTable: "GuildConfigs",
|
||||
principalColumn: "Id");
|
||||
}
|
||||
}
|
||||
}
|
@@ -5,6 +5,8 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using NadekoBot.Services.Database;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace NadekoBot.Migrations
|
||||
{
|
||||
[DbContext(typeof(NadekoContext))]
|
||||
@@ -13,8 +15,7 @@ namespace NadekoBot.Migrations
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "5.0.8");
|
||||
modelBuilder.HasAnnotation("ProductVersion", "6.0.1");
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Db.Models.ClubApplicants", b =>
|
||||
{
|
||||
@@ -549,7 +550,7 @@ namespace NadekoBot.Migrations
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("CustomReactions");
|
||||
b.ToTable("Expressions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.DelMsgOnCmdChannel", b =>
|
||||
@@ -681,28 +682,6 @@ namespace NadekoBot.Migrations
|
||||
b.ToTable("FilterChannelId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterLinksChannelId", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<ulong>("ChannelId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime?>("DateAdded")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("GuildConfigId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("GuildConfigId");
|
||||
|
||||
b.ToTable("FilterLinksChannelId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -725,6 +704,28 @@ namespace NadekoBot.Migrations
|
||||
b.ToTable("FilteredWord");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterLinksChannelId", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<ulong>("ChannelId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime?>("DateAdded")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("GuildConfigId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("GuildConfigId");
|
||||
|
||||
b.ToTable("FilterLinksChannelId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -1177,7 +1178,7 @@ namespace NadekoBot.Migrations
|
||||
|
||||
b.HasIndex("GuildConfigId");
|
||||
|
||||
b.ToTable("Permissionv2");
|
||||
b.ToTable("Permissions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.PlantedCurrency", b =>
|
||||
@@ -2311,13 +2312,6 @@ namespace NadekoBot.Migrations
|
||||
.HasForeignKey("GuildConfigId1");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterLinksChannelId", b =>
|
||||
{
|
||||
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null)
|
||||
.WithMany("FilterLinksChannelIds")
|
||||
.HasForeignKey("GuildConfigId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b =>
|
||||
{
|
||||
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null)
|
||||
@@ -2325,6 +2319,13 @@ namespace NadekoBot.Migrations
|
||||
.HasForeignKey("GuildConfigId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterLinksChannelId", b =>
|
||||
{
|
||||
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null)
|
||||
.WithMany("FilterLinksChannelIds")
|
||||
.HasForeignKey("GuildConfigId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b =>
|
||||
{
|
||||
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig")
|
||||
@@ -2629,14 +2630,14 @@ namespace NadekoBot.Migrations
|
||||
|
||||
b.Navigation("FeedSubs");
|
||||
|
||||
b.Navigation("FilteredWords");
|
||||
|
||||
b.Navigation("FilterInvitesChannelIds");
|
||||
|
||||
b.Navigation("FilterLinksChannelIds");
|
||||
|
||||
b.Navigation("FilterWordsChannelIds");
|
||||
|
||||
b.Navigation("FilteredWords");
|
||||
|
||||
b.Navigation("FollowedStreams");
|
||||
|
||||
b.Navigation("GenerateCurrencyChannelIds");
|
||||
|
@@ -110,7 +110,7 @@ namespace NadekoBot.Modules.Administration
|
||||
//[NadekoCommand, Usage, Description, Aliases]
|
||||
//[OwnerOnly]
|
||||
//public partial Task DeleteUnusedCrnQ() =>
|
||||
// SqlExec(DangerousCommandsService.DeleteUnusedCustomReactionsAndQuotes);
|
||||
// SqlExec(DangerousCommandsService.DeleteUnusedExpressionsAndQuotes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -31,7 +31,7 @@ SET ClubId=NULL,
|
||||
DELETE FROM ClubApplicants;
|
||||
DELETE FROM ClubBans;
|
||||
DELETE FROM Clubs;";
|
||||
// public const string DeleteUnusedCustomReactionsAndQuotes = @"DELETE FROM CustomReactions
|
||||
// public const string DeleteUnusedExpressionsAndQuotes = @"DELETE FROM Expressions
|
||||
//WHERE UseCount=0 AND (DateAdded < date('now', '-7 day') OR DateAdded is null);
|
||||
|
||||
//DELETE FROM Quotes
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#nullable disable
|
||||
using NadekoBot.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Modules.CustomReactions;
|
||||
namespace NadekoBot.Modules.NadekoExpressions;
|
||||
|
||||
public class ExportedExpr
|
||||
{
|
||||
|
10
src/NadekoBot/Modules/CustomReactions/ExprField.cs
Normal file
10
src/NadekoBot/Modules/CustomReactions/ExprField.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace NadekoBot.Modules.NadekoExpressions;
|
||||
|
||||
public enum ExprField
|
||||
{
|
||||
AutoDelete,
|
||||
DmResponse,
|
||||
AllowTarget,
|
||||
ContainsAnywhere,
|
||||
Message
|
||||
}
|
@@ -2,9 +2,9 @@
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace NadekoBot.Modules.CustomReactions;
|
||||
namespace NadekoBot.Modules.NadekoExpressions;
|
||||
|
||||
public static class CustomReactionExtensions
|
||||
public static class NadekoExpressionExtensions
|
||||
{
|
||||
private static string ResolveTriggerString(this string str, DiscordSocketClient client)
|
||||
=> str.Replace("%bot.mention%", client.CurrentUser.Mention, StringComparison.Ordinal);
|
@@ -1,8 +1,11 @@
|
||||
#nullable disable
|
||||
|
||||
namespace NadekoBot.Modules.CustomReactions;
|
||||
namespace NadekoBot.Modules.NadekoExpressions;
|
||||
|
||||
public partial class NadekoExpressions : NadekoModule<CustomReactionsService>
|
||||
// todo string.join to .Join(", ")
|
||||
|
||||
[Name("Expressions")]
|
||||
public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
|
||||
{
|
||||
public enum All
|
||||
{
|
||||
@@ -23,7 +26,7 @@ public partial class NadekoExpressions : NadekoModule<CustomReactionsService>
|
||||
|| (ctx.Guild is not null && ((IGuildUser)ctx.User).GuildPermissions.Administrator);
|
||||
|
||||
[Cmd]
|
||||
public async partial Task AddCustReact(string key, [Leftover] string message)
|
||||
public async partial Task ExprAdd(string key, [Leftover] string message)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(message) || string.IsNullOrWhiteSpace(key))
|
||||
return;
|
||||
@@ -34,19 +37,19 @@ public partial class NadekoExpressions : NadekoModule<CustomReactionsService>
|
||||
return;
|
||||
}
|
||||
|
||||
var cr = await _service.AddAsync(ctx.Guild?.Id, key, message);
|
||||
var ex = await _service.AddAsync(ctx.Guild?.Id, key, message);
|
||||
|
||||
await ctx.Channel.EmbedAsync(_eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.new_cust_react))
|
||||
.WithDescription($"#{cr.Id}")
|
||||
.WithDescription($"#{ex.Id}")
|
||||
.AddField(GetText(strs.trigger), key)
|
||||
.AddField(GetText(strs.response),
|
||||
message.Length > 1024 ? GetText(strs.redacted_too_long) : message));
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
public async partial Task EditCustReact(kwum id, [Leftover] string message)
|
||||
public async partial Task ExprEdit(kwum id, [Leftover] string message)
|
||||
{
|
||||
var channel = ctx.Channel as ITextChannel;
|
||||
if (string.IsNullOrWhiteSpace(message) || id < 0)
|
||||
@@ -59,13 +62,13 @@ public partial class NadekoExpressions : NadekoModule<CustomReactionsService>
|
||||
return;
|
||||
}
|
||||
|
||||
var cr = await _service.EditAsync(ctx.Guild?.Id, id, message);
|
||||
if (cr is not null)
|
||||
var ex = await _service.EditAsync(ctx.Guild?.Id, id, message);
|
||||
if (ex is not null)
|
||||
await ctx.Channel.EmbedAsync(_eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.edited_cust_react))
|
||||
.WithDescription($"#{id}")
|
||||
.AddField(GetText(strs.trigger), cr.Trigger)
|
||||
.AddField(GetText(strs.trigger), ex.Trigger)
|
||||
.AddField(GetText(strs.response),
|
||||
message.Length > 1024 ? GetText(strs.redacted_too_long) : message));
|
||||
else
|
||||
@@ -74,14 +77,14 @@ public partial class NadekoExpressions : NadekoModule<CustomReactionsService>
|
||||
|
||||
[Cmd]
|
||||
[Priority(1)]
|
||||
public async partial Task ListCustReact(int page = 1)
|
||||
public async partial Task ExprList(int page = 1)
|
||||
{
|
||||
if (--page < 0 || page > 999)
|
||||
return;
|
||||
|
||||
var customReactions = _service.GetCustomReactionsFor(ctx.Guild?.Id);
|
||||
var expressions = _service.GetExpressionsFor(ctx.Guild?.Id);
|
||||
|
||||
if (customReactions is null || !customReactions.Any())
|
||||
if (expressions is null || !expressions.Any())
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.no_found);
|
||||
return;
|
||||
@@ -90,28 +93,28 @@ public partial class NadekoExpressions : NadekoModule<CustomReactionsService>
|
||||
await ctx.SendPaginatedConfirmAsync(page,
|
||||
curPage =>
|
||||
{
|
||||
var desc = customReactions.OrderBy(cr => cr.Trigger)
|
||||
var desc = expressions.OrderBy(ex => ex.Trigger)
|
||||
.Skip(curPage * 20)
|
||||
.Take(20)
|
||||
.Select(cr => $"{(cr.ContainsAnywhere ? "🗯" : "◾")}"
|
||||
+ $"{(cr.DmResponse ? "✉" : "◾")}"
|
||||
+ $"{(cr.AutoDeleteTrigger ? "❌" : "◾")}"
|
||||
+ $"`{(kwum)cr.Id}` {cr.Trigger}"
|
||||
+ (string.IsNullOrWhiteSpace(cr.Reactions)
|
||||
.Select(ex => $"{(ex.ContainsAnywhere ? "🗯" : "◾")}"
|
||||
+ $"{(ex.DmResponse ? "✉" : "◾")}"
|
||||
+ $"{(ex.AutoDeleteTrigger ? "❌" : "◾")}"
|
||||
+ $"`{(kwum)ex.Id}` {ex.Trigger}"
|
||||
+ (string.IsNullOrWhiteSpace(ex.Reactions)
|
||||
? string.Empty
|
||||
: " // " + string.Join(" ", cr.GetReactions())))
|
||||
: " // " + string.Join(" ", ex.GetReactions())))
|
||||
.Join('\n');
|
||||
|
||||
return _eb.Create().WithOkColor().WithTitle(GetText(strs.custom_reactions)).WithDescription(desc);
|
||||
},
|
||||
customReactions.Length,
|
||||
expressions.Length,
|
||||
20);
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
public async partial Task ShowCustReact(kwum id)
|
||||
public async partial Task ExprShow(kwum id)
|
||||
{
|
||||
var found = _service.GetCustomReaction(ctx.Guild?.Id, id);
|
||||
var found = _service.GetExpression(ctx.Guild?.Id, id);
|
||||
|
||||
if (found is null)
|
||||
{
|
||||
@@ -128,7 +131,7 @@ public partial class NadekoExpressions : NadekoModule<CustomReactionsService>
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
public async partial Task DelCustReact(kwum id)
|
||||
public async partial Task ExprDelete(kwum id)
|
||||
{
|
||||
if (!AdminInGuildOrOwnerInDm())
|
||||
{
|
||||
@@ -136,21 +139,21 @@ public partial class NadekoExpressions : NadekoModule<CustomReactionsService>
|
||||
return;
|
||||
}
|
||||
|
||||
var cr = await _service.DeleteAsync(ctx.Guild?.Id, id);
|
||||
var ex = await _service.DeleteAsync(ctx.Guild?.Id, id);
|
||||
|
||||
if (cr is not null)
|
||||
if (ex is not null)
|
||||
await ctx.Channel.EmbedAsync(_eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.deleted))
|
||||
.WithDescription($"#{id}")
|
||||
.AddField(GetText(strs.trigger), cr.Trigger.TrimTo(1024))
|
||||
.AddField(GetText(strs.response), cr.Response.TrimTo(1024)));
|
||||
.AddField(GetText(strs.trigger), ex.Trigger.TrimTo(1024))
|
||||
.AddField(GetText(strs.response), ex.Response.TrimTo(1024)));
|
||||
else
|
||||
await ReplyErrorLocalizedAsync(strs.no_found_id);
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
public async partial Task CrReact(kwum id, params string[] emojiStrs)
|
||||
public async partial Task ExprReact(kwum id, params string[] emojiStrs)
|
||||
{
|
||||
if (!AdminInGuildOrOwnerInDm())
|
||||
{
|
||||
@@ -158,8 +161,8 @@ public partial class NadekoExpressions : NadekoModule<CustomReactionsService>
|
||||
return;
|
||||
}
|
||||
|
||||
var cr = _service.GetCustomReaction(ctx.Guild?.Id, id);
|
||||
if (cr is null)
|
||||
var ex = _service.GetExpression(ctx.Guild?.Id, id);
|
||||
if (ex is null)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.no_found);
|
||||
return;
|
||||
@@ -167,7 +170,7 @@ public partial class NadekoExpressions : NadekoModule<CustomReactionsService>
|
||||
|
||||
if (emojiStrs.Length == 0)
|
||||
{
|
||||
await _service.ResetCrReactions(ctx.Guild?.Id, id);
|
||||
await _service.ResetExprReactions(ctx.Guild?.Id, id);
|
||||
await ReplyConfirmLocalizedAsync(strs.crr_reset(Format.Bold(id.ToString())));
|
||||
return;
|
||||
}
|
||||
@@ -196,39 +199,39 @@ public partial class NadekoExpressions : NadekoModule<CustomReactionsService>
|
||||
return;
|
||||
}
|
||||
|
||||
await _service.SetCrReactions(ctx.Guild?.Id, id, succ);
|
||||
await _service.SetExprReactions(ctx.Guild?.Id, id, succ);
|
||||
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.crr_set(Format.Bold(id.ToString()),
|
||||
string.Join(", ", succ.Select(x => x.ToString()))));
|
||||
succ.Select(static x => x.ToString()).Join(", ")));
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
public partial Task CrCa(kwum id)
|
||||
=> InternalCrEdit(id, CustomReactionsService.CrField.ContainsAnywhere);
|
||||
public partial Task ExprCa(kwum id)
|
||||
=> InternalExprEdit(id, ExprField.ContainsAnywhere);
|
||||
|
||||
[Cmd]
|
||||
public partial Task CrDm(kwum id)
|
||||
=> InternalCrEdit(id, CustomReactionsService.CrField.DmResponse);
|
||||
public partial Task ExprDm(kwum id)
|
||||
=> InternalExprEdit(id, ExprField.DmResponse);
|
||||
|
||||
[Cmd]
|
||||
public partial Task CrAd(kwum id)
|
||||
=> InternalCrEdit(id, CustomReactionsService.CrField.AutoDelete);
|
||||
public partial Task ExprAd(kwum id)
|
||||
=> InternalExprEdit(id, ExprField.AutoDelete);
|
||||
|
||||
[Cmd]
|
||||
public partial Task CrAt(kwum id)
|
||||
=> InternalCrEdit(id, CustomReactionsService.CrField.AllowTarget);
|
||||
public partial Task ExprAt(kwum id)
|
||||
=> InternalExprEdit(id, ExprField.AllowTarget);
|
||||
|
||||
[Cmd]
|
||||
[OwnerOnly]
|
||||
public async partial Task CrsReload()
|
||||
public async partial Task ExprsReload()
|
||||
{
|
||||
await _service.TriggerReloadCustomReactions();
|
||||
await _service.TriggerReloadExpressions();
|
||||
|
||||
await ctx.OkAsync();
|
||||
}
|
||||
|
||||
private async Task InternalCrEdit(kwum id, CustomReactionsService.CrField option)
|
||||
private async Task InternalExprEdit(kwum id, ExprField option)
|
||||
{
|
||||
if (!AdminInGuildOrOwnerInDm())
|
||||
{
|
||||
@@ -236,7 +239,7 @@ public partial class NadekoExpressions : NadekoModule<CustomReactionsService>
|
||||
return;
|
||||
}
|
||||
|
||||
var (success, newVal) = await _service.ToggleCrOptionAsync(id, option);
|
||||
var (success, newVal) = await _service.ToggleExprOptionAsync(id, option);
|
||||
if (!success)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.no_found_id);
|
||||
@@ -254,19 +257,19 @@ public partial class NadekoExpressions : NadekoModule<CustomReactionsService>
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async partial Task CrClear()
|
||||
public async partial Task ExprClear()
|
||||
{
|
||||
if (await PromptUserConfirmAsync(_eb.Create()
|
||||
.WithTitle("Custom reaction clear")
|
||||
.WithDescription("This will delete all custom reactions on this server.")))
|
||||
{
|
||||
var count = _service.DeleteAllCustomReactions(ctx.Guild.Id);
|
||||
var count = _service.DeleteAllExpressions(ctx.Guild.Id);
|
||||
await ReplyConfirmLocalizedAsync(strs.cleared(count));
|
||||
}
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
public async partial Task CrsExport()
|
||||
public async partial Task ExprsExport()
|
||||
{
|
||||
if (!AdminInGuildOrOwnerInDm())
|
||||
{
|
||||
@@ -276,16 +279,16 @@ public partial class NadekoExpressions : NadekoModule<CustomReactionsService>
|
||||
|
||||
_ = ctx.Channel.TriggerTypingAsync();
|
||||
|
||||
var serialized = _service.ExportCrs(ctx.Guild?.Id);
|
||||
var serialized = _service.ExportExpressions(ctx.Guild?.Id);
|
||||
await using var stream = await serialized.ToStream();
|
||||
await ctx.Channel.SendFileAsync(stream, "crs-export.yml");
|
||||
await ctx.Channel.SendFileAsync(stream, "exprs-export.yml");
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
#if GLOBAL_NADEKO
|
||||
[OwnerOnly]
|
||||
[OwnerOnly]
|
||||
#endif
|
||||
public async partial Task CrsImport([Leftover] string input = null)
|
||||
public async partial Task ExprsImport([Leftover] string input = null)
|
||||
{
|
||||
if (!AdminInGuildOrOwnerInDm())
|
||||
{
|
||||
@@ -316,7 +319,7 @@ public partial class NadekoExpressions : NadekoModule<CustomReactionsService>
|
||||
}
|
||||
}
|
||||
|
||||
var succ = await _service.ImportCrsAsync(ctx.Guild?.Id, input);
|
||||
var succ = await _service.ImportExpressionsAsync(ctx.Guild?.Id, input);
|
||||
if (!succ)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.expr_import_invalid_data);
|
||||
|
@@ -10,33 +10,28 @@ using System.Runtime.CompilerServices;
|
||||
using YamlDotNet.Serialization;
|
||||
using YamlDotNet.Serialization.NamingConventions;
|
||||
|
||||
namespace NadekoBot.Modules.CustomReactions;
|
||||
namespace NadekoBot.Modules.NadekoExpressions;
|
||||
|
||||
public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
|
||||
// todo recheck whether ACTUALEXPRESSIONS perm works
|
||||
// todo run db migration and rename ACTUALCUSTOMREACTIONS to ACTUALEXPRESSIONS
|
||||
|
||||
public sealed class NadekoExpressionsService : IEarlyBehavior, IReadyExecutor
|
||||
{
|
||||
public enum CrField
|
||||
{
|
||||
AutoDelete,
|
||||
DmResponse,
|
||||
AllowTarget,
|
||||
ContainsAnywhere,
|
||||
Message
|
||||
}
|
||||
|
||||
private const string MentionPh = "%bot.mention%";
|
||||
private const string MENTION_PH = "%bot.mention%";
|
||||
|
||||
private const string _prependExport =
|
||||
@"# Keys are triggers, Each key has a LIST of custom reactions in the following format:
|
||||
private const string PREPEND_EXPORT =
|
||||
@"# Keys are triggers, Each key has a LIST of expressions in the following format:
|
||||
# - res: Response string
|
||||
# id: Alphanumeric id used for commands related to the custom reaction. (Note, when using .crsimport, a new id will be generated.)
|
||||
# id: Alphanumeric id used for commands related to the expression. (Note, when using .exprsimport, a new id will be generated.)
|
||||
# react:
|
||||
# - <List
|
||||
# - of
|
||||
# - reactions>
|
||||
# at: Whether custom reaction allows targets (see .h .crat)
|
||||
# ca: Whether custom reaction expects trigger anywhere (see .h .crca)
|
||||
# dm: Whether custom reaction DMs the response (see .h .crdm)
|
||||
# ad: Whether custom reaction automatically deletes triggering message (see .h .crad)
|
||||
# at: Whether expression allows targets (see .h .exprat)
|
||||
# ca: Whether expression expects trigger anywhere (see .h .exprca)
|
||||
# dm: Whether expression DMs the response (see .h .exprdm)
|
||||
# ad: Whether expression automatically deletes triggering message (see .h .exprad)
|
||||
|
||||
";
|
||||
|
||||
@@ -53,19 +48,19 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
|
||||
public int Priority
|
||||
=> 0;
|
||||
|
||||
private readonly object _gcrWriteLock = new();
|
||||
private readonly object _gexprWriteLock = new();
|
||||
|
||||
private readonly TypedKey<CustomReaction> _gcrAddedKey = new("gcr.added");
|
||||
private readonly TypedKey<int> _gcrDeletedkey = new("gcr.deleted");
|
||||
private readonly TypedKey<CustomReaction> _gcrEditedKey = new("gcr.edited");
|
||||
private readonly TypedKey<bool> _crsReloadedKey = new("crs.reloaded");
|
||||
private readonly TypedKey<CustomReaction> _gexprAddedKey = new("gexpr.added");
|
||||
private readonly TypedKey<int> _gexprDeletedkey = new("gexpr.deleted");
|
||||
private readonly TypedKey<CustomReaction> _gexprEditedKey = new("gexpr.edited");
|
||||
private readonly TypedKey<bool> _exprsReloadedKey = new("exprs.reloaded");
|
||||
|
||||
// it is perfectly fine to have global customreactions as an array
|
||||
// 1. custom reactions are almost never added (compared to how many times they are being looped through)
|
||||
// it is perfectly fine to have global expressions as an array
|
||||
// 1. expressions are almost never added (compared to how many times they are being looped through)
|
||||
// 2. only need write locks for this as we'll rebuild+replace the array on every edit
|
||||
// 3. there's never many of them (at most a thousand, usually < 100)
|
||||
private CustomReaction[] _globalReactions;
|
||||
private ConcurrentDictionary<ulong, CustomReaction[]> _newGuildReactions;
|
||||
private CustomReaction[] globalReactions;
|
||||
private ConcurrentDictionary<ulong, CustomReaction[]> newGuildReactions;
|
||||
|
||||
private readonly DbService _db;
|
||||
private readonly DiscordSocketClient _client;
|
||||
@@ -81,7 +76,7 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
|
||||
|
||||
private bool ready;
|
||||
|
||||
public CustomReactionsService(
|
||||
public NadekoExpressionsService(
|
||||
PermissionService perms,
|
||||
DbService db,
|
||||
IBotStrings strings,
|
||||
@@ -105,10 +100,10 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
|
||||
_eb = eb;
|
||||
_rng = new NadekoRandom();
|
||||
|
||||
_pubSub.Sub(_crsReloadedKey, OnCrsShouldReload);
|
||||
pubSub.Sub(_gcrAddedKey, OnGcrAdded);
|
||||
pubSub.Sub(_gcrDeletedkey, OnGcrDeleted);
|
||||
pubSub.Sub(_gcrEditedKey, OnGcrEdited);
|
||||
_pubSub.Sub(_exprsReloadedKey, OnExprsShouldReload);
|
||||
pubSub.Sub(_gexprAddedKey, OnGexprAdded);
|
||||
pubSub.Sub(_gexprDeletedkey, OnGexprDeleted);
|
||||
pubSub.Sub(_gexprEditedKey, OnGexprEdited);
|
||||
|
||||
bot.JoinedGuild += OnJoinedGuild;
|
||||
_client.LeftGuild += OnLeftGuild;
|
||||
@@ -117,39 +112,39 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
|
||||
private async Task ReloadInternal(IReadOnlyList<ulong> allGuildIds)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
var guildItems = await uow.CustomReactions.AsNoTracking()
|
||||
var guildItems = await uow.Expressions.AsNoTracking()
|
||||
.Where(x => allGuildIds.Contains(x.GuildId.Value))
|
||||
.ToListAsync();
|
||||
|
||||
_newGuildReactions = guildItems.GroupBy(k => k.GuildId!.Value)
|
||||
newGuildReactions = guildItems.GroupBy(k => k.GuildId!.Value)
|
||||
.ToDictionary(g => g.Key,
|
||||
g => g.Select(x =>
|
||||
{
|
||||
x.Trigger = x.Trigger.Replace(MentionPh, _bot.Mention);
|
||||
x.Trigger = x.Trigger.Replace(MENTION_PH, _bot.Mention);
|
||||
return x;
|
||||
})
|
||||
.ToArray())
|
||||
.ToConcurrent();
|
||||
|
||||
lock (_gcrWriteLock)
|
||||
lock (_gexprWriteLock)
|
||||
{
|
||||
var globalItems = uow.CustomReactions.AsNoTracking()
|
||||
var globalItems = uow.Expressions.AsNoTracking()
|
||||
.Where(x => x.GuildId == null || x.GuildId == 0)
|
||||
.AsEnumerable()
|
||||
.Select(x =>
|
||||
{
|
||||
x.Trigger = x.Trigger.Replace(MentionPh, _bot.Mention);
|
||||
x.Trigger = x.Trigger.Replace(MENTION_PH, _bot.Mention);
|
||||
return x;
|
||||
})
|
||||
.ToArray();
|
||||
|
||||
_globalReactions = globalItems;
|
||||
globalReactions = globalItems;
|
||||
}
|
||||
|
||||
ready = true;
|
||||
}
|
||||
|
||||
private CustomReaction TryGetCustomReaction(IUserMessage umsg)
|
||||
private CustomReaction TryGetExpression(IUserMessage umsg)
|
||||
{
|
||||
if (!ready)
|
||||
return null;
|
||||
@@ -159,37 +154,37 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
|
||||
|
||||
var content = umsg.Content.Trim().ToLowerInvariant();
|
||||
|
||||
if (_newGuildReactions.TryGetValue(channel.Guild.Id, out var reactions) && reactions.Length > 0)
|
||||
if (newGuildReactions.TryGetValue(channel.Guild.Id, out var reactions) && reactions.Length > 0)
|
||||
{
|
||||
var cr = MatchCustomReactions(content, reactions);
|
||||
if (cr is not null)
|
||||
return cr;
|
||||
var expr = MatchExpressions(content, reactions);
|
||||
if (expr is not null)
|
||||
return expr;
|
||||
}
|
||||
|
||||
var localGrs = _globalReactions;
|
||||
var localGrs = globalReactions;
|
||||
|
||||
return MatchCustomReactions(content, localGrs);
|
||||
return MatchExpressions(content, localGrs);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private CustomReaction MatchCustomReactions(in ReadOnlySpan<char> content, CustomReaction[] crs)
|
||||
private CustomReaction MatchExpressions(in ReadOnlySpan<char> content, CustomReaction[] exprs)
|
||||
{
|
||||
var result = new List<CustomReaction>(1);
|
||||
for (var i = 0; i < crs.Length; i++)
|
||||
for (var i = 0; i < exprs.Length; i++)
|
||||
{
|
||||
var cr = crs[i];
|
||||
var trigger = cr.Trigger;
|
||||
var expr = exprs[i];
|
||||
var trigger = expr.Trigger;
|
||||
if (content.Length > trigger.Length)
|
||||
{
|
||||
// if input is greater than the trigger, it can only work if:
|
||||
// it has CA enabled
|
||||
if (cr.ContainsAnywhere)
|
||||
if (expr.ContainsAnywhere)
|
||||
{
|
||||
// if ca is enabled, we have to check if it is a word within the content
|
||||
var wp = content.GetWordPosition(trigger);
|
||||
|
||||
// if it is, then that's valid
|
||||
if (wp != WordPosition.None) result.Add(cr);
|
||||
if (wp != WordPosition.None) result.Add(expr);
|
||||
|
||||
// if it's not, then it cant' work under any circumstance,
|
||||
// because content is greater than the trigger length
|
||||
@@ -199,12 +194,12 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
|
||||
|
||||
// if CA is disabled, and CR has AllowTarget, then the
|
||||
// content has to start with the trigger followed by a space
|
||||
if (cr.AllowTarget
|
||||
if (expr.AllowTarget
|
||||
&& content.StartsWith(trigger, StringComparison.OrdinalIgnoreCase)
|
||||
&& content[trigger.Length] == ' ')
|
||||
result.Add(cr);
|
||||
result.Add(expr);
|
||||
}
|
||||
else if (content.Length < cr.Trigger.Length)
|
||||
else if (content.Length < expr.Trigger.Length)
|
||||
{
|
||||
// if input length is less than trigger length, it means
|
||||
// that the reaction can never be triggered
|
||||
@@ -213,7 +208,7 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
|
||||
{
|
||||
// if input length is the same as trigger length
|
||||
// reaction can only trigger if the strings are equal
|
||||
if (content.SequenceEqual(cr.Trigger)) result.Add(cr);
|
||||
if (content.SequenceEqual(expr.Trigger)) result.Add(expr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,21 +224,21 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
|
||||
|
||||
public async Task<bool> RunBehavior(IGuild guild, IUserMessage msg)
|
||||
{
|
||||
// maybe this message is a custom reaction
|
||||
var cr = TryGetCustomReaction(msg);
|
||||
// maybe this message is an expression
|
||||
var expr = TryGetExpression(msg);
|
||||
|
||||
if (cr is null || cr.Response == "-")
|
||||
if (expr is null || expr.Response == "-")
|
||||
return false;
|
||||
|
||||
if (await _cmdCds.TryBlock(guild, msg.Author, cr.Trigger))
|
||||
if (await _cmdCds.TryBlock(guild, msg.Author, expr.Trigger))
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
if (_gperm.BlockedModules.Contains("ActualCustomReactions"))
|
||||
if (_gperm.BlockedModules.Contains("ACTUALEXPRESSIONS"))
|
||||
{
|
||||
Log.Information(
|
||||
"User {UserName} [{UserId}] tried to use a custom reaction but 'ActualCustomReactions' are globally disabled.",
|
||||
"User {UserName} [{UserId}] tried to use an expression but 'ActualExpressions' are globally disabled",
|
||||
msg.Author.ToString(),
|
||||
msg.Author.Id);
|
||||
|
||||
@@ -253,7 +248,7 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
|
||||
if (guild is SocketGuild sg)
|
||||
{
|
||||
var pc = _perms.GetCacheFor(guild.Id);
|
||||
if (!pc.Permissions.CheckPermissions(msg, cr.Trigger, "ActualCustomReactions", out var index))
|
||||
if (!pc.Permissions.CheckPermissions(msg, expr.Trigger, "ACTUALEXPRESSIONS", out var index))
|
||||
{
|
||||
if (pc.Verbose)
|
||||
{
|
||||
@@ -276,9 +271,9 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
|
||||
}
|
||||
}
|
||||
|
||||
var sentMsg = await cr.Send(msg, _client, false);
|
||||
var sentMsg = await expr.Send(msg, _client, false);
|
||||
|
||||
var reactions = cr.GetReactions();
|
||||
var reactions = expr.GetReactions();
|
||||
foreach (var reaction in reactions)
|
||||
{
|
||||
try
|
||||
@@ -289,14 +284,14 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
|
||||
{
|
||||
Log.Warning("Unable to add reactions to message {Message} in server {GuildId}",
|
||||
sentMsg.Id,
|
||||
cr.GuildId);
|
||||
expr.GuildId);
|
||||
break;
|
||||
}
|
||||
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
|
||||
if (cr.AutoDeleteTrigger)
|
||||
if (expr.AutoDeleteTrigger)
|
||||
try
|
||||
{
|
||||
await msg.DeleteAsync();
|
||||
@@ -310,7 +305,7 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
|
||||
msg.Channel.Id,
|
||||
msg.Author.Id,
|
||||
msg.Author.ToString(),
|
||||
cr.Trigger);
|
||||
expr.Trigger);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -322,61 +317,61 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task ResetCrReactions(ulong? maybeGuildId, int id)
|
||||
public async Task ResetExprReactions(ulong? maybeGuildId, int id)
|
||||
{
|
||||
CustomReaction cr;
|
||||
CustomReaction expr;
|
||||
await using var uow = _db.GetDbContext();
|
||||
cr = uow.CustomReactions.GetById(id);
|
||||
if (cr is null)
|
||||
expr = uow.Expressions.GetById(id);
|
||||
if (expr is null)
|
||||
return;
|
||||
|
||||
cr.Reactions = string.Empty;
|
||||
expr.Reactions = string.Empty;
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private Task UpdateInternalAsync(ulong? maybeGuildId, CustomReaction cr)
|
||||
private Task UpdateInternalAsync(ulong? maybeGuildId, CustomReaction expr)
|
||||
{
|
||||
if (maybeGuildId is { } guildId)
|
||||
UpdateInternal(guildId, cr);
|
||||
UpdateInternal(guildId, expr);
|
||||
else
|
||||
return _pubSub.Pub(_gcrEditedKey, cr);
|
||||
return _pubSub.Pub(_gexprEditedKey, expr);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void UpdateInternal(ulong? maybeGuildId, CustomReaction cr)
|
||||
private void UpdateInternal(ulong? maybeGuildId, CustomReaction expr)
|
||||
{
|
||||
if (maybeGuildId is { } guildId)
|
||||
_newGuildReactions.AddOrUpdate(guildId,
|
||||
new[] { cr },
|
||||
newGuildReactions.AddOrUpdate(guildId,
|
||||
new[] { expr },
|
||||
(_, old) =>
|
||||
{
|
||||
var newArray = old.ToArray();
|
||||
for (var i = 0; i < newArray.Length; i++)
|
||||
if (newArray[i].Id == cr.Id)
|
||||
newArray[i] = cr;
|
||||
if (newArray[i].Id == expr.Id)
|
||||
newArray[i] = expr;
|
||||
return newArray;
|
||||
});
|
||||
else
|
||||
lock (_gcrWriteLock)
|
||||
lock (_gexprWriteLock)
|
||||
{
|
||||
var crs = _globalReactions;
|
||||
for (var i = 0; i < crs.Length; i++)
|
||||
if (crs[i].Id == cr.Id)
|
||||
crs[i] = cr;
|
||||
var exprs = globalReactions;
|
||||
for (var i = 0; i < exprs.Length; i++)
|
||||
if (exprs[i].Id == expr.Id)
|
||||
exprs[i] = expr;
|
||||
}
|
||||
}
|
||||
|
||||
private Task AddInternalAsync(ulong? maybeGuildId, CustomReaction cr)
|
||||
private Task AddInternalAsync(ulong? maybeGuildId, CustomReaction expr)
|
||||
{
|
||||
// only do this for perf purposes
|
||||
cr.Trigger = cr.Trigger.Replace(MentionPh, _client.CurrentUser.Mention);
|
||||
expr.Trigger = expr.Trigger.Replace(MENTION_PH, _client.CurrentUser.Mention);
|
||||
|
||||
if (maybeGuildId is { } guildId)
|
||||
_newGuildReactions.AddOrUpdate(guildId, new[] { cr }, (_, old) => old.With(cr));
|
||||
newGuildReactions.AddOrUpdate(guildId, new[] { expr }, (_, old) => old.With(expr));
|
||||
else
|
||||
return _pubSub.Pub(_gcrAddedKey, cr);
|
||||
return _pubSub.Pub(_gexprAddedKey, expr);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
@@ -385,125 +380,128 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
|
||||
{
|
||||
if (maybeGuildId is { } guildId)
|
||||
{
|
||||
_newGuildReactions.AddOrUpdate(guildId,
|
||||
newGuildReactions.AddOrUpdate(guildId,
|
||||
Array.Empty<CustomReaction>(),
|
||||
(key, old) => DeleteInternal(old, id, out _));
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
lock (_gcrWriteLock)
|
||||
lock (_gexprWriteLock)
|
||||
{
|
||||
var cr = Array.Find(_globalReactions, item => item.Id == id);
|
||||
if (cr is not null) return _pubSub.Pub(_gcrDeletedkey, cr.Id);
|
||||
var expr = Array.Find(globalReactions, item => item.Id == id);
|
||||
if (expr is not null) return _pubSub.Pub(_gexprDeletedkey, expr.Id);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private CustomReaction[] DeleteInternal(IReadOnlyList<CustomReaction> crs, int id, out CustomReaction deleted)
|
||||
private CustomReaction[] DeleteInternal(
|
||||
IReadOnlyList<CustomReaction> exprs,
|
||||
int id,
|
||||
out CustomReaction deleted)
|
||||
{
|
||||
deleted = null;
|
||||
if (crs is null || crs.Count == 0)
|
||||
return crs as CustomReaction[] ?? crs?.ToArray();
|
||||
if (exprs is null || exprs.Count == 0)
|
||||
return exprs as CustomReaction[] ?? exprs?.ToArray();
|
||||
|
||||
var newCrs = new CustomReaction[crs.Count - 1];
|
||||
for (int i = 0, k = 0; i < crs.Count; i++, k++)
|
||||
var newExprs = new CustomReaction[exprs.Count - 1];
|
||||
for (int i = 0, k = 0; i < exprs.Count; i++, k++)
|
||||
{
|
||||
if (crs[i].Id == id)
|
||||
if (exprs[i].Id == id)
|
||||
{
|
||||
deleted = crs[i];
|
||||
deleted = exprs[i];
|
||||
k--;
|
||||
continue;
|
||||
}
|
||||
|
||||
newCrs[k] = crs[i];
|
||||
newExprs[k] = exprs[i];
|
||||
}
|
||||
|
||||
return newCrs;
|
||||
return newExprs;
|
||||
}
|
||||
|
||||
public async Task SetCrReactions(ulong? guildId, int id, IEnumerable<string> emojis)
|
||||
public async Task SetExprReactions(ulong? guildId, int id, IEnumerable<string> emojis)
|
||||
{
|
||||
CustomReaction cr;
|
||||
CustomReaction expr;
|
||||
await using (var uow = _db.GetDbContext())
|
||||
{
|
||||
cr = uow.CustomReactions.GetById(id);
|
||||
if (cr is null)
|
||||
expr = uow.Expressions.GetById(id);
|
||||
if (expr is null)
|
||||
return;
|
||||
|
||||
cr.Reactions = string.Join("@@@", emojis);
|
||||
expr.Reactions = string.Join("@@@", emojis);
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
await UpdateInternalAsync(guildId, cr);
|
||||
await UpdateInternalAsync(guildId, expr);
|
||||
}
|
||||
|
||||
public async Task<(bool Sucess, bool NewValue)> ToggleCrOptionAsync(int id, CrField field)
|
||||
public async Task<(bool Sucess, bool NewValue)> ToggleExprOptionAsync(int id, ExprField field)
|
||||
{
|
||||
var newVal = false;
|
||||
CustomReaction cr;
|
||||
CustomReaction expr;
|
||||
await using (var uow = _db.GetDbContext())
|
||||
{
|
||||
cr = uow.CustomReactions.GetById(id);
|
||||
if (cr is null)
|
||||
expr = uow.Expressions.GetById(id);
|
||||
if (expr is null)
|
||||
return (false, false);
|
||||
if (field == CrField.AutoDelete)
|
||||
newVal = cr.AutoDeleteTrigger = !cr.AutoDeleteTrigger;
|
||||
else if (field == CrField.ContainsAnywhere)
|
||||
newVal = cr.ContainsAnywhere = !cr.ContainsAnywhere;
|
||||
else if (field == CrField.DmResponse)
|
||||
newVal = cr.DmResponse = !cr.DmResponse;
|
||||
else if (field == CrField.AllowTarget)
|
||||
newVal = cr.AllowTarget = !cr.AllowTarget;
|
||||
if (field == ExprField.AutoDelete)
|
||||
newVal = expr.AutoDeleteTrigger = !expr.AutoDeleteTrigger;
|
||||
else if (field == ExprField.ContainsAnywhere)
|
||||
newVal = expr.ContainsAnywhere = !expr.ContainsAnywhere;
|
||||
else if (field == ExprField.DmResponse)
|
||||
newVal = expr.DmResponse = !expr.DmResponse;
|
||||
else if (field == ExprField.AllowTarget)
|
||||
newVal = expr.AllowTarget = !expr.AllowTarget;
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
await UpdateInternalAsync(cr.GuildId, cr);
|
||||
await UpdateInternalAsync(expr.GuildId, expr);
|
||||
|
||||
return (true, newVal);
|
||||
}
|
||||
|
||||
public CustomReaction GetCustomReaction(ulong? guildId, int id)
|
||||
public CustomReaction GetExpression(ulong? guildId, int id)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
var cr = uow.CustomReactions.GetById(id);
|
||||
if (cr is null || cr.GuildId != guildId)
|
||||
var expr = uow.Expressions.GetById(id);
|
||||
if (expr is null || expr.GuildId != guildId)
|
||||
return null;
|
||||
|
||||
return cr;
|
||||
return expr;
|
||||
}
|
||||
|
||||
public int DeleteAllCustomReactions(ulong guildId)
|
||||
public int DeleteAllExpressions(ulong guildId)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
var count = uow.CustomReactions.ClearFromGuild(guildId);
|
||||
var count = uow.Expressions.ClearFromGuild(guildId);
|
||||
uow.SaveChanges();
|
||||
|
||||
_newGuildReactions.TryRemove(guildId, out _);
|
||||
newGuildReactions.TryRemove(guildId, out _);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
public bool ReactionExists(ulong? guildId, string input)
|
||||
public bool ExpressionExists(ulong? guildId, string input)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
var cr = uow.CustomReactions.GetByGuildIdAndInput(guildId, input);
|
||||
return cr is not null;
|
||||
var expr = uow.Expressions.GetByGuildIdAndInput(guildId, input);
|
||||
return expr is not null;
|
||||
}
|
||||
|
||||
public string ExportCrs(ulong? guildId)
|
||||
public string ExportExpressions(ulong? guildId)
|
||||
{
|
||||
var crs = GetCustomReactionsFor(guildId);
|
||||
var exprs = GetExpressionsFor(guildId);
|
||||
|
||||
var crsDict = crs.GroupBy(x => x.Trigger).ToDictionary(x => x.Key, x => x.Select(ExportedExpr.FromModel));
|
||||
var exprsDict = exprs.GroupBy(x => x.Trigger).ToDictionary(x => x.Key, x => x.Select(ExportedExpr.FromModel));
|
||||
|
||||
return _prependExport + _exportSerializer.Serialize(crsDict).UnescapeUnicodeCodePoints();
|
||||
return PREPEND_EXPORT + _exportSerializer.Serialize(exprsDict).UnescapeUnicodeCodePoints();
|
||||
}
|
||||
|
||||
public async Task<bool> ImportCrsAsync(ulong? guildId, string input)
|
||||
public async Task<bool> ImportExpressionsAsync(ulong? guildId, string input)
|
||||
{
|
||||
Dictionary<string, List<ExportedExpr>> data;
|
||||
try
|
||||
@@ -521,22 +519,22 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
|
||||
foreach (var entry in data)
|
||||
{
|
||||
var trigger = entry.Key;
|
||||
await uow.CustomReactions.AddRangeAsync(entry.Value.Where(cr => !string.IsNullOrWhiteSpace(cr.Res))
|
||||
.Select(cr => new CustomReaction
|
||||
await uow.Expressions.AddRangeAsync(entry.Value.Where(expr => !string.IsNullOrWhiteSpace(expr.Res))
|
||||
.Select(expr => new CustomReaction
|
||||
{
|
||||
GuildId = guildId,
|
||||
Response = cr.Res,
|
||||
Reactions = cr.React?.Join("@@@"),
|
||||
Response = expr.Res,
|
||||
Reactions = expr.React?.Join("@@@"),
|
||||
Trigger = trigger,
|
||||
AllowTarget = cr.At,
|
||||
ContainsAnywhere = cr.Ca,
|
||||
DmResponse = cr.Dm,
|
||||
AutoDeleteTrigger = cr.Ad
|
||||
AllowTarget = expr.At,
|
||||
ContainsAnywhere = expr.Ca,
|
||||
DmResponse = expr.Dm,
|
||||
AutoDeleteTrigger = expr.Ad
|
||||
}));
|
||||
}
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
await TriggerReloadCustomReactions();
|
||||
await TriggerReloadExpressions();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -545,54 +543,54 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
|
||||
public Task OnReadyAsync()
|
||||
=> ReloadInternal(_bot.GetCurrentGuildIds());
|
||||
|
||||
private ValueTask OnCrsShouldReload(bool _)
|
||||
private ValueTask OnExprsShouldReload(bool _)
|
||||
=> new(ReloadInternal(_bot.GetCurrentGuildIds()));
|
||||
|
||||
private ValueTask OnGcrAdded(CustomReaction c)
|
||||
private ValueTask OnGexprAdded(CustomReaction c)
|
||||
{
|
||||
lock (_gcrWriteLock)
|
||||
lock (_gexprWriteLock)
|
||||
{
|
||||
var newGlobalReactions = new CustomReaction[_globalReactions.Length + 1];
|
||||
Array.Copy(_globalReactions, newGlobalReactions, _globalReactions.Length);
|
||||
newGlobalReactions[_globalReactions.Length] = c;
|
||||
_globalReactions = newGlobalReactions;
|
||||
var newGlobalReactions = new CustomReaction[globalReactions.Length + 1];
|
||||
Array.Copy(globalReactions, newGlobalReactions, globalReactions.Length);
|
||||
newGlobalReactions[globalReactions.Length] = c;
|
||||
globalReactions = newGlobalReactions;
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
private ValueTask OnGcrEdited(CustomReaction c)
|
||||
private ValueTask OnGexprEdited(CustomReaction c)
|
||||
{
|
||||
lock (_gcrWriteLock)
|
||||
lock (_gexprWriteLock)
|
||||
{
|
||||
for (var i = 0; i < _globalReactions.Length; i++)
|
||||
if (_globalReactions[i].Id == c.Id)
|
||||
for (var i = 0; i < globalReactions.Length; i++)
|
||||
if (globalReactions[i].Id == c.Id)
|
||||
{
|
||||
_globalReactions[i] = c;
|
||||
globalReactions[i] = c;
|
||||
return default;
|
||||
}
|
||||
|
||||
// if edited cr is not found?!
|
||||
// if edited expr is not found?!
|
||||
// add it
|
||||
OnGcrAdded(c);
|
||||
OnGexprAdded(c);
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
private ValueTask OnGcrDeleted(int id)
|
||||
private ValueTask OnGexprDeleted(int id)
|
||||
{
|
||||
lock (_gcrWriteLock)
|
||||
lock (_gexprWriteLock)
|
||||
{
|
||||
var newGlobalReactions = DeleteInternal(_globalReactions, id, out _);
|
||||
_globalReactions = newGlobalReactions;
|
||||
var newGlobalReactions = DeleteInternal(globalReactions, id, out _);
|
||||
globalReactions = newGlobalReactions;
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
public Task TriggerReloadCustomReactions()
|
||||
=> _pubSub.Pub(_crsReloadedKey, true);
|
||||
public Task TriggerReloadExpressions()
|
||||
=> _pubSub.Pub(_exprsReloadedKey, true);
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -600,7 +598,7 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
|
||||
|
||||
private Task OnLeftGuild(SocketGuild arg)
|
||||
{
|
||||
_newGuildReactions.TryRemove(arg.Id, out _);
|
||||
newGuildReactions.TryRemove(arg.Id, out _);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
@@ -608,9 +606,9 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
|
||||
private async Task OnJoinedGuild(GuildConfig gc)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
var crs = await uow.CustomReactions.AsNoTracking().Where(x => x.GuildId == gc.GuildId).ToArrayAsync();
|
||||
var exprs = await uow.Expressions.AsNoTracking().Where(x => x.GuildId == gc.GuildId).ToArrayAsync();
|
||||
|
||||
_newGuildReactions[gc.GuildId] = crs;
|
||||
newGuildReactions[gc.GuildId] = exprs;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -620,59 +618,59 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
|
||||
public async Task<CustomReaction> AddAsync(ulong? guildId, string key, string message)
|
||||
{
|
||||
key = key.ToLowerInvariant();
|
||||
var cr = new CustomReaction { GuildId = guildId, Trigger = key, Response = message };
|
||||
var expr = new CustomReaction { GuildId = guildId, Trigger = key, Response = message };
|
||||
|
||||
if (cr.Response.Contains("%target%", StringComparison.OrdinalIgnoreCase))
|
||||
cr.AllowTarget = true;
|
||||
if (expr.Response.Contains("%target%", StringComparison.OrdinalIgnoreCase))
|
||||
expr.AllowTarget = true;
|
||||
|
||||
await using (var uow = _db.GetDbContext())
|
||||
{
|
||||
uow.CustomReactions.Add(cr);
|
||||
uow.Expressions.Add(expr);
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
await AddInternalAsync(guildId, cr);
|
||||
await AddInternalAsync(guildId, expr);
|
||||
|
||||
return cr;
|
||||
return expr;
|
||||
}
|
||||
|
||||
public async Task<CustomReaction> EditAsync(ulong? guildId, int id, string message)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
var cr = uow.CustomReactions.GetById(id);
|
||||
var expr = uow.Expressions.GetById(id);
|
||||
|
||||
if (cr is null || cr.GuildId != guildId)
|
||||
if (expr is null || expr.GuildId != guildId)
|
||||
return null;
|
||||
|
||||
// disable allowtarget if message had target, but it was removed from it
|
||||
if (!message.Contains("%target%", StringComparison.OrdinalIgnoreCase)
|
||||
&& cr.Response.Contains("%target%", StringComparison.OrdinalIgnoreCase))
|
||||
cr.AllowTarget = false;
|
||||
&& expr.Response.Contains("%target%", StringComparison.OrdinalIgnoreCase))
|
||||
expr.AllowTarget = false;
|
||||
|
||||
cr.Response = message;
|
||||
expr.Response = message;
|
||||
|
||||
// enable allow target if message is edited to contain target
|
||||
if (cr.Response.Contains("%target%", StringComparison.OrdinalIgnoreCase))
|
||||
cr.AllowTarget = true;
|
||||
if (expr.Response.Contains("%target%", StringComparison.OrdinalIgnoreCase))
|
||||
expr.AllowTarget = true;
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
await UpdateInternalAsync(guildId, cr);
|
||||
await UpdateInternalAsync(guildId, expr);
|
||||
|
||||
return cr;
|
||||
return expr;
|
||||
}
|
||||
|
||||
|
||||
public async Task<CustomReaction> DeleteAsync(ulong? guildId, int id)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
var toDelete = uow.CustomReactions.GetById(id);
|
||||
var toDelete = uow.Expressions.GetById(id);
|
||||
|
||||
if (toDelete is null)
|
||||
return null;
|
||||
|
||||
if ((toDelete.IsGlobal() && guildId is null) || guildId == toDelete.GuildId)
|
||||
{
|
||||
uow.CustomReactions.Remove(toDelete);
|
||||
uow.Expressions.Remove(toDelete);
|
||||
await uow.SaveChangesAsync();
|
||||
await DeleteInternalAsync(guildId, id);
|
||||
return toDelete;
|
||||
@@ -682,12 +680,12 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public CustomReaction[] GetCustomReactionsFor(ulong? maybeGuildId)
|
||||
public CustomReaction[] GetExpressionsFor(ulong? maybeGuildId)
|
||||
{
|
||||
if (maybeGuildId is { } guildId)
|
||||
return _newGuildReactions.TryGetValue(guildId, out var crs) ? crs : Array.Empty<CustomReaction>();
|
||||
return newGuildReactions.TryGetValue(guildId, out var exprs) ? exprs : Array.Empty<CustomReaction>();
|
||||
|
||||
return _globalReactions;
|
||||
return globalReactions;
|
||||
}
|
||||
|
||||
#endregion
|
13
src/NadekoBot/Modules/Help/CommandJsonObject.cs
Normal file
13
src/NadekoBot/Modules/Help/CommandJsonObject.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
#nullable disable
|
||||
namespace NadekoBot.Modules.Help;
|
||||
|
||||
internal class CommandJsonObject
|
||||
{
|
||||
public string[] Aliases { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string[] Usage { get; set; }
|
||||
public string Submodule { get; set; }
|
||||
public string Module { get; set; }
|
||||
public List<string> Options { get; set; }
|
||||
public string[] Requirements { get; set; }
|
||||
}
|
@@ -12,8 +12,9 @@ namespace NadekoBot.Modules.Help;
|
||||
|
||||
public partial class Help : NadekoModule<HelpService>
|
||||
{
|
||||
public const string PatreonUrl = "https://patreon.com/nadekobot";
|
||||
public const string PaypalUrl = "https://paypal.me/Kwoth";
|
||||
public const string PATREON_URL = "https://patreon.com/nadekobot";
|
||||
public const string PAYPAL_URL = "https://paypal.me/Kwoth";
|
||||
|
||||
private readonly CommandService _cmds;
|
||||
private readonly BotConfigService _bss;
|
||||
private readonly GlobalPermissionService _perms;
|
||||
@@ -106,8 +107,8 @@ public partial class Help : NadekoModule<HelpService>
|
||||
return strs.module_description_help;
|
||||
case "administration":
|
||||
return strs.module_description_administration;
|
||||
case "customreactions":
|
||||
return strs.module_description_customreactions;
|
||||
case "expressions":
|
||||
return strs.module_description_expressions;
|
||||
case "searches":
|
||||
return strs.module_description_searches;
|
||||
case "utility":
|
||||
@@ -313,7 +314,7 @@ public partial class Help : NadekoModule<HelpService>
|
||||
.Select(com =>
|
||||
{
|
||||
List<string> optHelpStr = null;
|
||||
var opt = ((NadekoOptionsAttribute)com.Attributes.FirstOrDefault(x
|
||||
var opt = ((NadekoOptionsAttribute)com.Attributes.FirstOrDefault(static x
|
||||
=> x is NadekoOptionsAttribute))
|
||||
?.OptionType;
|
||||
if (opt is not null) optHelpStr = HelpService.GetCommandOptionHelpList(opt);
|
||||
@@ -371,7 +372,7 @@ public partial class Help : NadekoModule<HelpService>
|
||||
var versionListString = Encoding.UTF8.GetString(ms.ToArray());
|
||||
|
||||
var versionList = JsonSerializer.Deserialize<List<string>>(versionListString);
|
||||
if (!versionList.Contains(StatsService.BotVersion))
|
||||
if (versionList is not null && !versionList.Contains(StatsService.BotVersion))
|
||||
{
|
||||
// save the file with new version added
|
||||
// versionList.Add(StatsService.BotVersion);
|
||||
@@ -410,16 +411,5 @@ public partial class Help : NadekoModule<HelpService>
|
||||
|
||||
[Cmd]
|
||||
public async partial Task Donate()
|
||||
=> await ReplyConfirmLocalizedAsync(strs.donate(PatreonUrl, PaypalUrl));
|
||||
}
|
||||
|
||||
internal class CommandJsonObject
|
||||
{
|
||||
public string[] Aliases { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string[] Usage { get; set; }
|
||||
public string Submodule { get; set; }
|
||||
public string Module { get; set; }
|
||||
public List<string> Options { get; set; }
|
||||
public string[] Requirements { get; set; }
|
||||
=> await ReplyConfirmLocalizedAsync(strs.donate(PATREON_URL, PAYPAL_URL));
|
||||
}
|
@@ -62,11 +62,13 @@ public class HelpService : ILateExecutor, INService
|
||||
var alias = com.Aliases.Skip(1).FirstOrDefault();
|
||||
if (alias is not null)
|
||||
str += $" **/ `{prefix + alias}`**";
|
||||
|
||||
var em = _eb.Create().AddField(str, $"{com.RealSummary(_strings, guild?.Id, prefix)}", true);
|
||||
|
||||
_dpos.TryGetOverrides(guild?.Id ?? 0, com.Name, out var overrides);
|
||||
var reqs = GetCommandRequirements(com, overrides);
|
||||
if (reqs.Any()) em.AddField(GetText(strs.requires, guild), string.Join("\n", reqs));
|
||||
if (reqs.Any())
|
||||
em.AddField(GetText(strs.requires, guild), string.Join("\n", reqs));
|
||||
|
||||
em.AddField(_strings.GetText(strs.usage),
|
||||
string.Join("\n",
|
||||
@@ -128,6 +130,7 @@ public class HelpService : ILateExecutor, INService
|
||||
{
|
||||
if (userPerm.ChannelPermission is { } cPerm)
|
||||
userPermString = GetPreconditionString(cPerm);
|
||||
|
||||
if (userPerm.GuildPermission is { } gPerm)
|
||||
userPermString = GetPreconditionString(gPerm);
|
||||
}
|
||||
@@ -149,11 +152,11 @@ public class HelpService : ILateExecutor, INService
|
||||
}
|
||||
|
||||
public static string GetPreconditionString(ChannelPerm perm)
|
||||
=> (perm + " Channel Permission").Replace("Guild", "Server", StringComparison.InvariantCulture);
|
||||
=> (perm + " Channel Permission").Replace("Guild", "Server");
|
||||
|
||||
public static string GetPreconditionString(GuildPerm perm)
|
||||
=> (perm + " Server Permission").Replace("Guild", "Server", StringComparison.InvariantCulture);
|
||||
=> (perm + " Server Permission").Replace("Guild", "Server");
|
||||
|
||||
private string GetText(LocStr str, IGuild guild, params object[] replacements)
|
||||
private string GetText(LocStr str, IGuild guild)
|
||||
=> _strings.GetText(str, guild?.Id);
|
||||
}
|
@@ -39,9 +39,9 @@ public static class PermissionExtensions
|
||||
string moduleName)
|
||||
{
|
||||
if (!((perm.SecondaryTarget == SecondaryPermissionType.Command
|
||||
&& perm.SecondaryTargetName.ToLowerInvariant() == commandName.ToLowerInvariant())
|
||||
&& string.Equals(perm.SecondaryTargetName, commandName, StringComparison.InvariantCultureIgnoreCase))
|
||||
|| (perm.SecondaryTarget == SecondaryPermissionType.Module
|
||||
&& perm.SecondaryTargetName.ToLowerInvariant() == moduleName.ToLowerInvariant())
|
||||
&& string.Equals(perm.SecondaryTargetName, moduleName, StringComparison.InvariantCultureIgnoreCase))
|
||||
|| perm.SecondaryTarget == SecondaryPermissionType.AllModules))
|
||||
return null;
|
||||
|
||||
|
@@ -44,8 +44,6 @@ public partial class Searches : NadekoModule<SearchesService>
|
||||
public async partial Task Rip([Leftover] IGuildUser usr)
|
||||
{
|
||||
var av = usr.RealAvatarUrl();
|
||||
if (av is null)
|
||||
return;
|
||||
await using var picStream = await _service.GetRipPictureAsync(usr.Nickname ?? usr.Username, av);
|
||||
await ctx.Channel.SendFileAsync(picStream,
|
||||
"rip.png",
|
||||
@@ -220,7 +218,7 @@ public partial class Searches : NadekoModule<SearchesService>
|
||||
var oterms = query?.Trim();
|
||||
if (!await ValidateQuery(query))
|
||||
return;
|
||||
query = WebUtility.UrlEncode(oterms).Replace(' ', '+');
|
||||
query = WebUtility.UrlEncode(oterms)?.Replace(' ', '+');
|
||||
try
|
||||
{
|
||||
var res = await _google.GetImageAsync(oterms);
|
||||
|
@@ -17,9 +17,17 @@ public partial class Utility
|
||||
_stats = stats;
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[OwnerOnly]
|
||||
public partial Task ServerInfo([Leftover] string guildName)
|
||||
=> InternalServerInfo(guildName);
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async partial Task ServerInfo(string guildName = null)
|
||||
public partial Task ServerInfo()
|
||||
=> InternalServerInfo();
|
||||
|
||||
private async Task InternalServerInfo(string guildName = null)
|
||||
{
|
||||
var channel = (ITextChannel)ctx.Channel;
|
||||
guildName = guildName?.ToUpperInvariant();
|
||||
@@ -103,7 +111,7 @@ public partial class Utility
|
||||
.WithOkColor();
|
||||
|
||||
var av = user.RealAvatarUrl();
|
||||
if (av is not null && av.IsAbsoluteUri)
|
||||
if (av.IsAbsoluteUri)
|
||||
embed.WithThumbnailUrl(av.ToString());
|
||||
await ctx.Channel.EmbedAsync(embed);
|
||||
}
|
||||
|
@@ -276,10 +276,10 @@ public partial class Utility
|
||||
quotes = uow.Quotes.GetForGuild(ctx.Guild.Id).ToList();
|
||||
}
|
||||
|
||||
var crsDict = quotes.GroupBy(x => x.Keyword)
|
||||
var exprsDict = quotes.GroupBy(x => x.Keyword)
|
||||
.ToDictionary(x => x.Key, x => x.Select(ExportedQuote.FromModel));
|
||||
|
||||
var text = _prependExport + _exportSerializer.Serialize(crsDict).UnescapeUnicodeCodePoints();
|
||||
var text = _prependExport + _exportSerializer.Serialize(exprsDict).UnescapeUnicodeCodePoints();
|
||||
|
||||
await using var stream = await text.ToStream();
|
||||
await ctx.Channel.SendFileAsync(stream, "quote-export.yml");
|
||||
@@ -317,7 +317,7 @@ public partial class Utility
|
||||
}
|
||||
}
|
||||
|
||||
var succ = await ImportCrsAsync(ctx.Guild.Id, input);
|
||||
var succ = await ImportExprsAsync(ctx.Guild.Id, input);
|
||||
if (!succ)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.expr_import_invalid_data);
|
||||
@@ -327,7 +327,7 @@ public partial class Utility
|
||||
await ctx.OkAsync();
|
||||
}
|
||||
|
||||
private async Task<bool> ImportCrsAsync(ulong guildId, string input)
|
||||
private async Task<bool> ImportExprsAsync(ulong guildId, string input)
|
||||
{
|
||||
Dictionary<string, List<ExportedQuote>> data;
|
||||
try
|
||||
|
@@ -31,5 +31,21 @@ public sealed class BotConfigService : ConfigServiceBase<BotConfig>
|
||||
private void Migrate()
|
||||
{
|
||||
if (data.Version < 2) ModifyConfig(c => c.Version = 2);
|
||||
|
||||
if (data.Version < 3)
|
||||
{
|
||||
ModifyConfig(c =>
|
||||
{
|
||||
c.Version = 3;
|
||||
c.Blocked.Modules = c.Blocked.Modules?.Select(static x
|
||||
=> string.Equals(x,
|
||||
"ActualCustomReactions",
|
||||
StringComparison.InvariantCultureIgnoreCase)
|
||||
? "ACTUALEXPRESSIONS"
|
||||
: x)
|
||||
.Distinct()
|
||||
.ToHashSet();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
namespace NadekoBot.Extensions;
|
||||
|
||||
// made for customreactions because they almost never get added
|
||||
// made for expressions because they almost never get added
|
||||
// and they get looped through constantly
|
||||
public static class ArrayExtensions
|
||||
{
|
||||
@@ -13,10 +13,10 @@ public static class ArrayExtensions
|
||||
/// <returns>A new array with the new element at the end</returns>
|
||||
public static T[] With<T>(this T[] input, T added)
|
||||
{
|
||||
var newCrs = new T[input.Length + 1];
|
||||
Array.Copy(input, 0, newCrs, 0, input.Length);
|
||||
newCrs[input.Length] = added;
|
||||
return newCrs;
|
||||
var newExprs = new T[input.Length + 1];
|
||||
Array.Copy(input, 0, newExprs, 0, input.Length);
|
||||
newExprs[input.Length] = added;
|
||||
return newExprs;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using Humanizer.Localisation;
|
||||
using NadekoBot.Modules.Administration.Services;
|
||||
using SixLabors.Fonts;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Drawing;
|
||||
@@ -120,20 +119,16 @@ public static class Extensions
|
||||
IBotStrings strings,
|
||||
ulong? guildId,
|
||||
string prefix)
|
||||
=> string.Format(strings.GetCommandStrings(cmd.Name, guildId).Desc, prefix);
|
||||
=> string.Format(strings.GetCommandStrings(cmd.Summary, guildId).Desc, prefix);
|
||||
|
||||
public static string[] RealRemarksArr(
|
||||
this CommandInfo cmd,
|
||||
IBotStrings strings,
|
||||
ulong? guildId,
|
||||
string prefix)
|
||||
=> Array.ConvertAll(strings.GetCommandStrings(cmd.MethodName(), guildId).Args,
|
||||
=> Array.ConvertAll(strings.GetCommandStrings(cmd.Remarks, guildId).Args,
|
||||
arg => GetFullUsage(cmd.Name, arg, prefix));
|
||||
|
||||
private static string MethodName(this CommandInfo cmd)
|
||||
=> ((NadekoCommandAttribute?)cmd.Attributes.FirstOrDefault(x => x is NadekoCommandAttribute))?.MethodName
|
||||
?? cmd.Name;
|
||||
|
||||
private static string GetFullUsage(string commandName, string args, string prefix)
|
||||
=> $"{prefix}{commandName} {string.Format(args, prefix)}";
|
||||
|
||||
|
@@ -92,29 +92,6 @@ iam:
|
||||
iamnot:
|
||||
- iamnot
|
||||
- iamn
|
||||
addcustreact:
|
||||
- addcustreact
|
||||
- acr
|
||||
- expradd
|
||||
- exadd
|
||||
- exa
|
||||
listcustreact:
|
||||
- listcustreact
|
||||
- lcr
|
||||
- exprli
|
||||
- exl
|
||||
showcustreact:
|
||||
- showcustreact
|
||||
- scr
|
||||
- exs
|
||||
delcustreact:
|
||||
- delcustreact
|
||||
- dcr
|
||||
- exprdel
|
||||
- exd
|
||||
crclear:
|
||||
- crclear
|
||||
- exclear
|
||||
fwclear:
|
||||
- fwclear
|
||||
aliasesclear:
|
||||
@@ -900,25 +877,6 @@ languageslist:
|
||||
- langli
|
||||
rategirl:
|
||||
- rategirl
|
||||
crreact:
|
||||
- crreact
|
||||
- crr
|
||||
crad:
|
||||
- crad
|
||||
- exad
|
||||
crat:
|
||||
- crat
|
||||
- exat
|
||||
crdm:
|
||||
- crdm
|
||||
- exdm
|
||||
crca:
|
||||
- crca
|
||||
- exca
|
||||
crsreload:
|
||||
- crsreload
|
||||
- expreload
|
||||
- exrel
|
||||
aliaslist:
|
||||
- aliaslist
|
||||
- cmdmaplist
|
||||
@@ -1142,10 +1100,6 @@ feedremove:
|
||||
feedlist:
|
||||
- feedlist
|
||||
- feeds
|
||||
editcustreact:
|
||||
- editcustreact
|
||||
- ecr
|
||||
- exe
|
||||
say:
|
||||
- say
|
||||
sqlexec:
|
||||
@@ -1238,12 +1192,6 @@ economy:
|
||||
- economy
|
||||
purgeuser:
|
||||
- purgeuser
|
||||
crsimport:
|
||||
- crsimport
|
||||
- eximport
|
||||
crsexport:
|
||||
- crsexport
|
||||
- exexport
|
||||
imageonlychannel:
|
||||
- imageonlychannel
|
||||
- imageonly
|
||||
@@ -1257,4 +1205,49 @@ quotesimport:
|
||||
- quotesimport
|
||||
- qimport
|
||||
showembed:
|
||||
- showembed
|
||||
- showembed
|
||||
# NadekoExpressions
|
||||
exprreact:
|
||||
- exreact
|
||||
- exr
|
||||
exprad:
|
||||
- exad
|
||||
exprat:
|
||||
- exat
|
||||
exprdm:
|
||||
- exdm
|
||||
exprca:
|
||||
- exca
|
||||
exprsreload:
|
||||
- expreload
|
||||
- exrel
|
||||
expradd:
|
||||
- expradd
|
||||
- exadd
|
||||
- exa
|
||||
exprlist:
|
||||
- exprlist
|
||||
- exl
|
||||
- exprli
|
||||
- exlist
|
||||
- exli
|
||||
exprshow:
|
||||
- exprshow
|
||||
- exs
|
||||
- exshow
|
||||
exprdelete:
|
||||
- exprdel
|
||||
- exd
|
||||
- exdel
|
||||
exprclear:
|
||||
- exprclear
|
||||
- exc
|
||||
- exclear
|
||||
expredit:
|
||||
- expredit
|
||||
- exe
|
||||
- exedit
|
||||
exprsimport:
|
||||
- eximport
|
||||
exprsexport:
|
||||
- exexport
|
@@ -1,5 +1,5 @@
|
||||
# DO NOT CHANGE
|
||||
version: 2
|
||||
version: 3
|
||||
# Most commands, when executed, have a small colored line
|
||||
# next to the response. The color depends whether the command
|
||||
# is completed, errored or in progress (pending)
|
||||
|
@@ -937,7 +937,7 @@
|
||||
"module_description_music": "Spiele Musik von YouTube, lokalen Dateien und Radio Streams",
|
||||
"module_description_utility": "Verwalte benutzerdefinierte Zitate, wiederholte Nachrichten und überprüfe Fakten über den Server",
|
||||
"module_description_administration": "Moderation, bestrafe Nutzer, richte selbst zuweisbare Rollen und Willkommensnachrichten ein",
|
||||
"module_description_customreactions": "Richte benutzerdefinierte Bot-antworten für bestimmte Wörter oder Sätze ein",
|
||||
"module_description_expressions": "Richte benutzerdefinierte Bot-antworten für bestimmte Wörter oder Sätze ein",
|
||||
"module_description_permissions": "Richte Berechtigungen für Befehle ein, filtere Wörter und richte Abklingzeiten für Befehle ein",
|
||||
"module_description_searches": "Suche nach Witzen, Bilder von Tieren, Anime und Manga",
|
||||
"module_description_xp": "Erhalte XP basierend auf Chataktivität, prüfe Nutzer XP-karten",
|
||||
|
@@ -950,7 +950,7 @@
|
||||
"module_description_music": "Play music from youtube, local files soundcloud and radio streams",
|
||||
"module_description_utility": "Manage custom quotes, repeating messages and check facts about the server",
|
||||
"module_description_administration": "Moderation, punish users, setup self assignable roles and greet messages",
|
||||
"module_description_customreactions": "Setup custom bot responses to certain words or phrases",
|
||||
"module_description_expressions": "Setup custom bot responses to certain words or phrases",
|
||||
"module_description_permissions": "Setup perms for commands, filter words and set up command cooldowns",
|
||||
"module_description_searches": "Search for jokes, images of animals, anime and manga",
|
||||
"module_description_xp": "Gain xp based on chat activity, check users' xp cards",
|
||||
|
@@ -937,7 +937,7 @@
|
||||
"module_description_music": "Reproduce música de YouTube, archivos locales, SoundCloud y radios.",
|
||||
"module_description_utility": "Administra citas personalizadas, repeticiones de mensajes y revisa datos sobre el servidor",
|
||||
"module_description_administration": "Moderación, castigos, configuración de roles autoasignables y saludos",
|
||||
"module_description_customreactions": "Ajusta las respuestas del bot a ciertas palabras o frases",
|
||||
"module_description_expressions": "Ajusta las respuestas del bot a ciertas palabras o frases",
|
||||
"module_description_permissions": "Ajusta permisos para los comandos, filtro de palabras y configura enfriamiento para los comandos.",
|
||||
"module_description_searches": "Busca bromas, imágenes de animales, anime y manga",
|
||||
"module_description_xp": "Gana EXP según la actividad del chat, revisa la tarjeta de EXP de los usuarios.",
|
||||
|
@@ -937,7 +937,7 @@
|
||||
"module_description_music": "Jouez de la musique à partir de youtube, des fichiers locaux soundcloud et des flux radio",
|
||||
"module_description_utility": "Gérez les citations personnalisés, répéteurs et consulter les faits à propos du serveur",
|
||||
"module_description_administration": "Modération, punition des utilisateurs, configuration de rôles auto-assignables et messages d'accueil",
|
||||
"module_description_customreactions": "Configurer des réponses de bot personnalisées à certains mots ou expressions",
|
||||
"module_description_expressions": "Configurer des réponses de bot personnalisées à certains mots ou expressions",
|
||||
"module_description_permissions": "Configurer les autorisations pour les commandes, filtrer les mots et configurer les temps de recharge des commandes",
|
||||
"module_description_searches": "Recherche de blagues, d'images d'animaux, d'anime et de manga",
|
||||
"module_description_xp": "Gagnez de l'XP en fonction de l'activité de tchat, vérifiez les cartes XP des utilisateurs",
|
||||
|
@@ -937,7 +937,7 @@
|
||||
"module_description_music": "Memainkan musik dari youtube, file lokal soundcloud dan stream radio",
|
||||
"module_description_utility": "Mengatur kutipan kustom, pesan ulang dan cek fakta tentang server",
|
||||
"module_description_administration": "Moderasi, pengguna terhukum, persiapkan penetapan role mandiri dan pesan salam",
|
||||
"module_description_customreactions": "Persiapkan penanggapan bot yang kustom ke kata-kata atau frase yang ditentukan",
|
||||
"module_description_expressions": "Persiapkan penanggapan bot yang kustom ke kata-kata atau frase yang ditentukan",
|
||||
"module_description_permissions": "Persiapkan permisi untuk perintah, kata sensor dan persiapkan pertenangan perintah",
|
||||
"module_description_searches": "Mencari lelucon, gamba binatang, anime dan manga",
|
||||
"module_description_xp": "Mendapatkan xp berdasarkan aktivitas di chat, cek kartu xp pengguna",
|
||||
|
@@ -937,7 +937,7 @@
|
||||
"module_description_music": "Speel muziek van youtube, lokale bestanden soundcloud en radio streams",
|
||||
"module_description_utility": "Beheer speciale citaten, herhalende berichten en controleer feiten over de server",
|
||||
"module_description_administration": "Moderatie, gebruikers straffen, zelf in te delen rollen en begroetingsberichten",
|
||||
"module_description_customreactions": "Stel speciale bot reacties in op bepaalde woorden of zinnen",
|
||||
"module_description_expressions": "Stel speciale bot reacties in op bepaalde woorden of zinnen",
|
||||
"module_description_permissions": "Rechten instellen voor commando's, woorden filteren en cooldowns instellen",
|
||||
"module_description_searches": "Zoek naar grappen, afbeeldingen van dieren, anime en manga",
|
||||
"module_description_xp": "Xp verdienen op basis van chat-activiteit, xp-kaarten van gebruikers controleren",
|
||||
|
@@ -937,7 +937,7 @@
|
||||
"module_description_music": "Odtwarzaj muzykę z youtube, plików lokalnych, soundcloud'a i radia internetowego",
|
||||
"module_description_utility": "Zarządzaj customowymi cytatami, powtarzającymi wiadomościami i sprawdź fakty na temat serwera",
|
||||
"module_description_administration": "Moderacja, kary dla użytkowników, ustaw role do samodzielnego przypisania oraz wiadomości powitalne",
|
||||
"module_description_customreactions": "Ustaw customowe odpowiedzi bota na określone słowa bądź zdania",
|
||||
"module_description_expressions": "Ustaw customowe odpowiedzi bota na określone słowa bądź zdania",
|
||||
"module_description_permissions": "Ustaw uprawnienia dla komend, filtruj słowa i ustaw cooldown'y dla komend",
|
||||
"module_description_searches": "Wyszukaj żarty, obrazki ze zwierzętami, anime i mangą.",
|
||||
"module_description_xp": "Zdobywaj xp bazujące na aktywności na czacie, sprawdź karty xp użytkowników.",
|
||||
|
@@ -937,7 +937,7 @@
|
||||
"module_description_music": "Toque músicas do YouTube, arquivos locais do SoundCloud e rádios ao vivo",
|
||||
"module_description_utility": "Controle falas customizadas, mensagens repetidas e veja fatos sobre o servidor",
|
||||
"module_description_administration": "Moderação, punir usuários, configurar cargos dados á si mesmo e mensagens de boas vindas",
|
||||
"module_description_customreactions": "Configure respostas customizáveis para o bot para certas palavras ou frases",
|
||||
"module_description_expressions": "Configure respostas customizáveis para o bot para certas palavras ou frases",
|
||||
"module_description_permissions": "Configure permissões para comandos, filtro de palavras e configure intervalo de comandos",
|
||||
"module_description_searches": "Procure por piadas, imagens de animais, anime e manga",
|
||||
"module_description_xp": "Ganhe xp baseado na atividade em chat, verifique cartão de xp do usuário",
|
||||
|
@@ -937,7 +937,7 @@
|
||||
"module_description_music": "Воспроизведение музыки с YouTube, локальных файлов, звукового облака и радиопотоков",
|
||||
"module_description_utility": "Управляйте кастомными цитатами, повторяйте сообщения и проверяйте факты о сервере",
|
||||
"module_description_administration": "Модерация, наказание пользователей, установка самопределяемых ролей и приветственные сообщения",
|
||||
"module_description_customreactions": "Настройка пользовательских ответов бота на определенные слова или фразы",
|
||||
"module_description_expressions": "Настройка пользовательских ответов бота на определенные слова или фразы",
|
||||
"module_description_permissions": "Настройка разрешений для команд, фильтрация слов и настройка времени восстановления команд",
|
||||
"module_description_searches": "Поиск шуток, изображений животных, аниме и манги",
|
||||
"module_description_xp": "Получайте опыт на основе активности в чате, проверяйте карты опыта пользователей",
|
||||
|
@@ -937,7 +937,7 @@
|
||||
"module_description_music": "Відтворюйте музику з YouTube, локальні файли soundcloud та радіопотоки",
|
||||
"module_description_utility": "Керуйте власними цитатами, повторюваними повідомленнями та перевіряйте факти про сервер",
|
||||
"module_description_administration": "Модеруйте, карайте користувачів, налаштовуйте самостійно призначені ролі та вітайте повідомлення",
|
||||
"module_description_customreactions": "Налаштуйте власні відповіді бота на певні слова чи фрази",
|
||||
"module_description_expressions": "Налаштуйте власні відповіді бота на певні слова чи фрази",
|
||||
"module_description_permissions": "Налаштуйте дозволи для команд, фільтруйте слова та налаштуйте час відновлення",
|
||||
"module_description_searches": "Шукайте жарти, зображення тварин, аніме та мангу",
|
||||
"module_description_xp": "Отримайте ХР на основі активності в чаті, перевірте картки ХР користувачів",
|
||||
|
Reference in New Issue
Block a user