using System.Drawing; using Catalogger.Backend.Cache.InMemoryCache; using Humanizer; using Remora.Discord.API.Abstractions.Gateway.Events; using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.Commands.Contexts; using Remora.Discord.Commands.Extensions; using Remora.Discord.Commands.Services; using Remora.Rest.Core; using Remora.Results; namespace Catalogger.Backend.Extensions; public static class DiscordExtensions { public static string Tag(this IPartialUser user) { var discriminator = user.Discriminator.OrDefault(); return discriminator == 0 ? user.Username.Value : $"{user.Username.Value}#{discriminator:0000}"; } public static string AvatarUrl(this IUser user, int size = 256) { if (user.Avatar != null) { var ext = user.Avatar.HasGif ? ".gif" : ".webp"; return $"https://cdn.discordapp.com/avatars/{user.ID}/{user.Avatar.Value}{ext}?size={size}"; } var avatarIndex = user.Discriminator == 0 ? (int)((user.ID.Value >> 22) % 6) : user.Discriminator % 5; return $"https://cdn.discordapp.com/embed/avatars/{avatarIndex}.png?size={size}"; } public static string? AvatarUrl(this IGuildMemberUpdate member, int size = 256) => GuildAvatarUrl( member.GuildID, member.User.ID, member.Avatar.OrDefault()?.Value, isAnimated: member.Avatar.OrDefault()?.HasGif, size ); public static string? AvatarUrl(this IGuildMember member, Snowflake guildId, int size = 256) => GuildAvatarUrl( guildId, member.User.GetOrThrow().ID, member.Avatar.OrDefault()?.Value, isAnimated: member.Avatar.OrDefault()?.HasGif, size ); private static string? GuildAvatarUrl( Snowflake guildId, Snowflake userId, string? hash, bool? isAnimated, int size = 256 ) { if (hash == null) return null; var ext = isAnimated == true ? ".gif" : ".webp"; return $"https://cdn.discordapp.com/guilds/{guildId}/users/{userId}/avatars/{hash}{ext}?size={size}"; } public static string? IconUrl(this IGuild guild, int size = 256) { if (guild.Icon == null) return null; var ext = guild.Icon.HasGif ? ".gif" : ".webp"; return $"https://cdn.discordapp.com/icons/{guild.ID}/{guild.Icon.Value}{ext}?size={size}"; } public static ulong ToUlong(this Snowflake snowflake) => snowflake.Value; public static ulong ToUlong(this Optional snowflake) { if (!snowflake.IsDefined()) throw new Exception("ToUlong called on an undefined Snowflake"); return snowflake.Value.Value; } public static string ToPrettyString(this Color color) { var r = color.R.ToString("X2"); var g = color.G.ToString("X2"); var b = color.B.ToString("X2"); return $"#{r}{g}{b}"; } public static bool Is(this Optional s1, Snowflake s2) => s1.IsDefined(out var value) && value == s2; public static bool Is(this Optional s1, ulong s2) => s1.IsDefined(out var value) && value == s2; public static T GetOrThrow(this Result result) { if (result.Error != null) throw new DiscordRestException(result.Error.Message); return result.Entity; } public static T GetOrThrow(this Optional optional) => optional.OrThrow(() => new CataloggerError("Optional was unset")); public static async Task GetOrThrow(this Task> result) => (await result).GetOrThrow(); public static string ToPrettyString(this IDiscordPermissionSet permissionSet) => string.Join( ", ", permissionSet.GetPermissions().Select(p => p.Humanize(LetterCasing.Title)) ); public static (Snowflake, Snowflake) GetUserAndGuild( this ContextInjectionService contextInjectionService ) { if (contextInjectionService.Context is not IInteractionCommandContext ctx) throw new CataloggerError("No context"); if (!ctx.TryGetUserID(out var userId)) throw new CataloggerError("No user ID in context"); if (!ctx.TryGetGuildID(out var guildId)) throw new CataloggerError("No guild ID in context"); return (userId, guildId); } /// /// Sorts a list of roles by their position in the Discord interface. /// /// The list of guild roles to filter. /// An optional list of role IDs to return, from a member object or similar. /// If null, the entire list is returned. /// public static IEnumerable Sorted( this IEnumerable roles, IEnumerable? filterByIds = null ) { var sorted = roles.OrderByDescending(r => r.Position); return filterByIds != null ? sorted.Where(r => filterByIds.Contains(r.ID)) : sorted; } public static async Task TryFormatModeratorAsync( this UserCache userCache, AuditLogCache.ActionData actionData ) { var moderator = await userCache.GetUserAsync(actionData.ModeratorId); return moderator != null ? $"{moderator.Tag()} <@{moderator.ID}>" : $"*(unknown user {actionData.ModeratorId}) <@{actionData.ModeratorId}>*"; } public static int TextLength(this IEmbed embed) { var length = OptionalStringLength(embed.Description) + OptionalStringLength(embed.Title); var fieldLength = (embed.Fields.OrDefault() ?? []) .Select(f => f.Name.Length + f.Value.Length) .Sum(); return length + fieldLength; } private static int OptionalStringLength(Optional s) => s.OrDefault()?.Length ?? 0; public class DiscordRestException(string message) : Exception(message); }