using Humanizer; using OneOf; using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.API.Abstractions.Rest; using Remora.Discord.API.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? 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 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 async Task UpdateMessageAsync(this IDiscordRestInteractionAPI interactionApi, IInteraction interaction, InteractionMessageCallbackData data) => await interactionApi.CreateInteractionResponseAsync(interaction.ID, interaction.Token, new InteractionResponse(InteractionCallbackType.UpdateMessage, new Optional>(data))); 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 class DiscordRestException(string message) : Exception(message); }