using Discord; using RegexBot.Data; using System.Diagnostics.CodeAnalysis; using System.Text; using System.Text.RegularExpressions; namespace RegexBot.Common; /// /// Miscellaneous utility methods useful for the bot and modules. /// public static partial class Utilities { /// /// Gets a precompiled regex that matches a channel tag and pulls its snowflake value. /// [GeneratedRegex(@"<#(?\d+)>")] public static partial Regex ChannelMentionRegex(); /// /// Gets a precompiled regex that matches a custom emoji and pulls its name and ID. /// [GeneratedRegex(@"<:(?[A-Za-z0-9_]{2,}):(?\d+)>")] public static partial Regex CustomEmojiRegex(); /// /// Gets a precompiled regex that matches a fully formed Discord handle, extracting the name and discriminator. /// [GeneratedRegex(@"(.+)#(\d{4}(?!\d))")] public static partial Regex DiscriminatorSearchRegex(); /// /// Gets a precompiled regex that matches a user tag and pulls its snowflake value. /// [GeneratedRegex(@"<@!?(?\d+)>")] public static partial Regex UserMentionRegex(); /// /// Performs common checks on the specified message to see if it fits all the criteria of a /// typical, ordinary message sent by an ordinary guild user. /// public static bool IsValidUserMessage(SocketMessage msg, [NotNullWhen(true)] out SocketTextChannel? channel) { channel = default; if (msg.Channel is not SocketTextChannel ch) return false; if (msg.Author.IsBot || msg.Author.IsWebhook) return false; if (((IMessage)msg).Type != MessageType.Default) return false; if (msg is SocketSystemMessage) return false; channel = ch; return true; } /// /// Given a JToken, gets all string-based values out of it if the token may be a string /// or an array of strings. /// /// The JSON token to analyze and retrieve strings from. /// Thrown if the given token is not a string or array containing all strings. /// Thrown if the given token is null. public static List LoadStringOrStringArray(JToken? token) { const string ExNotString = "This token contains a non-string element."; if (token == null) throw new ArgumentNullException(nameof(token), "The provided token is null."); var results = new List(); if (token.Type == JTokenType.String) { results.Add(token.Value()!); } else if (token.Type == JTokenType.Array) { foreach (var entry in token.Values()) { if (entry.Type != JTokenType.String) throw new ArgumentException(ExNotString, nameof(token)); results.Add(entry.Value()!); } } else { throw new ArgumentException(ExNotString, nameof(token)); } return results; } /// /// Returns a representation of this entity that can be parsed by the constructor. /// public static string AsEntityNameString(this IUser entity) => $"@{entity.Id}::{entity.Username}"; /// /// If given string is in an EntityName format, returns a displayable representation of it based on /// a cache query. Otherwise, returns the input string as-is. /// [return: NotNullIfNotNull(nameof(input))] public static string? TryFromEntityNameString(string? input, RegexbotClient bot) { string? result = null; try { var entityTry = new EntityName(input!, EntityType.User); var issueq = bot.EcQueryUser(entityTry.Id!.Value.ToString()); if (issueq != null) result = $"<@{issueq.UserId}> - {issueq.GetDisplayableUsername()} `{issueq.UserId}`"; else result = $"Unknown user with ID `{entityTry.Id!.Value}`"; } catch (Exception) { } return result ?? input; } /// /// Given an input string, replaces certain special character combinations with information /// from the specified message. /// public static string ProcessTextTokens(string input, SocketMessage m) { // TODO elaborate on this // For now, replaces all instances of @_ with the message sender. return input .Replace("@_", m.Author.Mention) .Replace("@\\_", "@_"); } /// public static string GetDisplayableUsername(this SocketUser user) => GetDisplayableUsernameCommon(user.Username, user.Discriminator, user.GlobalName); /// public static string GetDisplayableUsername(this CachedUser user) => GetDisplayableUsernameCommon(user.Username, user.Discriminator, user.GlobalName); /// /// Returns a string representation of the user's username suitable for display purposes. /// For the sake of consistency, it is preferable using this instead of any other means, including Discord.Net's ToString. /// private static string GetDisplayableUsernameCommon(string username, string discriminator, string? global) { static string escapeFormattingCharacters(string input) { var result = new StringBuilder(); foreach (var c in input) { if (c is '\\' or '_' or '~' or '*' or '@' or '`') { result.Append('\\'); } result.Append(c); } return result.ToString(); } if (discriminator == "0000") { if (global != null) return $"{escapeFormattingCharacters(global)} ({username})"; return username; } else { return $"{escapeFormattingCharacters(username)}#{discriminator}"; } } }