fix warns, part 2

This commit is contained in:
13xforever
2025-03-25 11:12:53 +05:00
parent 8860ccc272
commit 12b733633f
13 changed files with 125 additions and 275 deletions

View File

@@ -12,19 +12,24 @@ public class WarningAutoCompleteProvider: IAutoCompleteProvider
if (!authorized)
return [new($"{Config.Reactions.Denied} You are not authorized to use this command.", -1)];
//var user = context.Arguments.FirstOrDefault(kvp => kvp.Key.Name.Equals("user") && kvp.Value is DiscordUser).Value as DiscordUser;
Expression<Func<Warning, bool>> filter = context.Command.Name is nameof(Warnings.Revert)
Expression<Func<Warning, bool>> filter = context.Command.Name is "revert"
? w => w.Retracted
: w => !w.Retracted;
if (context.Options.FirstOrDefault(o => o is { Name: "user", Value: ulong })?.Value is ulong userId)
filter = context.Command.Name is "revert"
? w => w.Retracted && w.DiscordId == userId
: w => !w.Retracted && w.DiscordId == userId;
await using var db = new BotDb();
IEnumerable<Warning> result;
List<Warning> result;
if (context.UserInput is not { Length: > 0 } prefix)
result = db.Warning
result = await db.Warning
.OrderByDescending(w => w.Id)
.Where(filter)
.Take(25)
.AsNoTracking()
.AsEnumerable();
.ToListAsync()
.ConfigureAwait(false);
else
{
prefix = prefix.ToLowerInvariant();
@@ -36,16 +41,27 @@ public class WarningAutoCompleteProvider: IAutoCompleteProvider
.Where(filter)
.Where(w => w.Id.ToString().Contains(prefix) || w.Reason.Contains(prefix))
.Take(50);
result = prefixMatches
result = await prefixMatches
.Concat(substringMatches)
.Distinct()
.OrderByDescending(i => i.Id)
.Take(25)
.AsNoTracking()
.AsEnumerable();
.ToListAsync()
.ConfigureAwait(false);
}
var userIds = result
.Select(w => w.DiscordId)
.Distinct()
.ToList();
var userNames = new Dictionary<ulong, string>(userIds.Count);
foreach (var id in userIds)
userNames[id] = await context.Client.GetUserNameAsync(context.Channel, id).ConfigureAwait(false);
return result.Select(
w => new DiscordAutoCompleteChoice($"{w.Id}: {w.Timestamp?.AsUtc():O}: {w.Reason}", w.Id)
w => new DiscordAutoCompleteChoice(
$"{w.Id}: {w.Timestamp?.AsUtc():yyyy-MM-dd HH:mmz}: {userNames[w.DiscordId]} - {w.Reason}",
w.Id
)
).ToList();
}
}

View File

@@ -187,7 +187,7 @@ internal static class BotStatus
var sortedTerms = StatsStorage.GetExplainStats();
var totalExplains = sortedTerms.Sum(t => t.stat);
var top = sortedTerms.Take(5).ToList();
if (top.Count == 0)
if (top.Count is 0)
return;
var statsBuilder = new StringBuilder();

View File

@@ -209,6 +209,7 @@ internal static class ForcedNicknames
}
*/
/*
[Command("🔍 Dump"), SlashCommandTypes(DiscordApplicationCommandType.UserContextMenu)]
[Description("Print hexadecimal binary representation of an UTF-8 encoded user name for diagnostic purposes")]
public static async ValueTask Dump(UserCommandContext ctx, DiscordUser discordUser)
@@ -226,6 +227,7 @@ internal static class ForcedNicknames
}
await ctx.RespondAsync(result, ephemeral: true).ConfigureAwait(false);
}
*/
[Command("📝 Rename automatically"), RequiresBotModRole, SlashCommandTypes(DiscordApplicationCommandType.UserContextMenu)]
[Description("Set automatically generated nickname without enforcing it")]

View File

@@ -54,7 +54,7 @@ internal static partial class Psn
];
}
await ctx.RespondAsync(embeds[0], ephemeral: ephemeral).ConfigureAwait(false);
foreach (var embed in embeds.Skip(1).Take(5))
foreach (var embed in embeds.Skip(1).Take(EmbedPager.MaxFollowupMessages))
await ctx.FollowupAsync(embed, ephemeral: ephemeral).ConfigureAwait(false);
}

View File

@@ -85,7 +85,7 @@ internal static partial class Sudo
table.Add(await ctx.GetUserNameAsync(mod.DiscordId), mod.Sudoer ? "✅" :"");
var pages = AutosplitResponseHelper.AutosplitMessage(table.ToString());
await ctx.RespondAsync(pages[0], ephemeral: ephemeral).ConfigureAwait(false);
foreach (var page in pages.Skip(1).Take(4))
foreach (var page in pages.Skip(1).Take(EmbedPager.MaxFollowupMessages))
await ctx.FollowupAsync(page, ephemeral: ephemeral).ConfigureAwait(false);
}
}

View File

@@ -16,6 +16,10 @@ internal static class WarningsContextMenus
public static ValueTask WarnMessageAuthor(MessageCommandContext ctx, DiscordMessage message)
=> Warn(ctx, message, null);
[Command("🔍 Show warnings"), SlashCommandTypes(DiscordApplicationCommandType.UserContextMenu)]
public static ValueTask ShowWarnings(UserCommandContext ctx, DiscordUser user)
=> Warnings.ListGroup.List(ctx, user);
private static async ValueTask Warn(SlashCommandContext ctx, DiscordMessage? message = null, DiscordUser? user = null)
{
var interactivity = ctx.Extension.ServiceProvider.GetService<InteractivityExtension>();
@@ -72,7 +76,7 @@ internal static class WarningsContextMenus
.AddMention(UserMention.All);
await ctx.Channel.SendMessageAsync(userMsg).ConfigureAwait(false);
}
await Warnings.ListUserWarningsAsync(ctx.Client, ctx.Interaction, user.Id, user.Username.Sanitize()).ConfigureAwait(false);
await Warnings.ListUserWarningsAsync(ctx.Client, interaction, user.Id, user.Username.Sanitize()).ConfigureAwait(false);
}
catch (Exception e)

View File

@@ -5,37 +5,31 @@ using DSharpPlus.Commands.Converters;
namespace CompatBot.Commands;
/*
internal sealed partial class Warnings
internal static partial class Warnings
{
[Command("list"), TextAlias("show")]
[Command("list")]
[Description("Allows to list warnings in various ways. Users can only see their own warnings.")]
public class ListGroup
internal static class ListGroup
{
[Command("me"), DefaultGroupCommand]
[Description("List your own warning list")]
public async Task List(CommandContext ctx)
=> await List(ctx, ctx.Message.Author).ConfigureAwait(false);
[Command("user")]
[Description("Show warning list for a user. Default is to show warning list for yourself")]
public async Task List(CommandContext ctx, [Description("Discord user to list warnings for")] DiscordUser user)
[Description("Show warning list for a user")]
public static async ValueTask List(SlashCommandContext ctx, DiscordUser user)
{
await ctx.DeferResponseAsync(true).ConfigureAwait(false);
if (await CheckListPermissionAsync(ctx, user.Id).ConfigureAwait(false))
await ListUserWarningsAsync(ctx.Client, ctx.Message, user.Id, user.Username.Sanitize(), false);
await ListUserWarningsAsync(ctx.Client, ctx.Interaction, user.Id, user.Username.Sanitize(), skipIfOne: false, useFollowup: true);
}
[Command("user")]
public async Task List(CommandContext ctx, [Description("Id of the user to list warnings for")] ulong userId)
{
if (await CheckListPermissionAsync(ctx, userId).ConfigureAwait(false))
await ListUserWarningsAsync(ctx.Client, ctx.Message, userId, $"<@{userId}>", false);
}
[Command("users"), TextAlias("top"), RequiresBotModRole, TriggersTyping]
[Description("List users with warnings, sorted from most warned to least")]
public async Task Users(CommandContext ctx, [Description("Optional number of items to show. Default is 10")] int number = 10)
[Command("top")]
[Description("List top users with warnings")]
public static async ValueTask Users(
SlashCommandContext ctx,
[Description("Number of items to show. Default is 10")]
int number = 10
)
{
var ephemeral = !ctx.Channel.IsSpamChannel() && !ctx.Channel.IsOfftopicChannel();
await ctx.DeferResponseAsync(ephemeral).ConfigureAwait(false);
try
{
if (number < 1)
@@ -58,19 +52,28 @@ internal sealed partial class Warnings
var username = await ctx.GetUserNameAsync(row.discordId).ConfigureAwait(false);
table.Add(username, row.discordId.ToString(), row.count.ToString(), row.total.ToString());
}
await ctx.SendAutosplitMessageAsync(new StringBuilder("Warning count per user:").Append(table)).ConfigureAwait(false);
var pages = AutosplitResponseHelper.AutosplitMessage(new StringBuilder("Warning count per user:").Append(table).ToString());
await ctx.RespondAsync(pages[0], ephemeral: ephemeral).ConfigureAwait(false);
foreach (var page in pages.Skip(1).Take(EmbedPager.MaxFollowupMessages))
await ctx.FollowupAsync(page, ephemeral: ephemeral).ConfigureAwait(false);
}
catch (Exception e)
{
Config.Log.Error(e);
await ctx.ReactWithAsync(Config.Reactions.Failure, "SQL query for this command is broken at the moment", true).ConfigureAwait(false);
await ctx.RespondAsync($"{Config.Reactions.Failure} Failed to execute the command: {e.Message}".Trim(EmbedPager.MaxMessageLength), ephemeral: true).ConfigureAwait(false);
}
}
[Command("mods"), TextAlias("mtop"), RequiresBotModRole, TriggersTyping]
[Description("List bot mods, sorted by the number of warnings issued")]
public async Task Mods(CommandContext ctx, [Description("Optional number of items to show. Default is 10")] int number = 10)
[Command("mods")]
[Description("List top bot mods giving warnings")]
public static async ValueTask Mods(
SlashCommandContext ctx,
[Description("Number of items to show. Default is 10")]
int number = 10
)
{
var ephemeral = !ctx.Channel.IsSpamChannel() && !ctx.Channel.IsOfftopicChannel();
await ctx.DeferResponseAsync(ephemeral).ConfigureAwait(false);
try
{
if (number < 1)
@@ -95,19 +98,27 @@ internal sealed partial class Warnings
username = "Unknown";
table.Add(username, row.userId.ToString(), row.count.ToString(), row.total.ToString());
}
await ctx.SendAutosplitMessageAsync(new StringBuilder("Warnings issued per bot mod:").Append(table)).ConfigureAwait(false);
var pages = AutosplitResponseHelper.AutosplitMessage(new StringBuilder("Warnings issued per bot mod:").Append(table).ToString());
await ctx.RespondAsync(pages[0], ephemeral: ephemeral).ConfigureAwait(false);
foreach (var page in pages.Skip(1).Take(EmbedPager.MaxFollowupMessages))
await ctx.FollowupAsync(page, ephemeral: ephemeral).ConfigureAwait(false);
}
catch (Exception e)
{
Config.Log.Error(e);
await ctx.ReactWithAsync(Config.Reactions.Failure, "SQL query for this command is broken at the moment", true).ConfigureAwait(false);
await ctx.RespondAsync($"{Config.Reactions.Failure} Failed to execute the command: {e.Message}".Trim(EmbedPager.MaxMessageLength), ephemeral: true).ConfigureAwait(false);
}
}
[Command("by"), RequiresBotModRole]
[Command("by")]
[Description("Shows warnings issued by the specified moderator")]
public async Task By(CommandContext ctx, ulong moderatorId, [Description("Optional number of items to show. Default is 10")] int number = 10)
public static async ValueTask By(
SlashCommandContext ctx,
DiscordUser moderator,
[Description("Number of items to show. Default is 10")] int number = 10
)
{
await ctx.DeferResponseAsync(true).ConfigureAwait(false);
if (number < 1)
number = 10;
var table = new AsciiTable(
@@ -120,7 +131,7 @@ internal sealed partial class Warnings
);
await using var db = new BotDb();
var query = from warn in db.Warning
where warn.IssuerId == moderatorId && !warn.Retracted
where warn.IssuerId == moderator.Id && !warn.Retracted
orderby warn.Id descending
select warn;
foreach (var row in query.Take(number))
@@ -129,50 +140,38 @@ internal sealed partial class Warnings
var timestamp = row.Timestamp.HasValue ? new DateTime(row.Timestamp.Value, DateTimeKind.Utc).ToString("u") : "";
table.Add(row.Id.ToString(), username, row.DiscordId.ToString(), timestamp, row.Reason, row.FullReason);
}
var modName = await ctx.GetUserNameAsync(moderatorId, defaultName: "Unknown mod").ConfigureAwait(false);
await ctx.SendAutosplitMessageAsync(new StringBuilder($"Recent warnings issued by {modName}:").Append(table)).ConfigureAwait(false);
var modName = moderator.Username;
var pages = AutosplitResponseHelper.AutosplitMessage(new StringBuilder($"Recent warnings issued by {modName}:").Append(table).ToString());
await ctx.RespondAsync(pages[0], ephemeral: true).ConfigureAwait(false);
foreach (var page in pages.Skip(1).Take(EmbedPager.MaxFollowupMessages))
await ctx.FollowupAsync(page, ephemeral: true).ConfigureAwait(false);
}
[Command("by"), RequiresBotModRole]
public async Task By(CommandContext ctx, string me, [Description("Optional number of items to show. Default is 10")] int number = 10)
{
if (me.ToLowerInvariant() == "me")
{
await By(ctx, ctx.User.Id, number).ConfigureAwait(false);
return;
}
var user = await ((IArgumentConverter<DiscordUser>)new DiscordUserConverter()).ConvertAsync(me, ctx).ConfigureAwait(false);
if (user.HasValue)
await By(ctx, user.Value, number).ConfigureAwait(false);
}
[Command("by"), RequiresBotModRole]
public Task By(CommandContext ctx, DiscordUser moderator, [Description("Optional number of items to show. Default is 10")] int number = 10)
=> By(ctx, moderator.Id, number);
[Command("recent"), TextAlias("last", "all"), RequiresBotModRole]
[Description("Shows last issued warnings in chronological order")]
public async Task Last(CommandContext ctx, [Description("Optional number of items to show. Default is 10")] int number = 10)
[Command("recent")]
[Description("Show last issued warnings")]
public static async ValueTask Last(
SlashCommandContext ctx,
[Description("Number of items to show. Default is 10")]
int number = 10
)
{
await ctx.DeferResponseAsync(true).ConfigureAwait(false);
var isMod = await ctx.User.IsWhitelistedAsync(ctx.Client, ctx.Guild).ConfigureAwait(false);
var showRetractions = ctx.Channel.IsPrivate && isMod;
if (number < 1)
number = 10;
var table = new AsciiTable(
new AsciiColumn("ID", alignToRight: true),
new AsciiColumn("±", disabled: !showRetractions),
new AsciiColumn("±", disabled: !isMod),
new AsciiColumn("Username", maxWidth: 24),
new AsciiColumn("User ID", disabled: !ctx.Channel.IsPrivate, alignToRight: true),
new AsciiColumn("User ID", disabled: !isMod, alignToRight: true),
new AsciiColumn("Issued by", maxWidth: 15),
new AsciiColumn("On date (UTC)"),
new AsciiColumn("Reason"),
new AsciiColumn("Context", disabled: !ctx.Channel.IsPrivate)
new AsciiColumn("Context", disabled: !isMod)
);
await using var db = new BotDb();
IOrderedQueryable<Warning> query;
if (showRetractions)
if (isMod)
query = from warn in db.Warning
orderby warn.Id descending
select warn;
@@ -196,17 +195,19 @@ internal sealed partial class Warnings
else
table.Add(row.Id.ToString(), "+", username, row.DiscordId.ToString(), modName, timestamp, row.Reason, row.FullReason);
}
await ctx.SendAutosplitMessageAsync(new StringBuilder("Recent warnings:").Append(table)).ConfigureAwait(false);
var pages = AutosplitResponseHelper.AutosplitMessage(new StringBuilder("Recent warnings:").Append(table).ToString());
await ctx.RespondAsync(pages[0], ephemeral: true).ConfigureAwait(false);
foreach (var page in pages.Skip(1).Take(EmbedPager.MaxFollowupMessages))
await ctx.FollowupAsync(page, ephemeral: true).ConfigureAwait(false);
}
private async Task<bool> CheckListPermissionAsync(CommandContext ctx, ulong userId)
private static async ValueTask<bool> CheckListPermissionAsync(SlashCommandContext ctx, ulong userId)
{
if (userId == ctx.Message.Author.Id || ModProvider.IsMod(ctx.Message.Author.Id))
if (userId == ctx.User.Id || ModProvider.IsMod(ctx.User.Id))
return true;
await ctx.ReactWithAsync(Config.Reactions.Denied, "Regular users can only view their own warnings").ConfigureAwait(false);
await ctx.RespondAsync($"{Config.Reactions.Denied} You can only view your own warnings").ConfigureAwait(false);
return false;
}
}
}
*/

View File

@@ -194,12 +194,12 @@ internal static partial class Warnings
//note: be sure to pass a sanitized userName
//note2: itneraction must be deferred
internal static async ValueTask ListUserWarningsAsync(DiscordClient client, DiscordInteraction interaction, ulong userId, string userName, bool skipIfOne = true)
internal static async ValueTask ListUserWarningsAsync(DiscordClient client, DiscordInteraction interaction, ulong userId, string userName, bool skipIfOne = true, bool useFollowup = false)
{
try
{
var isWhitelisted = await interaction.User.IsWhitelistedAsync(client, interaction.Guild).ConfigureAwait(false);
if (interaction.User.Id != userId && !isWhitelisted)
var isMod = await interaction.User.IsWhitelistedAsync(client, interaction.Guild).ConfigureAwait(false);
if (interaction.User.Id != userId && !isMod)
{
Config.Log.Error($"Somehow {interaction.User.Username} ({interaction.User.Id}) triggered warning list for {userId}");
return;
@@ -249,22 +249,22 @@ internal static partial class Warnings
var showCount = Math.Min(maxWarningsInPublicChannel, count);
var table = new AsciiTable(
new AsciiColumn("ID", alignToRight: true),
new AsciiColumn("±", disabled: !ephemeral || !isWhitelisted),
new AsciiColumn("±", disabled: !ephemeral || !isMod),
new AsciiColumn("By", maxWidth: 15),
new AsciiColumn("On date (UTC)"),
new AsciiColumn("Reason"),
new AsciiColumn("Context", disabled: !ephemeral, maxWidth: 4096)
new AsciiColumn("Context", disabled: !ephemeral || !isMod, maxWidth: 4096)
);
IQueryable<Warning> query = db.Warning.Where(w => w.DiscordId == userId).OrderByDescending(w => w.Id);
if (!ephemeral || !isWhitelisted)
if (!ephemeral || !isMod)
query = query.Where(w => !w.Retracted);
if (!ephemeral && !isWhitelisted)
if (!ephemeral && !isMod)
query = query.Take(maxWarningsInPublicChannel);
foreach (var warning in await query.ToListAsync().ConfigureAwait(false))
{
if (warning.Retracted)
{
if (isWhitelisted && ephemeral)
if (isMod && ephemeral)
{
var retractedByName = warning.RetractedBy.HasValue
? await client.GetUserNameAsync(interaction.Channel, warning.RetractedBy.Value, ephemeral, "unknown mod").ConfigureAwait(false)
@@ -274,7 +274,7 @@ internal static partial class Warnings
: "";
table.Add(warning.Id.ToString(), "-", retractedByName, retractionTimestamp, warning.RetractionReason ?? "", "");
var issuerName = warning.IssuerId == 0
var issuerName = warning.IssuerId is 0
? ""
: await client.GetUserNameAsync(interaction.Channel, warning.IssuerId, ephemeral, "unknown mod").ConfigureAwait(false);
var timestamp = warning.Timestamp.HasValue
@@ -285,7 +285,7 @@ internal static partial class Warnings
}
else
{
var issuerName = warning.IssuerId == 0
var issuerName = warning.IssuerId is 0
? ""
: await client.GetUserNameAsync(interaction.Channel, warning.IssuerId, ephemeral, "unknown mod").ConfigureAwait(false);
var timestamp = warning.Timestamp.HasValue
@@ -296,19 +296,21 @@ internal static partial class Warnings
}
var result = new StringBuilder("Warning list for ").Append(Formatter.Sanitize(userName));
if (!ephemeral && !isWhitelisted && count > maxWarningsInPublicChannel)
if (!ephemeral && !isMod && count > maxWarningsInPublicChannel)
result.Append($" (last {showCount} of {count}, full list in DMs)");
result.AppendLine(":").Append(table);
var pages = AutosplitResponseHelper.AutosplitMessage(result.ToString());
await interaction.EditOriginalResponseAsync(new(response.WithContent(pages[0]))).ConfigureAwait(false);
foreach (var page in pages.Skip(1).Take(4))
if (useFollowup)
{
var followupMsg = new DiscordFollowupMessageBuilder()
.AsEphemeral(ephemeral)
.WithContent(page);
await interaction.CreateFollowupMessageAsync(followupMsg).ConfigureAwait(false);
}
}
foreach (var page in pages.Skip(1).Take(EmbedPager.MaxFollowupMessages))
{
var followupMsg = new DiscordFollowupMessageBuilder()
.AsEphemeral(ephemeral)
.WithContent(page);
await interaction.CreateFollowupMessageAsync(followupMsg).ConfigureAwait(false);
}
} }
catch (Exception e)
{
Config.Log.Warn(e);

View File

@@ -44,6 +44,7 @@
<PackageReference Include="DSharpPlus" Version="5.0.0-nightly-02474" />
<PackageReference Include="DSharpPlus.Commands" Version="5.0.0-nightly-02474" />
<PackageReference Include="DSharpPlus.Interactivity" Version="5.0.0-nightly-02474" />
<PackageReference Include="DSharpPlus.Natives.Zstd" Version="1.5.7.19" />
<PackageReference Include="Google.Apis.Drive.v3" Version="1.69.0.3703" />
<PackageReference Include="ksemenenko.ColorThief" Version="1.1.1.4" />
<PackageReference Include="MathParser.org-mXparser" Version="6.1.0" />

View File

@@ -271,7 +271,7 @@ internal static class Config
};
watchdogTarget.Parameters.AddRange([new MethodCallParameter("${level}"), new("${message}")]);
#if DEBUG
loggingConfig.AddRule(LogLevel.Error, LogLevel.Fatal, consoleTarget);
loggingConfig.AddRule(LogLevel.Warn, LogLevel.Fatal, consoleTarget);
loggingConfig.AddRule(LogLevel.Trace, LogLevel.Fatal, consoleTarget, "default"); // echo all messages from default logger to the console
#else
loggingConfig.AddRule(LogLevel.Info, LogLevel.Fatal, consoleTarget, "default");

View File

@@ -1,177 +0,0 @@
//todo: rewrite this whole thing
/*
using System.Text.RegularExpressions;
using CompatBot.Utils.Extensions;
using DSharpPlus.Commands.EventArgs;
using DSharpPlus.Commands.Exceptions;
using DSharpPlus.Commands.Processors.TextCommands;
namespace CompatBot.EventHandlers;
internal static partial class UnknownCommandHandler
{
[GeneratedRegex(
@"^\s*(am I|(are|is|do(es)|did|can(?!\s+of\s+)|should|must|have)(n't)?|shall|shan't|may|will|won't)\b",
RegexOptions.Singleline | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase
)]
private static partial Regex BinaryQuestion();
public static Task OnError(CommandsExtension cne, CommandErroredEventArgs e)
{
OnErrorInternal(cne, e);
return Task.CompletedTask;
}
public static async void OnErrorInternal(CommandsExtension cne, CommandErroredEventArgs e)
{
try
{
if (e.Context is not TextCommandContext ctx)
return;
if (ctx.User.IsBotSafeCheck())
return;
var ex = e.Exception;
if (ex is InvalidOperationException && ex.Message.Contains("No matching subcommands were found"))
ex = new CommandNotFoundException(ctx.Command.Name);
if (ex is not CommandNotFoundException cnfe)
{
Config.Log.Error(e.Exception);
return;
}
if (string.IsNullOrEmpty(cnfe.CommandName))
return;
if (ctx.Prefix != Config.CommandPrefix
&& ctx.Prefix != Config.AutoRemoveCommandPrefix
&& ctx.Message.Content is string msgTxt
&& (msgTxt.EndsWith("?") || BinaryQuestion().IsMatch(msgTxt.AsSpan(ctx.Prefix.Length)))
&& ctx.Extension.Commands.TryGetValue("8ball", out var cmd))
{
var updatedContext = ctx.CommandsNext.CreateContext(
ctx.Message,
ctx.Prefix,
cmd,
msgTxt[ctx.Prefix.Length ..].Trim()
);
try { await cmd.ExecuteAsync(updatedContext).ConfigureAwait(false); } catch { }
return;
}
var content = ctx.Message.Content;
if (content is null or {Length: <3})
return;
if (ctx.Prefix == Config.CommandPrefix)
{
var knownCmds = GetAllRegisteredCommands(ctx);
var termParts = content.Split(' ', 4, StringSplitOptions.RemoveEmptyEntries);
var normalizedTerm = string.Join(' ', termParts);
var terms = new string[termParts.Length];
terms[0] = termParts[0].ToLowerInvariant();
for (var i = 1; i < termParts.Length; i++)
terms[i] = terms[i - 1] + ' ' + termParts[i].ToLowerInvariant();
var cmdMatches = (
from t in terms
from kc in knownCmds
let v = (cmd: kc.alias, fqn: kc.fqn, w: t.GetFuzzyCoefficientCached(kc.alias), arg: normalizedTerm[t.Length ..])
where v.w is >0.5 and <1 // if it was a 100% match, we wouldn't be here
orderby v.w descending
select v
)
.DistinctBy(i => i.fqn)
.Take(4)
.ToList();
var btnExplain = new DiscordButtonComponent(cmdMatches.Count == 0 ? DiscordButtonStyle.Primary : DiscordButtonStyle.Secondary, "unk:cmd:explain", "Explain this", emoji: new(DiscordEmoji.FromUnicode("🔍")));
var btnCompat = new DiscordButtonComponent(DiscordButtonStyle.Secondary, "unk:cmd:compat", "Is this game playable?", emoji: new(DiscordEmoji.FromUnicode("🔍")));
var btnHelp = new DiscordButtonComponent(DiscordButtonStyle.Secondary, "unk:cmd:help", "Show bot commands", emoji: new(DiscordEmoji.FromUnicode("❔")));
var btnCancel = new DiscordButtonComponent(DiscordButtonStyle.Danger, "unk:cmd:cancel", "Ignore", emoji: new(DiscordEmoji.FromUnicode("✖")));
var cmdEmoji = new DiscordComponentEmoji(DiscordEmoji.FromUnicode("🤖"));
var msgBuilder = new DiscordMessageBuilder()
.WithContent("I'm afraid the intended command didn't spell out quite right")
.AddComponents(btnExplain, btnCompat, btnHelp, btnCancel);
if (cmdMatches.Count > 0)
{
var btnSuggest = cmdMatches.Select((m, i) => new DiscordButtonComponent(i == 0 ? DiscordButtonStyle.Primary : DiscordButtonStyle.Secondary, "unk:cmd:s:" + m.cmd, Config.CommandPrefix + m.fqn + m.arg, emoji: cmdEmoji));
foreach (var btn in btnSuggest)
msgBuilder.AddComponents(btn);
}
var interactivity = cne.Client.GetInteractivity();
var botMsg = await DiscordMessageExtensions.UpdateOrCreateMessageAsync(null, ctx.Channel, msgBuilder).ConfigureAwait(false);
var (_, reaction) = await interactivity.WaitForMessageOrButtonAsync(botMsg, ctx.User, TimeSpan.FromMinutes(1)).ConfigureAwait(false);
string? newCmd = null, newArg = content;
if (reaction?.Id is string btnId)
{
if (btnId == btnCompat.CustomId)
newCmd = "c";
else if (btnId == btnExplain.CustomId)
newCmd = "explain";
else if (btnId == btnHelp.CustomId)
{
newCmd = "help";
newArg = null;
}
else if (btnId.StartsWith("unk:cmd:s:"))
{
newCmd = btnId["unk:cmd:s:".Length ..];
newArg = cmdMatches.First(m => m.cmd == newCmd).arg;
}
}
try { await botMsg.DeleteAsync().ConfigureAwait(false); } catch { }
if (newCmd is not null)
{
var botCommand = cne.FindCommand(newCmd, out _);
var commandCtx = cne.CreateContext(ctx.Message, ctx.Prefix, botCommand, newArg);
await cne.ExecuteCommandAsync(commandCtx).ConfigureAwait(false);
}
}
}
catch (Exception ex)
{
Config.Log.Error(ex);
}
}
private static List<(string alias, string fqn)> GetAllRegisteredCommands(CommandContext ctx)
{
if (allKnownBotCommands != null)
return allKnownBotCommands;
static void dumpCmd(List<(string alias, string fqn)> commandList, Command cmd, string qualifiedPrefix)
{
foreach (var alias in cmd.Aliases.Concat([cmd.Name]))
{
var qualifiedAlias = qualifiedPrefix + alias;
if (cmd is CommandGroup g)
{
if (g.IsExecutableWithoutSubcommands)
commandList.Add((qualifiedAlias, cmd.QualifiedName));
dumpChildren(g, commandList, qualifiedAlias + " ");
}
else
commandList.Add((qualifiedAlias, cmd.QualifiedName));
}
}
static void dumpChildren(CommandGroup group, List<(string alias, string fqn)> commandList, string qualifiedPrefix)
{
foreach (var cmd in group.Children)
dumpCmd(commandList, cmd, qualifiedPrefix);
}
var result = new List<(string alias, string fqn)>();
foreach (var cmd in ctx.CommandsNext.RegisteredCommands.Values)
dumpCmd(result, cmd, "");
allKnownBotCommands = result;
#if DEBUG
Config.Log.Debug("Total command alias permutations: " + allKnownBotCommands.Count);
#endif
return allKnownBotCommands;
}
private static List<(string alias, string fqn)>? allKnownBotCommands;
}
*/

View File

@@ -12,6 +12,7 @@ internal static class EmbedPager
public const int MaxFooterLength = 2048;
public const int MaxAuthorNameLength = 256;
public const int MaxMessageLength = 2000;
public const int MaxFollowupMessages = 5;
public static IEnumerable<DiscordEmbed> BreakInEmbeds(this IEnumerable<string> lines, DiscordEmbedBuilder builder, int maxLinesPerField = 10, string? titleBase = null)
{

View File

@@ -49,7 +49,7 @@ public static class DiscordClientExtensions
public static async ValueTask<string> GetUserNameAsync(this DiscordClient client, DiscordChannel channel, ulong userId, bool? forDmPurposes = null, string defaultName = "Unknown user")
{
var isPrivate = forDmPurposes ?? channel.IsPrivate;
if (userId == 0)
if (userId is 0)
return "";
try