Applied codestyle to all .cs files

This commit is contained in:
Kwoth
2021-12-29 06:07:16 +01:00
parent 723447c7d4
commit 82000c97a4
543 changed files with 13221 additions and 14059 deletions

View File

@@ -22,8 +22,6 @@ public class ExportedExpr
At = cr.AllowTarget,
Ca = cr.ContainsAnywhere,
Dm = cr.DmResponse,
React = string.IsNullOrWhiteSpace(cr.Reactions)
? null
: cr.GetReactions(),
React = string.IsNullOrWhiteSpace(cr.Reactions) ? null : cr.GetReactions()
};
}
}

View File

@@ -5,6 +5,11 @@ namespace NadekoBot.Modules.CustomReactions;
public class CustomReactions : NadekoModule<CustomReactionsService>
{
public enum All
{
All
}
private readonly IBotCredentials _creds;
private readonly IHttpClientFactory _clientFactory;
@@ -14,10 +19,12 @@ public class CustomReactions : NadekoModule<CustomReactionsService>
_clientFactory = clientFactory;
}
private bool AdminInGuildOrOwnerInDm() => (ctx.Guild is null && _creds.IsOwner(ctx.User))
|| (ctx.Guild != null && ((IGuildUser)ctx.User).GuildPermissions.Administrator);
private bool AdminInGuildOrOwnerInDm()
=> (ctx.Guild is null && _creds.IsOwner(ctx.User))
|| (ctx.Guild != null && ((IGuildUser)ctx.User).GuildPermissions.Administrator);
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
public async Task AddCustReact(string key, [Leftover] string message)
{
var channel = ctx.Channel as ITextChannel;
@@ -32,22 +39,25 @@ public class CustomReactions : NadekoModule<CustomReactionsService>
var cr = await _service.AddAsync(ctx.Guild?.Id, key, message);
await ctx.Channel.EmbedAsync(_eb.Create().WithOkColor()
.WithTitle(GetText(strs.new_cust_react))
.WithDescription($"#{cr.Id}")
.AddField(GetText(strs.trigger), key)
.AddField(GetText(strs.response), message.Length > 1024 ? GetText(strs.redacted_too_long) : message)
);
await ctx.Channel.EmbedAsync(_eb.Create()
.WithOkColor()
.WithTitle(GetText(strs.new_cust_react))
.WithDescription($"#{cr.Id}")
.AddField(GetText(strs.trigger), key)
.AddField(GetText(strs.response),
message.Length > 1024 ? GetText(strs.redacted_too_long) : message));
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
public async Task EditCustReact(kwum id, [Leftover] string message)
{
var channel = ctx.Channel as ITextChannel;
if (string.IsNullOrWhiteSpace(message) || id < 0)
return;
if ((channel is null && !_creds.IsOwner(ctx.User)) || (channel != null && !((IGuildUser)ctx.User).GuildPermissions.Administrator))
if ((channel is null && !_creds.IsOwner(ctx.User))
|| (channel != null && !((IGuildUser)ctx.User).GuildPermissions.Administrator))
{
await ReplyErrorLocalizedAsync(strs.insuff_perms);
return;
@@ -55,21 +65,19 @@ public class CustomReactions : NadekoModule<CustomReactionsService>
var cr = await _service.EditAsync(ctx.Guild?.Id, id, message);
if (cr != 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.response), message.Length > 1024 ? GetText(strs.redacted_too_long) : message)
);
}
await ctx.Channel.EmbedAsync(_eb.Create()
.WithOkColor()
.WithTitle(GetText(strs.edited_cust_react))
.WithDescription($"#{id}")
.AddField(GetText(strs.trigger), cr.Trigger)
.AddField(GetText(strs.response),
message.Length > 1024 ? GetText(strs.redacted_too_long) : message));
else
{
await ReplyErrorLocalizedAsync(strs.edit_fail);
}
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[Priority(1)]
public async Task ListCustReact(int page = 1)
{
@@ -84,33 +92,29 @@ public class CustomReactions : NadekoModule<CustomReactionsService>
return;
}
await ctx.SendPaginatedConfirmAsync(page, pageFunc: curPage =>
{
var desc = customReactions.OrderBy(cr => cr.Trigger)
.Skip(curPage * 20)
.Take(20)
.Select(cr => $"{(cr.ContainsAnywhere ? "🗯" : "")}" +
$"{(cr.DmResponse ? "" : "")}" +
$"{(cr.AutoDeleteTrigger ? "" : "")}" +
$"`{(kwum) cr.Id}` {cr.Trigger}"
+ (string.IsNullOrWhiteSpace(cr.Reactions)
? string.Empty
: " // " + string.Join(" ", cr.GetReactions())))
.Join('\n');
await ctx.SendPaginatedConfirmAsync(page,
curPage =>
{
var desc = customReactions.OrderBy(cr => cr.Trigger)
.Skip(curPage * 20)
.Take(20)
.Select(cr => $"{(cr.ContainsAnywhere ? "🗯" : "")}"
+ $"{(cr.DmResponse ? "" : "")}"
+ $"{(cr.AutoDeleteTrigger ? "" : "")}"
+ $"`{(kwum)cr.Id}` {cr.Trigger}"
+ (string.IsNullOrWhiteSpace(cr.Reactions)
? string.Empty
: " // " + string.Join(" ", cr.GetReactions())))
.Join('\n');
return _eb.Create().WithOkColor()
.WithTitle(GetText(strs.custom_reactions))
.WithDescription(desc);
}, customReactions.Length, 20);
return _eb.Create().WithOkColor().WithTitle(GetText(strs.custom_reactions)).WithDescription(desc);
},
customReactions.Length,
20);
}
public enum All
{
All
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
public async Task ShowCustReact(kwum id)
{
var found = _service.GetCustomReaction(ctx.Guild?.Id, id);
@@ -120,17 +124,17 @@ public class CustomReactions : NadekoModule<CustomReactionsService>
await ReplyErrorLocalizedAsync(strs.no_found_id);
return;
}
else
{
await ctx.Channel.EmbedAsync(_eb.Create().WithOkColor()
.WithDescription($"#{id}")
.AddField(GetText(strs.trigger), found.Trigger.TrimTo(1024))
.AddField(GetText(strs.response), found.Response.TrimTo(1000).Replace("](", "]\\("))
);
}
await ctx.Channel.EmbedAsync(_eb.Create()
.WithOkColor()
.WithDescription($"#{id}")
.AddField(GetText(strs.trigger), found.Trigger.TrimTo(1024))
.AddField(GetText(strs.response),
found.Response.TrimTo(1000).Replace("](", "]\\(")));
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
public async Task DelCustReact(kwum id)
{
if (!AdminInGuildOrOwnerInDm())
@@ -142,20 +146,18 @@ public class CustomReactions : NadekoModule<CustomReactionsService>
var cr = await _service.DeleteAsync(ctx.Guild?.Id, id);
if (cr != 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)));
}
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)));
else
{
await ReplyErrorLocalizedAsync(strs.no_found_id);
}
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
public async Task CrReact(kwum id, params string[] emojiStrs)
{
if (!AdminInGuildOrOwnerInDm())
@@ -181,7 +183,6 @@ public class CustomReactions : NadekoModule<CustomReactionsService>
var succ = new List<string>();
foreach (var emojiStr in emojiStrs)
{
var emote = emojiStr.ToIEmote();
// i should try adding these emojis right away to the message, to make sure the bot can react with these emojis. If it fails, skip that emoji
@@ -197,7 +198,7 @@ public class CustomReactions : NadekoModule<CustomReactionsService>
catch { }
}
if(succ.Count == 0)
if (succ.Count == 0)
{
await ReplyErrorLocalizedAsync(strs.invalid_emojis);
return;
@@ -206,27 +207,32 @@ public class CustomReactions : NadekoModule<CustomReactionsService>
await _service.SetCrReactions(ctx.Guild?.Id, id, succ);
await ReplyConfirmLocalizedAsync(strs.crr_set(Format.Bold(id.ToString()), string.Join(", ", succ.Select(x => x.ToString()))));
await ReplyConfirmLocalizedAsync(strs.crr_set(Format.Bold(id.ToString()),
string.Join(", ", succ.Select(x => x.ToString()))));
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
public Task CrCa(kwum id)
=> InternalCrEdit(id, CustomReactionsService.CrField.ContainsAnywhere);
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
public Task CrDm(kwum id)
=> InternalCrEdit(id, CustomReactionsService.CrField.DmResponse);
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
public Task CrAd(kwum id)
=> InternalCrEdit(id, CustomReactionsService.CrField.AutoDelete);
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
public Task CrAt(kwum id)
=> InternalCrEdit(id, CustomReactionsService.CrField.AllowTarget);
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[OwnerOnly]
public async Task CrsReload()
{
@@ -243,6 +249,7 @@ public class CustomReactions : NadekoModule<CustomReactionsService>
await ReplyErrorLocalizedAsync(strs.insuff_perms);
return;
}
var (success, newVal) = await _service.ToggleCrOptionAsync(id, option);
if (!success)
{
@@ -251,30 +258,30 @@ public class CustomReactions : NadekoModule<CustomReactionsService>
}
if (newVal)
{
await ReplyConfirmLocalizedAsync(strs.option_enabled(Format.Code(option.ToString()), Format.Code(id.ToString())));
}
await ReplyConfirmLocalizedAsync(strs.option_enabled(Format.Code(option.ToString()),
Format.Code(id.ToString())));
else
{
await ReplyConfirmLocalizedAsync(strs.option_disabled(Format.Code(option.ToString()), Format.Code(id.ToString())));
}
await ReplyConfirmLocalizedAsync(strs.option_disabled(Format.Code(option.ToString()),
Format.Code(id.ToString())));
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public async Task CrClear()
{
if (await PromptUserConfirmAsync(_eb.Create()
.WithTitle("Custom reaction clear")
.WithDescription("This will delete all custom reactions on this server.")))
.WithTitle("Custom reaction clear")
.WithDescription("This will delete all custom reactions on this server.")))
{
var count = _service.DeleteAllCustomReactions(ctx.Guild.Id);
await ReplyConfirmLocalizedAsync(strs.cleared(count));
}
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
public async Task CrsExport()
{
if (!AdminInGuildOrOwnerInDm())
@@ -282,19 +289,20 @@ public class CustomReactions : NadekoModule<CustomReactionsService>
await ReplyErrorLocalizedAsync(strs.insuff_perms);
return;
}
_ = ctx.Channel.TriggerTypingAsync();
var serialized = _service.ExportCrs(ctx.Guild?.Id);
await using var stream = await serialized.ToStream();
await ctx.Channel.SendFileAsync(stream, "crs-export.yml", text: null);
await ctx.Channel.SendFileAsync(stream, "crs-export.yml");
}
[NadekoCommand, Aliases]
[NadekoCommand]
[Aliases]
#if GLOBAL_NADEKO
[OwnerOnly]
#endif
public async Task CrsImport([Leftover]string input = null)
public async Task CrsImport([Leftover] string input = null)
{
if (!AdminInGuildOrOwnerInDm())
{
@@ -331,7 +339,7 @@ public class CustomReactions : NadekoModule<CustomReactionsService>
await ReplyErrorLocalizedAsync(strs.expr_import_invalid_data);
return;
}
await ctx.OkAsync();
}
}
}

View File

@@ -9,12 +9,13 @@ public static class CustomReactionExtensions
private static string ResolveTriggerString(this string str, DiscordSocketClient client)
=> str.Replace("%bot.mention%", client.CurrentUser.Mention, StringComparison.Ordinal);
public static async Task<IUserMessage> Send(this CustomReaction cr, IUserMessage ctx,
DiscordSocketClient client, bool sanitize)
public static async Task<IUserMessage> Send(
this CustomReaction cr,
IUserMessage ctx,
DiscordSocketClient client,
bool sanitize)
{
var channel = cr.DmResponse
? await ctx.Author.CreateDMChannelAsync()
: ctx.Channel;
var channel = cr.DmResponse ? await ctx.Author.CreateDMChannelAsync() : ctx.Channel;
var trigger = cr.Trigger.ResolveTriggerString(client);
var substringIndex = trigger.Length;
@@ -32,11 +33,12 @@ public static class CustomReactionExtensions
var canMentionEveryone = (ctx.Author as IGuildUser)?.GuildPermissions.MentionEveryone ?? true;
var rep = new ReplacementBuilder()
.WithDefault(ctx.Author, ctx.Channel, (ctx.Channel as ITextChannel)?.Guild as SocketGuild, client)
.WithOverride("%target%", () => canMentionEveryone
? ctx.Content[substringIndex..].Trim()
: ctx.Content[substringIndex..].Trim().SanitizeMentions(true))
.Build();
.WithDefault(ctx.Author, ctx.Channel, (ctx.Channel as ITextChannel)?.Guild as SocketGuild, client)
.WithOverride("%target%",
() => canMentionEveryone
? ctx.Content[substringIndex..].Trim()
: ctx.Content[substringIndex..].Trim().SanitizeMentions(true))
.Build();
var text = SmartText.CreateFrom(cr.Response);
text = rep.Replace(text);
@@ -62,7 +64,9 @@ public static class CustomReactionExtensions
return WordPosition.End;
}
else if (str.isValidWordDivider(wordIndex - 1) && str.isValidWordDivider(wordIndex + word.Length))
{
return WordPosition.Middle;
}
return WordPosition.None;
}
@@ -82,5 +86,5 @@ public enum WordPosition
None,
Start,
Middle,
End,
}
End
}

View File

@@ -1,14 +1,15 @@
#nullable disable
using Microsoft.EntityFrameworkCore;
using NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Services.Database.Models;
using NadekoBot.Common.Yml;
using NadekoBot.Db;
using NadekoBot.Modules.CustomReactions.Extensions;
using NadekoBot.Modules.Permissions.Common;
using NadekoBot.Modules.Permissions.Services;
using NadekoBot.Services.Database.Models;
using System.Runtime.CompilerServices;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Common.Yml;
using NadekoBot.Db;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
namespace NadekoBot.Modules.CustomReactions.Services;
@@ -20,16 +21,45 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
DmResponse,
AllowTarget,
ContainsAnywhere,
Message,
Message
}
private const string MentionPh = "%bot.mention%";
private const string _prependExport =
@"# Keys are triggers, Each key has a LIST of custom reactions 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.)
# 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)
";
private static readonly ISerializer _exportSerializer = new SerializerBuilder()
.WithEventEmitter(args
=> new MultilineScalarFlowStyleEmitter(args))
.WithNamingConvention(CamelCaseNamingConvention.Instance)
.WithIndentedSequences()
.ConfigureDefaultValuesHandling(DefaultValuesHandling
.OmitDefaults)
.DisableAliases()
.Build();
public int Priority
=> 0;
private readonly object _gcrWriteLock = 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 const string MentionPh = "%bot.mention%";
// 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)
@@ -38,8 +68,6 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
private CustomReaction[] _globalReactions;
private ConcurrentDictionary<ulong, CustomReaction[]> _newGuildReactions;
public int Priority => 0;
private readonly DbService _db;
private readonly DiscordSocketClient _client;
private readonly PermissionService _perms;
@@ -52,9 +80,19 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
private readonly IEmbedBuilderService _eb;
private readonly Random _rng;
public CustomReactionsService(PermissionService perms, DbService db, IBotStrings strings, Bot bot,
DiscordSocketClient client, CommandHandler cmd, GlobalPermissionService gperm, CmdCdService cmdCds,
IPubSub pubSub, IEmbedBuilderService eb)
private bool ready;
public CustomReactionsService(
PermissionService perms,
DbService db,
IBotStrings strings,
Bot bot,
DiscordSocketClient client,
CommandHandler cmd,
GlobalPermissionService gperm,
CmdCdService cmdCds,
IPubSub pubSub,
IEmbedBuilderService eb)
{
_db = db;
_client = client;
@@ -80,34 +118,31 @@ 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()
.Where(x => allGuildIds.Contains(x.GuildId.Value))
.ToListAsync();
var guildItems = await uow.CustomReactions.AsNoTracking()
.Where(x => allGuildIds.Contains(x.GuildId.Value))
.ToListAsync();
_newGuildReactions = guildItems
.GroupBy(k => k.GuildId!.Value)
.ToDictionary(g => g.Key,
g => g.Select(x =>
{
x.Trigger = x.Trigger.Replace(MentionPh, _bot.Mention);
return x;
}).ToArray())
.ToConcurrent();
_newGuildReactions = guildItems.GroupBy(k => k.GuildId!.Value)
.ToDictionary(g => g.Key,
g => g.Select(x =>
{
x.Trigger = x.Trigger.Replace(MentionPh, _bot.Mention);
return x;
})
.ToArray())
.ToConcurrent();
lock (_gcrWriteLock)
{
var globalItems = uow
.CustomReactions
.AsNoTracking()
.Where(x => x.GuildId == null || x.GuildId == 0)
.AsEnumerable()
.Select(x =>
{
x.Trigger = x.Trigger.Replace(MentionPh, _bot.Mention);
return x;
})
.ToArray();
var globalItems = uow.CustomReactions.AsNoTracking()
.Where(x => x.GuildId == null || x.GuildId == 0)
.AsEnumerable()
.Select(x =>
{
x.Trigger = x.Trigger.Replace(MentionPh, _bot.Mention);
return x;
})
.ToArray();
_globalReactions = globalItems;
}
@@ -115,176 +150,6 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
ready = true;
}
#region Event Handlers
public Task OnReadyAsync()
=> ReloadInternal(_bot.GetCurrentGuildIds());
private ValueTask OnCrsShouldReload(bool _) => new(ReloadInternal(_bot.GetCurrentGuildIds()));
private ValueTask OnGcrAdded(CustomReaction c)
{
lock (_gcrWriteLock)
{
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)
{
lock (_gcrWriteLock)
{
for (var i = 0; i < _globalReactions.Length; i++)
{
if (_globalReactions[i].Id == c.Id)
{
_globalReactions[i] = c;
return default;
}
}
// if edited cr is not found?!
// add it
OnGcrAdded(c);
}
return default;
}
private ValueTask OnGcrDeleted(int id)
{
lock (_gcrWriteLock)
{
var newGlobalReactions = DeleteInternal(_globalReactions, id, out _);
_globalReactions = newGlobalReactions;
}
return default;
}
public Task TriggerReloadCustomReactions()
=> _pubSub.Pub(_crsReloadedKey, true);
#endregion
#region Client Event Handlers
private Task OnLeftGuild(SocketGuild arg)
{
_newGuildReactions.TryRemove(arg.Id, out _);
return Task.CompletedTask;
}
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();
_newGuildReactions[gc.GuildId] = crs;
}
#endregion
#region Basic Operations
public async Task<CustomReaction> AddAsync(ulong? guildId, string key, string message)
{
key = key.ToLowerInvariant();
var cr = new CustomReaction()
{
GuildId = guildId,
Trigger = key,
Response = message,
};
if (cr.Response.Contains("%target%", StringComparison.OrdinalIgnoreCase))
cr.AllowTarget = true;
await using (var uow = _db.GetDbContext())
{
uow.CustomReactions.Add(cr);
await uow.SaveChangesAsync();
}
await AddInternalAsync(guildId, cr);
return cr;
}
public async Task<CustomReaction> EditAsync(ulong? guildId, int id, string message)
{
await using var uow = _db.GetDbContext();
var cr = uow.CustomReactions.GetById(id);
if (cr is null || cr.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;
}
cr.Response = message;
// enable allow target if message is edited to contain target
if (cr.Response.Contains("%target%", StringComparison.OrdinalIgnoreCase))
cr.AllowTarget = true;
await uow.SaveChangesAsync();
await UpdateInternalAsync(guildId, cr);
return cr;
}
public async Task<CustomReaction> DeleteAsync(ulong? guildId, int id)
{
await using var uow = _db.GetDbContext();
var toDelete = uow.CustomReactions.GetById(id);
if (toDelete is null)
return null;
if ((toDelete.IsGlobal() && guildId is null) || guildId == toDelete.GuildId)
{
uow.CustomReactions.Remove(toDelete);
await uow.SaveChangesAsync();
await DeleteInternalAsync(guildId, id);
return toDelete;
}
return null;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CustomReaction[] GetCustomReactionsFor(ulong? maybeGuildId)
{
if (maybeGuildId is { } guildId)
{
return _newGuildReactions.TryGetValue(guildId, out var crs)
? crs
: Array.Empty<CustomReaction>();
}
return _globalReactions;
}
#endregion
private bool ready;
private CustomReaction TryGetCustomReaction(IUserMessage umsg)
{
if (!ready)
@@ -294,7 +159,7 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
return null;
var content = umsg.Content.Trim().ToLowerInvariant();
if (_newGuildReactions.TryGetValue(channel.Guild.Id, out var reactions) && reactions.Length > 0)
{
var cr = MatchCustomReactions(content, reactions);
@@ -325,10 +190,7 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
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(cr);
// if it's not, then it cant' work under any circumstance,
// because content is greater than the trigger length
@@ -338,11 +200,10 @@ 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 && content.StartsWith(trigger, StringComparison.OrdinalIgnoreCase)
&& content[trigger.Length] == ' ')
{
if (cr.AllowTarget
&& content.StartsWith(trigger, StringComparison.OrdinalIgnoreCase)
&& content[trigger.Length] == ' ')
result.Add(cr);
}
}
else if (content.Length < cr.Trigger.Length)
{
@@ -353,10 +214,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(cr.Trigger)) result.Add(cr);
}
}
@@ -366,7 +224,7 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
var cancelled = result.FirstOrDefault(x => x.Response == "-");
if (cancelled is not null)
return cancelled;
return result[_rng.Next(0, result.Count)];
}
@@ -377,34 +235,33 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
if (cr is null || cr.Response == "-")
return false;
if(await _cmdCds.TryBlock(guild, msg.Author, cr.Trigger))
if (await _cmdCds.TryBlock(guild, msg.Author, cr.Trigger))
return false;
try
{
if (_gperm.BlockedModules.Contains("ActualCustomReactions"))
{
Log.Information("User {UserName} [{UserId}] tried to use a custom reaction but 'ActualCustomReactions' are globally disabled.",
Log.Information(
"User {UserName} [{UserId}] tried to use a custom reaction but 'ActualCustomReactions' are globally disabled.",
msg.Author.ToString(),
msg.Author.Id);
return true;
}
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, cr.Trigger, "ActualCustomReactions", out var index))
{
if (pc.Verbose)
{
var returnMsg = _strings.GetText(
strs.perm_prevent(index + 1,
var returnMsg = _strings.GetText(strs.perm_prevent(index + 1,
Format.Bold(pc.Permissions[index].GetCommand(_cmd.GetPrefix(guild), sg))),
sg.Id);
try
{
await msg.Channel.SendErrorAsync(_eb, returnMsg);
@@ -431,7 +288,8 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
}
catch
{
Log.Warning("Unable to add reactions to message {Message} in server {GuildId}", sentMsg.Id,
Log.Warning("Unable to add reactions to message {Message} in server {GuildId}",
sentMsg.Id,
cr.GuildId);
break;
}
@@ -440,7 +298,6 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
}
if (cr.AutoDeleteTrigger)
{
try
{
await msg.DeleteAsync();
@@ -448,7 +305,6 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
catch
{
}
}
Log.Information("s: {GuildId} c: {ChannelId} u: {UserId} | {UserName} executed expression {Expr}",
guild.Id,
@@ -456,7 +312,7 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
msg.Author.Id,
msg.Author.ToString(),
cr.Trigger);
return true;
}
catch (Exception ex)
@@ -493,31 +349,24 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
private void UpdateInternal(ulong? maybeGuildId, CustomReaction cr)
{
if (maybeGuildId is { } guildId)
{
_newGuildReactions.AddOrUpdate(guildId, new[] {cr},
_newGuildReactions.AddOrUpdate(guildId,
new[] { cr },
(key, old) =>
{
var newArray = old.ToArray();
for (var i = 0; i < newArray.Length; i++)
{
if (newArray[i].Id == cr.Id)
newArray[i] = cr;
}
return newArray;
});
}
else
{
lock (_gcrWriteLock)
{
var crs = _globalReactions;
for (var i = 0; i < crs.Length; i++)
{
if (crs[i].Id == cr.Id)
crs[i] = cr;
}
}
}
}
private Task AddInternalAsync(ulong? maybeGuildId, CustomReaction cr)
@@ -526,19 +375,13 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
cr.Trigger = cr.Trigger.Replace(MentionPh, _client.CurrentUser.Mention);
if (maybeGuildId is { } guildId)
{
_newGuildReactions.AddOrUpdate(guildId,
new[] {cr},
(key, old) => old.With(cr));
}
_newGuildReactions.AddOrUpdate(guildId, new[] { cr }, (key, old) => old.With(cr));
else
{
return _pubSub.Pub(_gcrAddedKey, cr);
}
return Task.CompletedTask;
}
private Task DeleteInternalAsync(ulong? maybeGuildId, int id)
{
if (maybeGuildId is { } guildId)
@@ -546,17 +389,14 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
_newGuildReactions.AddOrUpdate(guildId,
Array.Empty<CustomReaction>(),
(key, old) => DeleteInternal(old, id, out _));
return Task.CompletedTask;
}
lock (_gcrWriteLock)
{
var cr = Array.Find(_globalReactions, item => item.Id == id);
if (cr is not null)
{
return _pubSub.Pub(_gcrDeletedkey, cr.Id);
}
if (cr is not null) return _pubSub.Pub(_gcrDeletedkey, cr.Id);
}
return Task.CompletedTask;
@@ -567,7 +407,7 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
deleted = null;
if (crs is null || crs.Count == 0)
return crs as CustomReaction[] ?? crs?.ToArray();
var newCrs = new CustomReaction[crs.Count - 1];
for (int i = 0, k = 0; i < crs.Count; i++, k++)
{
@@ -636,13 +476,13 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
return cr;
}
public int DeleteAllCustomReactions(ulong guildId)
{
using var uow = _db.GetDbContext();
var count = uow.CustomReactions.ClearFromGuild(guildId);
uow.SaveChanges();
_newGuildReactions.TryRemove(guildId, out _);
return count;
@@ -655,39 +495,13 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
return cr != null;
}
private static readonly ISerializer _exportSerializer = new SerializerBuilder()
.WithEventEmitter(args => new MultilineScalarFlowStyleEmitter(args))
.WithNamingConvention(YamlDotNet.Serialization.NamingConventions.CamelCaseNamingConvention.Instance)
.WithIndentedSequences()
.ConfigureDefaultValuesHandling(DefaultValuesHandling.OmitDefaults)
.DisableAliases()
.Build();
private const string _prependExport =
@"# Keys are triggers, Each key has a LIST of custom reactions 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.)
# 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)
";
public string ExportCrs(ulong? guildId)
{
var crs = GetCustomReactionsFor(guildId);
var crsDict = crs
.GroupBy(x => x.Trigger)
.ToDictionary(x => x.Key, x => x.Select(ExportedExpr.FromModel));
return _prependExport + _exportSerializer
.Serialize(crsDict)
.UnescapeUnicodeCodePoints();
var crsDict = crs.GroupBy(x => x.Trigger).ToDictionary(x => x.Key, x => x.Select(ExportedExpr.FromModel));
return _prependExport + _exportSerializer.Serialize(crsDict).UnescapeUnicodeCodePoints();
}
public async Task<bool> ImportCrsAsync(ulong? guildId, string input)
@@ -708,23 +522,174 @@ 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()
{
GuildId = guildId,
Response = cr.Res,
Reactions = cr.React?.Join("@@@"),
Trigger = trigger,
AllowTarget = cr.At,
ContainsAnywhere = cr.Ca,
DmResponse = cr.Dm,
AutoDeleteTrigger = cr.Ad,
}));
await uow.CustomReactions.AddRangeAsync(entry.Value.Where(cr => !string.IsNullOrWhiteSpace(cr.Res))
.Select(cr => new CustomReaction
{
GuildId = guildId,
Response = cr.Res,
Reactions = cr.React?.Join("@@@"),
Trigger = trigger,
AllowTarget = cr.At,
ContainsAnywhere = cr.Ca,
DmResponse = cr.Dm,
AutoDeleteTrigger = cr.Ad
}));
}
await uow.SaveChangesAsync();
await TriggerReloadCustomReactions();
return true;
}
}
#region Event Handlers
public Task OnReadyAsync()
=> ReloadInternal(_bot.GetCurrentGuildIds());
private ValueTask OnCrsShouldReload(bool _)
=> new(ReloadInternal(_bot.GetCurrentGuildIds()));
private ValueTask OnGcrAdded(CustomReaction c)
{
lock (_gcrWriteLock)
{
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)
{
lock (_gcrWriteLock)
{
for (var i = 0; i < _globalReactions.Length; i++)
if (_globalReactions[i].Id == c.Id)
{
_globalReactions[i] = c;
return default;
}
// if edited cr is not found?!
// add it
OnGcrAdded(c);
}
return default;
}
private ValueTask OnGcrDeleted(int id)
{
lock (_gcrWriteLock)
{
var newGlobalReactions = DeleteInternal(_globalReactions, id, out _);
_globalReactions = newGlobalReactions;
}
return default;
}
public Task TriggerReloadCustomReactions()
=> _pubSub.Pub(_crsReloadedKey, true);
#endregion
#region Client Event Handlers
private Task OnLeftGuild(SocketGuild arg)
{
_newGuildReactions.TryRemove(arg.Id, out _);
return Task.CompletedTask;
}
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();
_newGuildReactions[gc.GuildId] = crs;
}
#endregion
#region Basic Operations
public async Task<CustomReaction> AddAsync(ulong? guildId, string key, string message)
{
key = key.ToLowerInvariant();
var cr = new CustomReaction { GuildId = guildId, Trigger = key, Response = message };
if (cr.Response.Contains("%target%", StringComparison.OrdinalIgnoreCase))
cr.AllowTarget = true;
await using (var uow = _db.GetDbContext())
{
uow.CustomReactions.Add(cr);
await uow.SaveChangesAsync();
}
await AddInternalAsync(guildId, cr);
return cr;
}
public async Task<CustomReaction> EditAsync(ulong? guildId, int id, string message)
{
await using var uow = _db.GetDbContext();
var cr = uow.CustomReactions.GetById(id);
if (cr is null || cr.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;
cr.Response = message;
// enable allow target if message is edited to contain target
if (cr.Response.Contains("%target%", StringComparison.OrdinalIgnoreCase))
cr.AllowTarget = true;
await uow.SaveChangesAsync();
await UpdateInternalAsync(guildId, cr);
return cr;
}
public async Task<CustomReaction> DeleteAsync(ulong? guildId, int id)
{
await using var uow = _db.GetDbContext();
var toDelete = uow.CustomReactions.GetById(id);
if (toDelete is null)
return null;
if ((toDelete.IsGlobal() && guildId is null) || guildId == toDelete.GuildId)
{
uow.CustomReactions.Remove(toDelete);
await uow.SaveChangesAsync();
await DeleteInternalAsync(guildId, id);
return toDelete;
}
return null;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CustomReaction[] GetCustomReactionsFor(ulong? maybeGuildId)
{
if (maybeGuildId is { } guildId)
return _newGuildReactions.TryGetValue(guildId, out var crs) ? crs : Array.Empty<CustomReaction>();
return _globalReactions;
}
#endregion
}