From 933ea42eaac47094ef77608aa2aa3f6d602ac30d Mon Sep 17 00:00:00 2001 From: Quin Lynch <49576606+quinchs@users.noreply.github.com> Date: Tue, 23 Nov 2021 09:58:05 -0400 Subject: [PATCH] Merge Labs 3.X into dev (#1923) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * meta: bump version * Null or empty fix (#176) * Add components and stickers to ReplyAsync extension * Fixed null or empty * Changed Label to Description * -||- Co-authored-by: quin lynch * More regions (#177) * Preconditions * ChannelHelper * RestDMChannel * RestGroupChannel * RestBan * RestGroupUser * EntityExtensions * DiscordSocketClient * DiscordSocketClient * Discord.net.core.xml fix (#178) * Changed Label to Description * Added Discord- .MessageComponent .ISticker[] ,Discord.MessageComponent,Discord.ISticker[] to ReplyAsync * Remove references to labs * Update Discord.Net.sln * Added SendMessagesInThreads and StartEmbeddedActivities. (#175) * Added SendMessagesInThreads and StartEmbeddedActivities. Adjusted owner perms. Change UsePublicThreads -> CreatePublicThreads Change UsePrivateThreads -> CreatePrivateThreads * removed extra /// * Added UsePublicThreads and UsePrivateThreads back with Obsolete Attribute * removed 'false' from Obsolete Attribute * Squashed commit of the following: commit dca41a348e36a9b4e7006ef3a76377eb32aad276 Author: quin lynch Date: Thu Sep 23 07:02:19 2021 -0300 Autocomplete commands * meta: xml. closes #171 * Revert user agent and $device to dnet * meta: bump version * meta: bump vers * Fix sticker args * Grammer fix (#179) * Made IVoiceChannel mentionable * Embeds array for send message async (#181) * meta: bump version * meta: bump vers * Fix sticker args * Grammer fix (#179) * Added embeds for SendMessageAsync * [JsonProperty("embed")] forgot to remove this public Optional Embed { get; set; } * It has been done as requested. * Changed the old way of handeling single embeds * Moved embeds param and added options param * xmls Co-authored-by: quin lynch * Fix thread permissions (#183) * Update GuildPermissionsTests.cs * Update GuildPermissions.cs * Use compound assignment (#186) * Used compound assignment * -||- * -||- * Remove unnecessary suppression (#188) * Inlined variable declarations (#185) * Fixed some warnings (#184) * Fixed some warnings * Another fixed warning * Changed the SSendFileAsync to SendFileAsync * Removed para AlwaysAcknowledgeInteractions * Moved it back to the previous version * Added periods to the end like quin requested!! :(( Co-authored-by: MrCakeSlayer <13650699+MrCakeSlayer@users.noreply.github.com> * Object initialization can be simplified fixed (#189) * Conditional-expression-simplification (#193) * Capitlazation fixes (#192) * Removed-this. (#191) * Use 'switch' expression (#187) * Use 'switch' expression * Reverted it to the old switch case * Fixed-compiler-error (#194) * Submitting updates to include new permissions. (#195) * Submitting updates to include new permissions. * Make old permissions obsolete and update tests Co-authored-by: quin lynch * Update azure-pipelines.yml * Update azure-pipelines.yml * Update azure-pipelines.yml * Add support for long in autocomplete option * Add support for sending files with multiple embeds (#196) * Add support for sending files with multiple embeds * Simplify prepending single embed to embed array * Consistency for embeds endpoints (#197) * Changed the way of handling prepending of embeds. For consistency. * reformatted the summary * Revert pipeline * Fix duplicate merge conflicts * Changed minimum slash command name length to 1 per Discord API docs (#198) * Channel endpoints requirements correction (#199) * Added some requirements to channels for topic * Changed check from NotNullOrEmpty to NotNullOrEmpty * Added some requirements to channels for name Preconditions.LessThan * Formatting of file * Added restriction for description not being null (#200) * Update azure-pipelines.yml * Update deploy.yml * Remove version tag from proj * Update deploy.yml * Removed versions from project files * Removed style of the nuget badge and added logo (#201) The style was not properly added to it and the plastic version does not look good with the discord badge. I thought it would look better with a logo * Fix Type not being set in SocketApplicationCommand * Remove useless GuildId property * meta: update XML * Add Autocomplete to SlashCommandOptionBuilder * Added autocomplete in SlashCommandOptionBuilder. (#206) Co-authored-by: Quin Lynch <49576606+quinchs@users.noreply.github.com> * Fix duplicate autocomplete * Fix #208 * Fix sub commands being interpreted as a parameter for autocomplete * Fix exposed optional * Support the discord:// protocol in buttons (#207) * Update UrlValidation.cs * Update ComponentBuilder.cs * Add docs and better error messages. * Fix wonky intentation * Add competing activity status type (#205) * Update GuildPermissionsTests.cs * Update GuildPermissions.cs * Add competing status type * Add Icons to IRole (#204) * Added icon field to IRole * Added GetGuildRoleIconUrl() * Added Clean Content Function (#174) * Added Clean Content Function * Fixed Spelling problems and bad var handling * Add StripMarkDown Method * Clean Content Expanded (#212) * Implement CleanContent In IMessage & RestMessage * Update Spelling and Documentation * Add SanatizeMessage to MessageHelper and Refactor Rest and Socket Message * Add event for autocomplete interaction (#214) * Spelling corrections (#215) * Remove null collections * Followup with file async warnings (#216) * Changed from NotNullOrWhitespace to NotNullOrEmpty * Added NotNullOrEmpty on filename * Added system to interpret from the path * Added a check for if it contains a period * It has been done, how ever it will break stuff * Changed to use ??= how ever still added error check * Added space under check * Changed from with a period to valid file extension * Added checks for SendFileAsync * Removed filename != null && * Add channel types in application command options. (#217) * add channel types in application command options * Indent Docs * Stage instance audit logs as well as thread audit log type * Update azure-pipelines.yml * Update azure-pipelines.yml * Fix system messages not including mentioned users. Added ContextMenuCommand message type * Remove file extension check (#218) * Fix NRE in modify guild channel * Fix 429's not being accounted for in ratelimit updates * meta: add net5 framework Co-Authored-By: MrCakeSlayer <13650699+MrCakeSlayer@users.noreply.github.com> * Proper doc logos (#221) * Update GuildPermissionsTests.cs * Update GuildPermissions.cs * Add competing activity status type * logo changes * logo text as path * add missing logo * Update package logo and favicon * Update docfx references * Remove XML files and use original pipeline format * Remove console writeline * Remove Console.WriteLine * Remove useless log * Rename Available sticker field to IsAvailable * Rename Available to IsAvailable in stickers * Add summary indent for role members * Add summary indent to SocketInvite * Rename DefaultPermission to IsDefaultPermission * Rename Default to IsDefault and Required to IsRequired in IApplicationCommandOption * Rename Default and Required to IsDefault and IsRequired in IApplicationCommandOption. Rename DefaultPermission to IsDefaultPermission in IApplicationCommand * Remove extra white spaces * Renamed Joined, Archived, and Locked to HasJoined, IsArchived, and IsLocked * Rename Live and DiscoverableDisabled to IsDiscoverableDisabled and IsLive in IStageChannel * Remove newline * Add indent to summaries * Remove unnecessary json serializer field * Fix ToEntity for roletags incorrectly using IsPremiumSubscriber * Update RestChannel for new channel types * Fix different rest channels not deserializing properly * fully qualify internal for UrlValidation and add indent to summary * Add missing periods to InteractionResponseType * Fix summary in IApplicationCommandOptionChoice * Update IApplicationCommandOption summaries * Update IApplicationCommandInteractionDataOption summaries * Update IApplicationCommandInteractionData summaries * Update IApplicationCommand summaries * Update ApplicationCommandType summaries * rename DefaultPermission to IsDefaultPermission in ApplicationCommandProperties * update ApplicationCommandOptionChoiceProperties summaries * Rename Default, Required, and Autocomplete to IsDefault, IsRequired, and IsAutocomplete in ApplicationCommandOptionProperties * Update SlashCommandProperties summaries * update SlashCommandBuilder boolean field names, summaries, and choice parameters * Update SelectMenuOption summaries, Rename Default to IsDefault in SelectMenuOption * update SelectMenuComponent summaries. Rename Disabled to IsDisabled in SelectMenuComponent * update ComponentBuilder summaries and boolean fields. * Update ButtonComponent summaries and boolean fields * update ActionRowComponent summaries * Update UserCommandBuilder * Update MessageCommandBuilder summaries and boolean properties * Update IGuild summary * Update IGuild summaries * Update StagePrivacyLevel summary * update IThreadChannel summaries * Update IStageChannel summaries * Refactor summaries and boolean property names * General cleanup (#223) * General cleanup * Add Async suffix to SendAutocompleteResult * Fix more formatting * Fix unused RequestOptions in GetActiveThreadsAsync * Add message to ArgumentNullException * Ephemeral attachments * Add missing jsonproperty attribute * Add IMessage.Interaction * Update attachment checks for embed urls * meta: bump version * Remove old package configs and update image * Update package logos * Fix logo reference for azure * Deprecate old package definitions in favor for target file * Deprecate old package definitions in favor for target file Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update package ids * Fix url validation * meta: bump version * Fix assignment of UserMentions (#233) * Fix CleanContent (#231) * Fix SocketSlashCommandData access modifier. (#237) Fixes #229 * Update README with better header (#232) * Update README with better header Adds HTML elements that implement the main logo & improve the redirection tag positions. * Resolving border issue in light-mode * Update sponsor section * Implement checks for interaction respond times and multiple interaction responses. closes #236, #235 * Add response check to socket auto complete * meta: bump versions * Fix #239 * meta: bump version * meta: update logo * meta: bump versions * Revert received at time, confirmed by discord staff to be accurate * Merge branch 'release/3.x' of https://github.com/Discord-Net-Labs/Discord.Net-Labs into merger-labs Update requested changes of obsolete and references to labs. Added `Interaction` to `IMessage` Fixed grammar Fixed bugs relating to interactions. * Update docs * Update CHANGELOG.md * meta: docs building * Update docs.yml * Update docs.yml * Fix docfx version * Update docs.yml * Update docs.bat * Rename docs repo for clone * update docfx version * Update docs.bat * Update docfx version * Remove docs from pipeline * FAQ revamped, metadata updated (#241) * FAQ revamped, metadata updated * Update FAQ.md * Update README.md * Docs index improvement * Fix InvalidOperationException in modify channel * feature: guild avatars, closes #238 * feature: modify role icons * meta: changelog * meta: bump version * Update README.md * Fix non value type options not being included in autocomplete * Add new activity flags (#254) * Add new activity flags * Add missing commas * Added support for GUILD_JOIN_REQUEST_DELETE event (#253) Fixes #247 * Adding BotHTTPInteraction user flag (#252) * animated guild banner support (#255) * Docs work (WIP) (#242) * Main page work * Metadata logo dir * More main page edits * Naming change * Dnet guide entries pruned * Add student hub guild directory channel (#256) * animated guild banner support * Add guild directory channel * Fix followup with file overwrite having incorrect parameter locations * Merge labs 3.x * Update GUILD_JOIN_REQUEST_DELETE event * Update head.tmpl.partial * Removed BannerId and AccentColor (#260) * Removed BannerId property, GetBannerURL method, and AccentColor property from IUser and socket entities. * Fixed errors in IUser.cs * Added back summary for GetAvatarUrl method in IUser.cs * Support Guild Boost Progress Bars (#262) * Support Guild Boost Progress Bars * Update SocketChannel.cs * Fix non-optional and unnecessary values. * Spelling * Reordering and consistency. * Remove log for reconnect * Add missing flags to SystemChannelMessageDeny (#267) * Fix labs reference in analyzer project and provider project * Rename new activity flags * Guild feature revamp and smart gateway intent checks * Get thread user implementation * Amend creating slash command guide (#269) * Adding BotHTTPInteraction user flag * Added comments explaining the Global command create stipulations. * Fix numeric type check for options * Add state checking to ConnectionManager.StartAsync (#272) * initial interface changes * Multi file upload + attachment editing * meta: bump versions * Update CHANGELOG.md * Update CHANGELOG.md * Support Min and Max values on ApplicationCommandOptions (#273) * Support Min and Max values on ApplicationCommandOptions * Support decimal min/max values * Docs imrpovments + use ToNullable * Logomark, doc settings edit (#258) * Logomark, doc settings edit * Replace standard logo * Bumping docfx plugins to latest release * Bump version metadata * Logo svg fix * Change default sticker behavior and add AlwaysResolveSticker to the config * Implement rest based interactions. Added ED25519 checks. Updated summaries. * Update package logo * Automatically fix ordering of optional command options (#276) * auto fix optional command option order * clean up indentation * Fix maximum number of Select Menu Options (#282) As of https://discord.com/developers/docs/interactions/message-components#select-menu-object-select-menu-structure the maximum number of options is 25, not less than 25. Hopefully the change catches all necessary locations * Add voice region to modify voice channels * Update summaries on rest interactions * Interaction Specific Interfaces (#283) * added interaction specific interfaces * fix build error * implement change requests * Update application * Add Guild Scheduled Events (#279) * guild events initial * sharded events * Add new gateway intents and fix bugs * More work on new changes to guild events * Update guild scheduled events * Added events to extended guild and add event start event * Update preconditions * Implement breaking changes guild guild events. Add guild event permissions * Update tests and change privacy level requirements * Update summaries and add docs for guild events * meta: bump version * Increment meta version (#285) * Increment meta version * Update docfx.json * Fix #289 and add configureawaits to rest based interactions * meta: bump version * Add GUILD_SCHEDULED_EVENT_USER_ADD and GUILD_SCHEDULED_EVENT_USER_REMOVE (#287) * Remove newline * Fix autocomplete result value * meta: bump versions * Add `GuildScheduledEventUserAdd` and `GuildScheduledEventUserRemove` to sharded client * Make RestUserCommand public (#292) * Fix Components not showing on FUWF (#288) (#293) Adds Components to Payload JSON Generation * Implement smarter rest resolvable interaction data. Fixes #294 * Add UseInteractionSnowflakeDate to config #286 * Implement Better Discord Errors (#291) * Initial error parsing * Implement better errors * Add missing error codes * Add voice disconnect opcodes * Remove unused class, add summaries to discordjsonerror, and remove public constructor of slash command properties * Add error code summary * Update error message summary * Update src/Discord.Net.Core/DiscordJsonError.cs Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update src/Discord.Net.WebSocket/API/Voice/VoiceCloseCode.cs Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Fix autocomplete result value Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Change the minimum length of slash commands to 1 (#284) * Change the minimum length of slash commands to 1. This is the correct value according to the docs and it has been changed after user feedback. * Fix the limit in 3 other places Co-authored-by: quin lynch * Add new thread creation properties * Add role emoji. Fixes #295 * Fix mocked text channel * Fix precondition checks. Closes #281 * Initial fix (#297) * meta: bump version * Update from release/3.x * Remove more labs references * Remove doc file for Discord.Net.Analyzers Co-authored-by: Simon Hjorthøj Co-authored-by: drobbins329 Co-authored-by: MrCakeSlayer <13650699+MrCakeSlayer@users.noreply.github.com> Co-authored-by: d4n3436 Co-authored-by: Will Co-authored-by: Eugene Garbuzov Co-authored-by: CottageDwellingCat <80918250+CottageDwellingCat@users.noreply.github.com> Co-authored-by: Emily <89871431+emillly-b@users.noreply.github.com> Co-authored-by: marens101 Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> Co-authored-by: Armano den Boef <68127614+Rozen4334@users.noreply.github.com> Co-authored-by: Bill Co-authored-by: Liege72 <65319395+Liege72@users.noreply.github.com> Co-authored-by: Floowey Co-authored-by: Cenk Ergen <57065323+Cenngo@users.noreply.github.com> Co-authored-by: exsersewo Co-authored-by: Dennis Fischer --- Discord.Net.sln | 2 +- LICENSE | 2 +- README.md | 10 + docs/guides/concepts/ratelimits.md | 49 + docs/guides/emoji/emoji.md | 8 +- docs/guides/guild_events/creating-guild-events.md | 31 + docs/guides/guild_events/getting-event-users.md | 16 + docs/guides/guild_events/intro.md | 41 + docs/guides/guild_events/modifying-events.md | 23 + .../application-commands/01-getting-started.md | 32 + .../creating-context-menu-commands.md | 105 + .../receiving-context-menu-command-events.md | 33 + .../slash-commands/02-creating-slash-commands.md | 98 + .../03-responding-to-slash-commands.md | 40 + .../slash-commands/04-parameters.md | 102 + .../slash-commands/05-responding-ephemerally.md | 23 + .../slash-commands/06-subcommands.md | 219 + .../slash-commands/07-choice-slash-command.md | 85 + .../08-bulk-overwrite-of-global-slash-commands.md | 40 + .../slash-commands/images/ephemeral1.png | Bin 0 -> 37660 bytes .../slash-commands/images/feedback1.png | Bin 0 -> 31294 bytes .../slash-commands/images/feedback2.png | Bin 0 -> 28118 bytes .../slash-commands/images/listroles1.png | Bin 0 -> 38224 bytes .../slash-commands/images/listroles2.png | Bin 0 -> 33026 bytes .../slash-commands/images/oauth.png | Bin 0 -> 113764 bytes .../slash-commands/images/settings1.png | Bin 0 -> 76171 bytes .../slash-commands/images/settings2.png | Bin 0 -> 51089 bytes .../slash-commands/images/settings3.png | Bin 0 -> 52771 bytes .../slash-commands/images/slashcommand1.png | Bin 0 -> 10886 bytes .../slash-commands/images/slashcommand2.png | Bin 0 -> 18094 bytes docs/guides/interactions/intro.md | 10 + .../message-components/01-getting-started.md | 66 + .../message-components/02-responding-to-buttons.md | 37 + .../message-components/03-buttons-in-depth.md | 45 + .../message-components/04-select-menus.md | 76 + .../interactions/message-components/05-advanced.md | 87 + .../message-components/images/image1.png | Bin 0 -> 17256 bytes .../message-components/images/image2.png | Bin 0 -> 19725 bytes .../message-components/images/image3.png | Bin 0 -> 40520 bytes .../message-components/images/image4.png | Bin 0 -> 20983 bytes .../message-components/images/image5.png | Bin 0 -> 17868 bytes .../message-components/images/image6.png | Bin 0 -> 39425 bytes docs/guides/toc.yml | 67 +- .../02_commands_framework.csproj | 2 +- .../02_commands_framework/Modules/PublicModule.cs | 2 +- samples/02_commands_framework/Program.cs | 2 +- samples/03_sharded_client/03_sharded_client.csproj | 2 +- .../Services/CommandHandlingService.cs | 2 +- samples/idn/Inspector.cs | 2 +- samples/idn/Program.cs | 2 +- samples/idn/idn.csproj | 2 +- .../Discord.Net.Analyzers.csproj | 4 +- .../Attributes/AliasAttribute.cs | 2 +- .../Builders/CommandBuilder.cs | 11 +- src/Discord.Net.Commands/Builders/ModuleBuilder.cs | 9 +- .../Builders/ModuleClassBuilder.cs | 6 +- .../Builders/ParameterBuilder.cs | 11 +- src/Discord.Net.Commands/CommandService.cs | 21 +- .../Discord.Net.Commands.csproj | 2 +- .../Extensions/MessageExtensions.cs | 3 +- src/Discord.Net.Commands/Info/CommandInfo.cs | 6 +- src/Discord.Net.Commands/Info/ParameterInfo.cs | 4 +- src/Discord.Net.Commands/ModuleBase.cs | 13 +- src/Discord.Net.Commands/RunMode.cs | 2 +- src/Discord.Net.Core/CDN.cs | 86 +- src/Discord.Net.Core/Discord.Net.Core.csproj | 4 +- src/Discord.Net.Core/DiscordConfig.cs | 19 + src/Discord.Net.Core/DiscordErrorCode.cs | 197 + src/Discord.Net.Core/DiscordJsonError.cs | 53 + .../Entities/Activities/ActivityProperties.cs | 14 +- .../Entities/Activities/ActivityType.cs | 4 + src/Discord.Net.Core/Entities/ApplicationFlags.cs | 23 + .../Entities/ApplicationInstallParams.cs | 31 + .../Entities/AuditLogs/ActionType.cs | 50 + .../Entities/Channels/ChannelType.cs | 14 +- .../Entities/Channels/IMessageChannel.cs | 73 +- .../Entities/Channels/INestedChannel.cs | 46 +- .../Entities/Channels/IStageChannel.cs | 114 + .../Entities/Channels/ITextChannel.cs | 35 + .../Entities/Channels/IThreadChannel.cs | 89 + .../Entities/Channels/StageInstanceProperties.cs | 18 + .../Entities/Channels/StagePrivacyLevel.cs | 17 + .../Entities/Channels/TextChannelProperties.cs | 16 + .../Entities/Channels/ThreadArchiveDuration.cs | 34 + .../Entities/Channels/ThreadType.cs | 23 + .../Entities/Channels/VoiceChannelProperties.cs | 4 + src/Discord.Net.Core/Entities/Emotes/Emoji.cs | 5945 +++++++++++++++++++- src/Discord.Net.Core/Entities/Emotes/Emote.cs | 2 + .../Entities/Guilds/GuildFeature.cs | 105 + .../Entities/Guilds/GuildFeatures.cs | 46 + .../Entities/Guilds/GuildProperties.cs | 9 +- .../Guilds/GuildScheduledEventPrivacyLevel.cs | 25 + .../Entities/Guilds/GuildScheduledEventStatus.cs | 34 + .../Entities/Guilds/GuildScheduledEventType.cs | 34 + .../Guilds/GuildScheduledEventsProperties.cs | 58 + src/Discord.Net.Core/Entities/Guilds/IGuild.cs | 271 +- .../Entities/Guilds/IGuildScheduledEvent.cs | 170 + src/Discord.Net.Core/Entities/Guilds/NsfwLevel.cs | 22 + .../Entities/Guilds/SystemChannelMessageDeny.cs | 10 +- src/Discord.Net.Core/Entities/IApplication.cs | 14 +- .../Interactions/ApplicationCommandOption.cs | 95 + .../Interactions/ApplicationCommandOptionChoice.cs | 44 + .../Interactions/ApplicationCommandOptionType.cs | 58 + .../Interactions/ApplicationCommandProperties.cs | 22 + .../Interactions/ApplicationCommandTypes.cs | 23 + .../Entities/Interactions/AutocompleteOption.cs | 36 + .../Entities/Interactions/AutocompleteResult.cs | 73 + .../ContextMenus/IMessageCommandInteraction.cs | 13 + .../ContextMenus/IMessageCommandInteractionData.cs | 13 + .../ContextMenus/IUserCommandInteraction.cs | 13 + .../ContextMenus/IUserCommandInteractionData.cs | 13 + .../ContextMenus/MessageCommandBuilder.cs | 77 + .../ContextMenus/MessageCommandProperties.cs | 10 + .../ContextMenus/UserCommandBuilder.cs | 75 + .../ContextMenus/UserCommandProperties.cs | 10 + .../Entities/Interactions/IApplicationCommand.cs | 64 + .../IApplicationCommandInteractionData.cs | 25 + .../IApplicationCommandInteractionDataOption.cs | 33 + .../Interactions/IApplicationCommandOption.cs | 60 + .../IApplicationCommandOptionChoice.cs | 18 + .../Entities/Interactions/IDiscordInteraction.cs | 90 + .../Interactions/IDiscordInteractionData.cs | 7 + .../Interactions/InteractionResponseType.cs | 46 + .../Entities/Interactions/InteractionType.cs | 28 + .../MessageComponents/ActionRowComponent.cs | 27 + .../MessageComponents/ButtonComponent.cs | 61 + .../Interactions/MessageComponents/ButtonStyle.cs | 33 + .../MessageComponents/ComponentBuilder.cs | 1064 ++++ .../MessageComponents/ComponentType.cs | 23 + .../MessageComponents/IComponentInteraction.cs | 18 + .../MessageComponents/IComponentInteractionData.cs | 25 + .../MessageComponents/IMessageComponent.cs | 18 + .../MessageComponents/MessageComponent.cs | 26 + .../MessageComponents/SelectMenuComponent.cs | 67 + .../MessageComponents/SelectMenuOption.cs | 42 + .../SlashCommands/IAutocompleteInteraction.cs | 13 + .../SlashCommands/IAutocompleteInteractionData.cs | 40 + .../SlashCommands/ISlashCommandInteraction.cs | 13 + .../SlashCommands/SlashCommandBuilder.cs | 640 +++ .../SlashCommands/SlashCommandProperties.cs | 24 + .../Entities/Invites/TargetUserType.cs | 6 +- .../Entities/Messages/EmbedBuilder.cs | 49 +- .../Entities/Messages/FileAttachment.cs | 83 + .../Entities/Messages/IAttachment.cs | 7 + src/Discord.Net.Core/Entities/Messages/IMessage.cs | 26 +- .../Entities/Messages/IMessageInteraction.cs | 34 + .../Entities/Messages/MessageFlags.cs | 12 + .../Entities/Messages/MessageInteraction.cs | 45 + .../Entities/Messages/MessageProperties.cs | 26 +- .../Entities/Messages/MessageType.cs | 45 + .../Entities/Messages/StickerFormatType.cs | 25 + .../Entities/Messages/SticketFormatType.cs | 15 - .../Entities/Messages/TimestampTag.cs | 47 + .../Entities/Messages/TimestampTagStyle.cs | 43 + .../ApplicationCommandPermissionTarget.cs | 17 + .../Permissions/ApplicationCommandPermissions.cs | 62 + .../Entities/Permissions/ChannelPermission.cs | 91 +- .../Entities/Permissions/ChannelPermissions.cs | 159 +- .../GuildApplicationCommandPermissions.cs | 39 + .../Entities/Permissions/GuildPermission.cs | 114 +- .../Entities/Permissions/GuildPermissions.cs | 98 +- .../Entities/Permissions/OverwritePermissions.cs | 72 +- src/Discord.Net.Core/Entities/Roles/Color.cs | 101 +- src/Discord.Net.Core/Entities/Roles/IRole.cs | 22 + .../Entities/Roles/RoleProperties.cs | 5 + .../Entities/Stickers/ICustomSticker.cs | 59 + .../Entities/{Messages => Stickers}/ISticker.cs | 36 +- .../Entities/Stickers/IStickerItem.cs | 23 + .../Entities/Stickers/StickerPack.cs | 59 + .../Entities/Stickers/StickerProperties.cs | 25 + .../Entities/Stickers/StickerType.cs | 18 + src/Discord.Net.Core/Entities/Users/IGuildUser.cs | 28 +- src/Discord.Net.Core/Entities/Users/IUser.cs | 13 +- src/Discord.Net.Core/Entities/Users/IVoiceState.cs | 6 + .../Entities/Users/UserProperties.cs | 6 +- src/Discord.Net.Core/Entities/Webhooks/IWebhook.cs | 7 +- src/Discord.Net.Core/Extensions/GuildExtensions.cs | 16 + .../Extensions/MessageExtensions.cs | 15 +- .../Extensions/ObjectExtensions.cs | 32 + src/Discord.Net.Core/Extensions/UserExtensions.cs | 25 +- src/Discord.Net.Core/Format.cs | 15 +- src/Discord.Net.Core/GatewayIntents.cs | 8 +- src/Discord.Net.Core/IDiscordClient.cs | 41 + .../Net/ApplicationCommandException.cs | 15 + src/Discord.Net.Core/Net/HttpException.cs | 13 +- src/Discord.Net.Core/Net/Rest/IRateLimitInfo.cs | 59 + src/Discord.Net.Core/RequestOptions.cs | 22 +- src/Discord.Net.Core/Utils/Cacheable.cs | 56 + src/Discord.Net.Core/Utils/MentionUtils.cs | 3 + src/Discord.Net.Core/Utils/Optional.cs | 10 +- src/Discord.Net.Core/Utils/Preconditions.cs | 12 +- src/Discord.Net.Core/Utils/UrlValidation.cs | 42 + .../Entities/Channels/IMessageChannel.Examples.cs | 1 - .../Discord.Net.Examples.csproj | 2 +- .../API/Common/ActionRowComponent.cs | 32 + src/Discord.Net.Rest/API/Common/Application.cs | 9 +- .../API/Common/ApplicationCommand.cs | 28 + .../Common/ApplicationCommandInteractionData.cs | 22 + .../ApplicationCommandInteractionDataOption.cs | 19 + .../ApplicationCommandInteractionDataResolved.cs | 22 + .../API/Common/ApplicationCommandOption.cs | 88 + .../API/Common/ApplicationCommandOptionChoice.cs | 13 + .../API/Common/ApplicationCommandPermissions.cs | 16 + src/Discord.Net.Rest/API/Common/Attachment.cs | 7 +- src/Discord.Net.Rest/API/Common/AuditLog.cs | 8 +- src/Discord.Net.Rest/API/Common/AuditLogEntry.cs | 2 +- .../API/Common/AutocompleteInteractionData.cs | 22 + .../Common/AutocompleteInteractionDataOption.cs | 22 + src/Discord.Net.Rest/API/Common/Ban.cs | 1 - src/Discord.Net.Rest/API/Common/ButtonComponent.cs | 63 + src/Discord.Net.Rest/API/Common/Channel.cs | 17 +- src/Discord.Net.Rest/API/Common/ChannelThreads.cs | 16 + src/Discord.Net.Rest/API/Common/Connection.cs | 1 - src/Discord.Net.Rest/API/Common/DiscordError.cs | 20 + src/Discord.Net.Rest/API/Common/Embed.cs | 1 - src/Discord.Net.Rest/API/Common/EmbedImage.cs | 1 - src/Discord.Net.Rest/API/Common/EmbedProvider.cs | 1 - src/Discord.Net.Rest/API/Common/EmbedThumbnail.cs | 1 - src/Discord.Net.Rest/API/Common/EmbedVideo.cs | 1 - src/Discord.Net.Rest/API/Common/Emoji.cs | 1 - src/Discord.Net.Rest/API/Common/Error.cs | 12 + src/Discord.Net.Rest/API/Common/Game.cs | 3 +- src/Discord.Net.Rest/API/Common/Guild.cs | 11 +- .../Common/GuildApplicationCommandPermissions.cs | 19 + src/Discord.Net.Rest/API/Common/GuildMember.cs | 3 +- .../API/Common/GuildScheduledEvent.cs | 43 + .../Common/GuildScheduledEventEntityMetadata.cs | 15 + .../API/Common/GuildScheduledEventUser.cs | 19 + src/Discord.Net.Rest/API/Common/GuildWidget.cs | 1 - src/Discord.Net.Rest/API/Common/InstallParams.cs | 17 + src/Discord.Net.Rest/API/Common/Integration.cs | 1 - .../API/Common/IntegrationAccount.cs | 1 - src/Discord.Net.Rest/API/Common/Interaction.cs | 41 + .../API/Common/InteractionCallbackData.cs | 28 + .../API/Common/InteractionResponse.cs | 13 + src/Discord.Net.Rest/API/Common/Invite.cs | 1 - src/Discord.Net.Rest/API/Common/InviteChannel.cs | 1 - src/Discord.Net.Rest/API/Common/InviteGuild.cs | 1 - src/Discord.Net.Rest/API/Common/InviteMetadata.cs | 1 - src/Discord.Net.Rest/API/Common/InviteVanity.cs | 10 + src/Discord.Net.Rest/API/Common/Message.cs | 10 +- .../API/Common/MessageComponentInteractionData.cs | 16 + .../API/Common/MessageInteraction.cs | 21 + .../API/Common/NitroStickerPacks.cs | 11 + src/Discord.Net.Rest/API/Common/Overwrite.cs | 1 - src/Discord.Net.Rest/API/Common/Presence.cs | 1 - .../API/Common/PropertyErrorDescription.cs | 17 + src/Discord.Net.Rest/API/Common/ReadState.cs | 1 - src/Discord.Net.Rest/API/Common/Relationship.cs | 1 - .../API/Common/RelationshipType.cs | 1 - src/Discord.Net.Rest/API/Common/Role.cs | 5 +- src/Discord.Net.Rest/API/Common/RoleTags.cs | 1 - .../API/Common/SelectMenuComponent.cs | 42 + .../API/Common/SelectMenuOption.cs | 53 + src/Discord.Net.Rest/API/Common/StageInstance.cs | 25 + src/Discord.Net.Rest/API/Common/Sticker.cs | 17 +- src/Discord.Net.Rest/API/Common/StickerItem.cs | 16 + src/Discord.Net.Rest/API/Common/StickerPack.cs | 22 + src/Discord.Net.Rest/API/Common/Team.cs | 1 - src/Discord.Net.Rest/API/Common/TeamMember.cs | 1 - src/Discord.Net.Rest/API/Common/ThreadMember.cs | 26 + src/Discord.Net.Rest/API/Common/ThreadMetadata.cs | 20 + src/Discord.Net.Rest/API/Common/User.cs | 5 +- src/Discord.Net.Rest/API/Common/UserGuild.cs | 1 - src/Discord.Net.Rest/API/Common/VoiceRegion.cs | 1 - src/Discord.Net.Rest/API/Common/VoiceState.cs | 4 +- src/Discord.Net.Rest/API/Common/Webhook.cs | 3 +- src/Discord.Net.Rest/API/Int53Attribute.cs | 1 - src/Discord.Net.Rest/API/Net/IResolvable.cs | 13 + src/Discord.Net.Rest/API/Net/MultipartFile.cs | 6 +- .../API/Rest/CreateApplicationCommandParams.cs | 31 + .../API/Rest/CreateChannelInviteParams.cs | 7 +- .../API/Rest/CreateDMChannelParams.cs | 1 - .../API/Rest/CreateGuildBanParams.cs | 1 - .../API/Rest/CreateGuildChannelParams.cs | 1 - .../API/Rest/CreateGuildEmoteParams.cs | 1 - .../API/Rest/CreateGuildIntegrationParams.cs | 1 - src/Discord.Net.Rest/API/Rest/CreateGuildParams.cs | 1 - .../API/Rest/CreateGuildScheduledEventParams.cs | 29 + .../API/Rest/CreateMessageParams.cs | 15 +- .../API/Rest/CreateStageInstanceParams.cs | 16 + .../API/Rest/CreateStickerParams.cs | 35 + .../API/Rest/CreateWebhookMessageParams.cs | 62 +- .../API/Rest/CreateWebhookParams.cs | 1 - .../API/Rest/DeleteMessagesParams.cs | 1 - .../API/Rest/GetBotGatewayResponse.cs | 1 - .../API/Rest/GetChannelMessagesParams.cs | 1 - .../API/Rest/GetEventUsersParams.cs | 15 + .../API/Rest/GetGatewayResponse.cs | 1 - .../API/Rest/GetGuildMembersParams.cs | 1 - .../API/Rest/GetGuildPruneCountResponse.cs | 1 - .../API/Rest/GetGuildSummariesParams.cs | 1 - src/Discord.Net.Rest/API/Rest/GuildPruneParams.cs | 1 - .../API/Rest/ModifyApplicationCommandParams.cs | 19 + .../API/Rest/ModifyChannelPermissionsParams.cs | 1 - .../API/Rest/ModifyCurrentUserNickParams.cs | 1 - .../API/Rest/ModifyCurrentUserParams.cs | 1 - .../ModifyGuildApplicationCommandPermissions.cs | 13 + ...difyGuildApplicationCommandPermissionsParams.cs | 10 + .../API/Rest/ModifyGuildChannelParams.cs | 1 - .../API/Rest/ModifyGuildChannelsParams.cs | 1 - .../API/Rest/ModifyGuildEmbedParams.cs | 1 - .../API/Rest/ModifyGuildEmoteParams.cs | 1 - .../API/Rest/ModifyGuildIntegrationParams.cs | 1 - .../API/Rest/ModifyGuildMemberParams.cs | 1 - src/Discord.Net.Rest/API/Rest/ModifyGuildParams.cs | 3 +- .../API/Rest/ModifyGuildRoleParams.cs | 3 +- .../API/Rest/ModifyGuildRolesParams.cs | 1 - .../API/Rest/ModifyGuildScheduledEventParams.cs | 31 + .../API/Rest/ModifyGuildWidgetParams.cs | 1 - .../API/Rest/ModifyInteractionResponseParams.cs | 22 + .../API/Rest/ModifyMessageParams.cs | 7 +- .../API/Rest/ModifyStageInstanceParams.cs | 13 + .../API/Rest/ModifyStickerParams.cs | 14 + .../API/Rest/ModifyTextChannelParams.cs | 1 - .../API/Rest/ModifyThreadParams.cs | 22 + .../API/Rest/ModifyVoiceChannelParams.cs | 3 +- .../API/Rest/ModifyVoiceStateParams.cs | 17 + .../API/Rest/ModifyWebhookMessageParams.cs | 3 +- .../API/Rest/ModifyWebhookParams.cs | 1 - .../API/Rest/SearchGuildMembersParams.cs | 1 - src/Discord.Net.Rest/API/Rest/StartThreadParams.cs | 22 + src/Discord.Net.Rest/API/Rest/UploadFileParams.cs | 54 +- .../API/Rest/UploadWebhookFileParams.cs | 1 - src/Discord.Net.Rest/BaseDiscordClient.cs | 21 +- src/Discord.Net.Rest/ClientHelper.cs | 69 +- src/Discord.Net.Rest/DiscordRestApiClient.cs | 1111 +++- src/Discord.Net.Rest/DiscordRestClient.cs | 113 +- .../Entities/AuditLogs/AuditLogHelper.cs | 3 +- .../Entities/AuditLogs/DataTypes/StageInfo.cs | 30 + .../DataTypes/StageInstanceCreateAuditLogData.cs | 50 + .../DataTypes/StageInstanceDeleteAuditLogData.cs | 50 + .../DataTypes/StageInstanceUpdatedAuditLogData.cs | 51 + .../Entities/Channels/ChannelHelper.cs | 151 +- .../Entities/Channels/IRestMessageChannel.cs | 21 +- .../Entities/Channels/RestCategoryChannel.cs | 5 +- .../Entities/Channels/RestChannel.cs | 61 +- .../Entities/Channels/RestDMChannel.cs | 57 +- .../Entities/Channels/RestGroupChannel.cs | 55 +- .../Entities/Channels/RestGuildChannel.cs | 48 +- .../Entities/Channels/RestStageChannel.cs | 150 + .../Entities/Channels/RestTextChannel.cs | 127 +- .../Entities/Channels/RestThreadChannel.cs | 223 + .../Entities/Channels/RestVoiceChannel.cs | 28 +- .../Entities/Channels/ThreadHelper.cs | 74 + .../Entities/Guilds/GuildHelper.cs | 382 +- src/Discord.Net.Rest/Entities/Guilds/RestBan.cs | 5 +- src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs | 519 +- .../Entities/Guilds/RestGuildEvent.cs | 188 + .../Interactions/CommandBase/RestCommandBase.cs | 345 ++ .../CommandBase/RestCommandBaseData.cs | 59 + .../Interactions/CommandBase/RestResolvableData.cs | 96 + .../MessageCommands/RestMessageCommand.cs | 45 + .../MessageCommands/RestMessageCommandData.cs | 42 + .../UserCommands/RestUserCommand.cs | 48 + .../UserCommands/RestUserCommandData.cs | 40 + .../Entities/Interactions/InteractionHelper.cs | 542 ++ .../MessageComponents/RestMessageComponent.cs | 452 ++ .../MessageComponents/RestMessageComponentData.cs | 37 + .../Interactions/RestApplicationCommand.cs | 77 + .../Interactions/RestApplicationCommandChoice.cs | 22 + .../Interactions/RestApplicationCommandOption.cs | 98 + .../Entities/Interactions/RestGlobalCommand.cs | 40 + .../Entities/Interactions/RestGuildCommand.cs | 83 + .../Entities/Interactions/RestInteraction.cs | 224 + .../Entities/Interactions/RestPingInteraction.cs | 46 + .../SlashCommands/RestAutocompleteInteraction.cs | 135 + .../RestAutocompleteInteractionData.cs | 76 + .../Interactions/SlashCommands/RestSlashCommand.cs | 48 + .../SlashCommands/RestSlashCommandData.cs | 32 + .../SlashCommands/RestSlashCommandDataOption.cs | 139 + .../Entities/Messages/Attachment.cs | 8 +- .../Entities/Messages/CustomSticker.cs | 74 + .../Entities/Messages/MessageHelper.cs | 102 +- .../Entities/Messages/RestFollowupMessage.cs | 78 + .../Entities/Messages/RestInteractionMessage.cs | 78 + .../Entities/Messages/RestMessage.cs | 114 +- .../Entities/Messages/RestUserMessage.cs | 35 +- src/Discord.Net.Rest/Entities/Messages/Sticker.cs | 55 +- .../Entities/Messages/StickerItem.cs | 39 + src/Discord.Net.Rest/Entities/RestApplication.cs | 17 +- src/Discord.Net.Rest/Entities/Roles/RestRole.cs | 25 +- src/Discord.Net.Rest/Entities/Roles/RoleHelper.cs | 12 +- .../Entities/Users/RestGroupUser.cs | 8 +- .../Entities/Users/RestGuildUser.cs | 29 +- .../Entities/Users/RestThreadUser.cs | 56 + src/Discord.Net.Rest/Entities/Users/RestUser.cs | 30 +- .../Entities/Users/RestWebhookUser.cs | 16 +- .../Entities/Webhooks/RestWebhook.cs | 9 +- .../Entities/Webhooks/WebhookHelper.cs | 1 - .../Extensions/EntityExtensions.cs | 3 +- src/Discord.Net.Rest/Net/BadSignatureException.cs | 16 + .../Net/Converters/ArrayConverter.cs | 2 +- .../Net/Converters/DiscordContractResolver.cs | 17 +- .../Net/Converters/DiscordErrorConverter.cs | 88 + .../Net/Converters/EmbedTypeConverter.cs | 33 +- .../Net/Converters/GuildFeaturesConverter.cs | 60 + .../Net/Converters/InteractionConverter.cs | 70 + .../Net/Converters/MessageComponentConverter.cs | 40 + .../Net/Converters/UnixTimestampConverter.cs | 2 +- .../Net/Converters/UserStatusConverter.cs | 24 +- src/Discord.Net.Rest/Net/DefaultRestClient.cs | 29 +- src/Discord.Net.Rest/Net/ED25519/Array16.cs | 27 + src/Discord.Net.Rest/Net/ED25519/Array8.cs | 18 + .../Net/ED25519/ByteIntegerConverter.cs | 55 + src/Discord.Net.Rest/Net/ED25519/CryptoBytes.cs | 272 + src/Discord.Net.Rest/Net/ED25519/Ed25519.cs | 67 + .../Net/ED25519/Ed25519Operations.cs | 45 + .../Net/ED25519/Ed25519Ref10/FieldElement.cs | 23 + .../Net/ED25519/Ed25519Ref10/GroupElement.cs | 63 + .../Net/ED25519/Ed25519Ref10/base.cs | 1355 +++++ .../Net/ED25519/Ed25519Ref10/base2.cs | 50 + src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/d.cs | 9 + .../Net/ED25519/Ed25519Ref10/d2.cs | 9 + .../Net/ED25519/Ed25519Ref10/fe_0.cs | 12 + .../Net/ED25519/Ed25519Ref10/fe_1.cs | 13 + .../Net/ED25519/Ed25519Ref10/fe_add.cs | 64 + .../Net/ED25519/Ed25519Ref10/fe_cmov.cs | 71 + .../Net/ED25519/Ed25519Ref10/fe_cswap.cs | 79 + .../Net/ED25519/Ed25519Ref10/fe_frombytes.cs | 102 + .../Net/ED25519/Ed25519Ref10/fe_invert.cs | 128 + .../Net/ED25519/Ed25519Ref10/fe_isnegative.cs | 22 + .../Net/ED25519/Ed25519Ref10/fe_isnonzero.cs | 37 + .../Net/ED25519/Ed25519Ref10/fe_mul.cs | 263 + .../Net/ED25519/Ed25519Ref10/fe_mul121666.cs | 67 + .../Net/ED25519/Ed25519Ref10/fe_neg.cs | 51 + .../Net/ED25519/Ed25519Ref10/fe_pow22523.cs | 125 + .../Net/ED25519/Ed25519Ref10/fe_sq.cs | 143 + .../Net/ED25519/Ed25519Ref10/fe_sq2.cs | 154 + .../Net/ED25519/Ed25519Ref10/fe_sub.cs | 66 + .../Net/ED25519/Ed25519Ref10/fe_tobytes.cs | 145 + .../Net/ED25519/Ed25519Ref10/ge_add.cs | 73 + .../ED25519/Ed25519Ref10/ge_double_scalarmult.cs | 115 + .../Net/ED25519/Ed25519Ref10/ge_frombytes.cs | 50 + .../Net/ED25519/Ed25519Ref10/ge_madd.cs | 69 + .../Net/ED25519/Ed25519Ref10/ge_msub.cs | 68 + .../Net/ED25519/Ed25519Ref10/ge_p1p1_to_p2.cs | 18 + .../Net/ED25519/Ed25519Ref10/ge_p1p1_to_p3.cs | 18 + .../Net/ED25519/Ed25519Ref10/ge_p2_0.cs | 14 + .../Net/ED25519/Ed25519Ref10/ge_p2_dbl.cs | 64 + .../Net/ED25519/Ed25519Ref10/ge_p3_0.cs | 15 + .../Net/ED25519/Ed25519Ref10/ge_p3_dbl.cs | 17 + .../Net/ED25519/Ed25519Ref10/ge_p3_to_cached.cs | 18 + .../Net/ED25519/Ed25519Ref10/ge_p3_to_p2.cs | 17 + .../Net/ED25519/Ed25519Ref10/ge_p3_tobytes.cs | 19 + .../Net/ED25519/Ed25519Ref10/ge_precomp_0.cs | 14 + .../Net/ED25519/Ed25519Ref10/ge_scalarmult_base.cs | 113 + .../Net/ED25519/Ed25519Ref10/ge_sub.cs | 74 + .../Net/ED25519/Ed25519Ref10/ge_tobytes.cs | 19 + .../Net/ED25519/Ed25519Ref10/keypair.cs | 23 + .../Net/ED25519/Ed25519Ref10/open.cs | 80 + .../Net/ED25519/Ed25519Ref10/sc_clamp.cs | 14 + .../Net/ED25519/Ed25519Ref10/sc_mul_add.cs | 374 ++ .../Net/ED25519/Ed25519Ref10/sc_reduce.cs | 264 + .../Net/ED25519/Ed25519Ref10/scalarmult.cs | 153 + .../Net/ED25519/Ed25519Ref10/sign.cs | 44 + .../Net/ED25519/Ed25519Ref10/sqrtm1.cs | 9 + src/Discord.Net.Rest/Net/ED25519/Sha512.cs | 155 + src/Discord.Net.Rest/Net/ED25519/Sha512Internal.cs | 447 ++ .../Net/Queue/RequestQueueBucket.cs | 27 +- src/Discord.Net.Rest/Net/RateLimitInfo.cs | 29 +- src/Discord.Net.Rest/Utils/HexConverter.cs | 36 + .../ApplicationCommandCreatedUpdatedEvent.cs | 10 + .../API/Gateway/ExtendedGuild.cs | 12 +- .../API/Gateway/GatewayOpCode.cs | 3 +- .../API/Gateway/GuildBanEvent.cs | 1 - .../API/Gateway/GuildEmojiUpdateEvent.cs | 1 - .../API/Gateway/GuildJoinRequestDeleteEvent.cs | 12 + .../API/Gateway/GuildMemberAddEvent.cs | 1 - .../API/Gateway/GuildMemberRemoveEvent.cs | 1 - .../API/Gateway/GuildMemberUpdateEvent.cs | 5 +- .../API/Gateway/GuildMembersChunkEvent.cs | 1 - .../API/Gateway/GuildRoleCreateEvent.cs | 1 - .../API/Gateway/GuildRoleDeleteEvent.cs | 1 - .../API/Gateway/GuildRoleUpdateEvent.cs | 1 - .../GuildScheduledEventUserAddRemoveEvent.cs | 19 + .../API/Gateway/GuildStickerUpdateEvent.cs | 13 + .../API/Gateway/GuildSyncEvent.cs | 1 - .../API/Gateway/HelloEvent.cs | 1 - .../API/Gateway/IdentifyParams.cs | 3 +- .../API/Gateway/InviteCreatedEvent.cs | 32 + .../API/Gateway/InviteDeletedEvent.cs | 19 + .../API/Gateway/MessageDeleteBulkEvent.cs | 1 - .../API/Gateway/ReadyEvent.cs | 1 - .../API/Gateway/RecipientEvent.cs | 1 - .../API/Gateway/RequestMembersParams.cs | 1 - .../API/Gateway/ResumeParams.cs | 1 - .../API/Gateway/ResumedEvent.cs | 1 - .../API/Gateway/StatusUpdateParams.cs | 10 +- .../API/Gateway/ThreadListSyncEvent.cs | 19 + .../API/Gateway/ThreadMembersUpdate.cs | 22 + .../API/Gateway/TypingStartEvent.cs | 1 - .../API/Gateway/VoiceServerUpdateEvent.cs | 1 - .../API/Gateway/VoiceStateUpdateParams.cs | 1 - .../API/Gateway/WebhookUpdateEvent.cs | 1 - src/Discord.Net.WebSocket/API/SocketFrame.cs | 1 - .../API/Voice/IdentifyParams.cs | 1 - src/Discord.Net.WebSocket/API/Voice/ReadyEvent.cs | 3 +- .../API/Voice/SelectProtocolParams.cs | 1 - .../API/Voice/SessionDescriptionEvent.cs | 1 - .../API/Voice/SpeakingEvent.cs | 1 - .../API/Voice/SpeakingParams.cs | 1 - .../API/Voice/UdpProtocolInfo.cs | 1 - .../API/Voice/VoiceCloseCode.cs | 63 + src/Discord.Net.WebSocket/API/Voice/VoiceOpCode.cs | 1 - .../Audio/Streams/BufferedWriteStream.cs | 2 +- .../BaseSocketClient.Events.cs | 475 +- src/Discord.Net.WebSocket/BaseSocketClient.cs | 23 +- src/Discord.Net.WebSocket/ClientState.cs | 31 + .../Commands/ShardedCommandContext.cs | 5 +- .../Commands/SocketCommandContext.cs | 5 +- src/Discord.Net.WebSocket/ConnectionManager.cs | 3 + .../Discord.Net.WebSocket.csproj | 4 +- .../DiscordShardedClient.Events.cs | 7 +- src/Discord.Net.WebSocket/DiscordShardedClient.cs | 125 +- .../DiscordSocketApiClient.cs | 46 +- .../DiscordSocketClient.Events.cs | 3 +- src/Discord.Net.WebSocket/DiscordSocketClient.cs | 889 ++- src/Discord.Net.WebSocket/DiscordSocketConfig.cs | 31 +- src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs | 18 +- .../Entities/Channels/ISocketMessageChannel.cs | 19 +- .../Entities/Channels/SocketCategoryChannel.cs | 11 +- .../Entities/Channels/SocketChannel.cs | 21 +- .../Entities/Channels/SocketChannelHelper.cs | 13 +- .../Entities/Channels/SocketDMChannel.cs | 65 +- .../Entities/Channels/SocketGroupChannel.cs | 77 +- .../Entities/Channels/SocketGuildChannel.cs | 40 +- .../Entities/Channels/SocketStageChannel.cs | 158 + .../Entities/Channels/SocketTextChannel.cs | 137 +- .../Entities/Channels/SocketThreadChannel.cs | 339 ++ .../Entities/Channels/SocketVoiceChannel.cs | 17 +- .../Entities/Guilds/SocketGuild.cs | 668 ++- .../Entities/Guilds/SocketGuildEvent.cs | 216 + .../MessageCommands/SocketMessageCommand.cs | 45 + .../MessageCommands/SocketMessageCommandData.cs | 39 + .../UserCommands/SocketUserCommand.cs | 45 + .../UserCommands/SocketUserCommandData.cs | 39 + .../MessageComponents/SocketMessageComponent.cs | 436 ++ .../SocketMessageComponentData.cs | 33 + .../SlashCommands/SocketAutocompleteInteraction.cs | 126 + .../SocketAutocompleteInteractionData.cs | 73 + .../SlashCommands/SocketSlashCommand.cs | 45 + .../SlashCommands/SocketSlashCommandData.cs | 30 + .../SlashCommands/SocketSlashCommandDataOption.cs | 135 + .../SocketBaseCommand/SocketApplicationCommand.cs | 116 + .../SocketApplicationCommandChoice.cs | 29 + .../SocketApplicationCommandOption.cs | 87 + .../SocketBaseCommand/SocketCommandBase.cs | 300 + .../SocketBaseCommand/SocketCommandBaseData.cs | 58 + .../SocketBaseCommand/SocketResolvableData.cs | 109 + .../Entities/Interaction/SocketInteraction.cs | 243 + .../Entities/Invites/SocketInvite.cs | 21 +- .../Entities/Messages/SocketMessage.cs | 130 +- .../Entities/Messages/SocketUserMessage.cs | 63 +- .../Entities/Roles/SocketRole.cs | 31 +- .../Entities/Stickers/SocketCustomSticker.cs | 81 + .../Entities/Stickers/SocketSticker.cs | 92 + .../Entities/Stickers/SocketUnknownSticker.cs | 64 + .../Entities/Users/SocketGlobalUser.cs | 2 +- .../Entities/Users/SocketGroupUser.cs | 11 +- .../Entities/Users/SocketGuildUser.cs | 19 +- .../Entities/Users/SocketThreadUser.cs | 209 + .../Entities/Users/SocketUnknownUser.cs | 3 +- .../Entities/Users/SocketVoiceState.cs | 10 +- .../Entities/Users/SocketWebhookUser.cs | 19 +- .../Extensions/EntityExtensions.cs | 21 +- src/Discord.Net.Webhook/DiscordWebhookClient.cs | 7 +- .../Entities/Messages/WebhookMessageProperties.cs | 4 + .../Entities/Webhooks/RestInternalWebhook.cs | 3 + src/Discord.Net.Webhook/WebhookClientHelper.cs | 16 +- src/Discord.Net/Discord.Net.nuspec | 2 +- .../Discord.Net.Analyzers.Tests.csproj | 6 +- .../Helpers/CodeFixVerifier.Helper.cs | 10 +- .../Helpers/DiagnosticResult.cs | 20 +- .../Helpers/DiagnosticVerifier.Helper.cs | 26 +- .../Verifiers/CodeFixVerifier.cs | 34 +- .../Verifiers/DiagnosticVerifier.cs | 42 +- .../Discord.Net.Tests.Integration/ChannelsTests.cs | 2 +- .../Discord.Net.Tests.Integration.csproj | 4 +- test/Discord.Net.Tests.Integration/GuildTests.cs | 2 +- .../ChannelPermissionsTests.cs | 4 + test/Discord.Net.Tests.Unit/ColorTests.cs | 3 +- .../Discord.Net.Tests.Unit.csproj | 4 +- test/Discord.Net.Tests.Unit/EmbedBuilderTests.cs | 146 +- test/Discord.Net.Tests.Unit/FormatTests.cs | 15 + .../GuildPermissionsTests.cs | 18 +- test/Discord.Net.Tests.Unit/MentionUtilsTests.cs | 12 +- .../MockedEntities/MockedDMChannel.cs | 21 +- .../MockedEntities/MockedGroupChannel.cs | 9 +- .../MockedEntities/MockedTextChannel.cs | 14 +- .../MockedEntities/MockedVoiceChannel.cs | 5 +- 591 files changed, 34497 insertions(+), 1560 deletions(-) create mode 100644 docs/guides/concepts/ratelimits.md create mode 100644 docs/guides/guild_events/creating-guild-events.md create mode 100644 docs/guides/guild_events/getting-event-users.md create mode 100644 docs/guides/guild_events/intro.md create mode 100644 docs/guides/guild_events/modifying-events.md create mode 100644 docs/guides/interactions/application-commands/01-getting-started.md create mode 100644 docs/guides/interactions/application-commands/context-menu-commands/creating-context-menu-commands.md create mode 100644 docs/guides/interactions/application-commands/context-menu-commands/receiving-context-menu-command-events.md create mode 100644 docs/guides/interactions/application-commands/slash-commands/02-creating-slash-commands.md create mode 100644 docs/guides/interactions/application-commands/slash-commands/03-responding-to-slash-commands.md create mode 100644 docs/guides/interactions/application-commands/slash-commands/04-parameters.md create mode 100644 docs/guides/interactions/application-commands/slash-commands/05-responding-ephemerally.md create mode 100644 docs/guides/interactions/application-commands/slash-commands/06-subcommands.md create mode 100644 docs/guides/interactions/application-commands/slash-commands/07-choice-slash-command.md create mode 100644 docs/guides/interactions/application-commands/slash-commands/08-bulk-overwrite-of-global-slash-commands.md create mode 100644 docs/guides/interactions/application-commands/slash-commands/images/ephemeral1.png create mode 100644 docs/guides/interactions/application-commands/slash-commands/images/feedback1.png create mode 100644 docs/guides/interactions/application-commands/slash-commands/images/feedback2.png create mode 100644 docs/guides/interactions/application-commands/slash-commands/images/listroles1.png create mode 100644 docs/guides/interactions/application-commands/slash-commands/images/listroles2.png create mode 100644 docs/guides/interactions/application-commands/slash-commands/images/oauth.png create mode 100644 docs/guides/interactions/application-commands/slash-commands/images/settings1.png create mode 100644 docs/guides/interactions/application-commands/slash-commands/images/settings2.png create mode 100644 docs/guides/interactions/application-commands/slash-commands/images/settings3.png create mode 100644 docs/guides/interactions/application-commands/slash-commands/images/slashcommand1.png create mode 100644 docs/guides/interactions/application-commands/slash-commands/images/slashcommand2.png create mode 100644 docs/guides/interactions/intro.md create mode 100644 docs/guides/interactions/message-components/01-getting-started.md create mode 100644 docs/guides/interactions/message-components/02-responding-to-buttons.md create mode 100644 docs/guides/interactions/message-components/03-buttons-in-depth.md create mode 100644 docs/guides/interactions/message-components/04-select-menus.md create mode 100644 docs/guides/interactions/message-components/05-advanced.md create mode 100644 docs/guides/interactions/message-components/images/image1.png create mode 100644 docs/guides/interactions/message-components/images/image2.png create mode 100644 docs/guides/interactions/message-components/images/image3.png create mode 100644 docs/guides/interactions/message-components/images/image4.png create mode 100644 docs/guides/interactions/message-components/images/image5.png create mode 100644 docs/guides/interactions/message-components/images/image6.png create mode 100644 src/Discord.Net.Core/DiscordErrorCode.cs create mode 100644 src/Discord.Net.Core/DiscordJsonError.cs create mode 100644 src/Discord.Net.Core/Entities/ApplicationFlags.cs create mode 100644 src/Discord.Net.Core/Entities/ApplicationInstallParams.cs create mode 100644 src/Discord.Net.Core/Entities/Channels/IStageChannel.cs create mode 100644 src/Discord.Net.Core/Entities/Channels/IThreadChannel.cs create mode 100644 src/Discord.Net.Core/Entities/Channels/StageInstanceProperties.cs create mode 100644 src/Discord.Net.Core/Entities/Channels/StagePrivacyLevel.cs create mode 100644 src/Discord.Net.Core/Entities/Channels/ThreadArchiveDuration.cs create mode 100644 src/Discord.Net.Core/Entities/Channels/ThreadType.cs create mode 100644 src/Discord.Net.Core/Entities/Guilds/GuildFeature.cs create mode 100644 src/Discord.Net.Core/Entities/Guilds/GuildFeatures.cs create mode 100644 src/Discord.Net.Core/Entities/Guilds/GuildScheduledEventPrivacyLevel.cs create mode 100644 src/Discord.Net.Core/Entities/Guilds/GuildScheduledEventStatus.cs create mode 100644 src/Discord.Net.Core/Entities/Guilds/GuildScheduledEventType.cs create mode 100644 src/Discord.Net.Core/Entities/Guilds/GuildScheduledEventsProperties.cs create mode 100644 src/Discord.Net.Core/Entities/Guilds/IGuildScheduledEvent.cs create mode 100644 src/Discord.Net.Core/Entities/Guilds/NsfwLevel.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOption.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionChoice.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionType.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/ApplicationCommandTypes.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/AutocompleteOption.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/AutocompleteResult.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/ContextMenus/IMessageCommandInteraction.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/ContextMenus/IMessageCommandInteractionData.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/ContextMenus/IUserCommandInteraction.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/ContextMenus/IUserCommandInteractionData.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/ContextMenus/MessageCommandBuilder.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/ContextMenus/MessageCommandProperties.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/ContextMenus/UserCommandBuilder.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/ContextMenus/UserCommandProperties.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionData.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionDataOption.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOption.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOptionChoice.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/IDiscordInteractionData.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/InteractionResponseType.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/InteractionType.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/MessageComponents/ActionRowComponent.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/MessageComponents/ButtonComponent.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/MessageComponents/ButtonStyle.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentBuilder.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentType.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/MessageComponents/IComponentInteraction.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/MessageComponents/IComponentInteractionData.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/MessageComponents/IMessageComponent.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/MessageComponents/MessageComponent.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/MessageComponents/SelectMenuComponent.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/MessageComponents/SelectMenuOption.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/SlashCommands/IAutocompleteInteraction.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/SlashCommands/IAutocompleteInteractionData.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/SlashCommands/ISlashCommandInteraction.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/SlashCommands/SlashCommandBuilder.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/SlashCommands/SlashCommandProperties.cs create mode 100644 src/Discord.Net.Core/Entities/Messages/FileAttachment.cs create mode 100644 src/Discord.Net.Core/Entities/Messages/IMessageInteraction.cs create mode 100644 src/Discord.Net.Core/Entities/Messages/MessageInteraction.cs create mode 100644 src/Discord.Net.Core/Entities/Messages/StickerFormatType.cs delete mode 100644 src/Discord.Net.Core/Entities/Messages/SticketFormatType.cs create mode 100644 src/Discord.Net.Core/Entities/Messages/TimestampTag.cs create mode 100644 src/Discord.Net.Core/Entities/Messages/TimestampTagStyle.cs create mode 100644 src/Discord.Net.Core/Entities/Permissions/ApplicationCommandPermissionTarget.cs create mode 100644 src/Discord.Net.Core/Entities/Permissions/ApplicationCommandPermissions.cs create mode 100644 src/Discord.Net.Core/Entities/Permissions/GuildApplicationCommandPermissions.cs create mode 100644 src/Discord.Net.Core/Entities/Stickers/ICustomSticker.cs rename src/Discord.Net.Core/Entities/{Messages => Stickers}/ISticker.cs (71%) create mode 100644 src/Discord.Net.Core/Entities/Stickers/IStickerItem.cs create mode 100644 src/Discord.Net.Core/Entities/Stickers/StickerPack.cs create mode 100644 src/Discord.Net.Core/Entities/Stickers/StickerProperties.cs create mode 100644 src/Discord.Net.Core/Entities/Stickers/StickerType.cs create mode 100644 src/Discord.Net.Core/Extensions/ObjectExtensions.cs create mode 100644 src/Discord.Net.Core/Net/ApplicationCommandException.cs create mode 100644 src/Discord.Net.Core/Net/Rest/IRateLimitInfo.cs create mode 100644 src/Discord.Net.Core/Utils/UrlValidation.cs create mode 100644 src/Discord.Net.Rest/API/Common/ActionRowComponent.cs create mode 100644 src/Discord.Net.Rest/API/Common/ApplicationCommand.cs create mode 100644 src/Discord.Net.Rest/API/Common/ApplicationCommandInteractionData.cs create mode 100644 src/Discord.Net.Rest/API/Common/ApplicationCommandInteractionDataOption.cs create mode 100644 src/Discord.Net.Rest/API/Common/ApplicationCommandInteractionDataResolved.cs create mode 100644 src/Discord.Net.Rest/API/Common/ApplicationCommandOption.cs create mode 100644 src/Discord.Net.Rest/API/Common/ApplicationCommandOptionChoice.cs create mode 100644 src/Discord.Net.Rest/API/Common/ApplicationCommandPermissions.cs create mode 100644 src/Discord.Net.Rest/API/Common/AutocompleteInteractionData.cs create mode 100644 src/Discord.Net.Rest/API/Common/AutocompleteInteractionDataOption.cs create mode 100644 src/Discord.Net.Rest/API/Common/ButtonComponent.cs create mode 100644 src/Discord.Net.Rest/API/Common/ChannelThreads.cs create mode 100644 src/Discord.Net.Rest/API/Common/DiscordError.cs create mode 100644 src/Discord.Net.Rest/API/Common/Error.cs create mode 100644 src/Discord.Net.Rest/API/Common/GuildApplicationCommandPermissions.cs create mode 100644 src/Discord.Net.Rest/API/Common/GuildScheduledEvent.cs create mode 100644 src/Discord.Net.Rest/API/Common/GuildScheduledEventEntityMetadata.cs create mode 100644 src/Discord.Net.Rest/API/Common/GuildScheduledEventUser.cs create mode 100644 src/Discord.Net.Rest/API/Common/InstallParams.cs create mode 100644 src/Discord.Net.Rest/API/Common/Interaction.cs create mode 100644 src/Discord.Net.Rest/API/Common/InteractionCallbackData.cs create mode 100644 src/Discord.Net.Rest/API/Common/InteractionResponse.cs create mode 100644 src/Discord.Net.Rest/API/Common/MessageComponentInteractionData.cs create mode 100644 src/Discord.Net.Rest/API/Common/MessageInteraction.cs create mode 100644 src/Discord.Net.Rest/API/Common/NitroStickerPacks.cs create mode 100644 src/Discord.Net.Rest/API/Common/PropertyErrorDescription.cs create mode 100644 src/Discord.Net.Rest/API/Common/SelectMenuComponent.cs create mode 100644 src/Discord.Net.Rest/API/Common/SelectMenuOption.cs create mode 100644 src/Discord.Net.Rest/API/Common/StageInstance.cs create mode 100644 src/Discord.Net.Rest/API/Common/StickerItem.cs create mode 100644 src/Discord.Net.Rest/API/Common/StickerPack.cs create mode 100644 src/Discord.Net.Rest/API/Common/ThreadMember.cs create mode 100644 src/Discord.Net.Rest/API/Common/ThreadMetadata.cs create mode 100644 src/Discord.Net.Rest/API/Net/IResolvable.cs create mode 100644 src/Discord.Net.Rest/API/Rest/CreateApplicationCommandParams.cs create mode 100644 src/Discord.Net.Rest/API/Rest/CreateGuildScheduledEventParams.cs create mode 100644 src/Discord.Net.Rest/API/Rest/CreateStageInstanceParams.cs create mode 100644 src/Discord.Net.Rest/API/Rest/CreateStickerParams.cs create mode 100644 src/Discord.Net.Rest/API/Rest/GetEventUsersParams.cs create mode 100644 src/Discord.Net.Rest/API/Rest/ModifyApplicationCommandParams.cs create mode 100644 src/Discord.Net.Rest/API/Rest/ModifyGuildApplicationCommandPermissions.cs create mode 100644 src/Discord.Net.Rest/API/Rest/ModifyGuildApplicationCommandPermissionsParams.cs create mode 100644 src/Discord.Net.Rest/API/Rest/ModifyGuildScheduledEventParams.cs create mode 100644 src/Discord.Net.Rest/API/Rest/ModifyInteractionResponseParams.cs create mode 100644 src/Discord.Net.Rest/API/Rest/ModifyStageInstanceParams.cs create mode 100644 src/Discord.Net.Rest/API/Rest/ModifyStickerParams.cs create mode 100644 src/Discord.Net.Rest/API/Rest/ModifyThreadParams.cs create mode 100644 src/Discord.Net.Rest/API/Rest/ModifyVoiceStateParams.cs create mode 100644 src/Discord.Net.Rest/API/Rest/StartThreadParams.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/StageInfo.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/StageInstanceCreateAuditLogData.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/StageInstanceDeleteAuditLogData.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/StageInstanceUpdatedAuditLogData.cs create mode 100644 src/Discord.Net.Rest/Entities/Channels/RestStageChannel.cs create mode 100644 src/Discord.Net.Rest/Entities/Channels/RestThreadChannel.cs create mode 100644 src/Discord.Net.Rest/Entities/Channels/ThreadHelper.cs create mode 100644 src/Discord.Net.Rest/Entities/Guilds/RestGuildEvent.cs create mode 100644 src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestCommandBase.cs create mode 100644 src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestCommandBaseData.cs create mode 100644 src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestResolvableData.cs create mode 100644 src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/MessageCommands/RestMessageCommand.cs create mode 100644 src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/MessageCommands/RestMessageCommandData.cs create mode 100644 src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/UserCommands/RestUserCommand.cs create mode 100644 src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/UserCommands/RestUserCommandData.cs create mode 100644 src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs create mode 100644 src/Discord.Net.Rest/Entities/Interactions/MessageComponents/RestMessageComponent.cs create mode 100644 src/Discord.Net.Rest/Entities/Interactions/MessageComponents/RestMessageComponentData.cs create mode 100644 src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommand.cs create mode 100644 src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandChoice.cs create mode 100644 src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandOption.cs create mode 100644 src/Discord.Net.Rest/Entities/Interactions/RestGlobalCommand.cs create mode 100644 src/Discord.Net.Rest/Entities/Interactions/RestGuildCommand.cs create mode 100644 src/Discord.Net.Rest/Entities/Interactions/RestInteraction.cs create mode 100644 src/Discord.Net.Rest/Entities/Interactions/RestPingInteraction.cs create mode 100644 src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestAutocompleteInteraction.cs create mode 100644 src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestAutocompleteInteractionData.cs create mode 100644 src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestSlashCommand.cs create mode 100644 src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestSlashCommandData.cs create mode 100644 src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestSlashCommandDataOption.cs create mode 100644 src/Discord.Net.Rest/Entities/Messages/CustomSticker.cs create mode 100644 src/Discord.Net.Rest/Entities/Messages/RestFollowupMessage.cs create mode 100644 src/Discord.Net.Rest/Entities/Messages/RestInteractionMessage.cs create mode 100644 src/Discord.Net.Rest/Entities/Messages/StickerItem.cs create mode 100644 src/Discord.Net.Rest/Entities/Users/RestThreadUser.cs create mode 100644 src/Discord.Net.Rest/Net/BadSignatureException.cs create mode 100644 src/Discord.Net.Rest/Net/Converters/DiscordErrorConverter.cs create mode 100644 src/Discord.Net.Rest/Net/Converters/GuildFeaturesConverter.cs create mode 100644 src/Discord.Net.Rest/Net/Converters/InteractionConverter.cs create mode 100644 src/Discord.Net.Rest/Net/Converters/MessageComponentConverter.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Array16.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Array8.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/ByteIntegerConverter.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/CryptoBytes.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Operations.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/FieldElement.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/GroupElement.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/base.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/base2.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/d.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/d2.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_0.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_1.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_add.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_cmov.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_cswap.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_frombytes.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_invert.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_isnegative.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_isnonzero.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_mul.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_mul121666.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_neg.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_pow22523.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_sq.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_sq2.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_sub.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_tobytes.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_add.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_double_scalarmult.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_frombytes.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_madd.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_msub.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_p1p1_to_p2.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_p1p1_to_p3.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_p2_0.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_p2_dbl.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_p3_0.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_p3_dbl.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_p3_to_cached.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_p3_to_p2.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_p3_tobytes.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_precomp_0.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_scalarmult_base.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_sub.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_tobytes.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/keypair.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/open.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/sc_clamp.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/sc_mul_add.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/sc_reduce.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/scalarmult.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/sign.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/sqrtm1.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Sha512.cs create mode 100644 src/Discord.Net.Rest/Net/ED25519/Sha512Internal.cs create mode 100644 src/Discord.Net.Rest/Utils/HexConverter.cs create mode 100644 src/Discord.Net.WebSocket/API/Gateway/ApplicationCommandCreatedUpdatedEvent.cs create mode 100644 src/Discord.Net.WebSocket/API/Gateway/GuildJoinRequestDeleteEvent.cs create mode 100644 src/Discord.Net.WebSocket/API/Gateway/GuildScheduledEventUserAddRemoveEvent.cs create mode 100644 src/Discord.Net.WebSocket/API/Gateway/GuildStickerUpdateEvent.cs create mode 100644 src/Discord.Net.WebSocket/API/Gateway/InviteCreatedEvent.cs create mode 100644 src/Discord.Net.WebSocket/API/Gateway/InviteDeletedEvent.cs create mode 100644 src/Discord.Net.WebSocket/API/Gateway/ThreadListSyncEvent.cs create mode 100644 src/Discord.Net.WebSocket/API/Gateway/ThreadMembersUpdate.cs create mode 100644 src/Discord.Net.WebSocket/API/Voice/VoiceCloseCode.cs create mode 100644 src/Discord.Net.WebSocket/Entities/Channels/SocketStageChannel.cs create mode 100644 src/Discord.Net.WebSocket/Entities/Channels/SocketThreadChannel.cs create mode 100644 src/Discord.Net.WebSocket/Entities/Guilds/SocketGuildEvent.cs create mode 100644 src/Discord.Net.WebSocket/Entities/Interaction/ContextMenuCommands/MessageCommands/SocketMessageCommand.cs create mode 100644 src/Discord.Net.WebSocket/Entities/Interaction/ContextMenuCommands/MessageCommands/SocketMessageCommandData.cs create mode 100644 src/Discord.Net.WebSocket/Entities/Interaction/ContextMenuCommands/UserCommands/SocketUserCommand.cs create mode 100644 src/Discord.Net.WebSocket/Entities/Interaction/ContextMenuCommands/UserCommands/SocketUserCommandData.cs create mode 100644 src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponent.cs create mode 100644 src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponentData.cs create mode 100644 src/Discord.Net.WebSocket/Entities/Interaction/SlashCommands/SocketAutocompleteInteraction.cs create mode 100644 src/Discord.Net.WebSocket/Entities/Interaction/SlashCommands/SocketAutocompleteInteractionData.cs create mode 100644 src/Discord.Net.WebSocket/Entities/Interaction/SlashCommands/SocketSlashCommand.cs create mode 100644 src/Discord.Net.WebSocket/Entities/Interaction/SlashCommands/SocketSlashCommandData.cs create mode 100644 src/Discord.Net.WebSocket/Entities/Interaction/SlashCommands/SocketSlashCommandDataOption.cs create mode 100644 src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommand.cs create mode 100644 src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommandChoice.cs create mode 100644 src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommandOption.cs create mode 100644 src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs create mode 100644 src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBaseData.cs create mode 100644 src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketResolvableData.cs create mode 100644 src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs create mode 100644 src/Discord.Net.WebSocket/Entities/Stickers/SocketCustomSticker.cs create mode 100644 src/Discord.Net.WebSocket/Entities/Stickers/SocketSticker.cs create mode 100644 src/Discord.Net.WebSocket/Entities/Stickers/SocketUnknownSticker.cs create mode 100644 src/Discord.Net.WebSocket/Entities/Users/SocketThreadUser.cs diff --git a/Discord.Net.sln b/Discord.Net.sln index 1a32f1270..084d8a834 100644 --- a/Discord.Net.sln +++ b/Discord.Net.sln @@ -40,7 +40,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Analyzers.Tests EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Examples", "src\Discord.Net.Examples\Discord.Net.Examples.csproj", "{47820065-3CFB-401C-ACEA-862BD564A404}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "idn", "samples\idn\idn.csproj", "{4A03840B-9EBE-47E3-89AB-E0914DF21AFB}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "idn", "samples\idn\idn.csproj", "{4A03840B-9EBE-47E3-89AB-E0914DF21AFB}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/LICENSE b/LICENSE index 3765bf39c..fb9480169 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015-2019 Discord.Net Contributors +Copyright (c) 2015-2021 Discord.Net Contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 32e1515af..87b46fb64 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # Discord.Net + [![NuGet](https://img.shields.io/nuget/vpre/Discord.Net.svg?maxAge=2592000?style=plastic)](https://www.nuget.org/packages/Discord.Net) [![MyGet](https://img.shields.io/myget/discord-net/vpre/Discord.Net.svg)](https://www.myget.org/feed/Packages/discord-net) [![Build Status](https://dev.azure.com/discord-net/Discord.Net/_apis/build/status/discord-net.Discord.Net?branchName=dev)](https://dev.azure.com/discord-net/Discord.Net/_build/latest?definitionId=1&branchName=dev) @@ -12,34 +13,43 @@ An unofficial .NET API Wrapper for the Discord client (https://discord.com). - [Latest CI repo](https://github.com/discord-net/docs-static) ## Installation + ### Stable (NuGet) + Our stable builds available from NuGet through the Discord.Net metapackage: + - [Discord.Net](https://www.nuget.org/packages/Discord.Net/) The individual components may also be installed from NuGet: + - [Discord.Net.Commands](https://www.nuget.org/packages/Discord.Net.Commands/) - [Discord.Net.Rest](https://www.nuget.org/packages/Discord.Net.Rest/) - [Discord.Net.WebSocket](https://www.nuget.org/packages/Discord.Net.WebSocket/) - [Discord.Net.Webhook](https://www.nuget.org/packages/Discord.Net.Webhook/) ### Unstable (MyGet) + Nightly builds are available through our MyGet feed (`https://www.myget.org/F/discord-net/api/v3/index.json`). ## Compiling + In order to compile Discord.Net, you require the following: ### Using Visual Studio + - [Visual Studio 2017](https://www.microsoft.com/net/core#windowsvs2017) - [.NET Core SDK](https://www.microsoft.com/net/download/core) The .NET Core workload must be selected during Visual Studio installation. ### Using Command Line + - [.NET Core SDK](https://www.microsoft.com/net/download/core) ## Known Issues ### WebSockets (Win7 and earlier) + .NET Core 1.1 does not support WebSockets on Win7 and earlier. This issue has been fixed since the release of .NET Core 2.1. It is recommended to target .NET Core 2.1 or above for your project if you wish to run your bot on legacy platforms; alternatively, you may choose to install the [Discord.Net.Providers.WS4Net](https://www.nuget.org/packages/Discord.Net.Providers.WS4Net/) package. ## Versioning Guarantees diff --git a/docs/guides/concepts/ratelimits.md b/docs/guides/concepts/ratelimits.md new file mode 100644 index 000000000..afeb5f795 --- /dev/null +++ b/docs/guides/concepts/ratelimits.md @@ -0,0 +1,49 @@ +# Ratelimits + +Ratelimits are a core concept of any API - Discords API is no exception. each verified library must follow the ratelimit guidelines. + +### Using the ratelimit callback + +There is a new property within `RequestOptions` called RatelimitCallback. This callback is called when a request is made via the rest api. The callback is called with a `IRateLimitInfo` parameter: + +| Name | Type | Description | +| ---------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | +| IsGlobal | bool | Whether or not this ratelimit info is global. | +| Limit | int? | The number of requests that can be made. | +| Remaining | int? | The number of remaining requests that can be made. | +| RetryAfter | int? | The total time (in seconds) of when the current rate limit bucket will reset. Can have decimals to match previous millisecond ratelimit precision. | +| Reset | DateTimeOffset? | The time at which the rate limit resets. | +| ResetAfter | TimeSpan? | The absolute time when this ratelimit resets. | +| Bucket | string | A unique string denoting the rate limit being encountered (non-inclusive of major parameters in the route path). | +| Lag | TimeSpan? | The amount of lag for the request. This is used to denote the precise time of when the ratelimit expires. | +| Endpoint | string | The endpoint that this ratelimit info came from. | + +Let's set up a ratelimit callback that will print out the ratelimit info to the console. + +```cs +public async Task MyRatelimitCallback(IRateLimitInfo info) +{ + Console.WriteLine($"{info.IsGlobal} {info.Limit} {info.Remaining} {info.RetryAfter} {info.Reset} {info.ResetAfter} {info.Bucket} {info.Lag} {info.Endpoint}"); +} +``` + +Let's use this callback in a send message function + +```cs +[Command("ping")] +public async Task ping() +{ + var options = new RequestOptions() + { + RatelimitCallback = MyRatelimitCallback + }; + + await Context.Channel.SendMessageAsync("Pong!", options: options); +} +``` + +Running this produces the following output: + +``` +False 5 4 2021-09-09 3:48:14 AM +00:00 00:00:05 a06de0de4a08126315431cc0c55ee3dc 00:00:00.9891364 channels/848511736872828929/messages +``` diff --git a/docs/guides/emoji/emoji.md b/docs/guides/emoji/emoji.md index 60a84409c..dbf654bbf 100644 --- a/docs/guides/emoji/emoji.md +++ b/docs/guides/emoji/emoji.md @@ -46,14 +46,16 @@ form; this can be obtained in several different ways. ### Emoji Declaration After obtaining the Unicode representation of the emoji, you may -create the @Discord.Emoji object by passing the string into its +create the @Discord.Emoji object by passing the string with unicode into its constructor (e.g. `new Emoji("👌");` or `new Emoji("\uD83D\uDC4C");`). Your method of declaring an @Discord.Emoji should look similar to this: - [!code-csharp[Emoji Sample](samples/emoji-sample.cs)] +Also you can use `Emoji.Parse()` or `Emoji.TryParse()` methods +for parsing emojis from strings like `:heart:`, `<3` or `❤`. + [FileFormat.Info]: https://www.fileformat.info/info/emoji/list.htm ## Emote @@ -97,4 +99,4 @@ this: ## Additional Information To learn more about emote and emojis and how they could be used, -see the documentation of @Discord.IEmote. \ No newline at end of file +see the documentation of @Discord.IEmote. diff --git a/docs/guides/guild_events/creating-guild-events.md b/docs/guides/guild_events/creating-guild-events.md new file mode 100644 index 000000000..64ac0de9b --- /dev/null +++ b/docs/guides/guild_events/creating-guild-events.md @@ -0,0 +1,31 @@ +--- +uid: Guides.GuildEvents.Creating +title: Creating Guild Events +--- + +# Creating guild events + +You can create new guild events by using the `CreateEventAsync` function on a guild. + +### Parameters + +| Name | Type | Summary | +| ------------- | --------------------------------- | ---------------------------------------------------------------------------- | +| name | `string` | Sets the name of the event. | +| startTime | `DateTimeOffset` | Sets the start time of the event. | +| type | `GuildScheduledEventType` | Sets the type of the event. | +| privacyLevel? | `GuildScheduledEventPrivacyLevel` | Sets the privacy level of the event | +| description? | `string` | Sets the description of the event. | +| endTime? | `DateTimeOffset?` | Sets the end time of the event. | +| channelId? | `ulong?` | Sets the channel id of the event, only valid on stage or voice channel types | +| location? | `string` | Sets the location of the event, only valid on external types | + +Lets create a basic test event. + +```cs +var guild = client.GetGuild(guildId); + +var guildEvent = await guild.CreateEventAsync("test event", DateTimeOffset.UtcNow.AddDays(1), GuildScheduledEventType.External, endTime: DateTimeOffset.UtcNow.AddDays(2), location: "Space"); +``` + +This code will create an event that lasts a day and starts tomorrow. It will be an external event thats in space. diff --git a/docs/guides/guild_events/getting-event-users.md b/docs/guides/guild_events/getting-event-users.md new file mode 100644 index 000000000..f4b5388a0 --- /dev/null +++ b/docs/guides/guild_events/getting-event-users.md @@ -0,0 +1,16 @@ +--- +uid: Guides.GuildEvents.GettingUsers +title: Getting Guild Event Users +--- + +# Getting Event Users + +You can get a collection of users who are currently interested in the event by calling `GetUsersAsync`. This method works like any other get users method as in it returns an async enumerable. This method also supports pagination by user id. + +```cs +// get all users and flatten the result into one collection. +var users = await event.GetUsersAsync().FlattenAsync(); + +// get users around the 613425648685547541 id. +var aroundUsers = await event.GetUsersAsync(613425648685547541, Direction.Around).FlattenAsync(); +``` diff --git a/docs/guides/guild_events/intro.md b/docs/guides/guild_events/intro.md new file mode 100644 index 000000000..b60a8c70d --- /dev/null +++ b/docs/guides/guild_events/intro.md @@ -0,0 +1,41 @@ +--- +uid: Guides.GuildEvents.Intro +title: Introduction to Guild Events +--- + +# Guild Events + +Guild events are a way to host events within a guild. They offer alot of features and flexibility. + +## Getting started with guild events + +You can access any events within a guild by calling `GetEventsAsync` on a guild. + +```cs +var guildEvents = await guild.GetEventsAsync(); +``` + +If your working with socket guilds you can just use the `Events` property: + +```cs +var guildEvents = guild.Events; +``` + +There are also new gateway events that you can hook to receive guild scheduled events on. + +```cs +// Fired when a guild event is cancelled. +client.GuildScheduledEventCancelled += ... + +// Fired when a guild event is completed. +client.GuildScheduledEventCompleted += ... + +// Fired when a guild event is started. +client.GuildScheduledEventStarted += ... + +// Fired when a guild event is created. +client.GuildScheduledEventCreated += ... + +// Fired when a guild event is updated. +client.GuildScheduledEventUpdated += ... +``` diff --git a/docs/guides/guild_events/modifying-events.md b/docs/guides/guild_events/modifying-events.md new file mode 100644 index 000000000..05e14ec98 --- /dev/null +++ b/docs/guides/guild_events/modifying-events.md @@ -0,0 +1,23 @@ +--- +uid: Guides.GuildEvents.Modifying +title: Modifying Guild Events +--- + +# Modifying Events + +You can modify events using the `ModifyAsync` method to modify the event, heres the properties you can modify: + +| Name | Type | Description | +| ------------ | --------------------------------- | -------------------------------------------- | +| ChannelId | `ulong?` | Gets or sets the channel id of the event. | +| string | `string` | Gets or sets the location of this event. | +| Name | `string` | Gets or sets the name of the event. | +| PrivacyLevel | `GuildScheduledEventPrivacyLevel` | Gets or sets the privacy level of the event. | +| StartTime | `DateTimeOffset` | Gets or sets the start time of the event. | +| EndTime | `DateTimeOffset` | Gets or sets the end time of the event. | +| Description | `string` | Gets or sets the description of the event. | +| Type | `GuildScheduledEventType` | Gets or sets the type of the event. | +| Status | `GuildScheduledEventStatus` | Gets or sets the status of the event. | + +> [!NOTE] +> All of these properties are optional. diff --git a/docs/guides/interactions/application-commands/01-getting-started.md b/docs/guides/interactions/application-commands/01-getting-started.md new file mode 100644 index 000000000..fc8c8fe30 --- /dev/null +++ b/docs/guides/interactions/application-commands/01-getting-started.md @@ -0,0 +1,32 @@ +--- +uid: Guides.SlashCommands.Intro +title: Introduction to slash commands +--- + + +# Getting started with application commands. + +Welcome! This guide will show you how to use application commands. + +## What is an application command? + +Application commands consist of three different types. Slash commands, context menu User commands and context menu Message commands. +Slash commands are made up of a name, description, and a block of options, which you can think of like arguments to a function. The name and description help users find your command among many others, and the options validate user input as they fill out your command. +Message and User commands are only a name, to the user. So try to make the name descriptive. They're accessed by right clicking (or long press, on mobile) a user or a message, respectively. + +All three varieties of application commands have both Global and Guild variants. Your global commands are available in every guild that adds your application. You can also make commands for a specific guild; they're only available in that guild. The User and Message commands are more limited in quantity than the slash commands. For specifics, check out their respective guide pages. + +An Interaction is the message that your application receives when a user uses a command. It includes the values that the user submitted, as well as some metadata about this particular instance of the command being used: the guild_id, channel_id, member and other fields. You can find all the values in our data models. + +## Authorizing your bot for application commands + +There is a new special OAuth2 scope for applications called `applications.commands`. In order to make Application Commands work within a guild, the guild must authorize your application with the `applications.commands` scope. The bot scope is not enough. + +Head over to your discord applications OAuth2 screen and make sure to select the `application.commands` scope. + +![OAuth2 scoping](slash-commands/images/oauth.png) + +From there you can then use the link to add your bot to a server. + +> [!NOTE] +> In order for users in your guild to use your slash commands, they need to have the "Use Slash Command" permission on the guild. diff --git a/docs/guides/interactions/application-commands/context-menu-commands/creating-context-menu-commands.md b/docs/guides/interactions/application-commands/context-menu-commands/creating-context-menu-commands.md new file mode 100644 index 000000000..02a9cde14 --- /dev/null +++ b/docs/guides/interactions/application-commands/context-menu-commands/creating-context-menu-commands.md @@ -0,0 +1,105 @@ +--- +uid: Guides.ContextCommands.Creating +title: Creating Context Commands +--- + +# Creating context menu commands. + +There are two kinds of Context Menu Commands: User Commands and Message Commands. +Each of these have a Global and Guild variant. +Global menu commands are available for every guild that adds your app. An individual app's global commands are also available in DMs if that app has a bot that shares a mutual guild with the user. + +Guild commands are specific to the guild you specify when making them. Guild commands are not available in DMs. Command names are unique per application within each scope (global and guild). That means: + +- Your app cannot have two global commands with the same name +- Your app cannot have two guild commands within the same name on the same guild +- Your app can have a global and guild command with the same name +- Multiple apps can have commands with the same names + +**Note**: Apps can have a maximum of 5 global context menu commands, and an additional 5 guild-specific context menu commands per guild. + +If you don't have the code for a bot ready yet please follow [this guide](https://docs.stillu.cc/guides/getting_started/first-bot.html). + +## UserCommandBuilder + +The context menu user command builder will help you create user commands. The builder has these available fields and methods: + +| Name | Type | Description | +| -------- | -------- | ------------------------------------------------------------------------------------------------ | +| Name | string | The name of this context menu command. | +| WithName | Function | Sets the field name. | +| Build | Function | Builds the builder into the appropriate `UserCommandProperties` class used to make Menu commands | + +## MessageCommandBuilder + +The context menu message command builder will help you create message commands. The builder has these available fields and methods: + +| Name | Type | Description | +| -------- | -------- | --------------------------------------------------------------------------------------------------- | +| Name | string | The name of this context menu command. | +| WithName | Function | Sets the field name. | +| Build | Function | Builds the builder into the appropriate `MessageCommandProperties` class used to make Menu commands | + +**Note**: Context Menu command names can be upper and lowercase, and use spaces. + +Let's use the user command builder to make a global and guild command. + +```cs +// Let's hook the ready event for creating our commands in. +client.Ready += Client_Ready; + +... + +public async Task Client_Ready() +{ + // Let's build a guild command! We're going to need a guild so lets just put that in a variable. + var guild = client.GetGuild(guildId); + + // Next, lets create our user and message command builder. This is like the embed builder but for context menu commands. + var guildUserCommand = new UserCommandBuilder(); + var guildMessageCommand = new MessageCommandBuilder(); + + // Note: Names have to be all lowercase and match the regular expression ^[\w -]{3,32}$ + guildUserCommand.WithName("Guild User Command"); + guildMessageCommand.WithName("Guild Message Command"); + + // Descriptions are not used with User and Message commands + //guildCommand.WithDescription(""); + + // Let's do our global commands + var globalUserCommand = new UserCommandBuilder(); + globalCommand.WithName("Global User Command"); + var globalMessageCommand = new MessageCommandBuilder(); + globalMessageCommand.WithName("Global Message Command"); + + + try + { + // Now that we have our builder, we can call the BulkOverwriteApplicationCommandAsync to make our context commands. Note: this will overwrite all your previous commands with this array. + await guild.BulkOverwriteApplicationCommandAsync(new ApplicationCommandProperties[] + { + guildUserCommand.Build(), + guildMessageCommand.Build() + }); + + // With global commands we dont need the guild. + await client.BulkOverwriteGlobalApplicationCommandsAsync(new ApplicationCommandProperties[] + { + globalUserCommand.Build(), + globalMessageCommand.Build() + }) + } + catch(ApplicationCommandException exception) + { + // If our command was invalid, we should catch an ApplicationCommandException. This exception contains the path of the error as well as the error message. You can serialize the Error field in the exception to get a visual of where your error is. + var json = JsonConvert.SerializeObject(exception.Error, Formatting.Indented); + + // You can send this error somewhere or just print it to the console, for this example we're just going to print it. + Console.WriteLine(json); + } +} + +``` + +> [!NOTE] +> Application commands only need to be created once. They do _not_ have to be 'created' on every startup or connection. The example simple shows creating them in the ready event as it's simpler than creating normal bot commands to register application commands. diff --git a/docs/guides/interactions/application-commands/context-menu-commands/receiving-context-menu-command-events.md b/docs/guides/interactions/application-commands/context-menu-commands/receiving-context-menu-command-events.md new file mode 100644 index 000000000..d4e973d04 --- /dev/null +++ b/docs/guides/interactions/application-commands/context-menu-commands/receiving-context-menu-command-events.md @@ -0,0 +1,33 @@ +--- +uid: Guides.ContextCommands.Reveiving +title: Receiving Context Commands +--- + +# Receiving Context Menu events + +User commands and Message commands have their own unique event just like the other interaction types. For user commands the event is `UserCommandExecuted` and for message commands the event is `MessageCommandExecuted`. + +```cs +// For message commands +client.MessageCommandExecuted += MessageCommandHandler; + +// For user commands +client.UserCommandExecuted += UserCommandHandler; + +... + +public async Task MessageCommandHandler(SocketMessageCommand arg) +{ + Console.Writeline("Message command received!"); +} + +public async Task UserCommandHandler(SocketUserCommand arg) +{ + Console.Writeline("User command received!"); +} +``` + +User commands contain a SocketUser object called `Member` in their data class, showing the user that was clicked to run the command. +Message commands contain a SocketMessage object called `Message` in their data class, showing the message that was clicked to run the command. + +Both return the user who ran the command, the guild (if any), channel, etc. \ No newline at end of file diff --git a/docs/guides/interactions/application-commands/slash-commands/02-creating-slash-commands.md b/docs/guides/interactions/application-commands/slash-commands/02-creating-slash-commands.md new file mode 100644 index 000000000..9e35de285 --- /dev/null +++ b/docs/guides/interactions/application-commands/slash-commands/02-creating-slash-commands.md @@ -0,0 +1,98 @@ +--- +uid: Guides.SlashCommands.Creating +title: Creating Slash Commands +--- + +# Creating your first slash commands. + +There are two kinds of Slash Commands: global commands and guild commands. +Global commands are available for every guild that adds your app. An individual app's global commands are also available in DMs if that app has a bot that shares a mutual guild with the user. + +Guild commands are specific to the guild you specify when making them. Guild commands are not available in DMs. Command names are unique per application within each scope (global and guild). That means: + +- Your app cannot have two global commands with the same name +- Your app cannot have two guild commands within the same name on the same guild +- Your app can have a global and guild command with the same name +- Multiple apps can have commands with the same names + +**Note**: Apps can have a maximum of 100 global commands, and an additional 100 guild-specific commands per guild. + +**Note**: Global commands will take up to 1 hour to create, delete or modify on guilds. If you need to update a command quickly for testing you can create it as a guild command. + +If you don't have the code for a bot ready yet please follow [this guide](https://docs.stillu.cc/guides/getting_started/first-bot.html). + +## SlashCommandBuilder + +The slash command builder will help you create slash commands. The builder has these available fields and methods: + +| Name | Type | Description | +| --------------------- | -------------------------------- | -------------------------------------------------------------------------------------------- | +| MaxNameLength | const int | The maximum length of a name for a slash command allowed by Discord. | +| MaxDescriptionLength | const int | The maximum length of a commands description allowed by Discord. | +| MaxOptionsCount | const int | The maximum count of command options allowed by Discord | +| Name | string | The name of this slash command. | +| Description | string | A 1-100 length description of this slash command | +| Options | List\ | The options for this command. | +| DefaultPermission | bool | Whether the command is enabled by default when the app is added to a guild. | +| WithName | Function | Sets the field name. | +| WithDescription | Function | Sets the description of the current command. | +| WithDefaultPermission | Function | Sets the default permission of the current command. | +| AddOption | Function | Adds an option to the current slash command. | +| Build | Function | Builds the builder into a `SlashCommandCreationProperties` class used to make slash commands | + +> [!NOTE] +> Slash command names must be all lowercase! + +## Creating a Slash Command + +Let's use the slash command builder to make a global and guild command. + +```cs +// Let's hook the ready event for creating our commands in. +client.Ready += Client_Ready; + +... + +public async Task Client_Ready() +{ + // Let's build a guild command! We're going to need a guild so lets just put that in a variable. + var guild = client.GetGuild(guildId); + + // Next, lets create our slash command builder. This is like the embed builder but for slash commands. + var guildCommand = new SlashCommandBuilder(); + + // Note: Names have to be all lowercase and match the regular expression ^[\w-]{3,32}$ + guildCommand.WithName("first-command"); + + // Descriptions can have a max length of 100. + guildCommand.WithDescription("This is my first guild slash command!"); + + // Let's do our global command + var globalCommand = new SlashCommandBuilder(); + globalCommand.WithName("first-global-command"); + globalCommand.WithDescription("This is my frist global slash command"); + + try + { + // Now that we have our builder, we can call the CreateApplicationCommandAsync method to make our slash command. + await guild.CreateApplicationCommandAsync(guildCommand.Build()); + + // With global commands we dont need the guild. + await client.CreateGlobalApplicationCommandAsync(globalCommand.Build()); + // Using the ready event is a simple implementation for the sake of the example. Suitable for testing and development. + // For a production bot, it is recommended to only run the CreateGlobalApplicationCommandAsync() once for each command. + } + catch(ApplicationCommandException exception) + { + // If our command was invalid, we should catch an ApplicationCommandException. This exception contains the path of the error as well as the error message. You can serialize the Error field in the exception to get a visual of where your error is. + var json = JsonConvert.SerializeObject(exception.Error, Formatting.Indented); + + // You can send this error somewhere or just print it to the console, for this example we're just going to print it. + Console.WriteLine(json); + } +} + +``` + +> [!NOTE] +> Slash commands only need to be created once. They do _not_ have to be 'created' on every startup or connection. The example simple shows creating them in the ready event as it's simpler than creating normal bot commands to register slash commands. The global commands take up to an hour to register every time the CreateGlobalApplicationCommandAsync() is called for a given command. diff --git a/docs/guides/interactions/application-commands/slash-commands/03-responding-to-slash-commands.md b/docs/guides/interactions/application-commands/slash-commands/03-responding-to-slash-commands.md new file mode 100644 index 000000000..3dbc579fe --- /dev/null +++ b/docs/guides/interactions/application-commands/slash-commands/03-responding-to-slash-commands.md @@ -0,0 +1,40 @@ +--- +uid: Guides.SlashCommands.Receiving +title: Receiving and Responding to Slash Commands +--- + +# Responding to interactions. + +Interactions are the base thing sent over by Discord. Slash commands are one of the interaction types. We can listen to the `SlashCommandExecuted` event to respond to them. Lets add this to our code: + +```cs +client.SlashCommandExecuted += SlashCommandHandler; + +... + +private async Task SlashCommandHandler(SocketSlashCommand command) +{ + +} +``` + +With every type of interaction there is a `Data` field. This is where the relevant information lives about our command that was executed. In our case, `Data` is a `SocketSlashCommandData` instance. In the data class, we can access the name of the command triggered as well as the options if there were any. For this example, we're just going to respond with the name of the command executed. + +```cs +private async Task SlashCommandHandler(SocketSlashCommand command) +{ + await command.RespondAsync($"You executed {command.Data.Name}"); +} +``` + +Let's try this out! + +![slash command picker](images/slashcommand1.png) + +![slash command result](images/slashcommand2.png) + +> [!NOTE] +> After receiving an interaction, you must respond to acknowledge it. You can choose to respond with a message immediately using `RespondAsync()` or you can choose to send a deferred response with `DeferAsync()`. +> If choosing a deferred response, the user will see a loading state for the interaction, and you'll have up to 15 minutes to edit the original deferred response using `ModifyOriginalResponseAsync()`. You can read more about response types [here](https://discord.com/developers/docs/interactions/slash-commands#interaction-response) + +This seems to be working! Next, we will look at parameters for slash commands. diff --git a/docs/guides/interactions/application-commands/slash-commands/04-parameters.md b/docs/guides/interactions/application-commands/slash-commands/04-parameters.md new file mode 100644 index 000000000..6afd83729 --- /dev/null +++ b/docs/guides/interactions/application-commands/slash-commands/04-parameters.md @@ -0,0 +1,102 @@ +--- +uid: Guides.SlashCommands.Parameters +title: Slash Command Parameters +--- + +# Slash command parameters + +Slash commands can have a bunch of parameters, each their own type. Let's first go over the types of parameters we can have. + +| Name | Description | +| --------------- | -------------------------------------------------- | +| SubCommand | A subcommand inside of a subcommand group. | +| SubCommandGroup | The parent command group of subcommands. | +| String | A string of text. | +| Integer | A number. | +| Boolean | True or False. | +| User | A user | +| Channel | A channel, this includes voice text and categories | +| Role | A role. | +| Mentionable | A role or a user. | + +Each one of the parameter types has its own DNET type in the `SocketSlashCommandDataOption`'s Value field: +| Name | C# Type | +| --------------- | ------------------------------------------------ | +| SubCommand | NA | +| SubCommandGroup | NA | +| String | `string` | +| Integer | `int` | +| Boolean | `bool` | +| User | `SocketGuildUser` or `SocketUser` | +| Role | `SocketRole` | +| Channel | `SocketChannel` | +| Mentionable | `SocketUser`, `SocketGuildUser`, or `SocketRole` | + +Let's start by making a command that takes in a user and lists their roles. + +```cs +client.Ready += Client_Ready; + +... + +public async Task Client_Ready() +{ + ulong guildId = 848176216011046962; + + var guildCommand = new SlashCommandBuilder() + .WithName("list-roles") + .WithDescription("Lists all roles of a user.") + .AddOption("user", ApplicationCommandOptionType.User, "The users whos roles you want to be listed", isRequired: true); + + try + { + await client.Rest.CreateGuildCommand(guildCommand.Build(), guildId); + } + catch(ApplicationCommandException exception) + { + var json = JsonConvert.SerializeObject(exception.Error, Formatting.Indented); + Console.WriteLine(json); + } +} + +``` + +![list roles command](images/listroles1.png) + +That seems to be working, now Let's handle the interaction. + +```cs +private async Task SlashCommandHandler(SocketSlashCommand command) +{ + // Let's add a switch statement for the command name so we can handle multiple commands in one event. + switch(command.Data.Name) + { + case "list-roles": + await HandleListRoleCommand(command); + break; + } +} + +private async Task HandleListRoleCommand(SocketSlashCommand command) +{ + // We need to extract the user parameter from the command. since we only have one option and it's required, we can just use the first option. + var guildUser = (SocketGuildUser)command.Data.Options.First().Value; + + // We remove the everyone role and select the mention of each role. + var roleList = string.Join(",\n", guildUser.Roles.Where(x => !x.IsEveryone).Select(x => x.Mention)); + + var embedBuiler = new EmbedBuilder() + .WithAuthor(guildUser.ToString(), guildUser.GetAvatarUrl() ?? guildUser.GetDefaultAvatarUrl()) + .WithTitle("Roles") + .WithDescription(roleList) + .WithColor(Color.Green) + .WithCurrentTimestamp(); + + // Now, Let's respond with the embed. + await command.RespondAsync(embed: embedBuiler.Build()); +} +``` + +![working list roles](images/listroles2.png) + +That has worked! Next, we will go over responding ephemerally. diff --git a/docs/guides/interactions/application-commands/slash-commands/05-responding-ephemerally.md b/docs/guides/interactions/application-commands/slash-commands/05-responding-ephemerally.md new file mode 100644 index 000000000..10b04a8d2 --- /dev/null +++ b/docs/guides/interactions/application-commands/slash-commands/05-responding-ephemerally.md @@ -0,0 +1,23 @@ +--- +uid: Guides.SlashCommands.Ephemeral +title: Ephemeral Responses +--- + +# Responding ephemerally + +What is an ephemeral response? Basically, only the user who executed the command can see the result of it, this is pretty simple to implement. + +> [!NOTE] +> You don't have to run arg.DeferAsync() to capture the interaction, you can use arg.RespondAsync() with a message to capture it, this also follows the ephemeral rule. + +When responding with either `FollowupAsync` or `RespondAsync` you can pass in an `ephemeral` property. When setting it to true it will respond ephemerally, false and it will respond non-ephemerally. + +Let's use this in our list role command. + +```cs +await command.RespondAsync(embed: embedBuiler.Build(), ephemeral: true); +``` + +Running the command now only shows the message to us! + +![ephemeral command](images/ephemeral1.png) \ No newline at end of file diff --git a/docs/guides/interactions/application-commands/slash-commands/06-subcommands.md b/docs/guides/interactions/application-commands/slash-commands/06-subcommands.md new file mode 100644 index 000000000..83d7b283c --- /dev/null +++ b/docs/guides/interactions/application-commands/slash-commands/06-subcommands.md @@ -0,0 +1,219 @@ +--- +uid: Guides.SlashCommands.SubCommand +title: Sub Commands +--- + +# Subcommands + +Subcommands allow you to have multiple commands available in a single command. They can be useful for representing sub options for a command. For example: A settings command. Let's first look at some limitations with subcommands set by discord. + +- An app can have up to 25 subcommand groups on a top-level command +- An app can have up to 25 subcommands within a subcommand group +- commands can have up to 25 `options` +- options can have up to 25 `choices` + +``` +VALID + +command +| +|__ subcommand +| +|__ subcommand + +---- + +command +| +|__ subcommand-group + | + |__ subcommand +| +|__ subcommand-group + | + |__ subcommand + + +------- + +INVALID + + +command +| +|__ subcommand-group + | + |__ subcommand-group +| +|__ subcommand-group + | + |__ subcommand-group + +---- + +INVALID + +command +| +|__ subcommand + | + |__ subcommand-group +| +|__ subcommand + | + |__ subcommand-group +``` + +Let's write a settings command that can change 3 fields in our bot. + +```cs +public string FieldA { get; set; } = "test"; +public int FieldB { get; set; } = 10; +public bool FieldC { get; set; } = true; + +public async Task Client_Ready() +{ + ulong guildId = 848176216011046962; + + var guildCommand = new SlashCommandBuilder() + .WithName("settings") + .WithDescription("Changes some settings within the bot.") + .AddOption(new SlashCommandOptionBuilder() + .WithName("field-a") + .WithDescription("Gets or sets the field A") + .WithType(ApplicationCommandOptionType.SubCommandGroup) + .AddOption(new SlashCommandOptionBuilder() + .WithName("set") + .WithDescription("Sets the field A") + .WithType(ApplicationCommandOptionType.SubCommand) + .AddOption("value", ApplicationCommandOptionType.String, "the value to set the field", isRequired: true) + ).AddOption(new SlashCommandOptionBuilder() + .WithName("get") + .WithDescription("Gets the value of field A.") + .WithType(ApplicationCommandOptionType.SubCommand) + ) + ).AddOption(new SlashCommandOptionBuilder() + .WithName("field-b") + .WithDescription("Gets or sets the field B") + .WithType(ApplicationCommandOptionType.SubCommandGroup) + .AddOption(new SlashCommandOptionBuilder() + .WithName("set") + .WithDescription("Sets the field B") + .WithType(ApplicationCommandOptionType.SubCommand) + .AddOption("value", ApplicationCommandOptionType.Integer, "the value to set the fie to.", isRequired: true) + ).AddOption(new SlashCommandOptionBuilder() + .WithName("get") + .WithDescription("Gets the value of field B.") + .WithType(ApplicationCommandOptionType.SubCommand) + ) + ).AddOption(new SlashCommandOptionBuilder() + .WithName("field-c") + .WithDescription("Gets or sets the field C") + .WithType(ApplicationCommandOptionType.SubCommandGroup) + .AddOption(new SlashCommandOptionBuilder() + .WithName("set") + .WithDescription("Sets the field C") + .WithType(ApplicationCommandOptionType.SubCommand) + .AddOption("value", ApplicationCommandOptionType.Boolean, "the value to set the fie to.", isRequired: true) + ).AddOption(new SlashCommandOptionBuilder() + .WithName("get") + .WithDescription("Gets the value of field C.") + .WithType(ApplicationCommandOptionType.SubCommand) + ) + ); + + try + { + await client.Rest.CreateGuildCommand(guildCommand.Build(), guildId); + } + catch(ApplicationCommandException exception) + { + var json = JsonConvert.SerializeObject(exception.Error, Formatting.Indented); + Console.WriteLine(json); + } +} +``` + +All that code generates a command that looks like this: +![settings](images/settings1.png) + +Now that we have our command made, we need to handle the multiple options with this command. So lets add this into our handler: + +```cs +private async Task SlashCommandHandler(SocketSlashCommand command) +{ + // Let's add a switch statement for the command name so we can handle multiple commands in one event. + switch(command.Data.Name) + { + case "list-roles": + await HandleListRoleCommand(command); + break; + case "settings": + await HandleSettingsCommand(command); + break; + } +} + +private async Task HandleSettingsCommand(SocketSlashCommand command) +{ + // First lets extract our variables + var fieldName = command.Data.Options.First().Name; + var getOrSet = command.Data.Options.First().Options.First().Name; + // Since there is no value on a get command, we use the ? operator because "Options" can be null. + var value = command.Data.Options.First().Options.First().Options?.FirstOrDefault().Value; + + switch (fieldName) + { + case "field-a": + { + if(getOrSet == "get") + { + await command.RespondAsync($"The value of `field-a` is `{FieldA}`"); + } + else if (getOrSet == "set") + { + this.FieldA = (string)value; + await command.RespondAsync($"`field-a` has been set to `{FieldA}`"); + } + } + break; + case "field-b": + { + if (getOrSet == "get") + { + await command.RespondAsync($"The value of `field-b` is `{FieldB}`"); + } + else if (getOrSet == "set") + { + this.FieldB = (int)value; + await command.RespondAsync($"`field-b` has been set to `{FieldB}`"); + } + } + break; + case "field-c": + { + if (getOrSet == "get") + { + await command.RespondAsync($"The value of `field-c` is `{FieldC}`"); + } + else if (getOrSet == "set") + { + this.FieldC = (bool)value; + await command.RespondAsync($"`field-c` has been set to `{FieldC}`"); + } + } + break; + } +} + +``` + +Now, let's try this out! Running the 3 get commands seems to get the default values we set. + +![settings get](images/settings2.png) + +Now let's try changing each to a different value. + +![settings set](images/settings3.png) + +That has worked! Next, let't look at choices in commands. diff --git a/docs/guides/interactions/application-commands/slash-commands/07-choice-slash-command.md b/docs/guides/interactions/application-commands/slash-commands/07-choice-slash-command.md new file mode 100644 index 000000000..3951e1141 --- /dev/null +++ b/docs/guides/interactions/application-commands/slash-commands/07-choice-slash-command.md @@ -0,0 +1,85 @@ +--- +uid: Guides.SlashCommands.Choices +title: Slash Command Choices +--- + +# Slash Command Choices. + +With slash command options you can add choices, making the user select between some set values. Lets create a command that asks how much they like our bot! + +Let's set up our slash command: + +```cs +private async Task Client_Ready() +{ + ulong guildId = 848176216011046962; + + var guildCommand = new SlashCommandBuilder() + .WithName("feedback") + .WithDescription("Tell us how much you are enjoying this bot!") + .AddOption(new SlashCommandOptionBuilder() + .WithName("rating") + .WithDescription("The rating your willing to give our bot") + .WithRequired(true) + .AddChoice("Terrible", 1) + .AddChoice("Meh", 2) + .AddChoice("Good", 3) + .AddChoice("Lovely", 4) + .AddChoice("Excellent!", 5) + .WithType(ApplicationCommandOptionType.Integer) + ).Build(); + + try + { + await client.Rest.CreateGuildCommand(guildCommand.Build(), guildId); + } + catch(ApplicationCommandException exception) + { + var json = JsonConvert.SerializeObject(exception.Error, Formatting.Indented); + Console.WriteLine(json); + } +} +``` +> [!NOTE] +> Your `ApplicationCommandOptionType` specifies which type your choices are, you need to use `ApplicationCommandOptionType.Integer` for choices whos values are whole numbers, `ApplicationCommandOptionType.Number` for choices whos values are doubles, and `ApplicationCommandOptionType.String` for string values. + +We have defined 5 choices for the user to pick from, each choice has a value assigned to it. The value can either be a string or an int. In our case we're going to use an int. This is what the command looks like: + +![feedback style](images/feedback1.png) + +Lets add our code for handling the interaction. + +```cs +private async Task SlashCommandHandler(SocketSlashCommand command) +{ + // Let's add a switch statement for the command name so we can handle multiple commands in one event. + switch(command.Data.Name) + { + case "list-roles": + await HandleListRoleCommand(command); + break; + case "settings": + await HandleSettingsCommand(command); + break; + case "feedback": + await HandleFeedbackCommand(command); + break; + } +} + +private async Task HandleFeedbackCommand(SocketSlashCommand command) +{ + var embedBuilder = new EmbedBuilder() + .WithAuthor(command.User) + .WithTitle("Feedback") + .WithDescription($"Thanks for your feedback! You rated us {command.Data.Options.First().Value}/5") + .WithColor(Color.Green) + .WithCurrentTimestamp(); + + await command.RespondAsync(embed: embedBuilder.Build()); +} +``` + +And this is the result: + +![feedback working](images/feedback2.png) diff --git a/docs/guides/interactions/application-commands/slash-commands/08-bulk-overwrite-of-global-slash-commands.md b/docs/guides/interactions/application-commands/slash-commands/08-bulk-overwrite-of-global-slash-commands.md new file mode 100644 index 000000000..095eda14f --- /dev/null +++ b/docs/guides/interactions/application-commands/slash-commands/08-bulk-overwrite-of-global-slash-commands.md @@ -0,0 +1,40 @@ +--- +uid: Guides.SlashCommands.BulkOverwrite +title: Slash Command Bulk Overwrites +--- + +If you have too many global commands then you might want to consider using the bulk overwrite function. + +```cs +public async Task Client_Ready() +{ + List applicationCommandProperties = new(); + try + { + // Simple help slash command. + SlashCommandBuilder globalCommandHelp = new SlashCommandBuilder(); + globalCommandHelp.WithName("help"); + globalCommandHelp.WithDescription("Shows information about the bot."); + applicationCommandProperties.Add(globalCommandHelp.Build()); + + // Slash command with name as its parameter. + SlashCommandOptionBuilder slashCommandOptionBuilder = new(); + slashCommandOptionBuilder.WithName("name"); + slashCommandOptionBuilder.WithType(ApplicationCommandOptionType.String); + slashCommandOptionBuilder.WithDescription("Add a family"); + slashCommandOptionBuilder.WithRequired(true); // Only add this if you want it to be required + + SlashCommandBuilder globalCommandAddFamily = new SlashCommandBuilder(); + globalCommandAddFamily.WithName("add-family"); + globalCommandAddFamily.WithDescription("Add a family"); + applicationCommandProperties.Add(globalCommandAddFamily.Build()); + + await _client.BulkOverwriteGlobalApplicationCommandsAsync(applicationCommandProperties.ToArray()); + } + catch (ApplicationCommandException exception) + { + var json = JsonConvert.SerializeObject(exception.Error, Formatting.Indented); + Console.WriteLine(json); + } +} +``` diff --git a/docs/guides/interactions/application-commands/slash-commands/images/ephemeral1.png b/docs/guides/interactions/application-commands/slash-commands/images/ephemeral1.png new file mode 100644 index 0000000000000000000000000000000000000000..61eab94b60e9b4366c64d0f7f983134f8947f2f4 GIT binary patch literal 37660 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 z007{$MKA;a000000002sB?yK90000000000yad4z0000000000fR`W`0ssI200000 z0Pqq7LjV8(00000003TsU z1ONa4000000N^DEh5!Hn00000006uM!4Lod00000004lOAQ%Dw000000002+5(Gm4 z0000000000UV>l<0000000000z)KJe0RR91000000C)+4ApigX00000001vRFa!Vq z00000007`62!;Rv000000002I1i=sh000000001hmmnAd0000000000@Dc<=00000 z000000A7M%2mk;8000000KiKS3;_TD00000004Lif*}9^000000000l;l067(+=u$ z{e2p7ZM;)avY5`jHRS&LG`O8p0RR91000000B{=eY0)Yn*Eud5BPMELVgsjeG^zUU z#0zxun^M_6Krz}4)$1esD(rugb^8O8wbj&(o}ynbj?(_c^Ax3@jvo4L= zuSMf}>ZZvae_8+l000000001wDEkpZ0f7n(4k*PIs)$C%9<5U*dY`9+h)_kf>!5Bv zK29Y7000000001hcPaZ|XzW0>Y<*6F?Q{KM53<>)>22B-I+yss63>qoGTxb{sf(8A z`}Zg7y`v2c0ssI200000062qr#~MRhrs?US!*tJ^^UEiQ0ssI20000005}DmI>Aug z-nwnvTbe%eJ1v|&T~l5ksR2!$bB-a;P0{psMyijG6mkAgjeBRRraW`mG0!RSAx->f zs-};-PQ^_m7c*d_-uUP%&HeE!tB{3Rlr&D4_9m`uF{Pl@R4pKwzlVM^_CQ^OnX9 zDlS4Qpz}kT{?2{6v~9Giq$g=#V7Quhy-rWPGeLvv9phR6000000001hli-vHhK7u} zSqasoZ8Jve(r*3KuTKvRoVZHq0rk~y=)H+7_JvqFuq5@L2{MBpKsm}lmo6`DC}mbN*yt6uy>i&LzF=y(lr zR~?qRM9*J6KqDqD$#0*}oV#XV@#l(>{=biP^S#rw$}S~o($G(|{GfGMH(Gt{x?&_= zrM7i_tmaEJ;Vt)-=GvJrF3_rMsYbKj8sO9d0000000000oGMO!Ff_1b&_q^8j*=36Hv+AqWP>kIW0000000000;M8&QgCP^77%3|> zbH=PdDwyf4n zch!+5uF*C#GO1BlJ=wX)WOH?U>+X97DZ(iN0000000000oGMO!Fr-;}de&;C%cX?d zCTjXikLsz%tnHZzn*QS?jTqQf33W?Y>!%F38egV~30v;Q!_Ry)z7-Vr}NC`M$2_@qh`MPk|sIr000000000008SM?Em|d% zdFJwW#B}bhD3_GFVV36o&#CmdXm#u?&6~5@DIbq;CG?C_Xh4Qimj9qdTbybD00000 z0001hQ_LwB3;_TD00000006)1ONa4000000N^DEh5!Hn00000 z006uM!4Lod00000004lOAQ%Dw000000002+5(Gm40000000000UV>l<0000000000 zz)KJe0RR91000000C)+4ApigX00000001vRFa!Vq00000007`62!;Rv000000002I z1i=sh000000001hmmnAd0000000000@Dc<=00000000000A7M%2mk;8000000KiKS z3;_TD00000004N2Ga?v@>D*gG?!Qli+c^~g0000000000ID*H5p$FfaqlNS4YT?Wm zG}y<-sVz=lJ@xvX7wlD_{nMe>VQ$6Ml$SpO&D{&>asOWT&t(za}?!c{|2?o)@= z?XBC!y`||hzth6m(>3Mwks8p{IaiG6fe-18kG|5}dEaTqJ5Os!XXgX}000000001h zm-E;fL$<$?^!(SW^=GaW(fl$c6iSBTb=$eo@==nOf4W4esZMo4bqDG7e?O`rUE>rU zn5v|7mtxxX)rfbdYV;9-PoN^M_)1gmzgP)TE(HX-6w$J)ZW}W}gMErdQUCw|00000 z007>{Gr`cnaMj4mVrER%;w`yScynEIkiL@R^AhdLFqNT-7^wU< zL&gr(In||YGe)U@w|?r^r-uekT&+U^_0{i|`&6e;-V>mRaB2DEQM#~gC!KTYcrDy# zRac8v#{q}?1ONa4000000Pt3x35Lx2LYCA&H(LKL(S-S{t$nCkBwm%D(Db`1UJ=%Q z)spA5E!S$B?yk?f)~r9&BQ8KXxI!}@nWe2x?Wz~2Y4I-WAUa+*6pDK?wCepKx^|q? zcFSaKOR+jO^7{k;000000002+UY-etYPn>V7|OHl!K0SO4t>6R zUNZ5`u6<@MIhW4LTV!Zp6GfOnCpb<+mM+w?{B5K!w?18>KXav+_UV8;q1A+}PBh^DJJio90RR91000000N~g>5)4(TXr_`&*CF?iAIIlL!qt!_d(veL5@YQEzt%uL@0Z;{yXLHG;@Unx~HKtwvBsG zV<#`swrrQ8&h4!}iPqLLUQtTYqRFr6<;|=y1ONa40000008m;!Em|d<7THQn=iZ8P zNvRuVY2N>wN&o-=00000005NQX%h?q0000000000;P3IsRB`|S000000000u^$-jJ z0000000000cnN|b0000000000053r>1ONa4000000N^DEh5!Hn00000006uM!4Lod z00000004lOAQ%Dw000000002+5(Gm40000000000UV>l<0000000000z)KJe0RR91 z000000C)+4ApigX00000001vRFa!Vq00000007`62!;Rv000000002I1i=sh00000 z0001hmmnAd0000000000@Dc<=00000000000A7M%2mk;8000000KiKS3;_TD00000 z004Lif*}9^000000000lK`;aW000000002sB?yK90000000000yad4z0000000000 zfR`W`0ssI2000000Pqq7LjV8(00000003TsUa1yRReAtsoVNG6#xJL00000003t= zpBAkWa-Cxjh}M9I@6f<*%@tKGz^Sujq-xi?CHnOJ@p}2|B&V``4Swxg4Ns_|4WFN* zOUFnad=mfw000000000^37#77 zSWo?DxppNb=_AJK00000000000F(>Q1Vc9t)dh_MrHs{@bo~%Lf5dcgt2OoU8`b0L zCzMMr1ONa40000008lPG5)9qeFod{={<~$4u38ee2r+RQkaT$v;4WrfJF&J%7V(nzY$zY4!Qi>qFJ4 zmUS}Q>N7pBPqz)9pJVkaCbRziZV7Ox|t*@T=w|;kq@ND~fw4VAgNx2GFi+;WB zx;_Kq?citE;>mhw);7D&tXuTf&C~SX15#kq4jN$B4ONe3F1yc9?;5R{`P161*1ki| zApigX000000Dzb9KroapWt)AdA_m$O`QCndgho6v%DsJ?p00000 z0002M+jt-tnx<7d&EBPmR`GUCo^6YN(99XL+}pu)r`F-;?6cFAHwLIgCO|T>oP9|e zx>UvNOVcL+000000002M8O|e9$xZ!rwLOq0qTO{GT-RN9OmkAS&s5A&jenZ)LoS^aWfu$Rt+Px+r7Cs3wOgMmdnKU&mlDh#0000000000z>9e# z7~1yFX_~joI&j5n*vn6AKs|STK@old&XHrwPuh@@D^-hD_rc)>qM>+=J~!GfZ}{yC zyJpf__cU}-ZO5u(!3=3oU$v-VRitdxyq~4qpVuhmpj98ZO#YLdp

&l(cQN_Em~jMD1`z)pQ3#sf)*} z^U%r4wVop4+Ltxu&bD%8q-gn%KWNTsspa{-)v=LF+A>SGUouK_?WgUa(f@fy{ptrO zb=`b@H)pw0YM!e;J>wJ+kgDZxUaP^cx+jn8_ph%sx^t+Mk!1C`LfeAtt8Lq8)i6`n zS=)xs&(Wo0iY!$G0000000000a0c>RFl02nOiw*@y}Ct*Dll&ip*#muv})n!n)t8R zG;@nnXX$w71U-0Vyy_GfP?WlEmR`Phl%{TWDvY|9>B;AY>Vn4h+;MIPleFmLXLa+7 z^9uw$9d!5G59``ip-x#!PnxecKMv5fcgAZM!4Lod00000008h3o(+cbg)56@QHgg=o5Yxv20000000000lowBAGYkU)0000000000@Dc<=00000000000A7M% z2mk;8000000KiKS3;_TD00000004Lif*}9^000000000lK`;aW000000002sB?yK9 z0000000000yad4z0000000000fR`W`0ssI2000000Pqst7Yx;Dt*)2fppiopoeBT| z000000002*;F)0P-ZwSp`)@S;pZ%PQQ%J`_8hQ6M>f5EMe0-cb0000000000059N~ zU??y^0YL$B1qV140000000000065uLV+a5M00000006*C5DWnT000000001ZiIW@* z)mHqVhxOL`AL_gBztmSBzNrZh_EWTT)`%N;uO_@RQ4{WHt-?zJU8ASoo2aSpJgm!{ z8q;6z8z0qMAO6SgZ~8mWYvkpPopU8~P34As`4e@)PYVLcQV8LB`9$W^DY;``pIx8A=`9h{J6MNoY;kBL-# z#|so+Fu1v~oz86-srr5?nr2s6{k{CA9=)!!;%WydH914B#;w)&?g^SYvR#o-ses=C z6cy80^*eUfKk5{0Ra@6}X{V?LE@k{a*Qo;l000000000ueVx2u=(>>_nh+xWwN$U& z)LWgp_fn@TAJ?o+8475SsQVsk?{4?*TrJ;k?dvpFuL8l%(1gYcREAc}d(B;8`fFWT z+WMVFUvr`QU3sZ`+&ERM(*jig{2O#{-IAiA_w>cG6l));_#p*GCDj?E=8@JuWsQFL z%3TEj000000001RIy-5>&>dZxDZtvV{`NWj`}bUT<<{>s>V==R%_@esxLCdI%Bh;Q zVz;%ARP({L?HV(UTzmuT>a?|5@|L^C-Cu@QeEWoEZE@FW)vKDe%(~XEvEm0gb;bDn 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 z{FfVbC?Hw`p1EBScIAAHz4JwVwKi3Oq0#D?XhM_@Y98)V>W01ONa4000000N^DEh5!Hn00000006uM!4Lod00000004lOAQ%Dw00000 z0002+624Nda|i$c00000004k9SFU_>bDU!U0000000000oUu$M+c^RUteEkXJ;!rD@(a{K*R_l0000000000fYVp5o%xvT$7Dbz3o@CI zPpdZVbFEW$T9lpbj2yCS97T@po&B%HDFXli00000005vI`S{4kIW))iu9#yJ2>JQ> z+k10`PpkO$x$b<&`p5pCJ3%s)1p}ah$9amY0{{R3000000DuQLp^uS&R6;1^V;%cA zVUTmuK5Yj>CgI78ew>uZNqvqCgo>7p!$(I!cmx0d00000002<30>Mv7@>>)Pgp5LE z>tE-D35e{yQ|HqvzC*5wcHFs*JG?0|95Rj)5FJbDTLJ(800000004LoC&xLq5&|J3 z|BO?W7YdmQ_o20K9p53>88YMqH_pEW10VZbfwECFAS%gm1V;b>0000000019tw7LI zocyyz+PQO|!*zv1A@|Nc@@d_+!x3u?s0owJV2p$Udea^ntf+z0u`&aO}#lz&)ENK{&kA_4#a00000001y4 zj&pE)+*jp=K5mCYAh$xHkb7r>p>yr%$N8V7$Z;OKu0YwyFCMNh(gFYg000000000d zs6ftRDNrhOXc8Z%U^^TH9dYaqh74=x{-68U*%c}q`KJohm829+000000000008l`o zkf(S~+*71&!4SwO917Y0wrP8Afr;JnQy=H3gmRt&#lJDJ8~^|S0000000536KL|1k z9J(DX5Hj2c_8LQN5<2GQ=H%K1YiI4<0Z(>zwsNd3Hz&s(`ndmRasOMQW1I#600000 z0000$DdmSd#c={3=N}&*A8YfKub-d%tj(?Ps3D&==XARkQ(zpC zTY<83=-!b80RR91000000F)0q;VBUI2Ol2wZAtEFI000000001hBNdKyN^nG@Bd;<8f!y2&CK&RsP{BSneB#gP zXy-LqnVHJY&MGDdI$TkFNK_Q@4*&oF0000006^I}dTew!3^EG0HRlS3Lq756bjr=n z%2HNVrd2#v2voSTbU_gS0000000000%F@xoAfxDc6AYPv$S1yCr`(J~hm>0=WIHUs z#%h?GlPjMVtrBtzO(W+Vmws>%!-xO?000000000yuW;ydH2E#v$&t-WC+E|=W!sXI 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 zZQQh3o40J!fddCTK6v7!2SWe=000000000eAMSBW00000000000660j3;_TD00000 z004Lif*}9^000000000lK`;aW000000002sC46&pbDcu~0000000000oT*GEt<8 literal 0 KcmV+b0RR6000031 diff --git a/docs/guides/interactions/application-commands/slash-commands/images/feedback2.png b/docs/guides/interactions/application-commands/slash-commands/images/feedback2.png new file mode 100644 index 0000000000000000000000000000000000000000..3e75c87dbfb2f4e9faaebb498315f52207d29f4b GIT binary patch literal 28118 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 literal 0 KcmV+b0RR6000031 diff --git a/docs/guides/interactions/application-commands/slash-commands/images/listroles1.png b/docs/guides/interactions/application-commands/slash-commands/images/listroles1.png new file mode 100644 index 0000000000000000000000000000000000000000..43015e203c1b79af5fac7729585540cdd22b28ee GIT binary patch literal 38224 zcmV)Vm9J`vP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>Dl;uf8K~#8N?A-@^ zRpr6Q@&6sUnHRDMkN_cs5vI(rWKYF~ds}x~t+uvW_o}Pb+Ul!ywpLpeTSsd}#X12& zhCl_B71;p-2_qr%=HB-_=bn%R5(r8d=J#z+?s=Z`oac;tFMU7v|31%g4Hz^$q#A<3 zN>x@?TCYK;#uBR4M72vlY;8I|X8m=A3x~+?i(@pYy_EAov7Y_HsZ@k6yTPX}aUspS zw^*+`wa1nSU3HUBnF?y^!zG&T)E-LdgCdl=qC&F^oC;gx9(Tj4>zuXyYt!*@XYJ5A z^O{%cS>-TRvb%&Hk!-u%FWLAZ0 zeN=`->z=W0X-&EAda1&xJT#8v=R}IsFYoj!B|o5>pDf$+Z*=0s82viirP3VhdS5=Y z*L%3s`5icLpml*a$xAN908E9DiMzT7F>+&b^tX!TBPoX_Zy{CDLC`+uv{ z^Bp*pj?Dk9(y)J50000008K-!UzA+cCAZrxkH=%ZdhBwofGAK=p~_%ocxO}Vvj3P8 z00000000000FF5!>%T5L?lGy4$L*0fB0}!)#v0*JRa|7}LWDyA0000000000$F6WV zWUqx}XF(s(Sbk00000000000DiDsq3{%Q zVT>RG0000000000_~CO^1w@P<0ssI2000000B9=QCL9Wp4*>uG00000001-@Zo(k| z000000001hrlrO+hyVZp00000007Vw@C+gV000000001hCdPgS5%~}R000000000$ z)8Qr@0ssI2000000BBm=;e3dc2mk;8000000HCRGo0RR91 z0000005mc72qN+!0000000000fTqJuIs^a!00000007XmxO(>;6so8wm)Glcsv2O_ zPxadq+R0_s3m56x-%QaedqdceEAP{3ox=SUE_hDAnUZ6#oBbZP_N&X<_iomcpP04L zy5_gzl^*U~3!l4RudX!fj=@*zfytS&d#`!yI{OZ&BzK9X{%wlp=7(#>T>FqF)OiZ) zH0`E8=_6}x%(V|%ZR``M7AVoiuin+)rp!};Q{`mf4%yF zR+ZRI00000000000FFBD?=pbM>!}eY`P->SsNq zZ#uX7wtLU)QzP8TR91S7w7XDt@BT#X(|c9lk3Xurem_HbrY^Ok`q&$*<$YQE_|HD` zZ!K}QRoT<#j}Pn4DN9sr)kS9wP`24PxQ{X;t=G*n^vLZG>oMzizq$Q(-Sx~2tqNZ{ z0000000000z){KVynZKztJm2EnJzafK^KC)LSn3)h9F2s?Al- zytT^P8EzA?_ZX->eW%LKYob$>)gyDC=N3h$)I7emWTe#|8DvR6;UPWzu(dpJo6hTP zCb|n~?WgZ*q1m`|hMsw1s+R4vuHWrfdf!R9?2h|%$7REmLQVt#000000001vV(x<; zKD0-LN_I(S|1%JdFUdMm52d9CJ}&V!~^wEW5DwT2pf-}TIFK@?YisnDVn~lKxVv? zKWmb%Ik!i+0RR91000000Kie`J3NCZQwhz@Ymo|brNDx<%Ck!T^xm4-*~}A{r)4uu zuu`H5lMwCw%$@`2*djq@Cbz;}rt^7PyV)xE+o|`2OnVBryyesFXB(B2*_|sQ%6h3s zNzT8u(0=$(qFQ#cs}4%Rtfw^1jF58s)okZHz5i8SIP}^}$%aLG=PwWIvCqt%kfJks z>^rR<0000000000z)`~eT?P=K zwp{Y}y+~J&GE>|+hFz#@#%I6#xJL000000Dz;F%;+Pl7yFD#RJh$PYU9`I>=8p= zyYqEgVvh*2*nL~Q@7;M--`Cxsd+xndeN29|dzt3V3vaHH(y#XMue$m|4G*6}ZTTPT znO7EB=ipWP2|v-9RU?tS&D3t}mAdDF`*g>zuG96ux=X(r5k8lKCA0ULt_}bI00000 z007`<@f`x9G$oqQB@nQK8)Mb7b@uajOt75+d|rC0y}000000000-A6KtF14Ds|az#Y!o#Frh000000002shv++uAOZjY z00000006+T%&qW5Z2$lO00000003wr7(oO8000000002c#1Iex0000000000G%*B3 z000000000008I=55dZ)H0000006-H%Km-5)00000007X$5D)a z0000000000O$-4M0000000000Kodhi1ONa4000000MNt`5CH%H00000001;G1VjJ; z000000000@3;_`U000000000$6GK1*0000000000(8LfB0RR910000005mZKL;wH) z00000002!40TBQI00000002M}LqG%o000000002c#JGC($qof7DijeB;Zz-%+wGCZ z>ygXlwicID3jhEB000000Dwaw6be~OP?dp71%s7N?V)qfBZy2m6yfvPuY^MY00000 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 zaO4pX0RR910000005mZKL;wH)00000002!40TBQI00000002M}LqG%o000000002c z#2j=$6bgl$H2?qr00000001<$#>i&ALqHUC)&Kwi00000007X~8Y7!I=zyp)Q0c4z z0000000000ps_VZHgnJcQ7~Akit=*%mFeaH00000000004n32>)XZcKHCL}b*`Yv1 zg(4y%oGJhS000000002|kR9|0A^-pY000000011@2#5dx000000001*7y=>y00000 z0001hCWe3r0000000000pot+M0ssI2000000BB+ehyVZp00000005d80wMqa00000 z004j{hJXkF000000001>i6I~Y0000000000XkuKw`ecU!6%~qzh;XV7ufoC#?Jf$c zw8W=!Pnv=rkAgeCkvF=fyb(JTTK6$<5SpWb400000007_D5ebMwA!%z~ zsS5HdR8irQ%N1#zNrc=n?G%di%kB0n!rw*}+rLnJ^A_5^Zl)@OK_w)4m6RBv=oqil z6951J00000000iwVIPvSb4R6CEHBlTEg@A@_!M$Qs!~1$U0wwv668zFmM`N}1$`Y9 zD0OMa`jysMHkZrYOgpywv~pFcHszK&y#W9K00000007`%9oB$o^X5veTjx?md7NBo zE|<%1EzK0=OO-daiz4H?DNtOZ@_sDodW;>00000005u?8a*J|zCECA+k%oSWR+a@VnU*j z%PY6rOf~0|t1_riMY&wo=IG26m2F+4^1}5BdcE3R;*vKa!P?DdEwKt(uP#L@WJVHM zT^5&xv~dH^Bmw{c00000003yHMh%F9L21k83Tw?Jg(9rQ9L}YXeOQn68n8p2Kv}u< zXNLl%JLOJDP|3Q*3YC;f zH&IcX6qT%Cz#~t5x_nt3Rr2ls6xq4ER8XXfrSB+crlSi56&)3!-6dPC)g5vNOC@)O zRbL@jC?KD&Qk}E>;RAmMC*1zLuIjZ-Q_sFx&pUNj{Qf0nZ=a?o?t58ZId#>#=$zZH z)8s*2lo21P^1@tw^Z7sZ=zXtinbV@SRGsjX>vZ0@Udl-E%U8Zl%NI`5tABi2@2z*5 z7?(Y$DHA&=(ka$y{rfuomoHfN5dZ)H000000N~&?>IkB3+sp_dkK}U8?ebZRR|^&3PdC*5YtMoR{hj$_z;yA7E8V~MI^UTbXseLV$$Vy`|W2H1%l?eMILM2 zCy)IsBagz*H1ept)J$hrZXfr%=-R!fGFtjo-NTv6?$brt-IMkWhuWU0-~ao0-Ew+= zWwi`PIlkof%0B%@z4+cAba6W~m0nFl^ozf~s0XhcrC#lm}D!kGfj8@{7${M&Z=%ZLAN}7qt5B#SJ~nh zbowPvYh!o^9e>vpJ#uP>qIS&J6F<9F&o4D&f4b<3#~;;|$0sW?_Y+-v(#@I~ZeFh| z{ybgR_KDQS_pi}Ox6Wq-5dZ)H00000;3$x(^X|A(efF8;t3(A~eXPGsnW4OZQ|0J* z?rplNQ;wef%@nP&YZ5i&%Ae`9PSFaKTaA5Qc?*~8Uw?W*v-0f*w#1=V>iSbN6&(mD zAghhLP|N=Fl>YL0fweVS9Y>s@NuzrxEm|sE@SJ`#B`3V2W5kFN%FoZ&k|j&js#PoX z?%i8+zW74hckEDh_5ihQ+tz7Wt+KLGUw*kj>(_^;12dschYlIa>XN01$OtW%zd-Ap zZB=4oqSd!wU$sbXVfX#nr=KYl3aMqQmg?TUyW-=UtEjL@)2Gi+X=$mm*WuM@BZ!KM zs^0CO{p=z8xkI4{do+;G7cY-5Ngh`-d4iR4MMcZkt(i)fE|NWhZ+%#{u5rqib5oUU zy%cVn7A{vA^kbbppqW&>SbzKZUEy%ZSU*h<-t@Zuw_A$rIYHMBsv6{TsjeKGp(y3* zqr0!spY3qSSgz-R z)$XDqyBrr6tIEoNN}PaZ?%a8LEN{N~mbPr!qTRc9+pmThNi<~eAoc9kQ`_?M z_0fkP?Gp~QNlVk1F=G@L>(|1Ci}r*=W;9W^u3c4LTBdDVw>hnkoJI?XN{WL{h++Pk zaL8IrkmL4PMW0-*mhyTMq+mdv*jTwMDx{(v3dN_{vDWMQ z#d>sl_-<62rAOzlvFd#4a{P~-y1GsNWQks#x%KI1Z_+<)tlyB^hF-dduX`H-19Zt&p28Z>y2Vq@c!k=a4RhYwRm zMus9IBkhqxCe+zmQc{u<5)zb~o2&Bj@B?a&>wTP(l9M!l-j|v)XO0RB!(mgEgv10L zcieF*DA=wKKm1T@)~vC^A;a|X$tRy`=I5WQxY)VQBdE~=qVlq!+%A_~E^`R$)g6+@ z6)CSTUX`IpMYijypmlStyCp@sRl38v8S$+pPmElda;^T{S~q2DnYqTi z+4=g)DSxyeWFKiv>m>VV+cv$tciLlnX=Scmo!rX$v|A0+vQq7!sc`f1JyXurv~gap zwplwwCbd_#Q@5{6HMzfuKXWzn1?#u~000000000S0}4Kzr!@hu(!2F`YU+@gsdh0X z%2~3c_RsE%)+$earFAyHvS%u@ZL~^OFVT|lR>vZ($qUJ!nzhgKjH(2KmG4jH)WtYv8QyL-dSgomdhU=bq+ulVP)_F;V+PX4a-pA%b zJDH61n?Oezlj3&*rC03oKBjI9Cz*b~vh0d|rQI~r9^s_rv-HFl;W_{S000000054D zc`7WCqMPk!k~w#T^(UZ;-Qi-jTon{r`}$MtQiR)TRle`sak-{ZtY0mi^4`ode&)=X ztBo5r*we+$obkCn|9qB8N{aREx8K^&0ot&!_7D#f>?9{8D|hoI?c8Z*4wG86Xd!Qe zS7~W!I&sno8a-yT{W@agaA*2DpOO+2W%8hrBS&iN*s=C<%E>3$Vbjs3(E=hfkjI2K z;fdKoW+FIwnx`nXOD{#9aiY|_nSvFi3RPN5lv|+~uXQ#-#q~&+w?n#0@~l4Gt>qaz zR8h_S^0#lUK&ey_+AU9uEV-MfD&(sgL1dWYe76+Ngg7e9)i-;lZkwPE<^e=Ia`lfr z{U0f(=;pB|Ix5uMcOI#86cPXc00000007v(xVSi#m6zM2hD?~_a(U{V>a9(iHgbF1 zTEDLPnJ*rXM;^CZd0X@J?$r0}XA~{`a-rgz$E$0%uJ#C}s!>Yw=FPLWPo4U%cI@1t ztS(u0=yWt`w1CL#2`U&0NI^5rTu_y+GPx_Pn;rC7H#Jf5?V_xs2NY7d?Bu6BAh&hX zO4saAXuC`9vS?MV+^Vt}D^(eYRQX1?JiZDwkKLu#E#@k%#d5W8SEP37l}c&pQcR4; zPUY+w?G8$+y0-F5JM#7Mo-nA3lH*+}+`7goS1T+tfBkCK$}aB9>%2O)mXzDQ`r{{r zEB4klQa)$3PFJ3y0Vb?kKTCgpae>+6^fSM z{1#qIx)QB@OA7O>^%9koSC1yDDLvV0w7XDSobui}cFfetC!eIYZQI(Tg3S83abxB8 z$Ei=B-Wok>q&?kSm6(_qyY2Sv+m)Z6@6<|ems@3JpUQp=Vub?E3-+w@3zJPjO0biosvv0#oCEqF^Ow6)4@ zC+M9oP1!6LsH<1<54cVXP2I;&>Ktc|z;CwXhXbFKx}T_(mRbGGA>+OE+w50=LVNpl zAET4h%yhjaS1*NYYDm>BqkGECV7Tn_r!|*+2mk;800000aLh>5kh2FXO}Se5X|7XO zhuj=(D)K6=%Ya&AFuX%Et$ho$=6_~dxm>N-8uxF~O;+ z&Ga9rs8B*ef<4;kXxC@~kr~lr=9mk*0;=?R6`9ac6~2&su~z-IZx!t;S7>*If|XV| z7?ig~oJuN!^27uc@VXQ?tc@bmQ)D$$LiuY-Z9YRjcYSYfShHr{h6hADaJsQ`}Q4n z*b@i@tk<>r`kQZ5xT{blC8b)rbg5RZSZRklh6#4sv`N#hUAvTVYtq(oU= zyC~6q){sjbJ9U)b@7I>iTkNUpHg4FY=FOX{dynq+b~8$;L&r?}SX;Mlb$U8FG+ICu z>o*ZzNG=5w728UIlAWs9wN-)8R#j|Trm{${yov3sHc<+!TA~;gDj3LD>83RbxCcDo=YU%^J`0R5u`QP8F-1^V*x6je(rfe+_{%@;w=4aF%(7|Qfx!o?6 zloZ=#e>1;|ckiw>Lda;{y0snnY}l~T4t#1dBZ%gI`K1(cY1F8ZI{oy?>e;=!)~{c$ z&*%{tq#InQ?%&RNPYCv$@B_Vs+tTWx)ueW~g-qNmb)R00000007_^7oDg?6{w)ZDeukNo|X`$io!hQ71sBgf3y-)6BHdN)Rw$L zH8dG8BYKRzL!rGjq+Lu*tRf;j+P%Bj9+|W^uh*;C*ceq*RI0dmw^Ms`X|xeUMsf?E zbt40++?A^cw_846(0Zd>@}+l{x;0m1?^f20^C-g8LM6+0sbuzIMKw#1+GMD3O|c@s zeBJ3OtW{b&We*!`znXB!h>nRUQMo zBfGq>jW1rSE1#OB4aI(?4H%^pCQZ-@L)*(=nX9=k-l?l=hC{~m2lTUhUe~f6J|%Y> z5^g`Pr_wxyT0QkK{cP`W$kOYQe#-DjWs5)2L+c5L0000000000KL92B35N`8`^Mbd z{e(kCi3+SX8xJBJGECrepm4}&NIMhy?A*Dl&Tz;u^=A9g8V(taI)W$|3~ALj1*)iU zsWKu#{^Viuo^Y1jfuI6uiE^hVOS?lVe*QyMZk!=^T#^)*ri63HN#$GA@}nE9Uczre zgwLmu6Hl|&!pYsH^{Z7|JYr`T|*${yEK zNj__}JYQe^cm4tU$QnLD8Gf^7!l7AOX4lrIi?X^c*RrpjA^-pY000000Kg&8r~#3& zV|$4^oOSL%dN|h#OC_2Wc?W;DcA`q*{V5PKvor-ei%3VsV#AUawJV^3GY>F3H5;gxH1 zj?+U}R8)-pT2#2RRybs=%UR*9NiL7aSpxt700000002N;8a*H~Qq!6#G0`hk7N~sv zV(otWP3$XR zBBM>a=1NY9RYfRY6=jQ5I`ywAUGk-xWh5%9>ri<+4wpBjp8{)&<*L}E%20U3P?gfs zV*54D-(0QI+S%(>+GTcj)}&BnrLzVA000000001hx_DZ)PP;o849asDre=%t`xF@^ z740rmVCN>4EnlpiU#yTP6n+L$pfIGejZ0O&=3lDV5mZ9lR%cHs5U5c5j#<{4OD$8| zDkdgY{^kkl(51Utwr*p$*}8e7Hf>mIuLA%800000005wFu3mkzLxGBLKy$c+LLudE z-K~Ox-Kr=HD&+FZ8}cjQ2}!|_+^#ZtT`_8t=~7H|^@t!da;Rs&!A_~R!d*MHXwGLU zEh%wo000000000008p1B5)jo?xT{1(MWre!u25M?K!HG|TrQVfZkH00qLrGK=(G%L zo{*$=9XhK;%hvLEBdq<3w0Yw?tzPksDk}p{D*ylh00000007kIhzCRf0000000000 zIK14>D*ylh00000005c@0wMqa00000004j{hJXkF000000001>i6I~Y0000000000 zXkrM600000000000Gb#AA^-pY000000DvZjfCvBp00000005wgaZ4^|6#xJL00000 z002z^BZvS1000000001*7y=>y000000001hCWe3r0000000000pot+M0ssI200000 z0BB;|E@uS*0000000000O+$?lL?P!D000000000006$zIxh>(000000001h zA25>%x#hC{csvRQNr(Ud000000001f=u9SL4-^EXZU+Zkajetp9qwKJ!2#RaRE2 zG7wP64vH921ONa4000000C4OI*_n^YeoO{rvLKTQx%&1W60$bgS&(@(=}~2+GmihcT6a02kh9U=?CLvUh@JX4f5NX8 z6AXn{!hf8S*${rQ|61w_iuQ*P2><{9000000HA&~0-*ZrX;Cv6GHR8rzs?2|6xpv% zU5$XquvYCAr&M1^Wb7v>I*^991ONa4000000BAa#JmhzUw`TBTZ>v!@_6~~ba}WU%0000000000K$BJ@@Ttq5t$Vc#=RehT zwZftBi@jDI5Lv<*PPkYz2--U&s=d|GfB@*gEdT%j00000005e{>Ud{A>I#O!>CYb3 z;gG4W+Geep)qVqpR*wM+|99VY=cQIGv}d)Bu&ALJRRjP400000003ap9B1X=g!ifn zf5NK6ps-ruQ20fze%V9qgeM%;gjecZuVUBLC>wi<)%AO|00000000000Kj)tBk!@) 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 VTmS$707*qoM6N<$f&l;k|Nm@Bx7q*z literal 0 KcmV+b0RR6000031 diff --git a/docs/guides/interactions/application-commands/slash-commands/images/oauth.png b/docs/guides/interactions/application-commands/slash-commands/images/oauth.png new file mode 100644 index 0000000000000000000000000000000000000000..e0f8224a82cbc6e3463f2cb825d78d5a51141bf8 GIT binary patch literal 113764 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 IM6N<$f>tr}!~g&Q literal 0 KcmV+b0RR6000031 diff --git a/docs/guides/interactions/application-commands/slash-commands/images/settings1.png b/docs/guides/interactions/application-commands/slash-commands/images/settings1.png new file mode 100644 index 0000000000000000000000000000000000000000..0eb4d711ab044287eb2d5fe817128f05c4bcf89e GIT binary patch literal 76171 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 z0001h2-c-*1_1y700000001Do2!j9s000000000Y7-0|q000000000$1S1Rr00000 z00000h+u?4000000000001=EZ2mk;80000003d=91_1y700000002ZV!XN+u00000 z004jpMi>MD000000000G!3cu@0000000000A{b#10000000000Km>~xiL#pj00000 z00000h%{6N0RR910000007NjtAOHXW000000DuTa7z6+S00000000od2!j9s00000 z0000YSag)#000000000006^q%R0cKLuK)l500000004*-(H7HaY%nhX0000000000 zh#=7tWqpZ>k%k7+AOHXW000000Dwqi%7UV!W2B+6K^hyFDh>bu00000001D8m@p_N zI$EM*qNT2(jtU_F000000001h2ofziPn`8HHa1Qd5R&@(da19glSUH?Q7Hrf00000 z0001R>1vdyQDY`HHfrc&{xzD)p!#~dLdbK(BcN?w%vX!O4*&oF000000Dw*u6{R&l z9#-4D1hnliNHyA-j`hX-v;J#nu=-jLgjBFT00000000000EB@Z^eA7er69;^M~xag z(IhoJfmSO}zF9o-rPnb*-{CpFdfc>XnS+b@0* zkgpje7-}!7g#Z8m00000001<(u^Sx|Gz1FVA3bi8Npn20jHeN_?PdBL`^vZ5)E_Vc z000000000002j!S?O6PhAD_Sw$nMcFsK^uVI0GHeKTq3!@!MCvLyo?dv||VW00000 z000009Q=Zv;5>1UX*k%u-b0=+$kOPHb$m&W-D+Xq%dy+8a0mbZ000000000T<_mw6 zUjU^1Lm=^5*-yQQ87{0YqaUpLv4mWtOo=D00000 z0000G8m+7;6j&Y9*w`oy^$pU{SSR&$^%6B^{A8=I(Q^^=YK>}Wlo(g6#KyX;Ztt1S zmw=$h+NB~00000000000pmPKUKpu{|A#c3X*l34AS_{;uwLn&f^`Fb-l2~i=j57Z` z4KKUd)Z;kT9vvJG0000000000@K?bv+-Z;I0g#8?Z+$Ugkm<7%8kZ|xVqLM0S>Bos z`?>wqzeE5000000000NC}>U;5CHkU`ud|Z44TsDogOYWHbz`=3DyBe?Bkc} z*gO6&7QaCN0000000000=oG;!gM1CMN>J3;3A$#G?ujHMCR#7io}-2%?rE0yI68u4 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 literal 0 KcmV+b0RR6000031 diff --git a/docs/guides/interactions/application-commands/slash-commands/images/settings3.png b/docs/guides/interactions/application-commands/slash-commands/images/settings3.png new file mode 100644 index 0000000000000000000000000000000000000000..48511081495773b3b39a7478ee64c404cd142da5 GIT binary patch literal 52771 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�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 G0RR8~MWa*z literal 0 KcmV+b0RR6000031 diff --git a/docs/guides/interactions/application-commands/slash-commands/images/slashcommand1.png b/docs/guides/interactions/application-commands/slash-commands/images/slashcommand1.png new file mode 100644 index 0000000000000000000000000000000000000000..0c4e0aec70b552b3323fc76d35b239c110069f24 GIT binary patch literal 10886 zcmV;1DtXn3P)004vt1^@s6_PkPx#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>DDjP{eK~#8N?VSr; z6jl4jf5;+&F32UoB|wA(6A%qlNHNjGUVJq&6{WNbm8Dj%R^|;+GcT!?>8q))CTVJB zq;I5{=Bt=D{F{jun1q0!5|?0=6?W18IkU5}T=oL)qS^EHvohy0XU?26^PA_~W-NM! z^}7XvAP7Pyt;D|sK@fxvNGJ({APhi4Ne~2K01`@qAP57HP!a?|7{DU5cfVT}7Qn4r z%)$P0YHDh5ll^Ypy2*YtC{7T>-C>Z%)1DXVewCKpN)NrE7VR=2?Z zF+o%^apVF?;z$q#QI}TCmSh5{6q?!uK@dbkTGgmjtjVnsodiJ;M15M3ND>4==%*F+ zNfHD>=!fP}5(GgQfP|7D2*Lm)lmtN#1|Xp%2!b#G2_-=ggaJq>34$ODKtf3n1YrOY z%DaGnL=@uRnSqIeWug!9*T)ZiqMygKX>sT&`VjXI7Tvn{yz4X0ZQBLHR@x11Y#)NP zjRz!FPOxZYBZS?$senpZf+|HmZdCq;O8L*Iyp|z^-P_18abptV{p8rc_$f^Jniq38 zrfr#vQGQZ&lnc8MJaRsIzPb{#hdLv3>tsyXSfCCg{$k8P@~NTlmY>A3eiM)+`V#jC zcg>=-vX;T+!I$70v;|)NAL0S`=OA@>6s_BMqLpP^SXi_Yetetz@h~2T$L0I*eR&%9 zEOFB@Do~25oCEkUS$r17#^E1aB;{F1UXg$WJ2Mc(yq|bH1n#aK(KGxpbv#orEeXet z9Y@-yaiRx75Y4581_IFWt;8#ixOM=%A}*063ZZW9D?N3^I= zws(F4J^`P>!DXoEbLa51c$z%zdxf5fZ(xAC1&R-Cz?4IPp}viR?H}OfSKh@s7MCE1 zdyf|DqvV#KDFw1I%8GX3O5SqOb(ioMkEAWnvnF*0){TzGihQ+q{L*cR_H}}{2Wxu& zS%{qCYRJ#5$JqB$@x;t^csWRl-@lK=l(oD%qA+{?3kWXTgQuo!fM4tkOdb*foO=o{ zFW}*R7&Y^C#Ps%qcY9``7L}l|;3W1XZAMahA>x;8MRZqpc)K!_vfi^xF8gN zKMHT{iNb_8N8pL>ez2=5#LxTIW5Lo?6g>PiX1_5MJs>Z80b zX%V6H@STfx?ZZWvdAauI5#(P;5M3?L*wx=-a?du%+#HM8rL_Yyv%fx$n2w@DL)K;l zk6wv9;|J7q-u5!Q}I;d5w#T^w-Se@guo&{3l*+R zs92P$Y9YHq+|2YGxcBgbl;yqFzXXR9|AC3S__n4T3Xv&yhQEwOV8=siAtI`>P9jGZ z0)L)aE&t*}^YQqWCv)Y(QqVOcPC5~kx$@?&$N8g8; zNM1dFiM{)xS0DBZjls(we}(BxUey}jL*K@desx7!>cOnY zyi_cBrWZ>u2Gf2hX1=ZAAL@syjLn!fOzpoeg9#a!)%_^5p0$z6m>3pf_u{eW&hivJ z{Q&-8S+VwGB3Ye*r0Fq2UZdBZVWI^%`-b11qOxWVAC*$C7-dGIzg69JhMlNULDKdI*gSd-J8 zi$>u^B#Hg68ct*s3IgQp`H0#>gF;^K9Mn`_FEP>zlcS=sfvy@kitFW}G5m*)$I|u5 zNIP~Mr%xqe#ke4Kv$+9Ps3=y8t}c4I@v<^dM#O!GS%i{L*Kze=&6>Lm&x6FuQFNJ; zILwX>gmrZmw$1v~giWw&+-M*Kg-9Ad4%5~iz?nihs%%+PH8cwG?{7oOQp0m5_y6a8 zOq-QZ=a;zdfI3j3{sVnq2N5kR)KvW?nwl~5zsAPI=P@AA8C7{Fv487oOxu_t2zqmH zl!~2^^aYi_6=o4i=MK*b6UCc?gx#d=+N!@VULWq)*46?N%}r_~JP*zto)KNRpT-G7!B&$70U}gUdffmu|TDwYFl{vaA)xjW5xU%6V>|gCb(Hw@bsR6$&v0r$q}rZ zDvtJ=m2f;!ry%2rsm978aDD?nI{*TDvI@sP1 z1IEd;#zI1n5U%c9%sKa8;qP51LI!7+!K#ZVu}tV6Rnx~$*6eaRQJ@}(@{4#~=+9Ln z!xQs@*#E@+ipho2;elxOzkUVd0{2Ka3Er7xgL%_zG3c?@=-}E4ZZ4LX{HhJ!eZv;B z-?Bvl`@Q?7E#7>^MhKI+wZfqOt%ZH~zCD5@#)+knCvzaf#8(HSlcXAFzWxBoqOV!$ zJron>CnN3KbyzX)ZN$fp$LvqG;c-44sH9L>=_7EJHOtu_X}35eEt-o36OHIEUb-5| zIczUke=OOX#L{Q`&HE5Z-|oZKd2viUd10qw!KRbUs+6MVtLw0F5zE61Z)3%m3)NGS z@>8+$gL|Y=p3SUZru^2vgON1zdBnf75Np2PhZRHEgRPmISp7d_GNaPkFAiI_eaOm( zrFvc1W`$=GJZ2?Qxq~`woZ)YD%bm?q~ zP8}`b!+u>JvV@s9OV-EU0vT00@=@hE16)=rtx%@NFfb2)Zj zki*)EzYT}&7afiunH2JzEaZsxa>2$4n7%O`1>kA3JWPBVeSBG*3rs{OCLqoHX0>q~ zOTd5j6rhTi!BgWgF}6Q?wlBf%1)FiEQjF77@|R=E(gVn0`%8V9bsx>r^Z+uqC$NW% z7>Bs;FzJMI{uUtq(h7aFV$wYPpRF-8%3A1`yc1L^U{`7tcKo**H*bobI>^oZm&xBc zwB*0XFwp%n_W$cCOgY%n>L5eU#{=Qi8mKPHxZTg#fQQ-5?9GKP6^FH&p9b*=!GItq z*2t72C*_FN^qRWxSH^iut5I_108SV&0)1By&jtF*sTruxU4Quy_Xj2wNHm(QB<=m% zt3U{XxIZyz)4PqWuejAXmh5Ozkyfy=xpR@^@KkJBT2cM2gtYa+-*stfc7MMENlQs2 z3F3aoq(aGGx~q>iZ7k5$zwrSWE;jlEM^`vcE1-)+NLwH3yqQLzHB5i^g9ekX34*x) zFsV@THnfGzp7AKS+@zzx34q5j9*kZm&L+aT-IxGt(3dxYAP9Y!R46r{Z)l8GOvJi%wM1*Hx@uNkeG8v$xq$^AD}>+1Z8uP( zH88`slbGMa0tgYSAUSwc=~=_G|^qM*KDC%Ns!ugg#6vl>BRny0B?ufi~6+m8`8T z5Ed#CmMU6X1ATi-VBNYlTYS5Z;HO^F*gHf5TeeSqg!J_c=;+HEK@fyKOgfM9L7zlO z9r}o_y0|!72{DNOdD`6a)P=sh5d=Z#!_uvTd!o2~$0)0dgtv-YHu5e?zAiiOQjPE;4Bl7C ztwR3eYqa+tQVQ#}uV23@G|jnHS;Ly)d><~bm#^N$;bTf{`MO$YV)HaKmM0g?vhtf) zz2OEE(VLB0uFC2^a5ewmqKhC1qG_1?D~v7=z62+?2CsA#;C+nzb!z?lSRuTp6{O;e zdb#*5__z`m^KS~H-drr(SaTt@fP#hb@S7}`d}Mp->$bwcjbM#lzl9?wl=$CaB?_6C za&c{pk}KO#bZM>VA_#(L8YZd z!zWZYbW|k>9dAPOPnYt!l6*xD-{yYY^8B;>mhfUrJNL}opDk^0qajzk%o426hc-iAL)}&+$p$!NchBpF= z@bMQ%MGs?;A>yS7sMhMJccWlwJQoeSw6*wFN7nwf)Tt9G{QHK%<&UE{lxZG=$fScg z8j3G}&Mdg=qDwbi==j=gV?~m4;|ANO-gg1+|UK?9UP%cPQ8Y`inpddgQk7?JPglHos0>?JVp0CfFu&5hxv&)|hYKBCLS)X$rfD|=Xp+l}Gm4w++9m%!S<-A#AB8wX3n!v#{a z+@*hUSe>}vt`j-jyG5d>nJyQOS*k##EW_3O)uQX}!SkR}mWnRZQ*dw-)_l5F{o9?* ztlVs*XxyJ}K&lZ|K<>^}Sh8qS6RnP;_rA&r8 z#)(Q^ui4F;g)$+Yi&Xb3;1y-{n%y>z!anXYW2_j>F|^|HE`a*SWPZ|Sn?x$e8*O&; zV{09^N%^jam}hrp4yto)0~@0f(UmuCY~WZYZCw~wUuwiO*{^|tYN#BQto#m?cJdLdr(_@xCN^LxR=b&BtX12eY$+bU^Apb&w z=xQMzip8v0V9UI%s5|XG5)&2$L-7-9X8Qr9HmKdKVziF&JJbLxnZFXmJ+b9+#ruV=kyDQm_lr5k)DBurf`KfM;f71a=^C z(^+vczl0t4J#^1mj3p^rmb%X{y!Cu2Tv{^;QYdlt*eZOyTZ+-MqEIyN-`LNh_{L60 zRPhcJ1jnLh`$Ftruoefs8qc@m3$roA*M=QU1qEvnWNccdE-Ni04-dneqXMA#H5+m_ zANaIYLM5?*;>;#2N%mr8?vKo=>SJ?!V`pGAwqo8^Zsz;c$(v9vuBmuW2xGTnKrG&f z4S-c~E-EB0aCKxiX5n7EBh-;L`s%ChF>Lz3gqZ`pAZKZo!UY{&lsL6vv2b3x>yM`q z3u$%{Tpw;&KMj!h;mOxo`P!7>Vlm+A#v{>|z zl;UcU3RW@~NDEW3P?N=g*pGiY$ld- znOHWh7p}S76_GTEBc#{a1v`f|&zZ}A%nXcp#0U7TUfbNKKf;}|1hG|%oCFey;nAA# z$3oc+I+NX?Vq6K^PrcOuX(um~u9=UdJRUa~W9P)8pKJ$K&P-=FFbj{Kj9J}6VUhL& z&i?N#hP)VzuIvUl%XgMUz~3z!M^@MC&dA=0B^zxplRYT5-hNOt?DX@Bd>Tr2b0(w} zF$n}B9t&alp4A}Vs`YbFR7cqABgNpsp{UrKh&4a*eI*EgB@v-{i(jG!d0>tpC+b* z=?f$SU&nk_KVbD!pMY29JaU9|0sa}N<5IYLpi+5sP(rKaW;S`N2Z$E>m zv&)fM&OK!4;=rzh*;>~@Sa;;IYh||PT1kvX=tIB-@uAh=T1sqqEzP{0*G!~*l!&hs znsa0Z#QZ??M4I?8W5;*f(fE1tIvLThP8Ms`NkM55+{;9V8EBDiVlG@Q(G2Iuxc5Yo zQ;4(wRf{A?b(390&W+s6xrMmOp8Nx1!VuuV8!{?k(!C+F3rgY0EVZZ}o)B6rY$u(& zU13js;lk#~p=3?}3rE!#w%}8FL)>Cq+KaPte}sprozlkx;GTaLCydBbV<<9HutpR~ zPIf_=@%gTU3)YCRl_zMT+r~ND}D&{Rw46*MkG0<$j##q4jxv6%#)%>a!OKhy08QM zLp74z$aTQ3l@#r3rT#qS2H|?HC3a*57fIH%4hS2BsDJvvu}*q=&Xd)NZ@K&=8VzqMJWZaqxuyZN7^8={>%G~mi4w|e2~!})!C#Bu6%@vAafJIhAoKW9t(&-MsH*EW7n`5& zY#cqSgg+CU0GSNFVX+wEC&!8Xjai>f!?%M2EUK9Zi*CI%p}u%m!CP|@@#a$j>>Nvx zkzH&4n5O>R;V6}(=)CBx0}E?cKTL((B020DdtjRdM})sH1Mk249KyYONfDf)@pMB1x)+JAir+Kdp+%y*zxA@jC!|drZ@lm=B28@nom$@Mn zxNvYQRxX%{cjoLs*+awdv}gt1De~{rk?R_Oo=#!#muBPCX)(YgG(X=3Dc>Q}CK!YN z8I1_9QtVmvZ={;wO?nqg<+#P}3d4^lHOAGK4@L|RMp1Gi=6$pY-|Rh#v)8mAH>Rk+ zTxL~E;nG=j)`5jxs~;wTmx~k?#kr!}yh!@T;>q@>v1Il#tl64^L#GO%=hX~#xLA5k zP%3?Oa`CG*LVvxvXi=f$@To)G^1F~RR#;ld*I_pmz7OA*r@_G{5L`6-N?g!AfFFm4-9lhw|RxG6umw&_(aBH!t*vz*08-St$@;F*wU0mBzjTd$k8lBJtITL=0efW6f~Ca zqqp;Y;r}vG{rt0de@+E5b}Yu0GirY|j_4WT1LT~ZTm0PMn3jvB}Iv*Vj?( zKN*IxQ)a-w6p-4;k&(qLD#1)KUHx77YAyDReF<;Qjet_g@@@%SJ-!J)v8UUGuQp-- zi_hY%_d-zmr;Q-or?#%beqkWT{9M?73`MUG!>qUZLLp^>mU|MpWp-ky0a%@^H&7@3 z;gJY0&Nkc19J5akBANl8OV&ogEDIw$aRm5$HfArdr?$zLH3mI{S&>Ef=%c-6=qJ*b zZx$xCf?Qs&9Kv40{IEhST$ZAhA4d$C{0_X*-^I!_wGW@O$NK|?3GhvZi3u~!DvIQW z`(%x+FTL?hSbzLDQYEf7mUn$&X6H`Nu?clzVp_cEO&Ecqw230g;ghIMERw>+;Q%~2 z&FVx>iyIJ7ej7w)A@688-xlmtN#2B4-lK@fxiNGJ({APhi4Ne~2K01`@q zAP57HP!a?|7=VP5APB+$B$Na}5C$NjBnW~q00|{Q5QG6pC<%ff3_wCj5CmZWi^m2= z(^_wWAP7B>MM)3@VE__Jf*=S3kWdl?K^TC9k{}4e03?(IK@bKYp(F@`FaQZ9K@fxi zNGJ({APhi4Ne~2K01`@qAP57HP!a?|7=VP5APB+$B$Na}5C$NjBnW~qz+HXKny04+ ztgWr#>f$1}uND=d>c$P^yQ8!3Luk{+TJ){UjT==sf8jSA zPCJJ3igMAHAPC~_&|*T#MRCA@#}E_{Ao>``nX_lHfB*jk@gxX>xEr*HP;x7B_)|k+ zZ*MR9G!5P)Pf1A?{t^U1v@~WP%;6$AX7tErizG*rhNds`p2XG-K3zJ)-`5Scq8IU3 z#|9mI!_cc~jUeJa#H>Qeo7>!y)YTW+6nGlZ(NQKz>&~!2-wE@vYR(w+5Pi@o1_=vi zVA`9L@YbtO;t}p=8-{UjPsX@#8zBnuSEMUOzB&d`T|_7Gx5cbNIWV%nK})i#s!AB; z=0Coz4{lNNH18@wBM}{Bh3vFsoD#iy40;5f$U*YElMp{;HPX0ee`bmJdZTyW2v`WQ z?wlU+v$1MnJbH<4f*@K3vk4^^wZOoxqDwbdi~hjoFSZFkzHJDeroCPBoAGysH{zb` z2K(Hj*t}06`T@R9c0f@MQp!XRPRc6G`(PR7ev%4Pbpl&i+rr990+T5(K@dh^HlYj+ zx!o`|Ieq(tiCZm;ZFCGCb+5p&t;x77`sqf2%NKHRxl(iz1o4-}vSX*tiQ=|N84^7h z5(%?@3{YNCfsBlE!gf$#0Bfo<>&yVWIe+Z<3DMO;*oNV?7bDR2+;%MfzEJe+ib1iD zp|e*gYH^gE6pgAM|^~7q0D8xWv+`64MIs^g@Sr%%HS)L+1cD;A{?zo(JF%g6L<4 zWAxzu=-0Owf<3HIo}G(gr5Mh!bwl5w&mb;l03ul)LOWTa^tXC(WMPPj?hGq->}!4z z7#TYpF^@;E{K{}6>lYN<;K{H!<3?h5J%2U)TzWxPdLDTatvvPaf_5dp;LZ;SnSnFfp2t4Xm%F4SH+a8XwvHxIY%g)`Y z5*e9Sp?z8c+GW%`5FH&XA@%l#)ur7y_=^~a_*-HYq2w!Wx(9a?UHWN`P;vq0eVXOv z6{3&%2^{w#hPqzDu8+P!_U${Y3nGUG;Xyk)*s^DWrL`1}?UcBbeja7$gSd&0;X!%r zP1bd2JYpXP&bbal!qeT*L83%)ei}~Y+93Mvd5G)f3}uA^H(6XO2QLJNc%$`!G-N2n z!_;H_`BmOJ5GU+056w5c(9E>V#W?YXNdxpHC$Xf{}rD_EMA?cmiT z5D(h0YxH*#%D+2inMc{VvoD6n3=>_o@WGn;{g8ZaZt#{96W0 ztNZzmxDOu|L+A^V8GQ2ZdSLPsP^(rKb{unq3t44Sr zbHh}8e2iyWT`r#0SCIDkC)k)SmO-a6SU5Ed&dL&;+OAbLvN2eY5C(--IYQPM{)+R+ z)b0}wipP=(52Iw)WGqNgdySm=2?lwvhr|DUg9U32iREa+>T3}Owf#RVnZ6O3+)Kvt zG%XU&+1oMeBTYO`2;Nva4)({ij!pb6F-spM7q`aH9=Or`G$P62+x5eB4e;nXhUsA< zG9nDJvXj`{h)6aWD-|-gF47W74OXys>0EaXrj|(RgE#5pVn@{Z4bsg0dmWC!gObIG( zS;4CQB-X;Cn0s3!ISSBlzj%W#qz#bg8kHcx z`snNF1dFmOI7S0+gbvIil)9`ymj@m|+~|>Ye!RJ@fp6Cj=le8 z$3~&qXf6~;TRj6)K1oJet`hcLA~13i6M%W+(AS7%KF3$SgZNh_)%lsMsaU9BxntZz zk5vBfTCjK$;vR8AoU)KvP_^KK{(4+m+ z?vv!(^}}_o#A%6_Wcb7mLl65)IK1@-To?V!N%`3yu<@h$m@;t^rmQ=PVuuincwVc? z*$m37n3-g4%PdAcl=Eh<1h&lhYycA@I}0T$>o;}p8WDJHLo~!d zS=q(Er_}8VagZ>nZyslgN?3Ep8<8^6L&Vn48_suo#Vm18V73O8IY*?;KiX}BzAp}d z&yAmvyg5hoGb6p@F>m&GjO_d1?Ewb+fD!O_V}fwyyy&B`4xQlJnTejQjd3sEIJg~$ zbNTd_sd(cF&0vuo0tb#k!fefQ$V1qC_!7`567P-+5%a*}L{GwVkJzF3bSjeXnf|*< zD3yS-dta@5j>p1;2*{ebVkz^xpOIJXhCUPH(cg_fP}FHAOo~7UD|H;=?~U1nGX11x zCA8^DJA72!G823~cL2MAQa2kv4d+fM%SUmmP8jseR4o0J4@`W5rR*lP&rQW=U#RDp zax#B7gFK}h`cGbrbsx^bOW|UGUasKFk5(fk*9w78PQr2_{rPxfcvs-J)9UFljLSQg znqR>2tLNLELRxTqw2Cth~{@ z;DRqp!>$D5XAie2_-=ggaJq>34$ODKtf3n1YrOY zN`fE=1CUS>1VI>pgpwc#!T_yq-J)rf1VIq`Xr)wEi<<;N5JY3NQYn?-KY}0#qH%;i z%IfMW5=(+02p!xW%;93mf7EA55Cl<|I-}m)s#H{77X-7WMn$4Y5Cp-n=o!|J)Q}(u cLMOog1K@>J%&iYRCjbBd07*qoM6N<$f)-u>2LJ#7 literal 0 KcmV+b0RR6000031 diff --git a/docs/guides/interactions/application-commands/slash-commands/images/slashcommand2.png b/docs/guides/interactions/application-commands/slash-commands/images/slashcommand2.png new file mode 100644 index 0000000000000000000000000000000000000000..828d8a2cea90d74b729d8ec0810c14f9d6cd052e GIT binary patch literal 18094 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�% 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 x.Value == value).Label}", + MaxValues = 1, + MinValues = 1, + Disabled = true + }; + + menu.AddOption("Meh", "1", "Its not gaming.") + .AddOption("Ish", "2", "Some would say that this is gaming.") + .AddOption("Moderate", "3", "It could pass as gaming") + .AddOption("Confirmed", "4", "We are gaming") + .AddOption("Excellent", "5", "It is renowned as gaming nation wide", new Emoji("🔥")); + + // We use UpdateAsync to update the message and its original content and components. + await arg.UpdateAsync(x => + { + x.Content = $"Thank you {arg.User.Mention} for rating us {value}/5 on the gaming scale"; + x.Components = new ComponentBuilder().WithSelectMenu(menu).Build(); + }); + break; + } +} +``` diff --git a/docs/guides/interactions/message-components/images/image1.png b/docs/guides/interactions/message-components/images/image1.png new file mode 100644 index 0000000000000000000000000000000000000000..a161d8a61f2eaba566050f1f538e06e3667855e5 GIT binary patch literal 17256 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 bVFLJnd>9#5PfhjA00000NkvXXu0mjfUC^Yu literal 0 KcmV+b0RR6000031 diff --git a/docs/guides/interactions/message-components/images/image3.png b/docs/guides/interactions/message-components/images/image3.png new file mode 100644 index 0000000000000000000000000000000000000000..7480e1da90a413f8dfe8e13b66a9a2a8c5dc2bb2 GIT binary patch literal 40520 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 - + diff --git a/samples/02_commands_framework/Modules/PublicModule.cs b/samples/02_commands_framework/Modules/PublicModule.cs index b9263649f..18423f609 100644 --- a/samples/02_commands_framework/Modules/PublicModule.cs +++ b/samples/02_commands_framework/Modules/PublicModule.cs @@ -31,7 +31,7 @@ namespace _02_commands_framework.Modules [Command("userinfo")] public async Task UserInfoAsync(IUser user = null) { - user = user ?? Context.User; + user ??= Context.User; await ReplyAsync(user.ToString()); } diff --git a/samples/02_commands_framework/Program.cs b/samples/02_commands_framework/Program.cs index 67cb87764..8a2f37dce 100644 --- a/samples/02_commands_framework/Program.cs +++ b/samples/02_commands_framework/Program.cs @@ -39,7 +39,7 @@ namespace _02_commands_framework services.GetRequiredService().Log += LogAsync; // Tokens should be considered secret data and never hard-coded. - // We can read from the environment variable to avoid hardcoding. + // We can read from the environment variable to avoid hard coding. await client.LoginAsync(TokenType.Bot, Environment.GetEnvironmentVariable("token")); await client.StartAsync(); diff --git a/samples/03_sharded_client/03_sharded_client.csproj b/samples/03_sharded_client/03_sharded_client.csproj index 24f9942f9..91cacef64 100644 --- a/samples/03_sharded_client/03_sharded_client.csproj +++ b/samples/03_sharded_client/03_sharded_client.csproj @@ -7,7 +7,7 @@ - + diff --git a/samples/03_sharded_client/Services/CommandHandlingService.cs b/samples/03_sharded_client/Services/CommandHandlingService.cs index 1230cbcff..adc91b12c 100644 --- a/samples/03_sharded_client/Services/CommandHandlingService.cs +++ b/samples/03_sharded_client/Services/CommandHandlingService.cs @@ -54,7 +54,7 @@ namespace _03_sharded_client.Services if (!command.IsSpecified) return; - // the command was succesful, we don't care about this result, unless we want to log that a command succeeded. + // the command was successful, we don't care about this result, unless we want to log that a command succeeded. if (result.IsSuccess) return; diff --git a/samples/idn/Inspector.cs b/samples/idn/Inspector.cs index 3806e0e79..1544c8d07 100644 --- a/samples/idn/Inspector.cs +++ b/samples/idn/Inspector.cs @@ -3,7 +3,7 @@ using System.Linq; using System.Reflection; using System.Text; -namespace idn +namespace Idn { public static class Inspector { diff --git a/samples/idn/Program.cs b/samples/idn/Program.cs index ffd8fd1af..abc315a2d 100644 --- a/samples/idn/Program.cs +++ b/samples/idn/Program.cs @@ -13,7 +13,7 @@ using System.Threading; using System.Text; using System.Diagnostics; -namespace idn +namespace Idn { public class Program { diff --git a/samples/idn/idn.csproj b/samples/idn/idn.csproj index 984c86383..f982ff86d 100644 --- a/samples/idn/idn.csproj +++ b/samples/idn/idn.csproj @@ -6,7 +6,7 @@ - + diff --git a/src/Discord.Net.Analyzers/Discord.Net.Analyzers.csproj b/src/Discord.Net.Analyzers/Discord.Net.Analyzers.csproj index 1b2ee45bf..5fe98fc86 100644 --- a/src/Discord.Net.Analyzers/Discord.Net.Analyzers.csproj +++ b/src/Discord.Net.Analyzers/Discord.Net.Analyzers.csproj @@ -1,4 +1,4 @@ - + Discord.Net.Analyzers @@ -7,7 +7,7 @@ netstandard2.0;netstandard2.1 - + diff --git a/src/Discord.Net.Commands/Attributes/AliasAttribute.cs b/src/Discord.Net.Commands/Attributes/AliasAttribute.cs index 16eb3ba73..c4b78f534 100644 --- a/src/Discord.Net.Commands/Attributes/AliasAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/AliasAttribute.cs @@ -16,7 +16,7 @@ namespace Discord.Commands /// /// [Command("stats")] /// [Alias("stat", "info")] - /// public async Task GetStatsAsync(IUser user) + /// public Task GetStatsAsync(IUser user) /// { /// // ...pull stats /// } diff --git a/src/Discord.Net.Commands/Builders/CommandBuilder.cs b/src/Discord.Net.Commands/Builders/CommandBuilder.cs index 3f1ca883a..1d946a33d 100644 --- a/src/Discord.Net.Commands/Builders/CommandBuilder.cs +++ b/src/Discord.Net.Commands/Builders/CommandBuilder.cs @@ -7,6 +7,7 @@ namespace Discord.Commands.Builders { public class CommandBuilder { + #region CommandBuilder private readonly List _preconditions; private readonly List _parameters; private readonly List _attributes; @@ -27,8 +28,9 @@ namespace Discord.Commands.Builders public IReadOnlyList Parameters => _parameters; public IReadOnlyList Attributes => _attributes; public IReadOnlyList Aliases => _aliases; + #endregion - //Automatic + #region Automatic internal CommandBuilder(ModuleBuilder module) { Module = module; @@ -38,7 +40,9 @@ namespace Discord.Commands.Builders _attributes = new List(); _aliases = new List(); } - //User-defined + #endregion + + #region User-defined internal CommandBuilder(ModuleBuilder module, string primaryAlias, Func callback) : this(module) { @@ -132,7 +136,7 @@ namespace Discord.Commands.Builders var firstMultipleParam = _parameters.FirstOrDefault(x => x.IsMultiple); if ((firstMultipleParam != null) && (firstMultipleParam != lastParam)) throw new InvalidOperationException($"Only the last parameter in a command may have the Multiple flag. Parameter: {firstMultipleParam.Name} in {PrimaryAlias}"); - + var firstRemainderParam = _parameters.FirstOrDefault(x => x.IsRemainder); if ((firstRemainderParam != null) && (firstRemainderParam != lastParam)) throw new InvalidOperationException($"Only the last parameter in a command may have the Remainder flag. Parameter: {firstRemainderParam.Name} in {PrimaryAlias}"); @@ -140,5 +144,6 @@ namespace Discord.Commands.Builders return new CommandInfo(this, info, service); } + #endregion } } diff --git a/src/Discord.Net.Commands/Builders/ModuleBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleBuilder.cs index 6dc50db31..ddb62e797 100644 --- a/src/Discord.Net.Commands/Builders/ModuleBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleBuilder.cs @@ -7,6 +7,7 @@ namespace Discord.Commands.Builders { public class ModuleBuilder { + #region ModuleBuilder private readonly List _commands; private readonly List _submodules; private readonly List _preconditions; @@ -27,8 +28,9 @@ namespace Discord.Commands.Builders public IReadOnlyList Aliases => _aliases; internal TypeInfo TypeInfo { get; set; } + #endregion - //Automatic + #region Automatic internal ModuleBuilder(CommandService service, ModuleBuilder parent) { Service = service; @@ -40,7 +42,9 @@ namespace Discord.Commands.Builders _attributes = new List(); _aliases = new List(); } - //User-defined + #endregion + + #region User-defined internal ModuleBuilder(CommandService service, ModuleBuilder parent, string primaryAlias) : this(service, parent) { @@ -132,5 +136,6 @@ namespace Discord.Commands.Builders public ModuleInfo Build(CommandService service, IServiceProvider services) => BuildImpl(service, services); internal ModuleInfo Build(CommandService service, IServiceProvider services, ModuleInfo parent) => BuildImpl(service, services, parent); + #endregion } } diff --git a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs index 7a752090e..8c10ae806 100644 --- a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs @@ -116,7 +116,7 @@ namespace Discord.Commands builder.AddAliases(alias.Aliases); break; case GroupAttribute group: - builder.Name = builder.Name ?? group.Prefix; + builder.Name ??= group.Prefix; builder.Group = group.Prefix; builder.AddAliases(group.Prefix); break; @@ -158,7 +158,7 @@ namespace Discord.Commands case CommandAttribute command: builder.AddAliases(command.Text); builder.RunMode = command.RunMode; - builder.Name = builder.Name ?? command.Text; + builder.Name ??= command.Text; builder.IgnoreExtraArgs = command.IgnoreExtraArgs ?? service._ignoreExtraArgs; break; case NameAttribute name: @@ -291,7 +291,7 @@ namespace Discord.Commands return reader; } - //We dont have a cached type reader, create one + //We don't have a cached type reader, create one reader = ReflectionUtils.CreateObject(typeReaderType.GetTypeInfo(), service, services); service.AddTypeReader(paramType, reader, false); diff --git a/src/Discord.Net.Commands/Builders/ParameterBuilder.cs b/src/Discord.Net.Commands/Builders/ParameterBuilder.cs index 4ad5bfac0..9ee1a748c 100644 --- a/src/Discord.Net.Commands/Builders/ParameterBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ParameterBuilder.cs @@ -8,6 +8,7 @@ namespace Discord.Commands.Builders { public class ParameterBuilder { + #region ParameterBuilder private readonly List _preconditions; private readonly List _attributes; @@ -24,8 +25,9 @@ namespace Discord.Commands.Builders public IReadOnlyList Preconditions => _preconditions; public IReadOnlyList Attributes => _attributes; +#endregion - //Automatic + #region Automatic internal ParameterBuilder(CommandBuilder command) { _preconditions = new List(); @@ -33,7 +35,9 @@ namespace Discord.Commands.Builders Command = command; } - //User-defined + #endregion + + #region User-defined internal ParameterBuilder(CommandBuilder command, string name, Type type) : this(command) { @@ -127,10 +131,11 @@ namespace Discord.Commands.Builders internal ParameterInfo Build(CommandInfo info) { - if ((TypeReader ?? (TypeReader = GetReader(ParameterType))) == null) + if ((TypeReader ??= GetReader(ParameterType)) == null) throw new InvalidOperationException($"No type reader found for type {ParameterType.Name}, one must be specified"); return new ParameterInfo(this, info, Command.Module.Service); } + #endregion } } diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index 8659b0130..db08d0d79 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -29,6 +29,7 @@ namespace Discord.Commands /// public class CommandService : IDisposable { + #region CommandService ///

/// Occurs when a command-related information is received. /// @@ -131,8 +132,9 @@ namespace Discord.Commands entityTypeReaders.Add((typeof(IUser), typeof(UserTypeReader<>))); _entityTypeReaders = entityTypeReaders.ToImmutable(); } + #endregion - //Modules + #region Modules public async Task CreateModuleAsync(string primaryAlias, Action buildFunc) { await _moduleLock.WaitAsync().ConfigureAwait(false); @@ -187,7 +189,7 @@ namespace Discord.Commands /// public async Task AddModuleAsync(Type type, IServiceProvider services) { - services = services ?? EmptyServiceProvider.Instance; + services ??= EmptyServiceProvider.Instance; await _moduleLock.WaitAsync().ConfigureAwait(false); try @@ -222,7 +224,7 @@ namespace Discord.Commands /// public async Task> AddModulesAsync(Assembly assembly, IServiceProvider services) { - services = services ?? EmptyServiceProvider.Instance; + services ??= EmptyServiceProvider.Instance; await _moduleLock.WaitAsync().ConfigureAwait(false); try @@ -322,8 +324,9 @@ namespace Discord.Commands return true; } + #endregion - //Type Readers + #region Type Readers /// /// Adds a custom to this for the supplied object /// type. @@ -448,8 +451,9 @@ namespace Discord.Commands } return null; } + #endregion - //Execution + #region Execution /// /// Searches for the command. /// @@ -503,7 +507,7 @@ namespace Discord.Commands /// public async Task ExecuteAsync(ICommandContext context, string input, IServiceProvider services, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) { - services = services ?? EmptyServiceProvider.Instance; + services ??= EmptyServiceProvider.Instance; var searchResult = Search(input); if (!searchResult.IsSuccess) @@ -598,11 +602,13 @@ namespace Discord.Commands //If we get this far, at least one parse was successful. Execute the most likely overload. var chosenOverload = successfulParses[0]; var result = await chosenOverload.Key.ExecuteAsync(context, chosenOverload.Value, services).ConfigureAwait(false); - if (!result.IsSuccess && !(result is RuntimeResult || result is ExecuteResult)) // succesful results raise the event in CommandInfo#ExecuteInternalAsync (have to raise it there b/c deffered execution) + if (!result.IsSuccess && !(result is RuntimeResult || result is ExecuteResult)) // successful results raise the event in CommandInfo#ExecuteInternalAsync (have to raise it there b/c deferred execution) await _commandExecutedEvent.InvokeAsync(chosenOverload.Key.Command, context, result); return result; } + #endregion + #region Dispose protected virtual void Dispose(bool disposing) { if (!_isDisposed) @@ -620,5 +626,6 @@ namespace Discord.Commands { Dispose(true); } + #endregion } } diff --git a/src/Discord.Net.Commands/Discord.Net.Commands.csproj b/src/Discord.Net.Commands/Discord.Net.Commands.csproj index 21869d91c..ec2795de2 100644 --- a/src/Discord.Net.Commands/Discord.Net.Commands.csproj +++ b/src/Discord.Net.Commands/Discord.Net.Commands.csproj @@ -12,4 +12,4 @@ - + \ No newline at end of file diff --git a/src/Discord.Net.Commands/Extensions/MessageExtensions.cs b/src/Discord.Net.Commands/Extensions/MessageExtensions.cs index f880e1d98..9aa83d418 100644 --- a/src/Discord.Net.Commands/Extensions/MessageExtensions.cs +++ b/src/Discord.Net.Commands/Extensions/MessageExtensions.cs @@ -51,8 +51,7 @@ namespace Discord.Commands if (endPos == -1) return false; if (text.Length < endPos + 2 || text[endPos + 1] != ' ') return false; //Must end in "> " - ulong userId; - if (!MentionUtils.TryParseUser(text.Substring(0, endPos + 1), out userId)) return false; + if (!MentionUtils.TryParseUser(text.Substring(0, endPos + 1), out ulong userId)) return false; if (userId == user.Id) { argPos = endPos + 2; diff --git a/src/Discord.Net.Commands/Info/CommandInfo.cs b/src/Discord.Net.Commands/Info/CommandInfo.cs index 3bcef9831..773c7c773 100644 --- a/src/Discord.Net.Commands/Info/CommandInfo.cs +++ b/src/Discord.Net.Commands/Info/CommandInfo.cs @@ -123,7 +123,7 @@ namespace Discord.Commands public async Task CheckPreconditionsAsync(ICommandContext context, IServiceProvider services = null) { - services = services ?? EmptyServiceProvider.Instance; + services ??= EmptyServiceProvider.Instance; async Task CheckGroups(IEnumerable preconditions, string type) { @@ -164,7 +164,7 @@ namespace Discord.Commands public async Task ParseAsync(ICommandContext context, int startIndex, SearchResult searchResult, PreconditionResult preconditionResult = null, IServiceProvider services = null) { - services = services ?? EmptyServiceProvider.Instance; + services ??= EmptyServiceProvider.Instance; if (!searchResult.IsSuccess) return ParseResult.FromError(searchResult); @@ -201,7 +201,7 @@ namespace Discord.Commands } public async Task ExecuteAsync(ICommandContext context, IEnumerable argList, IEnumerable paramList, IServiceProvider services) { - services = services ?? EmptyServiceProvider.Instance; + services ??= EmptyServiceProvider.Instance; try { diff --git a/src/Discord.Net.Commands/Info/ParameterInfo.cs b/src/Discord.Net.Commands/Info/ParameterInfo.cs index b435b301a..a6ba9dfde 100644 --- a/src/Discord.Net.Commands/Info/ParameterInfo.cs +++ b/src/Discord.Net.Commands/Info/ParameterInfo.cs @@ -75,7 +75,7 @@ namespace Discord.Commands public async Task CheckPreconditionsAsync(ICommandContext context, object arg, IServiceProvider services = null) { - services = services ?? EmptyServiceProvider.Instance; + services ??= EmptyServiceProvider.Instance; foreach (var precondition in Preconditions) { @@ -89,7 +89,7 @@ namespace Discord.Commands public async Task ParseAsync(ICommandContext context, string input, IServiceProvider services = null) { - services = services ?? EmptyServiceProvider.Instance; + services ??= EmptyServiceProvider.Instance; return await _reader.ReadAsync(context, input, services).ConfigureAwait(false); } diff --git a/src/Discord.Net.Commands/ModuleBase.cs b/src/Discord.Net.Commands/ModuleBase.cs index 6ec2db54d..3eddc11d2 100644 --- a/src/Discord.Net.Commands/ModuleBase.cs +++ b/src/Discord.Net.Commands/ModuleBase.cs @@ -16,6 +16,7 @@ namespace Discord.Commands public abstract class ModuleBase : IModuleBase where T : class, ICommandContext { + #region ModuleBase /// /// The underlying context of the command. /// @@ -35,10 +36,14 @@ namespace Discord.Commands /// Specifies if notifications are sent for mentioned users and roles in the . /// If null, all mentioned roles and users will be notified. /// + /// The request options for this request. /// The message references to be included. Used to reply to specific messages. - protected virtual async Task ReplyAsync(string message = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null) + /// The message components to be included with this message. Used for interactions. + /// A collection of stickers to send with the file. + /// A array of s to send with this response. Max 10. + protected virtual async Task ReplyAsync(string message = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null) { - return await Context.Channel.SendMessageAsync(message, isTTS, embed, options, allowedMentions, messageReference).ConfigureAwait(false); + return await Context.Channel.SendMessageAsync(message, isTTS, embed, options, allowedMentions, messageReference, component, stickers, embeds).ConfigureAwait(false); } /// /// The method to execute before executing the command. @@ -63,8 +68,9 @@ namespace Discord.Commands protected virtual void OnModuleBuilding(CommandService commandService, ModuleBuilder builder) { } + #endregion - //IModuleBase + #region IModuleBase void IModuleBase.SetContext(ICommandContext context) { var newValue = context as T; @@ -73,5 +79,6 @@ namespace Discord.Commands void IModuleBase.BeforeExecute(CommandInfo command) => BeforeExecute(command); void IModuleBase.AfterExecute(CommandInfo command) => AfterExecute(command); void IModuleBase.OnModuleBuilding(CommandService commandService, ModuleBuilder builder) => OnModuleBuilding(commandService, builder); + #endregion } } diff --git a/src/Discord.Net.Commands/RunMode.cs b/src/Discord.Net.Commands/RunMode.cs index 8e230b500..d6b49065b 100644 --- a/src/Discord.Net.Commands/RunMode.cs +++ b/src/Discord.Net.Commands/RunMode.cs @@ -8,7 +8,7 @@ namespace Discord.Commands public enum RunMode { /// - /// The default behaviour set in . + /// The default behavior set in . /// Default, /// diff --git a/src/Discord.Net.Core/CDN.cs b/src/Discord.Net.Core/CDN.cs index e1e8e5e1a..d6535a4f1 100644 --- a/src/Discord.Net.Core/CDN.cs +++ b/src/Discord.Net.Core/CDN.cs @@ -46,6 +46,32 @@ namespace Discord string extension = FormatToExtension(format, avatarId); return $"{DiscordConfig.CDNUrl}avatars/{userId}/{avatarId}.{extension}?size={size}"; } + + public static string GetGuildUserAvatarUrl(ulong userId, ulong guildId, string avatarId, ushort size, ImageFormat format) + { + if (avatarId == null) + return null; + string extension = FormatToExtension(format, avatarId); + return $"{DiscordConfig.CDNUrl}guilds/{guildId}/users/{userId}/avatars/{avatarId}.{extension}?size={size}"; + } + + /// + /// Returns a user banner URL. + /// + /// The user snowflake identifier. + /// The banner identifier. + /// The size of the image to return in horizontal pixels. This can be any power of two between 16 and 2048. + /// The format to return. + /// + /// A URL pointing to the user's banner in the specified size. + /// + public static string GetUserBannerUrl(ulong userId, string bannerId, ushort size, ImageFormat format) + { + if (bannerId == null) + return null; + string extension = FormatToExtension(format, bannerId); + return $"{DiscordConfig.CDNUrl}banners/{userId}/{bannerId}.{extension}?size={size}"; + } /// /// Returns the default user avatar URL. /// @@ -68,6 +94,16 @@ namespace Discord public static string GetGuildIconUrl(ulong guildId, string iconId) => iconId != null ? $"{DiscordConfig.CDNUrl}icons/{guildId}/{iconId}.jpg" : null; /// + /// Returns a guild role's icon URL. + /// + /// The role identifier. + /// The icon hash. + /// + /// A URL pointing to the guild role's icon. + /// + public static string GetGuildRoleIconUrl(ulong roleId, string roleHash) + => roleHash != null ? $"{DiscordConfig.CDNUrl}role-icons/{roleId}/{roleHash}.png" : null; + /// /// Returns a guild splash URL. /// /// The guild snowflake identifier. @@ -103,15 +139,17 @@ namespace Discord /// /// The guild snowflake identifier. /// The banner image identifier. + /// The format to return. /// The size of the image to return in horizontal pixels. This can be any power of two between 16 and 2048 inclusive. /// /// A URL pointing to the guild's banner image. /// - public static string GetGuildBannerUrl(ulong guildId, string bannerId, ushort? size = null) + public static string GetGuildBannerUrl(ulong guildId, string bannerId, ImageFormat format, ushort? size = null) { - if (!string.IsNullOrEmpty(bannerId)) - return $"{DiscordConfig.CDNUrl}banners/{guildId}/{bannerId}.jpg" + (size.HasValue ? $"?size={size}" : string.Empty); - return null; + if (string.IsNullOrEmpty(bannerId)) + return null; + string extension = FormatToExtension(format, bannerId); + return $"{DiscordConfig.CDNUrl}banners/{guildId}/{bannerId}.{extension}" + (size.HasValue ? $"?size={size}" : string.Empty); } /// /// Returns an emoji URL. @@ -159,23 +197,39 @@ namespace Discord public static string GetSpotifyDirectUrl(string trackId) => $"https://open.spotify.com/track/{trackId}"; + /// + /// Gets a stickers url based off the id and format. + /// + /// The id of the sticker. + /// The format of the sticker. + /// + /// A URL to the sticker. + /// + public static string GetStickerUrl(ulong stickerId, StickerFormatType format = StickerFormatType.Png) + => $"{DiscordConfig.CDNUrl}stickers/{stickerId}.{FormatToExtension(format)}"; + + private static string FormatToExtension(StickerFormatType format) + { + return format switch + { + StickerFormatType.None or StickerFormatType.Png or StickerFormatType.Apng => "png", // In the case of the Sticker endpoint, the sticker will be available as PNG if its format_type is PNG or APNG, and as Lottie if its format_type is LOTTIE. + StickerFormatType.Lottie => "lottie", + _ => throw new ArgumentException(nameof(format)), + }; + } + private static string FormatToExtension(ImageFormat format, string imageId) { if (format == ImageFormat.Auto) format = imageId.StartsWith("a_") ? ImageFormat.Gif : ImageFormat.Png; - switch (format) + return format switch { - case ImageFormat.Gif: - return "gif"; - case ImageFormat.Jpeg: - return "jpeg"; - case ImageFormat.Png: - return "png"; - case ImageFormat.WebP: - return "webp"; - default: - throw new ArgumentException(nameof(format)); - } + ImageFormat.Gif => "gif", + ImageFormat.Jpeg => "jpeg", + ImageFormat.Png => "png", + ImageFormat.WebP => "webp", + _ => throw new ArgumentException(nameof(format)), + }; } } } diff --git a/src/Discord.Net.Core/Discord.Net.Core.csproj b/src/Discord.Net.Core/Discord.Net.Core.csproj index bc513390c..29868e1c7 100644 --- a/src/Discord.Net.Core/Discord.Net.Core.csproj +++ b/src/Discord.Net.Core/Discord.Net.Core.csproj @@ -1,4 +1,4 @@ - + @@ -16,4 +16,4 @@ all - + \ No newline at end of file diff --git a/src/Discord.Net.Core/DiscordConfig.cs b/src/Discord.Net.Core/DiscordConfig.cs index da8525644..d5951bd07 100644 --- a/src/Discord.Net.Core/DiscordConfig.cs +++ b/src/Discord.Net.Core/DiscordConfig.cs @@ -94,6 +94,13 @@ namespace Discord /// The maximum number of users that can be gotten per-batch. /// public const int MaxUsersPerBatch = 1000; + /// + /// Returns the max users allowed to be in a request for guild event users. + /// + /// + /// The maximum number of users that can be gotten per-batch. + /// + public const int MaxGuildEventUsersPerBatch = 100; /// /// Returns the max guilds allowed to be in a request. /// @@ -158,5 +165,17 @@ namespace Discord /// clock. Your system will still need a stable clock. /// public bool UseSystemClock { get; set; } = true; + + /// + /// Gets or sets whether or not the internal experation check uses the system date + /// + snowflake date to check if an interaction can be responded to. + /// + /// + /// If set to then the CreatedAt property in an interaction + /// will be set to when it was received instead of the snowflakes date. + ///
+ /// This will still require a stable clock on your system. + ///
+ public bool UseInteractionSnowflakeDate { get; set; } = true; } } diff --git a/src/Discord.Net.Core/DiscordErrorCode.cs b/src/Discord.Net.Core/DiscordErrorCode.cs new file mode 100644 index 000000000..5a5223b93 --- /dev/null +++ b/src/Discord.Net.Core/DiscordErrorCode.cs @@ -0,0 +1,197 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord +{ + /// + /// Represents a set of json error codes received by discord. + /// + public enum DiscordErrorCode + { + GeneralError = 0, + + #region UnknownXYZ (10XXX) + UnknownAccount = 10001, + UnknownApplication = 10002, + UnknownChannel = 10003, + UnknownGuild = 10004, + UnknownIntegration = 10005, + UnknownInvite = 10006, + UnknownMember = 10007, + UnknownMessage = 10008, + UnknownPermissionOverwrite = 10009, + UnknownProvider = 10010, + UnknownRole = 10011, + UnknownToken = 10012, + UnknownUser = 10013, + UnknownEmoji = 10014, + UnknownWebhook = 10015, + UnknownWebhookService = 10016, + UnknownSession = 10020, + UnknownBan = 10026, + UnknownSKU = 10027, + UnknownStoreListing = 10028, + UnknownEntitlement = 10029, + UnknownBuild = 10030, + UnknownLobby = 10031, + UnknownBranch = 10032, + UnknownStoreDirectoryLayout = 10033, + UnknownRedistributable = 10036, + UnknownGiftCode = 10038, + UnknownStream = 10049, + UnknownPremiumServerSubscribeCooldown = 10050, + UnknownGuildTemplate = 10057, + UnknownDiscoverableServerCategory = 10059, + UnknownSticker = 10060, + UnknownInteraction = 10062, + UnknownApplicationCommand = 10063, + UnknownApplicationCommandPermissions = 10066, + UnknownStageInstance = 10067, + UnknownGuildMemberVerificationForm = 10068, + UnknownGuildWelcomeScreen = 10069, + UnknownGuildScheduledEvent = 10070, + UnknownGuildScheduledEventUser = 10071, + #endregion + + #region General Actions (20XXX) + BotsCannotUse = 20001, + OnlyBotsCanUse = 20002, + CannotSendExplicitContent = 20009, + ApplicationActionUnauthorized = 20012, + ActionSlowmode = 20016, + OnlyOwnerAction = 20018, + AnnouncementEditRatelimit = 20022, + ChannelWriteRatelimit = 20028, + WordsNotAllowed = 20031, + GuildPremiumTooLow = 20035, + #endregion + + #region Numeric Limits Reached (30XXX) + MaximumGuildsReached = 30001, + MaximumFriendsReached = 30002, + MaximumPinsReached = 30003, + MaximumRecipientsReached = 30004, + MaximumGuildRolesReached = 30005, + MaximumWebhooksReached = 30007, + MaximumEmojisReached = 30008, + MaximumReactionsReached = 30010, + MaximumGuildChannelsReached = 30013, + MaximumAttachmentsReached = 30015, + MaximumInvitesReached = 30016, + MaximumAnimatedEmojisReached = 30018, + MaximumServerMembersReached = 30019, + MaximumServerCategoriesReached = 30030, + GuildTemplateAlreadyExists = 30031, + MaximumThreadMembersReached = 30033, + MaximumBansForNonGuildMembersReached = 30035, + MaximumBanFetchesReached = 30037, + MaximumUncompleteGuildScheduledEvents = 30038, + MaximumStickersReached = 30039, + MaximumPruneRequestReached = 30040, + MaximumGuildWigitsReached = 30042, + #endregion + + #region General Request Errors (40XXX) + TokenUnauthorized = 40001, + InvalidVerification = 40002, + OpeningDMTooFast = 40003, + RequestEntityTooLarge = 40005, + FeatureDisabled = 40006, + UserBanned = 40007, + TargetUserNotInVoice = 40032, + MessageAlreadyCrossposted = 40033, + ApplicationNameAlreadyExists = 40041, + #endregion + + #region Action Preconditions/Checks (50XXX) + MissingPermissions = 50001, + InvalidAccountType = 50002, + CannotExecuteForDM = 50003, + GuildWigitDisabled = 50004, + CannotEditOtherUsersMessage = 50005, + CannotSendEmptyMessage = 50006, + CannotSendMessageToUser = 50007, + CannotSendMessageToVoiceChannel = 50008, + ChannelVerificationTooHight = 50009, + OAuth2ApplicationDoesntHaveBot = 50010, + OAuth2ApplicationLimitReached = 50011, + InvalidOAuth2State = 50012, + InsufficientPermissions = 50013, + InvalidAuthenticationToken = 50014, + NoteTooLong = 50015, + ProvidedMessageDeleteCountOutOfBounds = 50016, + InvalidPinChannel = 50019, + InvalidInvite = 50020, + CannotExecuteOnSystemMessage = 50021, + CannotExecuteOnChannelType = 50024, + InvalidOAuth2Token = 50025, + MissingOAuth2Scope = 50026, + InvalidWebhookToken = 50027, + InvalidRole = 50028, + InvalidRecipients = 50033, + BulkDeleteMessageTooOld = 50034, + InvalidFormBody = 50035, + InviteAcceptedForGuildThatBotIsntIn = 50036, + InvalidAPIVersion = 50041, + FileUploadTooBig = 50045, + InvalidFileUpload = 50046, + CannotSelfRedeemGift = 50054, + PaymentSourceRequiredForGift = 50070, + CannotDeleteRequiredCommunityChannel = 50074, + InvalidSticker = 50081, + CannotExecuteOnArchivedThread = 50083, + InvalidThreadNotificationSettings = 50084, + BeforeValueEarlierThanThreadCreation = 50085, + ServerLocaleUnavailable = 50095, + ServerRequiresMonetization = 50097, + ServerRequiresBoosts = 50101, + + #endregion + + #region 2FA (60XXX) + Requires2FA = 60003, + #endregion + + #region User Searches (80XXX) + NoUsersWithTag = 80004, + #endregion + + #region Reactions (90XXX) + ReactionBlocked = 90001, + #endregion + + #region API Status (130XXX) + APIOverloaded = 130000, + #endregion + + #region Stage Errors (150XXX) + StageAlreadyOpened = 150006, + #endregion + + #region Reply and Thread Errors (160XXX) + CannotReplyWithoutReadMessageHistory = 160002, + MessageAlreadyContainsThread = 160004, + ThreadIsLocked = 160005, + MaximumActiveThreadsReached = 160006, + MaximumAnnouncementThreadsReached = 160007, + #endregion + + #region Sticker Uploads (170XXX) + InvalidJSONLottie = 170001, + LottieCantContainRasters = 170002, + StickerMaximumFramerateExceeded = 170003, + StickerMaximumFrameCountExceeded = 170004, + LottieMaximumDimentionsExceeded = 170005, + StickerFramerateBoundsExceeed = 170006, + StickerAnimationDurationTooLong = 170007, + #endregion + + #region Guild Scheduled Events + CannotUpdateFinishedEvent = 180000, + FailedStageCreation = 180002, + #endregion + } +} diff --git a/src/Discord.Net.Core/DiscordJsonError.cs b/src/Discord.Net.Core/DiscordJsonError.cs new file mode 100644 index 000000000..fdf82ea0c --- /dev/null +++ b/src/Discord.Net.Core/DiscordJsonError.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord +{ + /// + /// Represents a generic parsed json error received from discord after performing a rest request. + /// + public struct DiscordJsonError + { + /// + /// Gets the json path of the error. + /// + public string Path { get; } + + /// + /// Gets a collection of errors associated with the specific property at the path. + /// + public IReadOnlyCollection Errors { get; } + + internal DiscordJsonError(string path, DiscordError[] errors) + { + Path = path; + Errors = errors.ToImmutableArray(); + } + } + + /// + /// Represents an error with a property. + /// + public struct DiscordError + { + /// + /// Gets the code of the error. + /// + public string Code { get; } + + /// + /// Gets the message describing what went wrong. + /// + public string Message { get; } + + internal DiscordError(string code, string message) + { + Code = code; + Message = message; + } + } +} diff --git a/src/Discord.Net.Core/Entities/Activities/ActivityProperties.cs b/src/Discord.Net.Core/Entities/Activities/ActivityProperties.cs index a7d13235f..15a79dff6 100644 --- a/src/Discord.Net.Core/Entities/Activities/ActivityProperties.cs +++ b/src/Discord.Net.Core/Entities/Activities/ActivityProperties.cs @@ -33,6 +33,18 @@ namespace Discord /// /// Indicates that a user can play this song. /// - Play = 0b100000 + Play = 0b100000, + /// + /// Indicates that a user is playing an activity in a voice channel with friends. + /// + PartyPrivacyFriends = 0b1000000, + /// + /// Indicates that a user is playing an activity in a voice channel. + /// + PartyPrivacyVoiceChannel = 0b10000000, + /// + /// Indicates that a user is playing an activity in a voice channel. + /// + Embedded = 0b10000000 } } diff --git a/src/Discord.Net.Core/Entities/Activities/ActivityType.cs b/src/Discord.Net.Core/Entities/Activities/ActivityType.cs index 8c44f49e3..1f67886eb 100644 --- a/src/Discord.Net.Core/Entities/Activities/ActivityType.cs +++ b/src/Discord.Net.Core/Entities/Activities/ActivityType.cs @@ -25,5 +25,9 @@ namespace Discord /// The user has set a custom status. ///
CustomStatus = 4, + /// + /// The user is competing in a game. + /// + Competing = 5, } } diff --git a/src/Discord.Net.Core/Entities/ApplicationFlags.cs b/src/Discord.Net.Core/Entities/ApplicationFlags.cs new file mode 100644 index 000000000..1ede4257d --- /dev/null +++ b/src/Discord.Net.Core/Entities/ApplicationFlags.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord +{ + /// + /// Represents public flags for an application. + /// + public enum ApplicationFlags + { + GatewayPresence = 1 << 12, + GatewayPresenceLimited = 1 << 13, + GatewayGuildMembers = 1 << 14, + GatewayGuildMembersLimited = 1 << 15, + VerificationPendingGuildLimit = 1 << 16, + Embedded = 1 << 17, + GatewayMessageContent = 1 << 18, + GatewayMessageContentLimited = 1 << 19 + } +} diff --git a/src/Discord.Net.Core/Entities/ApplicationInstallParams.cs b/src/Discord.Net.Core/Entities/ApplicationInstallParams.cs new file mode 100644 index 000000000..180592f1e --- /dev/null +++ b/src/Discord.Net.Core/Entities/ApplicationInstallParams.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord +{ + /// + /// Represents install parameters for an application. + /// + public class ApplicationInstallParams + { + /// + /// Gets the scopes to install this application. + /// + public IReadOnlyCollection Scopes { get; } + + /// + /// Gets the default permissions to install this application + /// + public GuildPermission? Permission { get; } + + internal ApplicationInstallParams(string[] scopes, GuildPermission? permission) + { + Scopes = scopes.ToImmutableArray(); + Permission = permission; + } + } +} diff --git a/src/Discord.Net.Core/Entities/AuditLogs/ActionType.cs b/src/Discord.Net.Core/Entities/AuditLogs/ActionType.cs index 1728b2021..5092b4e7f 100644 --- a/src/Discord.Net.Core/Entities/AuditLogs/ActionType.cs +++ b/src/Discord.Net.Core/Entities/AuditLogs/ActionType.cs @@ -142,5 +142,55 @@ namespace Discord /// A message was unpinned from this guild. ///
MessageUnpinned = 75, + + /// + /// A integration was created + /// + IntegrationCreated = 80, + /// + /// A integration was updated + /// + IntegrationUpdated = 81, + /// + /// An integration was deleted + /// + IntegrationDeleted = 82, + /// + /// A stage instance was created. + /// + StageInstanceCreated = 83, + /// + /// A stage instance was updated. + /// + StageInstanceUpdated = 84, + /// + /// A stage instance was deleted. + /// + StageInstanceDeleted = 85, + + /// + /// A sticker was created. + /// + StickerCreated = 90, + /// + /// A sticker was updated. + /// + StickerUpdated = 91, + /// + /// A sticker was deleted. + /// + StickerDeleted = 92, + /// + /// A thread was created. + /// + ThreadCreate = 110, + /// + /// A thread was updated. + /// + ThreadUpdate = 111, + /// + /// A thread was deleted. + /// + ThreadDelete = 112 } } diff --git a/src/Discord.Net.Core/Entities/Channels/ChannelType.cs b/src/Discord.Net.Core/Entities/Channels/ChannelType.cs index 6dd910ba6..e60bd5031 100644 --- a/src/Discord.Net.Core/Entities/Channels/ChannelType.cs +++ b/src/Discord.Net.Core/Entities/Channels/ChannelType.cs @@ -14,6 +14,18 @@ namespace Discord /// The channel is a category channel. Category = 4, /// The channel is a news channel. - News = 5 + News = 5, + /// The channel is a store channel. + Store = 6, + /// The channel is a temporary thread channel under a news channel. + NewsThread = 10, + /// The channel is a temporary thread channel under a text channel. + PublicThread = 11, + /// The channel is a private temporary thread channel under a text channel. + PrivateThread = 12, + /// The channel is a stage voice channel. + Stage = 13, + /// The channel is a guild directory used in hub servers. (Unreleased) + GuildDirectory = 14 } } diff --git a/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs b/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs index e60eb9c13..87dfb3460 100644 --- a/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs @@ -28,11 +28,14 @@ namespace Discord /// If null, all mentioned roles and users will be notified. /// /// The message references to be included. Used to reply to specific messages. + /// The message components to be included with this message. Used for interactions. + /// A collection of stickers to send with the message. + /// A array of s to send with this response. Max 10. /// /// A task that represents an asynchronous send operation for delivering the message. The task result /// contains the sent message. /// - Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null); + Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null); /// /// Sends a file to this message channel with an optional caption. /// @@ -65,11 +68,14 @@ namespace Discord /// If null, all mentioned roles and users will be notified. /// /// The message references to be included. Used to reply to specific messages. + /// The message components to be included with this message. Used for interactions. + /// A collection of stickers to send with the file. + /// A array of s to send with this response. Max 10. /// /// A task that represents an asynchronous send operation for delivering the message. The task result /// contains the sent message. /// - Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null); + Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null); /// /// Sends a file to this message channel with an optional caption. /// @@ -99,11 +105,72 @@ namespace Discord /// If null, all mentioned roles and users will be notified. /// /// The message references to be included. Used to reply to specific messages. + /// The message components to be included with this message. Used for interactions. + /// A collection of stickers to send with the file. + /// A array of s to send with this response. Max 10. /// /// A task that represents an asynchronous send operation for delivering the message. The task result /// contains the sent message. /// - Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null); + Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null); + /// + /// Sends a file to this message channel with an optional caption. + /// + /// + /// This method sends a file as if you are uploading an attachment directly from your Discord client. + /// + /// If you wish to upload an image and have it embedded in a embed, + /// you may upload the file and refer to the file with "attachment://filename.ext" in the + /// . See the example section for its usage. + /// + /// + /// The attachment containing the file and description. + /// The message to be sent. + /// Whether the message should be read aloud by Discord or not. + /// The to be sent. + /// The options to be used when sending the request. + /// + /// Specifies if notifications are sent for mentioned users and roles in the message . + /// If null, all mentioned roles and users will be notified. + /// + /// The message references to be included. Used to reply to specific messages. + /// The message components to be included with this message. Used for interactions. + /// A collection of stickers to send with the file. + /// A array of s to send with this response. Max 10. + /// + /// A task that represents an asynchronous send operation for delivering the message. The task result + /// contains the sent message. + /// + Task SendFileAsync(FileAttachment attachment, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null); + /// + /// Sends a collection of files to this message channel. + /// + /// + /// This method sends files as if you are uploading attachments directly from your Discord client. + /// + /// If you wish to upload an image and have it embedded in a embed, + /// you may upload the file and refer to the file with "attachment://filename.ext" in the + /// . See the example section for its usage. + /// + /// + /// A collection of attachments to upload. + /// The message to be sent. + /// Whether the message should be read aloud by Discord or not. + /// The to be sent. + /// The options to be used when sending the request. + /// + /// Specifies if notifications are sent for mentioned users and roles in the message . + /// If null, all mentioned roles and users will be notified. + /// + /// The message references to be included. Used to reply to specific messages. + /// The message components to be included with this message. Used for interactions. + /// A collection of stickers to send with the file. + /// A array of s to send with this response. Max 10. + /// + /// A task that represents an asynchronous send operation for delivering the message. The task result + /// contains the sent message. + /// + Task SendFilesAsync(IEnumerable attachments, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null); /// /// Gets a message from this message channel. diff --git a/src/Discord.Net.Core/Entities/Channels/INestedChannel.cs b/src/Discord.Net.Core/Entities/Channels/INestedChannel.cs index 2c9503db1..563acd4f8 100644 --- a/src/Discord.Net.Core/Entities/Channels/INestedChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/INestedChannel.cs @@ -12,7 +12,7 @@ namespace Discord /// Gets the parent (category) ID of this channel in the guild's channel list. /// /// - /// A representing the snowflake identifier of the parent of this channel; + /// A representing the snowflake identifier of the parent of this channel; /// null if none is set. /// ulong? CategoryId { get; } @@ -56,6 +56,50 @@ namespace Discord /// metadata object containing information for the created invite. /// Task CreateInviteAsync(int? maxAge = 86400, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null); + + /// + /// Creates a new invite to this channel. + /// + /// + /// The following example creates a new invite to this channel; the invite lasts for 12 hours and can only + /// be used 3 times throughout its lifespan. + /// + /// await guildChannel.CreateInviteAsync(maxAge: 43200, maxUses: 3); + /// + /// + /// The id of the embedded application to open for this invite. + /// The time (in seconds) until the invite expires. Set to null to never expire. + /// The max amount of times this invite may be used. Set to null to have unlimited uses. + /// If true, the user accepting this invite will be kicked from the guild after closing their client. + /// If true, don't try to reuse a similar invite (useful for creating many unique one time use invites). + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous invite creation operation. The task result contains an invite + /// metadata object containing information for the created invite. + /// + Task CreateInviteToApplicationAsync(ulong applicationId, int? maxAge = 86400, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null); + + /// + /// Creates a new invite to this channel. + /// + /// + /// The following example creates a new invite to this channel; the invite lasts for 12 hours and can only + /// be used 3 times throughout its lifespan. + /// + /// await guildChannel.CreateInviteAsync(maxAge: 43200, maxUses: 3); + /// + /// + /// The id of the user whose stream to display for this invite. + /// The time (in seconds) until the invite expires. Set to null to never expire. + /// The max amount of times this invite may be used. Set to null to have unlimited uses. + /// If true, the user accepting this invite will be kicked from the guild after closing their client. + /// If true, don't try to reuse a similar invite (useful for creating many unique one time use invites). + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous invite creation operation. The task result contains an invite + /// metadata object containing information for the created invite. + /// + Task CreateInviteToStreamAsync(IUser user, int? maxAge = 86400, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null); /// /// Gets a collection of all invites to this channel. /// B diff --git a/src/Discord.Net.Core/Entities/Channels/IStageChannel.cs b/src/Discord.Net.Core/Entities/Channels/IStageChannel.cs new file mode 100644 index 000000000..5e0be5b7e --- /dev/null +++ b/src/Discord.Net.Core/Entities/Channels/IStageChannel.cs @@ -0,0 +1,114 @@ +using System; +using System.Threading.Tasks; + +namespace Discord +{ + /// + /// Represents a generic Stage Channel. + /// + public interface IStageChannel : IVoiceChannel + { + /// + /// Gets the topic of the Stage instance. + /// + /// + /// If the stage isn't live then this property will be set to . + /// + string Topic { get; } + + /// + /// Gets the of the current stage. + /// + /// + /// If the stage isn't live then this property will be set to . + /// + StagePrivacyLevel? PrivacyLevel { get; } + + /// + /// Gets whether or not stage discovery is disabled. + /// + bool? IsDiscoverableDisabled { get; } + + /// + /// Gets whether or not the stage is live. + /// + bool IsLive { get; } + + /// + /// Starts the stage, creating a stage instance. + /// + /// The topic for the stage/ + /// The privacy level of the stage. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous start operation. + /// + Task StartStageAsync(string topic, StagePrivacyLevel privacyLevel = StagePrivacyLevel.GuildOnly, RequestOptions options = null); + + /// + /// Modifies the current stage instance. + /// + /// The properties to modify the stage instance with. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous modify operation. + /// + Task ModifyInstanceAsync(Action func, RequestOptions options = null); + + /// + /// Stops the stage, deleting the stage instance. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous stop operation. + /// + Task StopStageAsync(RequestOptions options = null); + + /// + /// Indicates that the bot would like to speak within a stage channel. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous request to speak operation. + /// + Task RequestToSpeakAsync(RequestOptions options = null); + + /// + /// Makes the current user become a speaker within a stage. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous speaker modify operation. + /// + Task BecomeSpeakerAsync(RequestOptions options = null); + + /// + /// Makes the current user a listener. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous stop operation. + /// + Task StopSpeakingAsync(RequestOptions options = null); + + /// + /// Makes a user a speaker within a stage. + /// + /// The user to make the speaker. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous move operation. + /// + Task MoveToSpeakerAsync(IGuildUser user, RequestOptions options = null); + + /// + /// Removes a user from speaking. + /// + /// The user to remove from speaking. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous remove operation. + /// + Task RemoveFromSpeakerAsync(IGuildUser user, RequestOptions options = null); + } +} diff --git a/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs b/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs index a2baf6990..ae0fe674b 100644 --- a/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs @@ -114,5 +114,40 @@ namespace Discord /// of webhooks that is available in this channel. /// Task> GetWebhooksAsync(RequestOptions options = null); + + /// + /// Creates a thread within this . + /// + /// + /// When is the thread type will be based off of the + /// channel its created in. When called on a , it creates a . + /// When called on a , it creates a . The id of the created + /// thread will be the same as the id of the message, and as such a message can only have a + /// single thread created from it. + /// + /// The name of the thread. + /// + /// The type of the thread. + /// + /// Note: This parameter is not used if the parameter is not specified. + /// + /// + /// + /// The duration on which this thread archives after. + /// + /// Note: Options and + /// are only available for guilds that are boosted. You can check in the to see if the + /// guild has the THREE_DAY_THREAD_ARCHIVE and SEVEN_DAY_THREAD_ARCHIVE. + /// + /// + /// The message which to start the thread from. + /// Whether non-moderators can add other non-moderators to a thread; only available when creating a private thread + /// The amount of seconds a user has to wait before sending another message (0-21600) + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous create operation. The task result contains a + /// + Task CreateThreadAsync(string name, ThreadType type = ThreadType.PublicThread, ThreadArchiveDuration autoArchiveDuration = ThreadArchiveDuration.OneDay, + IMessage message = null, bool? invitable = null, int? slowmode = null, RequestOptions options = null); } } diff --git a/src/Discord.Net.Core/Entities/Channels/IThreadChannel.cs b/src/Discord.Net.Core/Entities/Channels/IThreadChannel.cs new file mode 100644 index 000000000..50e46efa6 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Channels/IThreadChannel.cs @@ -0,0 +1,89 @@ +using System; +using System.Threading.Tasks; + +namespace Discord +{ + /// + /// Represents a thread channel inside of a guild. + /// + public interface IThreadChannel : ITextChannel + { + /// + /// Gets the type of the current thread channel. + /// + ThreadType Type { get; } + + /// + /// Gets whether or not the current user has joined this thread. + /// + bool HasJoined { get; } + + /// + /// Gets whether or not the current thread is archived. + /// + bool IsArchived { get; } + + /// + /// Gets the duration of time before the thread is automatically archived after no activity. + /// + ThreadArchiveDuration AutoArchiveDuration { get; } + + /// + /// Gets the timestamp when the thread's archive status was last changed, used for calculating recent activity. + /// + DateTimeOffset ArchiveTimestamp { get; } + + /// + /// Gets whether or not the current thread is locked. + /// + bool IsLocked { get; } + + /// + /// Gets an approximate count of users in a thread, stops counting after 50. + /// + int MemberCount { get; } + + /// + /// Gets an approximate count of messages in a thread, stops counting after 50. + /// + int MessageCount { get; } + + /// + /// Joins the current thread. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous join operation. + /// + Task JoinAsync(RequestOptions options = null); + + /// + /// Leaves the current thread. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous leave operation. + /// + Task LeaveAsync(RequestOptions options = null); + + /// + /// Adds a user to this thread. + /// + /// The to add. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous operation of adding a member to a thread. + /// + Task AddUserAsync(IGuildUser user, RequestOptions options = null); + + /// + /// Removes a user from this thread. + /// + /// The to remove from this thread. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous operation of removing a user from this thread. + /// + Task RemoveUserAsync(IGuildUser user, RequestOptions options = null); + } +} diff --git a/src/Discord.Net.Core/Entities/Channels/StageInstanceProperties.cs b/src/Discord.Net.Core/Entities/Channels/StageInstanceProperties.cs new file mode 100644 index 000000000..35201fe0f --- /dev/null +++ b/src/Discord.Net.Core/Entities/Channels/StageInstanceProperties.cs @@ -0,0 +1,18 @@ +namespace Discord +{ + /// + /// Represents properties to use when modifying a stage instance. + /// + public class StageInstanceProperties + { + /// + /// Gets or sets the topic of the stage. + /// + public Optional Topic { get; set; } + + /// + /// Gets or sets the privacy level of the stage. + /// + public Optional PrivacyLevel { get; set; } + } +} diff --git a/src/Discord.Net.Core/Entities/Channels/StagePrivacyLevel.cs b/src/Discord.Net.Core/Entities/Channels/StagePrivacyLevel.cs new file mode 100644 index 000000000..0582a3e52 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Channels/StagePrivacyLevel.cs @@ -0,0 +1,17 @@ +namespace Discord +{ + /// + /// Represents the privacy level of a stage. + /// + public enum StagePrivacyLevel + { + /// + /// The Stage instance is visible publicly, such as on Stage Discovery. + /// + Public = 1, + /// + /// The Stage instance is visible to only guild members. + /// + GuildOnly = 2 + } +} diff --git a/src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs b/src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs index 821f358f5..2dceb025c 100644 --- a/src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs +++ b/src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs @@ -38,5 +38,21 @@ namespace Discord /// /// Thrown if the value does not fall within [0, 21600]. public Optional SlowModeInterval { get; set; } + + /// + /// Gets or sets whether or not the thread is archived. + /// + public Optional Archived { get; set; } + + /// + /// Gets or sets whether or not the thread is locked. + /// + public Optional Locked { get; set; } + + /// + /// Gets or sets the auto archive duration. + /// + public Optional AutoArchiveDuration { get; set; } + } } diff --git a/src/Discord.Net.Core/Entities/Channels/ThreadArchiveDuration.cs b/src/Discord.Net.Core/Entities/Channels/ThreadArchiveDuration.cs new file mode 100644 index 000000000..2c8a0652c --- /dev/null +++ b/src/Discord.Net.Core/Entities/Channels/ThreadArchiveDuration.cs @@ -0,0 +1,34 @@ +namespace Discord +{ + /// + /// Represents the thread auto archive duration. + /// + public enum ThreadArchiveDuration + { + /// + /// One hour (60 minutes). + /// + OneHour = 60, + + /// + /// One day (1440 minutes). + /// + OneDay = 1440, + + /// + /// Three days (4320 minutes). + /// + /// This option is explicitly available to nitro users. + /// + /// + ThreeDays = 4320, + + /// + /// One week (10080 minutes). + /// + /// This option is explicitly available to nitro users. + /// + /// + OneWeek = 10080 + } +} diff --git a/src/Discord.Net.Core/Entities/Channels/ThreadType.cs b/src/Discord.Net.Core/Entities/Channels/ThreadType.cs new file mode 100644 index 000000000..379128d21 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Channels/ThreadType.cs @@ -0,0 +1,23 @@ +namespace Discord +{ + /// + /// Represents types of threads. + /// + public enum ThreadType + { + /// + /// Represents a temporary sub-channel within a GUILD_NEWS channel. + /// + NewsThread = 10, + + /// + /// Represents a temporary sub-channel within a GUILD_TEXT channel. + /// + PublicThread = 11, + + /// + /// Represents a temporary sub-channel within a GUILD_TEXT channel that is only viewable by those invited and those with the MANAGE_THREADS permission + /// + PrivateThread = 12 + } +} diff --git a/src/Discord.Net.Core/Entities/Channels/VoiceChannelProperties.cs b/src/Discord.Net.Core/Entities/Channels/VoiceChannelProperties.cs index fb4d47800..251a45c3d 100644 --- a/src/Discord.Net.Core/Entities/Channels/VoiceChannelProperties.cs +++ b/src/Discord.Net.Core/Entities/Channels/VoiceChannelProperties.cs @@ -13,5 +13,9 @@ namespace Discord /// Gets or sets the maximum number of users that can be present in a channel, or null if none. /// public Optional UserLimit { get; set; } + /// + /// Gets or sets the channel voice region id, automatic when set to . + /// + public Optional RTCRegion { get; set; } } } diff --git a/src/Discord.Net.Core/Entities/Emotes/Emoji.cs b/src/Discord.Net.Core/Entities/Emotes/Emoji.cs index d5e795094..15c20148e 100644 --- a/src/Discord.Net.Core/Entities/Emotes/Emoji.cs +++ b/src/Discord.Net.Core/Entities/Emotes/Emoji.cs @@ -1,3 +1,9 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Collections.ObjectModel; +using System.Linq; + namespace Discord { /// @@ -5,12 +11,11 @@ namespace Discord /// public class Emoji : IEmote { - // TODO: need to constrain this to Unicode-only emojis somehow - /// public string Name { get; } + /// - /// Gets the Unicode representation of this emote. + /// Gets the Unicode representation of this emoji. /// /// /// A string that resolves to . @@ -32,16 +37,5940 @@ namespace Discord /// The object to compare with the current object. public override bool Equals(object other) { - if (other == null) return false; - if (other == this) return true; + if (other == null) + return false; + + if (other == this) + return true; + + return other is Emoji otherEmoji && string.Equals(Name, otherEmoji.Name); + } + + /// Tries to parse an from its raw format. + /// The raw encoding of an emoji. For example: :heart: or ❤ + /// An emoji. + public static bool TryParse(string text, out Emoji result) + { + result = null; + if (string.IsNullOrWhiteSpace(text)) + return false; + + if (NamesAndUnicodes.ContainsKey(text)) + result = new Emoji(NamesAndUnicodes[text]); + + if (Unicodes.Contains(text)) + result = new Emoji(text); + + return result != null; + } - var otherEmoji = other as Emoji; - if (otherEmoji == null) return false; + /// Parse an from its raw format. + /// The raw encoding of an emoji. For example: :heart: or ❤ + /// String is not emoji or unicode! + public static Emoji Parse(string emojiStr) + { + if (!TryParse(emojiStr, out var emoji)) + throw new FormatException("String is not emoji name or unicode!"); - return string.Equals(Name, otherEmoji.Name); + return emoji; } /// public override int GetHashCode() => Name.GetHashCode(); + + private static IReadOnlyDictionary NamesAndUnicodes { get; } = new Dictionary + { + [",:("] = "\uD83D\uDE13", + [",:)"] = "\uD83D\uDE05", + [",:-("] = "\uD83D\uDE13", + [",:-)"] = "\uD83D\uDE05", + [",=("] = "\uD83D\uDE13", + [",=)"] = "\uD83D\uDE05", + [",=-("] = "\uD83D\uDE13", + [",=-)"] = "\uD83D\uDE05", + ["0:)"] = "\uD83D\uDE07", + ["0:-)"] = "\uD83D\uDE07", + ["0=)"] = "\uD83D\uDE07", + ["0=-)"] = "\uD83D\uDE07", + ["8-)"] = "\uD83D\uDE0E", + [":$"] = "\uD83D\uDE12", + [":'("] = "\uD83D\uDE22", + [":')"] = "\uD83D\uDE02", + [":'-("] = "\uD83D\uDE22", + [":'-)"] = "\uD83D\uDE02", + [":'-D"] = "\uD83D\uDE02", + [":'D"] = "\uD83D\uDE02", + [":("] = "\uD83D\uDE26", + [":)"] = "\uD83D\uDE42", + [":*"] = "\uD83D\uDE17", + [":+1:"] = "\uD83D\uDC4D", + [":+1::skin-tone-1:"] = "\uD83D\uDC4D\uD83C\uDFFB", + [":+1::skin-tone-2:"] = "\uD83D\uDC4D\uD83C\uDFFC", + [":+1::skin-tone-3:"] = "\uD83D\uDC4D\uD83C\uDFFD", + [":+1::skin-tone-4:"] = "\uD83D\uDC4D\uD83C\uDFFE", + [":+1::skin-tone-5:"] = "\uD83D\uDC4D\uD83C\uDFFF", + [":+1_tone1:"] = "\uD83D\uDC4D\uD83C\uDFFB", + [":+1_tone2:"] = "\uD83D\uDC4D\uD83C\uDFFC", + [":+1_tone3:"] = "\uD83D\uDC4D\uD83C\uDFFD", + [":+1_tone4:"] = "\uD83D\uDC4D\uD83C\uDFFE", + [":+1_tone5:"] = "\uD83D\uDC4D\uD83C\uDFFF", + [":,'("] = "\uD83D\uDE2D", + [":,'-("] = "\uD83D\uDE2D", + [":,("] = "\uD83D\uDE22", + [":,)"] = "\uD83D\uDE02", + [":,-("] = "\uD83D\uDE22", + [":,-)"] = "\uD83D\uDE02", + [":,-D"] = "\uD83D\uDE02", + [":,D"] = "\uD83D\uDE02", + [":-$"] = "\uD83D\uDE12", + [":-("] = "\uD83D\uDE26", + [":-)"] = "\uD83D\uDE42", + [":-*"] = "\uD83D\uDE17", + [":-/"] = "\uD83D\uDE15", + [":-1:"] = "\uD83D\uDC4E", + [":-1::skin-tone-1:"] = "\uD83D\uDC4E\uD83C\uDFFB", + [":-1::skin-tone-2:"] = "\uD83D\uDC4E\uD83C\uDFFC", + [":-1::skin-tone-3:"] = "\uD83D\uDC4E\uD83C\uDFFD", + [":-1::skin-tone-4:"] = "\uD83D\uDC4E\uD83C\uDFFE", + [":-1::skin-tone-5:"] = "\uD83D\uDC4E\uD83C\uDFFF", + [":-@"] = "\uD83D\uDE21", + [":-D"] = "\uD83D\uDE04", + [":-O"] = "\uD83D\uDE2E", + [":-P"] = "\uD83D\uDE1B", + [":-S"] = "\uD83D\uDE12", + [":-Z"] = "\uD83D\uDE12", + [":-\")"] = "\uD83D\uDE0A", + [":-\\"] = "\uD83D\uDE15", + [":-o"] = "\uD83D\uDE2E", + [":-|"] = "\uD83D\uDE10", + [":100:"] = "\uD83D\uDCAF", + [":1234:"] = "\uD83D\uDD22", + [":8ball:"] = "\uD83C\uDFB1", + [":@"] = "\uD83D\uDE21", + [":D"] = "\uD83D\uDE04", + [":O"] = "\uD83D\uDE2E", + [":P"] = "\uD83D\uDE1B", + [":\")"] = "\uD83D\uDE0A", + [":_1_tone1:"] = "\uD83D\uDC4E\uD83C\uDFFB", + [":_1_tone2:"] = "\uD83D\uDC4E\uD83C\uDFFC", + [":_1_tone3:"] = "\uD83D\uDC4E\uD83C\uDFFD", + [":_1_tone4:"] = "\uD83D\uDC4E\uD83C\uDFFE", + [":_1_tone5:"] = "\uD83D\uDC4E\uD83C\uDFFF", + [":a:"] = "\uD83C\uDD70️", + [":ab:"] = "\uD83C\uDD8E", + [":abacus:"] = "\uD83E\uDDEE", + [":abc:"] = "\uD83D\uDD24", + [":abcd:"] = "\uD83D\uDD21", + [":accept:"] = "\uD83C\uDE51", + [":adhesive_bandage:"] = "\uD83E\uDE79", + [":admission_tickets:"] = "\uD83C\uDF9F️", + [":adult:"] = "\uD83E\uDDD1", + [":adult::skin-tone-1:"] = "\uD83E\uDDD1\uD83C\uDFFB", + [":adult::skin-tone-2:"] = "\uD83E\uDDD1\uD83C\uDFFC", + [":adult::skin-tone-3:"] = "\uD83E\uDDD1\uD83C\uDFFD", + [":adult::skin-tone-4:"] = "\uD83E\uDDD1\uD83C\uDFFE", + [":adult::skin-tone-5:"] = "\uD83E\uDDD1\uD83C\uDFFF", + [":adult_dark_skin_tone:"] = "\uD83E\uDDD1\uD83C\uDFFF", + [":adult_light_skin_tone:"] = "\uD83E\uDDD1\uD83C\uDFFB", + [":adult_medium_dark_skin_tone:"] = "\uD83E\uDDD1\uD83C\uDFFE", + [":adult_medium_light_skin_tone:"] = "\uD83E\uDDD1\uD83C\uDFFC", + [":adult_medium_skin_tone:"] = "\uD83E\uDDD1\uD83C\uDFFD", + [":adult_tone1:"] = "\uD83E\uDDD1\uD83C\uDFFB", + [":adult_tone2:"] = "\uD83E\uDDD1\uD83C\uDFFC", + [":adult_tone3:"] = "\uD83E\uDDD1\uD83C\uDFFD", + [":adult_tone4:"] = "\uD83E\uDDD1\uD83C\uDFFE", + [":adult_tone5:"] = "\uD83E\uDDD1\uD83C\uDFFF", + [":aerial_tramway:"] = "\uD83D\uDEA1", + [":airplane:"] = "✈️", + [":airplane_arriving:"] = "\uD83D\uDEEC", + [":airplane_departure:"] = "\uD83D\uDEEB", + [":airplane_small:"] = "\uD83D\uDEE9️", + [":alarm_clock:"] = "⏰", + [":alembic:"] = "⚗️", + [":alien:"] = "\uD83D\uDC7D", + [":ambulance:"] = "\uD83D\uDE91", + [":amphora:"] = "\uD83C\uDFFA", + [":anchor:"] = "⚓", + [":angel:"] = "\uD83D\uDC7C", + [":angel::skin-tone-1:"] = "\uD83D\uDC7C\uD83C\uDFFB", + [":angel::skin-tone-2:"] = "\uD83D\uDC7C\uD83C\uDFFC", + [":angel::skin-tone-3:"] = "\uD83D\uDC7C\uD83C\uDFFD", + [":angel::skin-tone-4:"] = "\uD83D\uDC7C\uD83C\uDFFE", + [":angel::skin-tone-5:"] = "\uD83D\uDC7C\uD83C\uDFFF", + [":angel_tone1:"] = "\uD83D\uDC7C\uD83C\uDFFB", + [":angel_tone2:"] = "\uD83D\uDC7C\uD83C\uDFFC", + [":angel_tone3:"] = "\uD83D\uDC7C\uD83C\uDFFD", + [":angel_tone4:"] = "\uD83D\uDC7C\uD83C\uDFFE", + [":angel_tone5:"] = "\uD83D\uDC7C\uD83C\uDFFF", + [":anger:"] = "\uD83D\uDCA2", + [":anger_right:"] = "\uD83D\uDDEF️", + [":angry:"] = "\uD83D\uDE20", + [":anguished:"] = "\uD83D\uDE27", + [":ant:"] = "\uD83D\uDC1C", + [":apple:"] = "\uD83C\uDF4E", + [":aquarius:"] = "♒", + [":archery:"] = "\uD83C\uDFF9", + [":aries:"] = "♈", + [":arrow_backward:"] = "◀️", + [":arrow_double_down:"] = "⏬", + [":arrow_double_up:"] = "⏫", + [":arrow_down:"] = "⬇️", + [":arrow_down_small:"] = "\uD83D\uDD3D", + [":arrow_forward:"] = "▶️", + [":arrow_heading_down:"] = "⤵️", + [":arrow_heading_up:"] = "⤴️", + [":arrow_left:"] = "⬅️", + [":arrow_lower_left:"] = "↙️", + [":arrow_lower_right:"] = "↘️", + [":arrow_right:"] = "➡️", + [":arrow_right_hook:"] = "↪️", + [":arrow_up:"] = "⬆️", + [":arrow_up_down:"] = "↕️", + [":arrow_up_small:"] = "\uD83D\uDD3C", + [":arrow_upper_left:"] = "↖️", + [":arrow_upper_right:"] = "↗️", + [":arrows_clockwise:"] = "\uD83D\uDD03", + [":arrows_counterclockwise:"] = "\uD83D\uDD04", + [":art:"] = "\uD83C\uDFA8", + [":articulated_lorry:"] = "\uD83D\uDE9B", + [":asterisk:"] = "*️⃣", + [":astonished:"] = "\uD83D\uDE32", + [":athletic_shoe:"] = "\uD83D\uDC5F", + [":atm:"] = "\uD83C\uDFE7", + [":atom:"] = "⚛️", + [":atom_symbol:"] = "⚛️", + [":auto_rickshaw:"] = "\uD83D\uDEFA", + [":avocado:"] = "\uD83E\uDD51", + [":axe:"] = "\uD83E\uDE93", + [":b:"] = "\uD83C\uDD71️", + [":baby:"] = "\uD83D\uDC76", + [":baby::skin-tone-1:"] = "\uD83D\uDC76\uD83C\uDFFB", + [":baby::skin-tone-2:"] = "\uD83D\uDC76\uD83C\uDFFC", + [":baby::skin-tone-3:"] = "\uD83D\uDC76\uD83C\uDFFD", + [":baby::skin-tone-4:"] = "\uD83D\uDC76\uD83C\uDFFE", + [":baby::skin-tone-5:"] = "\uD83D\uDC76\uD83C\uDFFF", + [":baby_bottle:"] = "\uD83C\uDF7C", + [":baby_chick:"] = "\uD83D\uDC24", + [":baby_symbol:"] = "\uD83D\uDEBC", + [":baby_tone1:"] = "\uD83D\uDC76\uD83C\uDFFB", + [":baby_tone2:"] = "\uD83D\uDC76\uD83C\uDFFC", + [":baby_tone3:"] = "\uD83D\uDC76\uD83C\uDFFD", + [":baby_tone4:"] = "\uD83D\uDC76\uD83C\uDFFE", + [":baby_tone5:"] = "\uD83D\uDC76\uD83C\uDFFF", + [":back:"] = "\uD83D\uDD19", + [":back_of_hand:"] = "\uD83E\uDD1A", + [":back_of_hand::skin-tone-1:"] = "\uD83E\uDD1A\uD83C\uDFFB", + [":back_of_hand::skin-tone-2:"] = "\uD83E\uDD1A\uD83C\uDFFC", + [":back_of_hand::skin-tone-3:"] = "\uD83E\uDD1A\uD83C\uDFFD", + [":back_of_hand::skin-tone-4:"] = "\uD83E\uDD1A\uD83C\uDFFE", + [":back_of_hand::skin-tone-5:"] = "\uD83E\uDD1A\uD83C\uDFFF", + [":back_of_hand_tone1:"] = "\uD83E\uDD1A\uD83C\uDFFB", + [":back_of_hand_tone2:"] = "\uD83E\uDD1A\uD83C\uDFFC", + [":back_of_hand_tone3:"] = "\uD83E\uDD1A\uD83C\uDFFD", + [":back_of_hand_tone4:"] = "\uD83E\uDD1A\uD83C\uDFFE", + [":back_of_hand_tone5:"] = "\uD83E\uDD1A\uD83C\uDFFF", + [":bacon:"] = "\uD83E\uDD53", + [":badger:"] = "\uD83E\uDDA1", + [":badminton:"] = "\uD83C\uDFF8", + [":bagel:"] = "\uD83E\uDD6F", + [":baggage_claim:"] = "\uD83D\uDEC4", + [":baguette_bread:"] = "\uD83E\uDD56", + [":ballet_shoes:"] = "\uD83E\uDE70", + [":balloon:"] = "\uD83C\uDF88", + [":ballot_box:"] = "\uD83D\uDDF3️", + [":ballot_box_with_ballot:"] = "\uD83D\uDDF3️", + [":ballot_box_with_check:"] = "☑️", + [":bamboo:"] = "\uD83C\uDF8D", + [":banana:"] = "\uD83C\uDF4C", + [":bangbang:"] = "‼️", + [":banjo:"] = "\uD83E\uDE95", + [":bank:"] = "\uD83C\uDFE6", + [":bar_chart:"] = "\uD83D\uDCCA", + [":barber:"] = "\uD83D\uDC88", + [":baseball:"] = "⚾", + [":basket:"] = "\uD83E\uDDFA", + [":basketball:"] = "\uD83C\uDFC0", + [":basketball_player:"] = "⛹️", + [":basketball_player::skin-tone-1:"] = "⛹\uD83C\uDFFB", + [":basketball_player::skin-tone-2:"] = "⛹\uD83C\uDFFC", + [":basketball_player::skin-tone-3:"] = "⛹\uD83C\uDFFD", + [":basketball_player::skin-tone-4:"] = "⛹\uD83C\uDFFE", + [":basketball_player::skin-tone-5:"] = "⛹\uD83C\uDFFF", + [":basketball_player_tone1:"] = "⛹\uD83C\uDFFB", + [":basketball_player_tone2:"] = "⛹\uD83C\uDFFC", + [":basketball_player_tone3:"] = "⛹\uD83C\uDFFD", + [":basketball_player_tone4:"] = "⛹\uD83C\uDFFE", + [":basketball_player_tone5:"] = "⛹\uD83C\uDFFF", + [":bat:"] = "\uD83E\uDD87", + [":bath:"] = "\uD83D\uDEC0", + [":bath::skin-tone-1:"] = "\uD83D\uDEC0\uD83C\uDFFB", + [":bath::skin-tone-2:"] = "\uD83D\uDEC0\uD83C\uDFFC", + [":bath::skin-tone-3:"] = "\uD83D\uDEC0\uD83C\uDFFD", + [":bath::skin-tone-4:"] = "\uD83D\uDEC0\uD83C\uDFFE", + [":bath::skin-tone-5:"] = "\uD83D\uDEC0\uD83C\uDFFF", + [":bath_tone1:"] = "\uD83D\uDEC0\uD83C\uDFFB", + [":bath_tone2:"] = "\uD83D\uDEC0\uD83C\uDFFC", + [":bath_tone3:"] = "\uD83D\uDEC0\uD83C\uDFFD", + [":bath_tone4:"] = "\uD83D\uDEC0\uD83C\uDFFE", + [":bath_tone5:"] = "\uD83D\uDEC0\uD83C\uDFFF", + [":bathtub:"] = "\uD83D\uDEC1", + [":battery:"] = "\uD83D\uDD0B", + [":beach:"] = "\uD83C\uDFD6️", + [":beach_umbrella:"] = "⛱️", + [":beach_with_umbrella:"] = "\uD83C\uDFD6️", + [":bear:"] = "\uD83D\uDC3B", + [":bearded_person:"] = "\uD83E\uDDD4", + [":bearded_person::skin-tone-1:"] = "\uD83E\uDDD4\uD83C\uDFFB", + [":bearded_person::skin-tone-2:"] = "\uD83E\uDDD4\uD83C\uDFFC", + [":bearded_person::skin-tone-3:"] = "\uD83E\uDDD4\uD83C\uDFFD", + [":bearded_person::skin-tone-4:"] = "\uD83E\uDDD4\uD83C\uDFFE", + [":bearded_person::skin-tone-5:"] = "\uD83E\uDDD4\uD83C\uDFFF", + [":bearded_person_dark_skin_tone:"] = "\uD83E\uDDD4\uD83C\uDFFF", + [":bearded_person_light_skin_tone:"] = "\uD83E\uDDD4\uD83C\uDFFB", + [":bearded_person_medium_dark_skin_tone:"] = "\uD83E\uDDD4\uD83C\uDFFE", + [":bearded_person_medium_light_skin_tone:"] = "\uD83E\uDDD4\uD83C\uDFFC", + [":bearded_person_medium_skin_tone:"] = "\uD83E\uDDD4\uD83C\uDFFD", + [":bearded_person_tone1:"] = "\uD83E\uDDD4\uD83C\uDFFB", + [":bearded_person_tone2:"] = "\uD83E\uDDD4\uD83C\uDFFC", + [":bearded_person_tone3:"] = "\uD83E\uDDD4\uD83C\uDFFD", + [":bearded_person_tone4:"] = "\uD83E\uDDD4\uD83C\uDFFE", + [":bearded_person_tone5:"] = "\uD83E\uDDD4\uD83C\uDFFF", + [":bed:"] = "\uD83D\uDECF️", + [":bee:"] = "\uD83D\uDC1D", + [":beer:"] = "\uD83C\uDF7A", + [":beers:"] = "\uD83C\uDF7B", + [":beetle:"] = "\uD83D\uDC1E", + [":beginner:"] = "\uD83D\uDD30", + [":bell:"] = "\uD83D\uDD14", + [":bellhop:"] = "\uD83D\uDECE️", + [":bellhop_bell:"] = "\uD83D\uDECE️", + [":bento:"] = "\uD83C\uDF71", + [":beverage_box:"] = "\uD83E\uDDC3", + [":bicyclist:"] = "\uD83D\uDEB4", + [":bicyclist::skin-tone-1:"] = "\uD83D\uDEB4\uD83C\uDFFB", + [":bicyclist::skin-tone-2:"] = "\uD83D\uDEB4\uD83C\uDFFC", + [":bicyclist::skin-tone-3:"] = "\uD83D\uDEB4\uD83C\uDFFD", + [":bicyclist::skin-tone-4:"] = "\uD83D\uDEB4\uD83C\uDFFE", + [":bicyclist::skin-tone-5:"] = "\uD83D\uDEB4\uD83C\uDFFF", + [":bicyclist_tone1:"] = "\uD83D\uDEB4\uD83C\uDFFB", + [":bicyclist_tone2:"] = "\uD83D\uDEB4\uD83C\uDFFC", + [":bicyclist_tone3:"] = "\uD83D\uDEB4\uD83C\uDFFD", + [":bicyclist_tone4:"] = "\uD83D\uDEB4\uD83C\uDFFE", + [":bicyclist_tone5:"] = "\uD83D\uDEB4\uD83C\uDFFF", + [":bike:"] = "\uD83D\uDEB2", + [":bikini:"] = "\uD83D\uDC59", + [":billed_cap:"] = "\uD83E\uDDE2", + [":biohazard:"] = "☣️", + [":biohazard_sign:"] = "☣️", + [":bird:"] = "\uD83D\uDC26", + [":birthday:"] = "\uD83C\uDF82", + [":black_circle:"] = "⚫", + [":black_heart:"] = "\uD83D\uDDA4", + [":black_joker:"] = "\uD83C\uDCCF", + [":black_large_square:"] = "⬛", + [":black_medium_small_square:"] = "◾", + [":black_medium_square:"] = "◼️", + [":black_nib:"] = "✒️", + [":black_small_square:"] = "▪️", + [":black_square_button:"] = "\uD83D\uDD32", + [":blond_haired_man:"] = "\uD83D\uDC71\u200D♂️", + [":blond_haired_man::skin-tone-1:"] = "\uD83D\uDC71\uD83C\uDFFB\u200D♂️", + [":blond_haired_man::skin-tone-2:"] = "\uD83D\uDC71\uD83C\uDFFC\u200D♂️", + [":blond_haired_man::skin-tone-3:"] = "\uD83D\uDC71\uD83C\uDFFD\u200D♂️", + [":blond_haired_man::skin-tone-4:"] = "\uD83D\uDC71\uD83C\uDFFE\u200D♂️", + [":blond_haired_man::skin-tone-5:"] = "\uD83D\uDC71\uD83C\uDFFF\u200D♂️", + [":blond_haired_man_dark_skin_tone:"] = "\uD83D\uDC71\uD83C\uDFFF\u200D♂️", + [":blond_haired_man_light_skin_tone:"] = "\uD83D\uDC71\uD83C\uDFFB\u200D♂️", + [":blond_haired_man_medium_dark_skin_tone:"] = "\uD83D\uDC71\uD83C\uDFFE\u200D♂️", + [":blond_haired_man_medium_light_skin_tone:"] = "\uD83D\uDC71\uD83C\uDFFC\u200D♂️", + [":blond_haired_man_medium_skin_tone:"] = "\uD83D\uDC71\uD83C\uDFFD\u200D♂️", + [":blond_haired_man_tone1:"] = "\uD83D\uDC71\uD83C\uDFFB\u200D♂️", + [":blond_haired_man_tone2:"] = "\uD83D\uDC71\uD83C\uDFFC\u200D♂️", + [":blond_haired_man_tone3:"] = "\uD83D\uDC71\uD83C\uDFFD\u200D♂️", + [":blond_haired_man_tone4:"] = "\uD83D\uDC71\uD83C\uDFFE\u200D♂️", + [":blond_haired_man_tone5:"] = "\uD83D\uDC71\uD83C\uDFFF\u200D♂️", + [":blond_haired_person:"] = "\uD83D\uDC71", + [":blond_haired_person::skin-tone-1:"] = "\uD83D\uDC71\uD83C\uDFFB", + [":blond_haired_person::skin-tone-2:"] = "\uD83D\uDC71\uD83C\uDFFC", + [":blond_haired_person::skin-tone-3:"] = "\uD83D\uDC71\uD83C\uDFFD", + [":blond_haired_person::skin-tone-4:"] = "\uD83D\uDC71\uD83C\uDFFE", + [":blond_haired_person::skin-tone-5:"] = "\uD83D\uDC71\uD83C\uDFFF", + [":blond_haired_person_tone1:"] = "\uD83D\uDC71\uD83C\uDFFB", + [":blond_haired_person_tone2:"] = "\uD83D\uDC71\uD83C\uDFFC", + [":blond_haired_person_tone3:"] = "\uD83D\uDC71\uD83C\uDFFD", + [":blond_haired_person_tone4:"] = "\uD83D\uDC71\uD83C\uDFFE", + [":blond_haired_person_tone5:"] = "\uD83D\uDC71\uD83C\uDFFF", + [":blond_haired_woman:"] = "\uD83D\uDC71\u200D♀️", + [":blond_haired_woman::skin-tone-1:"] = "\uD83D\uDC71\uD83C\uDFFB\u200D♀️", + [":blond_haired_woman::skin-tone-2:"] = "\uD83D\uDC71\uD83C\uDFFC\u200D♀️", + [":blond_haired_woman::skin-tone-3:"] = "\uD83D\uDC71\uD83C\uDFFD\u200D♀️", + [":blond_haired_woman::skin-tone-4:"] = "\uD83D\uDC71\uD83C\uDFFE\u200D♀️", + [":blond_haired_woman::skin-tone-5:"] = "\uD83D\uDC71\uD83C\uDFFF\u200D♀️", + [":blond_haired_woman_dark_skin_tone:"] = "\uD83D\uDC71\uD83C\uDFFF\u200D♀️", + [":blond_haired_woman_light_skin_tone:"] = "\uD83D\uDC71\uD83C\uDFFB\u200D♀️", + [":blond_haired_woman_medium_dark_skin_tone:"] = "\uD83D\uDC71\uD83C\uDFFE\u200D♀️", + [":blond_haired_woman_medium_light_skin_tone:"] = "\uD83D\uDC71\uD83C\uDFFC\u200D♀️", + [":blond_haired_woman_medium_skin_tone:"] = "\uD83D\uDC71\uD83C\uDFFD\u200D♀️", + [":blond_haired_woman_tone1:"] = "\uD83D\uDC71\uD83C\uDFFB\u200D♀️", + [":blond_haired_woman_tone2:"] = "\uD83D\uDC71\uD83C\uDFFC\u200D♀️", + [":blond_haired_woman_tone3:"] = "\uD83D\uDC71\uD83C\uDFFD\u200D♀️", + [":blond_haired_woman_tone4:"] = "\uD83D\uDC71\uD83C\uDFFE\u200D♀️", + [":blond_haired_woman_tone5:"] = "\uD83D\uDC71\uD83C\uDFFF\u200D♀️", + [":blossom:"] = "\uD83C\uDF3C", + [":blowfish:"] = "\uD83D\uDC21", + [":blue_book:"] = "\uD83D\uDCD8", + [":blue_car:"] = "\uD83D\uDE99", + [":blue_circle:"] = "\uD83D\uDD35", + [":blue_heart:"] = "\uD83D\uDC99", + [":blue_square:"] = "\uD83D\uDFE6", + [":blush:"] = "\uD83D\uDE0A", + [":boar:"] = "\uD83D\uDC17", + [":bomb:"] = "\uD83D\uDCA3", + [":bone:"] = "\uD83E\uDDB4", + [":book:"] = "\uD83D\uDCD6", + [":bookmark:"] = "\uD83D\uDD16", + [":bookmark_tabs:"] = "\uD83D\uDCD1", + [":books:"] = "\uD83D\uDCDA", + [":boom:"] = "\uD83D\uDCA5", + [":boot:"] = "\uD83D\uDC62", + [":bottle_with_popping_cork:"] = "\uD83C\uDF7E", + [":bouquet:"] = "\uD83D\uDC90", + [":bow:"] = "\uD83D\uDE47", + [":bow::skin-tone-1:"] = "\uD83D\uDE47\uD83C\uDFFB", + [":bow::skin-tone-2:"] = "\uD83D\uDE47\uD83C\uDFFC", + [":bow::skin-tone-3:"] = "\uD83D\uDE47\uD83C\uDFFD", + [":bow::skin-tone-4:"] = "\uD83D\uDE47\uD83C\uDFFE", + [":bow::skin-tone-5:"] = "\uD83D\uDE47\uD83C\uDFFF", + [":bow_and_arrow:"] = "\uD83C\uDFF9", + [":bow_tone1:"] = "\uD83D\uDE47\uD83C\uDFFB", + [":bow_tone2:"] = "\uD83D\uDE47\uD83C\uDFFC", + [":bow_tone3:"] = "\uD83D\uDE47\uD83C\uDFFD", + [":bow_tone4:"] = "\uD83D\uDE47\uD83C\uDFFE", + [":bow_tone5:"] = "\uD83D\uDE47\uD83C\uDFFF", + [":bowl_with_spoon:"] = "\uD83E\uDD63", + [":bowling:"] = "\uD83C\uDFB3", + [":boxing_glove:"] = "\uD83E\uDD4A", + [":boxing_gloves:"] = "\uD83E\uDD4A", + [":boy:"] = "\uD83D\uDC66", + [":boy::skin-tone-1:"] = "\uD83D\uDC66\uD83C\uDFFB", + [":boy::skin-tone-2:"] = "\uD83D\uDC66\uD83C\uDFFC", + [":boy::skin-tone-3:"] = "\uD83D\uDC66\uD83C\uDFFD", + [":boy::skin-tone-4:"] = "\uD83D\uDC66\uD83C\uDFFE", + [":boy::skin-tone-5:"] = "\uD83D\uDC66\uD83C\uDFFF", + [":boy_tone1:"] = "\uD83D\uDC66\uD83C\uDFFB", + [":boy_tone2:"] = "\uD83D\uDC66\uD83C\uDFFC", + [":boy_tone3:"] = "\uD83D\uDC66\uD83C\uDFFD", + [":boy_tone4:"] = "\uD83D\uDC66\uD83C\uDFFE", + [":boy_tone5:"] = "\uD83D\uDC66\uD83C\uDFFF", + [":brain:"] = "\uD83E\uDDE0", + [":bread:"] = "\uD83C\uDF5E", + [":breast_feeding:"] = "\uD83E\uDD31", + [":breast_feeding::skin-tone-1:"] = "\uD83E\uDD31\uD83C\uDFFB", + [":breast_feeding::skin-tone-2:"] = "\uD83E\uDD31\uD83C\uDFFC", + [":breast_feeding::skin-tone-3:"] = "\uD83E\uDD31\uD83C\uDFFD", + [":breast_feeding::skin-tone-4:"] = "\uD83E\uDD31\uD83C\uDFFE", + [":breast_feeding::skin-tone-5:"] = "\uD83E\uDD31\uD83C\uDFFF", + [":breast_feeding_dark_skin_tone:"] = "\uD83E\uDD31\uD83C\uDFFF", + [":breast_feeding_light_skin_tone:"] = "\uD83E\uDD31\uD83C\uDFFB", + [":breast_feeding_medium_dark_skin_tone:"] = "\uD83E\uDD31\uD83C\uDFFE", + [":breast_feeding_medium_light_skin_tone:"] = "\uD83E\uDD31\uD83C\uDFFC", + [":breast_feeding_medium_skin_tone:"] = "\uD83E\uDD31\uD83C\uDFFD", + [":breast_feeding_tone1:"] = "\uD83E\uDD31\uD83C\uDFFB", + [":breast_feeding_tone2:"] = "\uD83E\uDD31\uD83C\uDFFC", + [":breast_feeding_tone3:"] = "\uD83E\uDD31\uD83C\uDFFD", + [":breast_feeding_tone4:"] = "\uD83E\uDD31\uD83C\uDFFE", + [":breast_feeding_tone5:"] = "\uD83E\uDD31\uD83C\uDFFF", + [":bricks:"] = "\uD83E\uDDF1", + [":bride_with_veil:"] = "\uD83D\uDC70", + [":bride_with_veil::skin-tone-1:"] = "\uD83D\uDC70\uD83C\uDFFB", + [":bride_with_veil::skin-tone-2:"] = "\uD83D\uDC70\uD83C\uDFFC", + [":bride_with_veil::skin-tone-3:"] = "\uD83D\uDC70\uD83C\uDFFD", + [":bride_with_veil::skin-tone-4:"] = "\uD83D\uDC70\uD83C\uDFFE", + [":bride_with_veil::skin-tone-5:"] = "\uD83D\uDC70\uD83C\uDFFF", + [":bride_with_veil_tone1:"] = "\uD83D\uDC70\uD83C\uDFFB", + [":bride_with_veil_tone2:"] = "\uD83D\uDC70\uD83C\uDFFC", + [":bride_with_veil_tone3:"] = "\uD83D\uDC70\uD83C\uDFFD", + [":bride_with_veil_tone4:"] = "\uD83D\uDC70\uD83C\uDFFE", + [":bride_with_veil_tone5:"] = "\uD83D\uDC70\uD83C\uDFFF", + [":bridge_at_night:"] = "\uD83C\uDF09", + [":briefcase:"] = "\uD83D\uDCBC", + [":briefs:"] = "\uD83E\uDE72", + [":broccoli:"] = "\uD83E\uDD66", + [":broken_heart:"] = "\uD83D\uDC94", + [":broom:"] = "\uD83E\uDDF9", + [":brown_circle:"] = "\uD83D\uDFE4", + [":brown_heart:"] = "\uD83E\uDD0E", + [":brown_square:"] = "\uD83D\uDFEB", + [":bug:"] = "\uD83D\uDC1B", + [":building_construction:"] = "\uD83C\uDFD7️", + [":bulb:"] = "\uD83D\uDCA1", + [":bullettrain_front:"] = "\uD83D\uDE85", + [":bullettrain_side:"] = "\uD83D\uDE84", + [":burrito:"] = "\uD83C\uDF2F", + [":bus:"] = "\uD83D\uDE8C", + [":busstop:"] = "\uD83D\uDE8F", + [":bust_in_silhouette:"] = "\uD83D\uDC64", + [":busts_in_silhouette:"] = "\uD83D\uDC65", + [":butter:"] = "\uD83E\uDDC8", + [":butterfly:"] = "\uD83E\uDD8B", + [":cactus:"] = "\uD83C\uDF35", + [":cake:"] = "\uD83C\uDF70", + [":calendar:"] = "\uD83D\uDCC6", + [":calendar_spiral:"] = "\uD83D\uDDD3️", + [":call_me:"] = "\uD83E\uDD19", + [":call_me::skin-tone-1:"] = "\uD83E\uDD19\uD83C\uDFFB", + [":call_me::skin-tone-2:"] = "\uD83E\uDD19\uD83C\uDFFC", + [":call_me::skin-tone-3:"] = "\uD83E\uDD19\uD83C\uDFFD", + [":call_me::skin-tone-4:"] = "\uD83E\uDD19\uD83C\uDFFE", + [":call_me::skin-tone-5:"] = "\uD83E\uDD19\uD83C\uDFFF", + [":call_me_hand:"] = "\uD83E\uDD19", + [":call_me_hand::skin-tone-1:"] = "\uD83E\uDD19\uD83C\uDFFB", + [":call_me_hand::skin-tone-2:"] = "\uD83E\uDD19\uD83C\uDFFC", + [":call_me_hand::skin-tone-3:"] = "\uD83E\uDD19\uD83C\uDFFD", + [":call_me_hand::skin-tone-4:"] = "\uD83E\uDD19\uD83C\uDFFE", + [":call_me_hand::skin-tone-5:"] = "\uD83E\uDD19\uD83C\uDFFF", + [":call_me_hand_tone1:"] = "\uD83E\uDD19\uD83C\uDFFB", + [":call_me_hand_tone2:"] = "\uD83E\uDD19\uD83C\uDFFC", + [":call_me_hand_tone3:"] = "\uD83E\uDD19\uD83C\uDFFD", + [":call_me_hand_tone4:"] = "\uD83E\uDD19\uD83C\uDFFE", + [":call_me_hand_tone5:"] = "\uD83E\uDD19\uD83C\uDFFF", + [":call_me_tone1:"] = "\uD83E\uDD19\uD83C\uDFFB", + [":call_me_tone2:"] = "\uD83E\uDD19\uD83C\uDFFC", + [":call_me_tone3:"] = "\uD83E\uDD19\uD83C\uDFFD", + [":call_me_tone4:"] = "\uD83E\uDD19\uD83C\uDFFE", + [":call_me_tone5:"] = "\uD83E\uDD19\uD83C\uDFFF", + [":calling:"] = "\uD83D\uDCF2", + [":camel:"] = "\uD83D\uDC2B", + [":camera:"] = "\uD83D\uDCF7", + [":camera_with_flash:"] = "\uD83D\uDCF8", + [":camping:"] = "\uD83C\uDFD5️", + [":cancer:"] = "♋", + [":candle:"] = "\uD83D\uDD6F️", + [":candy:"] = "\uD83C\uDF6C", + [":canned_food:"] = "\uD83E\uDD6B", + [":canoe:"] = "\uD83D\uDEF6", + [":capital_abcd:"] = "\uD83D\uDD20", + [":capricorn:"] = "♑", + [":card_box:"] = "\uD83D\uDDC3️", + [":card_file_box:"] = "\uD83D\uDDC3️", + [":card_index:"] = "\uD83D\uDCC7", + [":card_index_dividers:"] = "\uD83D\uDDC2️", + [":carousel_horse:"] = "\uD83C\uDFA0", + [":carrot:"] = "\uD83E\uDD55", + [":cartwheel:"] = "\uD83E\uDD38", + [":cartwheel::skin-tone-1:"] = "\uD83E\uDD38\uD83C\uDFFB", + [":cartwheel::skin-tone-2:"] = "\uD83E\uDD38\uD83C\uDFFC", + [":cartwheel::skin-tone-3:"] = "\uD83E\uDD38\uD83C\uDFFD", + [":cartwheel::skin-tone-4:"] = "\uD83E\uDD38\uD83C\uDFFE", + [":cartwheel::skin-tone-5:"] = "\uD83E\uDD38\uD83C\uDFFF", + [":cartwheel_tone1:"] = "\uD83E\uDD38\uD83C\uDFFB", + [":cartwheel_tone2:"] = "\uD83E\uDD38\uD83C\uDFFC", + [":cartwheel_tone3:"] = "\uD83E\uDD38\uD83C\uDFFD", + [":cartwheel_tone4:"] = "\uD83E\uDD38\uD83C\uDFFE", + [":cartwheel_tone5:"] = "\uD83E\uDD38\uD83C\uDFFF", + [":cat2:"] = "\uD83D\uDC08", + [":cat:"] = "\uD83D\uDC31", + [":cd:"] = "\uD83D\uDCBF", + [":chains:"] = "⛓️", + [":chair:"] = "\uD83E\uDE91", + [":champagne:"] = "\uD83C\uDF7E", + [":champagne_glass:"] = "\uD83E\uDD42", + [":chart:"] = "\uD83D\uDCB9", + [":chart_with_downwards_trend:"] = "\uD83D\uDCC9", + [":chart_with_upwards_trend:"] = "\uD83D\uDCC8", + [":checkered_flag:"] = "\uD83C\uDFC1", + [":cheese:"] = "\uD83E\uDDC0", + [":cheese_wedge:"] = "\uD83E\uDDC0", + [":cherries:"] = "\uD83C\uDF52", + [":cherry_blossom:"] = "\uD83C\uDF38", + [":chess_pawn:"] = "♟️", + [":chestnut:"] = "\uD83C\uDF30", + [":chicken:"] = "\uD83D\uDC14", + [":child:"] = "\uD83E\uDDD2", + [":child::skin-tone-1:"] = "\uD83E\uDDD2\uD83C\uDFFB", + [":child::skin-tone-2:"] = "\uD83E\uDDD2\uD83C\uDFFC", + [":child::skin-tone-3:"] = "\uD83E\uDDD2\uD83C\uDFFD", + [":child::skin-tone-4:"] = "\uD83E\uDDD2\uD83C\uDFFE", + [":child::skin-tone-5:"] = "\uD83E\uDDD2\uD83C\uDFFF", + [":child_dark_skin_tone:"] = "\uD83E\uDDD2\uD83C\uDFFF", + [":child_light_skin_tone:"] = "\uD83E\uDDD2\uD83C\uDFFB", + [":child_medium_dark_skin_tone:"] = "\uD83E\uDDD2\uD83C\uDFFE", + [":child_medium_light_skin_tone:"] = "\uD83E\uDDD2\uD83C\uDFFC", + [":child_medium_skin_tone:"] = "\uD83E\uDDD2\uD83C\uDFFD", + [":child_tone1:"] = "\uD83E\uDDD2\uD83C\uDFFB", + [":child_tone2:"] = "\uD83E\uDDD2\uD83C\uDFFC", + [":child_tone3:"] = "\uD83E\uDDD2\uD83C\uDFFD", + [":child_tone4:"] = "\uD83E\uDDD2\uD83C\uDFFE", + [":child_tone5:"] = "\uD83E\uDDD2\uD83C\uDFFF", + [":children_crossing:"] = "\uD83D\uDEB8", + [":chipmunk:"] = "\uD83D\uDC3F️", + [":chocolate_bar:"] = "\uD83C\uDF6B", + [":chopsticks:"] = "\uD83E\uDD62", + [":christmas_tree:"] = "\uD83C\uDF84", + [":church:"] = "⛪", + [":cinema:"] = "\uD83C\uDFA6", + [":circus_tent:"] = "\uD83C\uDFAA", + [":city_dusk:"] = "\uD83C\uDF06", + [":city_sunrise:"] = "\uD83C\uDF07", + [":city_sunset:"] = "\uD83C\uDF07", + [":cityscape:"] = "\uD83C\uDFD9️", + [":cl:"] = "\uD83C\uDD91", + [":clap:"] = "\uD83D\uDC4F", + [":clap::skin-tone-1:"] = "\uD83D\uDC4F\uD83C\uDFFB", + [":clap::skin-tone-2:"] = "\uD83D\uDC4F\uD83C\uDFFC", + [":clap::skin-tone-3:"] = "\uD83D\uDC4F\uD83C\uDFFD", + [":clap::skin-tone-4:"] = "\uD83D\uDC4F\uD83C\uDFFE", + [":clap::skin-tone-5:"] = "\uD83D\uDC4F\uD83C\uDFFF", + [":clap_tone1:"] = "\uD83D\uDC4F\uD83C\uDFFB", + [":clap_tone2:"] = "\uD83D\uDC4F\uD83C\uDFFC", + [":clap_tone3:"] = "\uD83D\uDC4F\uD83C\uDFFD", + [":clap_tone4:"] = "\uD83D\uDC4F\uD83C\uDFFE", + [":clap_tone5:"] = "\uD83D\uDC4F\uD83C\uDFFF", + [":clapper:"] = "\uD83C\uDFAC", + [":classical_building:"] = "\uD83C\uDFDB️", + [":clinking_glass:"] = "\uD83E\uDD42", + [":clipboard:"] = "\uD83D\uDCCB", + [":clock1030:"] = "\uD83D\uDD65", + [":clock10:"] = "\uD83D\uDD59", + [":clock1130:"] = "\uD83D\uDD66", + [":clock11:"] = "\uD83D\uDD5A", + [":clock1230:"] = "\uD83D\uDD67", + [":clock12:"] = "\uD83D\uDD5B", + [":clock130:"] = "\uD83D\uDD5C", + [":clock1:"] = "\uD83D\uDD50", + [":clock230:"] = "\uD83D\uDD5D", + [":clock2:"] = "\uD83D\uDD51", + [":clock330:"] = "\uD83D\uDD5E", + [":clock3:"] = "\uD83D\uDD52", + [":clock430:"] = "\uD83D\uDD5F", + [":clock4:"] = "\uD83D\uDD53", + [":clock530:"] = "\uD83D\uDD60", + [":clock5:"] = "\uD83D\uDD54", + [":clock630:"] = "\uD83D\uDD61", + [":clock6:"] = "\uD83D\uDD55", + [":clock730:"] = "\uD83D\uDD62", + [":clock7:"] = "\uD83D\uDD56", + [":clock830:"] = "\uD83D\uDD63", + [":clock8:"] = "\uD83D\uDD57", + [":clock930:"] = "\uD83D\uDD64", + [":clock9:"] = "\uD83D\uDD58", + [":clock:"] = "\uD83D\uDD70️", + [":closed_book:"] = "\uD83D\uDCD5", + [":closed_lock_with_key:"] = "\uD83D\uDD10", + [":closed_umbrella:"] = "\uD83C\uDF02", + [":cloud:"] = "☁️", + [":cloud_lightning:"] = "\uD83C\uDF29️", + [":cloud_rain:"] = "\uD83C\uDF27️", + [":cloud_snow:"] = "\uD83C\uDF28️", + [":cloud_tornado:"] = "\uD83C\uDF2A️", + [":cloud_with_lightning:"] = "\uD83C\uDF29️", + [":cloud_with_rain:"] = "\uD83C\uDF27️", + [":cloud_with_snow:"] = "\uD83C\uDF28️", + [":cloud_with_tornado:"] = "\uD83C\uDF2A️", + [":clown:"] = "\uD83E\uDD21", + [":clown_face:"] = "\uD83E\uDD21", + [":clubs:"] = "♣️", + [":coat:"] = "\uD83E\uDDE5", + [":cocktail:"] = "\uD83C\uDF78", + [":coconut:"] = "\uD83E\uDD65", + [":coffee:"] = "☕", + [":coffin:"] = "⚰️", + [":cold_face:"] = "\uD83E\uDD76", + [":cold_sweat:"] = "\uD83D\uDE30", + [":comet:"] = "☄️", + [":compass:"] = "\uD83E\uDDED", + [":compression:"] = "\uD83D\uDDDC️", + [":computer:"] = "\uD83D\uDCBB", + [":confetti_ball:"] = "\uD83C\uDF8A", + [":confounded:"] = "\uD83D\uDE16", + [":confused:"] = "\uD83D\uDE15", + [":congratulations:"] = "㊗️", + [":construction:"] = "\uD83D\uDEA7", + [":construction_site:"] = "\uD83C\uDFD7️", + [":construction_worker:"] = "\uD83D\uDC77", + [":construction_worker::skin-tone-1:"] = "\uD83D\uDC77\uD83C\uDFFB", + [":construction_worker::skin-tone-2:"] = "\uD83D\uDC77\uD83C\uDFFC", + [":construction_worker::skin-tone-3:"] = "\uD83D\uDC77\uD83C\uDFFD", + [":construction_worker::skin-tone-4:"] = "\uD83D\uDC77\uD83C\uDFFE", + [":construction_worker::skin-tone-5:"] = "\uD83D\uDC77\uD83C\uDFFF", + [":construction_worker_tone1:"] = "\uD83D\uDC77\uD83C\uDFFB", + [":construction_worker_tone2:"] = "\uD83D\uDC77\uD83C\uDFFC", + [":construction_worker_tone3:"] = "\uD83D\uDC77\uD83C\uDFFD", + [":construction_worker_tone4:"] = "\uD83D\uDC77\uD83C\uDFFE", + [":construction_worker_tone5:"] = "\uD83D\uDC77\uD83C\uDFFF", + [":control_knobs:"] = "\uD83C\uDF9B️", + [":convenience_store:"] = "\uD83C\uDFEA", + [":cookie:"] = "\uD83C\uDF6A", + [":cooking:"] = "\uD83C\uDF73", + [":cool:"] = "\uD83C\uDD92", + [":cop:"] = "\uD83D\uDC6E", + [":cop::skin-tone-1:"] = "\uD83D\uDC6E\uD83C\uDFFB", + [":cop::skin-tone-2:"] = "\uD83D\uDC6E\uD83C\uDFFC", + [":cop::skin-tone-3:"] = "\uD83D\uDC6E\uD83C\uDFFD", + [":cop::skin-tone-4:"] = "\uD83D\uDC6E\uD83C\uDFFE", + [":cop::skin-tone-5:"] = "\uD83D\uDC6E\uD83C\uDFFF", + [":cop_tone1:"] = "\uD83D\uDC6E\uD83C\uDFFB", + [":cop_tone2:"] = "\uD83D\uDC6E\uD83C\uDFFC", + [":cop_tone3:"] = "\uD83D\uDC6E\uD83C\uDFFD", + [":cop_tone4:"] = "\uD83D\uDC6E\uD83C\uDFFE", + [":cop_tone5:"] = "\uD83D\uDC6E\uD83C\uDFFF", + [":copyright:"] = "©️", + [":corn:"] = "\uD83C\uDF3D", + [":couch:"] = "\uD83D\uDECB️", + [":couch_and_lamp:"] = "\uD83D\uDECB️", + [":couple:"] = "\uD83D\uDC6B", + [":couple_mm:"] = "\uD83D\uDC68\u200D❤️\u200D\uD83D\uDC68", + [":couple_with_heart:"] = "\uD83D\uDC91", + [":couple_with_heart_mm:"] = "\uD83D\uDC68\u200D❤️\u200D\uD83D\uDC68", + [":couple_with_heart_woman_man:"] = "\uD83D\uDC69\u200D❤️\u200D\uD83D\uDC68", + [":couple_with_heart_ww:"] = "\uD83D\uDC69\u200D❤️\u200D\uD83D\uDC69", + [":couple_ww:"] = "\uD83D\uDC69\u200D❤️\u200D\uD83D\uDC69", + [":couplekiss:"] = "\uD83D\uDC8F", + [":couplekiss_mm:"] = "\uD83D\uDC68\u200D❤️\u200D\uD83D\uDC8B\u200D\uD83D\uDC68", + [":couplekiss_ww:"] = "\uD83D\uDC69\u200D❤️\u200D\uD83D\uDC8B\u200D\uD83D\uDC69", + [":cow2:"] = "\uD83D\uDC04", + [":cow:"] = "\uD83D\uDC2E", + [":cowboy:"] = "\uD83E\uDD20", + [":crab:"] = "\uD83E\uDD80", + [":crayon:"] = "\uD83D\uDD8D️", + [":credit_card:"] = "\uD83D\uDCB3", + [":crescent_moon:"] = "\uD83C\uDF19", + [":cricket:"] = "\uD83E\uDD97", + [":cricket_bat_ball:"] = "\uD83C\uDFCF", + [":cricket_game:"] = "\uD83C\uDFCF", + [":crocodile:"] = "\uD83D\uDC0A", + [":croissant:"] = "\uD83E\uDD50", + [":cross:"] = "✝️", + [":crossed_flags:"] = "\uD83C\uDF8C", + [":crossed_swords:"] = "⚔️", + [":crown:"] = "\uD83D\uDC51", + [":cruise_ship:"] = "\uD83D\uDEF3️", + [":cry:"] = "\uD83D\uDE22", + [":crying_cat_face:"] = "\uD83D\uDE3F", + [":crystal_ball:"] = "\uD83D\uDD2E", + [":cucumber:"] = "\uD83E\uDD52", + [":cup_with_straw:"] = "\uD83E\uDD64", + [":cupcake:"] = "\uD83E\uDDC1", + [":cupid:"] = "\uD83D\uDC98", + [":curling_stone:"] = "\uD83E\uDD4C", + [":curly_loop:"] = "➰", + [":currency_exchange:"] = "\uD83D\uDCB1", + [":curry:"] = "\uD83C\uDF5B", + [":custard:"] = "\uD83C\uDF6E", + [":customs:"] = "\uD83D\uDEC3", + [":cut_of_meat:"] = "\uD83E\uDD69", + [":cyclone:"] = "\uD83C\uDF00", + [":dagger:"] = "\uD83D\uDDE1️", + [":dagger_knife:"] = "\uD83D\uDDE1️", + [":dancer:"] = "\uD83D\uDC83", + [":dancer::skin-tone-1:"] = "\uD83D\uDC83\uD83C\uDFFB", + [":dancer::skin-tone-2:"] = "\uD83D\uDC83\uD83C\uDFFC", + [":dancer::skin-tone-3:"] = "\uD83D\uDC83\uD83C\uDFFD", + [":dancer::skin-tone-4:"] = "\uD83D\uDC83\uD83C\uDFFE", + [":dancer::skin-tone-5:"] = "\uD83D\uDC83\uD83C\uDFFF", + [":dancer_tone1:"] = "\uD83D\uDC83\uD83C\uDFFB", + [":dancer_tone2:"] = "\uD83D\uDC83\uD83C\uDFFC", + [":dancer_tone3:"] = "\uD83D\uDC83\uD83C\uDFFD", + [":dancer_tone4:"] = "\uD83D\uDC83\uD83C\uDFFE", + [":dancer_tone5:"] = "\uD83D\uDC83\uD83C\uDFFF", + [":dancers:"] = "\uD83D\uDC6F", + [":dango:"] = "\uD83C\uDF61", + [":dark_sunglasses:"] = "\uD83D\uDD76️", + [":dart:"] = "\uD83C\uDFAF", + [":dash:"] = "\uD83D\uDCA8", + [":date:"] = "\uD83D\uDCC5", + [":deaf_man:"] = "\uD83E\uDDCF\u200D♂️", + [":deaf_man::skin-tone-1:"] = "\uD83E\uDDCF\uD83C\uDFFB\u200D♂️", + [":deaf_man::skin-tone-2:"] = "\uD83E\uDDCF\uD83C\uDFFC\u200D♂️", + [":deaf_man::skin-tone-3:"] = "\uD83E\uDDCF\uD83C\uDFFD\u200D♂️", + [":deaf_man::skin-tone-4:"] = "\uD83E\uDDCF\uD83C\uDFFE\u200D♂️", + [":deaf_man::skin-tone-5:"] = "\uD83E\uDDCF\uD83C\uDFFF\u200D♂️", + [":deaf_man_dark_skin_tone:"] = "\uD83E\uDDCF\uD83C\uDFFF\u200D♂️", + [":deaf_man_light_skin_tone:"] = "\uD83E\uDDCF\uD83C\uDFFB\u200D♂️", + [":deaf_man_medium_dark_skin_tone:"] = "\uD83E\uDDCF\uD83C\uDFFE\u200D♂️", + [":deaf_man_medium_light_skin_tone:"] = "\uD83E\uDDCF\uD83C\uDFFC\u200D♂️", + [":deaf_man_medium_skin_tone:"] = "\uD83E\uDDCF\uD83C\uDFFD\u200D♂️", + [":deaf_man_tone1:"] = "\uD83E\uDDCF\uD83C\uDFFB\u200D♂️", + [":deaf_man_tone2:"] = "\uD83E\uDDCF\uD83C\uDFFC\u200D♂️", + [":deaf_man_tone3:"] = "\uD83E\uDDCF\uD83C\uDFFD\u200D♂️", + [":deaf_man_tone4:"] = "\uD83E\uDDCF\uD83C\uDFFE\u200D♂️", + [":deaf_man_tone5:"] = "\uD83E\uDDCF\uD83C\uDFFF\u200D♂️", + [":deaf_person:"] = "\uD83E\uDDCF", + [":deaf_person::skin-tone-1:"] = "\uD83E\uDDCF\uD83C\uDFFB", + [":deaf_person::skin-tone-2:"] = "\uD83E\uDDCF\uD83C\uDFFC", + [":deaf_person::skin-tone-3:"] = "\uD83E\uDDCF\uD83C\uDFFD", + [":deaf_person::skin-tone-4:"] = "\uD83E\uDDCF\uD83C\uDFFE", + [":deaf_person::skin-tone-5:"] = "\uD83E\uDDCF\uD83C\uDFFF", + [":deaf_person_dark_skin_tone:"] = "\uD83E\uDDCF\uD83C\uDFFF", + [":deaf_person_light_skin_tone:"] = "\uD83E\uDDCF\uD83C\uDFFB", + [":deaf_person_medium_dark_skin_tone:"] = "\uD83E\uDDCF\uD83C\uDFFE", + [":deaf_person_medium_light_skin_tone:"] = "\uD83E\uDDCF\uD83C\uDFFC", + [":deaf_person_medium_skin_tone:"] = "\uD83E\uDDCF\uD83C\uDFFD", + [":deaf_person_tone1:"] = "\uD83E\uDDCF\uD83C\uDFFB", + [":deaf_person_tone2:"] = "\uD83E\uDDCF\uD83C\uDFFC", + [":deaf_person_tone3:"] = "\uD83E\uDDCF\uD83C\uDFFD", + [":deaf_person_tone4:"] = "\uD83E\uDDCF\uD83C\uDFFE", + [":deaf_person_tone5:"] = "\uD83E\uDDCF\uD83C\uDFFF", + [":deaf_woman:"] = "\uD83E\uDDCF\u200D♀️", + [":deaf_woman::skin-tone-1:"] = "\uD83E\uDDCF\uD83C\uDFFB\u200D♀️", + [":deaf_woman::skin-tone-2:"] = "\uD83E\uDDCF\uD83C\uDFFC\u200D♀️", + [":deaf_woman::skin-tone-3:"] = "\uD83E\uDDCF\uD83C\uDFFD\u200D♀️", + [":deaf_woman::skin-tone-4:"] = "\uD83E\uDDCF\uD83C\uDFFE\u200D♀️", + [":deaf_woman::skin-tone-5:"] = "\uD83E\uDDCF\uD83C\uDFFF\u200D♀️", + [":deaf_woman_dark_skin_tone:"] = "\uD83E\uDDCF\uD83C\uDFFF\u200D♀️", + [":deaf_woman_light_skin_tone:"] = "\uD83E\uDDCF\uD83C\uDFFB\u200D♀️", + [":deaf_woman_medium_dark_skin_tone:"] = "\uD83E\uDDCF\uD83C\uDFFE\u200D♀️", + [":deaf_woman_medium_light_skin_tone:"] = "\uD83E\uDDCF\uD83C\uDFFC\u200D♀️", + [":deaf_woman_medium_skin_tone:"] = "\uD83E\uDDCF\uD83C\uDFFD\u200D♀️", + [":deaf_woman_tone1:"] = "\uD83E\uDDCF\uD83C\uDFFB\u200D♀️", + [":deaf_woman_tone2:"] = "\uD83E\uDDCF\uD83C\uDFFC\u200D♀️", + [":deaf_woman_tone3:"] = "\uD83E\uDDCF\uD83C\uDFFD\u200D♀️", + [":deaf_woman_tone4:"] = "\uD83E\uDDCF\uD83C\uDFFE\u200D♀️", + [":deaf_woman_tone5:"] = "\uD83E\uDDCF\uD83C\uDFFF\u200D♀️", + [":deciduous_tree:"] = "\uD83C\uDF33", + [":deer:"] = "\uD83E\uDD8C", + [":department_store:"] = "\uD83C\uDFEC", + [":derelict_house_building:"] = "\uD83C\uDFDA️", + [":desert:"] = "\uD83C\uDFDC️", + [":desert_island:"] = "\uD83C\uDFDD️", + [":desktop:"] = "\uD83D\uDDA5️", + [":desktop_computer:"] = "\uD83D\uDDA5️", + [":detective:"] = "\uD83D\uDD75️", + [":detective::skin-tone-1:"] = "\uD83D\uDD75\uD83C\uDFFB", + [":detective::skin-tone-2:"] = "\uD83D\uDD75\uD83C\uDFFC", + [":detective::skin-tone-3:"] = "\uD83D\uDD75\uD83C\uDFFD", + [":detective::skin-tone-4:"] = "\uD83D\uDD75\uD83C\uDFFE", + [":detective::skin-tone-5:"] = "\uD83D\uDD75\uD83C\uDFFF", + [":detective_tone1:"] = "\uD83D\uDD75\uD83C\uDFFB", + [":detective_tone2:"] = "\uD83D\uDD75\uD83C\uDFFC", + [":detective_tone3:"] = "\uD83D\uDD75\uD83C\uDFFD", + [":detective_tone4:"] = "\uD83D\uDD75\uD83C\uDFFE", + [":detective_tone5:"] = "\uD83D\uDD75\uD83C\uDFFF", + [":diamond_shape_with_a_dot_inside:"] = "\uD83D\uDCA0", + [":diamonds:"] = "♦️", + [":disappointed:"] = "\uD83D\uDE1E", + [":disappointed_relieved:"] = "\uD83D\uDE25", + [":dividers:"] = "\uD83D\uDDC2️", + [":diving_mask:"] = "\uD83E\uDD3F", + [":diya_lamp:"] = "\uD83E\uDE94", + [":dizzy:"] = "\uD83D\uDCAB", + [":dizzy_face:"] = "\uD83D\uDE35", + [":dna:"] = "\uD83E\uDDEC", + [":do_not_litter:"] = "\uD83D\uDEAF", + [":dog2:"] = "\uD83D\uDC15", + [":dog:"] = "\uD83D\uDC36", + [":dollar:"] = "\uD83D\uDCB5", + [":dolls:"] = "\uD83C\uDF8E", + [":dolphin:"] = "\uD83D\uDC2C", + [":door:"] = "\uD83D\uDEAA", + [":double_vertical_bar:"] = "⏸️", + [":doughnut:"] = "\uD83C\uDF69", + [":dove:"] = "\uD83D\uDD4A️", + [":dove_of_peace:"] = "\uD83D\uDD4A️", + [":dragon:"] = "\uD83D\uDC09", + [":dragon_face:"] = "\uD83D\uDC32", + [":dress:"] = "\uD83D\uDC57", + [":dromedary_camel:"] = "\uD83D\uDC2A", + [":drool:"] = "\uD83E\uDD24", + [":drooling_face:"] = "\uD83E\uDD24", + [":drop_of_blood:"] = "\uD83E\uDE78", + [":droplet:"] = "\uD83D\uDCA7", + [":drum:"] = "\uD83E\uDD41", + [":drum_with_drumsticks:"] = "\uD83E\uDD41", + [":duck:"] = "\uD83E\uDD86", + [":dumpling:"] = "\uD83E\uDD5F", + [":dvd:"] = "\uD83D\uDCC0", + [":e_mail:"] = "\uD83D\uDCE7", + [":eagle:"] = "\uD83E\uDD85", + [":ear:"] = "\uD83D\uDC42", + [":ear::skin-tone-1:"] = "\uD83D\uDC42\uD83C\uDFFB", + [":ear::skin-tone-2:"] = "\uD83D\uDC42\uD83C\uDFFC", + [":ear::skin-tone-3:"] = "\uD83D\uDC42\uD83C\uDFFD", + [":ear::skin-tone-4:"] = "\uD83D\uDC42\uD83C\uDFFE", + [":ear::skin-tone-5:"] = "\uD83D\uDC42\uD83C\uDFFF", + [":ear_of_rice:"] = "\uD83C\uDF3E", + [":ear_tone1:"] = "\uD83D\uDC42\uD83C\uDFFB", + [":ear_tone2:"] = "\uD83D\uDC42\uD83C\uDFFC", + [":ear_tone3:"] = "\uD83D\uDC42\uD83C\uDFFD", + [":ear_tone4:"] = "\uD83D\uDC42\uD83C\uDFFE", + [":ear_tone5:"] = "\uD83D\uDC42\uD83C\uDFFF", + [":ear_with_hearing_aid:"] = "\uD83E\uDDBB", + [":ear_with_hearing_aid::skin-tone-1:"] = "\uD83E\uDDBB\uD83C\uDFFB", + [":ear_with_hearing_aid::skin-tone-2:"] = "\uD83E\uDDBB\uD83C\uDFFC", + [":ear_with_hearing_aid::skin-tone-3:"] = "\uD83E\uDDBB\uD83C\uDFFD", + [":ear_with_hearing_aid::skin-tone-4:"] = "\uD83E\uDDBB\uD83C\uDFFE", + [":ear_with_hearing_aid::skin-tone-5:"] = "\uD83E\uDDBB\uD83C\uDFFF", + [":ear_with_hearing_aid_dark_skin_tone:"] = "\uD83E\uDDBB\uD83C\uDFFF", + [":ear_with_hearing_aid_light_skin_tone:"] = "\uD83E\uDDBB\uD83C\uDFFB", + [":ear_with_hearing_aid_medium_dark_skin_tone:"] = "\uD83E\uDDBB\uD83C\uDFFE", + [":ear_with_hearing_aid_medium_light_skin_tone:"] = "\uD83E\uDDBB\uD83C\uDFFC", + [":ear_with_hearing_aid_medium_skin_tone:"] = "\uD83E\uDDBB\uD83C\uDFFD", + [":ear_with_hearing_aid_tone1:"] = "\uD83E\uDDBB\uD83C\uDFFB", + [":ear_with_hearing_aid_tone2:"] = "\uD83E\uDDBB\uD83C\uDFFC", + [":ear_with_hearing_aid_tone3:"] = "\uD83E\uDDBB\uD83C\uDFFD", + [":ear_with_hearing_aid_tone4:"] = "\uD83E\uDDBB\uD83C\uDFFE", + [":ear_with_hearing_aid_tone5:"] = "\uD83E\uDDBB\uD83C\uDFFF", + [":earth_africa:"] = "\uD83C\uDF0D", + [":earth_americas:"] = "\uD83C\uDF0E", + [":earth_asia:"] = "\uD83C\uDF0F", + [":egg:"] = "\uD83E\uDD5A", + [":eggplant:"] = "\uD83C\uDF46", + [":eight:"] = "8️⃣", + [":eight_pointed_black_star:"] = "✴️", + [":eight_spoked_asterisk:"] = "✳️", + [":eject:"] = "⏏️", + [":eject_symbol:"] = "⏏️", + [":electric_plug:"] = "\uD83D\uDD0C", + [":elephant:"] = "\uD83D\uDC18", + [":elf:"] = "\uD83E\uDDDD", + [":elf::skin-tone-1:"] = "\uD83E\uDDDD\uD83C\uDFFB", + [":elf::skin-tone-2:"] = "\uD83E\uDDDD\uD83C\uDFFC", + [":elf::skin-tone-3:"] = "\uD83E\uDDDD\uD83C\uDFFD", + [":elf::skin-tone-4:"] = "\uD83E\uDDDD\uD83C\uDFFE", + [":elf::skin-tone-5:"] = "\uD83E\uDDDD\uD83C\uDFFF", + [":elf_dark_skin_tone:"] = "\uD83E\uDDDD\uD83C\uDFFF", + [":elf_light_skin_tone:"] = "\uD83E\uDDDD\uD83C\uDFFB", + [":elf_medium_dark_skin_tone:"] = "\uD83E\uDDDD\uD83C\uDFFE", + [":elf_medium_light_skin_tone:"] = "\uD83E\uDDDD\uD83C\uDFFC", + [":elf_medium_skin_tone:"] = "\uD83E\uDDDD\uD83C\uDFFD", + [":elf_tone1:"] = "\uD83E\uDDDD\uD83C\uDFFB", + [":elf_tone2:"] = "\uD83E\uDDDD\uD83C\uDFFC", + [":elf_tone3:"] = "\uD83E\uDDDD\uD83C\uDFFD", + [":elf_tone4:"] = "\uD83E\uDDDD\uD83C\uDFFE", + [":elf_tone5:"] = "\uD83E\uDDDD\uD83C\uDFFF", + [":email:"] = "\uD83D\uDCE7", + [":end:"] = "\uD83D\uDD1A", + [":england:"] = "\uD83C\uDFF4\uDB40\uDC67\uDB40\uDC62\uDB40\uDC65\uDB40\uDC6E\uDB40\uDC67\uDB40\uDC7F", + [":envelope:"] = "✉️", + [":envelope_with_arrow:"] = "\uD83D\uDCE9", + [":euro:"] = "\uD83D\uDCB6", + [":european_castle:"] = "\uD83C\uDFF0", + [":european_post_office:"] = "\uD83C\uDFE4", + [":evergreen_tree:"] = "\uD83C\uDF32", + [":exclamation:"] = "❗", + [":expecting_woman:"] = "\uD83E\uDD30", + [":expecting_woman::skin-tone-1:"] = "\uD83E\uDD30\uD83C\uDFFB", + [":expecting_woman::skin-tone-2:"] = "\uD83E\uDD30\uD83C\uDFFC", + [":expecting_woman::skin-tone-3:"] = "\uD83E\uDD30\uD83C\uDFFD", + [":expecting_woman::skin-tone-4:"] = "\uD83E\uDD30\uD83C\uDFFE", + [":expecting_woman::skin-tone-5:"] = "\uD83E\uDD30\uD83C\uDFFF", + [":expecting_woman_tone1:"] = "\uD83E\uDD30\uD83C\uDFFB", + [":expecting_woman_tone2:"] = "\uD83E\uDD30\uD83C\uDFFC", + [":expecting_woman_tone3:"] = "\uD83E\uDD30\uD83C\uDFFD", + [":expecting_woman_tone4:"] = "\uD83E\uDD30\uD83C\uDFFE", + [":expecting_woman_tone5:"] = "\uD83E\uDD30\uD83C\uDFFF", + [":exploding_head:"] = "\uD83E\uDD2F", + [":expressionless:"] = "\uD83D\uDE11", + [":eye:"] = "\uD83D\uDC41️", + [":eye_in_speech_bubble:"] = "\uD83D\uDC41\u200D\uD83D\uDDE8", + [":eyeglasses:"] = "\uD83D\uDC53", + [":eyes:"] = "\uD83D\uDC40", + [":face_palm:"] = "\uD83E\uDD26", + [":face_palm::skin-tone-1:"] = "\uD83E\uDD26\uD83C\uDFFB", + [":face_palm::skin-tone-2:"] = "\uD83E\uDD26\uD83C\uDFFC", + [":face_palm::skin-tone-3:"] = "\uD83E\uDD26\uD83C\uDFFD", + [":face_palm::skin-tone-4:"] = "\uD83E\uDD26\uD83C\uDFFE", + [":face_palm::skin-tone-5:"] = "\uD83E\uDD26\uD83C\uDFFF", + [":face_palm_tone1:"] = "\uD83E\uDD26\uD83C\uDFFB", + [":face_palm_tone2:"] = "\uD83E\uDD26\uD83C\uDFFC", + [":face_palm_tone3:"] = "\uD83E\uDD26\uD83C\uDFFD", + [":face_palm_tone4:"] = "\uD83E\uDD26\uD83C\uDFFE", + [":face_palm_tone5:"] = "\uD83E\uDD26\uD83C\uDFFF", + [":face_vomiting:"] = "\uD83E\uDD2E", + [":face_with_cowboy_hat:"] = "\uD83E\uDD20", + [":face_with_hand_over_mouth:"] = "\uD83E\uDD2D", + [":face_with_head_bandage:"] = "\uD83E\uDD15", + [":face_with_monocle:"] = "\uD83E\uDDD0", + [":face_with_raised_eyebrow:"] = "\uD83E\uDD28", + [":face_with_rolling_eyes:"] = "\uD83D\uDE44", + [":face_with_symbols_over_mouth:"] = "\uD83E\uDD2C", + [":face_with_thermometer:"] = "\uD83E\uDD12", + [":facepalm:"] = "\uD83E\uDD26", + [":facepalm::skin-tone-1:"] = "\uD83E\uDD26\uD83C\uDFFB", + [":facepalm::skin-tone-2:"] = "\uD83E\uDD26\uD83C\uDFFC", + [":facepalm::skin-tone-3:"] = "\uD83E\uDD26\uD83C\uDFFD", + [":facepalm::skin-tone-4:"] = "\uD83E\uDD26\uD83C\uDFFE", + [":facepalm::skin-tone-5:"] = "\uD83E\uDD26\uD83C\uDFFF", + [":facepalm_tone1:"] = "\uD83E\uDD26\uD83C\uDFFB", + [":facepalm_tone2:"] = "\uD83E\uDD26\uD83C\uDFFC", + [":facepalm_tone3:"] = "\uD83E\uDD26\uD83C\uDFFD", + [":facepalm_tone4:"] = "\uD83E\uDD26\uD83C\uDFFE", + [":facepalm_tone5:"] = "\uD83E\uDD26\uD83C\uDFFF", + [":factory:"] = "\uD83C\uDFED", + [":fairy:"] = "\uD83E\uDDDA", + [":fairy::skin-tone-1:"] = "\uD83E\uDDDA\uD83C\uDFFB", + [":fairy::skin-tone-2:"] = "\uD83E\uDDDA\uD83C\uDFFC", + [":fairy::skin-tone-3:"] = "\uD83E\uDDDA\uD83C\uDFFD", + [":fairy::skin-tone-4:"] = "\uD83E\uDDDA\uD83C\uDFFE", + [":fairy::skin-tone-5:"] = "\uD83E\uDDDA\uD83C\uDFFF", + [":fairy_dark_skin_tone:"] = "\uD83E\uDDDA\uD83C\uDFFF", + [":fairy_light_skin_tone:"] = "\uD83E\uDDDA\uD83C\uDFFB", + [":fairy_medium_dark_skin_tone:"] = "\uD83E\uDDDA\uD83C\uDFFE", + [":fairy_medium_light_skin_tone:"] = "\uD83E\uDDDA\uD83C\uDFFC", + [":fairy_medium_skin_tone:"] = "\uD83E\uDDDA\uD83C\uDFFD", + [":fairy_tone1:"] = "\uD83E\uDDDA\uD83C\uDFFB", + [":fairy_tone2:"] = "\uD83E\uDDDA\uD83C\uDFFC", + [":fairy_tone3:"] = "\uD83E\uDDDA\uD83C\uDFFD", + [":fairy_tone4:"] = "\uD83E\uDDDA\uD83C\uDFFE", + [":fairy_tone5:"] = "\uD83E\uDDDA\uD83C\uDFFF", + [":falafel:"] = "\uD83E\uDDC6", + [":fallen_leaf:"] = "\uD83C\uDF42", + [":family:"] = "\uD83D\uDC6A", + [":family_man_boy:"] = "\uD83D\uDC68\u200D\uD83D\uDC66", + [":family_man_boy_boy:"] = "\uD83D\uDC68\u200D\uD83D\uDC66\u200D\uD83D\uDC66", + [":family_man_girl:"] = "\uD83D\uDC68\u200D\uD83D\uDC67", + [":family_man_girl_boy:"] = "\uD83D\uDC68\u200D\uD83D\uDC67\u200D\uD83D\uDC66", + [":family_man_girl_girl:"] = "\uD83D\uDC68\u200D\uD83D\uDC67\u200D\uD83D\uDC67", + [":family_man_woman_boy:"] = "\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66", + [":family_mmb:"] = "\uD83D\uDC68\u200D\uD83D\uDC68\u200D\uD83D\uDC66", + [":family_mmbb:"] = "\uD83D\uDC68\u200D\uD83D\uDC68\u200D\uD83D\uDC66\u200D\uD83D\uDC66", + [":family_mmg:"] = "\uD83D\uDC68\u200D\uD83D\uDC68\u200D\uD83D\uDC67", + [":family_mmgb:"] = "\uD83D\uDC68\u200D\uD83D\uDC68\u200D\uD83D\uDC67\u200D\uD83D\uDC66", + [":family_mmgg:"] = "\uD83D\uDC68\u200D\uD83D\uDC68\u200D\uD83D\uDC67\u200D\uD83D\uDC67", + [":family_mwbb:"] = "\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66\u200D\uD83D\uDC66", + [":family_mwg:"] = "\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC67", + [":family_mwgb:"] = "\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC67\u200D\uD83D\uDC66", + [":family_mwgg:"] = "\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC67\u200D\uD83D\uDC67", + [":family_woman_boy:"] = "\uD83D\uDC69\u200D\uD83D\uDC66", + [":family_woman_boy_boy:"] = "\uD83D\uDC69\u200D\uD83D\uDC66\u200D\uD83D\uDC66", + [":family_woman_girl:"] = "\uD83D\uDC69\u200D\uD83D\uDC67", + [":family_woman_girl_boy:"] = "\uD83D\uDC69\u200D\uD83D\uDC67\u200D\uD83D\uDC66", + [":family_woman_girl_girl:"] = "\uD83D\uDC69\u200D\uD83D\uDC67\u200D\uD83D\uDC67", + [":family_wwb:"] = "\uD83D\uDC69\u200D\uD83D\uDC69\u200D\uD83D\uDC66", + [":family_wwbb:"] = "\uD83D\uDC69\u200D\uD83D\uDC69\u200D\uD83D\uDC66\u200D\uD83D\uDC66", + [":family_wwg:"] = "\uD83D\uDC69\u200D\uD83D\uDC69\u200D\uD83D\uDC67", + [":family_wwgb:"] = "\uD83D\uDC69\u200D\uD83D\uDC69\u200D\uD83D\uDC67\u200D\uD83D\uDC66", + [":family_wwgg:"] = "\uD83D\uDC69\u200D\uD83D\uDC69\u200D\uD83D\uDC67\u200D\uD83D\uDC67", + [":fast_forward:"] = "⏩", + [":fax:"] = "\uD83D\uDCE0", + [":fearful:"] = "\uD83D\uDE28", + [":feet:"] = "\uD83D\uDC3E", + [":female_sign:"] = "♀️", + [":fencer:"] = "\uD83E\uDD3A", + [":fencing:"] = "\uD83E\uDD3A", + [":ferris_wheel:"] = "\uD83C\uDFA1", + [":ferry:"] = "⛴️", + [":field_hockey:"] = "\uD83C\uDFD1", + [":file_cabinet:"] = "\uD83D\uDDC4️", + [":file_folder:"] = "\uD83D\uDCC1", + [":film_frames:"] = "\uD83C\uDF9E️", + [":film_projector:"] = "\uD83D\uDCFD️", + [":fingers_crossed:"] = "\uD83E\uDD1E", + [":fingers_crossed::skin-tone-1:"] = "\uD83E\uDD1E\uD83C\uDFFB", + [":fingers_crossed::skin-tone-2:"] = "\uD83E\uDD1E\uD83C\uDFFC", + [":fingers_crossed::skin-tone-3:"] = "\uD83E\uDD1E\uD83C\uDFFD", + [":fingers_crossed::skin-tone-4:"] = "\uD83E\uDD1E\uD83C\uDFFE", + [":fingers_crossed::skin-tone-5:"] = "\uD83E\uDD1E\uD83C\uDFFF", + [":fingers_crossed_tone1:"] = "\uD83E\uDD1E\uD83C\uDFFB", + [":fingers_crossed_tone2:"] = "\uD83E\uDD1E\uD83C\uDFFC", + [":fingers_crossed_tone3:"] = "\uD83E\uDD1E\uD83C\uDFFD", + [":fingers_crossed_tone4:"] = "\uD83E\uDD1E\uD83C\uDFFE", + [":fingers_crossed_tone5:"] = "\uD83E\uDD1E\uD83C\uDFFF", + [":fire:"] = "\uD83D\uDD25", + [":fire_engine:"] = "\uD83D\uDE92", + [":fire_extinguisher:"] = "\uD83E\uDDEF", + [":firecracker:"] = "\uD83E\uDDE8", + [":fireworks:"] = "\uD83C\uDF86", + [":first_place:"] = "\uD83E\uDD47", + [":first_place_medal:"] = "\uD83E\uDD47", + [":first_quarter_moon:"] = "\uD83C\uDF13", + [":first_quarter_moon_with_face:"] = "\uD83C\uDF1B", + [":fish:"] = "\uD83D\uDC1F", + [":fish_cake:"] = "\uD83C\uDF65", + [":fishing_pole_and_fish:"] = "\uD83C\uDFA3", + [":fist:"] = "✊", + [":fist::skin-tone-1:"] = "✊\uD83C\uDFFB", + [":fist::skin-tone-2:"] = "✊\uD83C\uDFFC", + [":fist::skin-tone-3:"] = "✊\uD83C\uDFFD", + [":fist::skin-tone-4:"] = "✊\uD83C\uDFFE", + [":fist::skin-tone-5:"] = "✊\uD83C\uDFFF", + [":fist_tone1:"] = "✊\uD83C\uDFFB", + [":fist_tone2:"] = "✊\uD83C\uDFFC", + [":fist_tone3:"] = "✊\uD83C\uDFFD", + [":fist_tone4:"] = "✊\uD83C\uDFFE", + [":fist_tone5:"] = "✊\uD83C\uDFFF", + [":five:"] = "5️⃣", + [":flag_ac:"] = "\uD83C\uDDE6\uD83C\uDDE8", + [":flag_ad:"] = "\uD83C\uDDE6\uD83C\uDDE9", + [":flag_ae:"] = "\uD83C\uDDE6\uD83C\uDDEA", + [":flag_af:"] = "\uD83C\uDDE6\uD83C\uDDEB", + [":flag_ag:"] = "\uD83C\uDDE6\uD83C\uDDEC", + [":flag_ai:"] = "\uD83C\uDDE6\uD83C\uDDEE", + [":flag_al:"] = "\uD83C\uDDE6\uD83C\uDDF1", + [":flag_am:"] = "\uD83C\uDDE6\uD83C\uDDF2", + [":flag_ao:"] = "\uD83C\uDDE6\uD83C\uDDF4", + [":flag_aq:"] = "\uD83C\uDDE6\uD83C\uDDF6", + [":flag_ar:"] = "\uD83C\uDDE6\uD83C\uDDF7", + [":flag_as:"] = "\uD83C\uDDE6\uD83C\uDDF8", + [":flag_at:"] = "\uD83C\uDDE6\uD83C\uDDF9", + [":flag_au:"] = "\uD83C\uDDE6\uD83C\uDDFA", + [":flag_aw:"] = "\uD83C\uDDE6\uD83C\uDDFC", + [":flag_ax:"] = "\uD83C\uDDE6\uD83C\uDDFD", + [":flag_az:"] = "\uD83C\uDDE6\uD83C\uDDFF", + [":flag_ba:"] = "\uD83C\uDDE7\uD83C\uDDE6", + [":flag_bb:"] = "\uD83C\uDDE7\uD83C\uDDE7", + [":flag_bd:"] = "\uD83C\uDDE7\uD83C\uDDE9", + [":flag_be:"] = "\uD83C\uDDE7\uD83C\uDDEA", + [":flag_bf:"] = "\uD83C\uDDE7\uD83C\uDDEB", + [":flag_bg:"] = "\uD83C\uDDE7\uD83C\uDDEC", + [":flag_bh:"] = "\uD83C\uDDE7\uD83C\uDDED", + [":flag_bi:"] = "\uD83C\uDDE7\uD83C\uDDEE", + [":flag_bj:"] = "\uD83C\uDDE7\uD83C\uDDEF", + [":flag_bl:"] = "\uD83C\uDDE7\uD83C\uDDF1", + [":flag_black:"] = "\uD83C\uDFF4", + [":flag_bm:"] = "\uD83C\uDDE7\uD83C\uDDF2", + [":flag_bn:"] = "\uD83C\uDDE7\uD83C\uDDF3", + [":flag_bo:"] = "\uD83C\uDDE7\uD83C\uDDF4", + [":flag_bq:"] = "\uD83C\uDDE7\uD83C\uDDF6", + [":flag_br:"] = "\uD83C\uDDE7\uD83C\uDDF7", + [":flag_bs:"] = "\uD83C\uDDE7\uD83C\uDDF8", + [":flag_bt:"] = "\uD83C\uDDE7\uD83C\uDDF9", + [":flag_bv:"] = "\uD83C\uDDE7\uD83C\uDDFB", + [":flag_bw:"] = "\uD83C\uDDE7\uD83C\uDDFC", + [":flag_by:"] = "\uD83C\uDDE7\uD83C\uDDFE", + [":flag_bz:"] = "\uD83C\uDDE7\uD83C\uDDFF", + [":flag_ca:"] = "\uD83C\uDDE8\uD83C\uDDE6", + [":flag_cc:"] = "\uD83C\uDDE8\uD83C\uDDE8", + [":flag_cd:"] = "\uD83C\uDDE8\uD83C\uDDE9", + [":flag_cf:"] = "\uD83C\uDDE8\uD83C\uDDEB", + [":flag_cg:"] = "\uD83C\uDDE8\uD83C\uDDEC", + [":flag_ch:"] = "\uD83C\uDDE8\uD83C\uDDED", + [":flag_ci:"] = "\uD83C\uDDE8\uD83C\uDDEE", + [":flag_ck:"] = "\uD83C\uDDE8\uD83C\uDDF0", + [":flag_cl:"] = "\uD83C\uDDE8\uD83C\uDDF1", + [":flag_cm:"] = "\uD83C\uDDE8\uD83C\uDDF2", + [":flag_cn:"] = "\uD83C\uDDE8\uD83C\uDDF3", + [":flag_co:"] = "\uD83C\uDDE8\uD83C\uDDF4", + [":flag_cp:"] = "\uD83C\uDDE8\uD83C\uDDF5", + [":flag_cr:"] = "\uD83C\uDDE8\uD83C\uDDF7", + [":flag_cu:"] = "\uD83C\uDDE8\uD83C\uDDFA", + [":flag_cv:"] = "\uD83C\uDDE8\uD83C\uDDFB", + [":flag_cw:"] = "\uD83C\uDDE8\uD83C\uDDFC", + [":flag_cx:"] = "\uD83C\uDDE8\uD83C\uDDFD", + [":flag_cy:"] = "\uD83C\uDDE8\uD83C\uDDFE", + [":flag_cz:"] = "\uD83C\uDDE8\uD83C\uDDFF", + [":flag_de:"] = "\uD83C\uDDE9\uD83C\uDDEA", + [":flag_dg:"] = "\uD83C\uDDE9\uD83C\uDDEC", + [":flag_dj:"] = "\uD83C\uDDE9\uD83C\uDDEF", + [":flag_dk:"] = "\uD83C\uDDE9\uD83C\uDDF0", + [":flag_dm:"] = "\uD83C\uDDE9\uD83C\uDDF2", + [":flag_do:"] = "\uD83C\uDDE9\uD83C\uDDF4", + [":flag_dz:"] = "\uD83C\uDDE9\uD83C\uDDFF", + [":flag_ea:"] = "\uD83C\uDDEA\uD83C\uDDE6", + [":flag_ec:"] = "\uD83C\uDDEA\uD83C\uDDE8", + [":flag_ee:"] = "\uD83C\uDDEA\uD83C\uDDEA", + [":flag_eg:"] = "\uD83C\uDDEA\uD83C\uDDEC", + [":flag_eh:"] = "\uD83C\uDDEA\uD83C\uDDED", + [":flag_er:"] = "\uD83C\uDDEA\uD83C\uDDF7", + [":flag_es:"] = "\uD83C\uDDEA\uD83C\uDDF8", + [":flag_et:"] = "\uD83C\uDDEA\uD83C\uDDF9", + [":flag_eu:"] = "\uD83C\uDDEA\uD83C\uDDFA", + [":flag_fi:"] = "\uD83C\uDDEB\uD83C\uDDEE", + [":flag_fj:"] = "\uD83C\uDDEB\uD83C\uDDEF", + [":flag_fk:"] = "\uD83C\uDDEB\uD83C\uDDF0", + [":flag_fm:"] = "\uD83C\uDDEB\uD83C\uDDF2", + [":flag_fo:"] = "\uD83C\uDDEB\uD83C\uDDF4", + [":flag_fr:"] = "\uD83C\uDDEB\uD83C\uDDF7", + [":flag_ga:"] = "\uD83C\uDDEC\uD83C\uDDE6", + [":flag_gb:"] = "\uD83C\uDDEC\uD83C\uDDE7", + [":flag_gd:"] = "\uD83C\uDDEC\uD83C\uDDE9", + [":flag_ge:"] = "\uD83C\uDDEC\uD83C\uDDEA", + [":flag_gf:"] = "\uD83C\uDDEC\uD83C\uDDEB", + [":flag_gg:"] = "\uD83C\uDDEC\uD83C\uDDEC", + [":flag_gh:"] = "\uD83C\uDDEC\uD83C\uDDED", + [":flag_gi:"] = "\uD83C\uDDEC\uD83C\uDDEE", + [":flag_gl:"] = "\uD83C\uDDEC\uD83C\uDDF1", + [":flag_gm:"] = "\uD83C\uDDEC\uD83C\uDDF2", + [":flag_gn:"] = "\uD83C\uDDEC\uD83C\uDDF3", + [":flag_gp:"] = "\uD83C\uDDEC\uD83C\uDDF5", + [":flag_gq:"] = "\uD83C\uDDEC\uD83C\uDDF6", + [":flag_gr:"] = "\uD83C\uDDEC\uD83C\uDDF7", + [":flag_gs:"] = "\uD83C\uDDEC\uD83C\uDDF8", + [":flag_gt:"] = "\uD83C\uDDEC\uD83C\uDDF9", + [":flag_gu:"] = "\uD83C\uDDEC\uD83C\uDDFA", + [":flag_gw:"] = "\uD83C\uDDEC\uD83C\uDDFC", + [":flag_gy:"] = "\uD83C\uDDEC\uD83C\uDDFE", + [":flag_hk:"] = "\uD83C\uDDED\uD83C\uDDF0", + [":flag_hm:"] = "\uD83C\uDDED\uD83C\uDDF2", + [":flag_hn:"] = "\uD83C\uDDED\uD83C\uDDF3", + [":flag_hr:"] = "\uD83C\uDDED\uD83C\uDDF7", + [":flag_ht:"] = "\uD83C\uDDED\uD83C\uDDF9", + [":flag_hu:"] = "\uD83C\uDDED\uD83C\uDDFA", + [":flag_ic:"] = "\uD83C\uDDEE\uD83C\uDDE8", + [":flag_id:"] = "\uD83C\uDDEE\uD83C\uDDE9", + [":flag_ie:"] = "\uD83C\uDDEE\uD83C\uDDEA", + [":flag_il:"] = "\uD83C\uDDEE\uD83C\uDDF1", + [":flag_im:"] = "\uD83C\uDDEE\uD83C\uDDF2", + [":flag_in:"] = "\uD83C\uDDEE\uD83C\uDDF3", + [":flag_io:"] = "\uD83C\uDDEE\uD83C\uDDF4", + [":flag_iq:"] = "\uD83C\uDDEE\uD83C\uDDF6", + [":flag_ir:"] = "\uD83C\uDDEE\uD83C\uDDF7", + [":flag_is:"] = "\uD83C\uDDEE\uD83C\uDDF8", + [":flag_it:"] = "\uD83C\uDDEE\uD83C\uDDF9", + [":flag_je:"] = "\uD83C\uDDEF\uD83C\uDDEA", + [":flag_jm:"] = "\uD83C\uDDEF\uD83C\uDDF2", + [":flag_jo:"] = "\uD83C\uDDEF\uD83C\uDDF4", + [":flag_jp:"] = "\uD83C\uDDEF\uD83C\uDDF5", + [":flag_ke:"] = "\uD83C\uDDF0\uD83C\uDDEA", + [":flag_kg:"] = "\uD83C\uDDF0\uD83C\uDDEC", + [":flag_kh:"] = "\uD83C\uDDF0\uD83C\uDDED", + [":flag_ki:"] = "\uD83C\uDDF0\uD83C\uDDEE", + [":flag_km:"] = "\uD83C\uDDF0\uD83C\uDDF2", + [":flag_kn:"] = "\uD83C\uDDF0\uD83C\uDDF3", + [":flag_kp:"] = "\uD83C\uDDF0\uD83C\uDDF5", + [":flag_kr:"] = "\uD83C\uDDF0\uD83C\uDDF7", + [":flag_kw:"] = "\uD83C\uDDF0\uD83C\uDDFC", + [":flag_ky:"] = "\uD83C\uDDF0\uD83C\uDDFE", + [":flag_kz:"] = "\uD83C\uDDF0\uD83C\uDDFF", + [":flag_la:"] = "\uD83C\uDDF1\uD83C\uDDE6", + [":flag_lb:"] = "\uD83C\uDDF1\uD83C\uDDE7", + [":flag_lc:"] = "\uD83C\uDDF1\uD83C\uDDE8", + [":flag_li:"] = "\uD83C\uDDF1\uD83C\uDDEE", + [":flag_lk:"] = "\uD83C\uDDF1\uD83C\uDDF0", + [":flag_lr:"] = "\uD83C\uDDF1\uD83C\uDDF7", + [":flag_ls:"] = "\uD83C\uDDF1\uD83C\uDDF8", + [":flag_lt:"] = "\uD83C\uDDF1\uD83C\uDDF9", + [":flag_lu:"] = "\uD83C\uDDF1\uD83C\uDDFA", + [":flag_lv:"] = "\uD83C\uDDF1\uD83C\uDDFB", + [":flag_ly:"] = "\uD83C\uDDF1\uD83C\uDDFE", + [":flag_ma:"] = "\uD83C\uDDF2\uD83C\uDDE6", + [":flag_mc:"] = "\uD83C\uDDF2\uD83C\uDDE8", + [":flag_md:"] = "\uD83C\uDDF2\uD83C\uDDE9", + [":flag_me:"] = "\uD83C\uDDF2\uD83C\uDDEA", + [":flag_mf:"] = "\uD83C\uDDF2\uD83C\uDDEB", + [":flag_mg:"] = "\uD83C\uDDF2\uD83C\uDDEC", + [":flag_mh:"] = "\uD83C\uDDF2\uD83C\uDDED", + [":flag_mk:"] = "\uD83C\uDDF2\uD83C\uDDF0", + [":flag_ml:"] = "\uD83C\uDDF2\uD83C\uDDF1", + [":flag_mm:"] = "\uD83C\uDDF2\uD83C\uDDF2", + [":flag_mn:"] = "\uD83C\uDDF2\uD83C\uDDF3", + [":flag_mo:"] = "\uD83C\uDDF2\uD83C\uDDF4", + [":flag_mp:"] = "\uD83C\uDDF2\uD83C\uDDF5", + [":flag_mq:"] = "\uD83C\uDDF2\uD83C\uDDF6", + [":flag_mr:"] = "\uD83C\uDDF2\uD83C\uDDF7", + [":flag_ms:"] = "\uD83C\uDDF2\uD83C\uDDF8", + [":flag_mt:"] = "\uD83C\uDDF2\uD83C\uDDF9", + [":flag_mu:"] = "\uD83C\uDDF2\uD83C\uDDFA", + [":flag_mv:"] = "\uD83C\uDDF2\uD83C\uDDFB", + [":flag_mw:"] = "\uD83C\uDDF2\uD83C\uDDFC", + [":flag_mx:"] = "\uD83C\uDDF2\uD83C\uDDFD", + [":flag_my:"] = "\uD83C\uDDF2\uD83C\uDDFE", + [":flag_mz:"] = "\uD83C\uDDF2\uD83C\uDDFF", + [":flag_na:"] = "\uD83C\uDDF3\uD83C\uDDE6", + [":flag_nc:"] = "\uD83C\uDDF3\uD83C\uDDE8", + [":flag_ne:"] = "\uD83C\uDDF3\uD83C\uDDEA", + [":flag_nf:"] = "\uD83C\uDDF3\uD83C\uDDEB", + [":flag_ng:"] = "\uD83C\uDDF3\uD83C\uDDEC", + [":flag_ni:"] = "\uD83C\uDDF3\uD83C\uDDEE", + [":flag_nl:"] = "\uD83C\uDDF3\uD83C\uDDF1", + [":flag_no:"] = "\uD83C\uDDF3\uD83C\uDDF4", + [":flag_np:"] = "\uD83C\uDDF3\uD83C\uDDF5", + [":flag_nr:"] = "\uD83C\uDDF3\uD83C\uDDF7", + [":flag_nu:"] = "\uD83C\uDDF3\uD83C\uDDFA", + [":flag_nz:"] = "\uD83C\uDDF3\uD83C\uDDFF", + [":flag_om:"] = "\uD83C\uDDF4\uD83C\uDDF2", + [":flag_pa:"] = "\uD83C\uDDF5\uD83C\uDDE6", + [":flag_pe:"] = "\uD83C\uDDF5\uD83C\uDDEA", + [":flag_pf:"] = "\uD83C\uDDF5\uD83C\uDDEB", + [":flag_pg:"] = "\uD83C\uDDF5\uD83C\uDDEC", + [":flag_ph:"] = "\uD83C\uDDF5\uD83C\uDDED", + [":flag_pk:"] = "\uD83C\uDDF5\uD83C\uDDF0", + [":flag_pl:"] = "\uD83C\uDDF5\uD83C\uDDF1", + [":flag_pm:"] = "\uD83C\uDDF5\uD83C\uDDF2", + [":flag_pn:"] = "\uD83C\uDDF5\uD83C\uDDF3", + [":flag_pr:"] = "\uD83C\uDDF5\uD83C\uDDF7", + [":flag_ps:"] = "\uD83C\uDDF5\uD83C\uDDF8", + [":flag_pt:"] = "\uD83C\uDDF5\uD83C\uDDF9", + [":flag_pw:"] = "\uD83C\uDDF5\uD83C\uDDFC", + [":flag_py:"] = "\uD83C\uDDF5\uD83C\uDDFE", + [":flag_qa:"] = "\uD83C\uDDF6\uD83C\uDDE6", + [":flag_re:"] = "\uD83C\uDDF7\uD83C\uDDEA", + [":flag_ro:"] = "\uD83C\uDDF7\uD83C\uDDF4", + [":flag_rs:"] = "\uD83C\uDDF7\uD83C\uDDF8", + [":flag_ru:"] = "\uD83C\uDDF7\uD83C\uDDFA", + [":flag_rw:"] = "\uD83C\uDDF7\uD83C\uDDFC", + [":flag_sa:"] = "\uD83C\uDDF8\uD83C\uDDE6", + [":flag_sb:"] = "\uD83C\uDDF8\uD83C\uDDE7", + [":flag_sc:"] = "\uD83C\uDDF8\uD83C\uDDE8", + [":flag_sd:"] = "\uD83C\uDDF8\uD83C\uDDE9", + [":flag_se:"] = "\uD83C\uDDF8\uD83C\uDDEA", + [":flag_sg:"] = "\uD83C\uDDF8\uD83C\uDDEC", + [":flag_sh:"] = "\uD83C\uDDF8\uD83C\uDDED", + [":flag_si:"] = "\uD83C\uDDF8\uD83C\uDDEE", + [":flag_sj:"] = "\uD83C\uDDF8\uD83C\uDDEF", + [":flag_sk:"] = "\uD83C\uDDF8\uD83C\uDDF0", + [":flag_sl:"] = "\uD83C\uDDF8\uD83C\uDDF1", + [":flag_sm:"] = "\uD83C\uDDF8\uD83C\uDDF2", + [":flag_sn:"] = "\uD83C\uDDF8\uD83C\uDDF3", + [":flag_so:"] = "\uD83C\uDDF8\uD83C\uDDF4", + [":flag_sr:"] = "\uD83C\uDDF8\uD83C\uDDF7", + [":flag_ss:"] = "\uD83C\uDDF8\uD83C\uDDF8", + [":flag_st:"] = "\uD83C\uDDF8\uD83C\uDDF9", + [":flag_sv:"] = "\uD83C\uDDF8\uD83C\uDDFB", + [":flag_sx:"] = "\uD83C\uDDF8\uD83C\uDDFD", + [":flag_sy:"] = "\uD83C\uDDF8\uD83C\uDDFE", + [":flag_sz:"] = "\uD83C\uDDF8\uD83C\uDDFF", + [":flag_ta:"] = "\uD83C\uDDF9\uD83C\uDDE6", + [":flag_tc:"] = "\uD83C\uDDF9\uD83C\uDDE8", + [":flag_td:"] = "\uD83C\uDDF9\uD83C\uDDE9", + [":flag_tf:"] = "\uD83C\uDDF9\uD83C\uDDEB", + [":flag_tg:"] = "\uD83C\uDDF9\uD83C\uDDEC", + [":flag_th:"] = "\uD83C\uDDF9\uD83C\uDDED", + [":flag_tj:"] = "\uD83C\uDDF9\uD83C\uDDEF", + [":flag_tk:"] = "\uD83C\uDDF9\uD83C\uDDF0", + [":flag_tl:"] = "\uD83C\uDDF9\uD83C\uDDF1", + [":flag_tm:"] = "\uD83C\uDDF9\uD83C\uDDF2", + [":flag_tn:"] = "\uD83C\uDDF9\uD83C\uDDF3", + [":flag_to:"] = "\uD83C\uDDF9\uD83C\uDDF4", + [":flag_tr:"] = "\uD83C\uDDF9\uD83C\uDDF7", + [":flag_tt:"] = "\uD83C\uDDF9\uD83C\uDDF9", + [":flag_tv:"] = "\uD83C\uDDF9\uD83C\uDDFB", + [":flag_tw:"] = "\uD83C\uDDF9\uD83C\uDDFC", + [":flag_tz:"] = "\uD83C\uDDF9\uD83C\uDDFF", + [":flag_ua:"] = "\uD83C\uDDFA\uD83C\uDDE6", + [":flag_ug:"] = "\uD83C\uDDFA\uD83C\uDDEC", + [":flag_um:"] = "\uD83C\uDDFA\uD83C\uDDF2", + [":flag_us:"] = "\uD83C\uDDFA\uD83C\uDDF8", + [":flag_uy:"] = "\uD83C\uDDFA\uD83C\uDDFE", + [":flag_uz:"] = "\uD83C\uDDFA\uD83C\uDDFF", + [":flag_va:"] = "\uD83C\uDDFB\uD83C\uDDE6", + [":flag_vc:"] = "\uD83C\uDDFB\uD83C\uDDE8", + [":flag_ve:"] = "\uD83C\uDDFB\uD83C\uDDEA", + [":flag_vg:"] = "\uD83C\uDDFB\uD83C\uDDEC", + [":flag_vi:"] = "\uD83C\uDDFB\uD83C\uDDEE", + [":flag_vn:"] = "\uD83C\uDDFB\uD83C\uDDF3", + [":flag_vu:"] = "\uD83C\uDDFB\uD83C\uDDFA", + [":flag_wf:"] = "\uD83C\uDDFC\uD83C\uDDEB", + [":flag_white:"] = "\uD83C\uDFF3️", + [":flag_ws:"] = "\uD83C\uDDFC\uD83C\uDDF8", + [":flag_xk:"] = "\uD83C\uDDFD\uD83C\uDDF0", + [":flag_ye:"] = "\uD83C\uDDFE\uD83C\uDDEA", + [":flag_yt:"] = "\uD83C\uDDFE\uD83C\uDDF9", + [":flag_za:"] = "\uD83C\uDDFF\uD83C\uDDE6", + [":flag_zm:"] = "\uD83C\uDDFF\uD83C\uDDF2", + [":flag_zw:"] = "\uD83C\uDDFF\uD83C\uDDFC", + [":flags:"] = "\uD83C\uDF8F", + [":flame:"] = "\uD83D\uDD25", + [":flamingo:"] = "\uD83E\uDDA9", + [":flan:"] = "\uD83C\uDF6E", + [":flashlight:"] = "\uD83D\uDD26", + [":fleur_de_lis:"] = "⚜️", + [":floppy_disk:"] = "\uD83D\uDCBE", + [":flower_playing_cards:"] = "\uD83C\uDFB4", + [":flushed:"] = "\uD83D\uDE33", + [":flying_disc:"] = "\uD83E\uDD4F", + [":flying_saucer:"] = "\uD83D\uDEF8", + [":fog:"] = "\uD83C\uDF2B️", + [":foggy:"] = "\uD83C\uDF01", + [":foot:"] = "\uD83E\uDDB6", + [":foot::skin-tone-1:"] = "\uD83E\uDDB6\uD83C\uDFFB", + [":foot::skin-tone-2:"] = "\uD83E\uDDB6\uD83C\uDFFC", + [":foot::skin-tone-3:"] = "\uD83E\uDDB6\uD83C\uDFFD", + [":foot::skin-tone-4:"] = "\uD83E\uDDB6\uD83C\uDFFE", + [":foot::skin-tone-5:"] = "\uD83E\uDDB6\uD83C\uDFFF", + [":foot_dark_skin_tone:"] = "\uD83E\uDDB6\uD83C\uDFFF", + [":foot_light_skin_tone:"] = "\uD83E\uDDB6\uD83C\uDFFB", + [":foot_medium_dark_skin_tone:"] = "\uD83E\uDDB6\uD83C\uDFFE", + [":foot_medium_light_skin_tone:"] = "\uD83E\uDDB6\uD83C\uDFFC", + [":foot_medium_skin_tone:"] = "\uD83E\uDDB6\uD83C\uDFFD", + [":foot_tone1:"] = "\uD83E\uDDB6\uD83C\uDFFB", + [":foot_tone2:"] = "\uD83E\uDDB6\uD83C\uDFFC", + [":foot_tone3:"] = "\uD83E\uDDB6\uD83C\uDFFD", + [":foot_tone4:"] = "\uD83E\uDDB6\uD83C\uDFFE", + [":foot_tone5:"] = "\uD83E\uDDB6\uD83C\uDFFF", + [":football:"] = "\uD83C\uDFC8", + [":footprints:"] = "\uD83D\uDC63", + [":fork_and_knife:"] = "\uD83C\uDF74", + [":fork_and_knife_with_plate:"] = "\uD83C\uDF7D️", + [":fork_knife_plate:"] = "\uD83C\uDF7D️", + [":fortune_cookie:"] = "\uD83E\uDD60", + [":fountain:"] = "⛲", + [":four:"] = "4️⃣", + [":four_leaf_clover:"] = "\uD83C\uDF40", + [":fox:"] = "\uD83E\uDD8A", + [":fox_face:"] = "\uD83E\uDD8A", + [":frame_photo:"] = "\uD83D\uDDBC️", + [":frame_with_picture:"] = "\uD83D\uDDBC️", + [":free:"] = "\uD83C\uDD93", + [":french_bread:"] = "\uD83E\uDD56", + [":fried_shrimp:"] = "\uD83C\uDF64", + [":fries:"] = "\uD83C\uDF5F", + [":frog:"] = "\uD83D\uDC38", + [":frowning2:"] = "☹️", + [":frowning:"] = "\uD83D\uDE26", + [":fuelpump:"] = "⛽", + [":full_moon:"] = "\uD83C\uDF15", + [":full_moon_with_face:"] = "\uD83C\uDF1D", + [":funeral_urn:"] = "⚱️", + [":game_die:"] = "\uD83C\uDFB2", + [":garlic:"] = "\uD83E\uDDC4", + [":gay_pride_flag:"] = "\uD83C\uDFF3️\u200D\uD83C\uDF08", + [":gear:"] = "⚙️", + [":gem:"] = "\uD83D\uDC8E", + [":gemini:"] = "♊", + [":genie:"] = "\uD83E\uDDDE", + [":ghost:"] = "\uD83D\uDC7B", + [":gift:"] = "\uD83C\uDF81", + [":gift_heart:"] = "\uD83D\uDC9D", + [":giraffe:"] = "\uD83E\uDD92", + [":girl:"] = "\uD83D\uDC67", + [":girl::skin-tone-1:"] = "\uD83D\uDC67\uD83C\uDFFB", + [":girl::skin-tone-2:"] = "\uD83D\uDC67\uD83C\uDFFC", + [":girl::skin-tone-3:"] = "\uD83D\uDC67\uD83C\uDFFD", + [":girl::skin-tone-4:"] = "\uD83D\uDC67\uD83C\uDFFE", + [":girl::skin-tone-5:"] = "\uD83D\uDC67\uD83C\uDFFF", + [":girl_tone1:"] = "\uD83D\uDC67\uD83C\uDFFB", + [":girl_tone2:"] = "\uD83D\uDC67\uD83C\uDFFC", + [":girl_tone3:"] = "\uD83D\uDC67\uD83C\uDFFD", + [":girl_tone4:"] = "\uD83D\uDC67\uD83C\uDFFE", + [":girl_tone5:"] = "\uD83D\uDC67\uD83C\uDFFF", + [":glass_of_milk:"] = "\uD83E\uDD5B", + [":globe_with_meridians:"] = "\uD83C\uDF10", + [":gloves:"] = "\uD83E\uDDE4", + [":goal:"] = "\uD83E\uDD45", + [":goal_net:"] = "\uD83E\uDD45", + [":goat:"] = "\uD83D\uDC10", + [":goggles:"] = "\uD83E\uDD7D", + [":golf:"] = "⛳", + [":golfer:"] = "\uD83C\uDFCC️", + [":golfer::skin-tone-1:"] = "\uD83C\uDFCC\uD83C\uDFFB", + [":golfer::skin-tone-2:"] = "\uD83C\uDFCC\uD83C\uDFFC", + [":golfer::skin-tone-3:"] = "\uD83C\uDFCC\uD83C\uDFFD", + [":golfer::skin-tone-4:"] = "\uD83C\uDFCC\uD83C\uDFFE", + [":golfer::skin-tone-5:"] = "\uD83C\uDFCC\uD83C\uDFFF", + [":gorilla:"] = "\uD83E\uDD8D", + [":grandma:"] = "\uD83D\uDC75", + [":grandma::skin-tone-1:"] = "\uD83D\uDC75\uD83C\uDFFB", + [":grandma::skin-tone-2:"] = "\uD83D\uDC75\uD83C\uDFFC", + [":grandma::skin-tone-3:"] = "\uD83D\uDC75\uD83C\uDFFD", + [":grandma::skin-tone-4:"] = "\uD83D\uDC75\uD83C\uDFFE", + [":grandma::skin-tone-5:"] = "\uD83D\uDC75\uD83C\uDFFF", + [":grandma_tone1:"] = "\uD83D\uDC75\uD83C\uDFFB", + [":grandma_tone2:"] = "\uD83D\uDC75\uD83C\uDFFC", + [":grandma_tone3:"] = "\uD83D\uDC75\uD83C\uDFFD", + [":grandma_tone4:"] = "\uD83D\uDC75\uD83C\uDFFE", + [":grandma_tone5:"] = "\uD83D\uDC75\uD83C\uDFFF", + [":grapes:"] = "\uD83C\uDF47", + [":green_apple:"] = "\uD83C\uDF4F", + [":green_book:"] = "\uD83D\uDCD7", + [":green_circle:"] = "\uD83D\uDFE2", + [":green_heart:"] = "\uD83D\uDC9A", + [":green_salad:"] = "\uD83E\uDD57", + [":green_square:"] = "\uD83D\uDFE9", + [":grey_exclamation:"] = "❕", + [":grey_question:"] = "❔", + [":grimacing:"] = "\uD83D\uDE2C", + [":grin:"] = "\uD83D\uDE01", + [":grinning:"] = "\uD83D\uDE00", + [":guard:"] = "\uD83D\uDC82", + [":guard::skin-tone-1:"] = "\uD83D\uDC82\uD83C\uDFFB", + [":guard::skin-tone-2:"] = "\uD83D\uDC82\uD83C\uDFFC", + [":guard::skin-tone-3:"] = "\uD83D\uDC82\uD83C\uDFFD", + [":guard::skin-tone-4:"] = "\uD83D\uDC82\uD83C\uDFFE", + [":guard::skin-tone-5:"] = "\uD83D\uDC82\uD83C\uDFFF", + [":guard_tone1:"] = "\uD83D\uDC82\uD83C\uDFFB", + [":guard_tone2:"] = "\uD83D\uDC82\uD83C\uDFFC", + [":guard_tone3:"] = "\uD83D\uDC82\uD83C\uDFFD", + [":guard_tone4:"] = "\uD83D\uDC82\uD83C\uDFFE", + [":guard_tone5:"] = "\uD83D\uDC82\uD83C\uDFFF", + [":guardsman:"] = "\uD83D\uDC82", + [":guardsman::skin-tone-1:"] = "\uD83D\uDC82\uD83C\uDFFB", + [":guardsman::skin-tone-2:"] = "\uD83D\uDC82\uD83C\uDFFC", + [":guardsman::skin-tone-3:"] = "\uD83D\uDC82\uD83C\uDFFD", + [":guardsman::skin-tone-4:"] = "\uD83D\uDC82\uD83C\uDFFE", + [":guardsman::skin-tone-5:"] = "\uD83D\uDC82\uD83C\uDFFF", + [":guardsman_tone1:"] = "\uD83D\uDC82\uD83C\uDFFB", + [":guardsman_tone2:"] = "\uD83D\uDC82\uD83C\uDFFC", + [":guardsman_tone3:"] = "\uD83D\uDC82\uD83C\uDFFD", + [":guardsman_tone4:"] = "\uD83D\uDC82\uD83C\uDFFE", + [":guardsman_tone5:"] = "\uD83D\uDC82\uD83C\uDFFF", + [":guide_dog:"] = "\uD83E\uDDAE", + [":guitar:"] = "\uD83C\uDFB8", + [":gun:"] = "\uD83D\uDD2B", + [":haircut:"] = "\uD83D\uDC87", + [":haircut::skin-tone-1:"] = "\uD83D\uDC87\uD83C\uDFFB", + [":haircut::skin-tone-2:"] = "\uD83D\uDC87\uD83C\uDFFC", + [":haircut::skin-tone-3:"] = "\uD83D\uDC87\uD83C\uDFFD", + [":haircut::skin-tone-4:"] = "\uD83D\uDC87\uD83C\uDFFE", + [":haircut::skin-tone-5:"] = "\uD83D\uDC87\uD83C\uDFFF", + [":haircut_tone1:"] = "\uD83D\uDC87\uD83C\uDFFB", + [":haircut_tone2:"] = "\uD83D\uDC87\uD83C\uDFFC", + [":haircut_tone3:"] = "\uD83D\uDC87\uD83C\uDFFD", + [":haircut_tone4:"] = "\uD83D\uDC87\uD83C\uDFFE", + [":haircut_tone5:"] = "\uD83D\uDC87\uD83C\uDFFF", + [":hamburger:"] = "\uD83C\uDF54", + [":hammer:"] = "\uD83D\uDD28", + [":hammer_and_pick:"] = "⚒️", + [":hammer_and_wrench:"] = "\uD83D\uDEE0️", + [":hammer_pick:"] = "⚒️", + [":hamster:"] = "\uD83D\uDC39", + [":hand_splayed:"] = "\uD83D\uDD90️", + [":hand_splayed::skin-tone-1:"] = "\uD83D\uDD90\uD83C\uDFFB", + [":hand_splayed::skin-tone-2:"] = "\uD83D\uDD90\uD83C\uDFFC", + [":hand_splayed::skin-tone-3:"] = "\uD83D\uDD90\uD83C\uDFFD", + [":hand_splayed::skin-tone-4:"] = "\uD83D\uDD90\uD83C\uDFFE", + [":hand_splayed::skin-tone-5:"] = "\uD83D\uDD90\uD83C\uDFFF", + [":hand_splayed_tone1:"] = "\uD83D\uDD90\uD83C\uDFFB", + [":hand_splayed_tone2:"] = "\uD83D\uDD90\uD83C\uDFFC", + [":hand_splayed_tone3:"] = "\uD83D\uDD90\uD83C\uDFFD", + [":hand_splayed_tone4:"] = "\uD83D\uDD90\uD83C\uDFFE", + [":hand_splayed_tone5:"] = "\uD83D\uDD90\uD83C\uDFFF", + [":hand_with_index_and_middle_finger_crossed:"] = "\uD83E\uDD1E", + [":hand_with_index_and_middle_finger_crossed::skin-tone-1:"] = "\uD83E\uDD1E\uD83C\uDFFB", + [":hand_with_index_and_middle_finger_crossed::skin-tone-2:"] = "\uD83E\uDD1E\uD83C\uDFFC", + [":hand_with_index_and_middle_finger_crossed::skin-tone-3:"] = "\uD83E\uDD1E\uD83C\uDFFD", + [":hand_with_index_and_middle_finger_crossed::skin-tone-4:"] = "\uD83E\uDD1E\uD83C\uDFFE", + [":hand_with_index_and_middle_finger_crossed::skin-tone-5:"] = "\uD83E\uDD1E\uD83C\uDFFF", + [":hand_with_index_and_middle_fingers_crossed_tone1:"] = "\uD83E\uDD1E\uD83C\uDFFB", + [":hand_with_index_and_middle_fingers_crossed_tone2:"] = "\uD83E\uDD1E\uD83C\uDFFC", + [":hand_with_index_and_middle_fingers_crossed_tone3:"] = "\uD83E\uDD1E\uD83C\uDFFD", + [":hand_with_index_and_middle_fingers_crossed_tone4:"] = "\uD83E\uDD1E\uD83C\uDFFE", + [":hand_with_index_and_middle_fingers_crossed_tone5:"] = "\uD83E\uDD1E\uD83C\uDFFF", + [":handbag:"] = "\uD83D\uDC5C", + [":handball:"] = "\uD83E\uDD3E", + [":handball::skin-tone-1:"] = "\uD83E\uDD3E\uD83C\uDFFB", + [":handball::skin-tone-2:"] = "\uD83E\uDD3E\uD83C\uDFFC", + [":handball::skin-tone-3:"] = "\uD83E\uDD3E\uD83C\uDFFD", + [":handball::skin-tone-4:"] = "\uD83E\uDD3E\uD83C\uDFFE", + [":handball::skin-tone-5:"] = "\uD83E\uDD3E\uD83C\uDFFF", + [":handball_tone1:"] = "\uD83E\uDD3E\uD83C\uDFFB", + [":handball_tone2:"] = "\uD83E\uDD3E\uD83C\uDFFC", + [":handball_tone3:"] = "\uD83E\uDD3E\uD83C\uDFFD", + [":handball_tone4:"] = "\uD83E\uDD3E\uD83C\uDFFE", + [":handball_tone5:"] = "\uD83E\uDD3E\uD83C\uDFFF", + [":handshake:"] = "\uD83E\uDD1D", + [":hankey:"] = "\uD83D\uDCA9", + [":hash:"] = "#️⃣", + [":hatched_chick:"] = "\uD83D\uDC25", + [":hatching_chick:"] = "\uD83D\uDC23", + [":head_bandage:"] = "\uD83E\uDD15", + [":headphones:"] = "\uD83C\uDFA7", + [":hear_no_evil:"] = "\uD83D\uDE49", + [":heart:"] = "❤️", + [":heart_decoration:"] = "\uD83D\uDC9F", + [":heart_exclamation:"] = "❣️", + [":heart_eyes:"] = "\uD83D\uDE0D", + [":heart_eyes_cat:"] = "\uD83D\uDE3B", + [":heartbeat:"] = "\uD83D\uDC93", + [":heartpulse:"] = "\uD83D\uDC97", + [":hearts:"] = "♥️", + [":heavy_check_mark:"] = "✔️", + [":heavy_division_sign:"] = "➗", + [":heavy_dollar_sign:"] = "\uD83D\uDCB2", + [":heavy_heart_exclamation_mark_ornament:"] = "❣️", + [":heavy_minus_sign:"] = "➖", + [":heavy_multiplication_x:"] = "✖️", + [":heavy_plus_sign:"] = "➕", + [":hedgehog:"] = "\uD83E\uDD94", + [":helicopter:"] = "\uD83D\uDE81", + [":helmet_with_cross:"] = "⛑️", + [":helmet_with_white_cross:"] = "⛑️", + [":herb:"] = "\uD83C\uDF3F", + [":hibiscus:"] = "\uD83C\uDF3A", + [":high_brightness:"] = "\uD83D\uDD06", + [":high_heel:"] = "\uD83D\uDC60", + [":hiking_boot:"] = "\uD83E\uDD7E", + [":hindu_temple:"] = "\uD83D\uDED5", + [":hippopotamus:"] = "\uD83E\uDD9B", + [":hockey:"] = "\uD83C\uDFD2", + [":hole:"] = "\uD83D\uDD73️", + [":homes:"] = "\uD83C\uDFD8️", + [":honey_pot:"] = "\uD83C\uDF6F", + [":horse:"] = "\uD83D\uDC34", + [":horse_racing:"] = "\uD83C\uDFC7", + [":horse_racing::skin-tone-1:"] = "\uD83C\uDFC7\uD83C\uDFFB", + [":horse_racing::skin-tone-2:"] = "\uD83C\uDFC7\uD83C\uDFFC", + [":horse_racing::skin-tone-3:"] = "\uD83C\uDFC7\uD83C\uDFFD", + [":horse_racing::skin-tone-4:"] = "\uD83C\uDFC7\uD83C\uDFFE", + [":horse_racing::skin-tone-5:"] = "\uD83C\uDFC7\uD83C\uDFFF", + [":horse_racing_tone1:"] = "\uD83C\uDFC7\uD83C\uDFFB", + [":horse_racing_tone2:"] = "\uD83C\uDFC7\uD83C\uDFFC", + [":horse_racing_tone3:"] = "\uD83C\uDFC7\uD83C\uDFFD", + [":horse_racing_tone4:"] = "\uD83C\uDFC7\uD83C\uDFFE", + [":horse_racing_tone5:"] = "\uD83C\uDFC7\uD83C\uDFFF", + [":hospital:"] = "\uD83C\uDFE5", + [":hot_dog:"] = "\uD83C\uDF2D", + [":hot_face:"] = "\uD83E\uDD75", + [":hot_pepper:"] = "\uD83C\uDF36️", + [":hotdog:"] = "\uD83C\uDF2D", + [":hotel:"] = "\uD83C\uDFE8", + [":hotsprings:"] = "♨️", + [":hourglass:"] = "⌛", + [":hourglass_flowing_sand:"] = "⏳", + [":house:"] = "\uD83C\uDFE0", + [":house_abandoned:"] = "\uD83C\uDFDA️", + [":house_buildings:"] = "\uD83C\uDFD8️", + [":house_with_garden:"] = "\uD83C\uDFE1", + [":hugging:"] = "\uD83E\uDD17", + [":hugging_face:"] = "\uD83E\uDD17", + [":hushed:"] = "\uD83D\uDE2F", + [":ice_cream:"] = "\uD83C\uDF68", + [":ice_cube:"] = "\uD83E\uDDCA", + [":ice_skate:"] = "⛸️", + [":icecream:"] = "\uD83C\uDF66", + [":id:"] = "\uD83C\uDD94", + [":ideograph_advantage:"] = "\uD83C\uDE50", + [":imp:"] = "\uD83D\uDC7F", + [":inbox_tray:"] = "\uD83D\uDCE5", + [":incoming_envelope:"] = "\uD83D\uDCE8", + [":infinity:"] = "♾️", + [":information_desk_person:"] = "\uD83D\uDC81", + [":information_desk_person::skin-tone-1:"] = "\uD83D\uDC81\uD83C\uDFFB", + [":information_desk_person::skin-tone-2:"] = "\uD83D\uDC81\uD83C\uDFFC", + [":information_desk_person::skin-tone-3:"] = "\uD83D\uDC81\uD83C\uDFFD", + [":information_desk_person::skin-tone-4:"] = "\uD83D\uDC81\uD83C\uDFFE", + [":information_desk_person::skin-tone-5:"] = "\uD83D\uDC81\uD83C\uDFFF", + [":information_desk_person_tone1:"] = "\uD83D\uDC81\uD83C\uDFFB", + [":information_desk_person_tone2:"] = "\uD83D\uDC81\uD83C\uDFFC", + [":information_desk_person_tone3:"] = "\uD83D\uDC81\uD83C\uDFFD", + [":information_desk_person_tone4:"] = "\uD83D\uDC81\uD83C\uDFFE", + [":information_desk_person_tone5:"] = "\uD83D\uDC81\uD83C\uDFFF", + [":information_source:"] = "ℹ️", + [":innocent:"] = "\uD83D\uDE07", + [":interrobang:"] = "⁉️", + [":iphone:"] = "\uD83D\uDCF1", + [":island:"] = "\uD83C\uDFDD️", + [":izakaya_lantern:"] = "\uD83C\uDFEE", + [":jack_o_lantern:"] = "\uD83C\uDF83", + [":japan:"] = "\uD83D\uDDFE", + [":japanese_castle:"] = "\uD83C\uDFEF", + [":japanese_goblin:"] = "\uD83D\uDC7A", + [":japanese_ogre:"] = "\uD83D\uDC79", + [":jeans:"] = "\uD83D\uDC56", + [":jigsaw:"] = "\uD83E\uDDE9", + [":joy:"] = "\uD83D\uDE02", + [":joy_cat:"] = "\uD83D\uDE39", + [":joystick:"] = "\uD83D\uDD79️", + [":juggler:"] = "\uD83E\uDD39", + [":juggler::skin-tone-1:"] = "\uD83E\uDD39\uD83C\uDFFB", + [":juggler::skin-tone-2:"] = "\uD83E\uDD39\uD83C\uDFFC", + [":juggler::skin-tone-3:"] = "\uD83E\uDD39\uD83C\uDFFD", + [":juggler::skin-tone-4:"] = "\uD83E\uDD39\uD83C\uDFFE", + [":juggler::skin-tone-5:"] = "\uD83E\uDD39\uD83C\uDFFF", + [":juggler_tone1:"] = "\uD83E\uDD39\uD83C\uDFFB", + [":juggler_tone2:"] = "\uD83E\uDD39\uD83C\uDFFC", + [":juggler_tone3:"] = "\uD83E\uDD39\uD83C\uDFFD", + [":juggler_tone4:"] = "\uD83E\uDD39\uD83C\uDFFE", + [":juggler_tone5:"] = "\uD83E\uDD39\uD83C\uDFFF", + [":juggling:"] = "\uD83E\uDD39", + [":juggling::skin-tone-1:"] = "\uD83E\uDD39\uD83C\uDFFB", + [":juggling::skin-tone-2:"] = "\uD83E\uDD39\uD83C\uDFFC", + [":juggling::skin-tone-3:"] = "\uD83E\uDD39\uD83C\uDFFD", + [":juggling::skin-tone-4:"] = "\uD83E\uDD39\uD83C\uDFFE", + [":juggling::skin-tone-5:"] = "\uD83E\uDD39\uD83C\uDFFF", + [":juggling_tone1:"] = "\uD83E\uDD39\uD83C\uDFFB", + [":juggling_tone2:"] = "\uD83E\uDD39\uD83C\uDFFC", + [":juggling_tone3:"] = "\uD83E\uDD39\uD83C\uDFFD", + [":juggling_tone4:"] = "\uD83E\uDD39\uD83C\uDFFE", + [":juggling_tone5:"] = "\uD83E\uDD39\uD83C\uDFFF", + [":kaaba:"] = "\uD83D\uDD4B", + [":kangaroo:"] = "\uD83E\uDD98", + [":karate_uniform:"] = "\uD83E\uDD4B", + [":kayak:"] = "\uD83D\uDEF6", + [":key2:"] = "\uD83D\uDDDD️", + [":key:"] = "\uD83D\uDD11", + [":keyboard:"] = "⌨️", + [":keycap_asterisk:"] = "*️⃣", + [":keycap_ten:"] = "\uD83D\uDD1F", + [":kimono:"] = "\uD83D\uDC58", + [":kiss:"] = "\uD83D\uDC8B", + [":kiss_mm:"] = "\uD83D\uDC68\u200D❤️\u200D\uD83D\uDC8B\u200D\uD83D\uDC68", + [":kiss_woman_man:"] = "\uD83D\uDC69\u200D❤️\u200D\uD83D\uDC8B\u200D\uD83D\uDC68", + [":kiss_ww:"] = "\uD83D\uDC69\u200D❤️\u200D\uD83D\uDC8B\u200D\uD83D\uDC69", + [":kissing:"] = "\uD83D\uDE17", + [":kissing_cat:"] = "\uD83D\uDE3D", + [":kissing_closed_eyes:"] = "\uD83D\uDE1A", + [":kissing_heart:"] = "\uD83D\uDE18", + [":kissing_smiling_eyes:"] = "\uD83D\uDE19", + [":kite:"] = "\uD83E\uDE81", + [":kiwi:"] = "\uD83E\uDD5D", + [":kiwifruit:"] = "\uD83E\uDD5D", + [":knife:"] = "\uD83D\uDD2A", + [":koala:"] = "\uD83D\uDC28", + [":koko:"] = "\uD83C\uDE01", + [":knot:"] = "\uD83E\uDEA2", + [":lab_coat:"] = "\uD83E\uDD7C", + [":label:"] = "\uD83C\uDFF7️", + [":lacrosse:"] = "\uD83E\uDD4D", + [":large_blue_diamond:"] = "\uD83D\uDD37", + [":large_orange_diamond:"] = "\uD83D\uDD36", + [":last_quarter_moon:"] = "\uD83C\uDF17", + [":last_quarter_moon_with_face:"] = "\uD83C\uDF1C", + [":latin_cross:"] = "✝️", + [":laughing:"] = "\uD83D\uDE06", + [":leafy_green:"] = "\uD83E\uDD6C", + [":leaves:"] = "\uD83C\uDF43", + [":ledger:"] = "\uD83D\uDCD2", + [":left_facing_fist:"] = "\uD83E\uDD1B", + [":left_facing_fist::skin-tone-1:"] = "\uD83E\uDD1B\uD83C\uDFFB", + [":left_facing_fist::skin-tone-2:"] = "\uD83E\uDD1B\uD83C\uDFFC", + [":left_facing_fist::skin-tone-3:"] = "\uD83E\uDD1B\uD83C\uDFFD", + [":left_facing_fist::skin-tone-4:"] = "\uD83E\uDD1B\uD83C\uDFFE", + [":left_facing_fist::skin-tone-5:"] = "\uD83E\uDD1B\uD83C\uDFFF", + [":left_facing_fist_tone1:"] = "\uD83E\uDD1B\uD83C\uDFFB", + [":left_facing_fist_tone2:"] = "\uD83E\uDD1B\uD83C\uDFFC", + [":left_facing_fist_tone3:"] = "\uD83E\uDD1B\uD83C\uDFFD", + [":left_facing_fist_tone4:"] = "\uD83E\uDD1B\uD83C\uDFFE", + [":left_facing_fist_tone5:"] = "\uD83E\uDD1B\uD83C\uDFFF", + [":left_fist:"] = "\uD83E\uDD1B", + [":left_fist::skin-tone-1:"] = "\uD83E\uDD1B\uD83C\uDFFB", + [":left_fist::skin-tone-2:"] = "\uD83E\uDD1B\uD83C\uDFFC", + [":left_fist::skin-tone-3:"] = "\uD83E\uDD1B\uD83C\uDFFD", + [":left_fist::skin-tone-4:"] = "\uD83E\uDD1B\uD83C\uDFFE", + [":left_fist::skin-tone-5:"] = "\uD83E\uDD1B\uD83C\uDFFF", + [":left_fist_tone1:"] = "\uD83E\uDD1B\uD83C\uDFFB", + [":left_fist_tone2:"] = "\uD83E\uDD1B\uD83C\uDFFC", + [":left_fist_tone3:"] = "\uD83E\uDD1B\uD83C\uDFFD", + [":left_fist_tone4:"] = "\uD83E\uDD1B\uD83C\uDFFE", + [":left_fist_tone5:"] = "\uD83E\uDD1B\uD83C\uDFFF", + [":left_luggage:"] = "\uD83D\uDEC5", + [":left_right_arrow:"] = "↔️", + [":left_speech_bubble:"] = "\uD83D\uDDE8️", + [":leftwards_arrow_with_hook:"] = "↩️", + [":leg:"] = "\uD83E\uDDB5", + [":leg::skin-tone-1:"] = "\uD83E\uDDB5\uD83C\uDFFB", + [":leg::skin-tone-2:"] = "\uD83E\uDDB5\uD83C\uDFFC", + [":leg::skin-tone-3:"] = "\uD83E\uDDB5\uD83C\uDFFD", + [":leg::skin-tone-4:"] = "\uD83E\uDDB5\uD83C\uDFFE", + [":leg::skin-tone-5:"] = "\uD83E\uDDB5\uD83C\uDFFF", + [":leg_dark_skin_tone:"] = "\uD83E\uDDB5\uD83C\uDFFF", + [":leg_light_skin_tone:"] = "\uD83E\uDDB5\uD83C\uDFFB", + [":leg_medium_dark_skin_tone:"] = "\uD83E\uDDB5\uD83C\uDFFE", + [":leg_medium_light_skin_tone:"] = "\uD83E\uDDB5\uD83C\uDFFC", + [":leg_medium_skin_tone:"] = "\uD83E\uDDB5\uD83C\uDFFD", + [":leg_tone1:"] = "\uD83E\uDDB5\uD83C\uDFFB", + [":leg_tone2:"] = "\uD83E\uDDB5\uD83C\uDFFC", + [":leg_tone3:"] = "\uD83E\uDDB5\uD83C\uDFFD", + [":leg_tone4:"] = "\uD83E\uDDB5\uD83C\uDFFE", + [":leg_tone5:"] = "\uD83E\uDDB5\uD83C\uDFFF", + [":lemon:"] = "\uD83C\uDF4B", + [":leo:"] = "♌", + [":leopard:"] = "\uD83D\uDC06", + [":level_slider:"] = "\uD83C\uDF9A️", + [":levitate:"] = "\uD83D\uDD74️", + [":levitate::skin-tone-1:"] = "\uD83D\uDD74\uD83C\uDFFB", + [":levitate::skin-tone-2:"] = "\uD83D\uDD74\uD83C\uDFFC", + [":levitate::skin-tone-3:"] = "\uD83D\uDD74\uD83C\uDFFD", + [":levitate::skin-tone-4:"] = "\uD83D\uDD74\uD83C\uDFFE", + [":levitate::skin-tone-5:"] = "\uD83D\uDD74\uD83C\uDFFF", + [":levitate_tone1:"] = "\uD83D\uDD74\uD83C\uDFFB", + [":levitate_tone2:"] = "\uD83D\uDD74\uD83C\uDFFC", + [":levitate_tone3:"] = "\uD83D\uDD74\uD83C\uDFFD", + [":levitate_tone4:"] = "\uD83D\uDD74\uD83C\uDFFE", + [":levitate_tone5:"] = "\uD83D\uDD74\uD83C\uDFFF", + [":liar:"] = "\uD83E\uDD25", + [":libra:"] = "♎", + [":lifter:"] = "\uD83C\uDFCB️", + [":lifter::skin-tone-1:"] = "\uD83C\uDFCB\uD83C\uDFFB", + [":lifter::skin-tone-2:"] = "\uD83C\uDFCB\uD83C\uDFFC", + [":lifter::skin-tone-3:"] = "\uD83C\uDFCB\uD83C\uDFFD", + [":lifter::skin-tone-4:"] = "\uD83C\uDFCB\uD83C\uDFFE", + [":lifter::skin-tone-5:"] = "\uD83C\uDFCB\uD83C\uDFFF", + [":lifter_tone1:"] = "\uD83C\uDFCB\uD83C\uDFFB", + [":lifter_tone2:"] = "\uD83C\uDFCB\uD83C\uDFFC", + [":lifter_tone3:"] = "\uD83C\uDFCB\uD83C\uDFFD", + [":lifter_tone4:"] = "\uD83C\uDFCB\uD83C\uDFFE", + [":lifter_tone5:"] = "\uD83C\uDFCB\uD83C\uDFFF", + [":light_rail:"] = "\uD83D\uDE88", + [":link:"] = "\uD83D\uDD17", + [":linked_paperclips:"] = "\uD83D\uDD87️", + [":lion:"] = "\uD83E\uDD81", + [":lion_face:"] = "\uD83E\uDD81", + [":lips:"] = "\uD83D\uDC44", + [":lipstick:"] = "\uD83D\uDC84", + [":lizard:"] = "\uD83E\uDD8E", + [":llama:"] = "\uD83E\uDD99", + [":lobster:"] = "\uD83E\uDD9E", + [":lock:"] = "\uD83D\uDD12", + [":lock_with_ink_pen:"] = "\uD83D\uDD0F", + [":lollipop:"] = "\uD83C\uDF6D", + [":loop:"] = "➿", + [":loud_sound:"] = "\uD83D\uDD0A", + [":loudspeaker:"] = "\uD83D\uDCE2", + [":love_hotel:"] = "\uD83C\uDFE9", + [":love_letter:"] = "\uD83D\uDC8C", + [":love_you_gesture:"] = "\uD83E\uDD1F", + [":love_you_gesture::skin-tone-1:"] = "\uD83E\uDD1F\uD83C\uDFFB", + [":love_you_gesture::skin-tone-2:"] = "\uD83E\uDD1F\uD83C\uDFFC", + [":love_you_gesture::skin-tone-3:"] = "\uD83E\uDD1F\uD83C\uDFFD", + [":love_you_gesture::skin-tone-4:"] = "\uD83E\uDD1F\uD83C\uDFFE", + [":love_you_gesture::skin-tone-5:"] = "\uD83E\uDD1F\uD83C\uDFFF", + [":love_you_gesture_dark_skin_tone:"] = "\uD83E\uDD1F\uD83C\uDFFF", + [":love_you_gesture_light_skin_tone:"] = "\uD83E\uDD1F\uD83C\uDFFB", + [":love_you_gesture_medium_dark_skin_tone:"] = "\uD83E\uDD1F\uD83C\uDFFE", + [":love_you_gesture_medium_light_skin_tone:"] = "\uD83E\uDD1F\uD83C\uDFFC", + [":love_you_gesture_medium_skin_tone:"] = "\uD83E\uDD1F\uD83C\uDFFD", + [":love_you_gesture_tone1:"] = "\uD83E\uDD1F\uD83C\uDFFB", + [":love_you_gesture_tone2:"] = "\uD83E\uDD1F\uD83C\uDFFC", + [":love_you_gesture_tone3:"] = "\uD83E\uDD1F\uD83C\uDFFD", + [":love_you_gesture_tone4:"] = "\uD83E\uDD1F\uD83C\uDFFE", + [":love_you_gesture_tone5:"] = "\uD83E\uDD1F\uD83C\uDFFF", + [":low_brightness:"] = "\uD83D\uDD05", + [":lower_left_ballpoint_pen:"] = "\uD83D\uDD8A️", + [":lower_left_crayon:"] = "\uD83D\uDD8D️", + [":lower_left_fountain_pen:"] = "\uD83D\uDD8B️", + [":lower_left_paintbrush:"] = "\uD83D\uDD8C️", + [":luggage:"] = "\uD83E\uDDF3", + [":lying_face:"] = "\uD83E\uDD25", + [":m:"] = "Ⓜ️", + [":mag:"] = "\uD83D\uDD0D", + [":mag_right:"] = "\uD83D\uDD0E", + [":mage:"] = "\uD83E\uDDD9", + [":mage::skin-tone-1:"] = "\uD83E\uDDD9\uD83C\uDFFB", + [":mage::skin-tone-2:"] = "\uD83E\uDDD9\uD83C\uDFFC", + [":mage::skin-tone-3:"] = "\uD83E\uDDD9\uD83C\uDFFD", + [":mage::skin-tone-4:"] = "\uD83E\uDDD9\uD83C\uDFFE", + [":mage::skin-tone-5:"] = "\uD83E\uDDD9\uD83C\uDFFF", + [":mage_dark_skin_tone:"] = "\uD83E\uDDD9\uD83C\uDFFF", + [":mage_light_skin_tone:"] = "\uD83E\uDDD9\uD83C\uDFFB", + [":mage_medium_dark_skin_tone:"] = "\uD83E\uDDD9\uD83C\uDFFE", + [":mage_medium_light_skin_tone:"] = "\uD83E\uDDD9\uD83C\uDFFC", + [":mage_medium_skin_tone:"] = "\uD83E\uDDD9\uD83C\uDFFD", + [":mage_tone1:"] = "\uD83E\uDDD9\uD83C\uDFFB", + [":mage_tone2:"] = "\uD83E\uDDD9\uD83C\uDFFC", + [":mage_tone3:"] = "\uD83E\uDDD9\uD83C\uDFFD", + [":mage_tone4:"] = "\uD83E\uDDD9\uD83C\uDFFE", + [":mage_tone5:"] = "\uD83E\uDDD9\uD83C\uDFFF", + [":magnet:"] = "\uD83E\uDDF2", + [":mahjong:"] = "\uD83C\uDC04", + [":mailbox:"] = "\uD83D\uDCEB", + [":mailbox_closed:"] = "\uD83D\uDCEA", + [":mailbox_with_mail:"] = "\uD83D\uDCEC", + [":mailbox_with_no_mail:"] = "\uD83D\uDCED", + [":male_dancer:"] = "\uD83D\uDD7A", + [":male_dancer::skin-tone-1:"] = "\uD83D\uDD7A\uD83C\uDFFB", + [":male_dancer::skin-tone-2:"] = "\uD83D\uDD7A\uD83C\uDFFC", + [":male_dancer::skin-tone-3:"] = "\uD83D\uDD7A\uD83C\uDFFD", + [":male_dancer::skin-tone-4:"] = "\uD83D\uDD7A\uD83C\uDFFE", + [":male_dancer::skin-tone-5:"] = "\uD83D\uDD7A\uD83C\uDFFF", + [":male_dancer_tone1:"] = "\uD83D\uDD7A\uD83C\uDFFB", + [":male_dancer_tone2:"] = "\uD83D\uDD7A\uD83C\uDFFC", + [":male_dancer_tone3:"] = "\uD83D\uDD7A\uD83C\uDFFD", + [":male_dancer_tone4:"] = "\uD83D\uDD7A\uD83C\uDFFE", + [":male_dancer_tone5:"] = "\uD83D\uDD7A\uD83C\uDFFF", + [":male_sign:"] = "♂️", + [":man:"] = "\uD83D\uDC68", + [":man::skin-tone-1:"] = "\uD83D\uDC68\uD83C\uDFFB", + [":man::skin-tone-2:"] = "\uD83D\uDC68\uD83C\uDFFC", + [":man::skin-tone-3:"] = "\uD83D\uDC68\uD83C\uDFFD", + [":man::skin-tone-4:"] = "\uD83D\uDC68\uD83C\uDFFE", + [":man::skin-tone-5:"] = "\uD83D\uDC68\uD83C\uDFFF", + [":man_artist:"] = "\uD83D\uDC68\u200D\uD83C\uDFA8", + [":man_artist::skin-tone-1:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83C\uDFA8", + [":man_artist::skin-tone-2:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83C\uDFA8", + [":man_artist::skin-tone-3:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83C\uDFA8", + [":man_artist::skin-tone-4:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83C\uDFA8", + [":man_artist::skin-tone-5:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83C\uDFA8", + [":man_artist_dark_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83C\uDFA8", + [":man_artist_light_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83C\uDFA8", + [":man_artist_medium_dark_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83C\uDFA8", + [":man_artist_medium_light_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83C\uDFA8", + [":man_artist_medium_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83C\uDFA8", + [":man_artist_tone1:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83C\uDFA8", + [":man_artist_tone2:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83C\uDFA8", + [":man_artist_tone3:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83C\uDFA8", + [":man_artist_tone4:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83C\uDFA8", + [":man_artist_tone5:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83C\uDFA8", + [":man_astronaut:"] = "\uD83D\uDC68\u200D\uD83D\uDE80", + [":man_astronaut::skin-tone-1:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83D\uDE80", + [":man_astronaut::skin-tone-2:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83D\uDE80", + [":man_astronaut::skin-tone-3:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83D\uDE80", + [":man_astronaut::skin-tone-4:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83D\uDE80", + [":man_astronaut::skin-tone-5:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83D\uDE80", + [":man_astronaut_dark_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83D\uDE80", + [":man_astronaut_light_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83D\uDE80", + [":man_astronaut_medium_dark_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83D\uDE80", + [":man_astronaut_medium_light_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83D\uDE80", + [":man_astronaut_medium_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83D\uDE80", + [":man_astronaut_tone1:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83D\uDE80", + [":man_astronaut_tone2:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83D\uDE80", + [":man_astronaut_tone3:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83D\uDE80", + [":man_astronaut_tone4:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83D\uDE80", + [":man_astronaut_tone5:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83D\uDE80", + [":man_bald:"] = "\uD83D\uDC68\u200D\uD83E\uDDB2", + [":man_bald::skin-tone-1:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83E\uDDB2", + [":man_bald::skin-tone-2:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83E\uDDB2", + [":man_bald::skin-tone-3:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83E\uDDB2", + [":man_bald::skin-tone-4:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83E\uDDB2", + [":man_bald::skin-tone-5:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83E\uDDB2", + [":man_bald_dark_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83E\uDDB2", + [":man_bald_light_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83E\uDDB2", + [":man_bald_medium_dark_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83E\uDDB2", + [":man_bald_medium_light_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83E\uDDB2", + [":man_bald_medium_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83E\uDDB2", + [":man_bald_tone1:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83E\uDDB2", + [":man_bald_tone2:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83E\uDDB2", + [":man_bald_tone3:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83E\uDDB2", + [":man_bald_tone4:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83E\uDDB2", + [":man_bald_tone5:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83E\uDDB2", + [":man_biking:"] = "\uD83D\uDEB4\u200D♂️", + [":man_biking::skin-tone-1:"] = "\uD83D\uDEB4\uD83C\uDFFB\u200D♂️", + [":man_biking::skin-tone-2:"] = "\uD83D\uDEB4\uD83C\uDFFC\u200D♂️", + [":man_biking::skin-tone-3:"] = "\uD83D\uDEB4\uD83C\uDFFD\u200D♂️", + [":man_biking::skin-tone-4:"] = "\uD83D\uDEB4\uD83C\uDFFE\u200D♂️", + [":man_biking::skin-tone-5:"] = "\uD83D\uDEB4\uD83C\uDFFF\u200D♂️", + [":man_biking_dark_skin_tone:"] = "\uD83D\uDEB4\uD83C\uDFFF\u200D♂️", + [":man_biking_light_skin_tone:"] = "\uD83D\uDEB4\uD83C\uDFFB\u200D♂️", + [":man_biking_medium_dark_skin_tone:"] = "\uD83D\uDEB4\uD83C\uDFFE\u200D♂️", + [":man_biking_medium_light_skin_tone:"] = "\uD83D\uDEB4\uD83C\uDFFC\u200D♂️", + [":man_biking_medium_skin_tone:"] = "\uD83D\uDEB4\uD83C\uDFFD\u200D♂️", + [":man_biking_tone1:"] = "\uD83D\uDEB4\uD83C\uDFFB\u200D♂️", + [":man_biking_tone2:"] = "\uD83D\uDEB4\uD83C\uDFFC\u200D♂️", + [":man_biking_tone3:"] = "\uD83D\uDEB4\uD83C\uDFFD\u200D♂️", + [":man_biking_tone4:"] = "\uD83D\uDEB4\uD83C\uDFFE\u200D♂️", + [":man_biking_tone5:"] = "\uD83D\uDEB4\uD83C\uDFFF\u200D♂️", + [":man_bouncing_ball:"] = "⛹️\u200D♂️", + [":man_bouncing_ball::skin-tone-1:"] = "⛹\uD83C\uDFFB\u200D♂️", + [":man_bouncing_ball::skin-tone-2:"] = "⛹\uD83C\uDFFC\u200D♂️", + [":man_bouncing_ball::skin-tone-3:"] = "⛹\uD83C\uDFFD\u200D♂️", + [":man_bouncing_ball::skin-tone-4:"] = "⛹\uD83C\uDFFE\u200D♂️", + [":man_bouncing_ball::skin-tone-5:"] = "⛹\uD83C\uDFFF\u200D♂️", + [":man_bouncing_ball_dark_skin_tone:"] = "⛹\uD83C\uDFFF\u200D♂️", + [":man_bouncing_ball_light_skin_tone:"] = "⛹\uD83C\uDFFB\u200D♂️", + [":man_bouncing_ball_medium_dark_skin_tone:"] = "⛹\uD83C\uDFFE\u200D♂️", + [":man_bouncing_ball_medium_light_skin_tone:"] = "⛹\uD83C\uDFFC\u200D♂️", + [":man_bouncing_ball_medium_skin_tone:"] = "⛹\uD83C\uDFFD\u200D♂️", + [":man_bouncing_ball_tone1:"] = "⛹\uD83C\uDFFB\u200D♂️", + [":man_bouncing_ball_tone2:"] = "⛹\uD83C\uDFFC\u200D♂️", + [":man_bouncing_ball_tone3:"] = "⛹\uD83C\uDFFD\u200D♂️", + [":man_bouncing_ball_tone4:"] = "⛹\uD83C\uDFFE\u200D♂️", + [":man_bouncing_ball_tone5:"] = "⛹\uD83C\uDFFF\u200D♂️", + [":man_bowing:"] = "\uD83D\uDE47\u200D♂️", + [":man_bowing::skin-tone-1:"] = "\uD83D\uDE47\uD83C\uDFFB\u200D♂️", + [":man_bowing::skin-tone-2:"] = "\uD83D\uDE47\uD83C\uDFFC\u200D♂️", + [":man_bowing::skin-tone-3:"] = "\uD83D\uDE47\uD83C\uDFFD\u200D♂️", + [":man_bowing::skin-tone-4:"] = "\uD83D\uDE47\uD83C\uDFFE\u200D♂️", + [":man_bowing::skin-tone-5:"] = "\uD83D\uDE47\uD83C\uDFFF\u200D♂️", + [":man_bowing_dark_skin_tone:"] = "\uD83D\uDE47\uD83C\uDFFF\u200D♂️", + [":man_bowing_light_skin_tone:"] = "\uD83D\uDE47\uD83C\uDFFB\u200D♂️", + [":man_bowing_medium_dark_skin_tone:"] = "\uD83D\uDE47\uD83C\uDFFE\u200D♂️", + [":man_bowing_medium_light_skin_tone:"] = "\uD83D\uDE47\uD83C\uDFFC\u200D♂️", + [":man_bowing_medium_skin_tone:"] = "\uD83D\uDE47\uD83C\uDFFD\u200D♂️", + [":man_bowing_tone1:"] = "\uD83D\uDE47\uD83C\uDFFB\u200D♂️", + [":man_bowing_tone2:"] = "\uD83D\uDE47\uD83C\uDFFC\u200D♂️", + [":man_bowing_tone3:"] = "\uD83D\uDE47\uD83C\uDFFD\u200D♂️", + [":man_bowing_tone4:"] = "\uD83D\uDE47\uD83C\uDFFE\u200D♂️", + [":man_bowing_tone5:"] = "\uD83D\uDE47\uD83C\uDFFF\u200D♂️", + [":man_cartwheeling:"] = "\uD83E\uDD38\u200D♂️", + [":man_cartwheeling::skin-tone-1:"] = "\uD83E\uDD38\uD83C\uDFFB\u200D♂️", + [":man_cartwheeling::skin-tone-2:"] = "\uD83E\uDD38\uD83C\uDFFC\u200D♂️", + [":man_cartwheeling::skin-tone-3:"] = "\uD83E\uDD38\uD83C\uDFFD\u200D♂️", + [":man_cartwheeling::skin-tone-4:"] = "\uD83E\uDD38\uD83C\uDFFE\u200D♂️", + [":man_cartwheeling::skin-tone-5:"] = "\uD83E\uDD38\uD83C\uDFFF\u200D♂️", + [":man_cartwheeling_dark_skin_tone:"] = "\uD83E\uDD38\uD83C\uDFFF\u200D♂️", + [":man_cartwheeling_light_skin_tone:"] = "\uD83E\uDD38\uD83C\uDFFB\u200D♂️", + [":man_cartwheeling_medium_dark_skin_tone:"] = "\uD83E\uDD38\uD83C\uDFFE\u200D♂️", + [":man_cartwheeling_medium_light_skin_tone:"] = "\uD83E\uDD38\uD83C\uDFFC\u200D♂️", + [":man_cartwheeling_medium_skin_tone:"] = "\uD83E\uDD38\uD83C\uDFFD\u200D♂️", + [":man_cartwheeling_tone1:"] = "\uD83E\uDD38\uD83C\uDFFB\u200D♂️", + [":man_cartwheeling_tone2:"] = "\uD83E\uDD38\uD83C\uDFFC\u200D♂️", + [":man_cartwheeling_tone3:"] = "\uD83E\uDD38\uD83C\uDFFD\u200D♂️", + [":man_cartwheeling_tone4:"] = "\uD83E\uDD38\uD83C\uDFFE\u200D♂️", + [":man_cartwheeling_tone5:"] = "\uD83E\uDD38\uD83C\uDFFF\u200D♂️", + [":man_climbing:"] = "\uD83E\uDDD7\u200D♂️", + [":man_climbing::skin-tone-1:"] = "\uD83E\uDDD7\uD83C\uDFFB\u200D♂️", + [":man_climbing::skin-tone-2:"] = "\uD83E\uDDD7\uD83C\uDFFC\u200D♂️", + [":man_climbing::skin-tone-3:"] = "\uD83E\uDDD7\uD83C\uDFFD\u200D♂️", + [":man_climbing::skin-tone-4:"] = "\uD83E\uDDD7\uD83C\uDFFE\u200D♂️", + [":man_climbing::skin-tone-5:"] = "\uD83E\uDDD7\uD83C\uDFFF\u200D♂️", + [":man_climbing_dark_skin_tone:"] = "\uD83E\uDDD7\uD83C\uDFFF\u200D♂️", + [":man_climbing_light_skin_tone:"] = "\uD83E\uDDD7\uD83C\uDFFB\u200D♂️", + [":man_climbing_medium_dark_skin_tone:"] = "\uD83E\uDDD7\uD83C\uDFFE\u200D♂️", + [":man_climbing_medium_light_skin_tone:"] = "\uD83E\uDDD7\uD83C\uDFFC\u200D♂️", + [":man_climbing_medium_skin_tone:"] = "\uD83E\uDDD7\uD83C\uDFFD\u200D♂️", + [":man_climbing_tone1:"] = "\uD83E\uDDD7\uD83C\uDFFB\u200D♂️", + [":man_climbing_tone2:"] = "\uD83E\uDDD7\uD83C\uDFFC\u200D♂️", + [":man_climbing_tone3:"] = "\uD83E\uDDD7\uD83C\uDFFD\u200D♂️", + [":man_climbing_tone4:"] = "\uD83E\uDDD7\uD83C\uDFFE\u200D♂️", + [":man_climbing_tone5:"] = "\uD83E\uDDD7\uD83C\uDFFF\u200D♂️", + [":man_construction_worker:"] = "\uD83D\uDC77\u200D♂️", + [":man_construction_worker::skin-tone-1:"] = "\uD83D\uDC77\uD83C\uDFFB\u200D♂️", + [":man_construction_worker::skin-tone-2:"] = "\uD83D\uDC77\uD83C\uDFFC\u200D♂️", + [":man_construction_worker::skin-tone-3:"] = "\uD83D\uDC77\uD83C\uDFFD\u200D♂️", + [":man_construction_worker::skin-tone-4:"] = "\uD83D\uDC77\uD83C\uDFFE\u200D♂️", + [":man_construction_worker::skin-tone-5:"] = "\uD83D\uDC77\uD83C\uDFFF\u200D♂️", + [":man_construction_worker_dark_skin_tone:"] = "\uD83D\uDC77\uD83C\uDFFF\u200D♂️", + [":man_construction_worker_light_skin_tone:"] = "\uD83D\uDC77\uD83C\uDFFB\u200D♂️", + [":man_construction_worker_medium_dark_skin_tone:"] = "\uD83D\uDC77\uD83C\uDFFE\u200D♂️", + [":man_construction_worker_medium_light_skin_tone:"] = "\uD83D\uDC77\uD83C\uDFFC\u200D♂️", + [":man_construction_worker_medium_skin_tone:"] = "\uD83D\uDC77\uD83C\uDFFD\u200D♂️", + [":man_construction_worker_tone1:"] = "\uD83D\uDC77\uD83C\uDFFB\u200D♂️", + [":man_construction_worker_tone2:"] = "\uD83D\uDC77\uD83C\uDFFC\u200D♂️", + [":man_construction_worker_tone3:"] = "\uD83D\uDC77\uD83C\uDFFD\u200D♂️", + [":man_construction_worker_tone4:"] = "\uD83D\uDC77\uD83C\uDFFE\u200D♂️", + [":man_construction_worker_tone5:"] = "\uD83D\uDC77\uD83C\uDFFF\u200D♂️", + [":man_cook:"] = "\uD83D\uDC68\u200D\uD83C\uDF73", + [":man_cook::skin-tone-1:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83C\uDF73", + [":man_cook::skin-tone-2:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83C\uDF73", + [":man_cook::skin-tone-3:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83C\uDF73", + [":man_cook::skin-tone-4:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83C\uDF73", + [":man_cook::skin-tone-5:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83C\uDF73", + [":man_cook_dark_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83C\uDF73", + [":man_cook_light_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83C\uDF73", + [":man_cook_medium_dark_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83C\uDF73", + [":man_cook_medium_light_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83C\uDF73", + [":man_cook_medium_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83C\uDF73", + [":man_cook_tone1:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83C\uDF73", + [":man_cook_tone2:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83C\uDF73", + [":man_cook_tone3:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83C\uDF73", + [":man_cook_tone4:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83C\uDF73", + [":man_cook_tone5:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83C\uDF73", + [":man_curly_haired:"] = "\uD83D\uDC68\u200D\uD83E\uDDB1", + [":man_curly_haired::skin-tone-1:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83E\uDDB1", + [":man_curly_haired::skin-tone-2:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83E\uDDB1", + [":man_curly_haired::skin-tone-3:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83E\uDDB1", + [":man_curly_haired::skin-tone-4:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83E\uDDB1", + [":man_curly_haired::skin-tone-5:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83E\uDDB1", + [":man_curly_haired_dark_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83E\uDDB1", + [":man_curly_haired_light_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83E\uDDB1", + [":man_curly_haired_medium_dark_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83E\uDDB1", + [":man_curly_haired_medium_light_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83E\uDDB1", + [":man_curly_haired_medium_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83E\uDDB1", + [":man_curly_haired_tone1:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83E\uDDB1", + [":man_curly_haired_tone2:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83E\uDDB1", + [":man_curly_haired_tone3:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83E\uDDB1", + [":man_curly_haired_tone4:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83E\uDDB1", + [":man_curly_haired_tone5:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83E\uDDB1", + [":man_dancing:"] = "\uD83D\uDD7A", + [":man_dancing::skin-tone-1:"] = "\uD83D\uDD7A\uD83C\uDFFB", + [":man_dancing::skin-tone-2:"] = "\uD83D\uDD7A\uD83C\uDFFC", + [":man_dancing::skin-tone-3:"] = "\uD83D\uDD7A\uD83C\uDFFD", + [":man_dancing::skin-tone-4:"] = "\uD83D\uDD7A\uD83C\uDFFE", + [":man_dancing::skin-tone-5:"] = "\uD83D\uDD7A\uD83C\uDFFF", + [":man_dancing_tone1:"] = "\uD83D\uDD7A\uD83C\uDFFB", + [":man_dancing_tone2:"] = "\uD83D\uDD7A\uD83C\uDFFC", + [":man_dancing_tone3:"] = "\uD83D\uDD7A\uD83C\uDFFD", + [":man_dancing_tone4:"] = "\uD83D\uDD7A\uD83C\uDFFE", + [":man_dancing_tone5:"] = "\uD83D\uDD7A\uD83C\uDFFF", + [":man_detective:"] = "\uD83D\uDD75️\u200D♂️", + [":man_detective::skin-tone-1:"] = "\uD83D\uDD75\uD83C\uDFFB\u200D♂️", + [":man_detective::skin-tone-2:"] = "\uD83D\uDD75\uD83C\uDFFC\u200D♂️", + [":man_detective::skin-tone-3:"] = "\uD83D\uDD75\uD83C\uDFFD\u200D♂️", + [":man_detective::skin-tone-4:"] = "\uD83D\uDD75\uD83C\uDFFE\u200D♂️", + [":man_detective::skin-tone-5:"] = "\uD83D\uDD75\uD83C\uDFFF\u200D♂️", + [":man_detective_dark_skin_tone:"] = "\uD83D\uDD75\uD83C\uDFFF\u200D♂️", + [":man_detective_light_skin_tone:"] = "\uD83D\uDD75\uD83C\uDFFB\u200D♂️", + [":man_detective_medium_dark_skin_tone:"] = "\uD83D\uDD75\uD83C\uDFFE\u200D♂️", + [":man_detective_medium_light_skin_tone:"] = "\uD83D\uDD75\uD83C\uDFFC\u200D♂️", + [":man_detective_medium_skin_tone:"] = "\uD83D\uDD75\uD83C\uDFFD\u200D♂️", + [":man_detective_tone1:"] = "\uD83D\uDD75\uD83C\uDFFB\u200D♂️", + [":man_detective_tone2:"] = "\uD83D\uDD75\uD83C\uDFFC\u200D♂️", + [":man_detective_tone3:"] = "\uD83D\uDD75\uD83C\uDFFD\u200D♂️", + [":man_detective_tone4:"] = "\uD83D\uDD75\uD83C\uDFFE\u200D♂️", + [":man_detective_tone5:"] = "\uD83D\uDD75\uD83C\uDFFF\u200D♂️", + [":man_elf:"] = "\uD83E\uDDDD\u200D♂️", + [":man_elf::skin-tone-1:"] = "\uD83E\uDDDD\uD83C\uDFFB\u200D♂️", + [":man_elf::skin-tone-2:"] = "\uD83E\uDDDD\uD83C\uDFFC\u200D♂️", + [":man_elf::skin-tone-3:"] = "\uD83E\uDDDD\uD83C\uDFFD\u200D♂️", + [":man_elf::skin-tone-4:"] = "\uD83E\uDDDD\uD83C\uDFFE\u200D♂️", + [":man_elf::skin-tone-5:"] = "\uD83E\uDDDD\uD83C\uDFFF\u200D♂️", + [":man_elf_dark_skin_tone:"] = "\uD83E\uDDDD\uD83C\uDFFF\u200D♂️", + [":man_elf_light_skin_tone:"] = "\uD83E\uDDDD\uD83C\uDFFB\u200D♂️", + [":man_elf_medium_dark_skin_tone:"] = "\uD83E\uDDDD\uD83C\uDFFE\u200D♂️", + [":man_elf_medium_light_skin_tone:"] = "\uD83E\uDDDD\uD83C\uDFFC\u200D♂️", + [":man_elf_medium_skin_tone:"] = "\uD83E\uDDDD\uD83C\uDFFD\u200D♂️", + [":man_elf_tone1:"] = "\uD83E\uDDDD\uD83C\uDFFB\u200D♂️", + [":man_elf_tone2:"] = "\uD83E\uDDDD\uD83C\uDFFC\u200D♂️", + [":man_elf_tone3:"] = "\uD83E\uDDDD\uD83C\uDFFD\u200D♂️", + [":man_elf_tone4:"] = "\uD83E\uDDDD\uD83C\uDFFE\u200D♂️", + [":man_elf_tone5:"] = "\uD83E\uDDDD\uD83C\uDFFF\u200D♂️", + [":man_facepalming:"] = "\uD83E\uDD26\u200D♂️", + [":man_facepalming::skin-tone-1:"] = "\uD83E\uDD26\uD83C\uDFFB\u200D♂️", + [":man_facepalming::skin-tone-2:"] = "\uD83E\uDD26\uD83C\uDFFC\u200D♂️", + [":man_facepalming::skin-tone-3:"] = "\uD83E\uDD26\uD83C\uDFFD\u200D♂️", + [":man_facepalming::skin-tone-4:"] = "\uD83E\uDD26\uD83C\uDFFE\u200D♂️", + [":man_facepalming::skin-tone-5:"] = "\uD83E\uDD26\uD83C\uDFFF\u200D♂️", + [":man_facepalming_dark_skin_tone:"] = "\uD83E\uDD26\uD83C\uDFFF\u200D♂️", + [":man_facepalming_light_skin_tone:"] = "\uD83E\uDD26\uD83C\uDFFB\u200D♂️", + [":man_facepalming_medium_dark_skin_tone:"] = "\uD83E\uDD26\uD83C\uDFFE\u200D♂️", + [":man_facepalming_medium_light_skin_tone:"] = "\uD83E\uDD26\uD83C\uDFFC\u200D♂️", + [":man_facepalming_medium_skin_tone:"] = "\uD83E\uDD26\uD83C\uDFFD\u200D♂️", + [":man_facepalming_tone1:"] = "\uD83E\uDD26\uD83C\uDFFB\u200D♂️", + [":man_facepalming_tone2:"] = "\uD83E\uDD26\uD83C\uDFFC\u200D♂️", + [":man_facepalming_tone3:"] = "\uD83E\uDD26\uD83C\uDFFD\u200D♂️", + [":man_facepalming_tone4:"] = "\uD83E\uDD26\uD83C\uDFFE\u200D♂️", + [":man_facepalming_tone5:"] = "\uD83E\uDD26\uD83C\uDFFF\u200D♂️", + [":man_factory_worker:"] = "\uD83D\uDC68\u200D\uD83C\uDFED", + [":man_factory_worker::skin-tone-1:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83C\uDFED", + [":man_factory_worker::skin-tone-2:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83C\uDFED", + [":man_factory_worker::skin-tone-3:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83C\uDFED", + [":man_factory_worker::skin-tone-4:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83C\uDFED", + [":man_factory_worker::skin-tone-5:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83C\uDFED", + [":man_factory_worker_dark_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83C\uDFED", + [":man_factory_worker_light_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83C\uDFED", + [":man_factory_worker_medium_dark_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83C\uDFED", + [":man_factory_worker_medium_light_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83C\uDFED", + [":man_factory_worker_medium_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83C\uDFED", + [":man_factory_worker_tone1:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83C\uDFED", + [":man_factory_worker_tone2:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83C\uDFED", + [":man_factory_worker_tone3:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83C\uDFED", + [":man_factory_worker_tone4:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83C\uDFED", + [":man_factory_worker_tone5:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83C\uDFED", + [":man_fairy:"] = "\uD83E\uDDDA\u200D♂️", + [":man_fairy::skin-tone-1:"] = "\uD83E\uDDDA\uD83C\uDFFB\u200D♂️", + [":man_fairy::skin-tone-2:"] = "\uD83E\uDDDA\uD83C\uDFFC\u200D♂️", + [":man_fairy::skin-tone-3:"] = "\uD83E\uDDDA\uD83C\uDFFD\u200D♂️", + [":man_fairy::skin-tone-4:"] = "\uD83E\uDDDA\uD83C\uDFFE\u200D♂️", + [":man_fairy::skin-tone-5:"] = "\uD83E\uDDDA\uD83C\uDFFF\u200D♂️", + [":man_fairy_dark_skin_tone:"] = "\uD83E\uDDDA\uD83C\uDFFF\u200D♂️", + [":man_fairy_light_skin_tone:"] = "\uD83E\uDDDA\uD83C\uDFFB\u200D♂️", + [":man_fairy_medium_dark_skin_tone:"] = "\uD83E\uDDDA\uD83C\uDFFE\u200D♂️", + [":man_fairy_medium_light_skin_tone:"] = "\uD83E\uDDDA\uD83C\uDFFC\u200D♂️", + [":man_fairy_medium_skin_tone:"] = "\uD83E\uDDDA\uD83C\uDFFD\u200D♂️", + [":man_fairy_tone1:"] = "\uD83E\uDDDA\uD83C\uDFFB\u200D♂️", + [":man_fairy_tone2:"] = "\uD83E\uDDDA\uD83C\uDFFC\u200D♂️", + [":man_fairy_tone3:"] = "\uD83E\uDDDA\uD83C\uDFFD\u200D♂️", + [":man_fairy_tone4:"] = "\uD83E\uDDDA\uD83C\uDFFE\u200D♂️", + [":man_fairy_tone5:"] = "\uD83E\uDDDA\uD83C\uDFFF\u200D♂️", + [":man_farmer:"] = "\uD83D\uDC68\u200D\uD83C\uDF3E", + [":man_farmer::skin-tone-1:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83C\uDF3E", + [":man_farmer::skin-tone-2:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83C\uDF3E", + [":man_farmer::skin-tone-3:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83C\uDF3E", + [":man_farmer::skin-tone-4:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83C\uDF3E", + [":man_farmer::skin-tone-5:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83C\uDF3E", + [":man_farmer_dark_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83C\uDF3E", + [":man_farmer_light_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83C\uDF3E", + [":man_farmer_medium_dark_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83C\uDF3E", + [":man_farmer_medium_light_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83C\uDF3E", + [":man_farmer_medium_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83C\uDF3E", + [":man_farmer_tone1:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83C\uDF3E", + [":man_farmer_tone2:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83C\uDF3E", + [":man_farmer_tone3:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83C\uDF3E", + [":man_farmer_tone4:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83C\uDF3E", + [":man_farmer_tone5:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83C\uDF3E", + [":man_firefighter:"] = "\uD83D\uDC68\u200D\uD83D\uDE92", + [":man_firefighter::skin-tone-1:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83D\uDE92", + [":man_firefighter::skin-tone-2:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83D\uDE92", + [":man_firefighter::skin-tone-3:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83D\uDE92", + [":man_firefighter::skin-tone-4:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83D\uDE92", + [":man_firefighter::skin-tone-5:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83D\uDE92", + [":man_firefighter_dark_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83D\uDE92", + [":man_firefighter_light_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83D\uDE92", + [":man_firefighter_medium_dark_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83D\uDE92", + [":man_firefighter_medium_light_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83D\uDE92", + [":man_firefighter_medium_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83D\uDE92", + [":man_firefighter_tone1:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83D\uDE92", + [":man_firefighter_tone2:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83D\uDE92", + [":man_firefighter_tone3:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83D\uDE92", + [":man_firefighter_tone4:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83D\uDE92", + [":man_firefighter_tone5:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83D\uDE92", + [":man_frowning:"] = "\uD83D\uDE4D\u200D♂️", + [":man_frowning::skin-tone-1:"] = "\uD83D\uDE4D\uD83C\uDFFB\u200D♂️", + [":man_frowning::skin-tone-2:"] = "\uD83D\uDE4D\uD83C\uDFFC\u200D♂️", + [":man_frowning::skin-tone-3:"] = "\uD83D\uDE4D\uD83C\uDFFD\u200D♂️", + [":man_frowning::skin-tone-4:"] = "\uD83D\uDE4D\uD83C\uDFFE\u200D♂️", + [":man_frowning::skin-tone-5:"] = "\uD83D\uDE4D\uD83C\uDFFF\u200D♂️", + [":man_frowning_dark_skin_tone:"] = "\uD83D\uDE4D\uD83C\uDFFF\u200D♂️", + [":man_frowning_light_skin_tone:"] = "\uD83D\uDE4D\uD83C\uDFFB\u200D♂️", + [":man_frowning_medium_dark_skin_tone:"] = "\uD83D\uDE4D\uD83C\uDFFE\u200D♂️", + [":man_frowning_medium_light_skin_tone:"] = "\uD83D\uDE4D\uD83C\uDFFC\u200D♂️", + [":man_frowning_medium_skin_tone:"] = "\uD83D\uDE4D\uD83C\uDFFD\u200D♂️", + [":man_frowning_tone1:"] = "\uD83D\uDE4D\uD83C\uDFFB\u200D♂️", + [":man_frowning_tone2:"] = "\uD83D\uDE4D\uD83C\uDFFC\u200D♂️", + [":man_frowning_tone3:"] = "\uD83D\uDE4D\uD83C\uDFFD\u200D♂️", + [":man_frowning_tone4:"] = "\uD83D\uDE4D\uD83C\uDFFE\u200D♂️", + [":man_frowning_tone5:"] = "\uD83D\uDE4D\uD83C\uDFFF\u200D♂️", + [":man_genie:"] = "\uD83E\uDDDE\u200D♂️", + [":man_gesturing_no:"] = "\uD83D\uDE45\u200D♂️", + [":man_gesturing_no::skin-tone-1:"] = "\uD83D\uDE45\uD83C\uDFFB\u200D♂️", + [":man_gesturing_no::skin-tone-2:"] = "\uD83D\uDE45\uD83C\uDFFC\u200D♂️", + [":man_gesturing_no::skin-tone-3:"] = "\uD83D\uDE45\uD83C\uDFFD\u200D♂️", + [":man_gesturing_no::skin-tone-4:"] = "\uD83D\uDE45\uD83C\uDFFE\u200D♂️", + [":man_gesturing_no::skin-tone-5:"] = "\uD83D\uDE45\uD83C\uDFFF\u200D♂️", + [":man_gesturing_no_dark_skin_tone:"] = "\uD83D\uDE45\uD83C\uDFFF\u200D♂️", + [":man_gesturing_no_light_skin_tone:"] = "\uD83D\uDE45\uD83C\uDFFB\u200D♂️", + [":man_gesturing_no_medium_dark_skin_tone:"] = "\uD83D\uDE45\uD83C\uDFFE\u200D♂️", + [":man_gesturing_no_medium_light_skin_tone:"] = "\uD83D\uDE45\uD83C\uDFFC\u200D♂️", + [":man_gesturing_no_medium_skin_tone:"] = "\uD83D\uDE45\uD83C\uDFFD\u200D♂️", + [":man_gesturing_no_tone1:"] = "\uD83D\uDE45\uD83C\uDFFB\u200D♂️", + [":man_gesturing_no_tone2:"] = "\uD83D\uDE45\uD83C\uDFFC\u200D♂️", + [":man_gesturing_no_tone3:"] = "\uD83D\uDE45\uD83C\uDFFD\u200D♂️", + [":man_gesturing_no_tone4:"] = "\uD83D\uDE45\uD83C\uDFFE\u200D♂️", + [":man_gesturing_no_tone5:"] = "\uD83D\uDE45\uD83C\uDFFF\u200D♂️", + [":man_gesturing_ok:"] = "\uD83D\uDE46\u200D♂️", + [":man_gesturing_ok::skin-tone-1:"] = "\uD83D\uDE46\uD83C\uDFFB\u200D♂️", + [":man_gesturing_ok::skin-tone-2:"] = "\uD83D\uDE46\uD83C\uDFFC\u200D♂️", + [":man_gesturing_ok::skin-tone-3:"] = "\uD83D\uDE46\uD83C\uDFFD\u200D♂️", + [":man_gesturing_ok::skin-tone-4:"] = "\uD83D\uDE46\uD83C\uDFFE\u200D♂️", + [":man_gesturing_ok::skin-tone-5:"] = "\uD83D\uDE46\uD83C\uDFFF\u200D♂️", + [":man_gesturing_ok_dark_skin_tone:"] = "\uD83D\uDE46\uD83C\uDFFF\u200D♂️", + [":man_gesturing_ok_light_skin_tone:"] = "\uD83D\uDE46\uD83C\uDFFB\u200D♂️", + [":man_gesturing_ok_medium_dark_skin_tone:"] = "\uD83D\uDE46\uD83C\uDFFE\u200D♂️", + [":man_gesturing_ok_medium_light_skin_tone:"] = "\uD83D\uDE46\uD83C\uDFFC\u200D♂️", + [":man_gesturing_ok_medium_skin_tone:"] = "\uD83D\uDE46\uD83C\uDFFD\u200D♂️", + [":man_gesturing_ok_tone1:"] = "\uD83D\uDE46\uD83C\uDFFB\u200D♂️", + [":man_gesturing_ok_tone2:"] = "\uD83D\uDE46\uD83C\uDFFC\u200D♂️", + [":man_gesturing_ok_tone3:"] = "\uD83D\uDE46\uD83C\uDFFD\u200D♂️", + [":man_gesturing_ok_tone4:"] = "\uD83D\uDE46\uD83C\uDFFE\u200D♂️", + [":man_gesturing_ok_tone5:"] = "\uD83D\uDE46\uD83C\uDFFF\u200D♂️", + [":man_getting_face_massage:"] = "\uD83D\uDC86\u200D♂️", + [":man_getting_face_massage::skin-tone-1:"] = "\uD83D\uDC86\uD83C\uDFFB\u200D♂️", + [":man_getting_face_massage::skin-tone-2:"] = "\uD83D\uDC86\uD83C\uDFFC\u200D♂️", + [":man_getting_face_massage::skin-tone-3:"] = "\uD83D\uDC86\uD83C\uDFFD\u200D♂️", + [":man_getting_face_massage::skin-tone-4:"] = "\uD83D\uDC86\uD83C\uDFFE\u200D♂️", + [":man_getting_face_massage::skin-tone-5:"] = "\uD83D\uDC86\uD83C\uDFFF\u200D♂️", + [":man_getting_face_massage_dark_skin_tone:"] = "\uD83D\uDC86\uD83C\uDFFF\u200D♂️", + [":man_getting_face_massage_light_skin_tone:"] = "\uD83D\uDC86\uD83C\uDFFB\u200D♂️", + [":man_getting_face_massage_medium_dark_skin_tone:"] = "\uD83D\uDC86\uD83C\uDFFE\u200D♂️", + [":man_getting_face_massage_medium_light_skin_tone:"] = "\uD83D\uDC86\uD83C\uDFFC\u200D♂️", + [":man_getting_face_massage_medium_skin_tone:"] = "\uD83D\uDC86\uD83C\uDFFD\u200D♂️", + [":man_getting_face_massage_tone1:"] = "\uD83D\uDC86\uD83C\uDFFB\u200D♂️", + [":man_getting_face_massage_tone2:"] = "\uD83D\uDC86\uD83C\uDFFC\u200D♂️", + [":man_getting_face_massage_tone3:"] = "\uD83D\uDC86\uD83C\uDFFD\u200D♂️", + [":man_getting_face_massage_tone4:"] = "\uD83D\uDC86\uD83C\uDFFE\u200D♂️", + [":man_getting_face_massage_tone5:"] = "\uD83D\uDC86\uD83C\uDFFF\u200D♂️", + [":man_getting_haircut:"] = "\uD83D\uDC87\u200D♂️", + [":man_getting_haircut::skin-tone-1:"] = "\uD83D\uDC87\uD83C\uDFFB\u200D♂️", + [":man_getting_haircut::skin-tone-2:"] = "\uD83D\uDC87\uD83C\uDFFC\u200D♂️", + [":man_getting_haircut::skin-tone-3:"] = "\uD83D\uDC87\uD83C\uDFFD\u200D♂️", + [":man_getting_haircut::skin-tone-4:"] = "\uD83D\uDC87\uD83C\uDFFE\u200D♂️", + [":man_getting_haircut::skin-tone-5:"] = "\uD83D\uDC87\uD83C\uDFFF\u200D♂️", + [":man_getting_haircut_dark_skin_tone:"] = "\uD83D\uDC87\uD83C\uDFFF\u200D♂️", + [":man_getting_haircut_light_skin_tone:"] = "\uD83D\uDC87\uD83C\uDFFB\u200D♂️", + [":man_getting_haircut_medium_dark_skin_tone:"] = "\uD83D\uDC87\uD83C\uDFFE\u200D♂️", + [":man_getting_haircut_medium_light_skin_tone:"] = "\uD83D\uDC87\uD83C\uDFFC\u200D♂️", + [":man_getting_haircut_medium_skin_tone:"] = "\uD83D\uDC87\uD83C\uDFFD\u200D♂️", + [":man_getting_haircut_tone1:"] = "\uD83D\uDC87\uD83C\uDFFB\u200D♂️", + [":man_getting_haircut_tone2:"] = "\uD83D\uDC87\uD83C\uDFFC\u200D♂️", + [":man_getting_haircut_tone3:"] = "\uD83D\uDC87\uD83C\uDFFD\u200D♂️", + [":man_getting_haircut_tone4:"] = "\uD83D\uDC87\uD83C\uDFFE\u200D♂️", + [":man_getting_haircut_tone5:"] = "\uD83D\uDC87\uD83C\uDFFF\u200D♂️", + [":man_golfing:"] = "\uD83C\uDFCC️\u200D♂️", + [":man_golfing::skin-tone-1:"] = "\uD83C\uDFCC\uD83C\uDFFB\u200D♂️", + [":man_golfing::skin-tone-2:"] = "\uD83C\uDFCC\uD83C\uDFFC\u200D♂️", + [":man_golfing::skin-tone-3:"] = "\uD83C\uDFCC\uD83C\uDFFD\u200D♂️", + [":man_golfing::skin-tone-4:"] = "\uD83C\uDFCC\uD83C\uDFFE\u200D♂️", + [":man_golfing::skin-tone-5:"] = "\uD83C\uDFCC\uD83C\uDFFF\u200D♂️", + [":man_golfing_dark_skin_tone:"] = "\uD83C\uDFCC\uD83C\uDFFF\u200D♂️", + [":man_golfing_light_skin_tone:"] = "\uD83C\uDFCC\uD83C\uDFFB\u200D♂️", + [":man_golfing_medium_dark_skin_tone:"] = "\uD83C\uDFCC\uD83C\uDFFE\u200D♂️", + [":man_golfing_medium_light_skin_tone:"] = "\uD83C\uDFCC\uD83C\uDFFC\u200D♂️", + [":man_golfing_medium_skin_tone:"] = "\uD83C\uDFCC\uD83C\uDFFD\u200D♂️", + [":man_golfing_tone1:"] = "\uD83C\uDFCC\uD83C\uDFFB\u200D♂️", + [":man_golfing_tone2:"] = "\uD83C\uDFCC\uD83C\uDFFC\u200D♂️", + [":man_golfing_tone3:"] = "\uD83C\uDFCC\uD83C\uDFFD\u200D♂️", + [":man_golfing_tone4:"] = "\uD83C\uDFCC\uD83C\uDFFE\u200D♂️", + [":man_golfing_tone5:"] = "\uD83C\uDFCC\uD83C\uDFFF\u200D♂️", + [":man_guard:"] = "\uD83D\uDC82\u200D♂️", + [":man_guard::skin-tone-1:"] = "\uD83D\uDC82\uD83C\uDFFB\u200D♂️", + [":man_guard::skin-tone-2:"] = "\uD83D\uDC82\uD83C\uDFFC\u200D♂️", + [":man_guard::skin-tone-3:"] = "\uD83D\uDC82\uD83C\uDFFD\u200D♂️", + [":man_guard::skin-tone-4:"] = "\uD83D\uDC82\uD83C\uDFFE\u200D♂️", + [":man_guard::skin-tone-5:"] = "\uD83D\uDC82\uD83C\uDFFF\u200D♂️", + [":man_guard_dark_skin_tone:"] = "\uD83D\uDC82\uD83C\uDFFF\u200D♂️", + [":man_guard_light_skin_tone:"] = "\uD83D\uDC82\uD83C\uDFFB\u200D♂️", + [":man_guard_medium_dark_skin_tone:"] = "\uD83D\uDC82\uD83C\uDFFE\u200D♂️", + [":man_guard_medium_light_skin_tone:"] = "\uD83D\uDC82\uD83C\uDFFC\u200D♂️", + [":man_guard_medium_skin_tone:"] = "\uD83D\uDC82\uD83C\uDFFD\u200D♂️", + [":man_guard_tone1:"] = "\uD83D\uDC82\uD83C\uDFFB\u200D♂️", + [":man_guard_tone2:"] = "\uD83D\uDC82\uD83C\uDFFC\u200D♂️", + [":man_guard_tone3:"] = "\uD83D\uDC82\uD83C\uDFFD\u200D♂️", + [":man_guard_tone4:"] = "\uD83D\uDC82\uD83C\uDFFE\u200D♂️", + [":man_guard_tone5:"] = "\uD83D\uDC82\uD83C\uDFFF\u200D♂️", + [":man_health_worker:"] = "\uD83D\uDC68\u200D⚕️", + [":man_health_worker::skin-tone-1:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D⚕️", + [":man_health_worker::skin-tone-2:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D⚕️", + [":man_health_worker::skin-tone-3:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D⚕️", + [":man_health_worker::skin-tone-4:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D⚕️", + [":man_health_worker::skin-tone-5:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D⚕️", + [":man_health_worker_dark_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D⚕️", + [":man_health_worker_light_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D⚕️", + [":man_health_worker_medium_dark_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D⚕️", + [":man_health_worker_medium_light_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D⚕️", + [":man_health_worker_medium_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D⚕️", + [":man_health_worker_tone1:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D⚕️", + [":man_health_worker_tone2:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D⚕️", + [":man_health_worker_tone3:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D⚕️", + [":man_health_worker_tone4:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D⚕️", + [":man_health_worker_tone5:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D⚕️", + [":man_in_business_suit_levitating:"] = "\uD83D\uDD74️", + [":man_in_business_suit_levitating::skin-tone-1:"] = "\uD83D\uDD74\uD83C\uDFFB", + [":man_in_business_suit_levitating::skin-tone-2:"] = "\uD83D\uDD74\uD83C\uDFFC", + [":man_in_business_suit_levitating::skin-tone-3:"] = "\uD83D\uDD74\uD83C\uDFFD", + [":man_in_business_suit_levitating::skin-tone-4:"] = "\uD83D\uDD74\uD83C\uDFFE", + [":man_in_business_suit_levitating::skin-tone-5:"] = "\uD83D\uDD74\uD83C\uDFFF", + [":man_in_business_suit_levitating_dark_skin_tone:"] = "\uD83D\uDD74\uD83C\uDFFF", + [":man_in_business_suit_levitating_light_skin_tone:"] = "\uD83D\uDD74\uD83C\uDFFB", + [":man_in_business_suit_levitating_medium_dark_skin_tone:"] = "\uD83D\uDD74\uD83C\uDFFE", + [":man_in_business_suit_levitating_medium_light_skin_tone:"] = "\uD83D\uDD74\uD83C\uDFFC", + [":man_in_business_suit_levitating_medium_skin_tone:"] = "\uD83D\uDD74\uD83C\uDFFD", + [":man_in_business_suit_levitating_tone1:"] = "\uD83D\uDD74\uD83C\uDFFB", + [":man_in_business_suit_levitating_tone2:"] = "\uD83D\uDD74\uD83C\uDFFC", + [":man_in_business_suit_levitating_tone3:"] = "\uD83D\uDD74\uD83C\uDFFD", + [":man_in_business_suit_levitating_tone4:"] = "\uD83D\uDD74\uD83C\uDFFE", + [":man_in_business_suit_levitating_tone5:"] = "\uD83D\uDD74\uD83C\uDFFF", + [":man_in_lotus_position:"] = "\uD83E\uDDD8\u200D♂️", + [":man_in_lotus_position::skin-tone-1:"] = "\uD83E\uDDD8\uD83C\uDFFB\u200D♂️", + [":man_in_lotus_position::skin-tone-2:"] = "\uD83E\uDDD8\uD83C\uDFFC\u200D♂️", + [":man_in_lotus_position::skin-tone-3:"] = "\uD83E\uDDD8\uD83C\uDFFD\u200D♂️", + [":man_in_lotus_position::skin-tone-4:"] = "\uD83E\uDDD8\uD83C\uDFFE\u200D♂️", + [":man_in_lotus_position::skin-tone-5:"] = "\uD83E\uDDD8\uD83C\uDFFF\u200D♂️", + [":man_in_lotus_position_dark_skin_tone:"] = "\uD83E\uDDD8\uD83C\uDFFF\u200D♂️", + [":man_in_lotus_position_light_skin_tone:"] = "\uD83E\uDDD8\uD83C\uDFFB\u200D♂️", + [":man_in_lotus_position_medium_dark_skin_tone:"] = "\uD83E\uDDD8\uD83C\uDFFE\u200D♂️", + [":man_in_lotus_position_medium_light_skin_tone:"] = "\uD83E\uDDD8\uD83C\uDFFC\u200D♂️", + [":man_in_lotus_position_medium_skin_tone:"] = "\uD83E\uDDD8\uD83C\uDFFD\u200D♂️", + [":man_in_lotus_position_tone1:"] = "\uD83E\uDDD8\uD83C\uDFFB\u200D♂️", + [":man_in_lotus_position_tone2:"] = "\uD83E\uDDD8\uD83C\uDFFC\u200D♂️", + [":man_in_lotus_position_tone3:"] = "\uD83E\uDDD8\uD83C\uDFFD\u200D♂️", + [":man_in_lotus_position_tone4:"] = "\uD83E\uDDD8\uD83C\uDFFE\u200D♂️", + [":man_in_lotus_position_tone5:"] = "\uD83E\uDDD8\uD83C\uDFFF\u200D♂️", + [":man_in_manual_wheelchair:"] = "\uD83D\uDC68\u200D\uD83E\uDDBD", + [":man_in_manual_wheelchair::skin-tone-1:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83E\uDDBD", + [":man_in_manual_wheelchair::skin-tone-2:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83E\uDDBD", + [":man_in_manual_wheelchair::skin-tone-3:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83E\uDDBD", + [":man_in_manual_wheelchair::skin-tone-4:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83E\uDDBD", + [":man_in_manual_wheelchair::skin-tone-5:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83E\uDDBD", + [":man_in_manual_wheelchair_dark_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83E\uDDBD", + [":man_in_manual_wheelchair_light_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83E\uDDBD", + [":man_in_manual_wheelchair_medium_dark_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83E\uDDBD", + [":man_in_manual_wheelchair_medium_light_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83E\uDDBD", + [":man_in_manual_wheelchair_medium_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83E\uDDBD", + [":man_in_manual_wheelchair_tone1:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83E\uDDBD", + [":man_in_manual_wheelchair_tone2:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83E\uDDBD", + [":man_in_manual_wheelchair_tone3:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83E\uDDBD", + [":man_in_manual_wheelchair_tone4:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83E\uDDBD", + [":man_in_manual_wheelchair_tone5:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83E\uDDBD", + [":man_in_motorized_wheelchair:"] = "\uD83D\uDC68\u200D\uD83E\uDDBC", + [":man_in_motorized_wheelchair::skin-tone-1:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83E\uDDBC", + [":man_in_motorized_wheelchair::skin-tone-2:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83E\uDDBC", + [":man_in_motorized_wheelchair::skin-tone-3:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83E\uDDBC", + [":man_in_motorized_wheelchair::skin-tone-4:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83E\uDDBC", + [":man_in_motorized_wheelchair::skin-tone-5:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83E\uDDBC", + [":man_in_motorized_wheelchair_dark_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83E\uDDBC", + [":man_in_motorized_wheelchair_light_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83E\uDDBC", + [":man_in_motorized_wheelchair_medium_dark_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83E\uDDBC", + [":man_in_motorized_wheelchair_medium_light_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83E\uDDBC", + [":man_in_motorized_wheelchair_medium_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83E\uDDBC", + [":man_in_motorized_wheelchair_tone1:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83E\uDDBC", + [":man_in_motorized_wheelchair_tone2:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83E\uDDBC", + [":man_in_motorized_wheelchair_tone3:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83E\uDDBC", + [":man_in_motorized_wheelchair_tone4:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83E\uDDBC", + [":man_in_motorized_wheelchair_tone5:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83E\uDDBC", + [":man_in_steamy_room:"] = "\uD83E\uDDD6\u200D♂️", + [":man_in_steamy_room::skin-tone-1:"] = "\uD83E\uDDD6\uD83C\uDFFB\u200D♂️", + [":man_in_steamy_room::skin-tone-2:"] = "\uD83E\uDDD6\uD83C\uDFFC\u200D♂️", + [":man_in_steamy_room::skin-tone-3:"] = "\uD83E\uDDD6\uD83C\uDFFD\u200D♂️", + [":man_in_steamy_room::skin-tone-4:"] = "\uD83E\uDDD6\uD83C\uDFFE\u200D♂️", + [":man_in_steamy_room::skin-tone-5:"] = "\uD83E\uDDD6\uD83C\uDFFF\u200D♂️", + [":man_in_steamy_room_dark_skin_tone:"] = "\uD83E\uDDD6\uD83C\uDFFF\u200D♂️", + [":man_in_steamy_room_light_skin_tone:"] = "\uD83E\uDDD6\uD83C\uDFFB\u200D♂️", + [":man_in_steamy_room_medium_dark_skin_tone:"] = "\uD83E\uDDD6\uD83C\uDFFE\u200D♂️", + [":man_in_steamy_room_medium_light_skin_tone:"] = "\uD83E\uDDD6\uD83C\uDFFC\u200D♂️", + [":man_in_steamy_room_medium_skin_tone:"] = "\uD83E\uDDD6\uD83C\uDFFD\u200D♂️", + [":man_in_steamy_room_tone1:"] = "\uD83E\uDDD6\uD83C\uDFFB\u200D♂️", + [":man_in_steamy_room_tone2:"] = "\uD83E\uDDD6\uD83C\uDFFC\u200D♂️", + [":man_in_steamy_room_tone3:"] = "\uD83E\uDDD6\uD83C\uDFFD\u200D♂️", + [":man_in_steamy_room_tone4:"] = "\uD83E\uDDD6\uD83C\uDFFE\u200D♂️", + [":man_in_steamy_room_tone5:"] = "\uD83E\uDDD6\uD83C\uDFFF\u200D♂️", + [":man_in_tuxedo:"] = "\uD83E\uDD35", + [":man_in_tuxedo::skin-tone-1:"] = "\uD83E\uDD35\uD83C\uDFFB", + [":man_in_tuxedo::skin-tone-2:"] = "\uD83E\uDD35\uD83C\uDFFC", + [":man_in_tuxedo::skin-tone-3:"] = "\uD83E\uDD35\uD83C\uDFFD", + [":man_in_tuxedo::skin-tone-4:"] = "\uD83E\uDD35\uD83C\uDFFE", + [":man_in_tuxedo::skin-tone-5:"] = "\uD83E\uDD35\uD83C\uDFFF", + [":man_in_tuxedo_tone1:"] = "\uD83E\uDD35\uD83C\uDFFB", + [":man_in_tuxedo_tone2:"] = "\uD83E\uDD35\uD83C\uDFFC", + [":man_in_tuxedo_tone3:"] = "\uD83E\uDD35\uD83C\uDFFD", + [":man_in_tuxedo_tone4:"] = "\uD83E\uDD35\uD83C\uDFFE", + [":man_in_tuxedo_tone5:"] = "\uD83E\uDD35\uD83C\uDFFF", + [":man_judge:"] = "\uD83D\uDC68\u200D⚖️", + [":man_judge::skin-tone-1:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D⚖️", + [":man_judge::skin-tone-2:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D⚖️", + [":man_judge::skin-tone-3:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D⚖️", + [":man_judge::skin-tone-4:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D⚖️", + [":man_judge::skin-tone-5:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D⚖️", + [":man_judge_dark_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D⚖️", + [":man_judge_light_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D⚖️", + [":man_judge_medium_dark_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D⚖️", + [":man_judge_medium_light_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D⚖️", + [":man_judge_medium_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D⚖️", + [":man_judge_tone1:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D⚖️", + [":man_judge_tone2:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D⚖️", + [":man_judge_tone3:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D⚖️", + [":man_judge_tone4:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D⚖️", + [":man_judge_tone5:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D⚖️", + [":man_juggling:"] = "\uD83E\uDD39\u200D♂️", + [":man_juggling::skin-tone-1:"] = "\uD83E\uDD39\uD83C\uDFFB\u200D♂️", + [":man_juggling::skin-tone-2:"] = "\uD83E\uDD39\uD83C\uDFFC\u200D♂️", + [":man_juggling::skin-tone-3:"] = "\uD83E\uDD39\uD83C\uDFFD\u200D♂️", + [":man_juggling::skin-tone-4:"] = "\uD83E\uDD39\uD83C\uDFFE\u200D♂️", + [":man_juggling::skin-tone-5:"] = "\uD83E\uDD39\uD83C\uDFFF\u200D♂️", + [":man_juggling_dark_skin_tone:"] = "\uD83E\uDD39\uD83C\uDFFF\u200D♂️", + [":man_juggling_light_skin_tone:"] = "\uD83E\uDD39\uD83C\uDFFB\u200D♂️", + [":man_juggling_medium_dark_skin_tone:"] = "\uD83E\uDD39\uD83C\uDFFE\u200D♂️", + [":man_juggling_medium_light_skin_tone:"] = "\uD83E\uDD39\uD83C\uDFFC\u200D♂️", + [":man_juggling_medium_skin_tone:"] = "\uD83E\uDD39\uD83C\uDFFD\u200D♂️", + [":man_juggling_tone1:"] = "\uD83E\uDD39\uD83C\uDFFB\u200D♂️", + [":man_juggling_tone2:"] = "\uD83E\uDD39\uD83C\uDFFC\u200D♂️", + [":man_juggling_tone3:"] = "\uD83E\uDD39\uD83C\uDFFD\u200D♂️", + [":man_juggling_tone4:"] = "\uD83E\uDD39\uD83C\uDFFE\u200D♂️", + [":man_juggling_tone5:"] = "\uD83E\uDD39\uD83C\uDFFF\u200D♂️", + [":man_kneeling:"] = "\uD83E\uDDCE\u200D♂️", + [":man_kneeling::skin-tone-1:"] = "\uD83E\uDDCE\uD83C\uDFFB\u200D♂️", + [":man_kneeling::skin-tone-2:"] = "\uD83E\uDDCE\uD83C\uDFFC\u200D♂️", + [":man_kneeling::skin-tone-3:"] = "\uD83E\uDDCE\uD83C\uDFFD\u200D♂️", + [":man_kneeling::skin-tone-4:"] = "\uD83E\uDDCE\uD83C\uDFFE\u200D♂️", + [":man_kneeling::skin-tone-5:"] = "\uD83E\uDDCE\uD83C\uDFFF\u200D♂️", + [":man_kneeling_dark_skin_tone:"] = "\uD83E\uDDCE\uD83C\uDFFF\u200D♂️", + [":man_kneeling_light_skin_tone:"] = "\uD83E\uDDCE\uD83C\uDFFB\u200D♂️", + [":man_kneeling_medium_dark_skin_tone:"] = "\uD83E\uDDCE\uD83C\uDFFE\u200D♂️", + [":man_kneeling_medium_light_skin_tone:"] = "\uD83E\uDDCE\uD83C\uDFFC\u200D♂️", + [":man_kneeling_medium_skin_tone:"] = "\uD83E\uDDCE\uD83C\uDFFD\u200D♂️", + [":man_kneeling_tone1:"] = "\uD83E\uDDCE\uD83C\uDFFB\u200D♂️", + [":man_kneeling_tone2:"] = "\uD83E\uDDCE\uD83C\uDFFC\u200D♂️", + [":man_kneeling_tone3:"] = "\uD83E\uDDCE\uD83C\uDFFD\u200D♂️", + [":man_kneeling_tone4:"] = "\uD83E\uDDCE\uD83C\uDFFE\u200D♂️", + [":man_kneeling_tone5:"] = "\uD83E\uDDCE\uD83C\uDFFF\u200D♂️", + [":man_lifting_weights:"] = "\uD83C\uDFCB️\u200D♂️", + [":man_lifting_weights::skin-tone-1:"] = "\uD83C\uDFCB\uD83C\uDFFB\u200D♂️", + [":man_lifting_weights::skin-tone-2:"] = "\uD83C\uDFCB\uD83C\uDFFC\u200D♂️", + [":man_lifting_weights::skin-tone-3:"] = "\uD83C\uDFCB\uD83C\uDFFD\u200D♂️", + [":man_lifting_weights::skin-tone-4:"] = "\uD83C\uDFCB\uD83C\uDFFE\u200D♂️", + [":man_lifting_weights::skin-tone-5:"] = "\uD83C\uDFCB\uD83C\uDFFF\u200D♂️", + [":man_lifting_weights_dark_skin_tone:"] = "\uD83C\uDFCB\uD83C\uDFFF\u200D♂️", + [":man_lifting_weights_light_skin_tone:"] = "\uD83C\uDFCB\uD83C\uDFFB\u200D♂️", + [":man_lifting_weights_medium_dark_skin_tone:"] = "\uD83C\uDFCB\uD83C\uDFFE\u200D♂️", + [":man_lifting_weights_medium_light_skin_tone:"] = "\uD83C\uDFCB\uD83C\uDFFC\u200D♂️", + [":man_lifting_weights_medium_skin_tone:"] = "\uD83C\uDFCB\uD83C\uDFFD\u200D♂️", + [":man_lifting_weights_tone1:"] = "\uD83C\uDFCB\uD83C\uDFFB\u200D♂️", + [":man_lifting_weights_tone2:"] = "\uD83C\uDFCB\uD83C\uDFFC\u200D♂️", + [":man_lifting_weights_tone3:"] = "\uD83C\uDFCB\uD83C\uDFFD\u200D♂️", + [":man_lifting_weights_tone4:"] = "\uD83C\uDFCB\uD83C\uDFFE\u200D♂️", + [":man_lifting_weights_tone5:"] = "\uD83C\uDFCB\uD83C\uDFFF\u200D♂️", + [":man_mage:"] = "\uD83E\uDDD9\u200D♂️", + [":man_mage::skin-tone-1:"] = "\uD83E\uDDD9\uD83C\uDFFB\u200D♂️", + [":man_mage::skin-tone-2:"] = "\uD83E\uDDD9\uD83C\uDFFC\u200D♂️", + [":man_mage::skin-tone-3:"] = "\uD83E\uDDD9\uD83C\uDFFD\u200D♂️", + [":man_mage::skin-tone-4:"] = "\uD83E\uDDD9\uD83C\uDFFE\u200D♂️", + [":man_mage::skin-tone-5:"] = "\uD83E\uDDD9\uD83C\uDFFF\u200D♂️", + [":man_mage_dark_skin_tone:"] = "\uD83E\uDDD9\uD83C\uDFFF\u200D♂️", + [":man_mage_light_skin_tone:"] = "\uD83E\uDDD9\uD83C\uDFFB\u200D♂️", + [":man_mage_medium_dark_skin_tone:"] = "\uD83E\uDDD9\uD83C\uDFFE\u200D♂️", + [":man_mage_medium_light_skin_tone:"] = "\uD83E\uDDD9\uD83C\uDFFC\u200D♂️", + [":man_mage_medium_skin_tone:"] = "\uD83E\uDDD9\uD83C\uDFFD\u200D♂️", + [":man_mage_tone1:"] = "\uD83E\uDDD9\uD83C\uDFFB\u200D♂️", + [":man_mage_tone2:"] = "\uD83E\uDDD9\uD83C\uDFFC\u200D♂️", + [":man_mage_tone3:"] = "\uD83E\uDDD9\uD83C\uDFFD\u200D♂️", + [":man_mage_tone4:"] = "\uD83E\uDDD9\uD83C\uDFFE\u200D♂️", + [":man_mage_tone5:"] = "\uD83E\uDDD9\uD83C\uDFFF\u200D♂️", + [":man_mechanic:"] = "\uD83D\uDC68\u200D\uD83D\uDD27", + [":man_mechanic::skin-tone-1:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83D\uDD27", + [":man_mechanic::skin-tone-2:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83D\uDD27", + [":man_mechanic::skin-tone-3:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83D\uDD27", + [":man_mechanic::skin-tone-4:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83D\uDD27", + [":man_mechanic::skin-tone-5:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83D\uDD27", + [":man_mechanic_dark_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83D\uDD27", + [":man_mechanic_light_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83D\uDD27", + [":man_mechanic_medium_dark_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83D\uDD27", + [":man_mechanic_medium_light_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83D\uDD27", + [":man_mechanic_medium_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83D\uDD27", + [":man_mechanic_tone1:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83D\uDD27", + [":man_mechanic_tone2:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83D\uDD27", + [":man_mechanic_tone3:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83D\uDD27", + [":man_mechanic_tone4:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83D\uDD27", + [":man_mechanic_tone5:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83D\uDD27", + [":man_mountain_biking:"] = "\uD83D\uDEB5\u200D♂️", + [":man_mountain_biking::skin-tone-1:"] = "\uD83D\uDEB5\uD83C\uDFFB\u200D♂️", + [":man_mountain_biking::skin-tone-2:"] = "\uD83D\uDEB5\uD83C\uDFFC\u200D♂️", + [":man_mountain_biking::skin-tone-3:"] = "\uD83D\uDEB5\uD83C\uDFFD\u200D♂️", + [":man_mountain_biking::skin-tone-4:"] = "\uD83D\uDEB5\uD83C\uDFFE\u200D♂️", + [":man_mountain_biking::skin-tone-5:"] = "\uD83D\uDEB5\uD83C\uDFFF\u200D♂️", + [":man_mountain_biking_dark_skin_tone:"] = "\uD83D\uDEB5\uD83C\uDFFF\u200D♂️", + [":man_mountain_biking_light_skin_tone:"] = "\uD83D\uDEB5\uD83C\uDFFB\u200D♂️", + [":man_mountain_biking_medium_dark_skin_tone:"] = "\uD83D\uDEB5\uD83C\uDFFE\u200D♂️", + [":man_mountain_biking_medium_light_skin_tone:"] = "\uD83D\uDEB5\uD83C\uDFFC\u200D♂️", + [":man_mountain_biking_medium_skin_tone:"] = "\uD83D\uDEB5\uD83C\uDFFD\u200D♂️", + [":man_mountain_biking_tone1:"] = "\uD83D\uDEB5\uD83C\uDFFB\u200D♂️", + [":man_mountain_biking_tone2:"] = "\uD83D\uDEB5\uD83C\uDFFC\u200D♂️", + [":man_mountain_biking_tone3:"] = "\uD83D\uDEB5\uD83C\uDFFD\u200D♂️", + [":man_mountain_biking_tone4:"] = "\uD83D\uDEB5\uD83C\uDFFE\u200D♂️", + [":man_mountain_biking_tone5:"] = "\uD83D\uDEB5\uD83C\uDFFF\u200D♂️", + [":man_office_worker:"] = "\uD83D\uDC68\u200D\uD83D\uDCBC", + [":man_office_worker::skin-tone-1:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83D\uDCBC", + [":man_office_worker::skin-tone-2:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83D\uDCBC", + [":man_office_worker::skin-tone-3:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83D\uDCBC", + [":man_office_worker::skin-tone-4:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83D\uDCBC", + [":man_office_worker::skin-tone-5:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83D\uDCBC", + [":man_office_worker_dark_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83D\uDCBC", + [":man_office_worker_light_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83D\uDCBC", + [":man_office_worker_medium_dark_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83D\uDCBC", + [":man_office_worker_medium_light_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83D\uDCBC", + [":man_office_worker_medium_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83D\uDCBC", + [":man_office_worker_tone1:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83D\uDCBC", + [":man_office_worker_tone2:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83D\uDCBC", + [":man_office_worker_tone3:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83D\uDCBC", + [":man_office_worker_tone4:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83D\uDCBC", + [":man_office_worker_tone5:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83D\uDCBC", + [":man_pilot:"] = "\uD83D\uDC68\u200D✈️", + [":man_pilot::skin-tone-1:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D✈️", + [":man_pilot::skin-tone-2:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D✈️", + [":man_pilot::skin-tone-3:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D✈️", + [":man_pilot::skin-tone-4:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D✈️", + [":man_pilot::skin-tone-5:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D✈️", + [":man_pilot_dark_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D✈️", + [":man_pilot_light_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D✈️", + [":man_pilot_medium_dark_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D✈️", + [":man_pilot_medium_light_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D✈️", + [":man_pilot_medium_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D✈️", + [":man_pilot_tone1:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D✈️", + [":man_pilot_tone2:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D✈️", + [":man_pilot_tone3:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D✈️", + [":man_pilot_tone4:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D✈️", + [":man_pilot_tone5:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D✈️", + [":man_playing_handball:"] = "\uD83E\uDD3E\u200D♂️", + [":man_playing_handball::skin-tone-1:"] = "\uD83E\uDD3E\uD83C\uDFFB\u200D♂️", + [":man_playing_handball::skin-tone-2:"] = "\uD83E\uDD3E\uD83C\uDFFC\u200D♂️", + [":man_playing_handball::skin-tone-3:"] = "\uD83E\uDD3E\uD83C\uDFFD\u200D♂️", + [":man_playing_handball::skin-tone-4:"] = "\uD83E\uDD3E\uD83C\uDFFE\u200D♂️", + [":man_playing_handball::skin-tone-5:"] = "\uD83E\uDD3E\uD83C\uDFFF\u200D♂️", + [":man_playing_handball_dark_skin_tone:"] = "\uD83E\uDD3E\uD83C\uDFFF\u200D♂️", + [":man_playing_handball_light_skin_tone:"] = "\uD83E\uDD3E\uD83C\uDFFB\u200D♂️", + [":man_playing_handball_medium_dark_skin_tone:"] = "\uD83E\uDD3E\uD83C\uDFFE\u200D♂️", + [":man_playing_handball_medium_light_skin_tone:"] = "\uD83E\uDD3E\uD83C\uDFFC\u200D♂️", + [":man_playing_handball_medium_skin_tone:"] = "\uD83E\uDD3E\uD83C\uDFFD\u200D♂️", + [":man_playing_handball_tone1:"] = "\uD83E\uDD3E\uD83C\uDFFB\u200D♂️", + [":man_playing_handball_tone2:"] = "\uD83E\uDD3E\uD83C\uDFFC\u200D♂️", + [":man_playing_handball_tone3:"] = "\uD83E\uDD3E\uD83C\uDFFD\u200D♂️", + [":man_playing_handball_tone4:"] = "\uD83E\uDD3E\uD83C\uDFFE\u200D♂️", + [":man_playing_handball_tone5:"] = "\uD83E\uDD3E\uD83C\uDFFF\u200D♂️", + [":man_playing_water_polo:"] = "\uD83E\uDD3D\u200D♂️", + [":man_playing_water_polo::skin-tone-1:"] = "\uD83E\uDD3D\uD83C\uDFFB\u200D♂️", + [":man_playing_water_polo::skin-tone-2:"] = "\uD83E\uDD3D\uD83C\uDFFC\u200D♂️", + [":man_playing_water_polo::skin-tone-3:"] = "\uD83E\uDD3D\uD83C\uDFFD\u200D♂️", + [":man_playing_water_polo::skin-tone-4:"] = "\uD83E\uDD3D\uD83C\uDFFE\u200D♂️", + [":man_playing_water_polo::skin-tone-5:"] = "\uD83E\uDD3D\uD83C\uDFFF\u200D♂️", + [":man_playing_water_polo_dark_skin_tone:"] = "\uD83E\uDD3D\uD83C\uDFFF\u200D♂️", + [":man_playing_water_polo_light_skin_tone:"] = "\uD83E\uDD3D\uD83C\uDFFB\u200D♂️", + [":man_playing_water_polo_medium_dark_skin_tone:"] = "\uD83E\uDD3D\uD83C\uDFFE\u200D♂️", + [":man_playing_water_polo_medium_light_skin_tone:"] = "\uD83E\uDD3D\uD83C\uDFFC\u200D♂️", + [":man_playing_water_polo_medium_skin_tone:"] = "\uD83E\uDD3D\uD83C\uDFFD\u200D♂️", + [":man_playing_water_polo_tone1:"] = "\uD83E\uDD3D\uD83C\uDFFB\u200D♂️", + [":man_playing_water_polo_tone2:"] = "\uD83E\uDD3D\uD83C\uDFFC\u200D♂️", + [":man_playing_water_polo_tone3:"] = "\uD83E\uDD3D\uD83C\uDFFD\u200D♂️", + [":man_playing_water_polo_tone4:"] = "\uD83E\uDD3D\uD83C\uDFFE\u200D♂️", + [":man_playing_water_polo_tone5:"] = "\uD83E\uDD3D\uD83C\uDFFF\u200D♂️", + [":man_police_officer:"] = "\uD83D\uDC6E\u200D♂️", + [":man_police_officer::skin-tone-1:"] = "\uD83D\uDC6E\uD83C\uDFFB\u200D♂️", + [":man_police_officer::skin-tone-2:"] = "\uD83D\uDC6E\uD83C\uDFFC\u200D♂️", + [":man_police_officer::skin-tone-3:"] = "\uD83D\uDC6E\uD83C\uDFFD\u200D♂️", + [":man_police_officer::skin-tone-4:"] = "\uD83D\uDC6E\uD83C\uDFFE\u200D♂️", + [":man_police_officer::skin-tone-5:"] = "\uD83D\uDC6E\uD83C\uDFFF\u200D♂️", + [":man_police_officer_dark_skin_tone:"] = "\uD83D\uDC6E\uD83C\uDFFF\u200D♂️", + [":man_police_officer_light_skin_tone:"] = "\uD83D\uDC6E\uD83C\uDFFB\u200D♂️", + [":man_police_officer_medium_dark_skin_tone:"] = "\uD83D\uDC6E\uD83C\uDFFE\u200D♂️", + [":man_police_officer_medium_light_skin_tone:"] = "\uD83D\uDC6E\uD83C\uDFFC\u200D♂️", + [":man_police_officer_medium_skin_tone:"] = "\uD83D\uDC6E\uD83C\uDFFD\u200D♂️", + [":man_police_officer_tone1:"] = "\uD83D\uDC6E\uD83C\uDFFB\u200D♂️", + [":man_police_officer_tone2:"] = "\uD83D\uDC6E\uD83C\uDFFC\u200D♂️", + [":man_police_officer_tone3:"] = "\uD83D\uDC6E\uD83C\uDFFD\u200D♂️", + [":man_police_officer_tone4:"] = "\uD83D\uDC6E\uD83C\uDFFE\u200D♂️", + [":man_police_officer_tone5:"] = "\uD83D\uDC6E\uD83C\uDFFF\u200D♂️", + [":man_pouting:"] = "\uD83D\uDE4E\u200D♂️", + [":man_pouting::skin-tone-1:"] = "\uD83D\uDE4E\uD83C\uDFFB\u200D♂️", + [":man_pouting::skin-tone-2:"] = "\uD83D\uDE4E\uD83C\uDFFC\u200D♂️", + [":man_pouting::skin-tone-3:"] = "\uD83D\uDE4E\uD83C\uDFFD\u200D♂️", + [":man_pouting::skin-tone-4:"] = "\uD83D\uDE4E\uD83C\uDFFE\u200D♂️", + [":man_pouting::skin-tone-5:"] = "\uD83D\uDE4E\uD83C\uDFFF\u200D♂️", + [":man_pouting_dark_skin_tone:"] = "\uD83D\uDE4E\uD83C\uDFFF\u200D♂️", + [":man_pouting_light_skin_tone:"] = "\uD83D\uDE4E\uD83C\uDFFB\u200D♂️", + [":man_pouting_medium_dark_skin_tone:"] = "\uD83D\uDE4E\uD83C\uDFFE\u200D♂️", + [":man_pouting_medium_light_skin_tone:"] = "\uD83D\uDE4E\uD83C\uDFFC\u200D♂️", + [":man_pouting_medium_skin_tone:"] = "\uD83D\uDE4E\uD83C\uDFFD\u200D♂️", + [":man_pouting_tone1:"] = "\uD83D\uDE4E\uD83C\uDFFB\u200D♂️", + [":man_pouting_tone2:"] = "\uD83D\uDE4E\uD83C\uDFFC\u200D♂️", + [":man_pouting_tone3:"] = "\uD83D\uDE4E\uD83C\uDFFD\u200D♂️", + [":man_pouting_tone4:"] = "\uD83D\uDE4E\uD83C\uDFFE\u200D♂️", + [":man_pouting_tone5:"] = "\uD83D\uDE4E\uD83C\uDFFF\u200D♂️", + [":man_raising_hand:"] = "\uD83D\uDE4B\u200D♂️", + [":man_raising_hand::skin-tone-1:"] = "\uD83D\uDE4B\uD83C\uDFFB\u200D♂️", + [":man_raising_hand::skin-tone-2:"] = "\uD83D\uDE4B\uD83C\uDFFC\u200D♂️", + [":man_raising_hand::skin-tone-3:"] = "\uD83D\uDE4B\uD83C\uDFFD\u200D♂️", + [":man_raising_hand::skin-tone-4:"] = "\uD83D\uDE4B\uD83C\uDFFE\u200D♂️", + [":man_raising_hand::skin-tone-5:"] = "\uD83D\uDE4B\uD83C\uDFFF\u200D♂️", + [":man_raising_hand_dark_skin_tone:"] = "\uD83D\uDE4B\uD83C\uDFFF\u200D♂️", + [":man_raising_hand_light_skin_tone:"] = "\uD83D\uDE4B\uD83C\uDFFB\u200D♂️", + [":man_raising_hand_medium_dark_skin_tone:"] = "\uD83D\uDE4B\uD83C\uDFFE\u200D♂️", + [":man_raising_hand_medium_light_skin_tone:"] = "\uD83D\uDE4B\uD83C\uDFFC\u200D♂️", + [":man_raising_hand_medium_skin_tone:"] = "\uD83D\uDE4B\uD83C\uDFFD\u200D♂️", + [":man_raising_hand_tone1:"] = "\uD83D\uDE4B\uD83C\uDFFB\u200D♂️", + [":man_raising_hand_tone2:"] = "\uD83D\uDE4B\uD83C\uDFFC\u200D♂️", + [":man_raising_hand_tone3:"] = "\uD83D\uDE4B\uD83C\uDFFD\u200D♂️", + [":man_raising_hand_tone4:"] = "\uD83D\uDE4B\uD83C\uDFFE\u200D♂️", + [":man_raising_hand_tone5:"] = "\uD83D\uDE4B\uD83C\uDFFF\u200D♂️", + [":man_red_haired:"] = "\uD83D\uDC68\u200D\uD83E\uDDB0", + [":man_red_haired::skin-tone-1:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83E\uDDB0", + [":man_red_haired::skin-tone-2:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83E\uDDB0", + [":man_red_haired::skin-tone-3:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83E\uDDB0", + [":man_red_haired::skin-tone-4:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83E\uDDB0", + [":man_red_haired::skin-tone-5:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83E\uDDB0", + [":man_red_haired_dark_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83E\uDDB0", + [":man_red_haired_light_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83E\uDDB0", + [":man_red_haired_medium_dark_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83E\uDDB0", + [":man_red_haired_medium_light_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83E\uDDB0", + [":man_red_haired_medium_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83E\uDDB0", + [":man_red_haired_tone1:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83E\uDDB0", + [":man_red_haired_tone2:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83E\uDDB0", + [":man_red_haired_tone3:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83E\uDDB0", + [":man_red_haired_tone4:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83E\uDDB0", + [":man_red_haired_tone5:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83E\uDDB0", + [":man_rowing_boat:"] = "\uD83D\uDEA3\u200D♂️", + [":man_rowing_boat::skin-tone-1:"] = "\uD83D\uDEA3\uD83C\uDFFB\u200D♂️", + [":man_rowing_boat::skin-tone-2:"] = "\uD83D\uDEA3\uD83C\uDFFC\u200D♂️", + [":man_rowing_boat::skin-tone-3:"] = "\uD83D\uDEA3\uD83C\uDFFD\u200D♂️", + [":man_rowing_boat::skin-tone-4:"] = "\uD83D\uDEA3\uD83C\uDFFE\u200D♂️", + [":man_rowing_boat::skin-tone-5:"] = "\uD83D\uDEA3\uD83C\uDFFF\u200D♂️", + [":man_rowing_boat_dark_skin_tone:"] = "\uD83D\uDEA3\uD83C\uDFFF\u200D♂️", + [":man_rowing_boat_light_skin_tone:"] = "\uD83D\uDEA3\uD83C\uDFFB\u200D♂️", + [":man_rowing_boat_medium_dark_skin_tone:"] = "\uD83D\uDEA3\uD83C\uDFFE\u200D♂️", + [":man_rowing_boat_medium_light_skin_tone:"] = "\uD83D\uDEA3\uD83C\uDFFC\u200D♂️", + [":man_rowing_boat_medium_skin_tone:"] = "\uD83D\uDEA3\uD83C\uDFFD\u200D♂️", + [":man_rowing_boat_tone1:"] = "\uD83D\uDEA3\uD83C\uDFFB\u200D♂️", + [":man_rowing_boat_tone2:"] = "\uD83D\uDEA3\uD83C\uDFFC\u200D♂️", + [":man_rowing_boat_tone3:"] = "\uD83D\uDEA3\uD83C\uDFFD\u200D♂️", + [":man_rowing_boat_tone4:"] = "\uD83D\uDEA3\uD83C\uDFFE\u200D♂️", + [":man_rowing_boat_tone5:"] = "\uD83D\uDEA3\uD83C\uDFFF\u200D♂️", + [":man_running:"] = "\uD83C\uDFC3\u200D♂️", + [":man_running::skin-tone-1:"] = "\uD83C\uDFC3\uD83C\uDFFB\u200D♂️", + [":man_running::skin-tone-2:"] = "\uD83C\uDFC3\uD83C\uDFFC\u200D♂️", + [":man_running::skin-tone-3:"] = "\uD83C\uDFC3\uD83C\uDFFD\u200D♂️", + [":man_running::skin-tone-4:"] = "\uD83C\uDFC3\uD83C\uDFFE\u200D♂️", + [":man_running::skin-tone-5:"] = "\uD83C\uDFC3\uD83C\uDFFF\u200D♂️", + [":man_running_dark_skin_tone:"] = "\uD83C\uDFC3\uD83C\uDFFF\u200D♂️", + [":man_running_light_skin_tone:"] = "\uD83C\uDFC3\uD83C\uDFFB\u200D♂️", + [":man_running_medium_dark_skin_tone:"] = "\uD83C\uDFC3\uD83C\uDFFE\u200D♂️", + [":man_running_medium_light_skin_tone:"] = "\uD83C\uDFC3\uD83C\uDFFC\u200D♂️", + [":man_running_medium_skin_tone:"] = "\uD83C\uDFC3\uD83C\uDFFD\u200D♂️", + [":man_running_tone1:"] = "\uD83C\uDFC3\uD83C\uDFFB\u200D♂️", + [":man_running_tone2:"] = "\uD83C\uDFC3\uD83C\uDFFC\u200D♂️", + [":man_running_tone3:"] = "\uD83C\uDFC3\uD83C\uDFFD\u200D♂️", + [":man_running_tone4:"] = "\uD83C\uDFC3\uD83C\uDFFE\u200D♂️", + [":man_running_tone5:"] = "\uD83C\uDFC3\uD83C\uDFFF\u200D♂️", + [":man_scientist:"] = "\uD83D\uDC68\u200D\uD83D\uDD2C", + [":man_scientist::skin-tone-1:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83D\uDD2C", + [":man_scientist::skin-tone-2:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83D\uDD2C", + [":man_scientist::skin-tone-3:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83D\uDD2C", + [":man_scientist::skin-tone-4:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83D\uDD2C", + [":man_scientist::skin-tone-5:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83D\uDD2C", + [":man_scientist_dark_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83D\uDD2C", + [":man_scientist_light_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83D\uDD2C", + [":man_scientist_medium_dark_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83D\uDD2C", + [":man_scientist_medium_light_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83D\uDD2C", + [":man_scientist_medium_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83D\uDD2C", + [":man_scientist_tone1:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83D\uDD2C", + [":man_scientist_tone2:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83D\uDD2C", + [":man_scientist_tone3:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83D\uDD2C", + [":man_scientist_tone4:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83D\uDD2C", + [":man_scientist_tone5:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83D\uDD2C", + [":man_shrugging:"] = "\uD83E\uDD37\u200D♂️", + [":man_shrugging::skin-tone-1:"] = "\uD83E\uDD37\uD83C\uDFFB\u200D♂️", + [":man_shrugging::skin-tone-2:"] = "\uD83E\uDD37\uD83C\uDFFC\u200D♂️", + [":man_shrugging::skin-tone-3:"] = "\uD83E\uDD37\uD83C\uDFFD\u200D♂️", + [":man_shrugging::skin-tone-4:"] = "\uD83E\uDD37\uD83C\uDFFE\u200D♂️", + [":man_shrugging::skin-tone-5:"] = "\uD83E\uDD37\uD83C\uDFFF\u200D♂️", + [":man_shrugging_dark_skin_tone:"] = "\uD83E\uDD37\uD83C\uDFFF\u200D♂️", + [":man_shrugging_light_skin_tone:"] = "\uD83E\uDD37\uD83C\uDFFB\u200D♂️", + [":man_shrugging_medium_dark_skin_tone:"] = "\uD83E\uDD37\uD83C\uDFFE\u200D♂️", + [":man_shrugging_medium_light_skin_tone:"] = "\uD83E\uDD37\uD83C\uDFFC\u200D♂️", + [":man_shrugging_medium_skin_tone:"] = "\uD83E\uDD37\uD83C\uDFFD\u200D♂️", + [":man_shrugging_tone1:"] = "\uD83E\uDD37\uD83C\uDFFB\u200D♂️", + [":man_shrugging_tone2:"] = "\uD83E\uDD37\uD83C\uDFFC\u200D♂️", + [":man_shrugging_tone3:"] = "\uD83E\uDD37\uD83C\uDFFD\u200D♂️", + [":man_shrugging_tone4:"] = "\uD83E\uDD37\uD83C\uDFFE\u200D♂️", + [":man_shrugging_tone5:"] = "\uD83E\uDD37\uD83C\uDFFF\u200D♂️", + [":man_singer:"] = "\uD83D\uDC68\u200D\uD83C\uDFA4", + [":man_singer::skin-tone-1:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83C\uDFA4", + [":man_singer::skin-tone-2:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83C\uDFA4", + [":man_singer::skin-tone-3:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83C\uDFA4", + [":man_singer::skin-tone-4:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83C\uDFA4", + [":man_singer::skin-tone-5:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83C\uDFA4", + [":man_singer_dark_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83C\uDFA4", + [":man_singer_light_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83C\uDFA4", + [":man_singer_medium_dark_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83C\uDFA4", + [":man_singer_medium_light_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83C\uDFA4", + [":man_singer_medium_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83C\uDFA4", + [":man_singer_tone1:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83C\uDFA4", + [":man_singer_tone2:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83C\uDFA4", + [":man_singer_tone3:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83C\uDFA4", + [":man_singer_tone4:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83C\uDFA4", + [":man_singer_tone5:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83C\uDFA4", + [":man_standing:"] = "\uD83E\uDDCD\u200D♂️", + [":man_standing::skin-tone-1:"] = "\uD83E\uDDCD\uD83C\uDFFB\u200D♂️", + [":man_standing::skin-tone-2:"] = "\uD83E\uDDCD\uD83C\uDFFC\u200D♂️", + [":man_standing::skin-tone-3:"] = "\uD83E\uDDCD\uD83C\uDFFD\u200D♂️", + [":man_standing::skin-tone-4:"] = "\uD83E\uDDCD\uD83C\uDFFE\u200D♂️", + [":man_standing::skin-tone-5:"] = "\uD83E\uDDCD\uD83C\uDFFF\u200D♂️", + [":man_standing_dark_skin_tone:"] = "\uD83E\uDDCD\uD83C\uDFFF\u200D♂️", + [":man_standing_light_skin_tone:"] = "\uD83E\uDDCD\uD83C\uDFFB\u200D♂️", + [":man_standing_medium_dark_skin_tone:"] = "\uD83E\uDDCD\uD83C\uDFFE\u200D♂️", + [":man_standing_medium_light_skin_tone:"] = "\uD83E\uDDCD\uD83C\uDFFC\u200D♂️", + [":man_standing_medium_skin_tone:"] = "\uD83E\uDDCD\uD83C\uDFFD\u200D♂️", + [":man_standing_tone1:"] = "\uD83E\uDDCD\uD83C\uDFFB\u200D♂️", + [":man_standing_tone2:"] = "\uD83E\uDDCD\uD83C\uDFFC\u200D♂️", + [":man_standing_tone3:"] = "\uD83E\uDDCD\uD83C\uDFFD\u200D♂️", + [":man_standing_tone4:"] = "\uD83E\uDDCD\uD83C\uDFFE\u200D♂️", + [":man_standing_tone5:"] = "\uD83E\uDDCD\uD83C\uDFFF\u200D♂️", + [":man_student:"] = "\uD83D\uDC68\u200D\uD83C\uDF93", + [":man_student::skin-tone-1:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83C\uDF93", + [":man_student::skin-tone-2:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83C\uDF93", + [":man_student::skin-tone-3:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83C\uDF93", + [":man_student::skin-tone-4:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83C\uDF93", + [":man_student::skin-tone-5:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83C\uDF93", + [":man_student_dark_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83C\uDF93", + [":man_student_light_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83C\uDF93", + [":man_student_medium_dark_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83C\uDF93", + [":man_student_medium_light_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83C\uDF93", + [":man_student_medium_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83C\uDF93", + [":man_student_tone1:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83C\uDF93", + [":man_student_tone2:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83C\uDF93", + [":man_student_tone3:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83C\uDF93", + [":man_student_tone4:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83C\uDF93", + [":man_student_tone5:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83C\uDF93", + [":man_superhero:"] = "\uD83E\uDDB8\u200D♂️", + [":man_superhero::skin-tone-1:"] = "\uD83E\uDDB8\uD83C\uDFFB\u200D♂️", + [":man_superhero::skin-tone-2:"] = "\uD83E\uDDB8\uD83C\uDFFC\u200D♂️", + [":man_superhero::skin-tone-3:"] = "\uD83E\uDDB8\uD83C\uDFFD\u200D♂️", + [":man_superhero::skin-tone-4:"] = "\uD83E\uDDB8\uD83C\uDFFE\u200D♂️", + [":man_superhero::skin-tone-5:"] = "\uD83E\uDDB8\uD83C\uDFFF\u200D♂️", + [":man_superhero_dark_skin_tone:"] = "\uD83E\uDDB8\uD83C\uDFFF\u200D♂️", + [":man_superhero_light_skin_tone:"] = "\uD83E\uDDB8\uD83C\uDFFB\u200D♂️", + [":man_superhero_medium_dark_skin_tone:"] = "\uD83E\uDDB8\uD83C\uDFFE\u200D♂️", + [":man_superhero_medium_light_skin_tone:"] = "\uD83E\uDDB8\uD83C\uDFFC\u200D♂️", + [":man_superhero_medium_skin_tone:"] = "\uD83E\uDDB8\uD83C\uDFFD\u200D♂️", + [":man_superhero_tone1:"] = "\uD83E\uDDB8\uD83C\uDFFB\u200D♂️", + [":man_superhero_tone2:"] = "\uD83E\uDDB8\uD83C\uDFFC\u200D♂️", + [":man_superhero_tone3:"] = "\uD83E\uDDB8\uD83C\uDFFD\u200D♂️", + [":man_superhero_tone4:"] = "\uD83E\uDDB8\uD83C\uDFFE\u200D♂️", + [":man_superhero_tone5:"] = "\uD83E\uDDB8\uD83C\uDFFF\u200D♂️", + [":man_supervillain:"] = "\uD83E\uDDB9\u200D♂️", + [":man_supervillain::skin-tone-1:"] = "\uD83E\uDDB9\uD83C\uDFFB\u200D♂️", + [":man_supervillain::skin-tone-2:"] = "\uD83E\uDDB9\uD83C\uDFFC\u200D♂️", + [":man_supervillain::skin-tone-3:"] = "\uD83E\uDDB9\uD83C\uDFFD\u200D♂️", + [":man_supervillain::skin-tone-4:"] = "\uD83E\uDDB9\uD83C\uDFFE\u200D♂️", + [":man_supervillain::skin-tone-5:"] = "\uD83E\uDDB9\uD83C\uDFFF\u200D♂️", + [":man_supervillain_dark_skin_tone:"] = "\uD83E\uDDB9\uD83C\uDFFF\u200D♂️", + [":man_supervillain_light_skin_tone:"] = "\uD83E\uDDB9\uD83C\uDFFB\u200D♂️", + [":man_supervillain_medium_dark_skin_tone:"] = "\uD83E\uDDB9\uD83C\uDFFE\u200D♂️", + [":man_supervillain_medium_light_skin_tone:"] = "\uD83E\uDDB9\uD83C\uDFFC\u200D♂️", + [":man_supervillain_medium_skin_tone:"] = "\uD83E\uDDB9\uD83C\uDFFD\u200D♂️", + [":man_supervillain_tone1:"] = "\uD83E\uDDB9\uD83C\uDFFB\u200D♂️", + [":man_supervillain_tone2:"] = "\uD83E\uDDB9\uD83C\uDFFC\u200D♂️", + [":man_supervillain_tone3:"] = "\uD83E\uDDB9\uD83C\uDFFD\u200D♂️", + [":man_supervillain_tone4:"] = "\uD83E\uDDB9\uD83C\uDFFE\u200D♂️", + [":man_supervillain_tone5:"] = "\uD83E\uDDB9\uD83C\uDFFF\u200D♂️", + [":man_surfing:"] = "\uD83C\uDFC4\u200D♂️", + [":man_surfing::skin-tone-1:"] = "\uD83C\uDFC4\uD83C\uDFFB\u200D♂️", + [":man_surfing::skin-tone-2:"] = "\uD83C\uDFC4\uD83C\uDFFC\u200D♂️", + [":man_surfing::skin-tone-3:"] = "\uD83C\uDFC4\uD83C\uDFFD\u200D♂️", + [":man_surfing::skin-tone-4:"] = "\uD83C\uDFC4\uD83C\uDFFE\u200D♂️", + [":man_surfing::skin-tone-5:"] = "\uD83C\uDFC4\uD83C\uDFFF\u200D♂️", + [":man_surfing_dark_skin_tone:"] = "\uD83C\uDFC4\uD83C\uDFFF\u200D♂️", + [":man_surfing_light_skin_tone:"] = "\uD83C\uDFC4\uD83C\uDFFB\u200D♂️", + [":man_surfing_medium_dark_skin_tone:"] = "\uD83C\uDFC4\uD83C\uDFFE\u200D♂️", + [":man_surfing_medium_light_skin_tone:"] = "\uD83C\uDFC4\uD83C\uDFFC\u200D♂️", + [":man_surfing_medium_skin_tone:"] = "\uD83C\uDFC4\uD83C\uDFFD\u200D♂️", + [":man_surfing_tone1:"] = "\uD83C\uDFC4\uD83C\uDFFB\u200D♂️", + [":man_surfing_tone2:"] = "\uD83C\uDFC4\uD83C\uDFFC\u200D♂️", + [":man_surfing_tone3:"] = "\uD83C\uDFC4\uD83C\uDFFD\u200D♂️", + [":man_surfing_tone4:"] = "\uD83C\uDFC4\uD83C\uDFFE\u200D♂️", + [":man_surfing_tone5:"] = "\uD83C\uDFC4\uD83C\uDFFF\u200D♂️", + [":man_swimming:"] = "\uD83C\uDFCA\u200D♂️", + [":man_swimming::skin-tone-1:"] = "\uD83C\uDFCA\uD83C\uDFFB\u200D♂️", + [":man_swimming::skin-tone-2:"] = "\uD83C\uDFCA\uD83C\uDFFC\u200D♂️", + [":man_swimming::skin-tone-3:"] = "\uD83C\uDFCA\uD83C\uDFFD\u200D♂️", + [":man_swimming::skin-tone-4:"] = "\uD83C\uDFCA\uD83C\uDFFE\u200D♂️", + [":man_swimming::skin-tone-5:"] = "\uD83C\uDFCA\uD83C\uDFFF\u200D♂️", + [":man_swimming_dark_skin_tone:"] = "\uD83C\uDFCA\uD83C\uDFFF\u200D♂️", + [":man_swimming_light_skin_tone:"] = "\uD83C\uDFCA\uD83C\uDFFB\u200D♂️", + [":man_swimming_medium_dark_skin_tone:"] = "\uD83C\uDFCA\uD83C\uDFFE\u200D♂️", + [":man_swimming_medium_light_skin_tone:"] = "\uD83C\uDFCA\uD83C\uDFFC\u200D♂️", + [":man_swimming_medium_skin_tone:"] = "\uD83C\uDFCA\uD83C\uDFFD\u200D♂️", + [":man_swimming_tone1:"] = "\uD83C\uDFCA\uD83C\uDFFB\u200D♂️", + [":man_swimming_tone2:"] = "\uD83C\uDFCA\uD83C\uDFFC\u200D♂️", + [":man_swimming_tone3:"] = "\uD83C\uDFCA\uD83C\uDFFD\u200D♂️", + [":man_swimming_tone4:"] = "\uD83C\uDFCA\uD83C\uDFFE\u200D♂️", + [":man_swimming_tone5:"] = "\uD83C\uDFCA\uD83C\uDFFF\u200D♂️", + [":man_teacher:"] = "\uD83D\uDC68\u200D\uD83C\uDFEB", + [":man_teacher::skin-tone-1:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83C\uDFEB", + [":man_teacher::skin-tone-2:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83C\uDFEB", + [":man_teacher::skin-tone-3:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83C\uDFEB", + [":man_teacher::skin-tone-4:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83C\uDFEB", + [":man_teacher::skin-tone-5:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83C\uDFEB", + [":man_teacher_dark_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83C\uDFEB", + [":man_teacher_light_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83C\uDFEB", + [":man_teacher_medium_dark_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83C\uDFEB", + [":man_teacher_medium_light_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83C\uDFEB", + [":man_teacher_medium_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83C\uDFEB", + [":man_teacher_tone1:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83C\uDFEB", + [":man_teacher_tone2:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83C\uDFEB", + [":man_teacher_tone3:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83C\uDFEB", + [":man_teacher_tone4:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83C\uDFEB", + [":man_teacher_tone5:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83C\uDFEB", + [":man_technologist:"] = "\uD83D\uDC68\u200D\uD83D\uDCBB", + [":man_technologist::skin-tone-1:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83D\uDCBB", + [":man_technologist::skin-tone-2:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83D\uDCBB", + [":man_technologist::skin-tone-3:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83D\uDCBB", + [":man_technologist::skin-tone-4:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83D\uDCBB", + [":man_technologist::skin-tone-5:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83D\uDCBB", + [":man_technologist_dark_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83D\uDCBB", + [":man_technologist_light_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83D\uDCBB", + [":man_technologist_medium_dark_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83D\uDCBB", + [":man_technologist_medium_light_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83D\uDCBB", + [":man_technologist_medium_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83D\uDCBB", + [":man_technologist_tone1:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83D\uDCBB", + [":man_technologist_tone2:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83D\uDCBB", + [":man_technologist_tone3:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83D\uDCBB", + [":man_technologist_tone4:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83D\uDCBB", + [":man_technologist_tone5:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83D\uDCBB", + [":man_tipping_hand:"] = "\uD83D\uDC81\u200D♂️", + [":man_tipping_hand::skin-tone-1:"] = "\uD83D\uDC81\uD83C\uDFFB\u200D♂️", + [":man_tipping_hand::skin-tone-2:"] = "\uD83D\uDC81\uD83C\uDFFC\u200D♂️", + [":man_tipping_hand::skin-tone-3:"] = "\uD83D\uDC81\uD83C\uDFFD\u200D♂️", + [":man_tipping_hand::skin-tone-4:"] = "\uD83D\uDC81\uD83C\uDFFE\u200D♂️", + [":man_tipping_hand::skin-tone-5:"] = "\uD83D\uDC81\uD83C\uDFFF\u200D♂️", + [":man_tipping_hand_dark_skin_tone:"] = "\uD83D\uDC81\uD83C\uDFFF\u200D♂️", + [":man_tipping_hand_light_skin_tone:"] = "\uD83D\uDC81\uD83C\uDFFB\u200D♂️", + [":man_tipping_hand_medium_dark_skin_tone:"] = "\uD83D\uDC81\uD83C\uDFFE\u200D♂️", + [":man_tipping_hand_medium_light_skin_tone:"] = "\uD83D\uDC81\uD83C\uDFFC\u200D♂️", + [":man_tipping_hand_medium_skin_tone:"] = "\uD83D\uDC81\uD83C\uDFFD\u200D♂️", + [":man_tipping_hand_tone1:"] = "\uD83D\uDC81\uD83C\uDFFB\u200D♂️", + [":man_tipping_hand_tone2:"] = "\uD83D\uDC81\uD83C\uDFFC\u200D♂️", + [":man_tipping_hand_tone3:"] = "\uD83D\uDC81\uD83C\uDFFD\u200D♂️", + [":man_tipping_hand_tone4:"] = "\uD83D\uDC81\uD83C\uDFFE\u200D♂️", + [":man_tipping_hand_tone5:"] = "\uD83D\uDC81\uD83C\uDFFF\u200D♂️", + [":man_tone1:"] = "\uD83D\uDC68\uD83C\uDFFB", + [":man_tone2:"] = "\uD83D\uDC68\uD83C\uDFFC", + [":man_tone3:"] = "\uD83D\uDC68\uD83C\uDFFD", + [":man_tone4:"] = "\uD83D\uDC68\uD83C\uDFFE", + [":man_tone5:"] = "\uD83D\uDC68\uD83C\uDFFF", + [":man_vampire:"] = "\uD83E\uDDDB\u200D♂️", + [":man_vampire::skin-tone-1:"] = "\uD83E\uDDDB\uD83C\uDFFB\u200D♂️", + [":man_vampire::skin-tone-2:"] = "\uD83E\uDDDB\uD83C\uDFFC\u200D♂️", + [":man_vampire::skin-tone-3:"] = "\uD83E\uDDDB\uD83C\uDFFD\u200D♂️", + [":man_vampire::skin-tone-4:"] = "\uD83E\uDDDB\uD83C\uDFFE\u200D♂️", + [":man_vampire::skin-tone-5:"] = "\uD83E\uDDDB\uD83C\uDFFF\u200D♂️", + [":man_vampire_dark_skin_tone:"] = "\uD83E\uDDDB\uD83C\uDFFF\u200D♂️", + [":man_vampire_light_skin_tone:"] = "\uD83E\uDDDB\uD83C\uDFFB\u200D♂️", + [":man_vampire_medium_dark_skin_tone:"] = "\uD83E\uDDDB\uD83C\uDFFE\u200D♂️", + [":man_vampire_medium_light_skin_tone:"] = "\uD83E\uDDDB\uD83C\uDFFC\u200D♂️", + [":man_vampire_medium_skin_tone:"] = "\uD83E\uDDDB\uD83C\uDFFD\u200D♂️", + [":man_vampire_tone1:"] = "\uD83E\uDDDB\uD83C\uDFFB\u200D♂️", + [":man_vampire_tone2:"] = "\uD83E\uDDDB\uD83C\uDFFC\u200D♂️", + [":man_vampire_tone3:"] = "\uD83E\uDDDB\uD83C\uDFFD\u200D♂️", + [":man_vampire_tone4:"] = "\uD83E\uDDDB\uD83C\uDFFE\u200D♂️", + [":man_vampire_tone5:"] = "\uD83E\uDDDB\uD83C\uDFFF\u200D♂️", + [":man_walking:"] = "\uD83D\uDEB6\u200D♂️", + [":man_walking::skin-tone-1:"] = "\uD83D\uDEB6\uD83C\uDFFB\u200D♂️", + [":man_walking::skin-tone-2:"] = "\uD83D\uDEB6\uD83C\uDFFC\u200D♂️", + [":man_walking::skin-tone-3:"] = "\uD83D\uDEB6\uD83C\uDFFD\u200D♂️", + [":man_walking::skin-tone-4:"] = "\uD83D\uDEB6\uD83C\uDFFE\u200D♂️", + [":man_walking::skin-tone-5:"] = "\uD83D\uDEB6\uD83C\uDFFF\u200D♂️", + [":man_walking_dark_skin_tone:"] = "\uD83D\uDEB6\uD83C\uDFFF\u200D♂️", + [":man_walking_light_skin_tone:"] = "\uD83D\uDEB6\uD83C\uDFFB\u200D♂️", + [":man_walking_medium_dark_skin_tone:"] = "\uD83D\uDEB6\uD83C\uDFFE\u200D♂️", + [":man_walking_medium_light_skin_tone:"] = "\uD83D\uDEB6\uD83C\uDFFC\u200D♂️", + [":man_walking_medium_skin_tone:"] = "\uD83D\uDEB6\uD83C\uDFFD\u200D♂️", + [":man_walking_tone1:"] = "\uD83D\uDEB6\uD83C\uDFFB\u200D♂️", + [":man_walking_tone2:"] = "\uD83D\uDEB6\uD83C\uDFFC\u200D♂️", + [":man_walking_tone3:"] = "\uD83D\uDEB6\uD83C\uDFFD\u200D♂️", + [":man_walking_tone4:"] = "\uD83D\uDEB6\uD83C\uDFFE\u200D♂️", + [":man_walking_tone5:"] = "\uD83D\uDEB6\uD83C\uDFFF\u200D♂️", + [":man_wearing_turban:"] = "\uD83D\uDC73\u200D♂️", + [":man_wearing_turban::skin-tone-1:"] = "\uD83D\uDC73\uD83C\uDFFB\u200D♂️", + [":man_wearing_turban::skin-tone-2:"] = "\uD83D\uDC73\uD83C\uDFFC\u200D♂️", + [":man_wearing_turban::skin-tone-3:"] = "\uD83D\uDC73\uD83C\uDFFD\u200D♂️", + [":man_wearing_turban::skin-tone-4:"] = "\uD83D\uDC73\uD83C\uDFFE\u200D♂️", + [":man_wearing_turban::skin-tone-5:"] = "\uD83D\uDC73\uD83C\uDFFF\u200D♂️", + [":man_wearing_turban_dark_skin_tone:"] = "\uD83D\uDC73\uD83C\uDFFF\u200D♂️", + [":man_wearing_turban_light_skin_tone:"] = "\uD83D\uDC73\uD83C\uDFFB\u200D♂️", + [":man_wearing_turban_medium_dark_skin_tone:"] = "\uD83D\uDC73\uD83C\uDFFE\u200D♂️", + [":man_wearing_turban_medium_light_skin_tone:"] = "\uD83D\uDC73\uD83C\uDFFC\u200D♂️", + [":man_wearing_turban_medium_skin_tone:"] = "\uD83D\uDC73\uD83C\uDFFD\u200D♂️", + [":man_wearing_turban_tone1:"] = "\uD83D\uDC73\uD83C\uDFFB\u200D♂️", + [":man_wearing_turban_tone2:"] = "\uD83D\uDC73\uD83C\uDFFC\u200D♂️", + [":man_wearing_turban_tone3:"] = "\uD83D\uDC73\uD83C\uDFFD\u200D♂️", + [":man_wearing_turban_tone4:"] = "\uD83D\uDC73\uD83C\uDFFE\u200D♂️", + [":man_wearing_turban_tone5:"] = "\uD83D\uDC73\uD83C\uDFFF\u200D♂️", + [":man_white_haired:"] = "\uD83D\uDC68\u200D\uD83E\uDDB3", + [":man_white_haired::skin-tone-1:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83E\uDDB3", + [":man_white_haired::skin-tone-2:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83E\uDDB3", + [":man_white_haired::skin-tone-3:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83E\uDDB3", + [":man_white_haired::skin-tone-4:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83E\uDDB3", + [":man_white_haired::skin-tone-5:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83E\uDDB3", + [":man_white_haired_dark_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83E\uDDB3", + [":man_white_haired_light_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83E\uDDB3", + [":man_white_haired_medium_dark_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83E\uDDB3", + [":man_white_haired_medium_light_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83E\uDDB3", + [":man_white_haired_medium_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83E\uDDB3", + [":man_white_haired_tone1:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83E\uDDB3", + [":man_white_haired_tone2:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83E\uDDB3", + [":man_white_haired_tone3:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83E\uDDB3", + [":man_white_haired_tone4:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83E\uDDB3", + [":man_white_haired_tone5:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83E\uDDB3", + [":man_with_chinese_cap:"] = "\uD83D\uDC72", + [":man_with_chinese_cap::skin-tone-1:"] = "\uD83D\uDC72\uD83C\uDFFB", + [":man_with_chinese_cap::skin-tone-2:"] = "\uD83D\uDC72\uD83C\uDFFC", + [":man_with_chinese_cap::skin-tone-3:"] = "\uD83D\uDC72\uD83C\uDFFD", + [":man_with_chinese_cap::skin-tone-4:"] = "\uD83D\uDC72\uD83C\uDFFE", + [":man_with_chinese_cap::skin-tone-5:"] = "\uD83D\uDC72\uD83C\uDFFF", + [":man_with_chinese_cap_tone1:"] = "\uD83D\uDC72\uD83C\uDFFB", + [":man_with_chinese_cap_tone2:"] = "\uD83D\uDC72\uD83C\uDFFC", + [":man_with_chinese_cap_tone3:"] = "\uD83D\uDC72\uD83C\uDFFD", + [":man_with_chinese_cap_tone4:"] = "\uD83D\uDC72\uD83C\uDFFE", + [":man_with_chinese_cap_tone5:"] = "\uD83D\uDC72\uD83C\uDFFF", + [":man_with_gua_pi_mao:"] = "\uD83D\uDC72", + [":man_with_gua_pi_mao::skin-tone-1:"] = "\uD83D\uDC72\uD83C\uDFFB", + [":man_with_gua_pi_mao::skin-tone-2:"] = "\uD83D\uDC72\uD83C\uDFFC", + [":man_with_gua_pi_mao::skin-tone-3:"] = "\uD83D\uDC72\uD83C\uDFFD", + [":man_with_gua_pi_mao::skin-tone-4:"] = "\uD83D\uDC72\uD83C\uDFFE", + [":man_with_gua_pi_mao::skin-tone-5:"] = "\uD83D\uDC72\uD83C\uDFFF", + [":man_with_gua_pi_mao_tone1:"] = "\uD83D\uDC72\uD83C\uDFFB", + [":man_with_gua_pi_mao_tone2:"] = "\uD83D\uDC72\uD83C\uDFFC", + [":man_with_gua_pi_mao_tone3:"] = "\uD83D\uDC72\uD83C\uDFFD", + [":man_with_gua_pi_mao_tone4:"] = "\uD83D\uDC72\uD83C\uDFFE", + [":man_with_gua_pi_mao_tone5:"] = "\uD83D\uDC72\uD83C\uDFFF", + [":man_with_probing_cane:"] = "\uD83D\uDC68\u200D\uD83E\uDDAF", + [":man_with_probing_cane::skin-tone-1:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83E\uDDAF", + [":man_with_probing_cane::skin-tone-2:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83E\uDDAF", + [":man_with_probing_cane::skin-tone-3:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83E\uDDAF", + [":man_with_probing_cane::skin-tone-4:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83E\uDDAF", + [":man_with_probing_cane::skin-tone-5:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83E\uDDAF", + [":man_with_probing_cane_dark_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83E\uDDAF", + [":man_with_probing_cane_light_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83E\uDDAF", + [":man_with_probing_cane_medium_dark_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83E\uDDAF", + [":man_with_probing_cane_medium_light_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83E\uDDAF", + [":man_with_probing_cane_medium_skin_tone:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83E\uDDAF", + [":man_with_probing_cane_tone1:"] = "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83E\uDDAF", + [":man_with_probing_cane_tone2:"] = "\uD83D\uDC68\uD83C\uDFFC\u200D\uD83E\uDDAF", + [":man_with_probing_cane_tone3:"] = "\uD83D\uDC68\uD83C\uDFFD\u200D\uD83E\uDDAF", + [":man_with_probing_cane_tone4:"] = "\uD83D\uDC68\uD83C\uDFFE\u200D\uD83E\uDDAF", + [":man_with_probing_cane_tone5:"] = "\uD83D\uDC68\uD83C\uDFFF\u200D\uD83E\uDDAF", + [":man_with_turban:"] = "\uD83D\uDC73", + [":man_with_turban::skin-tone-1:"] = "\uD83D\uDC73\uD83C\uDFFB", + [":man_with_turban::skin-tone-2:"] = "\uD83D\uDC73\uD83C\uDFFC", + [":man_with_turban::skin-tone-3:"] = "\uD83D\uDC73\uD83C\uDFFD", + [":man_with_turban::skin-tone-4:"] = "\uD83D\uDC73\uD83C\uDFFE", + [":man_with_turban::skin-tone-5:"] = "\uD83D\uDC73\uD83C\uDFFF", + [":man_with_turban_tone1:"] = "\uD83D\uDC73\uD83C\uDFFB", + [":man_with_turban_tone2:"] = "\uD83D\uDC73\uD83C\uDFFC", + [":man_with_turban_tone3:"] = "\uD83D\uDC73\uD83C\uDFFD", + [":man_with_turban_tone4:"] = "\uD83D\uDC73\uD83C\uDFFE", + [":man_with_turban_tone5:"] = "\uD83D\uDC73\uD83C\uDFFF", + [":man_zombie:"] = "\uD83E\uDDDF\u200D♂️", + [":mango:"] = "\uD83E\uDD6D", + [":mans_shoe:"] = "\uD83D\uDC5E", + [":mantlepiece_clock:"] = "\uD83D\uDD70️", + [":manual_wheelchair:"] = "\uD83E\uDDBD", + [":map:"] = "\uD83D\uDDFA️", + [":maple_leaf:"] = "\uD83C\uDF41", + [":martial_arts_uniform:"] = "\uD83E\uDD4B", + [":mask:"] = "\uD83D\uDE37", + [":massage:"] = "\uD83D\uDC86", + [":massage::skin-tone-1:"] = "\uD83D\uDC86\uD83C\uDFFB", + [":massage::skin-tone-2:"] = "\uD83D\uDC86\uD83C\uDFFC", + [":massage::skin-tone-3:"] = "\uD83D\uDC86\uD83C\uDFFD", + [":massage::skin-tone-4:"] = "\uD83D\uDC86\uD83C\uDFFE", + [":massage::skin-tone-5:"] = "\uD83D\uDC86\uD83C\uDFFF", + [":massage_tone1:"] = "\uD83D\uDC86\uD83C\uDFFB", + [":massage_tone2:"] = "\uD83D\uDC86\uD83C\uDFFC", + [":massage_tone3:"] = "\uD83D\uDC86\uD83C\uDFFD", + [":massage_tone4:"] = "\uD83D\uDC86\uD83C\uDFFE", + [":massage_tone5:"] = "\uD83D\uDC86\uD83C\uDFFF", + [":mate:"] = "\uD83E\uDDC9", + [":meat_on_bone:"] = "\uD83C\uDF56", + [":mechanical_arm:"] = "\uD83E\uDDBE", + [":mechanical_leg:"] = "\uD83E\uDDBF", + [":medal:"] = "\uD83C\uDFC5", + [":medical_symbol:"] = "⚕️", + [":mega:"] = "\uD83D\uDCE3", + [":melon:"] = "\uD83C\uDF48", + [":memo:"] = "\uD83D\uDCDD", + [":men_with_bunny_ears_partying:"] = "\uD83D\uDC6F\u200D♂️", + [":men_wrestling:"] = "\uD83E\uDD3C\u200D♂️", + [":menorah:"] = "\uD83D\uDD4E", + [":mens:"] = "\uD83D\uDEB9", + [":mermaid:"] = "\uD83E\uDDDC\u200D♀️", + [":mermaid::skin-tone-1:"] = "\uD83E\uDDDC\uD83C\uDFFB\u200D♀️", + [":mermaid::skin-tone-2:"] = "\uD83E\uDDDC\uD83C\uDFFC\u200D♀️", + [":mermaid::skin-tone-3:"] = "\uD83E\uDDDC\uD83C\uDFFD\u200D♀️", + [":mermaid::skin-tone-4:"] = "\uD83E\uDDDC\uD83C\uDFFE\u200D♀️", + [":mermaid::skin-tone-5:"] = "\uD83E\uDDDC\uD83C\uDFFF\u200D♀️", + [":mermaid_dark_skin_tone:"] = "\uD83E\uDDDC\uD83C\uDFFF\u200D♀️", + [":mermaid_light_skin_tone:"] = "\uD83E\uDDDC\uD83C\uDFFB\u200D♀️", + [":mermaid_medium_dark_skin_tone:"] = "\uD83E\uDDDC\uD83C\uDFFE\u200D♀️", + [":mermaid_medium_light_skin_tone:"] = "\uD83E\uDDDC\uD83C\uDFFC\u200D♀️", + [":mermaid_medium_skin_tone:"] = "\uD83E\uDDDC\uD83C\uDFFD\u200D♀️", + [":mermaid_tone1:"] = "\uD83E\uDDDC\uD83C\uDFFB\u200D♀️", + [":mermaid_tone2:"] = "\uD83E\uDDDC\uD83C\uDFFC\u200D♀️", + [":mermaid_tone3:"] = "\uD83E\uDDDC\uD83C\uDFFD\u200D♀️", + [":mermaid_tone4:"] = "\uD83E\uDDDC\uD83C\uDFFE\u200D♀️", + [":mermaid_tone5:"] = "\uD83E\uDDDC\uD83C\uDFFF\u200D♀️", + [":merman:"] = "\uD83E\uDDDC\u200D♂️", + [":merman::skin-tone-1:"] = "\uD83E\uDDDC\uD83C\uDFFB\u200D♂️", + [":merman::skin-tone-2:"] = "\uD83E\uDDDC\uD83C\uDFFC\u200D♂️", + [":merman::skin-tone-3:"] = "\uD83E\uDDDC\uD83C\uDFFD\u200D♂️", + [":merman::skin-tone-4:"] = "\uD83E\uDDDC\uD83C\uDFFE\u200D♂️", + [":merman::skin-tone-5:"] = "\uD83E\uDDDC\uD83C\uDFFF\u200D♂️", + [":merman_dark_skin_tone:"] = "\uD83E\uDDDC\uD83C\uDFFF\u200D♂️", + [":merman_light_skin_tone:"] = "\uD83E\uDDDC\uD83C\uDFFB\u200D♂️", + [":merman_medium_dark_skin_tone:"] = "\uD83E\uDDDC\uD83C\uDFFE\u200D♂️", + [":merman_medium_light_skin_tone:"] = "\uD83E\uDDDC\uD83C\uDFFC\u200D♂️", + [":merman_medium_skin_tone:"] = "\uD83E\uDDDC\uD83C\uDFFD\u200D♂️", + [":merman_tone1:"] = "\uD83E\uDDDC\uD83C\uDFFB\u200D♂️", + [":merman_tone2:"] = "\uD83E\uDDDC\uD83C\uDFFC\u200D♂️", + [":merman_tone3:"] = "\uD83E\uDDDC\uD83C\uDFFD\u200D♂️", + [":merman_tone4:"] = "\uD83E\uDDDC\uD83C\uDFFE\u200D♂️", + [":merman_tone5:"] = "\uD83E\uDDDC\uD83C\uDFFF\u200D♂️", + [":merperson:"] = "\uD83E\uDDDC", + [":merperson::skin-tone-1:"] = "\uD83E\uDDDC\uD83C\uDFFB", + [":merperson::skin-tone-2:"] = "\uD83E\uDDDC\uD83C\uDFFC", + [":merperson::skin-tone-3:"] = "\uD83E\uDDDC\uD83C\uDFFD", + [":merperson::skin-tone-4:"] = "\uD83E\uDDDC\uD83C\uDFFE", + [":merperson::skin-tone-5:"] = "\uD83E\uDDDC\uD83C\uDFFF", + [":merperson_dark_skin_tone:"] = "\uD83E\uDDDC\uD83C\uDFFF", + [":merperson_light_skin_tone:"] = "\uD83E\uDDDC\uD83C\uDFFB", + [":merperson_medium_dark_skin_tone:"] = "\uD83E\uDDDC\uD83C\uDFFE", + [":merperson_medium_light_skin_tone:"] = "\uD83E\uDDDC\uD83C\uDFFC", + [":merperson_medium_skin_tone:"] = "\uD83E\uDDDC\uD83C\uDFFD", + [":merperson_tone1:"] = "\uD83E\uDDDC\uD83C\uDFFB", + [":merperson_tone2:"] = "\uD83E\uDDDC\uD83C\uDFFC", + [":merperson_tone3:"] = "\uD83E\uDDDC\uD83C\uDFFD", + [":merperson_tone4:"] = "\uD83E\uDDDC\uD83C\uDFFE", + [":merperson_tone5:"] = "\uD83E\uDDDC\uD83C\uDFFF", + [":metal:"] = "\uD83E\uDD18", + [":metal::skin-tone-1:"] = "\uD83E\uDD18\uD83C\uDFFB", + [":metal::skin-tone-2:"] = "\uD83E\uDD18\uD83C\uDFFC", + [":metal::skin-tone-3:"] = "\uD83E\uDD18\uD83C\uDFFD", + [":metal::skin-tone-4:"] = "\uD83E\uDD18\uD83C\uDFFE", + [":metal::skin-tone-5:"] = "\uD83E\uDD18\uD83C\uDFFF", + [":metal_tone1:"] = "\uD83E\uDD18\uD83C\uDFFB", + [":metal_tone2:"] = "\uD83E\uDD18\uD83C\uDFFC", + [":metal_tone3:"] = "\uD83E\uDD18\uD83C\uDFFD", + [":metal_tone4:"] = "\uD83E\uDD18\uD83C\uDFFE", + [":metal_tone5:"] = "\uD83E\uDD18\uD83C\uDFFF", + [":metro:"] = "\uD83D\uDE87", + [":microbe:"] = "\uD83E\uDDA0", + [":microphone2:"] = "\uD83C\uDF99️", + [":microphone:"] = "\uD83C\uDFA4", + [":microscope:"] = "\uD83D\uDD2C", + [":middle_finger:"] = "\uD83D\uDD95", + [":middle_finger::skin-tone-1:"] = "\uD83D\uDD95\uD83C\uDFFB", + [":middle_finger::skin-tone-2:"] = "\uD83D\uDD95\uD83C\uDFFC", + [":middle_finger::skin-tone-3:"] = "\uD83D\uDD95\uD83C\uDFFD", + [":middle_finger::skin-tone-4:"] = "\uD83D\uDD95\uD83C\uDFFE", + [":middle_finger::skin-tone-5:"] = "\uD83D\uDD95\uD83C\uDFFF", + [":middle_finger_tone1:"] = "\uD83D\uDD95\uD83C\uDFFB", + [":middle_finger_tone2:"] = "\uD83D\uDD95\uD83C\uDFFC", + [":middle_finger_tone3:"] = "\uD83D\uDD95\uD83C\uDFFD", + [":middle_finger_tone4:"] = "\uD83D\uDD95\uD83C\uDFFE", + [":middle_finger_tone5:"] = "\uD83D\uDD95\uD83C\uDFFF", + [":military_medal:"] = "\uD83C\uDF96️", + [":milk:"] = "\uD83E\uDD5B", + [":milky_way:"] = "\uD83C\uDF0C", + [":minibus:"] = "\uD83D\uDE90", + [":minidisc:"] = "\uD83D\uDCBD", + [":mobile_phone_off:"] = "\uD83D\uDCF4", + [":money_mouth:"] = "\uD83E\uDD11", + [":money_mouth_face:"] = "\uD83E\uDD11", + [":money_with_wings:"] = "\uD83D\uDCB8", + [":moneybag:"] = "\uD83D\uDCB0", + [":monkey:"] = "\uD83D\uDC12", + [":monkey_face:"] = "\uD83D\uDC35", + [":monorail:"] = "\uD83D\uDE9D", + [":moon_cake:"] = "\uD83E\uDD6E", + [":mortar_board:"] = "\uD83C\uDF93", + [":mosque:"] = "\uD83D\uDD4C", + [":mosquito:"] = "\uD83E\uDD9F", + [":mother_christmas:"] = "\uD83E\uDD36", + [":mother_christmas::skin-tone-1:"] = "\uD83E\uDD36\uD83C\uDFFB", + [":mother_christmas::skin-tone-2:"] = "\uD83E\uDD36\uD83C\uDFFC", + [":mother_christmas::skin-tone-3:"] = "\uD83E\uDD36\uD83C\uDFFD", + [":mother_christmas::skin-tone-4:"] = "\uD83E\uDD36\uD83C\uDFFE", + [":mother_christmas::skin-tone-5:"] = "\uD83E\uDD36\uD83C\uDFFF", + [":mother_christmas_tone1:"] = "\uD83E\uDD36\uD83C\uDFFB", + [":mother_christmas_tone2:"] = "\uD83E\uDD36\uD83C\uDFFC", + [":mother_christmas_tone3:"] = "\uD83E\uDD36\uD83C\uDFFD", + [":mother_christmas_tone4:"] = "\uD83E\uDD36\uD83C\uDFFE", + [":mother_christmas_tone5:"] = "\uD83E\uDD36\uD83C\uDFFF", + [":motor_scooter:"] = "\uD83D\uDEF5", + [":motorbike:"] = "\uD83D\uDEF5", + [":motorboat:"] = "\uD83D\uDEE5️", + [":motorcycle:"] = "\uD83C\uDFCD️", + [":motorized_wheelchair:"] = "\uD83E\uDDBC", + [":motorway:"] = "\uD83D\uDEE3️", + [":mount_fuji:"] = "\uD83D\uDDFB", + [":mountain:"] = "⛰️", + [":mountain_bicyclist:"] = "\uD83D\uDEB5", + [":mountain_bicyclist::skin-tone-1:"] = "\uD83D\uDEB5\uD83C\uDFFB", + [":mountain_bicyclist::skin-tone-2:"] = "\uD83D\uDEB5\uD83C\uDFFC", + [":mountain_bicyclist::skin-tone-3:"] = "\uD83D\uDEB5\uD83C\uDFFD", + [":mountain_bicyclist::skin-tone-4:"] = "\uD83D\uDEB5\uD83C\uDFFE", + [":mountain_bicyclist::skin-tone-5:"] = "\uD83D\uDEB5\uD83C\uDFFF", + [":mountain_bicyclist_tone1:"] = "\uD83D\uDEB5\uD83C\uDFFB", + [":mountain_bicyclist_tone2:"] = "\uD83D\uDEB5\uD83C\uDFFC", + [":mountain_bicyclist_tone3:"] = "\uD83D\uDEB5\uD83C\uDFFD", + [":mountain_bicyclist_tone4:"] = "\uD83D\uDEB5\uD83C\uDFFE", + [":mountain_bicyclist_tone5:"] = "\uD83D\uDEB5\uD83C\uDFFF", + [":mountain_cableway:"] = "\uD83D\uDEA0", + [":mountain_railway:"] = "\uD83D\uDE9E", + [":mountain_snow:"] = "\uD83C\uDFD4️", + [":mouse2:"] = "\uD83D\uDC01", + [":mouse:"] = "\uD83D\uDC2D", + [":mouse_three_button:"] = "\uD83D\uDDB1️", + [":movie_camera:"] = "\uD83C\uDFA5", + [":moyai:"] = "\uD83D\uDDFF", + [":mrs_claus:"] = "\uD83E\uDD36", + [":mrs_claus::skin-tone-1:"] = "\uD83E\uDD36\uD83C\uDFFB", + [":mrs_claus::skin-tone-2:"] = "\uD83E\uDD36\uD83C\uDFFC", + [":mrs_claus::skin-tone-3:"] = "\uD83E\uDD36\uD83C\uDFFD", + [":mrs_claus::skin-tone-4:"] = "\uD83E\uDD36\uD83C\uDFFE", + [":mrs_claus::skin-tone-5:"] = "\uD83E\uDD36\uD83C\uDFFF", + [":mrs_claus_tone1:"] = "\uD83E\uDD36\uD83C\uDFFB", + [":mrs_claus_tone2:"] = "\uD83E\uDD36\uD83C\uDFFC", + [":mrs_claus_tone3:"] = "\uD83E\uDD36\uD83C\uDFFD", + [":mrs_claus_tone4:"] = "\uD83E\uDD36\uD83C\uDFFE", + [":mrs_claus_tone5:"] = "\uD83E\uDD36\uD83C\uDFFF", + [":muscle:"] = "\uD83D\uDCAA", + [":muscle::skin-tone-1:"] = "\uD83D\uDCAA\uD83C\uDFFB", + [":muscle::skin-tone-2:"] = "\uD83D\uDCAA\uD83C\uDFFC", + [":muscle::skin-tone-3:"] = "\uD83D\uDCAA\uD83C\uDFFD", + [":muscle::skin-tone-4:"] = "\uD83D\uDCAA\uD83C\uDFFE", + [":muscle::skin-tone-5:"] = "\uD83D\uDCAA\uD83C\uDFFF", + [":muscle_tone1:"] = "\uD83D\uDCAA\uD83C\uDFFB", + [":muscle_tone2:"] = "\uD83D\uDCAA\uD83C\uDFFC", + [":muscle_tone3:"] = "\uD83D\uDCAA\uD83C\uDFFD", + [":muscle_tone4:"] = "\uD83D\uDCAA\uD83C\uDFFE", + [":muscle_tone5:"] = "\uD83D\uDCAA\uD83C\uDFFF", + [":mushroom:"] = "\uD83C\uDF44", + [":musical_keyboard:"] = "\uD83C\uDFB9", + [":musical_note:"] = "\uD83C\uDFB5", + [":musical_score:"] = "\uD83C\uDFBC", + [":mute:"] = "\uD83D\uDD07", + [":nail_care:"] = "\uD83D\uDC85", + [":nail_care::skin-tone-1:"] = "\uD83D\uDC85\uD83C\uDFFB", + [":nail_care::skin-tone-2:"] = "\uD83D\uDC85\uD83C\uDFFC", + [":nail_care::skin-tone-3:"] = "\uD83D\uDC85\uD83C\uDFFD", + [":nail_care::skin-tone-4:"] = "\uD83D\uDC85\uD83C\uDFFE", + [":nail_care::skin-tone-5:"] = "\uD83D\uDC85\uD83C\uDFFF", + [":nail_care_tone1:"] = "\uD83D\uDC85\uD83C\uDFFB", + [":nail_care_tone2:"] = "\uD83D\uDC85\uD83C\uDFFC", + [":nail_care_tone3:"] = "\uD83D\uDC85\uD83C\uDFFD", + [":nail_care_tone4:"] = "\uD83D\uDC85\uD83C\uDFFE", + [":nail_care_tone5:"] = "\uD83D\uDC85\uD83C\uDFFF", + [":name_badge:"] = "\uD83D\uDCDB", + [":national_park:"] = "\uD83C\uDFDE️", + [":nauseated_face:"] = "\uD83E\uDD22", + [":nazar_amulet:"] = "\uD83E\uDDFF", + [":necktie:"] = "\uD83D\uDC54", + [":negative_squared_cross_mark:"] = "❎", + [":nerd:"] = "\uD83E\uDD13", + [":nerd_face:"] = "\uD83E\uDD13", + [":neutral_face:"] = "\uD83D\uDE10", + [":new:"] = "\uD83C\uDD95", + [":new_moon:"] = "\uD83C\uDF11", + [":new_moon_with_face:"] = "\uD83C\uDF1A", + [":newspaper2:"] = "\uD83D\uDDDE️", + [":newspaper:"] = "\uD83D\uDCF0", + [":next_track:"] = "⏭️", + [":ng:"] = "\uD83C\uDD96", + [":night_with_stars:"] = "\uD83C\uDF03", + [":nine:"] = "9️⃣", + [":no_bell:"] = "\uD83D\uDD15", + [":no_bicycles:"] = "\uD83D\uDEB3", + [":no_entry:"] = "⛔", + [":no_entry_sign:"] = "\uD83D\uDEAB", + [":no_good:"] = "\uD83D\uDE45", + [":no_good::skin-tone-1:"] = "\uD83D\uDE45\uD83C\uDFFB", + [":no_good::skin-tone-2:"] = "\uD83D\uDE45\uD83C\uDFFC", + [":no_good::skin-tone-3:"] = "\uD83D\uDE45\uD83C\uDFFD", + [":no_good::skin-tone-4:"] = "\uD83D\uDE45\uD83C\uDFFE", + [":no_good::skin-tone-5:"] = "\uD83D\uDE45\uD83C\uDFFF", + [":no_good_tone1:"] = "\uD83D\uDE45\uD83C\uDFFB", + [":no_good_tone2:"] = "\uD83D\uDE45\uD83C\uDFFC", + [":no_good_tone3:"] = "\uD83D\uDE45\uD83C\uDFFD", + [":no_good_tone4:"] = "\uD83D\uDE45\uD83C\uDFFE", + [":no_good_tone5:"] = "\uD83D\uDE45\uD83C\uDFFF", + [":no_mobile_phones:"] = "\uD83D\uDCF5", + [":no_mouth:"] = "\uD83D\uDE36", + [":no_pedestrians:"] = "\uD83D\uDEB7", + [":no_smoking:"] = "\uD83D\uDEAD", + [":non_potable_water:"] = "\uD83D\uDEB1", + [":nose:"] = "\uD83D\uDC43", + [":nose::skin-tone-1:"] = "\uD83D\uDC43\uD83C\uDFFB", + [":nose::skin-tone-2:"] = "\uD83D\uDC43\uD83C\uDFFC", + [":nose::skin-tone-3:"] = "\uD83D\uDC43\uD83C\uDFFD", + [":nose::skin-tone-4:"] = "\uD83D\uDC43\uD83C\uDFFE", + [":nose::skin-tone-5:"] = "\uD83D\uDC43\uD83C\uDFFF", + [":nose_tone1:"] = "\uD83D\uDC43\uD83C\uDFFB", + [":nose_tone2:"] = "\uD83D\uDC43\uD83C\uDFFC", + [":nose_tone3:"] = "\uD83D\uDC43\uD83C\uDFFD", + [":nose_tone4:"] = "\uD83D\uDC43\uD83C\uDFFE", + [":nose_tone5:"] = "\uD83D\uDC43\uD83C\uDFFF", + [":notebook:"] = "\uD83D\uDCD3", + [":notebook_with_decorative_cover:"] = "\uD83D\uDCD4", + [":notepad_spiral:"] = "\uD83D\uDDD2️", + [":notes:"] = "\uD83C\uDFB6", + [":nut_and_bolt:"] = "\uD83D\uDD29", + [":o"] = "\uD83D\uDE2E", + [":o2:"] = "\uD83C\uDD7E️", + [":o:"] = "⭕", + [":ocean:"] = "\uD83C\uDF0A", + [":octagonal_sign:"] = "\uD83D\uDED1", + [":octopus:"] = "\uD83D\uDC19", + [":oden:"] = "\uD83C\uDF62", + [":office:"] = "\uD83C\uDFE2", + [":oil:"] = "\uD83D\uDEE2️", + [":oil_drum:"] = "\uD83D\uDEE2️", + [":ok:"] = "\uD83C\uDD97", + [":ok_hand:"] = "\uD83D\uDC4C", + [":ok_hand::skin-tone-1:"] = "\uD83D\uDC4C\uD83C\uDFFB", + [":ok_hand::skin-tone-2:"] = "\uD83D\uDC4C\uD83C\uDFFC", + [":ok_hand::skin-tone-3:"] = "\uD83D\uDC4C\uD83C\uDFFD", + [":ok_hand::skin-tone-4:"] = "\uD83D\uDC4C\uD83C\uDFFE", + [":ok_hand::skin-tone-5:"] = "\uD83D\uDC4C\uD83C\uDFFF", + [":ok_hand_tone1:"] = "\uD83D\uDC4C\uD83C\uDFFB", + [":ok_hand_tone2:"] = "\uD83D\uDC4C\uD83C\uDFFC", + [":ok_hand_tone3:"] = "\uD83D\uDC4C\uD83C\uDFFD", + [":ok_hand_tone4:"] = "\uD83D\uDC4C\uD83C\uDFFE", + [":ok_hand_tone5:"] = "\uD83D\uDC4C\uD83C\uDFFF", + [":ok_woman:"] = "\uD83D\uDE46", + [":ok_woman::skin-tone-1:"] = "\uD83D\uDE46\uD83C\uDFFB", + [":ok_woman::skin-tone-2:"] = "\uD83D\uDE46\uD83C\uDFFC", + [":ok_woman::skin-tone-3:"] = "\uD83D\uDE46\uD83C\uDFFD", + [":ok_woman::skin-tone-4:"] = "\uD83D\uDE46\uD83C\uDFFE", + [":ok_woman::skin-tone-5:"] = "\uD83D\uDE46\uD83C\uDFFF", + [":ok_woman_tone1:"] = "\uD83D\uDE46\uD83C\uDFFB", + [":ok_woman_tone2:"] = "\uD83D\uDE46\uD83C\uDFFC", + [":ok_woman_tone3:"] = "\uD83D\uDE46\uD83C\uDFFD", + [":ok_woman_tone4:"] = "\uD83D\uDE46\uD83C\uDFFE", + [":ok_woman_tone5:"] = "\uD83D\uDE46\uD83C\uDFFF", + [":old_key:"] = "\uD83D\uDDDD️", + [":older_adult:"] = "\uD83E\uDDD3", + [":older_adult::skin-tone-1:"] = "\uD83E\uDDD3\uD83C\uDFFB", + [":older_adult::skin-tone-2:"] = "\uD83E\uDDD3\uD83C\uDFFC", + [":older_adult::skin-tone-3:"] = "\uD83E\uDDD3\uD83C\uDFFD", + [":older_adult::skin-tone-4:"] = "\uD83E\uDDD3\uD83C\uDFFE", + [":older_adult::skin-tone-5:"] = "\uD83E\uDDD3\uD83C\uDFFF", + [":older_adult_dark_skin_tone:"] = "\uD83E\uDDD3\uD83C\uDFFF", + [":older_adult_light_skin_tone:"] = "\uD83E\uDDD3\uD83C\uDFFB", + [":older_adult_medium_dark_skin_tone:"] = "\uD83E\uDDD3\uD83C\uDFFE", + [":older_adult_medium_light_skin_tone:"] = "\uD83E\uDDD3\uD83C\uDFFC", + [":older_adult_medium_skin_tone:"] = "\uD83E\uDDD3\uD83C\uDFFD", + [":older_adult_tone1:"] = "\uD83E\uDDD3\uD83C\uDFFB", + [":older_adult_tone2:"] = "\uD83E\uDDD3\uD83C\uDFFC", + [":older_adult_tone3:"] = "\uD83E\uDDD3\uD83C\uDFFD", + [":older_adult_tone4:"] = "\uD83E\uDDD3\uD83C\uDFFE", + [":older_adult_tone5:"] = "\uD83E\uDDD3\uD83C\uDFFF", + [":older_man:"] = "\uD83D\uDC74", + [":older_man::skin-tone-1:"] = "\uD83D\uDC74\uD83C\uDFFB", + [":older_man::skin-tone-2:"] = "\uD83D\uDC74\uD83C\uDFFC", + [":older_man::skin-tone-3:"] = "\uD83D\uDC74\uD83C\uDFFD", + [":older_man::skin-tone-4:"] = "\uD83D\uDC74\uD83C\uDFFE", + [":older_man::skin-tone-5:"] = "\uD83D\uDC74\uD83C\uDFFF", + [":older_man_tone1:"] = "\uD83D\uDC74\uD83C\uDFFB", + [":older_man_tone2:"] = "\uD83D\uDC74\uD83C\uDFFC", + [":older_man_tone3:"] = "\uD83D\uDC74\uD83C\uDFFD", + [":older_man_tone4:"] = "\uD83D\uDC74\uD83C\uDFFE", + [":older_man_tone5:"] = "\uD83D\uDC74\uD83C\uDFFF", + [":older_woman:"] = "\uD83D\uDC75", + [":older_woman::skin-tone-1:"] = "\uD83D\uDC75\uD83C\uDFFB", + [":older_woman::skin-tone-2:"] = "\uD83D\uDC75\uD83C\uDFFC", + [":older_woman::skin-tone-3:"] = "\uD83D\uDC75\uD83C\uDFFD", + [":older_woman::skin-tone-4:"] = "\uD83D\uDC75\uD83C\uDFFE", + [":older_woman::skin-tone-5:"] = "\uD83D\uDC75\uD83C\uDFFF", + [":older_woman_tone1:"] = "\uD83D\uDC75\uD83C\uDFFB", + [":older_woman_tone2:"] = "\uD83D\uDC75\uD83C\uDFFC", + [":older_woman_tone3:"] = "\uD83D\uDC75\uD83C\uDFFD", + [":older_woman_tone4:"] = "\uD83D\uDC75\uD83C\uDFFE", + [":older_woman_tone5:"] = "\uD83D\uDC75\uD83C\uDFFF", + [":om_symbol:"] = "\uD83D\uDD49️", + [":on:"] = "\uD83D\uDD1B", + [":oncoming_automobile:"] = "\uD83D\uDE98", + [":oncoming_bus:"] = "\uD83D\uDE8D", + [":oncoming_police_car:"] = "\uD83D\uDE94", + [":oncoming_taxi:"] = "\uD83D\uDE96", + [":one:"] = "1️⃣", + [":one_piece_swimsuit:"] = "\uD83E\uDE71", + [":onion:"] = "\uD83E\uDDC5", + [":open_file_folder:"] = "\uD83D\uDCC2", + [":open_hands:"] = "\uD83D\uDC50", + [":open_hands::skin-tone-1:"] = "\uD83D\uDC50\uD83C\uDFFB", + [":open_hands::skin-tone-2:"] = "\uD83D\uDC50\uD83C\uDFFC", + [":open_hands::skin-tone-3:"] = "\uD83D\uDC50\uD83C\uDFFD", + [":open_hands::skin-tone-4:"] = "\uD83D\uDC50\uD83C\uDFFE", + [":open_hands::skin-tone-5:"] = "\uD83D\uDC50\uD83C\uDFFF", + [":open_hands_tone1:"] = "\uD83D\uDC50\uD83C\uDFFB", + [":open_hands_tone2:"] = "\uD83D\uDC50\uD83C\uDFFC", + [":open_hands_tone3:"] = "\uD83D\uDC50\uD83C\uDFFD", + [":open_hands_tone4:"] = "\uD83D\uDC50\uD83C\uDFFE", + [":open_hands_tone5:"] = "\uD83D\uDC50\uD83C\uDFFF", + [":open_mouth:"] = "\uD83D\uDE2E", + [":ophiuchus:"] = "⛎", + [":orange_book:"] = "\uD83D\uDCD9", + [":orange_circle:"] = "\uD83D\uDFE0", + [":orange_heart:"] = "\uD83E\uDDE1", + [":orange_square:"] = "\uD83D\uDFE7", + [":orangutan:"] = "\uD83E\uDDA7", + [":orthodox_cross:"] = "☦️", + [":otter:"] = "\uD83E\uDDA6", + [":outbox_tray:"] = "\uD83D\uDCE4", + [":owl:"] = "\uD83E\uDD89", + [":ox:"] = "\uD83D\uDC02", + [":oyster:"] = "\uD83E\uDDAA", + [":package:"] = "\uD83D\uDCE6", + [":paella:"] = "\uD83E\uDD58", + [":page_facing_up:"] = "\uD83D\uDCC4", + [":page_with_curl:"] = "\uD83D\uDCC3", + [":pager:"] = "\uD83D\uDCDF", + [":paintbrush:"] = "\uD83D\uDD8C️", + [":palm_tree:"] = "\uD83C\uDF34", + [":palms_up_together:"] = "\uD83E\uDD32", + [":palms_up_together::skin-tone-1:"] = "\uD83E\uDD32\uD83C\uDFFB", + [":palms_up_together::skin-tone-2:"] = "\uD83E\uDD32\uD83C\uDFFC", + [":palms_up_together::skin-tone-3:"] = "\uD83E\uDD32\uD83C\uDFFD", + [":palms_up_together::skin-tone-4:"] = "\uD83E\uDD32\uD83C\uDFFE", + [":palms_up_together::skin-tone-5:"] = "\uD83E\uDD32\uD83C\uDFFF", + [":palms_up_together_dark_skin_tone:"] = "\uD83E\uDD32\uD83C\uDFFF", + [":palms_up_together_light_skin_tone:"] = "\uD83E\uDD32\uD83C\uDFFB", + [":palms_up_together_medium_dark_skin_tone:"] = "\uD83E\uDD32\uD83C\uDFFE", + [":palms_up_together_medium_light_skin_tone:"] = "\uD83E\uDD32\uD83C\uDFFC", + [":palms_up_together_medium_skin_tone:"] = "\uD83E\uDD32\uD83C\uDFFD", + [":palms_up_together_tone1:"] = "\uD83E\uDD32\uD83C\uDFFB", + [":palms_up_together_tone2:"] = "\uD83E\uDD32\uD83C\uDFFC", + [":palms_up_together_tone3:"] = "\uD83E\uDD32\uD83C\uDFFD", + [":palms_up_together_tone4:"] = "\uD83E\uDD32\uD83C\uDFFE", + [":palms_up_together_tone5:"] = "\uD83E\uDD32\uD83C\uDFFF", + [":pancakes:"] = "\uD83E\uDD5E", + [":panda_face:"] = "\uD83D\uDC3C", + [":paperclip:"] = "\uD83D\uDCCE", + [":paperclips:"] = "\uD83D\uDD87️", + [":parachute:"] = "\uD83E\uDE82", + [":park:"] = "\uD83C\uDFDE️", + [":parking:"] = "\uD83C\uDD7F️", + [":parrot:"] = "\uD83E\uDD9C", + [":part_alternation_mark:"] = "〽️", + [":partly_sunny:"] = "⛅", + [":partying_face:"] = "\uD83E\uDD73", + [":passenger_ship:"] = "\uD83D\uDEF3️", + [":passport_control:"] = "\uD83D\uDEC2", + [":pause_button:"] = "⏸️", + [":paw_prints:"] = "\uD83D\uDC3E", + [":peace:"] = "☮️", + [":peace_symbol:"] = "☮️", + [":peach:"] = "\uD83C\uDF51", + [":peacock:"] = "\uD83E\uDD9A", + [":peanuts:"] = "\uD83E\uDD5C", + [":pear:"] = "\uD83C\uDF50", + [":pen_ballpoint:"] = "\uD83D\uDD8A️", + [":pen_fountain:"] = "\uD83D\uDD8B️", + [":pencil2:"] = "✏️", + [":pencil:"] = "\uD83D\uDCDD", + [":penguin:"] = "\uD83D\uDC27", + [":pensive:"] = "\uD83D\uDE14", + [":people_holding_hands:"] = "\uD83E\uDDD1\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1", + [":people_with_bunny_ears_partying:"] = "\uD83D\uDC6F", + [":people_wrestling:"] = "\uD83E\uDD3C", + [":performing_arts:"] = "\uD83C\uDFAD", + [":persevere:"] = "\uD83D\uDE23", + [":person_biking:"] = "\uD83D\uDEB4", + [":person_biking::skin-tone-1:"] = "\uD83D\uDEB4\uD83C\uDFFB", + [":person_biking::skin-tone-2:"] = "\uD83D\uDEB4\uD83C\uDFFC", + [":person_biking::skin-tone-3:"] = "\uD83D\uDEB4\uD83C\uDFFD", + [":person_biking::skin-tone-4:"] = "\uD83D\uDEB4\uD83C\uDFFE", + [":person_biking::skin-tone-5:"] = "\uD83D\uDEB4\uD83C\uDFFF", + [":person_biking_tone1:"] = "\uD83D\uDEB4\uD83C\uDFFB", + [":person_biking_tone2:"] = "\uD83D\uDEB4\uD83C\uDFFC", + [":person_biking_tone3:"] = "\uD83D\uDEB4\uD83C\uDFFD", + [":person_biking_tone4:"] = "\uD83D\uDEB4\uD83C\uDFFE", + [":person_biking_tone5:"] = "\uD83D\uDEB4\uD83C\uDFFF", + [":person_bouncing_ball:"] = "⛹️", + [":person_bouncing_ball::skin-tone-1:"] = "⛹\uD83C\uDFFB", + [":person_bouncing_ball::skin-tone-2:"] = "⛹\uD83C\uDFFC", + [":person_bouncing_ball::skin-tone-3:"] = "⛹\uD83C\uDFFD", + [":person_bouncing_ball::skin-tone-4:"] = "⛹\uD83C\uDFFE", + [":person_bouncing_ball::skin-tone-5:"] = "⛹\uD83C\uDFFF", + [":person_bouncing_ball_tone1:"] = "⛹\uD83C\uDFFB", + [":person_bouncing_ball_tone2:"] = "⛹\uD83C\uDFFC", + [":person_bouncing_ball_tone3:"] = "⛹\uD83C\uDFFD", + [":person_bouncing_ball_tone4:"] = "⛹\uD83C\uDFFE", + [":person_bouncing_ball_tone5:"] = "⛹\uD83C\uDFFF", + [":person_bowing:"] = "\uD83D\uDE47", + [":person_bowing::skin-tone-1:"] = "\uD83D\uDE47\uD83C\uDFFB", + [":person_bowing::skin-tone-2:"] = "\uD83D\uDE47\uD83C\uDFFC", + [":person_bowing::skin-tone-3:"] = "\uD83D\uDE47\uD83C\uDFFD", + [":person_bowing::skin-tone-4:"] = "\uD83D\uDE47\uD83C\uDFFE", + [":person_bowing::skin-tone-5:"] = "\uD83D\uDE47\uD83C\uDFFF", + [":person_bowing_tone1:"] = "\uD83D\uDE47\uD83C\uDFFB", + [":person_bowing_tone2:"] = "\uD83D\uDE47\uD83C\uDFFC", + [":person_bowing_tone3:"] = "\uD83D\uDE47\uD83C\uDFFD", + [":person_bowing_tone4:"] = "\uD83D\uDE47\uD83C\uDFFE", + [":person_bowing_tone5:"] = "\uD83D\uDE47\uD83C\uDFFF", + [":person_climbing:"] = "\uD83E\uDDD7", + [":person_climbing::skin-tone-1:"] = "\uD83E\uDDD7\uD83C\uDFFB", + [":person_climbing::skin-tone-2:"] = "\uD83E\uDDD7\uD83C\uDFFC", + [":person_climbing::skin-tone-3:"] = "\uD83E\uDDD7\uD83C\uDFFD", + [":person_climbing::skin-tone-4:"] = "\uD83E\uDDD7\uD83C\uDFFE", + [":person_climbing::skin-tone-5:"] = "\uD83E\uDDD7\uD83C\uDFFF", + [":person_climbing_dark_skin_tone:"] = "\uD83E\uDDD7\uD83C\uDFFF", + [":person_climbing_light_skin_tone:"] = "\uD83E\uDDD7\uD83C\uDFFB", + [":person_climbing_medium_dark_skin_tone:"] = "\uD83E\uDDD7\uD83C\uDFFE", + [":person_climbing_medium_light_skin_tone:"] = "\uD83E\uDDD7\uD83C\uDFFC", + [":person_climbing_medium_skin_tone:"] = "\uD83E\uDDD7\uD83C\uDFFD", + [":person_climbing_tone1:"] = "\uD83E\uDDD7\uD83C\uDFFB", + [":person_climbing_tone2:"] = "\uD83E\uDDD7\uD83C\uDFFC", + [":person_climbing_tone3:"] = "\uD83E\uDDD7\uD83C\uDFFD", + [":person_climbing_tone4:"] = "\uD83E\uDDD7\uD83C\uDFFE", + [":person_climbing_tone5:"] = "\uD83E\uDDD7\uD83C\uDFFF", + [":person_doing_cartwheel:"] = "\uD83E\uDD38", + [":person_doing_cartwheel::skin-tone-1:"] = "\uD83E\uDD38\uD83C\uDFFB", + [":person_doing_cartwheel::skin-tone-2:"] = "\uD83E\uDD38\uD83C\uDFFC", + [":person_doing_cartwheel::skin-tone-3:"] = "\uD83E\uDD38\uD83C\uDFFD", + [":person_doing_cartwheel::skin-tone-4:"] = "\uD83E\uDD38\uD83C\uDFFE", + [":person_doing_cartwheel::skin-tone-5:"] = "\uD83E\uDD38\uD83C\uDFFF", + [":person_doing_cartwheel_tone1:"] = "\uD83E\uDD38\uD83C\uDFFB", + [":person_doing_cartwheel_tone2:"] = "\uD83E\uDD38\uD83C\uDFFC", + [":person_doing_cartwheel_tone3:"] = "\uD83E\uDD38\uD83C\uDFFD", + [":person_doing_cartwheel_tone4:"] = "\uD83E\uDD38\uD83C\uDFFE", + [":person_doing_cartwheel_tone5:"] = "\uD83E\uDD38\uD83C\uDFFF", + [":person_facepalming:"] = "\uD83E\uDD26", + [":person_facepalming::skin-tone-1:"] = "\uD83E\uDD26\uD83C\uDFFB", + [":person_facepalming::skin-tone-2:"] = "\uD83E\uDD26\uD83C\uDFFC", + [":person_facepalming::skin-tone-3:"] = "\uD83E\uDD26\uD83C\uDFFD", + [":person_facepalming::skin-tone-4:"] = "\uD83E\uDD26\uD83C\uDFFE", + [":person_facepalming::skin-tone-5:"] = "\uD83E\uDD26\uD83C\uDFFF", + [":person_facepalming_tone1:"] = "\uD83E\uDD26\uD83C\uDFFB", + [":person_facepalming_tone2:"] = "\uD83E\uDD26\uD83C\uDFFC", + [":person_facepalming_tone3:"] = "\uD83E\uDD26\uD83C\uDFFD", + [":person_facepalming_tone4:"] = "\uD83E\uDD26\uD83C\uDFFE", + [":person_facepalming_tone5:"] = "\uD83E\uDD26\uD83C\uDFFF", + [":person_fencing:"] = "\uD83E\uDD3A", + [":person_frowning:"] = "\uD83D\uDE4D", + [":person_frowning::skin-tone-1:"] = "\uD83D\uDE4D\uD83C\uDFFB", + [":person_frowning::skin-tone-2:"] = "\uD83D\uDE4D\uD83C\uDFFC", + [":person_frowning::skin-tone-3:"] = "\uD83D\uDE4D\uD83C\uDFFD", + [":person_frowning::skin-tone-4:"] = "\uD83D\uDE4D\uD83C\uDFFE", + [":person_frowning::skin-tone-5:"] = "\uD83D\uDE4D\uD83C\uDFFF", + [":person_frowning_tone1:"] = "\uD83D\uDE4D\uD83C\uDFFB", + [":person_frowning_tone2:"] = "\uD83D\uDE4D\uD83C\uDFFC", + [":person_frowning_tone3:"] = "\uD83D\uDE4D\uD83C\uDFFD", + [":person_frowning_tone4:"] = "\uD83D\uDE4D\uD83C\uDFFE", + [":person_frowning_tone5:"] = "\uD83D\uDE4D\uD83C\uDFFF", + [":person_gesturing_no:"] = "\uD83D\uDE45", + [":person_gesturing_no::skin-tone-1:"] = "\uD83D\uDE45\uD83C\uDFFB", + [":person_gesturing_no::skin-tone-2:"] = "\uD83D\uDE45\uD83C\uDFFC", + [":person_gesturing_no::skin-tone-3:"] = "\uD83D\uDE45\uD83C\uDFFD", + [":person_gesturing_no::skin-tone-4:"] = "\uD83D\uDE45\uD83C\uDFFE", + [":person_gesturing_no::skin-tone-5:"] = "\uD83D\uDE45\uD83C\uDFFF", + [":person_gesturing_no_tone1:"] = "\uD83D\uDE45\uD83C\uDFFB", + [":person_gesturing_no_tone2:"] = "\uD83D\uDE45\uD83C\uDFFC", + [":person_gesturing_no_tone3:"] = "\uD83D\uDE45\uD83C\uDFFD", + [":person_gesturing_no_tone4:"] = "\uD83D\uDE45\uD83C\uDFFE", + [":person_gesturing_no_tone5:"] = "\uD83D\uDE45\uD83C\uDFFF", + [":person_gesturing_ok:"] = "\uD83D\uDE46", + [":person_gesturing_ok::skin-tone-1:"] = "\uD83D\uDE46\uD83C\uDFFB", + [":person_gesturing_ok::skin-tone-2:"] = "\uD83D\uDE46\uD83C\uDFFC", + [":person_gesturing_ok::skin-tone-3:"] = "\uD83D\uDE46\uD83C\uDFFD", + [":person_gesturing_ok::skin-tone-4:"] = "\uD83D\uDE46\uD83C\uDFFE", + [":person_gesturing_ok::skin-tone-5:"] = "\uD83D\uDE46\uD83C\uDFFF", + [":person_gesturing_ok_tone1:"] = "\uD83D\uDE46\uD83C\uDFFB", + [":person_gesturing_ok_tone2:"] = "\uD83D\uDE46\uD83C\uDFFC", + [":person_gesturing_ok_tone3:"] = "\uD83D\uDE46\uD83C\uDFFD", + [":person_gesturing_ok_tone4:"] = "\uD83D\uDE46\uD83C\uDFFE", + [":person_gesturing_ok_tone5:"] = "\uD83D\uDE46\uD83C\uDFFF", + [":person_getting_haircut:"] = "\uD83D\uDC87", + [":person_getting_haircut::skin-tone-1:"] = "\uD83D\uDC87\uD83C\uDFFB", + [":person_getting_haircut::skin-tone-2:"] = "\uD83D\uDC87\uD83C\uDFFC", + [":person_getting_haircut::skin-tone-3:"] = "\uD83D\uDC87\uD83C\uDFFD", + [":person_getting_haircut::skin-tone-4:"] = "\uD83D\uDC87\uD83C\uDFFE", + [":person_getting_haircut::skin-tone-5:"] = "\uD83D\uDC87\uD83C\uDFFF", + [":person_getting_haircut_tone1:"] = "\uD83D\uDC87\uD83C\uDFFB", + [":person_getting_haircut_tone2:"] = "\uD83D\uDC87\uD83C\uDFFC", + [":person_getting_haircut_tone3:"] = "\uD83D\uDC87\uD83C\uDFFD", + [":person_getting_haircut_tone4:"] = "\uD83D\uDC87\uD83C\uDFFE", + [":person_getting_haircut_tone5:"] = "\uD83D\uDC87\uD83C\uDFFF", + [":person_getting_massage:"] = "\uD83D\uDC86", + [":person_getting_massage::skin-tone-1:"] = "\uD83D\uDC86\uD83C\uDFFB", + [":person_getting_massage::skin-tone-2:"] = "\uD83D\uDC86\uD83C\uDFFC", + [":person_getting_massage::skin-tone-3:"] = "\uD83D\uDC86\uD83C\uDFFD", + [":person_getting_massage::skin-tone-4:"] = "\uD83D\uDC86\uD83C\uDFFE", + [":person_getting_massage::skin-tone-5:"] = "\uD83D\uDC86\uD83C\uDFFF", + [":person_getting_massage_tone1:"] = "\uD83D\uDC86\uD83C\uDFFB", + [":person_getting_massage_tone2:"] = "\uD83D\uDC86\uD83C\uDFFC", + [":person_getting_massage_tone3:"] = "\uD83D\uDC86\uD83C\uDFFD", + [":person_getting_massage_tone4:"] = "\uD83D\uDC86\uD83C\uDFFE", + [":person_getting_massage_tone5:"] = "\uD83D\uDC86\uD83C\uDFFF", + [":person_golfing:"] = "\uD83C\uDFCC️", + [":person_golfing::skin-tone-1:"] = "\uD83C\uDFCC\uD83C\uDFFB", + [":person_golfing::skin-tone-2:"] = "\uD83C\uDFCC\uD83C\uDFFC", + [":person_golfing::skin-tone-3:"] = "\uD83C\uDFCC\uD83C\uDFFD", + [":person_golfing::skin-tone-4:"] = "\uD83C\uDFCC\uD83C\uDFFE", + [":person_golfing::skin-tone-5:"] = "\uD83C\uDFCC\uD83C\uDFFF", + [":person_golfing_dark_skin_tone:"] = "\uD83C\uDFCC\uD83C\uDFFF", + [":person_golfing_light_skin_tone:"] = "\uD83C\uDFCC\uD83C\uDFFB", + [":person_golfing_medium_dark_skin_tone:"] = "\uD83C\uDFCC\uD83C\uDFFE", + [":person_golfing_medium_light_skin_tone:"] = "\uD83C\uDFCC\uD83C\uDFFC", + [":person_golfing_medium_skin_tone:"] = "\uD83C\uDFCC\uD83C\uDFFD", + [":person_golfing_tone1:"] = "\uD83C\uDFCC\uD83C\uDFFB", + [":person_golfing_tone2:"] = "\uD83C\uDFCC\uD83C\uDFFC", + [":person_golfing_tone3:"] = "\uD83C\uDFCC\uD83C\uDFFD", + [":person_golfing_tone4:"] = "\uD83C\uDFCC\uD83C\uDFFE", + [":person_golfing_tone5:"] = "\uD83C\uDFCC\uD83C\uDFFF", + [":person_in_bed_dark_skin_tone:"] = "\uD83D\uDECC\uD83C\uDFFF", + [":person_in_bed_light_skin_tone:"] = "\uD83D\uDECC\uD83C\uDFFB", + [":person_in_bed_medium_dark_skin_tone:"] = "\uD83D\uDECC\uD83C\uDFFE", + [":person_in_bed_medium_light_skin_tone:"] = "\uD83D\uDECC\uD83C\uDFFC", + [":person_in_bed_medium_skin_tone:"] = "\uD83D\uDECC\uD83C\uDFFD", + [":person_in_bed_tone1:"] = "\uD83D\uDECC\uD83C\uDFFB", + [":person_in_bed_tone2:"] = "\uD83D\uDECC\uD83C\uDFFC", + [":person_in_bed_tone3:"] = "\uD83D\uDECC\uD83C\uDFFD", + [":person_in_bed_tone4:"] = "\uD83D\uDECC\uD83C\uDFFE", + [":person_in_bed_tone5:"] = "\uD83D\uDECC\uD83C\uDFFF", + [":person_in_lotus_position:"] = "\uD83E\uDDD8", + [":person_in_lotus_position::skin-tone-1:"] = "\uD83E\uDDD8\uD83C\uDFFB", + [":person_in_lotus_position::skin-tone-2:"] = "\uD83E\uDDD8\uD83C\uDFFC", + [":person_in_lotus_position::skin-tone-3:"] = "\uD83E\uDDD8\uD83C\uDFFD", + [":person_in_lotus_position::skin-tone-4:"] = "\uD83E\uDDD8\uD83C\uDFFE", + [":person_in_lotus_position::skin-tone-5:"] = "\uD83E\uDDD8\uD83C\uDFFF", + [":person_in_lotus_position_dark_skin_tone:"] = "\uD83E\uDDD8\uD83C\uDFFF", + [":person_in_lotus_position_light_skin_tone:"] = "\uD83E\uDDD8\uD83C\uDFFB", + [":person_in_lotus_position_medium_dark_skin_tone:"] = "\uD83E\uDDD8\uD83C\uDFFE", + [":person_in_lotus_position_medium_light_skin_tone:"] = "\uD83E\uDDD8\uD83C\uDFFC", + [":person_in_lotus_position_medium_skin_tone:"] = "\uD83E\uDDD8\uD83C\uDFFD", + [":person_in_lotus_position_tone1:"] = "\uD83E\uDDD8\uD83C\uDFFB", + [":person_in_lotus_position_tone2:"] = "\uD83E\uDDD8\uD83C\uDFFC", + [":person_in_lotus_position_tone3:"] = "\uD83E\uDDD8\uD83C\uDFFD", + [":person_in_lotus_position_tone4:"] = "\uD83E\uDDD8\uD83C\uDFFE", + [":person_in_lotus_position_tone5:"] = "\uD83E\uDDD8\uD83C\uDFFF", + [":person_in_steamy_room:"] = "\uD83E\uDDD6", + [":person_in_steamy_room::skin-tone-1:"] = "\uD83E\uDDD6\uD83C\uDFFB", + [":person_in_steamy_room::skin-tone-2:"] = "\uD83E\uDDD6\uD83C\uDFFC", + [":person_in_steamy_room::skin-tone-3:"] = "\uD83E\uDDD6\uD83C\uDFFD", + [":person_in_steamy_room::skin-tone-4:"] = "\uD83E\uDDD6\uD83C\uDFFE", + [":person_in_steamy_room::skin-tone-5:"] = "\uD83E\uDDD6\uD83C\uDFFF", + [":person_in_steamy_room_dark_skin_tone:"] = "\uD83E\uDDD6\uD83C\uDFFF", + [":person_in_steamy_room_light_skin_tone:"] = "\uD83E\uDDD6\uD83C\uDFFB", + [":person_in_steamy_room_medium_dark_skin_tone:"] = "\uD83E\uDDD6\uD83C\uDFFE", + [":person_in_steamy_room_medium_light_skin_tone:"] = "\uD83E\uDDD6\uD83C\uDFFC", + [":person_in_steamy_room_medium_skin_tone:"] = "\uD83E\uDDD6\uD83C\uDFFD", + [":person_in_steamy_room_tone1:"] = "\uD83E\uDDD6\uD83C\uDFFB", + [":person_in_steamy_room_tone2:"] = "\uD83E\uDDD6\uD83C\uDFFC", + [":person_in_steamy_room_tone3:"] = "\uD83E\uDDD6\uD83C\uDFFD", + [":person_in_steamy_room_tone4:"] = "\uD83E\uDDD6\uD83C\uDFFE", + [":person_in_steamy_room_tone5:"] = "\uD83E\uDDD6\uD83C\uDFFF", + [":person_juggling:"] = "\uD83E\uDD39", + [":person_juggling::skin-tone-1:"] = "\uD83E\uDD39\uD83C\uDFFB", + [":person_juggling::skin-tone-2:"] = "\uD83E\uDD39\uD83C\uDFFC", + [":person_juggling::skin-tone-3:"] = "\uD83E\uDD39\uD83C\uDFFD", + [":person_juggling::skin-tone-4:"] = "\uD83E\uDD39\uD83C\uDFFE", + [":person_juggling::skin-tone-5:"] = "\uD83E\uDD39\uD83C\uDFFF", + [":person_juggling_tone1:"] = "\uD83E\uDD39\uD83C\uDFFB", + [":person_juggling_tone2:"] = "\uD83E\uDD39\uD83C\uDFFC", + [":person_juggling_tone3:"] = "\uD83E\uDD39\uD83C\uDFFD", + [":person_juggling_tone4:"] = "\uD83E\uDD39\uD83C\uDFFE", + [":person_juggling_tone5:"] = "\uD83E\uDD39\uD83C\uDFFF", + [":person_kneeling:"] = "\uD83E\uDDCE", + [":person_kneeling::skin-tone-1:"] = "\uD83E\uDDCE\uD83C\uDFFB", + [":person_kneeling::skin-tone-2:"] = "\uD83E\uDDCE\uD83C\uDFFC", + [":person_kneeling::skin-tone-3:"] = "\uD83E\uDDCE\uD83C\uDFFD", + [":person_kneeling::skin-tone-4:"] = "\uD83E\uDDCE\uD83C\uDFFE", + [":person_kneeling::skin-tone-5:"] = "\uD83E\uDDCE\uD83C\uDFFF", + [":person_kneeling_dark_skin_tone:"] = "\uD83E\uDDCE\uD83C\uDFFF", + [":person_kneeling_light_skin_tone:"] = "\uD83E\uDDCE\uD83C\uDFFB", + [":person_kneeling_medium_dark_skin_tone:"] = "\uD83E\uDDCE\uD83C\uDFFE", + [":person_kneeling_medium_light_skin_tone:"] = "\uD83E\uDDCE\uD83C\uDFFC", + [":person_kneeling_medium_skin_tone:"] = "\uD83E\uDDCE\uD83C\uDFFD", + [":person_kneeling_tone1:"] = "\uD83E\uDDCE\uD83C\uDFFB", + [":person_kneeling_tone2:"] = "\uD83E\uDDCE\uD83C\uDFFC", + [":person_kneeling_tone3:"] = "\uD83E\uDDCE\uD83C\uDFFD", + [":person_kneeling_tone4:"] = "\uD83E\uDDCE\uD83C\uDFFE", + [":person_kneeling_tone5:"] = "\uD83E\uDDCE\uD83C\uDFFF", + [":person_lifting_weights:"] = "\uD83C\uDFCB️", + [":person_lifting_weights::skin-tone-1:"] = "\uD83C\uDFCB\uD83C\uDFFB", + [":person_lifting_weights::skin-tone-2:"] = "\uD83C\uDFCB\uD83C\uDFFC", + [":person_lifting_weights::skin-tone-3:"] = "\uD83C\uDFCB\uD83C\uDFFD", + [":person_lifting_weights::skin-tone-4:"] = "\uD83C\uDFCB\uD83C\uDFFE", + [":person_lifting_weights::skin-tone-5:"] = "\uD83C\uDFCB\uD83C\uDFFF", + [":person_lifting_weights_tone1:"] = "\uD83C\uDFCB\uD83C\uDFFB", + [":person_lifting_weights_tone2:"] = "\uD83C\uDFCB\uD83C\uDFFC", + [":person_lifting_weights_tone3:"] = "\uD83C\uDFCB\uD83C\uDFFD", + [":person_lifting_weights_tone4:"] = "\uD83C\uDFCB\uD83C\uDFFE", + [":person_lifting_weights_tone5:"] = "\uD83C\uDFCB\uD83C\uDFFF", + [":person_mountain_biking:"] = "\uD83D\uDEB5", + [":person_mountain_biking::skin-tone-1:"] = "\uD83D\uDEB5\uD83C\uDFFB", + [":person_mountain_biking::skin-tone-2:"] = "\uD83D\uDEB5\uD83C\uDFFC", + [":person_mountain_biking::skin-tone-3:"] = "\uD83D\uDEB5\uD83C\uDFFD", + [":person_mountain_biking::skin-tone-4:"] = "\uD83D\uDEB5\uD83C\uDFFE", + [":person_mountain_biking::skin-tone-5:"] = "\uD83D\uDEB5\uD83C\uDFFF", + [":person_mountain_biking_tone1:"] = "\uD83D\uDEB5\uD83C\uDFFB", + [":person_mountain_biking_tone2:"] = "\uD83D\uDEB5\uD83C\uDFFC", + [":person_mountain_biking_tone3:"] = "\uD83D\uDEB5\uD83C\uDFFD", + [":person_mountain_biking_tone4:"] = "\uD83D\uDEB5\uD83C\uDFFE", + [":person_mountain_biking_tone5:"] = "\uD83D\uDEB5\uD83C\uDFFF", + [":person_playing_handball:"] = "\uD83E\uDD3E", + [":person_playing_handball::skin-tone-1:"] = "\uD83E\uDD3E\uD83C\uDFFB", + [":person_playing_handball::skin-tone-2:"] = "\uD83E\uDD3E\uD83C\uDFFC", + [":person_playing_handball::skin-tone-3:"] = "\uD83E\uDD3E\uD83C\uDFFD", + [":person_playing_handball::skin-tone-4:"] = "\uD83E\uDD3E\uD83C\uDFFE", + [":person_playing_handball::skin-tone-5:"] = "\uD83E\uDD3E\uD83C\uDFFF", + [":person_playing_handball_tone1:"] = "\uD83E\uDD3E\uD83C\uDFFB", + [":person_playing_handball_tone2:"] = "\uD83E\uDD3E\uD83C\uDFFC", + [":person_playing_handball_tone3:"] = "\uD83E\uDD3E\uD83C\uDFFD", + [":person_playing_handball_tone4:"] = "\uD83E\uDD3E\uD83C\uDFFE", + [":person_playing_handball_tone5:"] = "\uD83E\uDD3E\uD83C\uDFFF", + [":person_playing_water_polo:"] = "\uD83E\uDD3D", + [":person_playing_water_polo::skin-tone-1:"] = "\uD83E\uDD3D\uD83C\uDFFB", + [":person_playing_water_polo::skin-tone-2:"] = "\uD83E\uDD3D\uD83C\uDFFC", + [":person_playing_water_polo::skin-tone-3:"] = "\uD83E\uDD3D\uD83C\uDFFD", + [":person_playing_water_polo::skin-tone-4:"] = "\uD83E\uDD3D\uD83C\uDFFE", + [":person_playing_water_polo::skin-tone-5:"] = "\uD83E\uDD3D\uD83C\uDFFF", + [":person_playing_water_polo_tone1:"] = "\uD83E\uDD3D\uD83C\uDFFB", + [":person_playing_water_polo_tone2:"] = "\uD83E\uDD3D\uD83C\uDFFC", + [":person_playing_water_polo_tone3:"] = "\uD83E\uDD3D\uD83C\uDFFD", + [":person_playing_water_polo_tone4:"] = "\uD83E\uDD3D\uD83C\uDFFE", + [":person_playing_water_polo_tone5:"] = "\uD83E\uDD3D\uD83C\uDFFF", + [":person_pouting:"] = "\uD83D\uDE4E", + [":person_pouting::skin-tone-1:"] = "\uD83D\uDE4E\uD83C\uDFFB", + [":person_pouting::skin-tone-2:"] = "\uD83D\uDE4E\uD83C\uDFFC", + [":person_pouting::skin-tone-3:"] = "\uD83D\uDE4E\uD83C\uDFFD", + [":person_pouting::skin-tone-4:"] = "\uD83D\uDE4E\uD83C\uDFFE", + [":person_pouting::skin-tone-5:"] = "\uD83D\uDE4E\uD83C\uDFFF", + [":person_pouting_tone1:"] = "\uD83D\uDE4E\uD83C\uDFFB", + [":person_pouting_tone2:"] = "\uD83D\uDE4E\uD83C\uDFFC", + [":person_pouting_tone3:"] = "\uD83D\uDE4E\uD83C\uDFFD", + [":person_pouting_tone4:"] = "\uD83D\uDE4E\uD83C\uDFFE", + [":person_pouting_tone5:"] = "\uD83D\uDE4E\uD83C\uDFFF", + [":person_raising_hand:"] = "\uD83D\uDE4B", + [":person_raising_hand::skin-tone-1:"] = "\uD83D\uDE4B\uD83C\uDFFB", + [":person_raising_hand::skin-tone-2:"] = "\uD83D\uDE4B\uD83C\uDFFC", + [":person_raising_hand::skin-tone-3:"] = "\uD83D\uDE4B\uD83C\uDFFD", + [":person_raising_hand::skin-tone-4:"] = "\uD83D\uDE4B\uD83C\uDFFE", + [":person_raising_hand::skin-tone-5:"] = "\uD83D\uDE4B\uD83C\uDFFF", + [":person_raising_hand_tone1:"] = "\uD83D\uDE4B\uD83C\uDFFB", + [":person_raising_hand_tone2:"] = "\uD83D\uDE4B\uD83C\uDFFC", + [":person_raising_hand_tone3:"] = "\uD83D\uDE4B\uD83C\uDFFD", + [":person_raising_hand_tone4:"] = "\uD83D\uDE4B\uD83C\uDFFE", + [":person_raising_hand_tone5:"] = "\uD83D\uDE4B\uD83C\uDFFF", + [":person_rowing_boat:"] = "\uD83D\uDEA3", + [":person_rowing_boat::skin-tone-1:"] = "\uD83D\uDEA3\uD83C\uDFFB", + [":person_rowing_boat::skin-tone-2:"] = "\uD83D\uDEA3\uD83C\uDFFC", + [":person_rowing_boat::skin-tone-3:"] = "\uD83D\uDEA3\uD83C\uDFFD", + [":person_rowing_boat::skin-tone-4:"] = "\uD83D\uDEA3\uD83C\uDFFE", + [":person_rowing_boat::skin-tone-5:"] = "\uD83D\uDEA3\uD83C\uDFFF", + [":person_rowing_boat_tone1:"] = "\uD83D\uDEA3\uD83C\uDFFB", + [":person_rowing_boat_tone2:"] = "\uD83D\uDEA3\uD83C\uDFFC", + [":person_rowing_boat_tone3:"] = "\uD83D\uDEA3\uD83C\uDFFD", + [":person_rowing_boat_tone4:"] = "\uD83D\uDEA3\uD83C\uDFFE", + [":person_rowing_boat_tone5:"] = "\uD83D\uDEA3\uD83C\uDFFF", + [":person_running:"] = "\uD83C\uDFC3", + [":person_running::skin-tone-1:"] = "\uD83C\uDFC3\uD83C\uDFFB", + [":person_running::skin-tone-2:"] = "\uD83C\uDFC3\uD83C\uDFFC", + [":person_running::skin-tone-3:"] = "\uD83C\uDFC3\uD83C\uDFFD", + [":person_running::skin-tone-4:"] = "\uD83C\uDFC3\uD83C\uDFFE", + [":person_running::skin-tone-5:"] = "\uD83C\uDFC3\uD83C\uDFFF", + [":person_running_tone1:"] = "\uD83C\uDFC3\uD83C\uDFFB", + [":person_running_tone2:"] = "\uD83C\uDFC3\uD83C\uDFFC", + [":person_running_tone3:"] = "\uD83C\uDFC3\uD83C\uDFFD", + [":person_running_tone4:"] = "\uD83C\uDFC3\uD83C\uDFFE", + [":person_running_tone5:"] = "\uD83C\uDFC3\uD83C\uDFFF", + [":person_shrugging:"] = "\uD83E\uDD37", + [":person_shrugging::skin-tone-1:"] = "\uD83E\uDD37\uD83C\uDFFB", + [":person_shrugging::skin-tone-2:"] = "\uD83E\uDD37\uD83C\uDFFC", + [":person_shrugging::skin-tone-3:"] = "\uD83E\uDD37\uD83C\uDFFD", + [":person_shrugging::skin-tone-4:"] = "\uD83E\uDD37\uD83C\uDFFE", + [":person_shrugging::skin-tone-5:"] = "\uD83E\uDD37\uD83C\uDFFF", + [":person_shrugging_tone1:"] = "\uD83E\uDD37\uD83C\uDFFB", + [":person_shrugging_tone2:"] = "\uD83E\uDD37\uD83C\uDFFC", + [":person_shrugging_tone3:"] = "\uD83E\uDD37\uD83C\uDFFD", + [":person_shrugging_tone4:"] = "\uD83E\uDD37\uD83C\uDFFE", + [":person_shrugging_tone5:"] = "\uD83E\uDD37\uD83C\uDFFF", + [":person_standing:"] = "\uD83E\uDDCD", + [":person_standing::skin-tone-1:"] = "\uD83E\uDDCD\uD83C\uDFFB", + [":person_standing::skin-tone-2:"] = "\uD83E\uDDCD\uD83C\uDFFC", + [":person_standing::skin-tone-3:"] = "\uD83E\uDDCD\uD83C\uDFFD", + [":person_standing::skin-tone-4:"] = "\uD83E\uDDCD\uD83C\uDFFE", + [":person_standing::skin-tone-5:"] = "\uD83E\uDDCD\uD83C\uDFFF", + [":person_standing_dark_skin_tone:"] = "\uD83E\uDDCD\uD83C\uDFFF", + [":person_standing_light_skin_tone:"] = "\uD83E\uDDCD\uD83C\uDFFB", + [":person_standing_medium_dark_skin_tone:"] = "\uD83E\uDDCD\uD83C\uDFFE", + [":person_standing_medium_light_skin_tone:"] = "\uD83E\uDDCD\uD83C\uDFFC", + [":person_standing_medium_skin_tone:"] = "\uD83E\uDDCD\uD83C\uDFFD", + [":person_standing_tone1:"] = "\uD83E\uDDCD\uD83C\uDFFB", + [":person_standing_tone2:"] = "\uD83E\uDDCD\uD83C\uDFFC", + [":person_standing_tone3:"] = "\uD83E\uDDCD\uD83C\uDFFD", + [":person_standing_tone4:"] = "\uD83E\uDDCD\uD83C\uDFFE", + [":person_standing_tone5:"] = "\uD83E\uDDCD\uD83C\uDFFF", + [":person_surfing:"] = "\uD83C\uDFC4", + [":person_surfing::skin-tone-1:"] = "\uD83C\uDFC4\uD83C\uDFFB", + [":person_surfing::skin-tone-2:"] = "\uD83C\uDFC4\uD83C\uDFFC", + [":person_surfing::skin-tone-3:"] = "\uD83C\uDFC4\uD83C\uDFFD", + [":person_surfing::skin-tone-4:"] = "\uD83C\uDFC4\uD83C\uDFFE", + [":person_surfing::skin-tone-5:"] = "\uD83C\uDFC4\uD83C\uDFFF", + [":person_surfing_tone1:"] = "\uD83C\uDFC4\uD83C\uDFFB", + [":person_surfing_tone2:"] = "\uD83C\uDFC4\uD83C\uDFFC", + [":person_surfing_tone3:"] = "\uD83C\uDFC4\uD83C\uDFFD", + [":person_surfing_tone4:"] = "\uD83C\uDFC4\uD83C\uDFFE", + [":person_surfing_tone5:"] = "\uD83C\uDFC4\uD83C\uDFFF", + [":person_swimming:"] = "\uD83C\uDFCA", + [":person_swimming::skin-tone-1:"] = "\uD83C\uDFCA\uD83C\uDFFB", + [":person_swimming::skin-tone-2:"] = "\uD83C\uDFCA\uD83C\uDFFC", + [":person_swimming::skin-tone-3:"] = "\uD83C\uDFCA\uD83C\uDFFD", + [":person_swimming::skin-tone-4:"] = "\uD83C\uDFCA\uD83C\uDFFE", + [":person_swimming::skin-tone-5:"] = "\uD83C\uDFCA\uD83C\uDFFF", + [":person_swimming_tone1:"] = "\uD83C\uDFCA\uD83C\uDFFB", + [":person_swimming_tone2:"] = "\uD83C\uDFCA\uD83C\uDFFC", + [":person_swimming_tone3:"] = "\uD83C\uDFCA\uD83C\uDFFD", + [":person_swimming_tone4:"] = "\uD83C\uDFCA\uD83C\uDFFE", + [":person_swimming_tone5:"] = "\uD83C\uDFCA\uD83C\uDFFF", + [":person_tipping_hand:"] = "\uD83D\uDC81", + [":person_tipping_hand::skin-tone-1:"] = "\uD83D\uDC81\uD83C\uDFFB", + [":person_tipping_hand::skin-tone-2:"] = "\uD83D\uDC81\uD83C\uDFFC", + [":person_tipping_hand::skin-tone-3:"] = "\uD83D\uDC81\uD83C\uDFFD", + [":person_tipping_hand::skin-tone-4:"] = "\uD83D\uDC81\uD83C\uDFFE", + [":person_tipping_hand::skin-tone-5:"] = "\uD83D\uDC81\uD83C\uDFFF", + [":person_tipping_hand_tone1:"] = "\uD83D\uDC81\uD83C\uDFFB", + [":person_tipping_hand_tone2:"] = "\uD83D\uDC81\uD83C\uDFFC", + [":person_tipping_hand_tone3:"] = "\uD83D\uDC81\uD83C\uDFFD", + [":person_tipping_hand_tone4:"] = "\uD83D\uDC81\uD83C\uDFFE", + [":person_tipping_hand_tone5:"] = "\uD83D\uDC81\uD83C\uDFFF", + [":person_walking:"] = "\uD83D\uDEB6", + [":person_walking::skin-tone-1:"] = "\uD83D\uDEB6\uD83C\uDFFB", + [":person_walking::skin-tone-2:"] = "\uD83D\uDEB6\uD83C\uDFFC", + [":person_walking::skin-tone-3:"] = "\uD83D\uDEB6\uD83C\uDFFD", + [":person_walking::skin-tone-4:"] = "\uD83D\uDEB6\uD83C\uDFFE", + [":person_walking::skin-tone-5:"] = "\uD83D\uDEB6\uD83C\uDFFF", + [":person_walking_tone1:"] = "\uD83D\uDEB6\uD83C\uDFFB", + [":person_walking_tone2:"] = "\uD83D\uDEB6\uD83C\uDFFC", + [":person_walking_tone3:"] = "\uD83D\uDEB6\uD83C\uDFFD", + [":person_walking_tone4:"] = "\uD83D\uDEB6\uD83C\uDFFE", + [":person_walking_tone5:"] = "\uD83D\uDEB6\uD83C\uDFFF", + [":person_wearing_turban:"] = "\uD83D\uDC73", + [":person_wearing_turban::skin-tone-1:"] = "\uD83D\uDC73\uD83C\uDFFB", + [":person_wearing_turban::skin-tone-2:"] = "\uD83D\uDC73\uD83C\uDFFC", + [":person_wearing_turban::skin-tone-3:"] = "\uD83D\uDC73\uD83C\uDFFD", + [":person_wearing_turban::skin-tone-4:"] = "\uD83D\uDC73\uD83C\uDFFE", + [":person_wearing_turban::skin-tone-5:"] = "\uD83D\uDC73\uD83C\uDFFF", + [":person_wearing_turban_tone1:"] = "\uD83D\uDC73\uD83C\uDFFB", + [":person_wearing_turban_tone2:"] = "\uD83D\uDC73\uD83C\uDFFC", + [":person_wearing_turban_tone3:"] = "\uD83D\uDC73\uD83C\uDFFD", + [":person_wearing_turban_tone4:"] = "\uD83D\uDC73\uD83C\uDFFE", + [":person_wearing_turban_tone5:"] = "\uD83D\uDC73\uD83C\uDFFF", + [":person_with_ball:"] = "⛹️", + [":person_with_ball::skin-tone-1:"] = "⛹\uD83C\uDFFB", + [":person_with_ball::skin-tone-2:"] = "⛹\uD83C\uDFFC", + [":person_with_ball::skin-tone-3:"] = "⛹\uD83C\uDFFD", + [":person_with_ball::skin-tone-4:"] = "⛹\uD83C\uDFFE", + [":person_with_ball::skin-tone-5:"] = "⛹\uD83C\uDFFF", + [":person_with_ball_tone1:"] = "⛹\uD83C\uDFFB", + [":person_with_ball_tone2:"] = "⛹\uD83C\uDFFC", + [":person_with_ball_tone3:"] = "⛹\uD83C\uDFFD", + [":person_with_ball_tone4:"] = "⛹\uD83C\uDFFE", + [":person_with_ball_tone5:"] = "⛹\uD83C\uDFFF", + [":person_with_blond_hair:"] = "\uD83D\uDC71", + [":person_with_blond_hair::skin-tone-1:"] = "\uD83D\uDC71\uD83C\uDFFB", + [":person_with_blond_hair::skin-tone-2:"] = "\uD83D\uDC71\uD83C\uDFFC", + [":person_with_blond_hair::skin-tone-3:"] = "\uD83D\uDC71\uD83C\uDFFD", + [":person_with_blond_hair::skin-tone-4:"] = "\uD83D\uDC71\uD83C\uDFFE", + [":person_with_blond_hair::skin-tone-5:"] = "\uD83D\uDC71\uD83C\uDFFF", + [":person_with_blond_hair_tone1:"] = "\uD83D\uDC71\uD83C\uDFFB", + [":person_with_blond_hair_tone2:"] = "\uD83D\uDC71\uD83C\uDFFC", + [":person_with_blond_hair_tone3:"] = "\uD83D\uDC71\uD83C\uDFFD", + [":person_with_blond_hair_tone4:"] = "\uD83D\uDC71\uD83C\uDFFE", + [":person_with_blond_hair_tone5:"] = "\uD83D\uDC71\uD83C\uDFFF", + [":person_with_pouting_face:"] = "\uD83D\uDE4E", + [":person_with_pouting_face::skin-tone-1:"] = "\uD83D\uDE4E\uD83C\uDFFB", + [":person_with_pouting_face::skin-tone-2:"] = "\uD83D\uDE4E\uD83C\uDFFC", + [":person_with_pouting_face::skin-tone-3:"] = "\uD83D\uDE4E\uD83C\uDFFD", + [":person_with_pouting_face::skin-tone-4:"] = "\uD83D\uDE4E\uD83C\uDFFE", + [":person_with_pouting_face::skin-tone-5:"] = "\uD83D\uDE4E\uD83C\uDFFF", + [":person_with_pouting_face_tone1:"] = "\uD83D\uDE4E\uD83C\uDFFB", + [":person_with_pouting_face_tone2:"] = "\uD83D\uDE4E\uD83C\uDFFC", + [":person_with_pouting_face_tone3:"] = "\uD83D\uDE4E\uD83C\uDFFD", + [":person_with_pouting_face_tone4:"] = "\uD83D\uDE4E\uD83C\uDFFE", + [":person_with_pouting_face_tone5:"] = "\uD83D\uDE4E\uD83C\uDFFF", + [":petri_dish:"] = "\uD83E\uDDEB", + [":pick:"] = "⛏️", + [":pie:"] = "\uD83E\uDD67", + [":pig2:"] = "\uD83D\uDC16", + [":pig:"] = "\uD83D\uDC37", + [":pig_nose:"] = "\uD83D\uDC3D", + [":pill:"] = "\uD83D\uDC8A", + [":pinching_hand:"] = "\uD83E\uDD0F", + [":pinching_hand::skin-tone-1:"] = "\uD83E\uDD0F\uD83C\uDFFB", + [":pinching_hand::skin-tone-2:"] = "\uD83E\uDD0F\uD83C\uDFFC", + [":pinching_hand::skin-tone-3:"] = "\uD83E\uDD0F\uD83C\uDFFD", + [":pinching_hand::skin-tone-4:"] = "\uD83E\uDD0F\uD83C\uDFFE", + [":pinching_hand::skin-tone-5:"] = "\uD83E\uDD0F\uD83C\uDFFF", + [":pinching_hand_dark_skin_tone:"] = "\uD83E\uDD0F\uD83C\uDFFF", + [":pinching_hand_light_skin_tone:"] = "\uD83E\uDD0F\uD83C\uDFFB", + [":pinching_hand_medium_dark_skin_tone:"] = "\uD83E\uDD0F\uD83C\uDFFE", + [":pinching_hand_medium_light_skin_tone:"] = "\uD83E\uDD0F\uD83C\uDFFC", + [":pinching_hand_medium_skin_tone:"] = "\uD83E\uDD0F\uD83C\uDFFD", + [":pinching_hand_tone1:"] = "\uD83E\uDD0F\uD83C\uDFFB", + [":pinching_hand_tone2:"] = "\uD83E\uDD0F\uD83C\uDFFC", + [":pinching_hand_tone3:"] = "\uD83E\uDD0F\uD83C\uDFFD", + [":pinching_hand_tone4:"] = "\uD83E\uDD0F\uD83C\uDFFE", + [":pinching_hand_tone5:"] = "\uD83E\uDD0F\uD83C\uDFFF", + [":pineapple:"] = "\uD83C\uDF4D", + [":ping_pong:"] = "\uD83C\uDFD3", + [":pirate_flag:"] = "\uD83C\uDFF4\u200D☠️", + [":pisces:"] = "♓", + [":pizza:"] = "\uD83C\uDF55", + [":place_of_worship:"] = "\uD83D\uDED0", + [":play_pause:"] = "⏯️", + [":pleading_face:"] = "\uD83E\uDD7A", + [":point_down:"] = "\uD83D\uDC47", + [":point_down::skin-tone-1:"] = "\uD83D\uDC47\uD83C\uDFFB", + [":point_down::skin-tone-2:"] = "\uD83D\uDC47\uD83C\uDFFC", + [":point_down::skin-tone-3:"] = "\uD83D\uDC47\uD83C\uDFFD", + [":point_down::skin-tone-4:"] = "\uD83D\uDC47\uD83C\uDFFE", + [":point_down::skin-tone-5:"] = "\uD83D\uDC47\uD83C\uDFFF", + [":point_down_tone1:"] = "\uD83D\uDC47\uD83C\uDFFB", + [":point_down_tone2:"] = "\uD83D\uDC47\uD83C\uDFFC", + [":point_down_tone3:"] = "\uD83D\uDC47\uD83C\uDFFD", + [":point_down_tone4:"] = "\uD83D\uDC47\uD83C\uDFFE", + [":point_down_tone5:"] = "\uD83D\uDC47\uD83C\uDFFF", + [":point_left:"] = "\uD83D\uDC48", + [":point_left::skin-tone-1:"] = "\uD83D\uDC48\uD83C\uDFFB", + [":point_left::skin-tone-2:"] = "\uD83D\uDC48\uD83C\uDFFC", + [":point_left::skin-tone-3:"] = "\uD83D\uDC48\uD83C\uDFFD", + [":point_left::skin-tone-4:"] = "\uD83D\uDC48\uD83C\uDFFE", + [":point_left::skin-tone-5:"] = "\uD83D\uDC48\uD83C\uDFFF", + [":point_left_tone1:"] = "\uD83D\uDC48\uD83C\uDFFB", + [":point_left_tone2:"] = "\uD83D\uDC48\uD83C\uDFFC", + [":point_left_tone3:"] = "\uD83D\uDC48\uD83C\uDFFD", + [":point_left_tone4:"] = "\uD83D\uDC48\uD83C\uDFFE", + [":point_left_tone5:"] = "\uD83D\uDC48\uD83C\uDFFF", + [":point_right:"] = "\uD83D\uDC49", + [":point_right::skin-tone-1:"] = "\uD83D\uDC49\uD83C\uDFFB", + [":point_right::skin-tone-2:"] = "\uD83D\uDC49\uD83C\uDFFC", + [":point_right::skin-tone-3:"] = "\uD83D\uDC49\uD83C\uDFFD", + [":point_right::skin-tone-4:"] = "\uD83D\uDC49\uD83C\uDFFE", + [":point_right::skin-tone-5:"] = "\uD83D\uDC49\uD83C\uDFFF", + [":point_right_tone1:"] = "\uD83D\uDC49\uD83C\uDFFB", + [":point_right_tone2:"] = "\uD83D\uDC49\uD83C\uDFFC", + [":point_right_tone3:"] = "\uD83D\uDC49\uD83C\uDFFD", + [":point_right_tone4:"] = "\uD83D\uDC49\uD83C\uDFFE", + [":point_right_tone5:"] = "\uD83D\uDC49\uD83C\uDFFF", + [":point_up:"] = "☝️", + [":point_up::skin-tone-1:"] = "☝\uD83C\uDFFB", + [":point_up::skin-tone-2:"] = "☝\uD83C\uDFFC", + [":point_up::skin-tone-3:"] = "☝\uD83C\uDFFD", + [":point_up::skin-tone-4:"] = "☝\uD83C\uDFFE", + [":point_up::skin-tone-5:"] = "☝\uD83C\uDFFF", + [":point_up_2:"] = "\uD83D\uDC46", + [":point_up_2::skin-tone-1:"] = "\uD83D\uDC46\uD83C\uDFFB", + [":point_up_2::skin-tone-2:"] = "\uD83D\uDC46\uD83C\uDFFC", + [":point_up_2::skin-tone-3:"] = "\uD83D\uDC46\uD83C\uDFFD", + [":point_up_2::skin-tone-4:"] = "\uD83D\uDC46\uD83C\uDFFE", + [":point_up_2::skin-tone-5:"] = "\uD83D\uDC46\uD83C\uDFFF", + [":point_up_2_tone1:"] = "\uD83D\uDC46\uD83C\uDFFB", + [":point_up_2_tone2:"] = "\uD83D\uDC46\uD83C\uDFFC", + [":point_up_2_tone3:"] = "\uD83D\uDC46\uD83C\uDFFD", + [":point_up_2_tone4:"] = "\uD83D\uDC46\uD83C\uDFFE", + [":point_up_2_tone5:"] = "\uD83D\uDC46\uD83C\uDFFF", + [":point_up_tone1:"] = "☝\uD83C\uDFFB", + [":point_up_tone2:"] = "☝\uD83C\uDFFC", + [":point_up_tone3:"] = "☝\uD83C\uDFFD", + [":point_up_tone4:"] = "☝\uD83C\uDFFE", + [":point_up_tone5:"] = "☝\uD83C\uDFFF", + [":police_car:"] = "\uD83D\uDE93", + [":police_officer:"] = "\uD83D\uDC6E", + [":police_officer::skin-tone-1:"] = "\uD83D\uDC6E\uD83C\uDFFB", + [":police_officer::skin-tone-2:"] = "\uD83D\uDC6E\uD83C\uDFFC", + [":police_officer::skin-tone-3:"] = "\uD83D\uDC6E\uD83C\uDFFD", + [":police_officer::skin-tone-4:"] = "\uD83D\uDC6E\uD83C\uDFFE", + [":police_officer::skin-tone-5:"] = "\uD83D\uDC6E\uD83C\uDFFF", + [":police_officer_tone1:"] = "\uD83D\uDC6E\uD83C\uDFFB", + [":police_officer_tone2:"] = "\uD83D\uDC6E\uD83C\uDFFC", + [":police_officer_tone3:"] = "\uD83D\uDC6E\uD83C\uDFFD", + [":police_officer_tone4:"] = "\uD83D\uDC6E\uD83C\uDFFE", + [":police_officer_tone5:"] = "\uD83D\uDC6E\uD83C\uDFFF", + [":poo:"] = "\uD83D\uDCA9", + [":poodle:"] = "\uD83D\uDC29", + [":poop:"] = "\uD83D\uDCA9", + [":popcorn:"] = "\uD83C\uDF7F", + [":post_office:"] = "\uD83C\uDFE3", + [":postal_horn:"] = "\uD83D\uDCEF", + [":postbox:"] = "\uD83D\uDCEE", + [":potable_water:"] = "\uD83D\uDEB0", + [":potato:"] = "\uD83E\uDD54", + [":pouch:"] = "\uD83D\uDC5D", + [":poultry_leg:"] = "\uD83C\uDF57", + [":pound:"] = "\uD83D\uDCB7", + [":pouting_cat:"] = "\uD83D\uDE3E", + [":pray:"] = "\uD83D\uDE4F", + [":pray::skin-tone-1:"] = "\uD83D\uDE4F\uD83C\uDFFB", + [":pray::skin-tone-2:"] = "\uD83D\uDE4F\uD83C\uDFFC", + [":pray::skin-tone-3:"] = "\uD83D\uDE4F\uD83C\uDFFD", + [":pray::skin-tone-4:"] = "\uD83D\uDE4F\uD83C\uDFFE", + [":pray::skin-tone-5:"] = "\uD83D\uDE4F\uD83C\uDFFF", + [":pray_tone1:"] = "\uD83D\uDE4F\uD83C\uDFFB", + [":pray_tone2:"] = "\uD83D\uDE4F\uD83C\uDFFC", + [":pray_tone3:"] = "\uD83D\uDE4F\uD83C\uDFFD", + [":pray_tone4:"] = "\uD83D\uDE4F\uD83C\uDFFE", + [":pray_tone5:"] = "\uD83D\uDE4F\uD83C\uDFFF", + [":prayer_beads:"] = "\uD83D\uDCFF", + [":pregnant_woman:"] = "\uD83E\uDD30", + [":pregnant_woman::skin-tone-1:"] = "\uD83E\uDD30\uD83C\uDFFB", + [":pregnant_woman::skin-tone-2:"] = "\uD83E\uDD30\uD83C\uDFFC", + [":pregnant_woman::skin-tone-3:"] = "\uD83E\uDD30\uD83C\uDFFD", + [":pregnant_woman::skin-tone-4:"] = "\uD83E\uDD30\uD83C\uDFFE", + [":pregnant_woman::skin-tone-5:"] = "\uD83E\uDD30\uD83C\uDFFF", + [":pregnant_woman_tone1:"] = "\uD83E\uDD30\uD83C\uDFFB", + [":pregnant_woman_tone2:"] = "\uD83E\uDD30\uD83C\uDFFC", + [":pregnant_woman_tone3:"] = "\uD83E\uDD30\uD83C\uDFFD", + [":pregnant_woman_tone4:"] = "\uD83E\uDD30\uD83C\uDFFE", + [":pregnant_woman_tone5:"] = "\uD83E\uDD30\uD83C\uDFFF", + [":pretzel:"] = "\uD83E\uDD68", + [":previous_track:"] = "⏮️", + [":prince:"] = "\uD83E\uDD34", + [":prince::skin-tone-1:"] = "\uD83E\uDD34\uD83C\uDFFB", + [":prince::skin-tone-2:"] = "\uD83E\uDD34\uD83C\uDFFC", + [":prince::skin-tone-3:"] = "\uD83E\uDD34\uD83C\uDFFD", + [":prince::skin-tone-4:"] = "\uD83E\uDD34\uD83C\uDFFE", + [":prince::skin-tone-5:"] = "\uD83E\uDD34\uD83C\uDFFF", + [":prince_tone1:"] = "\uD83E\uDD34\uD83C\uDFFB", + [":prince_tone2:"] = "\uD83E\uDD34\uD83C\uDFFC", + [":prince_tone3:"] = "\uD83E\uDD34\uD83C\uDFFD", + [":prince_tone4:"] = "\uD83E\uDD34\uD83C\uDFFE", + [":prince_tone5:"] = "\uD83E\uDD34\uD83C\uDFFF", + [":princess:"] = "\uD83D\uDC78", + [":princess::skin-tone-1:"] = "\uD83D\uDC78\uD83C\uDFFB", + [":princess::skin-tone-2:"] = "\uD83D\uDC78\uD83C\uDFFC", + [":princess::skin-tone-3:"] = "\uD83D\uDC78\uD83C\uDFFD", + [":princess::skin-tone-4:"] = "\uD83D\uDC78\uD83C\uDFFE", + [":princess::skin-tone-5:"] = "\uD83D\uDC78\uD83C\uDFFF", + [":princess_tone1:"] = "\uD83D\uDC78\uD83C\uDFFB", + [":princess_tone2:"] = "\uD83D\uDC78\uD83C\uDFFC", + [":princess_tone3:"] = "\uD83D\uDC78\uD83C\uDFFD", + [":princess_tone4:"] = "\uD83D\uDC78\uD83C\uDFFE", + [":princess_tone5:"] = "\uD83D\uDC78\uD83C\uDFFF", + [":printer:"] = "\uD83D\uDDA8️", + [":probing_cane:"] = "\uD83E\uDDAF", + [":projector:"] = "\uD83D\uDCFD️", + [":pudding:"] = "\uD83C\uDF6E", + [":punch:"] = "\uD83D\uDC4A", + [":punch::skin-tone-1:"] = "\uD83D\uDC4A\uD83C\uDFFB", + [":punch::skin-tone-2:"] = "\uD83D\uDC4A\uD83C\uDFFC", + [":punch::skin-tone-3:"] = "\uD83D\uDC4A\uD83C\uDFFD", + [":punch::skin-tone-4:"] = "\uD83D\uDC4A\uD83C\uDFFE", + [":punch::skin-tone-5:"] = "\uD83D\uDC4A\uD83C\uDFFF", + [":punch_tone1:"] = "\uD83D\uDC4A\uD83C\uDFFB", + [":punch_tone2:"] = "\uD83D\uDC4A\uD83C\uDFFC", + [":punch_tone3:"] = "\uD83D\uDC4A\uD83C\uDFFD", + [":punch_tone4:"] = "\uD83D\uDC4A\uD83C\uDFFE", + [":punch_tone5:"] = "\uD83D\uDC4A\uD83C\uDFFF", + [":purple_circle:"] = "\uD83D\uDFE3", + [":purple_heart:"] = "\uD83D\uDC9C", + [":purple_square:"] = "\uD83D\uDFEA", + [":purse:"] = "\uD83D\uDC5B", + [":pushpin:"] = "\uD83D\uDCCC", + [":put_litter_in_its_place:"] = "\uD83D\uDEAE", + [":question:"] = "❓", + [":rabbit2:"] = "\uD83D\uDC07", + [":rabbit:"] = "\uD83D\uDC30", + [":raccoon:"] = "\uD83E\uDD9D", + [":race_car:"] = "\uD83C\uDFCE️", + [":racehorse:"] = "\uD83D\uDC0E", + [":racing_car:"] = "\uD83C\uDFCE️", + [":racing_motorcycle:"] = "\uD83C\uDFCD️", + [":radio:"] = "\uD83D\uDCFB", + [":radio_button:"] = "\uD83D\uDD18", + [":radioactive:"] = "☢️", + [":radioactive_sign:"] = "☢️", + [":rage:"] = "\uD83D\uDE21", + [":railroad_track:"] = "\uD83D\uDEE4️", + [":railway_car:"] = "\uD83D\uDE83", + [":railway_track:"] = "\uD83D\uDEE4️", + [":rainbow:"] = "\uD83C\uDF08", + [":rainbow_flag:"] = "\uD83C\uDFF3️\u200D\uD83C\uDF08", + [":raised_back_of_hand:"] = "\uD83E\uDD1A", + [":raised_back_of_hand::skin-tone-1:"] = "\uD83E\uDD1A\uD83C\uDFFB", + [":raised_back_of_hand::skin-tone-2:"] = "\uD83E\uDD1A\uD83C\uDFFC", + [":raised_back_of_hand::skin-tone-3:"] = "\uD83E\uDD1A\uD83C\uDFFD", + [":raised_back_of_hand::skin-tone-4:"] = "\uD83E\uDD1A\uD83C\uDFFE", + [":raised_back_of_hand::skin-tone-5:"] = "\uD83E\uDD1A\uD83C\uDFFF", + [":raised_back_of_hand_tone1:"] = "\uD83E\uDD1A\uD83C\uDFFB", + [":raised_back_of_hand_tone2:"] = "\uD83E\uDD1A\uD83C\uDFFC", + [":raised_back_of_hand_tone3:"] = "\uD83E\uDD1A\uD83C\uDFFD", + [":raised_back_of_hand_tone4:"] = "\uD83E\uDD1A\uD83C\uDFFE", + [":raised_back_of_hand_tone5:"] = "\uD83E\uDD1A\uD83C\uDFFF", + [":raised_hand:"] = "✋", + [":raised_hand::skin-tone-1:"] = "✋\uD83C\uDFFB", + [":raised_hand::skin-tone-2:"] = "✋\uD83C\uDFFC", + [":raised_hand::skin-tone-3:"] = "✋\uD83C\uDFFD", + [":raised_hand::skin-tone-4:"] = "✋\uD83C\uDFFE", + [":raised_hand::skin-tone-5:"] = "✋\uD83C\uDFFF", + [":raised_hand_tone1:"] = "✋\uD83C\uDFFB", + [":raised_hand_tone2:"] = "✋\uD83C\uDFFC", + [":raised_hand_tone3:"] = "✋\uD83C\uDFFD", + [":raised_hand_tone4:"] = "✋\uD83C\uDFFE", + [":raised_hand_tone5:"] = "✋\uD83C\uDFFF", + [":raised_hand_with_fingers_splayed:"] = "\uD83D\uDD90️", + [":raised_hand_with_fingers_splayed::skin-tone-1:"] = "\uD83D\uDD90\uD83C\uDFFB", + [":raised_hand_with_fingers_splayed::skin-tone-2:"] = "\uD83D\uDD90\uD83C\uDFFC", + [":raised_hand_with_fingers_splayed::skin-tone-3:"] = "\uD83D\uDD90\uD83C\uDFFD", + [":raised_hand_with_fingers_splayed::skin-tone-4:"] = "\uD83D\uDD90\uD83C\uDFFE", + [":raised_hand_with_fingers_splayed::skin-tone-5:"] = "\uD83D\uDD90\uD83C\uDFFF", + [":raised_hand_with_fingers_splayed_tone1:"] = "\uD83D\uDD90\uD83C\uDFFB", + [":raised_hand_with_fingers_splayed_tone2:"] = "\uD83D\uDD90\uD83C\uDFFC", + [":raised_hand_with_fingers_splayed_tone3:"] = "\uD83D\uDD90\uD83C\uDFFD", + [":raised_hand_with_fingers_splayed_tone4:"] = "\uD83D\uDD90\uD83C\uDFFE", + [":raised_hand_with_fingers_splayed_tone5:"] = "\uD83D\uDD90\uD83C\uDFFF", + [":raised_hand_with_part_between_middle_and_ring_fingers:"] = "\uD83D\uDD96", + [":raised_hand_with_part_between_middle_and_ring_fingers::skin-tone-1:"] = "\uD83D\uDD96\uD83C\uDFFB", + [":raised_hand_with_part_between_middle_and_ring_fingers::skin-tone-2:"] = "\uD83D\uDD96\uD83C\uDFFC", + [":raised_hand_with_part_between_middle_and_ring_fingers::skin-tone-3:"] = "\uD83D\uDD96\uD83C\uDFFD", + [":raised_hand_with_part_between_middle_and_ring_fingers::skin-tone-4:"] = "\uD83D\uDD96\uD83C\uDFFE", + [":raised_hand_with_part_between_middle_and_ring_fingers::skin-tone-5:"] = "\uD83D\uDD96\uD83C\uDFFF", + [":raised_hand_with_part_between_middle_and_ring_fingers_tone1:"] = "\uD83D\uDD96\uD83C\uDFFB", + [":raised_hand_with_part_between_middle_and_ring_fingers_tone2:"] = "\uD83D\uDD96\uD83C\uDFFC", + [":raised_hand_with_part_between_middle_and_ring_fingers_tone3:"] = "\uD83D\uDD96\uD83C\uDFFD", + [":raised_hand_with_part_between_middle_and_ring_fingers_tone4:"] = "\uD83D\uDD96\uD83C\uDFFE", + [":raised_hand_with_part_between_middle_and_ring_fingers_tone5:"] = "\uD83D\uDD96\uD83C\uDFFF", + [":raised_hands:"] = "\uD83D\uDE4C", + [":raised_hands::skin-tone-1:"] = "\uD83D\uDE4C\uD83C\uDFFB", + [":raised_hands::skin-tone-2:"] = "\uD83D\uDE4C\uD83C\uDFFC", + [":raised_hands::skin-tone-3:"] = "\uD83D\uDE4C\uD83C\uDFFD", + [":raised_hands::skin-tone-4:"] = "\uD83D\uDE4C\uD83C\uDFFE", + [":raised_hands::skin-tone-5:"] = "\uD83D\uDE4C\uD83C\uDFFF", + [":raised_hands_tone1:"] = "\uD83D\uDE4C\uD83C\uDFFB", + [":raised_hands_tone2:"] = "\uD83D\uDE4C\uD83C\uDFFC", + [":raised_hands_tone3:"] = "\uD83D\uDE4C\uD83C\uDFFD", + [":raised_hands_tone4:"] = "\uD83D\uDE4C\uD83C\uDFFE", + [":raised_hands_tone5:"] = "\uD83D\uDE4C\uD83C\uDFFF", + [":raising_hand:"] = "\uD83D\uDE4B", + [":raising_hand::skin-tone-1:"] = "\uD83D\uDE4B\uD83C\uDFFB", + [":raising_hand::skin-tone-2:"] = "\uD83D\uDE4B\uD83C\uDFFC", + [":raising_hand::skin-tone-3:"] = "\uD83D\uDE4B\uD83C\uDFFD", + [":raising_hand::skin-tone-4:"] = "\uD83D\uDE4B\uD83C\uDFFE", + [":raising_hand::skin-tone-5:"] = "\uD83D\uDE4B\uD83C\uDFFF", + [":raising_hand_tone1:"] = "\uD83D\uDE4B\uD83C\uDFFB", + [":raising_hand_tone2:"] = "\uD83D\uDE4B\uD83C\uDFFC", + [":raising_hand_tone3:"] = "\uD83D\uDE4B\uD83C\uDFFD", + [":raising_hand_tone4:"] = "\uD83D\uDE4B\uD83C\uDFFE", + [":raising_hand_tone5:"] = "\uD83D\uDE4B\uD83C\uDFFF", + [":ram:"] = "\uD83D\uDC0F", + [":ramen:"] = "\uD83C\uDF5C", + [":rat:"] = "\uD83D\uDC00", + [":razor:"] = "\uD83E\uDE92", + [":receipt:"] = "\uD83E\uDDFE", + [":record_button:"] = "⏺️", + [":recycle:"] = "♻️", + [":red_car:"] = "\uD83D\uDE97", + [":red_circle:"] = "\uD83D\uDD34", + [":red_envelope:"] = "\uD83E\uDDE7", + [":red_square:"] = "\uD83D\uDFE5", + [":regional_indicator_a:"] = "\uD83C\uDDE6", + [":regional_indicator_b:"] = "\uD83C\uDDE7", + [":regional_indicator_c:"] = "\uD83C\uDDE8", + [":regional_indicator_d:"] = "\uD83C\uDDE9", + [":regional_indicator_e:"] = "\uD83C\uDDEA", + [":regional_indicator_f:"] = "\uD83C\uDDEB", + [":regional_indicator_g:"] = "\uD83C\uDDEC", + [":regional_indicator_h:"] = "\uD83C\uDDED", + [":regional_indicator_i:"] = "\uD83C\uDDEE", + [":regional_indicator_j:"] = "\uD83C\uDDEF", + [":regional_indicator_k:"] = "\uD83C\uDDF0", + [":regional_indicator_l:"] = "\uD83C\uDDF1", + [":regional_indicator_m:"] = "\uD83C\uDDF2", + [":regional_indicator_n:"] = "\uD83C\uDDF3", + [":regional_indicator_o:"] = "\uD83C\uDDF4", + [":regional_indicator_p:"] = "\uD83C\uDDF5", + [":regional_indicator_q:"] = "\uD83C\uDDF6", + [":regional_indicator_r:"] = "\uD83C\uDDF7", + [":regional_indicator_s:"] = "\uD83C\uDDF8", + [":regional_indicator_t:"] = "\uD83C\uDDF9", + [":regional_indicator_u:"] = "\uD83C\uDDFA", + [":regional_indicator_v:"] = "\uD83C\uDDFB", + [":regional_indicator_w:"] = "\uD83C\uDDFC", + [":regional_indicator_x:"] = "\uD83C\uDDFD", + [":regional_indicator_y:"] = "\uD83C\uDDFE", + [":regional_indicator_z:"] = "\uD83C\uDDFF", + [":registered:"] = "®️", + [":relaxed:"] = "☺️", + [":relieved:"] = "\uD83D\uDE0C", + [":reminder_ribbon:"] = "\uD83C\uDF97️", + [":repeat:"] = "\uD83D\uDD01", + [":repeat_one:"] = "\uD83D\uDD02", + [":restroom:"] = "\uD83D\uDEBB", + [":reversed_hand_with_middle_finger_extended:"] = "\uD83D\uDD95", + [":reversed_hand_with_middle_finger_extended::skin-tone-1:"] = "\uD83D\uDD95\uD83C\uDFFB", + [":reversed_hand_with_middle_finger_extended::skin-tone-2:"] = "\uD83D\uDD95\uD83C\uDFFC", + [":reversed_hand_with_middle_finger_extended::skin-tone-3:"] = "\uD83D\uDD95\uD83C\uDFFD", + [":reversed_hand_with_middle_finger_extended::skin-tone-4:"] = "\uD83D\uDD95\uD83C\uDFFE", + [":reversed_hand_with_middle_finger_extended::skin-tone-5:"] = "\uD83D\uDD95\uD83C\uDFFF", + [":reversed_hand_with_middle_finger_extended_tone1:"] = "\uD83D\uDD95\uD83C\uDFFB", + [":reversed_hand_with_middle_finger_extended_tone2:"] = "\uD83D\uDD95\uD83C\uDFFC", + [":reversed_hand_with_middle_finger_extended_tone3:"] = "\uD83D\uDD95\uD83C\uDFFD", + [":reversed_hand_with_middle_finger_extended_tone4:"] = "\uD83D\uDD95\uD83C\uDFFE", + [":reversed_hand_with_middle_finger_extended_tone5:"] = "\uD83D\uDD95\uD83C\uDFFF", + [":revolving_hearts:"] = "\uD83D\uDC9E", + [":rewind:"] = "⏪", + [":rhino:"] = "\uD83E\uDD8F", + [":rhinoceros:"] = "\uD83E\uDD8F", + [":ribbon:"] = "\uD83C\uDF80", + [":rice:"] = "\uD83C\uDF5A", + [":rice_ball:"] = "\uD83C\uDF59", + [":rice_cracker:"] = "\uD83C\uDF58", + [":rice_scene:"] = "\uD83C\uDF91", + [":right_anger_bubble:"] = "\uD83D\uDDEF️", + [":right_facing_fist:"] = "\uD83E\uDD1C", + [":right_facing_fist::skin-tone-1:"] = "\uD83E\uDD1C\uD83C\uDFFB", + [":right_facing_fist::skin-tone-2:"] = "\uD83E\uDD1C\uD83C\uDFFC", + [":right_facing_fist::skin-tone-3:"] = "\uD83E\uDD1C\uD83C\uDFFD", + [":right_facing_fist::skin-tone-4:"] = "\uD83E\uDD1C\uD83C\uDFFE", + [":right_facing_fist::skin-tone-5:"] = "\uD83E\uDD1C\uD83C\uDFFF", + [":right_facing_fist_tone1:"] = "\uD83E\uDD1C\uD83C\uDFFB", + [":right_facing_fist_tone2:"] = "\uD83E\uDD1C\uD83C\uDFFC", + [":right_facing_fist_tone3:"] = "\uD83E\uDD1C\uD83C\uDFFD", + [":right_facing_fist_tone4:"] = "\uD83E\uDD1C\uD83C\uDFFE", + [":right_facing_fist_tone5:"] = "\uD83E\uDD1C\uD83C\uDFFF", + [":right_fist:"] = "\uD83E\uDD1C", + [":right_fist::skin-tone-1:"] = "\uD83E\uDD1C\uD83C\uDFFB", + [":right_fist::skin-tone-2:"] = "\uD83E\uDD1C\uD83C\uDFFC", + [":right_fist::skin-tone-3:"] = "\uD83E\uDD1C\uD83C\uDFFD", + [":right_fist::skin-tone-4:"] = "\uD83E\uDD1C\uD83C\uDFFE", + [":right_fist::skin-tone-5:"] = "\uD83E\uDD1C\uD83C\uDFFF", + [":right_fist_tone1:"] = "\uD83E\uDD1C\uD83C\uDFFB", + [":right_fist_tone2:"] = "\uD83E\uDD1C\uD83C\uDFFC", + [":right_fist_tone3:"] = "\uD83E\uDD1C\uD83C\uDFFD", + [":right_fist_tone4:"] = "\uD83E\uDD1C\uD83C\uDFFE", + [":right_fist_tone5:"] = "\uD83E\uDD1C\uD83C\uDFFF", + [":ring:"] = "\uD83D\uDC8D", + [":ringed_planet:"] = "\uD83E\uDE90", + [":robot:"] = "\uD83E\uDD16", + [":robot_face:"] = "\uD83E\uDD16", + [":rocket:"] = "\uD83D\uDE80", + [":rofl:"] = "\uD83E\uDD23", + [":roll_of_paper:"] = "\uD83E\uDDFB", + [":rolled_up_newspaper:"] = "\uD83D\uDDDE️", + [":roller_coaster:"] = "\uD83C\uDFA2", + [":rolling_eyes:"] = "\uD83D\uDE44", + [":rolling_on_the_floor_laughing:"] = "\uD83E\uDD23", + [":rooster:"] = "\uD83D\uDC13", + [":rose:"] = "\uD83C\uDF39", + [":rosette:"] = "\uD83C\uDFF5️", + [":rotating_light:"] = "\uD83D\uDEA8", + [":round_pushpin:"] = "\uD83D\uDCCD", + [":rowboat:"] = "\uD83D\uDEA3", + [":rowboat::skin-tone-1:"] = "\uD83D\uDEA3\uD83C\uDFFB", + [":rowboat::skin-tone-2:"] = "\uD83D\uDEA3\uD83C\uDFFC", + [":rowboat::skin-tone-3:"] = "\uD83D\uDEA3\uD83C\uDFFD", + [":rowboat::skin-tone-4:"] = "\uD83D\uDEA3\uD83C\uDFFE", + [":rowboat::skin-tone-5:"] = "\uD83D\uDEA3\uD83C\uDFFF", + [":rowboat_tone1:"] = "\uD83D\uDEA3\uD83C\uDFFB", + [":rowboat_tone2:"] = "\uD83D\uDEA3\uD83C\uDFFC", + [":rowboat_tone3:"] = "\uD83D\uDEA3\uD83C\uDFFD", + [":rowboat_tone4:"] = "\uD83D\uDEA3\uD83C\uDFFE", + [":rowboat_tone5:"] = "\uD83D\uDEA3\uD83C\uDFFF", + [":rugby_football:"] = "\uD83C\uDFC9", + [":runner:"] = "\uD83C\uDFC3", + [":runner::skin-tone-1:"] = "\uD83C\uDFC3\uD83C\uDFFB", + [":runner::skin-tone-2:"] = "\uD83C\uDFC3\uD83C\uDFFC", + [":runner::skin-tone-3:"] = "\uD83C\uDFC3\uD83C\uDFFD", + [":runner::skin-tone-4:"] = "\uD83C\uDFC3\uD83C\uDFFE", + [":runner::skin-tone-5:"] = "\uD83C\uDFC3\uD83C\uDFFF", + [":runner_tone1:"] = "\uD83C\uDFC3\uD83C\uDFFB", + [":runner_tone2:"] = "\uD83C\uDFC3\uD83C\uDFFC", + [":runner_tone3:"] = "\uD83C\uDFC3\uD83C\uDFFD", + [":runner_tone4:"] = "\uD83C\uDFC3\uD83C\uDFFE", + [":runner_tone5:"] = "\uD83C\uDFC3\uD83C\uDFFF", + [":running_shirt_with_sash:"] = "\uD83C\uDFBD", + [":s"] = "\uD83D\uDE12", + [":sa:"] = "\uD83C\uDE02️", + [":safety_pin:"] = "\uD83E\uDDF7", + [":safety_vest:"] = "\uD83E\uDDBA", + [":sagittarius:"] = "♐", + [":sailboat:"] = "⛵", + [":sake:"] = "\uD83C\uDF76", + [":salad:"] = "\uD83E\uDD57", + [":salt:"] = "\uD83E\uDDC2", + [":sandal:"] = "\uD83D\uDC61", + [":sandwich:"] = "\uD83E\uDD6A", + [":santa:"] = "\uD83C\uDF85", + [":santa::skin-tone-1:"] = "\uD83C\uDF85\uD83C\uDFFB", + [":santa::skin-tone-2:"] = "\uD83C\uDF85\uD83C\uDFFC", + [":santa::skin-tone-3:"] = "\uD83C\uDF85\uD83C\uDFFD", + [":santa::skin-tone-4:"] = "\uD83C\uDF85\uD83C\uDFFE", + [":santa::skin-tone-5:"] = "\uD83C\uDF85\uD83C\uDFFF", + [":santa_tone1:"] = "\uD83C\uDF85\uD83C\uDFFB", + [":santa_tone2:"] = "\uD83C\uDF85\uD83C\uDFFC", + [":santa_tone3:"] = "\uD83C\uDF85\uD83C\uDFFD", + [":santa_tone4:"] = "\uD83C\uDF85\uD83C\uDFFE", + [":santa_tone5:"] = "\uD83C\uDF85\uD83C\uDFFF", + [":sari:"] = "\uD83E\uDD7B", + [":satellite:"] = "\uD83D\uDCE1", + [":satellite_orbital:"] = "\uD83D\uDEF0️", + [":satisfied:"] = "\uD83D\uDE06", + [":sauropod:"] = "\uD83E\uDD95", + [":saxophone:"] = "\uD83C\uDFB7", + [":scales:"] = "⚖️", + [":scarf:"] = "\uD83E\uDDE3", + [":school:"] = "\uD83C\uDFEB", + [":school_satchel:"] = "\uD83C\uDF92", + [":scissors:"] = "✂️", + [":scooter:"] = "\uD83D\uDEF4", + [":scorpion:"] = "\uD83E\uDD82", + [":scorpius:"] = "♏", + [":scotland:"] = "\uD83C\uDFF4\uDB40\uDC67\uDB40\uDC62\uDB40\uDC73\uDB40\uDC63\uDB40\uDC74\uDB40\uDC7F", + [":scream:"] = "\uD83D\uDE31", + [":scream_cat:"] = "\uD83D\uDE40", + [":scroll:"] = "\uD83D\uDCDC", + [":seat:"] = "\uD83D\uDCBA", + [":second_place:"] = "\uD83E\uDD48", + [":second_place_medal:"] = "\uD83E\uDD48", + [":secret:"] = "㊙️", + [":see_no_evil:"] = "\uD83D\uDE48", + [":seedling:"] = "\uD83C\uDF31", + [":selfie:"] = "\uD83E\uDD33", + [":selfie::skin-tone-1:"] = "\uD83E\uDD33\uD83C\uDFFB", + [":selfie::skin-tone-2:"] = "\uD83E\uDD33\uD83C\uDFFC", + [":selfie::skin-tone-3:"] = "\uD83E\uDD33\uD83C\uDFFD", + [":selfie::skin-tone-4:"] = "\uD83E\uDD33\uD83C\uDFFE", + [":selfie::skin-tone-5:"] = "\uD83E\uDD33\uD83C\uDFFF", + [":selfie_tone1:"] = "\uD83E\uDD33\uD83C\uDFFB", + [":selfie_tone2:"] = "\uD83E\uDD33\uD83C\uDFFC", + [":selfie_tone3:"] = "\uD83E\uDD33\uD83C\uDFFD", + [":selfie_tone4:"] = "\uD83E\uDD33\uD83C\uDFFE", + [":selfie_tone5:"] = "\uD83E\uDD33\uD83C\uDFFF", + [":service_dog:"] = "\uD83D\uDC15\u200D\uD83E\uDDBA", + [":seven:"] = "7️⃣", + [":shaking_hands:"] = "\uD83E\uDD1D", + [":shallow_pan_of_food:"] = "\uD83E\uDD58", + [":shamrock:"] = "☘️", + [":shark:"] = "\uD83E\uDD88", + [":shaved_ice:"] = "\uD83C\uDF67", + [":sheep:"] = "\uD83D\uDC11", + [":shell:"] = "\uD83D\uDC1A", + [":shelled_peanut:"] = "\uD83E\uDD5C", + [":shield:"] = "\uD83D\uDEE1️", + [":shinto_shrine:"] = "⛩️", + [":ship:"] = "\uD83D\uDEA2", + [":shirt:"] = "\uD83D\uDC55", + [":shit:"] = "\uD83D\uDCA9", + [":shopping_bags:"] = "\uD83D\uDECD️", + [":shopping_cart:"] = "\uD83D\uDED2", + [":shopping_trolley:"] = "\uD83D\uDED2", + [":shorts:"] = "\uD83E\uDE73", + [":shower:"] = "\uD83D\uDEBF", + [":shrimp:"] = "\uD83E\uDD90", + [":shrug:"] = "\uD83E\uDD37", + [":shrug::skin-tone-1:"] = "\uD83E\uDD37\uD83C\uDFFB", + [":shrug::skin-tone-2:"] = "\uD83E\uDD37\uD83C\uDFFC", + [":shrug::skin-tone-3:"] = "\uD83E\uDD37\uD83C\uDFFD", + [":shrug::skin-tone-4:"] = "\uD83E\uDD37\uD83C\uDFFE", + [":shrug::skin-tone-5:"] = "\uD83E\uDD37\uD83C\uDFFF", + [":shrug_tone1:"] = "\uD83E\uDD37\uD83C\uDFFB", + [":shrug_tone2:"] = "\uD83E\uDD37\uD83C\uDFFC", + [":shrug_tone3:"] = "\uD83E\uDD37\uD83C\uDFFD", + [":shrug_tone4:"] = "\uD83E\uDD37\uD83C\uDFFE", + [":shrug_tone5:"] = "\uD83E\uDD37\uD83C\uDFFF", + [":shushing_face:"] = "\uD83E\uDD2B", + [":sick:"] = "\uD83E\uDD22", + [":sign_of_the_horns:"] = "\uD83E\uDD18", + [":sign_of_the_horns::skin-tone-1:"] = "\uD83E\uDD18\uD83C\uDFFB", + [":sign_of_the_horns::skin-tone-2:"] = "\uD83E\uDD18\uD83C\uDFFC", + [":sign_of_the_horns::skin-tone-3:"] = "\uD83E\uDD18\uD83C\uDFFD", + [":sign_of_the_horns::skin-tone-4:"] = "\uD83E\uDD18\uD83C\uDFFE", + [":sign_of_the_horns::skin-tone-5:"] = "\uD83E\uDD18\uD83C\uDFFF", + [":sign_of_the_horns_tone1:"] = "\uD83E\uDD18\uD83C\uDFFB", + [":sign_of_the_horns_tone2:"] = "\uD83E\uDD18\uD83C\uDFFC", + [":sign_of_the_horns_tone3:"] = "\uD83E\uDD18\uD83C\uDFFD", + [":sign_of_the_horns_tone4:"] = "\uD83E\uDD18\uD83C\uDFFE", + [":sign_of_the_horns_tone5:"] = "\uD83E\uDD18\uD83C\uDFFF", + [":signal_strength:"] = "\uD83D\uDCF6", + [":six:"] = "6️⃣", + [":six_pointed_star:"] = "\uD83D\uDD2F", + [":skateboard:"] = "\uD83D\uDEF9", + [":skeleton:"] = "\uD83D\uDC80", + [":ski:"] = "\uD83C\uDFBF", + [":skier:"] = "⛷️", + [":skull:"] = "\uD83D\uDC80", + [":skull_and_crossbones:"] = "☠️", + [":skull_crossbones:"] = "☠️", + [":skunk:"] = "\uD83E\uDDA8", + [":sled:"] = "\uD83D\uDEF7", + [":sleeping:"] = "\uD83D\uDE34", + [":sleeping_accommodation:"] = "\uD83D\uDECC", + [":sleeping_accommodation::skin-tone-1:"] = "\uD83D\uDECC\uD83C\uDFFB", + [":sleeping_accommodation::skin-tone-2:"] = "\uD83D\uDECC\uD83C\uDFFC", + [":sleeping_accommodation::skin-tone-3:"] = "\uD83D\uDECC\uD83C\uDFFD", + [":sleeping_accommodation::skin-tone-4:"] = "\uD83D\uDECC\uD83C\uDFFE", + [":sleeping_accommodation::skin-tone-5:"] = "\uD83D\uDECC\uD83C\uDFFF", + [":sleepy:"] = "\uD83D\uDE2A", + [":sleuth_or_spy:"] = "\uD83D\uDD75️", + [":sleuth_or_spy::skin-tone-1:"] = "\uD83D\uDD75\uD83C\uDFFB", + [":sleuth_or_spy::skin-tone-2:"] = "\uD83D\uDD75\uD83C\uDFFC", + [":sleuth_or_spy::skin-tone-3:"] = "\uD83D\uDD75\uD83C\uDFFD", + [":sleuth_or_spy::skin-tone-4:"] = "\uD83D\uDD75\uD83C\uDFFE", + [":sleuth_or_spy::skin-tone-5:"] = "\uD83D\uDD75\uD83C\uDFFF", + [":sleuth_or_spy_tone1:"] = "\uD83D\uDD75\uD83C\uDFFB", + [":sleuth_or_spy_tone2:"] = "\uD83D\uDD75\uD83C\uDFFC", + [":sleuth_or_spy_tone3:"] = "\uD83D\uDD75\uD83C\uDFFD", + [":sleuth_or_spy_tone4:"] = "\uD83D\uDD75\uD83C\uDFFE", + [":sleuth_or_spy_tone5:"] = "\uD83D\uDD75\uD83C\uDFFF", + [":slight_frown:"] = "\uD83D\uDE41", + [":slight_smile:"] = "\uD83D\uDE42", + [":slightly_frowning_face:"] = "\uD83D\uDE41", + [":slightly_smiling_face:"] = "\uD83D\uDE42", + [":slot_machine:"] = "\uD83C\uDFB0", + [":sloth:"] = "\uD83E\uDDA5", + [":small_airplane:"] = "\uD83D\uDEE9️", + [":small_blue_diamond:"] = "\uD83D\uDD39", + [":small_orange_diamond:"] = "\uD83D\uDD38", + [":small_red_triangle:"] = "\uD83D\uDD3A", + [":small_red_triangle_down:"] = "\uD83D\uDD3B", + [":smile:"] = "\uD83D\uDE04", + [":smile_cat:"] = "\uD83D\uDE38", + [":smiley:"] = "\uD83D\uDE03", + [":smiley_cat:"] = "\uD83D\uDE3A", + [":smiling_face_with_3_hearts:"] = "\uD83E\uDD70", + [":smiling_imp:"] = "\uD83D\uDE08", + [":smirk:"] = "\uD83D\uDE0F", + [":smirk_cat:"] = "\uD83D\uDE3C", + [":smoking:"] = "\uD83D\uDEAC", + [":snail:"] = "\uD83D\uDC0C", + [":snake:"] = "\uD83D\uDC0D", + [":sneeze:"] = "\uD83E\uDD27", + [":sneezing_face:"] = "\uD83E\uDD27", + [":snow_capped_mountain:"] = "\uD83C\uDFD4️", + [":snowboarder:"] = "\uD83C\uDFC2", + [":snowboarder::skin-tone-1:"] = "\uD83C\uDFC2\uD83C\uDFFB", + [":snowboarder::skin-tone-2:"] = "\uD83C\uDFC2\uD83C\uDFFC", + [":snowboarder::skin-tone-3:"] = "\uD83C\uDFC2\uD83C\uDFFD", + [":snowboarder::skin-tone-4:"] = "\uD83C\uDFC2\uD83C\uDFFE", + [":snowboarder::skin-tone-5:"] = "\uD83C\uDFC2\uD83C\uDFFF", + [":snowboarder_dark_skin_tone:"] = "\uD83C\uDFC2\uD83C\uDFFF", + [":snowboarder_light_skin_tone:"] = "\uD83C\uDFC2\uD83C\uDFFB", + [":snowboarder_medium_dark_skin_tone:"] = "\uD83C\uDFC2\uD83C\uDFFE", + [":snowboarder_medium_light_skin_tone:"] = "\uD83C\uDFC2\uD83C\uDFFC", + [":snowboarder_medium_skin_tone:"] = "\uD83C\uDFC2\uD83C\uDFFD", + [":snowboarder_tone1:"] = "\uD83C\uDFC2\uD83C\uDFFB", + [":snowboarder_tone2:"] = "\uD83C\uDFC2\uD83C\uDFFC", + [":snowboarder_tone3:"] = "\uD83C\uDFC2\uD83C\uDFFD", + [":snowboarder_tone4:"] = "\uD83C\uDFC2\uD83C\uDFFE", + [":snowboarder_tone5:"] = "\uD83C\uDFC2\uD83C\uDFFF", + [":snowflake:"] = "❄️", + [":snowman2:"] = "☃️", + [":snowman:"] = "⛄", + [":soap:"] = "\uD83E\uDDFC", + [":sob:"] = "\uD83D\uDE2D", + [":soccer:"] = "⚽", + [":socks:"] = "\uD83E\uDDE6", + [":softball:"] = "\uD83E\uDD4E", + [":soon:"] = "\uD83D\uDD1C", + [":sos:"] = "\uD83C\uDD98", + [":sound:"] = "\uD83D\uDD09", + [":space_invader:"] = "\uD83D\uDC7E", + [":spades:"] = "♠️", + [":spaghetti:"] = "\uD83C\uDF5D", + [":sparkle:"] = "❇️", + [":sparkler:"] = "\uD83C\uDF87", + [":sparkles:"] = "✨", + [":sparkling_heart:"] = "\uD83D\uDC96", + [":speak_no_evil:"] = "\uD83D\uDE4A", + [":speaker:"] = "\uD83D\uDD08", + [":speaking_head:"] = "\uD83D\uDDE3️", + [":speaking_head_in_silhouette:"] = "\uD83D\uDDE3️", + [":speech_balloon:"] = "\uD83D\uDCAC", + [":speech_left:"] = "\uD83D\uDDE8️", + [":speedboat:"] = "\uD83D\uDEA4", + [":spider:"] = "\uD83D\uDD77️", + [":spider_web:"] = "\uD83D\uDD78️", + [":spiral_calendar_pad:"] = "\uD83D\uDDD3️", + [":spiral_note_pad:"] = "\uD83D\uDDD2️", + [":sponge:"] = "\uD83E\uDDFD", + [":spoon:"] = "\uD83E\uDD44", + [":sports_medal:"] = "\uD83C\uDFC5", + [":spy:"] = "\uD83D\uDD75️", + [":spy::skin-tone-1:"] = "\uD83D\uDD75\uD83C\uDFFB", + [":spy::skin-tone-2:"] = "\uD83D\uDD75\uD83C\uDFFC", + [":spy::skin-tone-3:"] = "\uD83D\uDD75\uD83C\uDFFD", + [":spy::skin-tone-4:"] = "\uD83D\uDD75\uD83C\uDFFE", + [":spy::skin-tone-5:"] = "\uD83D\uDD75\uD83C\uDFFF", + [":spy_tone1:"] = "\uD83D\uDD75\uD83C\uDFFB", + [":spy_tone2:"] = "\uD83D\uDD75\uD83C\uDFFC", + [":spy_tone3:"] = "\uD83D\uDD75\uD83C\uDFFD", + [":spy_tone4:"] = "\uD83D\uDD75\uD83C\uDFFE", + [":spy_tone5:"] = "\uD83D\uDD75\uD83C\uDFFF", + [":squeeze_bottle:"] = "\uD83E\uDDF4", + [":squid:"] = "\uD83E\uDD91", + [":stadium:"] = "\uD83C\uDFDF️", + [":star2:"] = "\uD83C\uDF1F", + [":star:"] = "⭐", + [":star_and_crescent:"] = "☪️", + [":star_of_david:"] = "✡️", + [":star_struck:"] = "\uD83E\uDD29", + [":stars:"] = "\uD83C\uDF20", + [":station:"] = "\uD83D\uDE89", + [":statue_of_liberty:"] = "\uD83D\uDDFD", + [":steam_locomotive:"] = "\uD83D\uDE82", + [":stethoscope:"] = "\uD83E\uDE7A", + [":stew:"] = "\uD83C\uDF72", + [":stop_button:"] = "⏹️", + [":stop_sign:"] = "\uD83D\uDED1", + [":stopwatch:"] = "⏱️", + [":straight_ruler:"] = "\uD83D\uDCCF", + [":strawberry:"] = "\uD83C\uDF53", + [":stuck_out_tongue:"] = "\uD83D\uDE1B", + [":stuck_out_tongue_closed_eyes:"] = "\uD83D\uDE1D", + [":stuck_out_tongue_winking_eye:"] = "\uD83D\uDE1C", + [":studio_microphone:"] = "\uD83C\uDF99️", + [":stuffed_flatbread:"] = "\uD83E\uDD59", + [":stuffed_pita:"] = "\uD83E\uDD59", + [":sun_with_face:"] = "\uD83C\uDF1E", + [":sunflower:"] = "\uD83C\uDF3B", + [":sunglasses:"] = "\uD83D\uDE0E", + [":sunny:"] = "☀️", + [":sunrise:"] = "\uD83C\uDF05", + [":sunrise_over_mountains:"] = "\uD83C\uDF04", + [":superhero:"] = "\uD83E\uDDB8", + [":superhero::skin-tone-1:"] = "\uD83E\uDDB8\uD83C\uDFFB", + [":superhero::skin-tone-2:"] = "\uD83E\uDDB8\uD83C\uDFFC", + [":superhero::skin-tone-3:"] = "\uD83E\uDDB8\uD83C\uDFFD", + [":superhero::skin-tone-4:"] = "\uD83E\uDDB8\uD83C\uDFFE", + [":superhero::skin-tone-5:"] = "\uD83E\uDDB8\uD83C\uDFFF", + [":superhero_dark_skin_tone:"] = "\uD83E\uDDB8\uD83C\uDFFF", + [":superhero_light_skin_tone:"] = "\uD83E\uDDB8\uD83C\uDFFB", + [":superhero_medium_dark_skin_tone:"] = "\uD83E\uDDB8\uD83C\uDFFE", + [":superhero_medium_light_skin_tone:"] = "\uD83E\uDDB8\uD83C\uDFFC", + [":superhero_medium_skin_tone:"] = "\uD83E\uDDB8\uD83C\uDFFD", + [":superhero_tone1:"] = "\uD83E\uDDB8\uD83C\uDFFB", + [":superhero_tone2:"] = "\uD83E\uDDB8\uD83C\uDFFC", + [":superhero_tone3:"] = "\uD83E\uDDB8\uD83C\uDFFD", + [":superhero_tone4:"] = "\uD83E\uDDB8\uD83C\uDFFE", + [":superhero_tone5:"] = "\uD83E\uDDB8\uD83C\uDFFF", + [":supervillain:"] = "\uD83E\uDDB9", + [":supervillain::skin-tone-1:"] = "\uD83E\uDDB9\uD83C\uDFFB", + [":supervillain::skin-tone-2:"] = "\uD83E\uDDB9\uD83C\uDFFC", + [":supervillain::skin-tone-3:"] = "\uD83E\uDDB9\uD83C\uDFFD", + [":supervillain::skin-tone-4:"] = "\uD83E\uDDB9\uD83C\uDFFE", + [":supervillain::skin-tone-5:"] = "\uD83E\uDDB9\uD83C\uDFFF", + [":supervillain_dark_skin_tone:"] = "\uD83E\uDDB9\uD83C\uDFFF", + [":supervillain_light_skin_tone:"] = "\uD83E\uDDB9\uD83C\uDFFB", + [":supervillain_medium_dark_skin_tone:"] = "\uD83E\uDDB9\uD83C\uDFFE", + [":supervillain_medium_light_skin_tone:"] = "\uD83E\uDDB9\uD83C\uDFFC", + [":supervillain_medium_skin_tone:"] = "\uD83E\uDDB9\uD83C\uDFFD", + [":supervillain_tone1:"] = "\uD83E\uDDB9\uD83C\uDFFB", + [":supervillain_tone2:"] = "\uD83E\uDDB9\uD83C\uDFFC", + [":supervillain_tone3:"] = "\uD83E\uDDB9\uD83C\uDFFD", + [":supervillain_tone4:"] = "\uD83E\uDDB9\uD83C\uDFFE", + [":supervillain_tone5:"] = "\uD83E\uDDB9\uD83C\uDFFF", + [":surfer:"] = "\uD83C\uDFC4", + [":surfer::skin-tone-1:"] = "\uD83C\uDFC4\uD83C\uDFFB", + [":surfer::skin-tone-2:"] = "\uD83C\uDFC4\uD83C\uDFFC", + [":surfer::skin-tone-3:"] = "\uD83C\uDFC4\uD83C\uDFFD", + [":surfer::skin-tone-4:"] = "\uD83C\uDFC4\uD83C\uDFFE", + [":surfer::skin-tone-5:"] = "\uD83C\uDFC4\uD83C\uDFFF", + [":surfer_tone1:"] = "\uD83C\uDFC4\uD83C\uDFFB", + [":surfer_tone2:"] = "\uD83C\uDFC4\uD83C\uDFFC", + [":surfer_tone3:"] = "\uD83C\uDFC4\uD83C\uDFFD", + [":surfer_tone4:"] = "\uD83C\uDFC4\uD83C\uDFFE", + [":surfer_tone5:"] = "\uD83C\uDFC4\uD83C\uDFFF", + [":sushi:"] = "\uD83C\uDF63", + [":suspension_railway:"] = "\uD83D\uDE9F", + [":swan:"] = "\uD83E\uDDA2", + [":sweat:"] = "\uD83D\uDE13", + [":sweat_drops:"] = "\uD83D\uDCA6", + [":sweat_smile:"] = "\uD83D\uDE05", + [":sweet_potato:"] = "\uD83C\uDF60", + [":swimmer:"] = "\uD83C\uDFCA", + [":swimmer::skin-tone-1:"] = "\uD83C\uDFCA\uD83C\uDFFB", + [":swimmer::skin-tone-2:"] = "\uD83C\uDFCA\uD83C\uDFFC", + [":swimmer::skin-tone-3:"] = "\uD83C\uDFCA\uD83C\uDFFD", + [":swimmer::skin-tone-4:"] = "\uD83C\uDFCA\uD83C\uDFFE", + [":swimmer::skin-tone-5:"] = "\uD83C\uDFCA\uD83C\uDFFF", + [":swimmer_tone1:"] = "\uD83C\uDFCA\uD83C\uDFFB", + [":swimmer_tone2:"] = "\uD83C\uDFCA\uD83C\uDFFC", + [":swimmer_tone3:"] = "\uD83C\uDFCA\uD83C\uDFFD", + [":swimmer_tone4:"] = "\uD83C\uDFCA\uD83C\uDFFE", + [":swimmer_tone5:"] = "\uD83C\uDFCA\uD83C\uDFFF", + [":symbols:"] = "\uD83D\uDD23", + [":synagogue:"] = "\uD83D\uDD4D", + [":syringe:"] = "\uD83D\uDC89", + [":t_rex:"] = "\uD83E\uDD96", + [":table_tennis:"] = "\uD83C\uDFD3", + [":taco:"] = "\uD83C\uDF2E", + [":tada:"] = "\uD83C\uDF89", + [":takeout_box:"] = "\uD83E\uDD61", + [":tanabata_tree:"] = "\uD83C\uDF8B", + [":tangerine:"] = "\uD83C\uDF4A", + [":taurus:"] = "♉", + [":taxi:"] = "\uD83D\uDE95", + [":tea:"] = "\uD83C\uDF75", + [":teddy_bear:"] = "\uD83E\uDDF8", + [":telephone:"] = "☎️", + [":telephone_receiver:"] = "\uD83D\uDCDE", + [":telescope:"] = "\uD83D\uDD2D", + [":tennis:"] = "\uD83C\uDFBE", + [":tent:"] = "⛺", + [":test_tube:"] = "\uD83E\uDDEA", + [":thermometer:"] = "\uD83C\uDF21️", + [":thermometer_face:"] = "\uD83E\uDD12", + [":thinking:"] = "\uD83E\uDD14", + [":thinking_face:"] = "\uD83E\uDD14", + [":third_place:"] = "\uD83E\uDD49", + [":third_place_medal:"] = "\uD83E\uDD49", + [":thought_balloon:"] = "\uD83D\uDCAD", + [":thread:"] = "\uD83E\uDDF5", + [":three:"] = "3️⃣", + [":three_button_mouse:"] = "\uD83D\uDDB1️", + [":thumbdown:"] = "\uD83D\uDC4E", + [":thumbdown::skin-tone-1:"] = "\uD83D\uDC4E\uD83C\uDFFB", + [":thumbdown::skin-tone-2:"] = "\uD83D\uDC4E\uD83C\uDFFC", + [":thumbdown::skin-tone-3:"] = "\uD83D\uDC4E\uD83C\uDFFD", + [":thumbdown::skin-tone-4:"] = "\uD83D\uDC4E\uD83C\uDFFE", + [":thumbdown::skin-tone-5:"] = "\uD83D\uDC4E\uD83C\uDFFF", + [":thumbdown_tone1:"] = "\uD83D\uDC4E\uD83C\uDFFB", + [":thumbdown_tone2:"] = "\uD83D\uDC4E\uD83C\uDFFC", + [":thumbdown_tone3:"] = "\uD83D\uDC4E\uD83C\uDFFD", + [":thumbdown_tone4:"] = "\uD83D\uDC4E\uD83C\uDFFE", + [":thumbdown_tone5:"] = "\uD83D\uDC4E\uD83C\uDFFF", + [":thumbsdown:"] = "\uD83D\uDC4E", + [":thumbsdown::skin-tone-1:"] = "\uD83D\uDC4E\uD83C\uDFFB", + [":thumbsdown::skin-tone-2:"] = "\uD83D\uDC4E\uD83C\uDFFC", + [":thumbsdown::skin-tone-3:"] = "\uD83D\uDC4E\uD83C\uDFFD", + [":thumbsdown::skin-tone-4:"] = "\uD83D\uDC4E\uD83C\uDFFE", + [":thumbsdown::skin-tone-5:"] = "\uD83D\uDC4E\uD83C\uDFFF", + [":thumbsdown_tone1:"] = "\uD83D\uDC4E\uD83C\uDFFB", + [":thumbsdown_tone2:"] = "\uD83D\uDC4E\uD83C\uDFFC", + [":thumbsdown_tone3:"] = "\uD83D\uDC4E\uD83C\uDFFD", + [":thumbsdown_tone4:"] = "\uD83D\uDC4E\uD83C\uDFFE", + [":thumbsdown_tone5:"] = "\uD83D\uDC4E\uD83C\uDFFF", + [":thumbsup:"] = "\uD83D\uDC4D", + [":thumbsup::skin-tone-1:"] = "\uD83D\uDC4D\uD83C\uDFFB", + [":thumbsup::skin-tone-2:"] = "\uD83D\uDC4D\uD83C\uDFFC", + [":thumbsup::skin-tone-3:"] = "\uD83D\uDC4D\uD83C\uDFFD", + [":thumbsup::skin-tone-4:"] = "\uD83D\uDC4D\uD83C\uDFFE", + [":thumbsup::skin-tone-5:"] = "\uD83D\uDC4D\uD83C\uDFFF", + [":thumbsup_tone1:"] = "\uD83D\uDC4D\uD83C\uDFFB", + [":thumbsup_tone2:"] = "\uD83D\uDC4D\uD83C\uDFFC", + [":thumbsup_tone3:"] = "\uD83D\uDC4D\uD83C\uDFFD", + [":thumbsup_tone4:"] = "\uD83D\uDC4D\uD83C\uDFFE", + [":thumbsup_tone5:"] = "\uD83D\uDC4D\uD83C\uDFFF", + [":thumbup:"] = "\uD83D\uDC4D", + [":thumbup::skin-tone-1:"] = "\uD83D\uDC4D\uD83C\uDFFB", + [":thumbup::skin-tone-2:"] = "\uD83D\uDC4D\uD83C\uDFFC", + [":thumbup::skin-tone-3:"] = "\uD83D\uDC4D\uD83C\uDFFD", + [":thumbup::skin-tone-4:"] = "\uD83D\uDC4D\uD83C\uDFFE", + [":thumbup::skin-tone-5:"] = "\uD83D\uDC4D\uD83C\uDFFF", + [":thumbup_tone1:"] = "\uD83D\uDC4D\uD83C\uDFFB", + [":thumbup_tone2:"] = "\uD83D\uDC4D\uD83C\uDFFC", + [":thumbup_tone3:"] = "\uD83D\uDC4D\uD83C\uDFFD", + [":thumbup_tone4:"] = "\uD83D\uDC4D\uD83C\uDFFE", + [":thumbup_tone5:"] = "\uD83D\uDC4D\uD83C\uDFFF", + [":thunder_cloud_and_rain:"] = "⛈️", + [":thunder_cloud_rain:"] = "⛈️", + [":ticket:"] = "\uD83C\uDFAB", + [":tickets:"] = "\uD83C\uDF9F️", + [":tiger2:"] = "\uD83D\uDC05", + [":tiger:"] = "\uD83D\uDC2F", + [":timer:"] = "⏲️", + [":timer_clock:"] = "⏲️", + [":tired_face:"] = "\uD83D\uDE2B", + [":tm:"] = "™️", + [":toilet:"] = "\uD83D\uDEBD", + [":tokyo_tower:"] = "\uD83D\uDDFC", + [":tomato:"] = "\uD83C\uDF45", + [":tongue:"] = "\uD83D\uDC45", + [":toolbox:"] = "\uD83E\uDDF0", + [":tools:"] = "\uD83D\uDEE0️", + [":tooth:"] = "\uD83E\uDDB7", + [":top:"] = "\uD83D\uDD1D", + [":tophat:"] = "\uD83C\uDFA9", + [":track_next:"] = "⏭️", + [":track_previous:"] = "⏮️", + [":trackball:"] = "\uD83D\uDDB2️", + [":tractor:"] = "\uD83D\uDE9C", + [":traffic_light:"] = "\uD83D\uDEA5", + [":train2:"] = "\uD83D\uDE86", + [":train:"] = "\uD83D\uDE8B", + [":tram:"] = "\uD83D\uDE8A", + [":triangular_flag_on_post:"] = "\uD83D\uDEA9", + [":triangular_ruler:"] = "\uD83D\uDCD0", + [":trident:"] = "\uD83D\uDD31", + [":triumph:"] = "\uD83D\uDE24", + [":trolleybus:"] = "\uD83D\uDE8E", + [":trophy:"] = "\uD83C\uDFC6", + [":tropical_drink:"] = "\uD83C\uDF79", + [":tropical_fish:"] = "\uD83D\uDC20", + [":truck:"] = "\uD83D\uDE9A", + [":trumpet:"] = "\uD83C\uDFBA", + [":tulip:"] = "\uD83C\uDF37", + [":tumbler_glass:"] = "\uD83E\uDD43", + [":turkey:"] = "\uD83E\uDD83", + [":turtle:"] = "\uD83D\uDC22", + [":tuxedo_tone1:"] = "\uD83E\uDD35\uD83C\uDFFB", + [":tuxedo_tone2:"] = "\uD83E\uDD35\uD83C\uDFFC", + [":tuxedo_tone3:"] = "\uD83E\uDD35\uD83C\uDFFD", + [":tuxedo_tone4:"] = "\uD83E\uDD35\uD83C\uDFFE", + [":tuxedo_tone5:"] = "\uD83E\uDD35\uD83C\uDFFF", + [":tv:"] = "\uD83D\uDCFA", + [":twisted_rightwards_arrows:"] = "\uD83D\uDD00", + [":two:"] = "2️⃣", + [":two_hearts:"] = "\uD83D\uDC95", + [":two_men_holding_hands:"] = "\uD83D\uDC6C", + [":two_women_holding_hands:"] = "\uD83D\uDC6D", + [":u5272:"] = "\uD83C\uDE39", + [":u5408:"] = "\uD83C\uDE34", + [":u55b6:"] = "\uD83C\uDE3A", + [":u6307:"] = "\uD83C\uDE2F", + [":u6708:"] = "\uD83C\uDE37️", + [":u6709:"] = "\uD83C\uDE36", + [":u6e80:"] = "\uD83C\uDE35", + [":u7121:"] = "\uD83C\uDE1A", + [":u7533:"] = "\uD83C\uDE38", + [":u7981:"] = "\uD83C\uDE32", + [":u7a7a:"] = "\uD83C\uDE33", + [":umbrella2:"] = "☂️", + [":umbrella:"] = "☔", + [":umbrella_on_ground:"] = "⛱️", + [":unamused:"] = "\uD83D\uDE12", + [":underage:"] = "\uD83D\uDD1E", + [":unicorn:"] = "\uD83E\uDD84", + [":unicorn_face:"] = "\uD83E\uDD84", + [":united_nations:"] = "\uD83C\uDDFA\uD83C\uDDF3", + [":unlock:"] = "\uD83D\uDD13", + [":up:"] = "\uD83C\uDD99", + [":upside_down:"] = "\uD83D\uDE43", + [":upside_down_face:"] = "\uD83D\uDE43", + [":urn:"] = "⚱️", + [":v:"] = "✌️", + [":v::skin-tone-1:"] = "✌\uD83C\uDFFB", + [":v::skin-tone-2:"] = "✌\uD83C\uDFFC", + [":v::skin-tone-3:"] = "✌\uD83C\uDFFD", + [":v::skin-tone-4:"] = "✌\uD83C\uDFFE", + [":v::skin-tone-5:"] = "✌\uD83C\uDFFF", + [":v_tone1:"] = "✌\uD83C\uDFFB", + [":v_tone2:"] = "✌\uD83C\uDFFC", + [":v_tone3:"] = "✌\uD83C\uDFFD", + [":v_tone4:"] = "✌\uD83C\uDFFE", + [":v_tone5:"] = "✌\uD83C\uDFFF", + [":vampire:"] = "\uD83E\uDDDB", + [":vampire::skin-tone-1:"] = "\uD83E\uDDDB\uD83C\uDFFB", + [":vampire::skin-tone-2:"] = "\uD83E\uDDDB\uD83C\uDFFC", + [":vampire::skin-tone-3:"] = "\uD83E\uDDDB\uD83C\uDFFD", + [":vampire::skin-tone-4:"] = "\uD83E\uDDDB\uD83C\uDFFE", + [":vampire::skin-tone-5:"] = "\uD83E\uDDDB\uD83C\uDFFF", + [":vampire_dark_skin_tone:"] = "\uD83E\uDDDB\uD83C\uDFFF", + [":vampire_light_skin_tone:"] = "\uD83E\uDDDB\uD83C\uDFFB", + [":vampire_medium_dark_skin_tone:"] = "\uD83E\uDDDB\uD83C\uDFFE", + [":vampire_medium_light_skin_tone:"] = "\uD83E\uDDDB\uD83C\uDFFC", + [":vampire_medium_skin_tone:"] = "\uD83E\uDDDB\uD83C\uDFFD", + [":vampire_tone1:"] = "\uD83E\uDDDB\uD83C\uDFFB", + [":vampire_tone2:"] = "\uD83E\uDDDB\uD83C\uDFFC", + [":vampire_tone3:"] = "\uD83E\uDDDB\uD83C\uDFFD", + [":vampire_tone4:"] = "\uD83E\uDDDB\uD83C\uDFFE", + [":vampire_tone5:"] = "\uD83E\uDDDB\uD83C\uDFFF", + [":vertical_traffic_light:"] = "\uD83D\uDEA6", + [":vhs:"] = "\uD83D\uDCFC", + [":vibration_mode:"] = "\uD83D\uDCF3", + [":video_camera:"] = "\uD83D\uDCF9", + [":video_game:"] = "\uD83C\uDFAE", + [":violin:"] = "\uD83C\uDFBB", + [":virgo:"] = "♍", + [":volcano:"] = "\uD83C\uDF0B", + [":volleyball:"] = "\uD83C\uDFD0", + [":vs:"] = "\uD83C\uDD9A", + [":vulcan:"] = "\uD83D\uDD96", + [":vulcan::skin-tone-1:"] = "\uD83D\uDD96\uD83C\uDFFB", + [":vulcan::skin-tone-2:"] = "\uD83D\uDD96\uD83C\uDFFC", + [":vulcan::skin-tone-3:"] = "\uD83D\uDD96\uD83C\uDFFD", + [":vulcan::skin-tone-4:"] = "\uD83D\uDD96\uD83C\uDFFE", + [":vulcan::skin-tone-5:"] = "\uD83D\uDD96\uD83C\uDFFF", + [":vulcan_tone1:"] = "\uD83D\uDD96\uD83C\uDFFB", + [":vulcan_tone2:"] = "\uD83D\uDD96\uD83C\uDFFC", + [":vulcan_tone3:"] = "\uD83D\uDD96\uD83C\uDFFD", + [":vulcan_tone4:"] = "\uD83D\uDD96\uD83C\uDFFE", + [":vulcan_tone5:"] = "\uD83D\uDD96\uD83C\uDFFF", + [":waffle:"] = "\uD83E\uDDC7", + [":wales:"] = "\uD83C\uDFF4\uDB40\uDC67\uDB40\uDC62\uDB40\uDC77\uDB40\uDC6C\uDB40\uDC73\uDB40\uDC7F", + [":walking:"] = "\uD83D\uDEB6", + [":walking::skin-tone-1:"] = "\uD83D\uDEB6\uD83C\uDFFB", + [":walking::skin-tone-2:"] = "\uD83D\uDEB6\uD83C\uDFFC", + [":walking::skin-tone-3:"] = "\uD83D\uDEB6\uD83C\uDFFD", + [":walking::skin-tone-4:"] = "\uD83D\uDEB6\uD83C\uDFFE", + [":walking::skin-tone-5:"] = "\uD83D\uDEB6\uD83C\uDFFF", + [":walking_tone1:"] = "\uD83D\uDEB6\uD83C\uDFFB", + [":walking_tone2:"] = "\uD83D\uDEB6\uD83C\uDFFC", + [":walking_tone3:"] = "\uD83D\uDEB6\uD83C\uDFFD", + [":walking_tone4:"] = "\uD83D\uDEB6\uD83C\uDFFE", + [":walking_tone5:"] = "\uD83D\uDEB6\uD83C\uDFFF", + [":waning_crescent_moon:"] = "\uD83C\uDF18", + [":waning_gibbous_moon:"] = "\uD83C\uDF16", + [":warning:"] = "⚠️", + [":wastebasket:"] = "\uD83D\uDDD1️", + [":watch:"] = "⌚", + [":water_buffalo:"] = "\uD83D\uDC03", + [":water_polo:"] = "\uD83E\uDD3D", + [":water_polo::skin-tone-1:"] = "\uD83E\uDD3D\uD83C\uDFFB", + [":water_polo::skin-tone-2:"] = "\uD83E\uDD3D\uD83C\uDFFC", + [":water_polo::skin-tone-3:"] = "\uD83E\uDD3D\uD83C\uDFFD", + [":water_polo::skin-tone-4:"] = "\uD83E\uDD3D\uD83C\uDFFE", + [":water_polo::skin-tone-5:"] = "\uD83E\uDD3D\uD83C\uDFFF", + [":water_polo_tone1:"] = "\uD83E\uDD3D\uD83C\uDFFB", + [":water_polo_tone2:"] = "\uD83E\uDD3D\uD83C\uDFFC", + [":water_polo_tone3:"] = "\uD83E\uDD3D\uD83C\uDFFD", + [":water_polo_tone4:"] = "\uD83E\uDD3D\uD83C\uDFFE", + [":water_polo_tone5:"] = "\uD83E\uDD3D\uD83C\uDFFF", + [":watermelon:"] = "\uD83C\uDF49", + [":wave:"] = "\uD83D\uDC4B", + [":wave::skin-tone-1:"] = "\uD83D\uDC4B\uD83C\uDFFB", + [":wave::skin-tone-2:"] = "\uD83D\uDC4B\uD83C\uDFFC", + [":wave::skin-tone-3:"] = "\uD83D\uDC4B\uD83C\uDFFD", + [":wave::skin-tone-4:"] = "\uD83D\uDC4B\uD83C\uDFFE", + [":wave::skin-tone-5:"] = "\uD83D\uDC4B\uD83C\uDFFF", + [":wave_tone1:"] = "\uD83D\uDC4B\uD83C\uDFFB", + [":wave_tone2:"] = "\uD83D\uDC4B\uD83C\uDFFC", + [":wave_tone3:"] = "\uD83D\uDC4B\uD83C\uDFFD", + [":wave_tone4:"] = "\uD83D\uDC4B\uD83C\uDFFE", + [":wave_tone5:"] = "\uD83D\uDC4B\uD83C\uDFFF", + [":wavy_dash:"] = "〰️", + [":waxing_crescent_moon:"] = "\uD83C\uDF12", + [":waxing_gibbous_moon:"] = "\uD83C\uDF14", + [":wc:"] = "\uD83D\uDEBE", + [":weary:"] = "\uD83D\uDE29", + [":wedding:"] = "\uD83D\uDC92", + [":weight_lifter:"] = "\uD83C\uDFCB️", + [":weight_lifter::skin-tone-1:"] = "\uD83C\uDFCB\uD83C\uDFFB", + [":weight_lifter::skin-tone-2:"] = "\uD83C\uDFCB\uD83C\uDFFC", + [":weight_lifter::skin-tone-3:"] = "\uD83C\uDFCB\uD83C\uDFFD", + [":weight_lifter::skin-tone-4:"] = "\uD83C\uDFCB\uD83C\uDFFE", + [":weight_lifter::skin-tone-5:"] = "\uD83C\uDFCB\uD83C\uDFFF", + [":weight_lifter_tone1:"] = "\uD83C\uDFCB\uD83C\uDFFB", + [":weight_lifter_tone2:"] = "\uD83C\uDFCB\uD83C\uDFFC", + [":weight_lifter_tone3:"] = "\uD83C\uDFCB\uD83C\uDFFD", + [":weight_lifter_tone4:"] = "\uD83C\uDFCB\uD83C\uDFFE", + [":weight_lifter_tone5:"] = "\uD83C\uDFCB\uD83C\uDFFF", + [":whale2:"] = "\uD83D\uDC0B", + [":whale:"] = "\uD83D\uDC33", + [":wheel_of_dharma:"] = "☸️", + [":wheelchair:"] = "♿", + [":whisky:"] = "\uD83E\uDD43", + [":white_check_mark:"] = "✅", + [":white_circle:"] = "⚪", + [":white_flower:"] = "\uD83D\uDCAE", + [":white_frowning_face:"] = "☹️", + [":white_heart:"] = "\uD83E\uDD0D", + [":white_large_square:"] = "⬜", + [":white_medium_small_square:"] = "◽", + [":white_medium_square:"] = "◻️", + [":white_small_square:"] = "▫️", + [":white_square_button:"] = "\uD83D\uDD33", + [":white_sun_behind_cloud:"] = "\uD83C\uDF25️", + [":white_sun_behind_cloud_with_rain:"] = "\uD83C\uDF26️", + [":white_sun_cloud:"] = "\uD83C\uDF25️", + [":white_sun_rain_cloud:"] = "\uD83C\uDF26️", + [":white_sun_small_cloud:"] = "\uD83C\uDF24️", + [":white_sun_with_small_cloud:"] = "\uD83C\uDF24️", + [":wilted_flower:"] = "\uD83E\uDD40", + [":wilted_rose:"] = "\uD83E\uDD40", + [":wind_blowing_face:"] = "\uD83C\uDF2C️", + [":wind_chime:"] = "\uD83C\uDF90", + [":wine_glass:"] = "\uD83C\uDF77", + [":wink:"] = "\uD83D\uDE09", + [":wolf:"] = "\uD83D\uDC3A", + [":woman:"] = "\uD83D\uDC69", + [":woman::skin-tone-1:"] = "\uD83D\uDC69\uD83C\uDFFB", + [":woman::skin-tone-2:"] = "\uD83D\uDC69\uD83C\uDFFC", + [":woman::skin-tone-3:"] = "\uD83D\uDC69\uD83C\uDFFD", + [":woman::skin-tone-4:"] = "\uD83D\uDC69\uD83C\uDFFE", + [":woman::skin-tone-5:"] = "\uD83D\uDC69\uD83C\uDFFF", + [":woman_artist:"] = "\uD83D\uDC69\u200D\uD83C\uDFA8", + [":woman_artist::skin-tone-1:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83C\uDFA8", + [":woman_artist::skin-tone-2:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83C\uDFA8", + [":woman_artist::skin-tone-3:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83C\uDFA8", + [":woman_artist::skin-tone-4:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83C\uDFA8", + [":woman_artist::skin-tone-5:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83C\uDFA8", + [":woman_artist_dark_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83C\uDFA8", + [":woman_artist_light_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83C\uDFA8", + [":woman_artist_medium_dark_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83C\uDFA8", + [":woman_artist_medium_light_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83C\uDFA8", + [":woman_artist_medium_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83C\uDFA8", + [":woman_artist_tone1:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83C\uDFA8", + [":woman_artist_tone2:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83C\uDFA8", + [":woman_artist_tone3:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83C\uDFA8", + [":woman_artist_tone4:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83C\uDFA8", + [":woman_artist_tone5:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83C\uDFA8", + [":woman_astronaut:"] = "\uD83D\uDC69\u200D\uD83D\uDE80", + [":woman_astronaut::skin-tone-1:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83D\uDE80", + [":woman_astronaut::skin-tone-2:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83D\uDE80", + [":woman_astronaut::skin-tone-3:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83D\uDE80", + [":woman_astronaut::skin-tone-4:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83D\uDE80", + [":woman_astronaut::skin-tone-5:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83D\uDE80", + [":woman_astronaut_dark_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83D\uDE80", + [":woman_astronaut_light_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83D\uDE80", + [":woman_astronaut_medium_dark_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83D\uDE80", + [":woman_astronaut_medium_light_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83D\uDE80", + [":woman_astronaut_medium_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83D\uDE80", + [":woman_astronaut_tone1:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83D\uDE80", + [":woman_astronaut_tone2:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83D\uDE80", + [":woman_astronaut_tone3:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83D\uDE80", + [":woman_astronaut_tone4:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83D\uDE80", + [":woman_astronaut_tone5:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83D\uDE80", + [":woman_bald:"] = "\uD83D\uDC69\u200D\uD83E\uDDB2", + [":woman_bald::skin-tone-1:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83E\uDDB2", + [":woman_bald::skin-tone-2:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83E\uDDB2", + [":woman_bald::skin-tone-3:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83E\uDDB2", + [":woman_bald::skin-tone-4:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83E\uDDB2", + [":woman_bald::skin-tone-5:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83E\uDDB2", + [":woman_bald_dark_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83E\uDDB2", + [":woman_bald_light_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83E\uDDB2", + [":woman_bald_medium_dark_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83E\uDDB2", + [":woman_bald_medium_light_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83E\uDDB2", + [":woman_bald_medium_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83E\uDDB2", + [":woman_bald_tone1:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83E\uDDB2", + [":woman_bald_tone2:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83E\uDDB2", + [":woman_bald_tone3:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83E\uDDB2", + [":woman_bald_tone4:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83E\uDDB2", + [":woman_bald_tone5:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83E\uDDB2", + [":woman_biking:"] = "\uD83D\uDEB4\u200D♀️", + [":woman_biking::skin-tone-1:"] = "\uD83D\uDEB4\uD83C\uDFFB\u200D♀️", + [":woman_biking::skin-tone-2:"] = "\uD83D\uDEB4\uD83C\uDFFC\u200D♀️", + [":woman_biking::skin-tone-3:"] = "\uD83D\uDEB4\uD83C\uDFFD\u200D♀️", + [":woman_biking::skin-tone-4:"] = "\uD83D\uDEB4\uD83C\uDFFE\u200D♀️", + [":woman_biking::skin-tone-5:"] = "\uD83D\uDEB4\uD83C\uDFFF\u200D♀️", + [":woman_biking_dark_skin_tone:"] = "\uD83D\uDEB4\uD83C\uDFFF\u200D♀️", + [":woman_biking_light_skin_tone:"] = "\uD83D\uDEB4\uD83C\uDFFB\u200D♀️", + [":woman_biking_medium_dark_skin_tone:"] = "\uD83D\uDEB4\uD83C\uDFFE\u200D♀️", + [":woman_biking_medium_light_skin_tone:"] = "\uD83D\uDEB4\uD83C\uDFFC\u200D♀️", + [":woman_biking_medium_skin_tone:"] = "\uD83D\uDEB4\uD83C\uDFFD\u200D♀️", + [":woman_biking_tone1:"] = "\uD83D\uDEB4\uD83C\uDFFB\u200D♀️", + [":woman_biking_tone2:"] = "\uD83D\uDEB4\uD83C\uDFFC\u200D♀️", + [":woman_biking_tone3:"] = "\uD83D\uDEB4\uD83C\uDFFD\u200D♀️", + [":woman_biking_tone4:"] = "\uD83D\uDEB4\uD83C\uDFFE\u200D♀️", + [":woman_biking_tone5:"] = "\uD83D\uDEB4\uD83C\uDFFF\u200D♀️", + [":woman_bouncing_ball:"] = "⛹️\u200D♀️", + [":woman_bouncing_ball::skin-tone-1:"] = "⛹\uD83C\uDFFB\u200D♀️", + [":woman_bouncing_ball::skin-tone-2:"] = "⛹\uD83C\uDFFC\u200D♀️", + [":woman_bouncing_ball::skin-tone-3:"] = "⛹\uD83C\uDFFD\u200D♀️", + [":woman_bouncing_ball::skin-tone-4:"] = "⛹\uD83C\uDFFE\u200D♀️", + [":woman_bouncing_ball::skin-tone-5:"] = "⛹\uD83C\uDFFF\u200D♀️", + [":woman_bouncing_ball_dark_skin_tone:"] = "⛹\uD83C\uDFFF\u200D♀️", + [":woman_bouncing_ball_light_skin_tone:"] = "⛹\uD83C\uDFFB\u200D♀️", + [":woman_bouncing_ball_medium_dark_skin_tone:"] = "⛹\uD83C\uDFFE\u200D♀️", + [":woman_bouncing_ball_medium_light_skin_tone:"] = "⛹\uD83C\uDFFC\u200D♀️", + [":woman_bouncing_ball_medium_skin_tone:"] = "⛹\uD83C\uDFFD\u200D♀️", + [":woman_bouncing_ball_tone1:"] = "⛹\uD83C\uDFFB\u200D♀️", + [":woman_bouncing_ball_tone2:"] = "⛹\uD83C\uDFFC\u200D♀️", + [":woman_bouncing_ball_tone3:"] = "⛹\uD83C\uDFFD\u200D♀️", + [":woman_bouncing_ball_tone4:"] = "⛹\uD83C\uDFFE\u200D♀️", + [":woman_bouncing_ball_tone5:"] = "⛹\uD83C\uDFFF\u200D♀️", + [":woman_bowing:"] = "\uD83D\uDE47\u200D♀️", + [":woman_bowing::skin-tone-1:"] = "\uD83D\uDE47\uD83C\uDFFB\u200D♀️", + [":woman_bowing::skin-tone-2:"] = "\uD83D\uDE47\uD83C\uDFFC\u200D♀️", + [":woman_bowing::skin-tone-3:"] = "\uD83D\uDE47\uD83C\uDFFD\u200D♀️", + [":woman_bowing::skin-tone-4:"] = "\uD83D\uDE47\uD83C\uDFFE\u200D♀️", + [":woman_bowing::skin-tone-5:"] = "\uD83D\uDE47\uD83C\uDFFF\u200D♀️", + [":woman_bowing_dark_skin_tone:"] = "\uD83D\uDE47\uD83C\uDFFF\u200D♀️", + [":woman_bowing_light_skin_tone:"] = "\uD83D\uDE47\uD83C\uDFFB\u200D♀️", + [":woman_bowing_medium_dark_skin_tone:"] = "\uD83D\uDE47\uD83C\uDFFE\u200D♀️", + [":woman_bowing_medium_light_skin_tone:"] = "\uD83D\uDE47\uD83C\uDFFC\u200D♀️", + [":woman_bowing_medium_skin_tone:"] = "\uD83D\uDE47\uD83C\uDFFD\u200D♀️", + [":woman_bowing_tone1:"] = "\uD83D\uDE47\uD83C\uDFFB\u200D♀️", + [":woman_bowing_tone2:"] = "\uD83D\uDE47\uD83C\uDFFC\u200D♀️", + [":woman_bowing_tone3:"] = "\uD83D\uDE47\uD83C\uDFFD\u200D♀️", + [":woman_bowing_tone4:"] = "\uD83D\uDE47\uD83C\uDFFE\u200D♀️", + [":woman_bowing_tone5:"] = "\uD83D\uDE47\uD83C\uDFFF\u200D♀️", + [":woman_cartwheeling:"] = "\uD83E\uDD38\u200D♀️", + [":woman_cartwheeling::skin-tone-1:"] = "\uD83E\uDD38\uD83C\uDFFB\u200D♀️", + [":woman_cartwheeling::skin-tone-2:"] = "\uD83E\uDD38\uD83C\uDFFC\u200D♀️", + [":woman_cartwheeling::skin-tone-3:"] = "\uD83E\uDD38\uD83C\uDFFD\u200D♀️", + [":woman_cartwheeling::skin-tone-4:"] = "\uD83E\uDD38\uD83C\uDFFE\u200D♀️", + [":woman_cartwheeling::skin-tone-5:"] = "\uD83E\uDD38\uD83C\uDFFF\u200D♀️", + [":woman_cartwheeling_dark_skin_tone:"] = "\uD83E\uDD38\uD83C\uDFFF\u200D♀️", + [":woman_cartwheeling_light_skin_tone:"] = "\uD83E\uDD38\uD83C\uDFFB\u200D♀️", + [":woman_cartwheeling_medium_dark_skin_tone:"] = "\uD83E\uDD38\uD83C\uDFFE\u200D♀️", + [":woman_cartwheeling_medium_light_skin_tone:"] = "\uD83E\uDD38\uD83C\uDFFC\u200D♀️", + [":woman_cartwheeling_medium_skin_tone:"] = "\uD83E\uDD38\uD83C\uDFFD\u200D♀️", + [":woman_cartwheeling_tone1:"] = "\uD83E\uDD38\uD83C\uDFFB\u200D♀️", + [":woman_cartwheeling_tone2:"] = "\uD83E\uDD38\uD83C\uDFFC\u200D♀️", + [":woman_cartwheeling_tone3:"] = "\uD83E\uDD38\uD83C\uDFFD\u200D♀️", + [":woman_cartwheeling_tone4:"] = "\uD83E\uDD38\uD83C\uDFFE\u200D♀️", + [":woman_cartwheeling_tone5:"] = "\uD83E\uDD38\uD83C\uDFFF\u200D♀️", + [":woman_climbing:"] = "\uD83E\uDDD7\u200D♀️", + [":woman_climbing::skin-tone-1:"] = "\uD83E\uDDD7\uD83C\uDFFB\u200D♀️", + [":woman_climbing::skin-tone-2:"] = "\uD83E\uDDD7\uD83C\uDFFC\u200D♀️", + [":woman_climbing::skin-tone-3:"] = "\uD83E\uDDD7\uD83C\uDFFD\u200D♀️", + [":woman_climbing::skin-tone-4:"] = "\uD83E\uDDD7\uD83C\uDFFE\u200D♀️", + [":woman_climbing::skin-tone-5:"] = "\uD83E\uDDD7\uD83C\uDFFF\u200D♀️", + [":woman_climbing_dark_skin_tone:"] = "\uD83E\uDDD7\uD83C\uDFFF\u200D♀️", + [":woman_climbing_light_skin_tone:"] = "\uD83E\uDDD7\uD83C\uDFFB\u200D♀️", + [":woman_climbing_medium_dark_skin_tone:"] = "\uD83E\uDDD7\uD83C\uDFFE\u200D♀️", + [":woman_climbing_medium_light_skin_tone:"] = "\uD83E\uDDD7\uD83C\uDFFC\u200D♀️", + [":woman_climbing_medium_skin_tone:"] = "\uD83E\uDDD7\uD83C\uDFFD\u200D♀️", + [":woman_climbing_tone1:"] = "\uD83E\uDDD7\uD83C\uDFFB\u200D♀️", + [":woman_climbing_tone2:"] = "\uD83E\uDDD7\uD83C\uDFFC\u200D♀️", + [":woman_climbing_tone3:"] = "\uD83E\uDDD7\uD83C\uDFFD\u200D♀️", + [":woman_climbing_tone4:"] = "\uD83E\uDDD7\uD83C\uDFFE\u200D♀️", + [":woman_climbing_tone5:"] = "\uD83E\uDDD7\uD83C\uDFFF\u200D♀️", + [":woman_construction_worker:"] = "\uD83D\uDC77\u200D♀️", + [":woman_construction_worker::skin-tone-1:"] = "\uD83D\uDC77\uD83C\uDFFB\u200D♀️", + [":woman_construction_worker::skin-tone-2:"] = "\uD83D\uDC77\uD83C\uDFFC\u200D♀️", + [":woman_construction_worker::skin-tone-3:"] = "\uD83D\uDC77\uD83C\uDFFD\u200D♀️", + [":woman_construction_worker::skin-tone-4:"] = "\uD83D\uDC77\uD83C\uDFFE\u200D♀️", + [":woman_construction_worker::skin-tone-5:"] = "\uD83D\uDC77\uD83C\uDFFF\u200D♀️", + [":woman_construction_worker_dark_skin_tone:"] = "\uD83D\uDC77\uD83C\uDFFF\u200D♀️", + [":woman_construction_worker_light_skin_tone:"] = "\uD83D\uDC77\uD83C\uDFFB\u200D♀️", + [":woman_construction_worker_medium_dark_skin_tone:"] = "\uD83D\uDC77\uD83C\uDFFE\u200D♀️", + [":woman_construction_worker_medium_light_skin_tone:"] = "\uD83D\uDC77\uD83C\uDFFC\u200D♀️", + [":woman_construction_worker_medium_skin_tone:"] = "\uD83D\uDC77\uD83C\uDFFD\u200D♀️", + [":woman_construction_worker_tone1:"] = "\uD83D\uDC77\uD83C\uDFFB\u200D♀️", + [":woman_construction_worker_tone2:"] = "\uD83D\uDC77\uD83C\uDFFC\u200D♀️", + [":woman_construction_worker_tone3:"] = "\uD83D\uDC77\uD83C\uDFFD\u200D♀️", + [":woman_construction_worker_tone4:"] = "\uD83D\uDC77\uD83C\uDFFE\u200D♀️", + [":woman_construction_worker_tone5:"] = "\uD83D\uDC77\uD83C\uDFFF\u200D♀️", + [":woman_cook:"] = "\uD83D\uDC69\u200D\uD83C\uDF73", + [":woman_cook::skin-tone-1:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83C\uDF73", + [":woman_cook::skin-tone-2:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83C\uDF73", + [":woman_cook::skin-tone-3:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83C\uDF73", + [":woman_cook::skin-tone-4:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83C\uDF73", + [":woman_cook::skin-tone-5:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83C\uDF73", + [":woman_cook_dark_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83C\uDF73", + [":woman_cook_light_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83C\uDF73", + [":woman_cook_medium_dark_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83C\uDF73", + [":woman_cook_medium_light_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83C\uDF73", + [":woman_cook_medium_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83C\uDF73", + [":woman_cook_tone1:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83C\uDF73", + [":woman_cook_tone2:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83C\uDF73", + [":woman_cook_tone3:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83C\uDF73", + [":woman_cook_tone4:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83C\uDF73", + [":woman_cook_tone5:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83C\uDF73", + [":woman_curly_haired:"] = "\uD83D\uDC69\u200D\uD83E\uDDB1", + [":woman_curly_haired::skin-tone-1:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83E\uDDB1", + [":woman_curly_haired::skin-tone-2:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83E\uDDB1", + [":woman_curly_haired::skin-tone-3:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83E\uDDB1", + [":woman_curly_haired::skin-tone-4:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83E\uDDB1", + [":woman_curly_haired::skin-tone-5:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83E\uDDB1", + [":woman_curly_haired_dark_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83E\uDDB1", + [":woman_curly_haired_light_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83E\uDDB1", + [":woman_curly_haired_medium_dark_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83E\uDDB1", + [":woman_curly_haired_medium_light_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83E\uDDB1", + [":woman_curly_haired_medium_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83E\uDDB1", + [":woman_curly_haired_tone1:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83E\uDDB1", + [":woman_curly_haired_tone2:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83E\uDDB1", + [":woman_curly_haired_tone3:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83E\uDDB1", + [":woman_curly_haired_tone4:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83E\uDDB1", + [":woman_curly_haired_tone5:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83E\uDDB1", + [":woman_detective:"] = "\uD83D\uDD75️\u200D♀️", + [":woman_detective::skin-tone-1:"] = "\uD83D\uDD75\uD83C\uDFFB\u200D♀️", + [":woman_detective::skin-tone-2:"] = "\uD83D\uDD75\uD83C\uDFFC\u200D♀️", + [":woman_detective::skin-tone-3:"] = "\uD83D\uDD75\uD83C\uDFFD\u200D♀️", + [":woman_detective::skin-tone-4:"] = "\uD83D\uDD75\uD83C\uDFFE\u200D♀️", + [":woman_detective::skin-tone-5:"] = "\uD83D\uDD75\uD83C\uDFFF\u200D♀️", + [":woman_detective_dark_skin_tone:"] = "\uD83D\uDD75\uD83C\uDFFF\u200D♀️", + [":woman_detective_light_skin_tone:"] = "\uD83D\uDD75\uD83C\uDFFB\u200D♀️", + [":woman_detective_medium_dark_skin_tone:"] = "\uD83D\uDD75\uD83C\uDFFE\u200D♀️", + [":woman_detective_medium_light_skin_tone:"] = "\uD83D\uDD75\uD83C\uDFFC\u200D♀️", + [":woman_detective_medium_skin_tone:"] = "\uD83D\uDD75\uD83C\uDFFD\u200D♀️", + [":woman_detective_tone1:"] = "\uD83D\uDD75\uD83C\uDFFB\u200D♀️", + [":woman_detective_tone2:"] = "\uD83D\uDD75\uD83C\uDFFC\u200D♀️", + [":woman_detective_tone3:"] = "\uD83D\uDD75\uD83C\uDFFD\u200D♀️", + [":woman_detective_tone4:"] = "\uD83D\uDD75\uD83C\uDFFE\u200D♀️", + [":woman_detective_tone5:"] = "\uD83D\uDD75\uD83C\uDFFF\u200D♀️", + [":woman_elf:"] = "\uD83E\uDDDD\u200D♀️", + [":woman_elf::skin-tone-1:"] = "\uD83E\uDDDD\uD83C\uDFFB\u200D♀️", + [":woman_elf::skin-tone-2:"] = "\uD83E\uDDDD\uD83C\uDFFC\u200D♀️", + [":woman_elf::skin-tone-3:"] = "\uD83E\uDDDD\uD83C\uDFFD\u200D♀️", + [":woman_elf::skin-tone-4:"] = "\uD83E\uDDDD\uD83C\uDFFE\u200D♀️", + [":woman_elf::skin-tone-5:"] = "\uD83E\uDDDD\uD83C\uDFFF\u200D♀️", + [":woman_elf_dark_skin_tone:"] = "\uD83E\uDDDD\uD83C\uDFFF\u200D♀️", + [":woman_elf_light_skin_tone:"] = "\uD83E\uDDDD\uD83C\uDFFB\u200D♀️", + [":woman_elf_medium_dark_skin_tone:"] = "\uD83E\uDDDD\uD83C\uDFFE\u200D♀️", + [":woman_elf_medium_light_skin_tone:"] = "\uD83E\uDDDD\uD83C\uDFFC\u200D♀️", + [":woman_elf_medium_skin_tone:"] = "\uD83E\uDDDD\uD83C\uDFFD\u200D♀️", + [":woman_elf_tone1:"] = "\uD83E\uDDDD\uD83C\uDFFB\u200D♀️", + [":woman_elf_tone2:"] = "\uD83E\uDDDD\uD83C\uDFFC\u200D♀️", + [":woman_elf_tone3:"] = "\uD83E\uDDDD\uD83C\uDFFD\u200D♀️", + [":woman_elf_tone4:"] = "\uD83E\uDDDD\uD83C\uDFFE\u200D♀️", + [":woman_elf_tone5:"] = "\uD83E\uDDDD\uD83C\uDFFF\u200D♀️", + [":woman_facepalming:"] = "\uD83E\uDD26\u200D♀️", + [":woman_facepalming::skin-tone-1:"] = "\uD83E\uDD26\uD83C\uDFFB\u200D♀️", + [":woman_facepalming::skin-tone-2:"] = "\uD83E\uDD26\uD83C\uDFFC\u200D♀️", + [":woman_facepalming::skin-tone-3:"] = "\uD83E\uDD26\uD83C\uDFFD\u200D♀️", + [":woman_facepalming::skin-tone-4:"] = "\uD83E\uDD26\uD83C\uDFFE\u200D♀️", + [":woman_facepalming::skin-tone-5:"] = "\uD83E\uDD26\uD83C\uDFFF\u200D♀️", + [":woman_facepalming_dark_skin_tone:"] = "\uD83E\uDD26\uD83C\uDFFF\u200D♀️", + [":woman_facepalming_light_skin_tone:"] = "\uD83E\uDD26\uD83C\uDFFB\u200D♀️", + [":woman_facepalming_medium_dark_skin_tone:"] = "\uD83E\uDD26\uD83C\uDFFE\u200D♀️", + [":woman_facepalming_medium_light_skin_tone:"] = "\uD83E\uDD26\uD83C\uDFFC\u200D♀️", + [":woman_facepalming_medium_skin_tone:"] = "\uD83E\uDD26\uD83C\uDFFD\u200D♀️", + [":woman_facepalming_tone1:"] = "\uD83E\uDD26\uD83C\uDFFB\u200D♀️", + [":woman_facepalming_tone2:"] = "\uD83E\uDD26\uD83C\uDFFC\u200D♀️", + [":woman_facepalming_tone3:"] = "\uD83E\uDD26\uD83C\uDFFD\u200D♀️", + [":woman_facepalming_tone4:"] = "\uD83E\uDD26\uD83C\uDFFE\u200D♀️", + [":woman_facepalming_tone5:"] = "\uD83E\uDD26\uD83C\uDFFF\u200D♀️", + [":woman_factory_worker:"] = "\uD83D\uDC69\u200D\uD83C\uDFED", + [":woman_factory_worker::skin-tone-1:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83C\uDFED", + [":woman_factory_worker::skin-tone-2:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83C\uDFED", + [":woman_factory_worker::skin-tone-3:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83C\uDFED", + [":woman_factory_worker::skin-tone-4:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83C\uDFED", + [":woman_factory_worker::skin-tone-5:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83C\uDFED", + [":woman_factory_worker_dark_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83C\uDFED", + [":woman_factory_worker_light_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83C\uDFED", + [":woman_factory_worker_medium_dark_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83C\uDFED", + [":woman_factory_worker_medium_light_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83C\uDFED", + [":woman_factory_worker_medium_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83C\uDFED", + [":woman_factory_worker_tone1:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83C\uDFED", + [":woman_factory_worker_tone2:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83C\uDFED", + [":woman_factory_worker_tone3:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83C\uDFED", + [":woman_factory_worker_tone4:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83C\uDFED", + [":woman_factory_worker_tone5:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83C\uDFED", + [":woman_fairy:"] = "\uD83E\uDDDA\u200D♀️", + [":woman_fairy::skin-tone-1:"] = "\uD83E\uDDDA\uD83C\uDFFB\u200D♀️", + [":woman_fairy::skin-tone-2:"] = "\uD83E\uDDDA\uD83C\uDFFC\u200D♀️", + [":woman_fairy::skin-tone-3:"] = "\uD83E\uDDDA\uD83C\uDFFD\u200D♀️", + [":woman_fairy::skin-tone-4:"] = "\uD83E\uDDDA\uD83C\uDFFE\u200D♀️", + [":woman_fairy::skin-tone-5:"] = "\uD83E\uDDDA\uD83C\uDFFF\u200D♀️", + [":woman_fairy_dark_skin_tone:"] = "\uD83E\uDDDA\uD83C\uDFFF\u200D♀️", + [":woman_fairy_light_skin_tone:"] = "\uD83E\uDDDA\uD83C\uDFFB\u200D♀️", + [":woman_fairy_medium_dark_skin_tone:"] = "\uD83E\uDDDA\uD83C\uDFFE\u200D♀️", + [":woman_fairy_medium_light_skin_tone:"] = "\uD83E\uDDDA\uD83C\uDFFC\u200D♀️", + [":woman_fairy_medium_skin_tone:"] = "\uD83E\uDDDA\uD83C\uDFFD\u200D♀️", + [":woman_fairy_tone1:"] = "\uD83E\uDDDA\uD83C\uDFFB\u200D♀️", + [":woman_fairy_tone2:"] = "\uD83E\uDDDA\uD83C\uDFFC\u200D♀️", + [":woman_fairy_tone3:"] = "\uD83E\uDDDA\uD83C\uDFFD\u200D♀️", + [":woman_fairy_tone4:"] = "\uD83E\uDDDA\uD83C\uDFFE\u200D♀️", + [":woman_fairy_tone5:"] = "\uD83E\uDDDA\uD83C\uDFFF\u200D♀️", + [":woman_farmer:"] = "\uD83D\uDC69\u200D\uD83C\uDF3E", + [":woman_farmer::skin-tone-1:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83C\uDF3E", + [":woman_farmer::skin-tone-2:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83C\uDF3E", + [":woman_farmer::skin-tone-3:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83C\uDF3E", + [":woman_farmer::skin-tone-4:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83C\uDF3E", + [":woman_farmer::skin-tone-5:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83C\uDF3E", + [":woman_farmer_dark_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83C\uDF3E", + [":woman_farmer_light_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83C\uDF3E", + [":woman_farmer_medium_dark_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83C\uDF3E", + [":woman_farmer_medium_light_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83C\uDF3E", + [":woman_farmer_medium_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83C\uDF3E", + [":woman_farmer_tone1:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83C\uDF3E", + [":woman_farmer_tone2:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83C\uDF3E", + [":woman_farmer_tone3:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83C\uDF3E", + [":woman_farmer_tone4:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83C\uDF3E", + [":woman_farmer_tone5:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83C\uDF3E", + [":woman_firefighter:"] = "\uD83D\uDC69\u200D\uD83D\uDE92", + [":woman_firefighter::skin-tone-1:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83D\uDE92", + [":woman_firefighter::skin-tone-2:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83D\uDE92", + [":woman_firefighter::skin-tone-3:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83D\uDE92", + [":woman_firefighter::skin-tone-4:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83D\uDE92", + [":woman_firefighter::skin-tone-5:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83D\uDE92", + [":woman_firefighter_dark_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83D\uDE92", + [":woman_firefighter_light_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83D\uDE92", + [":woman_firefighter_medium_dark_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83D\uDE92", + [":woman_firefighter_medium_light_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83D\uDE92", + [":woman_firefighter_medium_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83D\uDE92", + [":woman_firefighter_tone1:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83D\uDE92", + [":woman_firefighter_tone2:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83D\uDE92", + [":woman_firefighter_tone3:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83D\uDE92", + [":woman_firefighter_tone4:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83D\uDE92", + [":woman_firefighter_tone5:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83D\uDE92", + [":woman_frowning:"] = "\uD83D\uDE4D\u200D♀️", + [":woman_frowning::skin-tone-1:"] = "\uD83D\uDE4D\uD83C\uDFFB\u200D♀️", + [":woman_frowning::skin-tone-2:"] = "\uD83D\uDE4D\uD83C\uDFFC\u200D♀️", + [":woman_frowning::skin-tone-3:"] = "\uD83D\uDE4D\uD83C\uDFFD\u200D♀️", + [":woman_frowning::skin-tone-4:"] = "\uD83D\uDE4D\uD83C\uDFFE\u200D♀️", + [":woman_frowning::skin-tone-5:"] = "\uD83D\uDE4D\uD83C\uDFFF\u200D♀️", + [":woman_frowning_dark_skin_tone:"] = "\uD83D\uDE4D\uD83C\uDFFF\u200D♀️", + [":woman_frowning_light_skin_tone:"] = "\uD83D\uDE4D\uD83C\uDFFB\u200D♀️", + [":woman_frowning_medium_dark_skin_tone:"] = "\uD83D\uDE4D\uD83C\uDFFE\u200D♀️", + [":woman_frowning_medium_light_skin_tone:"] = "\uD83D\uDE4D\uD83C\uDFFC\u200D♀️", + [":woman_frowning_medium_skin_tone:"] = "\uD83D\uDE4D\uD83C\uDFFD\u200D♀️", + [":woman_frowning_tone1:"] = "\uD83D\uDE4D\uD83C\uDFFB\u200D♀️", + [":woman_frowning_tone2:"] = "\uD83D\uDE4D\uD83C\uDFFC\u200D♀️", + [":woman_frowning_tone3:"] = "\uD83D\uDE4D\uD83C\uDFFD\u200D♀️", + [":woman_frowning_tone4:"] = "\uD83D\uDE4D\uD83C\uDFFE\u200D♀️", + [":woman_frowning_tone5:"] = "\uD83D\uDE4D\uD83C\uDFFF\u200D♀️", + [":woman_genie:"] = "\uD83E\uDDDE\u200D♀️", + [":woman_gesturing_no:"] = "\uD83D\uDE45\u200D♀️", + [":woman_gesturing_no::skin-tone-1:"] = "\uD83D\uDE45\uD83C\uDFFB\u200D♀️", + [":woman_gesturing_no::skin-tone-2:"] = "\uD83D\uDE45\uD83C\uDFFC\u200D♀️", + [":woman_gesturing_no::skin-tone-3:"] = "\uD83D\uDE45\uD83C\uDFFD\u200D♀️", + [":woman_gesturing_no::skin-tone-4:"] = "\uD83D\uDE45\uD83C\uDFFE\u200D♀️", + [":woman_gesturing_no::skin-tone-5:"] = "\uD83D\uDE45\uD83C\uDFFF\u200D♀️", + [":woman_gesturing_no_dark_skin_tone:"] = "\uD83D\uDE45\uD83C\uDFFF\u200D♀️", + [":woman_gesturing_no_light_skin_tone:"] = "\uD83D\uDE45\uD83C\uDFFB\u200D♀️", + [":woman_gesturing_no_medium_dark_skin_tone:"] = "\uD83D\uDE45\uD83C\uDFFE\u200D♀️", + [":woman_gesturing_no_medium_light_skin_tone:"] = "\uD83D\uDE45\uD83C\uDFFC\u200D♀️", + [":woman_gesturing_no_medium_skin_tone:"] = "\uD83D\uDE45\uD83C\uDFFD\u200D♀️", + [":woman_gesturing_no_tone1:"] = "\uD83D\uDE45\uD83C\uDFFB\u200D♀️", + [":woman_gesturing_no_tone2:"] = "\uD83D\uDE45\uD83C\uDFFC\u200D♀️", + [":woman_gesturing_no_tone3:"] = "\uD83D\uDE45\uD83C\uDFFD\u200D♀️", + [":woman_gesturing_no_tone4:"] = "\uD83D\uDE45\uD83C\uDFFE\u200D♀️", + [":woman_gesturing_no_tone5:"] = "\uD83D\uDE45\uD83C\uDFFF\u200D♀️", + [":woman_gesturing_ok:"] = "\uD83D\uDE46\u200D♀️", + [":woman_gesturing_ok::skin-tone-1:"] = "\uD83D\uDE46\uD83C\uDFFB\u200D♀️", + [":woman_gesturing_ok::skin-tone-2:"] = "\uD83D\uDE46\uD83C\uDFFC\u200D♀️", + [":woman_gesturing_ok::skin-tone-3:"] = "\uD83D\uDE46\uD83C\uDFFD\u200D♀️", + [":woman_gesturing_ok::skin-tone-4:"] = "\uD83D\uDE46\uD83C\uDFFE\u200D♀️", + [":woman_gesturing_ok::skin-tone-5:"] = "\uD83D\uDE46\uD83C\uDFFF\u200D♀️", + [":woman_gesturing_ok_dark_skin_tone:"] = "\uD83D\uDE46\uD83C\uDFFF\u200D♀️", + [":woman_gesturing_ok_light_skin_tone:"] = "\uD83D\uDE46\uD83C\uDFFB\u200D♀️", + [":woman_gesturing_ok_medium_dark_skin_tone:"] = "\uD83D\uDE46\uD83C\uDFFE\u200D♀️", + [":woman_gesturing_ok_medium_light_skin_tone:"] = "\uD83D\uDE46\uD83C\uDFFC\u200D♀️", + [":woman_gesturing_ok_medium_skin_tone:"] = "\uD83D\uDE46\uD83C\uDFFD\u200D♀️", + [":woman_gesturing_ok_tone1:"] = "\uD83D\uDE46\uD83C\uDFFB\u200D♀️", + [":woman_gesturing_ok_tone2:"] = "\uD83D\uDE46\uD83C\uDFFC\u200D♀️", + [":woman_gesturing_ok_tone3:"] = "\uD83D\uDE46\uD83C\uDFFD\u200D♀️", + [":woman_gesturing_ok_tone4:"] = "\uD83D\uDE46\uD83C\uDFFE\u200D♀️", + [":woman_gesturing_ok_tone5:"] = "\uD83D\uDE46\uD83C\uDFFF\u200D♀️", + [":woman_getting_face_massage:"] = "\uD83D\uDC86\u200D♀️", + [":woman_getting_face_massage::skin-tone-1:"] = "\uD83D\uDC86\uD83C\uDFFB\u200D♀️", + [":woman_getting_face_massage::skin-tone-2:"] = "\uD83D\uDC86\uD83C\uDFFC\u200D♀️", + [":woman_getting_face_massage::skin-tone-3:"] = "\uD83D\uDC86\uD83C\uDFFD\u200D♀️", + [":woman_getting_face_massage::skin-tone-4:"] = "\uD83D\uDC86\uD83C\uDFFE\u200D♀️", + [":woman_getting_face_massage::skin-tone-5:"] = "\uD83D\uDC86\uD83C\uDFFF\u200D♀️", + [":woman_getting_face_massage_dark_skin_tone:"] = "\uD83D\uDC86\uD83C\uDFFF\u200D♀️", + [":woman_getting_face_massage_light_skin_tone:"] = "\uD83D\uDC86\uD83C\uDFFB\u200D♀️", + [":woman_getting_face_massage_medium_dark_skin_tone:"] = "\uD83D\uDC86\uD83C\uDFFE\u200D♀️", + [":woman_getting_face_massage_medium_light_skin_tone:"] = "\uD83D\uDC86\uD83C\uDFFC\u200D♀️", + [":woman_getting_face_massage_medium_skin_tone:"] = "\uD83D\uDC86\uD83C\uDFFD\u200D♀️", + [":woman_getting_face_massage_tone1:"] = "\uD83D\uDC86\uD83C\uDFFB\u200D♀️", + [":woman_getting_face_massage_tone2:"] = "\uD83D\uDC86\uD83C\uDFFC\u200D♀️", + [":woman_getting_face_massage_tone3:"] = "\uD83D\uDC86\uD83C\uDFFD\u200D♀️", + [":woman_getting_face_massage_tone4:"] = "\uD83D\uDC86\uD83C\uDFFE\u200D♀️", + [":woman_getting_face_massage_tone5:"] = "\uD83D\uDC86\uD83C\uDFFF\u200D♀️", + [":woman_getting_haircut:"] = "\uD83D\uDC87\u200D♀️", + [":woman_getting_haircut::skin-tone-1:"] = "\uD83D\uDC87\uD83C\uDFFB\u200D♀️", + [":woman_getting_haircut::skin-tone-2:"] = "\uD83D\uDC87\uD83C\uDFFC\u200D♀️", + [":woman_getting_haircut::skin-tone-3:"] = "\uD83D\uDC87\uD83C\uDFFD\u200D♀️", + [":woman_getting_haircut::skin-tone-4:"] = "\uD83D\uDC87\uD83C\uDFFE\u200D♀️", + [":woman_getting_haircut::skin-tone-5:"] = "\uD83D\uDC87\uD83C\uDFFF\u200D♀️", + [":woman_getting_haircut_dark_skin_tone:"] = "\uD83D\uDC87\uD83C\uDFFF\u200D♀️", + [":woman_getting_haircut_light_skin_tone:"] = "\uD83D\uDC87\uD83C\uDFFB\u200D♀️", + [":woman_getting_haircut_medium_dark_skin_tone:"] = "\uD83D\uDC87\uD83C\uDFFE\u200D♀️", + [":woman_getting_haircut_medium_light_skin_tone:"] = "\uD83D\uDC87\uD83C\uDFFC\u200D♀️", + [":woman_getting_haircut_medium_skin_tone:"] = "\uD83D\uDC87\uD83C\uDFFD\u200D♀️", + [":woman_getting_haircut_tone1:"] = "\uD83D\uDC87\uD83C\uDFFB\u200D♀️", + [":woman_getting_haircut_tone2:"] = "\uD83D\uDC87\uD83C\uDFFC\u200D♀️", + [":woman_getting_haircut_tone3:"] = "\uD83D\uDC87\uD83C\uDFFD\u200D♀️", + [":woman_getting_haircut_tone4:"] = "\uD83D\uDC87\uD83C\uDFFE\u200D♀️", + [":woman_getting_haircut_tone5:"] = "\uD83D\uDC87\uD83C\uDFFF\u200D♀️", + [":woman_golfing:"] = "\uD83C\uDFCC️\u200D♀️", + [":woman_golfing::skin-tone-1:"] = "\uD83C\uDFCC\uD83C\uDFFB\u200D♀️", + [":woman_golfing::skin-tone-2:"] = "\uD83C\uDFCC\uD83C\uDFFC\u200D♀️", + [":woman_golfing::skin-tone-3:"] = "\uD83C\uDFCC\uD83C\uDFFD\u200D♀️", + [":woman_golfing::skin-tone-4:"] = "\uD83C\uDFCC\uD83C\uDFFE\u200D♀️", + [":woman_golfing::skin-tone-5:"] = "\uD83C\uDFCC\uD83C\uDFFF\u200D♀️", + [":woman_golfing_dark_skin_tone:"] = "\uD83C\uDFCC\uD83C\uDFFF\u200D♀️", + [":woman_golfing_light_skin_tone:"] = "\uD83C\uDFCC\uD83C\uDFFB\u200D♀️", + [":woman_golfing_medium_dark_skin_tone:"] = "\uD83C\uDFCC\uD83C\uDFFE\u200D♀️", + [":woman_golfing_medium_light_skin_tone:"] = "\uD83C\uDFCC\uD83C\uDFFC\u200D♀️", + [":woman_golfing_medium_skin_tone:"] = "\uD83C\uDFCC\uD83C\uDFFD\u200D♀️", + [":woman_golfing_tone1:"] = "\uD83C\uDFCC\uD83C\uDFFB\u200D♀️", + [":woman_golfing_tone2:"] = "\uD83C\uDFCC\uD83C\uDFFC\u200D♀️", + [":woman_golfing_tone3:"] = "\uD83C\uDFCC\uD83C\uDFFD\u200D♀️", + [":woman_golfing_tone4:"] = "\uD83C\uDFCC\uD83C\uDFFE\u200D♀️", + [":woman_golfing_tone5:"] = "\uD83C\uDFCC\uD83C\uDFFF\u200D♀️", + [":woman_guard:"] = "\uD83D\uDC82\u200D♀️", + [":woman_guard::skin-tone-1:"] = "\uD83D\uDC82\uD83C\uDFFB\u200D♀️", + [":woman_guard::skin-tone-2:"] = "\uD83D\uDC82\uD83C\uDFFC\u200D♀️", + [":woman_guard::skin-tone-3:"] = "\uD83D\uDC82\uD83C\uDFFD\u200D♀️", + [":woman_guard::skin-tone-4:"] = "\uD83D\uDC82\uD83C\uDFFE\u200D♀️", + [":woman_guard::skin-tone-5:"] = "\uD83D\uDC82\uD83C\uDFFF\u200D♀️", + [":woman_guard_dark_skin_tone:"] = "\uD83D\uDC82\uD83C\uDFFF\u200D♀️", + [":woman_guard_light_skin_tone:"] = "\uD83D\uDC82\uD83C\uDFFB\u200D♀️", + [":woman_guard_medium_dark_skin_tone:"] = "\uD83D\uDC82\uD83C\uDFFE\u200D♀️", + [":woman_guard_medium_light_skin_tone:"] = "\uD83D\uDC82\uD83C\uDFFC\u200D♀️", + [":woman_guard_medium_skin_tone:"] = "\uD83D\uDC82\uD83C\uDFFD\u200D♀️", + [":woman_guard_tone1:"] = "\uD83D\uDC82\uD83C\uDFFB\u200D♀️", + [":woman_guard_tone2:"] = "\uD83D\uDC82\uD83C\uDFFC\u200D♀️", + [":woman_guard_tone3:"] = "\uD83D\uDC82\uD83C\uDFFD\u200D♀️", + [":woman_guard_tone4:"] = "\uD83D\uDC82\uD83C\uDFFE\u200D♀️", + [":woman_guard_tone5:"] = "\uD83D\uDC82\uD83C\uDFFF\u200D♀️", + [":woman_health_worker:"] = "\uD83D\uDC69\u200D⚕️", + [":woman_health_worker::skin-tone-1:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D⚕️", + [":woman_health_worker::skin-tone-2:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D⚕️", + [":woman_health_worker::skin-tone-3:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D⚕️", + [":woman_health_worker::skin-tone-4:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D⚕️", + [":woman_health_worker::skin-tone-5:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D⚕️", + [":woman_health_worker_dark_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D⚕️", + [":woman_health_worker_light_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D⚕️", + [":woman_health_worker_medium_dark_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D⚕️", + [":woman_health_worker_medium_light_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D⚕️", + [":woman_health_worker_medium_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D⚕️", + [":woman_health_worker_tone1:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D⚕️", + [":woman_health_worker_tone2:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D⚕️", + [":woman_health_worker_tone3:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D⚕️", + [":woman_health_worker_tone4:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D⚕️", + [":woman_health_worker_tone5:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D⚕️", + [":woman_in_lotus_position:"] = "\uD83E\uDDD8\u200D♀️", + [":woman_in_lotus_position::skin-tone-1:"] = "\uD83E\uDDD8\uD83C\uDFFB\u200D♀️", + [":woman_in_lotus_position::skin-tone-2:"] = "\uD83E\uDDD8\uD83C\uDFFC\u200D♀️", + [":woman_in_lotus_position::skin-tone-3:"] = "\uD83E\uDDD8\uD83C\uDFFD\u200D♀️", + [":woman_in_lotus_position::skin-tone-4:"] = "\uD83E\uDDD8\uD83C\uDFFE\u200D♀️", + [":woman_in_lotus_position::skin-tone-5:"] = "\uD83E\uDDD8\uD83C\uDFFF\u200D♀️", + [":woman_in_lotus_position_dark_skin_tone:"] = "\uD83E\uDDD8\uD83C\uDFFF\u200D♀️", + [":woman_in_lotus_position_light_skin_tone:"] = "\uD83E\uDDD8\uD83C\uDFFB\u200D♀️", + [":woman_in_lotus_position_medium_dark_skin_tone:"] = "\uD83E\uDDD8\uD83C\uDFFE\u200D♀️", + [":woman_in_lotus_position_medium_light_skin_tone:"] = "\uD83E\uDDD8\uD83C\uDFFC\u200D♀️", + [":woman_in_lotus_position_medium_skin_tone:"] = "\uD83E\uDDD8\uD83C\uDFFD\u200D♀️", + [":woman_in_lotus_position_tone1:"] = "\uD83E\uDDD8\uD83C\uDFFB\u200D♀️", + [":woman_in_lotus_position_tone2:"] = "\uD83E\uDDD8\uD83C\uDFFC\u200D♀️", + [":woman_in_lotus_position_tone3:"] = "\uD83E\uDDD8\uD83C\uDFFD\u200D♀️", + [":woman_in_lotus_position_tone4:"] = "\uD83E\uDDD8\uD83C\uDFFE\u200D♀️", + [":woman_in_lotus_position_tone5:"] = "\uD83E\uDDD8\uD83C\uDFFF\u200D♀️", + [":woman_in_manual_wheelchair:"] = "\uD83D\uDC69\u200D\uD83E\uDDBD", + [":woman_in_manual_wheelchair::skin-tone-1:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83E\uDDBD", + [":woman_in_manual_wheelchair::skin-tone-2:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83E\uDDBD", + [":woman_in_manual_wheelchair::skin-tone-3:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83E\uDDBD", + [":woman_in_manual_wheelchair::skin-tone-4:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83E\uDDBD", + [":woman_in_manual_wheelchair::skin-tone-5:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83E\uDDBD", + [":woman_in_manual_wheelchair_dark_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83E\uDDBD", + [":woman_in_manual_wheelchair_light_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83E\uDDBD", + [":woman_in_manual_wheelchair_medium_dark_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83E\uDDBD", + [":woman_in_manual_wheelchair_medium_light_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83E\uDDBD", + [":woman_in_manual_wheelchair_medium_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83E\uDDBD", + [":woman_in_manual_wheelchair_tone1:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83E\uDDBD", + [":woman_in_manual_wheelchair_tone2:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83E\uDDBD", + [":woman_in_manual_wheelchair_tone3:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83E\uDDBD", + [":woman_in_manual_wheelchair_tone4:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83E\uDDBD", + [":woman_in_manual_wheelchair_tone5:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83E\uDDBD", + [":woman_in_motorized_wheelchair:"] = "\uD83D\uDC69\u200D\uD83E\uDDBC", + [":woman_in_motorized_wheelchair::skin-tone-1:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83E\uDDBC", + [":woman_in_motorized_wheelchair::skin-tone-2:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83E\uDDBC", + [":woman_in_motorized_wheelchair::skin-tone-3:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83E\uDDBC", + [":woman_in_motorized_wheelchair::skin-tone-4:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83E\uDDBC", + [":woman_in_motorized_wheelchair::skin-tone-5:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83E\uDDBC", + [":woman_in_motorized_wheelchair_dark_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83E\uDDBC", + [":woman_in_motorized_wheelchair_light_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83E\uDDBC", + [":woman_in_motorized_wheelchair_medium_dark_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83E\uDDBC", + [":woman_in_motorized_wheelchair_medium_light_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83E\uDDBC", + [":woman_in_motorized_wheelchair_medium_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83E\uDDBC", + [":woman_in_motorized_wheelchair_tone1:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83E\uDDBC", + [":woman_in_motorized_wheelchair_tone2:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83E\uDDBC", + [":woman_in_motorized_wheelchair_tone3:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83E\uDDBC", + [":woman_in_motorized_wheelchair_tone4:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83E\uDDBC", + [":woman_in_motorized_wheelchair_tone5:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83E\uDDBC", + [":woman_in_steamy_room:"] = "\uD83E\uDDD6\u200D♀️", + [":woman_in_steamy_room::skin-tone-1:"] = "\uD83E\uDDD6\uD83C\uDFFB\u200D♀️", + [":woman_in_steamy_room::skin-tone-2:"] = "\uD83E\uDDD6\uD83C\uDFFC\u200D♀️", + [":woman_in_steamy_room::skin-tone-3:"] = "\uD83E\uDDD6\uD83C\uDFFD\u200D♀️", + [":woman_in_steamy_room::skin-tone-4:"] = "\uD83E\uDDD6\uD83C\uDFFE\u200D♀️", + [":woman_in_steamy_room::skin-tone-5:"] = "\uD83E\uDDD6\uD83C\uDFFF\u200D♀️", + [":woman_in_steamy_room_dark_skin_tone:"] = "\uD83E\uDDD6\uD83C\uDFFF\u200D♀️", + [":woman_in_steamy_room_light_skin_tone:"] = "\uD83E\uDDD6\uD83C\uDFFB\u200D♀️", + [":woman_in_steamy_room_medium_dark_skin_tone:"] = "\uD83E\uDDD6\uD83C\uDFFE\u200D♀️", + [":woman_in_steamy_room_medium_light_skin_tone:"] = "\uD83E\uDDD6\uD83C\uDFFC\u200D♀️", + [":woman_in_steamy_room_medium_skin_tone:"] = "\uD83E\uDDD6\uD83C\uDFFD\u200D♀️", + [":woman_in_steamy_room_tone1:"] = "\uD83E\uDDD6\uD83C\uDFFB\u200D♀️", + [":woman_in_steamy_room_tone2:"] = "\uD83E\uDDD6\uD83C\uDFFC\u200D♀️", + [":woman_in_steamy_room_tone3:"] = "\uD83E\uDDD6\uD83C\uDFFD\u200D♀️", + [":woman_in_steamy_room_tone4:"] = "\uD83E\uDDD6\uD83C\uDFFE\u200D♀️", + [":woman_in_steamy_room_tone5:"] = "\uD83E\uDDD6\uD83C\uDFFF\u200D♀️", + [":woman_judge:"] = "\uD83D\uDC69\u200D⚖️", + [":woman_judge::skin-tone-1:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D⚖️", + [":woman_judge::skin-tone-2:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D⚖️", + [":woman_judge::skin-tone-3:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D⚖️", + [":woman_judge::skin-tone-4:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D⚖️", + [":woman_judge::skin-tone-5:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D⚖️", + [":woman_judge_dark_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D⚖️", + [":woman_judge_light_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D⚖️", + [":woman_judge_medium_dark_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D⚖️", + [":woman_judge_medium_light_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D⚖️", + [":woman_judge_medium_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D⚖️", + [":woman_judge_tone1:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D⚖️", + [":woman_judge_tone2:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D⚖️", + [":woman_judge_tone3:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D⚖️", + [":woman_judge_tone4:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D⚖️", + [":woman_judge_tone5:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D⚖️", + [":woman_juggling:"] = "\uD83E\uDD39\u200D♀️", + [":woman_juggling::skin-tone-1:"] = "\uD83E\uDD39\uD83C\uDFFB\u200D♀️", + [":woman_juggling::skin-tone-2:"] = "\uD83E\uDD39\uD83C\uDFFC\u200D♀️", + [":woman_juggling::skin-tone-3:"] = "\uD83E\uDD39\uD83C\uDFFD\u200D♀️", + [":woman_juggling::skin-tone-4:"] = "\uD83E\uDD39\uD83C\uDFFE\u200D♀️", + [":woman_juggling::skin-tone-5:"] = "\uD83E\uDD39\uD83C\uDFFF\u200D♀️", + [":woman_juggling_dark_skin_tone:"] = "\uD83E\uDD39\uD83C\uDFFF\u200D♀️", + [":woman_juggling_light_skin_tone:"] = "\uD83E\uDD39\uD83C\uDFFB\u200D♀️", + [":woman_juggling_medium_dark_skin_tone:"] = "\uD83E\uDD39\uD83C\uDFFE\u200D♀️", + [":woman_juggling_medium_light_skin_tone:"] = "\uD83E\uDD39\uD83C\uDFFC\u200D♀️", + [":woman_juggling_medium_skin_tone:"] = "\uD83E\uDD39\uD83C\uDFFD\u200D♀️", + [":woman_juggling_tone1:"] = "\uD83E\uDD39\uD83C\uDFFB\u200D♀️", + [":woman_juggling_tone2:"] = "\uD83E\uDD39\uD83C\uDFFC\u200D♀️", + [":woman_juggling_tone3:"] = "\uD83E\uDD39\uD83C\uDFFD\u200D♀️", + [":woman_juggling_tone4:"] = "\uD83E\uDD39\uD83C\uDFFE\u200D♀️", + [":woman_juggling_tone5:"] = "\uD83E\uDD39\uD83C\uDFFF\u200D♀️", + [":woman_kneeling:"] = "\uD83E\uDDCE\u200D♀️", + [":woman_kneeling::skin-tone-1:"] = "\uD83E\uDDCE\uD83C\uDFFB\u200D♀️", + [":woman_kneeling::skin-tone-2:"] = "\uD83E\uDDCE\uD83C\uDFFC\u200D♀️", + [":woman_kneeling::skin-tone-3:"] = "\uD83E\uDDCE\uD83C\uDFFD\u200D♀️", + [":woman_kneeling::skin-tone-4:"] = "\uD83E\uDDCE\uD83C\uDFFE\u200D♀️", + [":woman_kneeling::skin-tone-5:"] = "\uD83E\uDDCE\uD83C\uDFFF\u200D♀️", + [":woman_kneeling_dark_skin_tone:"] = "\uD83E\uDDCE\uD83C\uDFFF\u200D♀️", + [":woman_kneeling_light_skin_tone:"] = "\uD83E\uDDCE\uD83C\uDFFB\u200D♀️", + [":woman_kneeling_medium_dark_skin_tone:"] = "\uD83E\uDDCE\uD83C\uDFFE\u200D♀️", + [":woman_kneeling_medium_light_skin_tone:"] = "\uD83E\uDDCE\uD83C\uDFFC\u200D♀️", + [":woman_kneeling_medium_skin_tone:"] = "\uD83E\uDDCE\uD83C\uDFFD\u200D♀️", + [":woman_kneeling_tone1:"] = "\uD83E\uDDCE\uD83C\uDFFB\u200D♀️", + [":woman_kneeling_tone2:"] = "\uD83E\uDDCE\uD83C\uDFFC\u200D♀️", + [":woman_kneeling_tone3:"] = "\uD83E\uDDCE\uD83C\uDFFD\u200D♀️", + [":woman_kneeling_tone4:"] = "\uD83E\uDDCE\uD83C\uDFFE\u200D♀️", + [":woman_kneeling_tone5:"] = "\uD83E\uDDCE\uD83C\uDFFF\u200D♀️", + [":woman_lifting_weights:"] = "\uD83C\uDFCB️\u200D♀️", + [":woman_lifting_weights::skin-tone-1:"] = "\uD83C\uDFCB\uD83C\uDFFB\u200D♀️", + [":woman_lifting_weights::skin-tone-2:"] = "\uD83C\uDFCB\uD83C\uDFFC\u200D♀️", + [":woman_lifting_weights::skin-tone-3:"] = "\uD83C\uDFCB\uD83C\uDFFD\u200D♀️", + [":woman_lifting_weights::skin-tone-4:"] = "\uD83C\uDFCB\uD83C\uDFFE\u200D♀️", + [":woman_lifting_weights::skin-tone-5:"] = "\uD83C\uDFCB\uD83C\uDFFF\u200D♀️", + [":woman_lifting_weights_dark_skin_tone:"] = "\uD83C\uDFCB\uD83C\uDFFF\u200D♀️", + [":woman_lifting_weights_light_skin_tone:"] = "\uD83C\uDFCB\uD83C\uDFFB\u200D♀️", + [":woman_lifting_weights_medium_dark_skin_tone:"] = "\uD83C\uDFCB\uD83C\uDFFE\u200D♀️", + [":woman_lifting_weights_medium_light_skin_tone:"] = "\uD83C\uDFCB\uD83C\uDFFC\u200D♀️", + [":woman_lifting_weights_medium_skin_tone:"] = "\uD83C\uDFCB\uD83C\uDFFD\u200D♀️", + [":woman_lifting_weights_tone1:"] = "\uD83C\uDFCB\uD83C\uDFFB\u200D♀️", + [":woman_lifting_weights_tone2:"] = "\uD83C\uDFCB\uD83C\uDFFC\u200D♀️", + [":woman_lifting_weights_tone3:"] = "\uD83C\uDFCB\uD83C\uDFFD\u200D♀️", + [":woman_lifting_weights_tone4:"] = "\uD83C\uDFCB\uD83C\uDFFE\u200D♀️", + [":woman_lifting_weights_tone5:"] = "\uD83C\uDFCB\uD83C\uDFFF\u200D♀️", + [":woman_mage:"] = "\uD83E\uDDD9\u200D♀️", + [":woman_mage::skin-tone-1:"] = "\uD83E\uDDD9\uD83C\uDFFB\u200D♀️", + [":woman_mage::skin-tone-2:"] = "\uD83E\uDDD9\uD83C\uDFFC\u200D♀️", + [":woman_mage::skin-tone-3:"] = "\uD83E\uDDD9\uD83C\uDFFD\u200D♀️", + [":woman_mage::skin-tone-4:"] = "\uD83E\uDDD9\uD83C\uDFFE\u200D♀️", + [":woman_mage::skin-tone-5:"] = "\uD83E\uDDD9\uD83C\uDFFF\u200D♀️", + [":woman_mage_dark_skin_tone:"] = "\uD83E\uDDD9\uD83C\uDFFF\u200D♀️", + [":woman_mage_light_skin_tone:"] = "\uD83E\uDDD9\uD83C\uDFFB\u200D♀️", + [":woman_mage_medium_dark_skin_tone:"] = "\uD83E\uDDD9\uD83C\uDFFE\u200D♀️", + [":woman_mage_medium_light_skin_tone:"] = "\uD83E\uDDD9\uD83C\uDFFC\u200D♀️", + [":woman_mage_medium_skin_tone:"] = "\uD83E\uDDD9\uD83C\uDFFD\u200D♀️", + [":woman_mage_tone1:"] = "\uD83E\uDDD9\uD83C\uDFFB\u200D♀️", + [":woman_mage_tone2:"] = "\uD83E\uDDD9\uD83C\uDFFC\u200D♀️", + [":woman_mage_tone3:"] = "\uD83E\uDDD9\uD83C\uDFFD\u200D♀️", + [":woman_mage_tone4:"] = "\uD83E\uDDD9\uD83C\uDFFE\u200D♀️", + [":woman_mage_tone5:"] = "\uD83E\uDDD9\uD83C\uDFFF\u200D♀️", + [":woman_mechanic:"] = "\uD83D\uDC69\u200D\uD83D\uDD27", + [":woman_mechanic::skin-tone-1:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83D\uDD27", + [":woman_mechanic::skin-tone-2:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83D\uDD27", + [":woman_mechanic::skin-tone-3:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83D\uDD27", + [":woman_mechanic::skin-tone-4:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83D\uDD27", + [":woman_mechanic::skin-tone-5:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83D\uDD27", + [":woman_mechanic_dark_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83D\uDD27", + [":woman_mechanic_light_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83D\uDD27", + [":woman_mechanic_medium_dark_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83D\uDD27", + [":woman_mechanic_medium_light_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83D\uDD27", + [":woman_mechanic_medium_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83D\uDD27", + [":woman_mechanic_tone1:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83D\uDD27", + [":woman_mechanic_tone2:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83D\uDD27", + [":woman_mechanic_tone3:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83D\uDD27", + [":woman_mechanic_tone4:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83D\uDD27", + [":woman_mechanic_tone5:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83D\uDD27", + [":woman_mountain_biking:"] = "\uD83D\uDEB5\u200D♀️", + [":woman_mountain_biking::skin-tone-1:"] = "\uD83D\uDEB5\uD83C\uDFFB\u200D♀️", + [":woman_mountain_biking::skin-tone-2:"] = "\uD83D\uDEB5\uD83C\uDFFC\u200D♀️", + [":woman_mountain_biking::skin-tone-3:"] = "\uD83D\uDEB5\uD83C\uDFFD\u200D♀️", + [":woman_mountain_biking::skin-tone-4:"] = "\uD83D\uDEB5\uD83C\uDFFE\u200D♀️", + [":woman_mountain_biking::skin-tone-5:"] = "\uD83D\uDEB5\uD83C\uDFFF\u200D♀️", + [":woman_mountain_biking_dark_skin_tone:"] = "\uD83D\uDEB5\uD83C\uDFFF\u200D♀️", + [":woman_mountain_biking_light_skin_tone:"] = "\uD83D\uDEB5\uD83C\uDFFB\u200D♀️", + [":woman_mountain_biking_medium_dark_skin_tone:"] = "\uD83D\uDEB5\uD83C\uDFFE\u200D♀️", + [":woman_mountain_biking_medium_light_skin_tone:"] = "\uD83D\uDEB5\uD83C\uDFFC\u200D♀️", + [":woman_mountain_biking_medium_skin_tone:"] = "\uD83D\uDEB5\uD83C\uDFFD\u200D♀️", + [":woman_mountain_biking_tone1:"] = "\uD83D\uDEB5\uD83C\uDFFB\u200D♀️", + [":woman_mountain_biking_tone2:"] = "\uD83D\uDEB5\uD83C\uDFFC\u200D♀️", + [":woman_mountain_biking_tone3:"] = "\uD83D\uDEB5\uD83C\uDFFD\u200D♀️", + [":woman_mountain_biking_tone4:"] = "\uD83D\uDEB5\uD83C\uDFFE\u200D♀️", + [":woman_mountain_biking_tone5:"] = "\uD83D\uDEB5\uD83C\uDFFF\u200D♀️", + [":woman_office_worker:"] = "\uD83D\uDC69\u200D\uD83D\uDCBC", + [":woman_office_worker::skin-tone-1:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83D\uDCBC", + [":woman_office_worker::skin-tone-2:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83D\uDCBC", + [":woman_office_worker::skin-tone-3:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83D\uDCBC", + [":woman_office_worker::skin-tone-4:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83D\uDCBC", + [":woman_office_worker::skin-tone-5:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83D\uDCBC", + [":woman_office_worker_dark_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83D\uDCBC", + [":woman_office_worker_light_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83D\uDCBC", + [":woman_office_worker_medium_dark_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83D\uDCBC", + [":woman_office_worker_medium_light_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83D\uDCBC", + [":woman_office_worker_medium_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83D\uDCBC", + [":woman_office_worker_tone1:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83D\uDCBC", + [":woman_office_worker_tone2:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83D\uDCBC", + [":woman_office_worker_tone3:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83D\uDCBC", + [":woman_office_worker_tone4:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83D\uDCBC", + [":woman_office_worker_tone5:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83D\uDCBC", + [":woman_pilot:"] = "\uD83D\uDC69\u200D✈️", + [":woman_pilot::skin-tone-1:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D✈️", + [":woman_pilot::skin-tone-2:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D✈️", + [":woman_pilot::skin-tone-3:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D✈️", + [":woman_pilot::skin-tone-4:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D✈️", + [":woman_pilot::skin-tone-5:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D✈️", + [":woman_pilot_dark_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D✈️", + [":woman_pilot_light_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D✈️", + [":woman_pilot_medium_dark_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D✈️", + [":woman_pilot_medium_light_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D✈️", + [":woman_pilot_medium_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D✈️", + [":woman_pilot_tone1:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D✈️", + [":woman_pilot_tone2:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D✈️", + [":woman_pilot_tone3:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D✈️", + [":woman_pilot_tone4:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D✈️", + [":woman_pilot_tone5:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D✈️", + [":woman_playing_handball:"] = "\uD83E\uDD3E\u200D♀️", + [":woman_playing_handball::skin-tone-1:"] = "\uD83E\uDD3E\uD83C\uDFFB\u200D♀️", + [":woman_playing_handball::skin-tone-2:"] = "\uD83E\uDD3E\uD83C\uDFFC\u200D♀️", + [":woman_playing_handball::skin-tone-3:"] = "\uD83E\uDD3E\uD83C\uDFFD\u200D♀️", + [":woman_playing_handball::skin-tone-4:"] = "\uD83E\uDD3E\uD83C\uDFFE\u200D♀️", + [":woman_playing_handball::skin-tone-5:"] = "\uD83E\uDD3E\uD83C\uDFFF\u200D♀️", + [":woman_playing_handball_dark_skin_tone:"] = "\uD83E\uDD3E\uD83C\uDFFF\u200D♀️", + [":woman_playing_handball_light_skin_tone:"] = "\uD83E\uDD3E\uD83C\uDFFB\u200D♀️", + [":woman_playing_handball_medium_dark_skin_tone:"] = "\uD83E\uDD3E\uD83C\uDFFE\u200D♀️", + [":woman_playing_handball_medium_light_skin_tone:"] = "\uD83E\uDD3E\uD83C\uDFFC\u200D♀️", + [":woman_playing_handball_medium_skin_tone:"] = "\uD83E\uDD3E\uD83C\uDFFD\u200D♀️", + [":woman_playing_handball_tone1:"] = "\uD83E\uDD3E\uD83C\uDFFB\u200D♀️", + [":woman_playing_handball_tone2:"] = "\uD83E\uDD3E\uD83C\uDFFC\u200D♀️", + [":woman_playing_handball_tone3:"] = "\uD83E\uDD3E\uD83C\uDFFD\u200D♀️", + [":woman_playing_handball_tone4:"] = "\uD83E\uDD3E\uD83C\uDFFE\u200D♀️", + [":woman_playing_handball_tone5:"] = "\uD83E\uDD3E\uD83C\uDFFF\u200D♀️", + [":woman_playing_water_polo:"] = "\uD83E\uDD3D\u200D♀️", + [":woman_playing_water_polo::skin-tone-1:"] = "\uD83E\uDD3D\uD83C\uDFFB\u200D♀️", + [":woman_playing_water_polo::skin-tone-2:"] = "\uD83E\uDD3D\uD83C\uDFFC\u200D♀️", + [":woman_playing_water_polo::skin-tone-3:"] = "\uD83E\uDD3D\uD83C\uDFFD\u200D♀️", + [":woman_playing_water_polo::skin-tone-4:"] = "\uD83E\uDD3D\uD83C\uDFFE\u200D♀️", + [":woman_playing_water_polo::skin-tone-5:"] = "\uD83E\uDD3D\uD83C\uDFFF\u200D♀️", + [":woman_playing_water_polo_dark_skin_tone:"] = "\uD83E\uDD3D\uD83C\uDFFF\u200D♀️", + [":woman_playing_water_polo_light_skin_tone:"] = "\uD83E\uDD3D\uD83C\uDFFB\u200D♀️", + [":woman_playing_water_polo_medium_dark_skin_tone:"] = "\uD83E\uDD3D\uD83C\uDFFE\u200D♀️", + [":woman_playing_water_polo_medium_light_skin_tone:"] = "\uD83E\uDD3D\uD83C\uDFFC\u200D♀️", + [":woman_playing_water_polo_medium_skin_tone:"] = "\uD83E\uDD3D\uD83C\uDFFD\u200D♀️", + [":woman_playing_water_polo_tone1:"] = "\uD83E\uDD3D\uD83C\uDFFB\u200D♀️", + [":woman_playing_water_polo_tone2:"] = "\uD83E\uDD3D\uD83C\uDFFC\u200D♀️", + [":woman_playing_water_polo_tone3:"] = "\uD83E\uDD3D\uD83C\uDFFD\u200D♀️", + [":woman_playing_water_polo_tone4:"] = "\uD83E\uDD3D\uD83C\uDFFE\u200D♀️", + [":woman_playing_water_polo_tone5:"] = "\uD83E\uDD3D\uD83C\uDFFF\u200D♀️", + [":woman_police_officer:"] = "\uD83D\uDC6E\u200D♀️", + [":woman_police_officer::skin-tone-1:"] = "\uD83D\uDC6E\uD83C\uDFFB\u200D♀️", + [":woman_police_officer::skin-tone-2:"] = "\uD83D\uDC6E\uD83C\uDFFC\u200D♀️", + [":woman_police_officer::skin-tone-3:"] = "\uD83D\uDC6E\uD83C\uDFFD\u200D♀️", + [":woman_police_officer::skin-tone-4:"] = "\uD83D\uDC6E\uD83C\uDFFE\u200D♀️", + [":woman_police_officer::skin-tone-5:"] = "\uD83D\uDC6E\uD83C\uDFFF\u200D♀️", + [":woman_police_officer_dark_skin_tone:"] = "\uD83D\uDC6E\uD83C\uDFFF\u200D♀️", + [":woman_police_officer_light_skin_tone:"] = "\uD83D\uDC6E\uD83C\uDFFB\u200D♀️", + [":woman_police_officer_medium_dark_skin_tone:"] = "\uD83D\uDC6E\uD83C\uDFFE\u200D♀️", + [":woman_police_officer_medium_light_skin_tone:"] = "\uD83D\uDC6E\uD83C\uDFFC\u200D♀️", + [":woman_police_officer_medium_skin_tone:"] = "\uD83D\uDC6E\uD83C\uDFFD\u200D♀️", + [":woman_police_officer_tone1:"] = "\uD83D\uDC6E\uD83C\uDFFB\u200D♀️", + [":woman_police_officer_tone2:"] = "\uD83D\uDC6E\uD83C\uDFFC\u200D♀️", + [":woman_police_officer_tone3:"] = "\uD83D\uDC6E\uD83C\uDFFD\u200D♀️", + [":woman_police_officer_tone4:"] = "\uD83D\uDC6E\uD83C\uDFFE\u200D♀️", + [":woman_police_officer_tone5:"] = "\uD83D\uDC6E\uD83C\uDFFF\u200D♀️", + [":woman_pouting:"] = "\uD83D\uDE4E\u200D♀️", + [":woman_pouting::skin-tone-1:"] = "\uD83D\uDE4E\uD83C\uDFFB\u200D♀️", + [":woman_pouting::skin-tone-2:"] = "\uD83D\uDE4E\uD83C\uDFFC\u200D♀️", + [":woman_pouting::skin-tone-3:"] = "\uD83D\uDE4E\uD83C\uDFFD\u200D♀️", + [":woman_pouting::skin-tone-4:"] = "\uD83D\uDE4E\uD83C\uDFFE\u200D♀️", + [":woman_pouting::skin-tone-5:"] = "\uD83D\uDE4E\uD83C\uDFFF\u200D♀️", + [":woman_pouting_dark_skin_tone:"] = "\uD83D\uDE4E\uD83C\uDFFF\u200D♀️", + [":woman_pouting_light_skin_tone:"] = "\uD83D\uDE4E\uD83C\uDFFB\u200D♀️", + [":woman_pouting_medium_dark_skin_tone:"] = "\uD83D\uDE4E\uD83C\uDFFE\u200D♀️", + [":woman_pouting_medium_light_skin_tone:"] = "\uD83D\uDE4E\uD83C\uDFFC\u200D♀️", + [":woman_pouting_medium_skin_tone:"] = "\uD83D\uDE4E\uD83C\uDFFD\u200D♀️", + [":woman_pouting_tone1:"] = "\uD83D\uDE4E\uD83C\uDFFB\u200D♀️", + [":woman_pouting_tone2:"] = "\uD83D\uDE4E\uD83C\uDFFC\u200D♀️", + [":woman_pouting_tone3:"] = "\uD83D\uDE4E\uD83C\uDFFD\u200D♀️", + [":woman_pouting_tone4:"] = "\uD83D\uDE4E\uD83C\uDFFE\u200D♀️", + [":woman_pouting_tone5:"] = "\uD83D\uDE4E\uD83C\uDFFF\u200D♀️", + [":woman_raising_hand:"] = "\uD83D\uDE4B\u200D♀️", + [":woman_raising_hand::skin-tone-1:"] = "\uD83D\uDE4B\uD83C\uDFFB\u200D♀️", + [":woman_raising_hand::skin-tone-2:"] = "\uD83D\uDE4B\uD83C\uDFFC\u200D♀️", + [":woman_raising_hand::skin-tone-3:"] = "\uD83D\uDE4B\uD83C\uDFFD\u200D♀️", + [":woman_raising_hand::skin-tone-4:"] = "\uD83D\uDE4B\uD83C\uDFFE\u200D♀️", + [":woman_raising_hand::skin-tone-5:"] = "\uD83D\uDE4B\uD83C\uDFFF\u200D♀️", + [":woman_raising_hand_dark_skin_tone:"] = "\uD83D\uDE4B\uD83C\uDFFF\u200D♀️", + [":woman_raising_hand_light_skin_tone:"] = "\uD83D\uDE4B\uD83C\uDFFB\u200D♀️", + [":woman_raising_hand_medium_dark_skin_tone:"] = "\uD83D\uDE4B\uD83C\uDFFE\u200D♀️", + [":woman_raising_hand_medium_light_skin_tone:"] = "\uD83D\uDE4B\uD83C\uDFFC\u200D♀️", + [":woman_raising_hand_medium_skin_tone:"] = "\uD83D\uDE4B\uD83C\uDFFD\u200D♀️", + [":woman_raising_hand_tone1:"] = "\uD83D\uDE4B\uD83C\uDFFB\u200D♀️", + [":woman_raising_hand_tone2:"] = "\uD83D\uDE4B\uD83C\uDFFC\u200D♀️", + [":woman_raising_hand_tone3:"] = "\uD83D\uDE4B\uD83C\uDFFD\u200D♀️", + [":woman_raising_hand_tone4:"] = "\uD83D\uDE4B\uD83C\uDFFE\u200D♀️", + [":woman_raising_hand_tone5:"] = "\uD83D\uDE4B\uD83C\uDFFF\u200D♀️", + [":woman_red_haired:"] = "\uD83D\uDC69\u200D\uD83E\uDDB0", + [":woman_red_haired::skin-tone-1:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83E\uDDB0", + [":woman_red_haired::skin-tone-2:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83E\uDDB0", + [":woman_red_haired::skin-tone-3:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83E\uDDB0", + [":woman_red_haired::skin-tone-4:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83E\uDDB0", + [":woman_red_haired::skin-tone-5:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83E\uDDB0", + [":woman_red_haired_dark_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83E\uDDB0", + [":woman_red_haired_light_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83E\uDDB0", + [":woman_red_haired_medium_dark_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83E\uDDB0", + [":woman_red_haired_medium_light_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83E\uDDB0", + [":woman_red_haired_medium_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83E\uDDB0", + [":woman_red_haired_tone1:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83E\uDDB0", + [":woman_red_haired_tone2:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83E\uDDB0", + [":woman_red_haired_tone3:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83E\uDDB0", + [":woman_red_haired_tone4:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83E\uDDB0", + [":woman_red_haired_tone5:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83E\uDDB0", + [":woman_rowing_boat:"] = "\uD83D\uDEA3\u200D♀️", + [":woman_rowing_boat::skin-tone-1:"] = "\uD83D\uDEA3\uD83C\uDFFB\u200D♀️", + [":woman_rowing_boat::skin-tone-2:"] = "\uD83D\uDEA3\uD83C\uDFFC\u200D♀️", + [":woman_rowing_boat::skin-tone-3:"] = "\uD83D\uDEA3\uD83C\uDFFD\u200D♀️", + [":woman_rowing_boat::skin-tone-4:"] = "\uD83D\uDEA3\uD83C\uDFFE\u200D♀️", + [":woman_rowing_boat::skin-tone-5:"] = "\uD83D\uDEA3\uD83C\uDFFF\u200D♀️", + [":woman_rowing_boat_dark_skin_tone:"] = "\uD83D\uDEA3\uD83C\uDFFF\u200D♀️", + [":woman_rowing_boat_light_skin_tone:"] = "\uD83D\uDEA3\uD83C\uDFFB\u200D♀️", + [":woman_rowing_boat_medium_dark_skin_tone:"] = "\uD83D\uDEA3\uD83C\uDFFE\u200D♀️", + [":woman_rowing_boat_medium_light_skin_tone:"] = "\uD83D\uDEA3\uD83C\uDFFC\u200D♀️", + [":woman_rowing_boat_medium_skin_tone:"] = "\uD83D\uDEA3\uD83C\uDFFD\u200D♀️", + [":woman_rowing_boat_tone1:"] = "\uD83D\uDEA3\uD83C\uDFFB\u200D♀️", + [":woman_rowing_boat_tone2:"] = "\uD83D\uDEA3\uD83C\uDFFC\u200D♀️", + [":woman_rowing_boat_tone3:"] = "\uD83D\uDEA3\uD83C\uDFFD\u200D♀️", + [":woman_rowing_boat_tone4:"] = "\uD83D\uDEA3\uD83C\uDFFE\u200D♀️", + [":woman_rowing_boat_tone5:"] = "\uD83D\uDEA3\uD83C\uDFFF\u200D♀️", + [":woman_running:"] = "\uD83C\uDFC3\u200D♀️", + [":woman_running::skin-tone-1:"] = "\uD83C\uDFC3\uD83C\uDFFB\u200D♀️", + [":woman_running::skin-tone-2:"] = "\uD83C\uDFC3\uD83C\uDFFC\u200D♀️", + [":woman_running::skin-tone-3:"] = "\uD83C\uDFC3\uD83C\uDFFD\u200D♀️", + [":woman_running::skin-tone-4:"] = "\uD83C\uDFC3\uD83C\uDFFE\u200D♀️", + [":woman_running::skin-tone-5:"] = "\uD83C\uDFC3\uD83C\uDFFF\u200D♀️", + [":woman_running_dark_skin_tone:"] = "\uD83C\uDFC3\uD83C\uDFFF\u200D♀️", + [":woman_running_light_skin_tone:"] = "\uD83C\uDFC3\uD83C\uDFFB\u200D♀️", + [":woman_running_medium_dark_skin_tone:"] = "\uD83C\uDFC3\uD83C\uDFFE\u200D♀️", + [":woman_running_medium_light_skin_tone:"] = "\uD83C\uDFC3\uD83C\uDFFC\u200D♀️", + [":woman_running_medium_skin_tone:"] = "\uD83C\uDFC3\uD83C\uDFFD\u200D♀️", + [":woman_running_tone1:"] = "\uD83C\uDFC3\uD83C\uDFFB\u200D♀️", + [":woman_running_tone2:"] = "\uD83C\uDFC3\uD83C\uDFFC\u200D♀️", + [":woman_running_tone3:"] = "\uD83C\uDFC3\uD83C\uDFFD\u200D♀️", + [":woman_running_tone4:"] = "\uD83C\uDFC3\uD83C\uDFFE\u200D♀️", + [":woman_running_tone5:"] = "\uD83C\uDFC3\uD83C\uDFFF\u200D♀️", + [":woman_scientist:"] = "\uD83D\uDC69\u200D\uD83D\uDD2C", + [":woman_scientist::skin-tone-1:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83D\uDD2C", + [":woman_scientist::skin-tone-2:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83D\uDD2C", + [":woman_scientist::skin-tone-3:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83D\uDD2C", + [":woman_scientist::skin-tone-4:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83D\uDD2C", + [":woman_scientist::skin-tone-5:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83D\uDD2C", + [":woman_scientist_dark_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83D\uDD2C", + [":woman_scientist_light_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83D\uDD2C", + [":woman_scientist_medium_dark_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83D\uDD2C", + [":woman_scientist_medium_light_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83D\uDD2C", + [":woman_scientist_medium_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83D\uDD2C", + [":woman_scientist_tone1:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83D\uDD2C", + [":woman_scientist_tone2:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83D\uDD2C", + [":woman_scientist_tone3:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83D\uDD2C", + [":woman_scientist_tone4:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83D\uDD2C", + [":woman_scientist_tone5:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83D\uDD2C", + [":woman_shrugging:"] = "\uD83E\uDD37\u200D♀️", + [":woman_shrugging::skin-tone-1:"] = "\uD83E\uDD37\uD83C\uDFFB\u200D♀️", + [":woman_shrugging::skin-tone-2:"] = "\uD83E\uDD37\uD83C\uDFFC\u200D♀️", + [":woman_shrugging::skin-tone-3:"] = "\uD83E\uDD37\uD83C\uDFFD\u200D♀️", + [":woman_shrugging::skin-tone-4:"] = "\uD83E\uDD37\uD83C\uDFFE\u200D♀️", + [":woman_shrugging::skin-tone-5:"] = "\uD83E\uDD37\uD83C\uDFFF\u200D♀️", + [":woman_shrugging_dark_skin_tone:"] = "\uD83E\uDD37\uD83C\uDFFF\u200D♀️", + [":woman_shrugging_light_skin_tone:"] = "\uD83E\uDD37\uD83C\uDFFB\u200D♀️", + [":woman_shrugging_medium_dark_skin_tone:"] = "\uD83E\uDD37\uD83C\uDFFE\u200D♀️", + [":woman_shrugging_medium_light_skin_tone:"] = "\uD83E\uDD37\uD83C\uDFFC\u200D♀️", + [":woman_shrugging_medium_skin_tone:"] = "\uD83E\uDD37\uD83C\uDFFD\u200D♀️", + [":woman_shrugging_tone1:"] = "\uD83E\uDD37\uD83C\uDFFB\u200D♀️", + [":woman_shrugging_tone2:"] = "\uD83E\uDD37\uD83C\uDFFC\u200D♀️", + [":woman_shrugging_tone3:"] = "\uD83E\uDD37\uD83C\uDFFD\u200D♀️", + [":woman_shrugging_tone4:"] = "\uD83E\uDD37\uD83C\uDFFE\u200D♀️", + [":woman_shrugging_tone5:"] = "\uD83E\uDD37\uD83C\uDFFF\u200D♀️", + [":woman_singer:"] = "\uD83D\uDC69\u200D\uD83C\uDFA4", + [":woman_singer::skin-tone-1:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83C\uDFA4", + [":woman_singer::skin-tone-2:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83C\uDFA4", + [":woman_singer::skin-tone-3:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83C\uDFA4", + [":woman_singer::skin-tone-4:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83C\uDFA4", + [":woman_singer::skin-tone-5:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83C\uDFA4", + [":woman_singer_dark_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83C\uDFA4", + [":woman_singer_light_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83C\uDFA4", + [":woman_singer_medium_dark_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83C\uDFA4", + [":woman_singer_medium_light_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83C\uDFA4", + [":woman_singer_medium_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83C\uDFA4", + [":woman_singer_tone1:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83C\uDFA4", + [":woman_singer_tone2:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83C\uDFA4", + [":woman_singer_tone3:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83C\uDFA4", + [":woman_singer_tone4:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83C\uDFA4", + [":woman_singer_tone5:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83C\uDFA4", + [":woman_standing:"] = "\uD83E\uDDCD\u200D♀️", + [":woman_standing::skin-tone-1:"] = "\uD83E\uDDCD\uD83C\uDFFB\u200D♀️", + [":woman_standing::skin-tone-2:"] = "\uD83E\uDDCD\uD83C\uDFFC\u200D♀️", + [":woman_standing::skin-tone-3:"] = "\uD83E\uDDCD\uD83C\uDFFD\u200D♀️", + [":woman_standing::skin-tone-4:"] = "\uD83E\uDDCD\uD83C\uDFFE\u200D♀️", + [":woman_standing::skin-tone-5:"] = "\uD83E\uDDCD\uD83C\uDFFF\u200D♀️", + [":woman_standing_dark_skin_tone:"] = "\uD83E\uDDCD\uD83C\uDFFF\u200D♀️", + [":woman_standing_light_skin_tone:"] = "\uD83E\uDDCD\uD83C\uDFFB\u200D♀️", + [":woman_standing_medium_dark_skin_tone:"] = "\uD83E\uDDCD\uD83C\uDFFE\u200D♀️", + [":woman_standing_medium_light_skin_tone:"] = "\uD83E\uDDCD\uD83C\uDFFC\u200D♀️", + [":woman_standing_medium_skin_tone:"] = "\uD83E\uDDCD\uD83C\uDFFD\u200D♀️", + [":woman_standing_tone1:"] = "\uD83E\uDDCD\uD83C\uDFFB\u200D♀️", + [":woman_standing_tone2:"] = "\uD83E\uDDCD\uD83C\uDFFC\u200D♀️", + [":woman_standing_tone3:"] = "\uD83E\uDDCD\uD83C\uDFFD\u200D♀️", + [":woman_standing_tone4:"] = "\uD83E\uDDCD\uD83C\uDFFE\u200D♀️", + [":woman_standing_tone5:"] = "\uD83E\uDDCD\uD83C\uDFFF\u200D♀️", + [":woman_student:"] = "\uD83D\uDC69\u200D\uD83C\uDF93", + [":woman_student::skin-tone-1:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83C\uDF93", + [":woman_student::skin-tone-2:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83C\uDF93", + [":woman_student::skin-tone-3:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83C\uDF93", + [":woman_student::skin-tone-4:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83C\uDF93", + [":woman_student::skin-tone-5:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83C\uDF93", + [":woman_student_dark_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83C\uDF93", + [":woman_student_light_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83C\uDF93", + [":woman_student_medium_dark_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83C\uDF93", + [":woman_student_medium_light_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83C\uDF93", + [":woman_student_medium_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83C\uDF93", + [":woman_student_tone1:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83C\uDF93", + [":woman_student_tone2:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83C\uDF93", + [":woman_student_tone3:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83C\uDF93", + [":woman_student_tone4:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83C\uDF93", + [":woman_student_tone5:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83C\uDF93", + [":woman_superhero:"] = "\uD83E\uDDB8\u200D♀️", + [":woman_superhero::skin-tone-1:"] = "\uD83E\uDDB8\uD83C\uDFFB\u200D♀️", + [":woman_superhero::skin-tone-2:"] = "\uD83E\uDDB8\uD83C\uDFFC\u200D♀️", + [":woman_superhero::skin-tone-3:"] = "\uD83E\uDDB8\uD83C\uDFFD\u200D♀️", + [":woman_superhero::skin-tone-4:"] = "\uD83E\uDDB8\uD83C\uDFFE\u200D♀️", + [":woman_superhero::skin-tone-5:"] = "\uD83E\uDDB8\uD83C\uDFFF\u200D♀️", + [":woman_superhero_dark_skin_tone:"] = "\uD83E\uDDB8\uD83C\uDFFF\u200D♀️", + [":woman_superhero_light_skin_tone:"] = "\uD83E\uDDB8\uD83C\uDFFB\u200D♀️", + [":woman_superhero_medium_dark_skin_tone:"] = "\uD83E\uDDB8\uD83C\uDFFE\u200D♀️", + [":woman_superhero_medium_light_skin_tone:"] = "\uD83E\uDDB8\uD83C\uDFFC\u200D♀️", + [":woman_superhero_medium_skin_tone:"] = "\uD83E\uDDB8\uD83C\uDFFD\u200D♀️", + [":woman_superhero_tone1:"] = "\uD83E\uDDB8\uD83C\uDFFB\u200D♀️", + [":woman_superhero_tone2:"] = "\uD83E\uDDB8\uD83C\uDFFC\u200D♀️", + [":woman_superhero_tone3:"] = "\uD83E\uDDB8\uD83C\uDFFD\u200D♀️", + [":woman_superhero_tone4:"] = "\uD83E\uDDB8\uD83C\uDFFE\u200D♀️", + [":woman_superhero_tone5:"] = "\uD83E\uDDB8\uD83C\uDFFF\u200D♀️", + [":woman_supervillain:"] = "\uD83E\uDDB9\u200D♀️", + [":woman_supervillain::skin-tone-1:"] = "\uD83E\uDDB9\uD83C\uDFFB\u200D♀️", + [":woman_supervillain::skin-tone-2:"] = "\uD83E\uDDB9\uD83C\uDFFC\u200D♀️", + [":woman_supervillain::skin-tone-3:"] = "\uD83E\uDDB9\uD83C\uDFFD\u200D♀️", + [":woman_supervillain::skin-tone-4:"] = "\uD83E\uDDB9\uD83C\uDFFE\u200D♀️", + [":woman_supervillain::skin-tone-5:"] = "\uD83E\uDDB9\uD83C\uDFFF\u200D♀️", + [":woman_supervillain_dark_skin_tone:"] = "\uD83E\uDDB9\uD83C\uDFFF\u200D♀️", + [":woman_supervillain_light_skin_tone:"] = "\uD83E\uDDB9\uD83C\uDFFB\u200D♀️", + [":woman_supervillain_medium_dark_skin_tone:"] = "\uD83E\uDDB9\uD83C\uDFFE\u200D♀️", + [":woman_supervillain_medium_light_skin_tone:"] = "\uD83E\uDDB9\uD83C\uDFFC\u200D♀️", + [":woman_supervillain_medium_skin_tone:"] = "\uD83E\uDDB9\uD83C\uDFFD\u200D♀️", + [":woman_supervillain_tone1:"] = "\uD83E\uDDB9\uD83C\uDFFB\u200D♀️", + [":woman_supervillain_tone2:"] = "\uD83E\uDDB9\uD83C\uDFFC\u200D♀️", + [":woman_supervillain_tone3:"] = "\uD83E\uDDB9\uD83C\uDFFD\u200D♀️", + [":woman_supervillain_tone4:"] = "\uD83E\uDDB9\uD83C\uDFFE\u200D♀️", + [":woman_supervillain_tone5:"] = "\uD83E\uDDB9\uD83C\uDFFF\u200D♀️", + [":woman_surfing:"] = "\uD83C\uDFC4\u200D♀️", + [":woman_surfing::skin-tone-1:"] = "\uD83C\uDFC4\uD83C\uDFFB\u200D♀️", + [":woman_surfing::skin-tone-2:"] = "\uD83C\uDFC4\uD83C\uDFFC\u200D♀️", + [":woman_surfing::skin-tone-3:"] = "\uD83C\uDFC4\uD83C\uDFFD\u200D♀️", + [":woman_surfing::skin-tone-4:"] = "\uD83C\uDFC4\uD83C\uDFFE\u200D♀️", + [":woman_surfing::skin-tone-5:"] = "\uD83C\uDFC4\uD83C\uDFFF\u200D♀️", + [":woman_surfing_dark_skin_tone:"] = "\uD83C\uDFC4\uD83C\uDFFF\u200D♀️", + [":woman_surfing_light_skin_tone:"] = "\uD83C\uDFC4\uD83C\uDFFB\u200D♀️", + [":woman_surfing_medium_dark_skin_tone:"] = "\uD83C\uDFC4\uD83C\uDFFE\u200D♀️", + [":woman_surfing_medium_light_skin_tone:"] = "\uD83C\uDFC4\uD83C\uDFFC\u200D♀️", + [":woman_surfing_medium_skin_tone:"] = "\uD83C\uDFC4\uD83C\uDFFD\u200D♀️", + [":woman_surfing_tone1:"] = "\uD83C\uDFC4\uD83C\uDFFB\u200D♀️", + [":woman_surfing_tone2:"] = "\uD83C\uDFC4\uD83C\uDFFC\u200D♀️", + [":woman_surfing_tone3:"] = "\uD83C\uDFC4\uD83C\uDFFD\u200D♀️", + [":woman_surfing_tone4:"] = "\uD83C\uDFC4\uD83C\uDFFE\u200D♀️", + [":woman_surfing_tone5:"] = "\uD83C\uDFC4\uD83C\uDFFF\u200D♀️", + [":woman_swimming:"] = "\uD83C\uDFCA\u200D♀️", + [":woman_swimming::skin-tone-1:"] = "\uD83C\uDFCA\uD83C\uDFFB\u200D♀️", + [":woman_swimming::skin-tone-2:"] = "\uD83C\uDFCA\uD83C\uDFFC\u200D♀️", + [":woman_swimming::skin-tone-3:"] = "\uD83C\uDFCA\uD83C\uDFFD\u200D♀️", + [":woman_swimming::skin-tone-4:"] = "\uD83C\uDFCA\uD83C\uDFFE\u200D♀️", + [":woman_swimming::skin-tone-5:"] = "\uD83C\uDFCA\uD83C\uDFFF\u200D♀️", + [":woman_swimming_dark_skin_tone:"] = "\uD83C\uDFCA\uD83C\uDFFF\u200D♀️", + [":woman_swimming_light_skin_tone:"] = "\uD83C\uDFCA\uD83C\uDFFB\u200D♀️", + [":woman_swimming_medium_dark_skin_tone:"] = "\uD83C\uDFCA\uD83C\uDFFE\u200D♀️", + [":woman_swimming_medium_light_skin_tone:"] = "\uD83C\uDFCA\uD83C\uDFFC\u200D♀️", + [":woman_swimming_medium_skin_tone:"] = "\uD83C\uDFCA\uD83C\uDFFD\u200D♀️", + [":woman_swimming_tone1:"] = "\uD83C\uDFCA\uD83C\uDFFB\u200D♀️", + [":woman_swimming_tone2:"] = "\uD83C\uDFCA\uD83C\uDFFC\u200D♀️", + [":woman_swimming_tone3:"] = "\uD83C\uDFCA\uD83C\uDFFD\u200D♀️", + [":woman_swimming_tone4:"] = "\uD83C\uDFCA\uD83C\uDFFE\u200D♀️", + [":woman_swimming_tone5:"] = "\uD83C\uDFCA\uD83C\uDFFF\u200D♀️", + [":woman_teacher:"] = "\uD83D\uDC69\u200D\uD83C\uDFEB", + [":woman_teacher::skin-tone-1:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83C\uDFEB", + [":woman_teacher::skin-tone-2:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83C\uDFEB", + [":woman_teacher::skin-tone-3:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83C\uDFEB", + [":woman_teacher::skin-tone-4:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83C\uDFEB", + [":woman_teacher::skin-tone-5:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83C\uDFEB", + [":woman_teacher_dark_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83C\uDFEB", + [":woman_teacher_light_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83C\uDFEB", + [":woman_teacher_medium_dark_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83C\uDFEB", + [":woman_teacher_medium_light_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83C\uDFEB", + [":woman_teacher_medium_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83C\uDFEB", + [":woman_teacher_tone1:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83C\uDFEB", + [":woman_teacher_tone2:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83C\uDFEB", + [":woman_teacher_tone3:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83C\uDFEB", + [":woman_teacher_tone4:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83C\uDFEB", + [":woman_teacher_tone5:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83C\uDFEB", + [":woman_technologist:"] = "\uD83D\uDC69\u200D\uD83D\uDCBB", + [":woman_technologist::skin-tone-1:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83D\uDCBB", + [":woman_technologist::skin-tone-2:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83D\uDCBB", + [":woman_technologist::skin-tone-3:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83D\uDCBB", + [":woman_technologist::skin-tone-4:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83D\uDCBB", + [":woman_technologist::skin-tone-5:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83D\uDCBB", + [":woman_technologist_dark_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83D\uDCBB", + [":woman_technologist_light_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83D\uDCBB", + [":woman_technologist_medium_dark_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83D\uDCBB", + [":woman_technologist_medium_light_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83D\uDCBB", + [":woman_technologist_medium_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83D\uDCBB", + [":woman_technologist_tone1:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83D\uDCBB", + [":woman_technologist_tone2:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83D\uDCBB", + [":woman_technologist_tone3:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83D\uDCBB", + [":woman_technologist_tone4:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83D\uDCBB", + [":woman_technologist_tone5:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83D\uDCBB", + [":woman_tipping_hand:"] = "\uD83D\uDC81\u200D♀️", + [":woman_tipping_hand::skin-tone-1:"] = "\uD83D\uDC81\uD83C\uDFFB\u200D♀️", + [":woman_tipping_hand::skin-tone-2:"] = "\uD83D\uDC81\uD83C\uDFFC\u200D♀️", + [":woman_tipping_hand::skin-tone-3:"] = "\uD83D\uDC81\uD83C\uDFFD\u200D♀️", + [":woman_tipping_hand::skin-tone-4:"] = "\uD83D\uDC81\uD83C\uDFFE\u200D♀️", + [":woman_tipping_hand::skin-tone-5:"] = "\uD83D\uDC81\uD83C\uDFFF\u200D♀️", + [":woman_tipping_hand_dark_skin_tone:"] = "\uD83D\uDC81\uD83C\uDFFF\u200D♀️", + [":woman_tipping_hand_light_skin_tone:"] = "\uD83D\uDC81\uD83C\uDFFB\u200D♀️", + [":woman_tipping_hand_medium_dark_skin_tone:"] = "\uD83D\uDC81\uD83C\uDFFE\u200D♀️", + [":woman_tipping_hand_medium_light_skin_tone:"] = "\uD83D\uDC81\uD83C\uDFFC\u200D♀️", + [":woman_tipping_hand_medium_skin_tone:"] = "\uD83D\uDC81\uD83C\uDFFD\u200D♀️", + [":woman_tipping_hand_tone1:"] = "\uD83D\uDC81\uD83C\uDFFB\u200D♀️", + [":woman_tipping_hand_tone2:"] = "\uD83D\uDC81\uD83C\uDFFC\u200D♀️", + [":woman_tipping_hand_tone3:"] = "\uD83D\uDC81\uD83C\uDFFD\u200D♀️", + [":woman_tipping_hand_tone4:"] = "\uD83D\uDC81\uD83C\uDFFE\u200D♀️", + [":woman_tipping_hand_tone5:"] = "\uD83D\uDC81\uD83C\uDFFF\u200D♀️", + [":woman_tone1:"] = "\uD83D\uDC69\uD83C\uDFFB", + [":woman_tone2:"] = "\uD83D\uDC69\uD83C\uDFFC", + [":woman_tone3:"] = "\uD83D\uDC69\uD83C\uDFFD", + [":woman_tone4:"] = "\uD83D\uDC69\uD83C\uDFFE", + [":woman_tone5:"] = "\uD83D\uDC69\uD83C\uDFFF", + [":woman_vampire:"] = "\uD83E\uDDDB\u200D♀️", + [":woman_vampire::skin-tone-1:"] = "\uD83E\uDDDB\uD83C\uDFFB\u200D♀️", + [":woman_vampire::skin-tone-2:"] = "\uD83E\uDDDB\uD83C\uDFFC\u200D♀️", + [":woman_vampire::skin-tone-3:"] = "\uD83E\uDDDB\uD83C\uDFFD\u200D♀️", + [":woman_vampire::skin-tone-4:"] = "\uD83E\uDDDB\uD83C\uDFFE\u200D♀️", + [":woman_vampire::skin-tone-5:"] = "\uD83E\uDDDB\uD83C\uDFFF\u200D♀️", + [":woman_vampire_dark_skin_tone:"] = "\uD83E\uDDDB\uD83C\uDFFF\u200D♀️", + [":woman_vampire_light_skin_tone:"] = "\uD83E\uDDDB\uD83C\uDFFB\u200D♀️", + [":woman_vampire_medium_dark_skin_tone:"] = "\uD83E\uDDDB\uD83C\uDFFE\u200D♀️", + [":woman_vampire_medium_light_skin_tone:"] = "\uD83E\uDDDB\uD83C\uDFFC\u200D♀️", + [":woman_vampire_medium_skin_tone:"] = "\uD83E\uDDDB\uD83C\uDFFD\u200D♀️", + [":woman_vampire_tone1:"] = "\uD83E\uDDDB\uD83C\uDFFB\u200D♀️", + [":woman_vampire_tone2:"] = "\uD83E\uDDDB\uD83C\uDFFC\u200D♀️", + [":woman_vampire_tone3:"] = "\uD83E\uDDDB\uD83C\uDFFD\u200D♀️", + [":woman_vampire_tone4:"] = "\uD83E\uDDDB\uD83C\uDFFE\u200D♀️", + [":woman_vampire_tone5:"] = "\uD83E\uDDDB\uD83C\uDFFF\u200D♀️", + [":woman_walking:"] = "\uD83D\uDEB6\u200D♀️", + [":woman_walking::skin-tone-1:"] = "\uD83D\uDEB6\uD83C\uDFFB\u200D♀️", + [":woman_walking::skin-tone-2:"] = "\uD83D\uDEB6\uD83C\uDFFC\u200D♀️", + [":woman_walking::skin-tone-3:"] = "\uD83D\uDEB6\uD83C\uDFFD\u200D♀️", + [":woman_walking::skin-tone-4:"] = "\uD83D\uDEB6\uD83C\uDFFE\u200D♀️", + [":woman_walking::skin-tone-5:"] = "\uD83D\uDEB6\uD83C\uDFFF\u200D♀️", + [":woman_walking_dark_skin_tone:"] = "\uD83D\uDEB6\uD83C\uDFFF\u200D♀️", + [":woman_walking_light_skin_tone:"] = "\uD83D\uDEB6\uD83C\uDFFB\u200D♀️", + [":woman_walking_medium_dark_skin_tone:"] = "\uD83D\uDEB6\uD83C\uDFFE\u200D♀️", + [":woman_walking_medium_light_skin_tone:"] = "\uD83D\uDEB6\uD83C\uDFFC\u200D♀️", + [":woman_walking_medium_skin_tone:"] = "\uD83D\uDEB6\uD83C\uDFFD\u200D♀️", + [":woman_walking_tone1:"] = "\uD83D\uDEB6\uD83C\uDFFB\u200D♀️", + [":woman_walking_tone2:"] = "\uD83D\uDEB6\uD83C\uDFFC\u200D♀️", + [":woman_walking_tone3:"] = "\uD83D\uDEB6\uD83C\uDFFD\u200D♀️", + [":woman_walking_tone4:"] = "\uD83D\uDEB6\uD83C\uDFFE\u200D♀️", + [":woman_walking_tone5:"] = "\uD83D\uDEB6\uD83C\uDFFF\u200D♀️", + [":woman_wearing_turban:"] = "\uD83D\uDC73\u200D♀️", + [":woman_wearing_turban::skin-tone-1:"] = "\uD83D\uDC73\uD83C\uDFFB\u200D♀️", + [":woman_wearing_turban::skin-tone-2:"] = "\uD83D\uDC73\uD83C\uDFFC\u200D♀️", + [":woman_wearing_turban::skin-tone-3:"] = "\uD83D\uDC73\uD83C\uDFFD\u200D♀️", + [":woman_wearing_turban::skin-tone-4:"] = "\uD83D\uDC73\uD83C\uDFFE\u200D♀️", + [":woman_wearing_turban::skin-tone-5:"] = "\uD83D\uDC73\uD83C\uDFFF\u200D♀️", + [":woman_wearing_turban_dark_skin_tone:"] = "\uD83D\uDC73\uD83C\uDFFF\u200D♀️", + [":woman_wearing_turban_light_skin_tone:"] = "\uD83D\uDC73\uD83C\uDFFB\u200D♀️", + [":woman_wearing_turban_medium_dark_skin_tone:"] = "\uD83D\uDC73\uD83C\uDFFE\u200D♀️", + [":woman_wearing_turban_medium_light_skin_tone:"] = "\uD83D\uDC73\uD83C\uDFFC\u200D♀️", + [":woman_wearing_turban_medium_skin_tone:"] = "\uD83D\uDC73\uD83C\uDFFD\u200D♀️", + [":woman_wearing_turban_tone1:"] = "\uD83D\uDC73\uD83C\uDFFB\u200D♀️", + [":woman_wearing_turban_tone2:"] = "\uD83D\uDC73\uD83C\uDFFC\u200D♀️", + [":woman_wearing_turban_tone3:"] = "\uD83D\uDC73\uD83C\uDFFD\u200D♀️", + [":woman_wearing_turban_tone4:"] = "\uD83D\uDC73\uD83C\uDFFE\u200D♀️", + [":woman_wearing_turban_tone5:"] = "\uD83D\uDC73\uD83C\uDFFF\u200D♀️", + [":woman_white_haired:"] = "\uD83D\uDC69\u200D\uD83E\uDDB3", + [":woman_white_haired::skin-tone-1:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83E\uDDB3", + [":woman_white_haired::skin-tone-2:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83E\uDDB3", + [":woman_white_haired::skin-tone-3:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83E\uDDB3", + [":woman_white_haired::skin-tone-4:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83E\uDDB3", + [":woman_white_haired::skin-tone-5:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83E\uDDB3", + [":woman_white_haired_dark_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83E\uDDB3", + [":woman_white_haired_light_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83E\uDDB3", + [":woman_white_haired_medium_dark_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83E\uDDB3", + [":woman_white_haired_medium_light_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83E\uDDB3", + [":woman_white_haired_medium_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83E\uDDB3", + [":woman_white_haired_tone1:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83E\uDDB3", + [":woman_white_haired_tone2:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83E\uDDB3", + [":woman_white_haired_tone3:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83E\uDDB3", + [":woman_white_haired_tone4:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83E\uDDB3", + [":woman_white_haired_tone5:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83E\uDDB3", + [":woman_with_headscarf:"] = "\uD83E\uDDD5", + [":woman_with_headscarf::skin-tone-1:"] = "\uD83E\uDDD5\uD83C\uDFFB", + [":woman_with_headscarf::skin-tone-2:"] = "\uD83E\uDDD5\uD83C\uDFFC", + [":woman_with_headscarf::skin-tone-3:"] = "\uD83E\uDDD5\uD83C\uDFFD", + [":woman_with_headscarf::skin-tone-4:"] = "\uD83E\uDDD5\uD83C\uDFFE", + [":woman_with_headscarf::skin-tone-5:"] = "\uD83E\uDDD5\uD83C\uDFFF", + [":woman_with_headscarf_dark_skin_tone:"] = "\uD83E\uDDD5\uD83C\uDFFF", + [":woman_with_headscarf_light_skin_tone:"] = "\uD83E\uDDD5\uD83C\uDFFB", + [":woman_with_headscarf_medium_dark_skin_tone:"] = "\uD83E\uDDD5\uD83C\uDFFE", + [":woman_with_headscarf_medium_light_skin_tone:"] = "\uD83E\uDDD5\uD83C\uDFFC", + [":woman_with_headscarf_medium_skin_tone:"] = "\uD83E\uDDD5\uD83C\uDFFD", + [":woman_with_headscarf_tone1:"] = "\uD83E\uDDD5\uD83C\uDFFB", + [":woman_with_headscarf_tone2:"] = "\uD83E\uDDD5\uD83C\uDFFC", + [":woman_with_headscarf_tone3:"] = "\uD83E\uDDD5\uD83C\uDFFD", + [":woman_with_headscarf_tone4:"] = "\uD83E\uDDD5\uD83C\uDFFE", + [":woman_with_headscarf_tone5:"] = "\uD83E\uDDD5\uD83C\uDFFF", + [":woman_with_probing_cane:"] = "\uD83D\uDC69\u200D\uD83E\uDDAF", + [":woman_with_probing_cane::skin-tone-1:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83E\uDDAF", + [":woman_with_probing_cane::skin-tone-2:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83E\uDDAF", + [":woman_with_probing_cane::skin-tone-3:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83E\uDDAF", + [":woman_with_probing_cane::skin-tone-4:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83E\uDDAF", + [":woman_with_probing_cane::skin-tone-5:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83E\uDDAF", + [":woman_with_probing_cane_dark_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83E\uDDAF", + [":woman_with_probing_cane_light_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83E\uDDAF", + [":woman_with_probing_cane_medium_dark_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83E\uDDAF", + [":woman_with_probing_cane_medium_light_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83E\uDDAF", + [":woman_with_probing_cane_medium_skin_tone:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83E\uDDAF", + [":woman_with_probing_cane_tone1:"] = "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83E\uDDAF", + [":woman_with_probing_cane_tone2:"] = "\uD83D\uDC69\uD83C\uDFFC\u200D\uD83E\uDDAF", + [":woman_with_probing_cane_tone3:"] = "\uD83D\uDC69\uD83C\uDFFD\u200D\uD83E\uDDAF", + [":woman_with_probing_cane_tone4:"] = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83E\uDDAF", + [":woman_with_probing_cane_tone5:"] = "\uD83D\uDC69\uD83C\uDFFF\u200D\uD83E\uDDAF", + [":woman_zombie:"] = "\uD83E\uDDDF\u200D♀️", + [":womans_clothes:"] = "\uD83D\uDC5A", + [":womans_flat_shoe:"] = "\uD83E\uDD7F", + [":womans_hat:"] = "\uD83D\uDC52", + [":women_with_bunny_ears_partying:"] = "\uD83D\uDC6F\u200D♀️", + [":women_wrestling:"] = "\uD83E\uDD3C\u200D♀️", + [":womens:"] = "\uD83D\uDEBA", + [":woozy_face:"] = "\uD83E\uDD74", + [":world_map:"] = "\uD83D\uDDFA️", + [":worried:"] = "\uD83D\uDE1F", + [":worship_symbol:"] = "\uD83D\uDED0", + [":wrench:"] = "\uD83D\uDD27", + [":wrestlers:"] = "\uD83E\uDD3C", + [":wrestling:"] = "\uD83E\uDD3C", + [":writing_hand:"] = "✍️", + [":writing_hand::skin-tone-1:"] = "✍\uD83C\uDFFB", + [":writing_hand::skin-tone-2:"] = "✍\uD83C\uDFFC", + [":writing_hand::skin-tone-3:"] = "✍\uD83C\uDFFD", + [":writing_hand::skin-tone-4:"] = "✍\uD83C\uDFFE", + [":writing_hand::skin-tone-5:"] = "✍\uD83C\uDFFF", + [":writing_hand_tone1:"] = "✍\uD83C\uDFFB", + [":writing_hand_tone2:"] = "✍\uD83C\uDFFC", + [":writing_hand_tone3:"] = "✍\uD83C\uDFFD", + [":writing_hand_tone4:"] = "✍\uD83C\uDFFE", + [":writing_hand_tone5:"] = "✍\uD83C\uDFFF", + [":x:"] = "❌", + [":yarn:"] = "\uD83E\uDDF6", + [":yawning_face:"] = "\uD83E\uDD71", + [":yellow_circle:"] = "\uD83D\uDFE1", + [":yellow_heart:"] = "\uD83D\uDC9B", + [":yellow_square:"] = "\uD83D\uDFE8", + [":yen:"] = "\uD83D\uDCB4", + [":yin_yang:"] = "☯️", + [":yo_yo:"] = "\uD83E\uDE80", + [":yum:"] = "\uD83D\uDE0B", + [":z"] = "\uD83D\uDE12", + [":zany_face:"] = "\uD83E\uDD2A", + [":zap:"] = "⚡", + [":zebra:"] = "\uD83E\uDD93", + [":zero:"] = "0️⃣", + [":zipper_mouth:"] = "\uD83E\uDD10", + [":zipper_mouth_face:"] = "\uD83E\uDD10", + [":zombie:"] = "\uD83E\uDDDF", + [":zzz:"] = "\uD83D\uDCA4", + [":|"] = "\uD83D\uDE10", + [";("] = "\uD83D\uDE2D", + [";)"] = "\uD83D\uDE09", + [";-("] = "\uD83D\uDE2D", + [";-)"] = "\uD83D\uDE09", + [":("] = "\uD83D\uDE20", + [">:-("] = "\uD83D\uDE20", + [">=("] = "\uD83D\uDE20", + [">=-("] = "\uD83D\uDE20", + ["B-)"] = "\uD83D\uDE0E", + ["O:)"] = "\uD83D\uDE07", + ["O:-)"] = "\uD83D\uDE07", + ["O=)"] = "\uD83D\uDE07", + ["O=-)"] = "\uD83D\uDE07", + ["X-)"] = "\uD83D\uDE06", + ["]:("] = "\uD83D\uDC7F", + ["]:)"] = "\uD83D\uDE08", + ["]:-("] = "\uD83D\uDC7F", + ["]:-)"] = "\uD83D\uDE08", + ["]=("] = "\uD83D\uDC7F", + ["]=)"] = "\uD83D\uDE08", + ["]=-("] = "\uD83D\uDC7F", + ["]=-)"] = "\uD83D\uDE08", + ["o:)"] = "\uD83D\uDE07", + ["o:-)"] = "\uD83D\uDE07", + ["o=)"] = "\uD83D\uDE07", + ["o=-)"] = "\uD83D\uDE07", + ["x-)"] = "\uD83D\uDE06", + ["♡"] = "❤️" + }; + + private static IReadOnlyCollection _unicodes; + private static IReadOnlyCollection Unicodes + { + get + { + _unicodes ??= NamesAndUnicodes.Select(kvp => kvp.Value).ToImmutableHashSet(); + return _unicodes; + } + } + + private static IReadOnlyDictionary> _unicodesAndNames; + private static IReadOnlyDictionary> UnicodesAndNames + { + get + { + _unicodesAndNames ??= + NamesAndUnicodes + .GroupBy(kvp => kvp.Value) + .ToImmutableDictionary( + grouping => grouping.Key, + grouping => grouping.Select(kvp => kvp.Key) + .ToList() + .AsReadOnly() + ); + return _unicodesAndNames; + } + } + + public static implicit operator Emoji(string s) => Parse(s); } } diff --git a/src/Discord.Net.Core/Entities/Emotes/Emote.cs b/src/Discord.Net.Core/Entities/Emotes/Emote.cs index 6054b3f74..cd88f97cc 100644 --- a/src/Discord.Net.Core/Entities/Emotes/Emote.cs +++ b/src/Discord.Net.Core/Entities/Emotes/Emote.cs @@ -102,5 +102,7 @@ namespace Discord /// A string representing the raw presentation of the emote (e.g. <:thonkang:282745590985523200>). /// public override string ToString() => $"<{(Animated ? "a" : "")}:{Name}:{Id}>"; + + public static implicit operator Emote(string s) => Parse(s); } } diff --git a/src/Discord.Net.Core/Entities/Guilds/GuildFeature.cs b/src/Discord.Net.Core/Entities/Guilds/GuildFeature.cs new file mode 100644 index 000000000..e3c325227 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Guilds/GuildFeature.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord +{ + [Flags] + public enum GuildFeature + { + /// + /// The guild has no features. + /// + None = 0, + /// + /// The guild has access to set an animated guild icon. + /// + AnimatedIcon = 1 << 0, + /// + /// The guild has access to set a guild banner image. + /// + Banner = 1 << 1, + /// + /// The guild has access to use commerce features (i.e. create store channels). + /// + Commerce = 1 << 2, + /// + /// The guild can enable welcome screen, Membership Screening, stage channels and discovery, and receives community updates. + /// + Community = 1 << 3, + /// + /// The guild is able to be discovered in the directory. + /// + Discoverable = 1 << 4, + /// + /// The guild is able to be featured in the directory. + /// + Featureable = 1 << 5, + /// + /// The guild has access to set an invite splash background. + /// + InviteSplash = 1 << 6, + /// + /// The guild has enabled Membership Screening. + /// + MemberVerificationGateEnabled = 1 << 7, + /// + /// The guild has enabled monetization. + /// + MonetizationEnabled = 1 << 8, + /// + /// The guild has increased custom sticker slots. + /// + MoreStickers = 1 << 9, + /// + /// The guild has access to create news channels. + /// + News = 1 << 10, + /// + /// The guild is partnered. + /// + Partnered = 1 << 11, + /// + /// The guild can be previewed before joining via Membership Screening or the directory. + /// + PreviewEnabled = 1 << 12, + /// + /// The guild has access to create private threads. + /// + PrivateThreads = 1 << 13, + /// + /// The guild is able to set role icons. + /// + RoleIcons = 1 << 14, + /// + /// The guild has access to the seven day archive time for threads. + /// + SevenDayThreadArchive = 1 << 15, + /// + /// The guild has access to the three day archive time for threads. + /// + ThreeDayThreadArchive = 1 << 16, + /// + /// The guild has enabled ticketed events. + /// + TicketedEventsEnabled = 1 << 17, + /// + /// The guild has access to set a vanity URL. + /// + VanityUrl = 1 << 18, + /// + /// The guild is verified. + /// + Verified = 1 << 19, + /// + /// The guild has access to set 384kbps bitrate in voice (previously VIP voice servers). + /// + VIPRegions = 1 << 20, + /// + /// The guild has enabled the welcome screen. + /// + WelcomeScreenEnabled = 1 << 21, + } +} diff --git a/src/Discord.Net.Core/Entities/Guilds/GuildFeatures.cs b/src/Discord.Net.Core/Entities/Guilds/GuildFeatures.cs new file mode 100644 index 000000000..699e47cf3 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Guilds/GuildFeatures.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord +{ + public class GuildFeatures + { + /// + /// Gets the flags of recognized features for this guild. + /// + public GuildFeature Value { get; } + + /// + /// Gets a collection of experimental features for this guild. + /// + public IReadOnlyCollection Experimental { get; } + + + internal GuildFeatures(GuildFeature value, string[] experimental) + { + Value = value; + Experimental = experimental.ToImmutableArray(); + } + + public bool HasFeature(GuildFeature feature) + => Value.HasFlag(feature); + public bool HasFeature(string feature) + => Experimental.Contains(feature); + + internal void EnsureFeature(GuildFeature feature) + { + if (!HasFeature(feature)) + { + var vals = Enum.GetValues(typeof(GuildFeature)).Cast(); + + var missingValues = vals.Where(x => feature.HasFlag(x) && !Value.HasFlag(x)); + + throw new InvalidOperationException($"Missing required guild feature{(missingValues.Count() > 1 ? "s" : "")} {string.Join(", ", missingValues.Select(x => x.ToString()))} in order to execute this operation."); + } + } + } +} diff --git a/src/Discord.Net.Core/Entities/Guilds/GuildProperties.cs b/src/Discord.Net.Core/Entities/Guilds/GuildProperties.cs index 981e1198c..d50b2ac38 100644 --- a/src/Discord.Net.Core/Entities/Guilds/GuildProperties.cs +++ b/src/Discord.Net.Core/Entities/Guilds/GuildProperties.cs @@ -85,8 +85,9 @@ namespace Discord /// given that the has also been set. /// A value of will deny guild boost messages from being sent, and allow all /// other types of messages. - /// Refer to the extension methods and - /// to check if these system channel message types + /// Refer to the extension methods , + /// , , + /// and to check if these system channel message types /// are enabled, without the need to manipulate the logic of the flag. /// public Optional SystemChannelFlags { get; set; } @@ -108,5 +109,9 @@ namespace Discord /// the value of will be unused. /// public Optional PreferredCulture { get; set; } + /// + /// Gets or sets if the boost progress bar is enabled. + /// + public Optional IsBoostProgressBarEnabled { get; set; } } } diff --git a/src/Discord.Net.Core/Entities/Guilds/GuildScheduledEventPrivacyLevel.cs b/src/Discord.Net.Core/Entities/Guilds/GuildScheduledEventPrivacyLevel.cs new file mode 100644 index 000000000..87881104c --- /dev/null +++ b/src/Discord.Net.Core/Entities/Guilds/GuildScheduledEventPrivacyLevel.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord +{ + /// + /// Represents the privacy level of a guild scheduled event. + /// + public enum GuildScheduledEventPrivacyLevel + { + /// + /// The scheduled event is public and available in discovery. + /// + [Obsolete("This event type isn't supported yet! check back later.", true)] + Public = 1, + + /// + /// The scheduled event is only accessible to guild members. + /// + Private = 2, + } +} diff --git a/src/Discord.Net.Core/Entities/Guilds/GuildScheduledEventStatus.cs b/src/Discord.Net.Core/Entities/Guilds/GuildScheduledEventStatus.cs new file mode 100644 index 000000000..6e3aa1ab3 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Guilds/GuildScheduledEventStatus.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord +{ + /// + /// Represents the status of a guild event. + /// + public enum GuildScheduledEventStatus + { + /// + /// The event is scheduled for a set time. + /// + Scheduled = 1, + + /// + /// The event has started. + /// + Active = 2, + + /// + /// The event was completed. + /// + Completed = 3, + + /// + /// The event was canceled. + /// + Cancelled = 4, + } +} diff --git a/src/Discord.Net.Core/Entities/Guilds/GuildScheduledEventType.cs b/src/Discord.Net.Core/Entities/Guilds/GuildScheduledEventType.cs new file mode 100644 index 000000000..ad741eee1 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Guilds/GuildScheduledEventType.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord +{ + /// + /// Represents the type of a guild scheduled event. + /// + public enum GuildScheduledEventType + { + /// + /// The event doesn't have a set type. + /// + None = 0, + + /// + /// The event is set in a stage channel. + /// + Stage = 1, + + /// + /// The event is set in a voice channel. + /// + Voice = 2, + + /// + /// The event is set for somewhere externally from discord. + /// + External = 3, + } +} diff --git a/src/Discord.Net.Core/Entities/Guilds/GuildScheduledEventsProperties.cs b/src/Discord.Net.Core/Entities/Guilds/GuildScheduledEventsProperties.cs new file mode 100644 index 000000000..a3fd729e5 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Guilds/GuildScheduledEventsProperties.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord +{ + /// + /// Provides properties that are used to modify an with the specified changes. + /// + public class GuildScheduledEventsProperties + { + /// + /// Gets or sets the channel id of the event. + /// + public Optional ChannelId { get; set; } + + /// + /// Gets or sets the location of this event. + /// + public Optional Location { get; set; } + + /// + /// Gets or sets the name of the event. + /// + public Optional Name { get; set; } + + /// + /// Gets or sets the privacy level of the event. + /// + public Optional PrivacyLevel { get; set; } + + /// + /// Gets or sets the start time of the event. + /// + public Optional StartTime { get; set; } + /// + /// Gets or sets the end time of the event. + /// + public Optional EndTime { get; set; } + + /// + /// Gets or sets the description of the event. + /// + public Optional Description { get; set; } + + /// + /// Gets or sets the type of the event. + /// + public Optional Type { get; set; } + + /// + /// Gets or sets the status of the event. + /// + public Optional Status { get; set; } + } +} diff --git a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs index b8fd858df..ebf2ccd4a 100644 --- a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs +++ b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs @@ -2,6 +2,7 @@ using Discord.Audio; using System; using System.Collections.Generic; using System.Globalization; +using System.IO; using System.Threading.Tasks; namespace Discord @@ -199,12 +200,19 @@ namespace Discord /// IReadOnlyCollection Emotes { get; } /// - /// Gets a collection of all extra features added to this guild. + /// Gets a collection of all custom stickers for this guild. /// /// - /// A read-only collection of enabled features in this guild. + /// A read-only collection of all custom stickers for this guild. /// - IReadOnlyCollection Features { get; } + IReadOnlyCollection Stickers { get; } + /// + /// Gets the features for this guild. + /// + /// + /// A flags enum containing all the features for the guild. + /// + GuildFeatures Features { get; } /// /// Gets a collection of all roles in this guild. /// @@ -317,12 +325,27 @@ namespace Discord string PreferredLocale { get; } /// + /// Gets the NSFW level of this guild. + /// + /// + /// The NSFW level of this guild. + /// + NsfwLevel NsfwLevel { get; } + + /// /// Gets the preferred culture of this guild. /// /// /// The preferred culture information of this guild. /// CultureInfo PreferredCulture { get; } + /// + /// Gets whether the guild has the boost progress bar enabled. + /// + /// + /// if the boost progress bar is enabled; otherwise . + /// + bool IsBoostProgressBarEnabled { get; } /// /// Modifies this guild. @@ -522,6 +545,27 @@ namespace Discord /// Task GetVoiceChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); /// + /// Gets a stage channel in this guild. + /// + /// The snowflake identifier for the stage channel. + /// The that determines whether the object should be fetched from cache. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains the stage channel associated + /// with the specified ; if none is found. + /// + Task GetStageChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + /// + /// Gets a collection of all stage channels in this guild. + /// + /// The that determines whether the object should be fetched from cache. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection of + /// stage channels found within this guild. + /// + Task> GetStageChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + /// /// Gets the AFK voice channel in this guild. /// /// The that determines whether the object should be fetched from cache. @@ -572,15 +616,35 @@ namespace Discord /// Task GetRulesChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); /// - /// Gets the text channel channel where admins and moderators of Community guilds receive notices from Discord. + /// Gets the text channel where admins and moderators of Community guilds receive notices from Discord. /// /// The that determines whether the object should be fetched from cache. /// The options to be used when sending the request. /// - /// A task that represents the asynchronous get operation. The task result contains the text channel channel where + /// A task that represents the asynchronous get operation. The task result contains the text channel where /// admins and moderators of Community guilds receive notices from Discord; if none is set. /// Task GetPublicUpdatesChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + /// + /// Gets a thread channel within this guild. + /// + /// The id of the thread channel. + /// The that determines whether the object should be fetched from cache. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains the thread channel. + /// + Task GetThreadChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + /// + /// Gets a collection of all thread channels in this guild. + /// + /// The that determines whether the object should be fetched from cache. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection of + /// thread channels found within this guild. + /// + Task> GetThreadChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); /// /// Creates a new text channel in this guild. @@ -610,6 +674,17 @@ namespace Discord /// Task CreateVoiceChannelAsync(string name, Action func = null, RequestOptions options = null); /// + /// Creates a new stage channel in this guild. + /// + /// The new name for the stage channel. + /// The delegate containing the properties to be applied to the channel upon its creation. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous creation operation. The task result contains the newly created + /// stage channel. + /// + Task CreateStageChannelAsync(string name, Action func = null, RequestOptions options = null); + /// /// Creates a new channel category in this guild. /// /// The new name for the category. @@ -703,6 +778,12 @@ namespace Discord /// A guild user associated with the specified ; if the user is already in the guild. Task AddGuildUserAsync(ulong userId, string accessToken, Action func = null, RequestOptions options = null); /// + /// Disconnects the user from its current voice channel. + /// + /// The user to disconnect. + /// A task that represents the asynchronous operation for disconnecting a user. + Task DisconnectAsync(IGuildUser user); + /// /// Gets a collection of all users in this guild. /// /// @@ -760,7 +841,7 @@ namespace Discord /// Downloads all users for this guild if the current list is incomplete. /// /// - /// This method downloads all users found within this guild throught the Gateway and caches them. + /// This method downloads all users found within this guild through the Gateway and caches them. /// /// /// A task that represents the asynchronous download operation. @@ -883,6 +964,15 @@ namespace Discord /// emote. /// Task ModifyEmoteAsync(GuildEmote emote, Action func, RequestOptions options = null); + + /// + /// Moves the user to the voice channel. + /// + /// The user to move. + /// the channel where the user gets moved to. + /// A task that represents the asynchronous operation for moving a user. + Task MoveAsync(IGuildUser user, IVoiceChannel targetChannel); + /// /// Deletes an existing from this guild. /// @@ -892,5 +982,174 @@ namespace Discord /// A task that represents the asynchronous removal operation. /// Task DeleteEmoteAsync(GuildEmote emote, RequestOptions options = null); + + /// + /// Creates a new sticker in this guild. + /// + /// The name of the sticker. + /// The description of the sticker. + /// The tags of the sticker. + /// The image of the new emote. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous creation operation. The task result contains the created sticker. + /// + Task CreateStickerAsync(string name, string description, IEnumerable tags, Image image, RequestOptions options = null); + + /// + /// Creates a new sticker in this guild. + /// + /// The name of the sticker. + /// The description of the sticker. + /// The tags of the sticker. + /// The path of the file to upload. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous creation operation. The task result contains the created sticker. + /// + Task CreateStickerAsync(string name, string description, IEnumerable tags, string path, RequestOptions options = null); + + /// + /// Creates a new sticker in this guild. + /// + /// The name of the sticker. + /// The description of the sticker. + /// The tags of the sticker. + /// The stream containing the file data. + /// The name of the file with the extension, ex: image.png. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous creation operation. The task result contains the created sticker. + /// + Task CreateStickerAsync(string name, string description, IEnumerable tags, Stream stream, string filename, RequestOptions options = null); + + /// + /// Gets a specific sticker within this guild. + /// + /// The id of the sticker to get. + /// The that determines whether the object should be fetched from cache. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains the sticker found with the + /// specified ; if none is found. + /// + Task GetStickerAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + + /// + /// Gets a collection of all stickers within this guild. + /// + /// The that determines whether the object should be fetched from cache. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection + /// of stickers found within the guild. + /// + Task> GetStickersAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + + /// + /// Deletes a sticker within this guild. + /// + /// The sticker to delete. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous removal operation. + /// + Task DeleteStickerAsync(ICustomSticker sticker, RequestOptions options = null); + + /// + /// Gets a event within this guild. + /// + /// The id of the event. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. + /// + Task GetEventAsync(ulong id, RequestOptions options = null); + + /// + /// Gets a collection of events within this guild. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. + /// + Task> GetEventsAsync(RequestOptions options = null); + + /// + /// Creates an event within this guild. + /// + /// The name of the event. + /// The privacy level of the event. + /// The start time of the event. + /// The type of the event. + /// The description of the event. + /// The end time of the event. + /// + /// The channel id of the event. + /// + /// The event must have a type of or + /// in order to use this property. + /// + /// + /// A collection of speakers for the event. + /// The location of the event; links are supported + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous create operation. + /// + Task CreateEventAsync( + string name, + DateTimeOffset startTime, + GuildScheduledEventType type, + GuildScheduledEventPrivacyLevel privacyLevel = GuildScheduledEventPrivacyLevel.Private, + string description = null, + DateTimeOffset? endTime = null, + ulong? channelId = null, + string location = null, + RequestOptions options = null); + + /// + /// Gets this guilds application commands. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection + /// of application commands found within the guild. + /// + Task> GetApplicationCommandsAsync(RequestOptions options = null); + + /// + /// Gets an application command within this guild with the specified id. + /// + /// The id of the application command to get. + /// The that determines whether the object should be fetched from cache. + /// The options to be used when sending the request. + /// + /// A ValueTask that represents the asynchronous get operation. The task result contains a + /// if found, otherwise . + /// + Task GetApplicationCommandAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, + RequestOptions options = null); + + /// + /// Creates an application command within this guild. + /// + /// The properties to use when creating the command. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous creation operation. The task result contains the command that was created. + /// + Task CreateApplicationCommandAsync(ApplicationCommandProperties properties, RequestOptions options = null); + + /// + /// Overwrites the application commands within this guild. + /// + /// A collection of properties to use when creating the commands. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous creation operation. The task result contains a collection of commands that was created. + /// + Task> BulkOverwriteApplicationCommandsAsync(ApplicationCommandProperties[] properties, + RequestOptions options = null); } } diff --git a/src/Discord.Net.Core/Entities/Guilds/IGuildScheduledEvent.cs b/src/Discord.Net.Core/Entities/Guilds/IGuildScheduledEvent.cs new file mode 100644 index 000000000..e50f4cc2b --- /dev/null +++ b/src/Discord.Net.Core/Entities/Guilds/IGuildScheduledEvent.cs @@ -0,0 +1,170 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord +{ + /// + /// Represents a generic guild scheduled event. + /// + public interface IGuildScheduledEvent : IEntity + { + /// + /// Gets the guild this event is scheduled in. + /// + IGuild Guild { get; } + + /// + /// Gets the optional channel id where this event will be hosted. + /// + ulong? ChannelId { get; } + + /// + /// Gets the user who created the event. + /// + IUser Creator { get; } + + /// + /// Gets the name of the event. + /// + string Name { get; } + + /// + /// Gets the description of the event. + /// + /// + /// This field is when the event doesn't have a discription. + /// + string Description { get; } + + /// + /// Gets the start time of the event. + /// + DateTimeOffset StartTime { get; } + + /// + /// Gets the optional end time of the event. + /// + DateTimeOffset? EndTime { get; } + + /// + /// Gets the privacy level of the event. + /// + GuildScheduledEventPrivacyLevel PrivacyLevel { get; } + + /// + /// Gets the status of the event. + /// + GuildScheduledEventStatus Status { get; } + + /// + /// Gets the type of the event. + /// + GuildScheduledEventType Type { get; } + + /// + /// Gets the optional entity id of the event. The "entity" of the event + /// can be a stage instance event as is seperate from . + /// + ulong? EntityId { get; } + + /// + /// Gets the location of the event if the is external. + /// + string Location { get; } + + /// + /// Gets the user count of the event. + /// + int? UserCount { get; } + + /// + /// Starts the event. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous start operation. + /// + Task StartAsync(RequestOptions options = null); + /// + /// Ends or canceles the event. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous end operation. + /// + Task EndAsync(RequestOptions options = null); + + /// + /// Modifies the guild event. + /// + /// The delegate containing the properties to modify the event with. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous modification operation. + /// + Task ModifyAsync(Action func, RequestOptions options = null); + + /// + /// Deletes the current event. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous delete operation. + /// + Task DeleteAsync(RequestOptions options = null); + + /// + /// Gets a collection of N users interested in the event. + /// + /// + /// + /// The returned collection is an asynchronous enumerable object; one must call + /// to access the individual messages as a + /// collection. + /// + /// This method will attempt to fetch all users that are interested in the event. + /// The library will attempt to split up the requests according to and . + /// In other words, if there are 300 users, and the constant + /// is 100, the request will be split into 3 individual requests; thus returning 3 individual asynchronous + /// responses, hence the need of flattening. + /// + /// The options to be used when sending the request. + /// + /// Paged collection of users. + /// + IAsyncEnumerable> GetUsersAsync(RequestOptions options = null); + + /// + /// Gets a collection of N users interested in the event. + /// + /// + /// + /// The returned collection is an asynchronous enumerable object; one must call + /// to access the individual users as a + /// collection. + /// + /// + /// Do not fetch too many users at once! This may cause unwanted preemptive rate limit or even actual + /// rate limit, causing your bot to freeze! + /// + /// This method will attempt to fetch the number of users specified under around + /// the user depending on the . The library will + /// attempt to split up the requests according to your and + /// . In other words, should the user request 500 users, + /// and the constant is 100, the request will + /// be split into 5 individual requests; thus returning 5 individual asynchronous responses, hence the need + /// of flattening. + /// + /// The ID of the starting user to get the users from. + /// The direction of the users to be gotten from. + /// The numbers of users to be gotten from. + /// The options to be used when sending the request. + /// + /// Paged collection of users. + /// + IAsyncEnumerable> GetUsersAsync(ulong fromUserId, Direction dir, int limit = DiscordConfig.MaxGuildEventUsersPerBatch, RequestOptions options = null); + } +} diff --git a/src/Discord.Net.Core/Entities/Guilds/NsfwLevel.cs b/src/Discord.Net.Core/Entities/Guilds/NsfwLevel.cs new file mode 100644 index 000000000..e3ac345d9 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Guilds/NsfwLevel.cs @@ -0,0 +1,22 @@ +namespace Discord +{ + public enum NsfwLevel + { + /// + /// Default or unset. + /// + Default = 0, + /// + /// Guild has extremely suggestive or mature content that would only be suitable for users 18 or over. + /// + Explicit = 1, + /// + /// Guild has no content that could be deemed NSFW; in other words, SFW. + /// + Safe = 2, + /// + /// Guild has mildly NSFW content that may not be suitable for users under 18. + /// + AgeRestricted = 3 + } +} \ No newline at end of file diff --git a/src/Discord.Net.Core/Entities/Guilds/SystemChannelMessageDeny.cs b/src/Discord.Net.Core/Entities/Guilds/SystemChannelMessageDeny.cs index 3f69693d0..06de7b812 100644 --- a/src/Discord.Net.Core/Entities/Guilds/SystemChannelMessageDeny.cs +++ b/src/Discord.Net.Core/Entities/Guilds/SystemChannelMessageDeny.cs @@ -17,6 +17,14 @@ namespace Discord /// /// Deny the messages that are sent when a user boosts the guild. /// - GuildBoost = 0b10 + GuildBoost = 0b10, + /// + /// Deny the messages that are related to guild setup. + /// + GuildSetupTip = 0b100, + /// + /// Deny the reply with sticker button on welcome messages. + /// + WelcomeMessageReply = 0b1000 } } diff --git a/src/Discord.Net.Core/Entities/IApplication.cs b/src/Discord.Net.Core/Entities/IApplication.cs index 2174baff9..9f9881340 100644 --- a/src/Discord.Net.Core/Entities/IApplication.cs +++ b/src/Discord.Net.Core/Entities/IApplication.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; + namespace Discord { /// @@ -16,8 +18,16 @@ namespace Discord /// /// Gets the RPC origins of the application. /// - string[] RPCOrigins { get; } - ulong Flags { get; } + IReadOnlyCollection RPCOrigins { get; } + ApplicationFlags Flags { get; } + /// + /// Gets a collection of install parameters for this application. + /// + ApplicationInstallParams InstallParams { get; } + /// + /// Gets a collection of tags related to the application. + /// + IReadOnlyCollection Tags { get; } /// /// Gets the icon URL of the application. /// diff --git a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOption.cs b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOption.cs new file mode 100644 index 000000000..9a69d9d18 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOption.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace Discord +{ + /// + /// Represents a for making slash commands. + /// + public class ApplicationCommandOptionProperties + { + private string _name; + private string _description; + + /// + /// Gets or sets the name of this option. + /// + public string Name + { + get => _name; + set + { + if (value == null) + throw new ArgumentNullException(nameof(value), $"{nameof(Name)} cannot be null."); + + if (value.Length > 32) + throw new ArgumentOutOfRangeException(nameof(value), "Name length must be less than or equal to 32."); + + if (!Regex.IsMatch(value, @"^[\w-]{1,32}$")) + throw new FormatException($"{nameof(value)} must match the regex ^[\\w-]{{1,32}}$"); + + _name = value; + } + } + + /// + /// Gets or sets the description of this option. + /// + public string Description + { + get => _description; + set => _description = value?.Length switch + { + > 100 => throw new ArgumentOutOfRangeException(nameof(value), "Description length must be less than or equal to 100."), + 0 => throw new ArgumentOutOfRangeException(nameof(value), "Description length must be at least 1."), + _ => value + }; + } + + /// + /// Gets or sets the type of this option. + /// + public ApplicationCommandOptionType Type { get; set; } + + /// + /// Gets or sets whether or not this options is the first required option for the user to complete. only one option can be default. + /// + public bool? IsDefault { get; set; } + + /// + /// Gets or sets if the option is required. + /// + public bool? IsRequired { get; set; } + + /// + /// Gets or sets whether or not this option supports autocomplete. + /// + public bool IsAutocomplete { get; set; } + + /// + /// Gets or sets the smallest number value the user can input. + /// + public double? MinValue { get; set; } + + /// + /// Gets or sets the largest number value the user can input. + /// + public double? MaxValue { get; set; } + + /// + /// Gets or sets the choices for string and int types for the user to pick from. + /// + public List Choices { get; set; } + + /// + /// Gets or sets if this option is a subcommand or subcommand group type, these nested options will be the parameters. + /// + public List Options { get; set; } + + /// + /// Gets or sets the allowed channel types for this option. + /// + public List ChannelTypes { get; set; } + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionChoice.cs b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionChoice.cs new file mode 100644 index 000000000..6a908b075 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionChoice.cs @@ -0,0 +1,44 @@ +using System; + +namespace Discord +{ + /// + /// Represents a choice for a . This class is used when making new commands. + /// + public class ApplicationCommandOptionChoiceProperties + { + private string _name; + private object _value; + + /// + /// Gets or sets the name of this choice. + /// + public string Name + { + get => _name; + set => _name = value?.Length switch + { + > 100 => throw new ArgumentOutOfRangeException(nameof(value), "Name length must be less than or equal to 100."), + 0 => throw new ArgumentOutOfRangeException(nameof(value), "Name length must at least 1."), + _ => value + }; + } + + /// + /// Gets the value of this choice. + /// + /// Discord only accepts int, double/floats, and string as the input. + /// + /// + public object Value + { + get => _value; + set + { + if (value != null && value is not string && !value.IsNumericType()) + throw new ArgumentException("The value of a choice must be a string or a numeric type!"); + _value = value; + } + } + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionType.cs b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionType.cs new file mode 100644 index 000000000..0f919f1f6 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionType.cs @@ -0,0 +1,58 @@ +namespace Discord +{ + /// + /// The option type of the Slash command parameter, See the discord docs. + /// + public enum ApplicationCommandOptionType : byte + { + /// + /// A sub command. + /// + SubCommand = 1, + + /// + /// A group of sub commands. + /// + SubCommandGroup = 2, + + /// + /// A of text. + /// + String = 3, + + /// + /// An . + /// + Integer = 4, + + /// + /// A . + /// + Boolean = 5, + + /// + /// A . + /// + User = 6, + + /// + /// A . + /// + Channel = 7, + + /// + /// A . + /// + Role = 8, + + /// + /// A or . + /// + Mentionable = 9, + + /// + /// A . + /// + Number = 10 + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs new file mode 100644 index 000000000..501a0e905 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs @@ -0,0 +1,22 @@ +namespace Discord +{ + /// + /// Represents the base class to create/modify application commands. + /// + public abstract class ApplicationCommandProperties + { + internal abstract ApplicationCommandType Type { get; } + + /// + /// Gets or sets the name of this command. + /// + public Optional Name { get; set; } + + /// + /// Gets or sets whether the command is enabled by default when the app is added to a guild. Default is + /// + public Optional IsDefaultPermission { get; set; } + + internal ApplicationCommandProperties() { } + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandTypes.cs b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandTypes.cs new file mode 100644 index 000000000..8cd31a420 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandTypes.cs @@ -0,0 +1,23 @@ +namespace Discord +{ + /// + /// Represents the types of application commands. + /// + public enum ApplicationCommandType : byte + { + /// + /// A Slash command type + /// + Slash = 1, + + /// + /// A Context Menu User command type + /// + User = 2, + + /// + /// A Context Menu Message command type + /// + Message = 3 + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/AutocompleteOption.cs b/src/Discord.Net.Core/Entities/Interactions/AutocompleteOption.cs new file mode 100644 index 000000000..eb22a9d27 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/AutocompleteOption.cs @@ -0,0 +1,36 @@ +namespace Discord +{ + /// + /// Represents an autocomplete option. + /// + public class AutocompleteOption + { + /// + /// Gets the type of this option. + /// + public ApplicationCommandOptionType Type { get; } + + /// + /// Gets the name of the option. + /// + public string Name { get; } + + /// + /// Gets the value of the option. + /// + public object Value { get; } + + /// + /// Gets whether or not this option is focused by the executing user. + /// + public bool Focused { get; } + + internal AutocompleteOption(ApplicationCommandOptionType type, string name, object value, bool focused) + { + Type = type; + Name = name; + Value = value; + Focused = focused; + } + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/AutocompleteResult.cs b/src/Discord.Net.Core/Entities/Interactions/AutocompleteResult.cs new file mode 100644 index 000000000..0603a5a50 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/AutocompleteResult.cs @@ -0,0 +1,73 @@ +using System; + +namespace Discord +{ + /// + /// Represents a result to an autocomplete interaction. + /// + public class AutocompleteResult + { + private object _value; + private string _name; + + /// + /// Gets or sets the name of the result. + /// + /// + /// Name cannot be null and has to be between 1-100 characters in length. + /// + /// + /// + public string Name + { + get => _name; + set + { + if (value == null) + throw new ArgumentNullException(nameof(value), $"{nameof(Name)} cannot be null."); + _name = value.Length switch + { + > 100 => throw new ArgumentOutOfRangeException(nameof(value), "Name length must be less than or equal to 100."), + 0 => throw new ArgumentOutOfRangeException(nameof(value), "Name length must be at least 1."), + _ => value + }; + } + } + + /// + /// Gets or sets the value of the result. + /// + /// + /// Only , , and are allowed for a value. + /// + /// + /// + public object Value + { + get => _value; + set + { + if (value is not string && !value.IsNumericType()) + throw new ArgumentException($"{nameof(value)} must be a numeric type or a string!"); + + _value = value; + } + } + + /// + /// Creates a new . + /// + public AutocompleteResult() { } + + /// + /// Creates a new with the passed in and . + /// + /// + /// + public AutocompleteResult(string name, object value) + { + Name = name; + Value = value; + } + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/ContextMenus/IMessageCommandInteraction.cs b/src/Discord.Net.Core/Entities/Interactions/ContextMenus/IMessageCommandInteraction.cs new file mode 100644 index 000000000..b1b331e8b --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/ContextMenus/IMessageCommandInteraction.cs @@ -0,0 +1,13 @@ +namespace Discord +{ + /// + /// Represents a Message Command interaction. + /// + public interface IMessageCommandInteraction : IDiscordInteraction + { + /// + /// Gets the data associated with this interaction. + /// + new IMessageCommandInteractionData Data { get; } + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/ContextMenus/IMessageCommandInteractionData.cs b/src/Discord.Net.Core/Entities/Interactions/ContextMenus/IMessageCommandInteractionData.cs new file mode 100644 index 000000000..311eef2d6 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/ContextMenus/IMessageCommandInteractionData.cs @@ -0,0 +1,13 @@ +namespace Discord +{ + /// + /// Represents the data tied with the interaction. + /// + public interface IMessageCommandInteractionData : IApplicationCommandInteractionData + { + /// + /// Gets the message associated with this message command. + /// + IMessage Message { get; } + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/ContextMenus/IUserCommandInteraction.cs b/src/Discord.Net.Core/Entities/Interactions/ContextMenus/IUserCommandInteraction.cs new file mode 100644 index 000000000..f7cfd67f0 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/ContextMenus/IUserCommandInteraction.cs @@ -0,0 +1,13 @@ +namespace Discord +{ + /// + /// Represents a User Command interaction. + /// + public interface IUserCommandInteraction : IDiscordInteraction + { + /// + /// Gets the data associated with this interaction. + /// + new IUserCommandInteractionData Data { get; } + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/ContextMenus/IUserCommandInteractionData.cs b/src/Discord.Net.Core/Entities/Interactions/ContextMenus/IUserCommandInteractionData.cs new file mode 100644 index 000000000..36e482ec9 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/ContextMenus/IUserCommandInteractionData.cs @@ -0,0 +1,13 @@ +namespace Discord +{ + /// + /// Represents the data tied with the interaction. + /// + public interface IUserCommandInteractionData : IApplicationCommandInteractionData + { + /// + /// Gets the user who this command targets. + /// + IUser User { get; } + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/ContextMenus/MessageCommandBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/ContextMenus/MessageCommandBuilder.cs new file mode 100644 index 000000000..c7a7cf741 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/ContextMenus/MessageCommandBuilder.cs @@ -0,0 +1,77 @@ +namespace Discord +{ + /// + /// A class used to build Message commands. + /// + public class MessageCommandBuilder + { + /// + /// Returns the maximum length a commands name allowed by Discord + /// + public const int MaxNameLength = 32; + + /// + /// Gets or sets the name of this Message command. + /// + public string Name + { + get => _name; + set + { + Preconditions.NotNullOrEmpty(value, nameof(Name)); + Preconditions.AtLeast(value.Length, 1, nameof(Name)); + Preconditions.AtMost(value.Length, MaxNameLength, nameof(Name)); + + _name = value; + } + } + + /// + /// Gets or sets whether the command is enabled by default when the app is added to a guild + /// + public bool IsDefaultPermission { get; set; } = true; + + private string _name; + + /// + /// Build the current builder into a class. + /// + /// + /// A that can be used to create message commands. + /// + public MessageCommandProperties Build() + { + var props = new MessageCommandProperties + { + Name = Name, + IsDefaultPermission = IsDefaultPermission + }; + + return props; + } + + /// + /// Sets the field name. + /// + /// The value to set the field name to. + /// + /// The current builder. + /// + public MessageCommandBuilder WithName(string name) + { + Name = name; + return this; + } + + /// + /// Sets the default permission of the current command. + /// + /// The default permission value to set. + /// The current builder. + public MessageCommandBuilder WithDefaultPermission(bool isDefaultPermission) + { + IsDefaultPermission = isDefaultPermission; + return this; + } + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/ContextMenus/MessageCommandProperties.cs b/src/Discord.Net.Core/Entities/Interactions/ContextMenus/MessageCommandProperties.cs new file mode 100644 index 000000000..356ed23d6 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/ContextMenus/MessageCommandProperties.cs @@ -0,0 +1,10 @@ +namespace Discord +{ + /// + /// A class used to create message commands. + /// + public class MessageCommandProperties : ApplicationCommandProperties + { + internal override ApplicationCommandType Type => ApplicationCommandType.Message; + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/ContextMenus/UserCommandBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/ContextMenus/UserCommandBuilder.cs new file mode 100644 index 000000000..bd1078be3 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/ContextMenus/UserCommandBuilder.cs @@ -0,0 +1,75 @@ +namespace Discord +{ + /// + /// A class used to build user commands. + /// + public class UserCommandBuilder + { + /// + /// Returns the maximum length a commands name allowed by Discord. + /// + public const int MaxNameLength = 32; + + /// + /// Gets or sets the name of this User command. + /// + public string Name + { + get => _name; + set + { + Preconditions.NotNullOrEmpty(value, nameof(Name)); + Preconditions.AtLeast(value.Length, 1, nameof(Name)); + Preconditions.AtMost(value.Length, MaxNameLength, nameof(Name)); + + _name = value; + } + } + + /// + /// Gets or sets whether the command is enabled by default when the app is added to a guild. + /// + public bool IsDefaultPermission { get; set; } = true; + + private string _name; + + /// + /// Build the current builder into a class. + /// + /// A that can be used to create user commands. + public UserCommandProperties Build() + { + var props = new UserCommandProperties + { + Name = Name, + IsDefaultPermission = IsDefaultPermission + }; + + return props; + } + + /// + /// Sets the field name. + /// + /// The value to set the field name to. + /// + /// The current builder. + /// + public UserCommandBuilder WithName(string name) + { + Name = name; + return this; + } + + /// + /// Sets the default permission of the current command. + /// + /// The default permission value to set. + /// The current builder. + public UserCommandBuilder WithDefaultPermission(bool isDefaultPermission) + { + IsDefaultPermission = isDefaultPermission; + return this; + } + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/ContextMenus/UserCommandProperties.cs b/src/Discord.Net.Core/Entities/Interactions/ContextMenus/UserCommandProperties.cs new file mode 100644 index 000000000..c42e916d9 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/ContextMenus/UserCommandProperties.cs @@ -0,0 +1,10 @@ +namespace Discord +{ + /// + /// A class used to create User commands. + /// + public class UserCommandProperties : ApplicationCommandProperties + { + internal override ApplicationCommandType Type => ApplicationCommandType.User; + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs new file mode 100644 index 000000000..72045a52a --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Discord +{ + /// + /// The base command model that belongs to an application. + /// + public interface IApplicationCommand : ISnowflakeEntity, IDeletable + { + /// + /// Gets the unique id of the parent application. + /// + ulong ApplicationId { get; } + + /// + /// Gets the type of the command. + /// + ApplicationCommandType Type { get; } + + /// + /// Gets the name of the command. + /// + string Name { get; } + + /// + /// Gets the description of the command. + /// + string Description { get; } + + /// + /// Gets whether the command is enabled by default when the app is added to a guild. + /// + bool IsDefaultPermission { get; } + + /// + /// Gets a collection of options for this application command. + /// + IReadOnlyCollection Options { get; } + + /// + /// Modifies the current application command. + /// + /// The new properties to use when modifying the command. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous modification operation. + /// + Task ModifyAsync(Action func, RequestOptions options = null); + + /// + /// Modifies the current application command. + /// + /// The new properties to use when modifying the command. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous modification operation. + /// + /// Thrown when you pass in an invalid type. + Task ModifyAsync(Action func, RequestOptions options = null) + where TArg : ApplicationCommandProperties; + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionData.cs b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionData.cs new file mode 100644 index 000000000..428f20fb6 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionData.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; + +namespace Discord +{ + /// + /// Represents data of an Interaction Command, see . + /// + public interface IApplicationCommandInteractionData : IDiscordInteractionData + { + /// + /// Gets the snowflake id of this command. + /// + ulong Id { get; } + + /// + /// Gets the name of this command. + /// + string Name { get; } + + /// + /// Gets the options that the user has provided. + /// + IReadOnlyCollection Options { get; } + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionDataOption.cs b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionDataOption.cs new file mode 100644 index 000000000..072d2b32b --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionDataOption.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; + +namespace Discord +{ + /// + /// Represents a option group for a command. + /// + public interface IApplicationCommandInteractionDataOption + { + /// + /// Gets the name of the parameter. + /// + string Name { get; } + + /// + /// Gets the value of the pair. + /// + /// This objects type can be any one of the option types in . + /// + /// + object Value { get; } + + /// + /// Gets the type of this data's option. + /// + ApplicationCommandOptionType Type { get; } + + /// + /// Gets the nested options of this option. + /// + IReadOnlyCollection Options { get; } + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOption.cs b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOption.cs new file mode 100644 index 000000000..440c4bd6b --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOption.cs @@ -0,0 +1,60 @@ +using System.Collections.Generic; + +namespace Discord +{ + /// + /// Options for the . + /// + public interface IApplicationCommandOption + { + /// + /// Gets the type of this . + /// + ApplicationCommandOptionType Type { get; } + + /// + /// Gets the name of this command option. + /// + string Name { get; } + + /// + /// Gets the description of this command option. + /// + string Description { get; } + + /// + /// Gets whether or not this is the first required option for the user to complete. + /// + bool? IsDefault { get; } + + /// + /// Gets whether or not the parameter is required or optional. + /// + bool? IsRequired { get; } + + /// + /// Gets the smallest number value the user can input. + /// + double? MinValue { get; } + + /// + /// Gets the largest number value the user can input. + /// + double? MaxValue { get; } + + /// + /// Gets the choices for string and int types for the user to pick from. + /// + IReadOnlyCollection Choices { get; } + + /// + /// Gets the sub-options for this command option. + /// + IReadOnlyCollection Options { get; } + + /// + /// Gets the allowed channel types for this option. + /// + IReadOnlyCollection ChannelTypes { get; } + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOptionChoice.cs b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOptionChoice.cs new file mode 100644 index 000000000..631706c6f --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOptionChoice.cs @@ -0,0 +1,18 @@ +namespace Discord +{ + /// + /// Specifies choices for command group. + /// + public interface IApplicationCommandOptionChoice + { + /// + /// Gets the choice name. + /// + string Name { get; } + + /// + /// Gets the value of the choice. + /// + object Value { get; } + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs b/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs new file mode 100644 index 000000000..d9e250118 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs @@ -0,0 +1,90 @@ +using System; +using System.Threading.Tasks; + +namespace Discord +{ + /// + /// Represents a discord interaction. + /// + public interface IDiscordInteraction : ISnowflakeEntity + { + /// + /// Gets the id of the interaction. + /// + new ulong Id { get; } + + /// + /// Gets the type of this . + /// + InteractionType Type { get; } + + /// + /// Gets the data sent within this interaction. + /// + IDiscordInteractionData Data { get; } + + /// + /// Gets the continuation token for responding to the interaction. + /// + string Token { get; } + + /// + /// Gets the version of the interaction, always 1. + /// + int Version { get; } + + /// + /// Responds to an Interaction with type . + /// + /// The text of the message to be sent. + /// A array of embeds to send with this response. Max 10. + /// if the message should be read out by a text-to-speech reader, otherwise . + /// if the response should be hidden to everyone besides the invoker of the command, otherwise . + /// The allowed mentions for this response. + /// The request options for this response. + /// A to be sent with this response. + /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. + Task RespondAsync(string text = null, Embed[] embeds = null, bool isTTS = false, + bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null); + + /// + /// Sends a followup message for this interaction. + /// + /// The text of the message to be sent. + /// A array of embeds to send with this response. Max 10. + /// if the message should be read out by a text-to-speech reader, otherwise . + /// if the response should be hidden to everyone besides the invoker of the command, otherwise . + /// The allowed mentions for this response. + /// The request options for this response. + /// A to be sent with this response. + /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. + /// + /// The sent message. + /// + Task FollowupAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, + AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null); + + /// + /// Gets the original response for this interaction. + /// + /// The request options for this request. + /// A that represents the initial response. + Task GetOriginalResponseAsync(RequestOptions options = null); + + /// + /// Edits original response for this interaction. + /// + /// A delegate containing the properties to modify the message with. + /// The request options for this request. + /// A that represents the initial response. + Task ModifyOriginalResponseAsync(Action func, RequestOptions options = null); + + /// + /// Acknowledges this interaction. + /// + /// + /// A task that represents the asynchronous operation of acknowledging the interaction. + /// + Task DeferAsync(bool ephemeral = false, RequestOptions options = null); + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/IDiscordInteractionData.cs b/src/Discord.Net.Core/Entities/Interactions/IDiscordInteractionData.cs new file mode 100644 index 000000000..42b95738e --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/IDiscordInteractionData.cs @@ -0,0 +1,7 @@ +namespace Discord +{ + /// + /// Represents an interface used to specify classes that they are a valid data type of a class. + /// + public interface IDiscordInteractionData { } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/InteractionResponseType.cs b/src/Discord.Net.Core/Entities/Interactions/InteractionResponseType.cs new file mode 100644 index 000000000..ebdf29781 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/InteractionResponseType.cs @@ -0,0 +1,46 @@ +using System; + +namespace Discord +{ + /// + /// The response type for an . + /// + /// + /// After receiving an interaction, you must respond to acknowledge it. You can choose to respond with a message immediately using + /// or you can choose to send a deferred response with . If choosing a deferred response, the user will see a loading state for the interaction, + /// and you'll have up to 15 minutes to edit the original deferred response using Edit Original Interaction Response. + /// You can read more about Response types Here. + /// + public enum InteractionResponseType : byte + { + /// + /// ACK a Ping. + /// + Pong = 1, + + /// + /// Respond to an interaction with a message. + /// + ChannelMessageWithSource = 4, + + /// + /// ACK an interaction and edit a response later, the user sees a loading state. + /// + DeferredChannelMessageWithSource = 5, + + /// + /// For components: ACK an interaction and edit the original message later; the user does not see a loading state. + /// + DeferredUpdateMessage = 6, + + /// + /// For components: edit the message the component was attached to. + /// + UpdateMessage = 7, + + /// + /// Respond with a set of choices to a autocomplete interaction. + /// + ApplicationCommandAutocompleteResult = 8 + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/InteractionType.cs b/src/Discord.Net.Core/Entities/Interactions/InteractionType.cs new file mode 100644 index 000000000..e09c906b5 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/InteractionType.cs @@ -0,0 +1,28 @@ +namespace Discord +{ + /// + /// Represents a type of Interaction from discord. + /// + public enum InteractionType : byte + { + /// + /// A ping from discord. + /// + Ping = 1, + + /// + /// A sent from discord. + /// + ApplicationCommand = 2, + + /// + /// A sent from discord. + /// + MessageComponent = 3, + + /// + /// An autocomplete request sent from discord. + /// + ApplicationCommandAutocomplete = 4 + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ActionRowComponent.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ActionRowComponent.cs new file mode 100644 index 000000000..202a5687f --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ActionRowComponent.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; + +namespace Discord +{ + /// + /// Represents a Row for child components to live in. + /// + public class ActionRowComponent : IMessageComponent + { + /// + public ComponentType Type => ComponentType.ActionRow; + + /// + /// Gets the child components in this row. + /// + public IReadOnlyCollection Components { get; internal set; } + + internal ActionRowComponent() { } + + internal ActionRowComponent(List components) + { + Components = components; + } + + string IMessageComponent.CustomId => null; + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ButtonComponent.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ButtonComponent.cs new file mode 100644 index 000000000..4b9fa2753 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ButtonComponent.cs @@ -0,0 +1,61 @@ +namespace Discord +{ + /// + /// Represents a Button. + /// + public class ButtonComponent : IMessageComponent + { + /// + public ComponentType Type => ComponentType.Button; + + /// + /// Gets the of this button, example buttons with each style can be found Here. + /// + public ButtonStyle Style { get; } + + /// + /// Gets the label of the button, this is the text that is shown. + /// + public string Label { get; } + + /// + /// Gets the displayed with this button. + /// + public IEmote Emote { get; } + + /// + public string CustomId { get; } + + /// + /// Gets the URL for a button. + /// + /// + /// You cannot have a button with a URL and a CustomId. + /// + public string Url { get; } + + /// + /// Gets whether this button is disabled or not. + /// + public bool IsDisabled { get; } + + /// + /// Turns this button into a button builder. + /// + /// + /// A newly created button builder with the same properties as this button. + /// + public ButtonBuilder ToBuilder() + => new ButtonBuilder(Label, CustomId, Style, Url, Emote, IsDisabled); + + internal ButtonComponent(ButtonStyle style, string label, IEmote emote, string customId, string url, bool isDisabled) + { + Style = style; + Label = label; + Emote = emote; + CustomId = customId; + Url = url; + IsDisabled = isDisabled; + } + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ButtonStyle.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ButtonStyle.cs new file mode 100644 index 000000000..92d48ab4f --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ButtonStyle.cs @@ -0,0 +1,33 @@ +namespace Discord +{ + /// + /// Represents different styles to use with buttons. You can see an example of the different styles at + /// + public enum ButtonStyle + { + /// + /// A Blurple button + /// + Primary = 1, + + /// + /// A Grey (or gray) button + /// + Secondary = 2, + + /// + /// A Green button + /// + Success = 3, + + /// + /// A Red button + /// + Danger = 4, + + /// + /// A button with a little popup box indicating that this button is a link. + /// + Link = 5 + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentBuilder.cs new file mode 100644 index 000000000..4461a4205 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentBuilder.cs @@ -0,0 +1,1064 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Discord.Utils; + +namespace Discord +{ + /// + /// Represents a builder for creating a . + /// + public class ComponentBuilder + { + /// + /// The max length of a . + /// + public const int MaxCustomIdLength = 100; + + /// + /// The max amount of rows a message can have. + /// + public const int MaxActionRowCount = 5; + + /// + /// Gets or sets the Action Rows for this Component Builder. + /// + /// cannot be null. + /// count exceeds . + public List ActionRows + { + get => _actionRows; + set + { + if (value == null) + throw new ArgumentNullException(nameof(value), $"{nameof(ActionRows)} cannot be null."); + if (value.Count > MaxActionRowCount) + throw new ArgumentOutOfRangeException(nameof(value), $"Action row count must be less than or equal to {MaxActionRowCount}."); + _actionRows = value; + } + } + + private List _actionRows; + + /// + /// Creates a new builder from a message. + /// + /// The message to create the builder from. + /// The newly created builder. + public static ComponentBuilder FromMessage(IMessage message) + => FromComponents(message.Components); + + /// + /// Creates a new builder from the provided list of components. + /// + /// The components to create the builder from. + /// The newly created builder. + public static ComponentBuilder FromComponents(IReadOnlyCollection components) + { + var builder = new ComponentBuilder(); + for (int i = 0; i != components.Count; i++) + { + var component = components.ElementAt(i); + builder.AddComponent(component, i); + } + return builder; + } + + internal void AddComponent(IMessageComponent component, int row) + { + switch (component) + { + case ButtonComponent button: + WithButton(button.Label, button.CustomId, button.Style, button.Emote, button.Url, button.IsDisabled, row); + break; + case ActionRowComponent actionRow: + foreach (var cmp in actionRow.Components) + AddComponent(cmp, row); + break; + case SelectMenuComponent menu: + WithSelectMenu(menu.CustomId, menu.Options.Select(x => new SelectMenuOptionBuilder(x.Label, x.Value, x.Description, x.Emote, x.IsDefault)).ToList(), menu.Placeholder, menu.MinValues, menu.MaxValues, menu.IsDisabled, row); + break; + } + } + + /// + /// Adds a to the at the specific row. + /// If the row cannot accept the component then it will add it to a row that can. + /// + /// The custom id of the menu. + /// The options of the menu. + /// The placeholder of the menu. + /// The min values of the placeholder. + /// The max values of the placeholder. + /// Whether or not the menu is disabled. + /// The row to add the menu to. + /// + public ComponentBuilder WithSelectMenu(string customId, List options, + string placeholder = null, int minValues = 1, int maxValues = 1, bool disabled = false, int row = 0) + { + return WithSelectMenu(new SelectMenuBuilder() + .WithCustomId(customId) + .WithOptions(options) + .WithPlaceholder(placeholder) + .WithMaxValues(maxValues) + .WithMinValues(minValues) + .WithDisabled(disabled), + row); + } + + /// + /// Adds a to the at the specific row. + /// If the row cannot accept the component then it will add it to a row that can. + /// + /// The menu to add. + /// The row to attempt to add this component on. + /// There is no more row to add a menu. + /// must be less than . + /// The current builder. + public ComponentBuilder WithSelectMenu(SelectMenuBuilder menu, int row = 0) + { + Preconditions.LessThan(row, MaxActionRowCount, nameof(row)); + if (menu.Options.Distinct().Count() != menu.Options.Count) + throw new InvalidOperationException("Please make sure that there is no duplicates values."); + + var builtMenu = menu.Build(); + + if (_actionRows == null) + { + _actionRows = new List + { + new ActionRowBuilder().AddComponent(builtMenu) + }; + } + else + { + if (_actionRows.Count == row) + _actionRows.Add(new ActionRowBuilder().AddComponent(builtMenu)); + else + { + ActionRowBuilder actionRow; + if (_actionRows.Count > row) + actionRow = _actionRows.ElementAt(row); + else + { + actionRow = new ActionRowBuilder(); + _actionRows.Add(actionRow); + } + + if (actionRow.CanTakeComponent(builtMenu)) + actionRow.AddComponent(builtMenu); + else if (row < MaxActionRowCount) + WithSelectMenu(menu, row + 1); + else + throw new InvalidOperationException($"There is no more row to add a {nameof(builtMenu)}"); + } + } + + return this; + } + + /// + /// Adds a with specified parameters to the at the specific row. + /// If the row cannot accept the component then it will add it to a row that can. + /// + /// The label text for the newly added button. + /// The style of this newly added button. + /// A to be used with this button. + /// The custom id of the newly added button. + /// A URL to be used only if the is a Link. + /// Whether or not the newly created button is disabled. + /// The row the button should be placed on. + /// The current builder. + public ComponentBuilder WithButton( + string label = null, + string customId = null, + ButtonStyle style = ButtonStyle.Primary, + IEmote emote = null, + string url = null, + bool disabled = false, + int row = 0) + { + var button = new ButtonBuilder() + .WithLabel(label) + .WithStyle(style) + .WithEmote(emote) + .WithCustomId(customId) + .WithUrl(url) + .WithDisabled(disabled); + + return WithButton(button, row); + } + + /// + /// Adds a to the at the specific row. + /// If the row cannot accept the component then it will add it to a row that can. + /// + /// The button to add. + /// The row to add the button. + /// There is no more row to add a menu. + /// must be less than . + /// The current builder. + public ComponentBuilder WithButton(ButtonBuilder button, int row = 0) + { + Preconditions.LessThan(row, MaxActionRowCount, nameof(row)); + + var builtButton = button.Build(); + + if (_actionRows == null) + { + _actionRows = new List + { + new ActionRowBuilder().AddComponent(builtButton) + }; + } + else + { + if (_actionRows.Count == row) + _actionRows.Add(new ActionRowBuilder().AddComponent(builtButton)); + else + { + ActionRowBuilder actionRow; + if (_actionRows.Count > row) + actionRow = _actionRows.ElementAt(row); + else + { + actionRow = new ActionRowBuilder(); + _actionRows.Add(actionRow); + } + + if (actionRow.CanTakeComponent(builtButton)) + actionRow.AddComponent(builtButton); + else if (row < MaxActionRowCount) + WithButton(button, row + 1); + else + throw new InvalidOperationException($"There is no more row to add a {nameof(button)}"); + } + } + + return this; + } + + /// + /// Builds this builder into a used to send your components. + /// + /// A that can be sent with . + public MessageComponent Build() + { + return _actionRows != null + ? new MessageComponent(_actionRows.Select(x => x.Build()).ToList()) + : MessageComponent.Empty; + } + } + + /// + /// Represents a class used to build Action rows. + /// + public class ActionRowBuilder + { + /// + /// The max amount of child components this row can hold. + /// + public const int MaxChildCount = 5; + + /// + /// Gets or sets the components inside this row. + /// + /// cannot be null. + /// count exceeds . + public List Components + { + get => _components; + set + { + if (value == null) + throw new ArgumentNullException(nameof(value), $"{nameof(Components)} cannot be null."); + + _components = value.Count switch + { + 0 => throw new ArgumentOutOfRangeException(nameof(value), "There must be at least 1 component in a row."), + > MaxChildCount => throw new ArgumentOutOfRangeException(nameof(value), $"Action row can only contain {MaxChildCount} child components!"), + _ => value + }; + } + } + + private List _components = new List(); + + /// + /// Adds a list of components to the current row. + /// + /// The list of components to add. + /// + /// The current builder. + public ActionRowBuilder WithComponents(List components) + { + Components = components; + return this; + } + + /// + /// Adds a component at the end of the current row. + /// + /// The component to add. + /// Components count reached + /// The current builder. + public ActionRowBuilder AddComponent(IMessageComponent component) + { + if (Components.Count >= MaxChildCount) + throw new InvalidOperationException($"Components count reached {MaxChildCount}"); + + Components.Add(component); + return this; + } + + /// + /// Builds the current builder to a that can be used within a + /// + /// A that can be used within a + public ActionRowComponent Build() + { + return new ActionRowComponent(_components); + } + + internal bool CanTakeComponent(IMessageComponent component) + { + switch (component.Type) + { + case ComponentType.ActionRow: + return false; + case ComponentType.Button: + if (Components.Any(x => x.Type == ComponentType.SelectMenu)) + return false; + else + return Components.Count < 5; + case ComponentType.SelectMenu: + return Components.Count == 0; + default: + return false; + } + } + } + + /// + /// Represents a class used to build 's. + /// + public class ButtonBuilder + { + /// + /// The max length of a . + /// + public const int MaxButtonLabelLength = 80; + + /// + /// Gets or sets the label of the current button. + /// + /// length exceeds . + /// length exceeds . + public string Label + { + get => _label; + set => _label = value?.Length switch + { + > MaxButtonLabelLength => throw new ArgumentOutOfRangeException(nameof(value), $"Label length must be less or equal to {MaxButtonLabelLength}."), + 0 => throw new ArgumentOutOfRangeException(nameof(value), "Label length must be at least 1."), + _ => value + }; + } + + /// + /// Gets or sets the custom id of the current button. + /// + /// length exceeds + /// length subceeds 1. + public string CustomId + { + get => _customId; + set => _customId = value?.Length switch + { + > ComponentBuilder.MaxCustomIdLength => throw new ArgumentOutOfRangeException(nameof(value), $"Custom Id length must be less or equal to {ComponentBuilder.MaxCustomIdLength}."), + 0 => throw new ArgumentOutOfRangeException(nameof(value), "Custom Id length must be at least 1."), + _ => value + }; + } + + /// + /// Gets or sets the of the current button. + /// + public ButtonStyle Style { get; set; } + + /// + /// Gets or sets the of the current button. + /// + public IEmote Emote { get; set; } + + /// + /// Gets or sets the url of the current button. + /// + public string Url { get; set; } + + /// + /// Gets or sets whether the current button is disabled. + /// + public bool IsDisabled { get; set; } + + private string _label; + private string _customId; + + /// + /// Creates a new instance of a . + /// + public ButtonBuilder() { } + + /// + /// Creates a new instance of a . + /// + /// The label to use on the newly created link button. + /// The url of this button. + /// The custom ID of this button. + /// The custom ID of this button. + /// The emote of this button. + /// Disabled this button or not. + public ButtonBuilder(string label = null, string customId = null, ButtonStyle style = ButtonStyle.Primary, string url = null, IEmote emote = null, bool isDisabled = false) + { + CustomId = customId; + Style = style; + Url = url; + Label = label; + IsDisabled = isDisabled; + Emote = emote; + } + + /// + /// Creates a new instance of a from instance of a . + /// + public ButtonBuilder(ButtonComponent button) + { + CustomId = button.CustomId; + Style = button.Style; + Url = button.Url; + Label = button.Label; + IsDisabled = button.IsDisabled; + Emote = button.Emote; + } + + /// + /// Creates a button with the style. + /// + /// The label for this link button. + /// The url for this link button to go to. + /// The emote for this link button. + /// A builder with the newly created button. + public static ButtonBuilder CreateLinkButton(string label, string url, IEmote emote = null) + => new ButtonBuilder(label, null, ButtonStyle.Link, url, emote: emote); + + /// + /// Creates a button with the style. + /// + /// The label for this danger button. + /// The custom id for this danger button. + /// The emote for this danger button. + /// A builder with the newly created button. + public static ButtonBuilder CreateDangerButton(string label, string customId, IEmote emote = null) + => new ButtonBuilder(label, customId, ButtonStyle.Danger, emote: emote); + + /// + /// Creates a button with the style. + /// + /// The label for this primary button. + /// The custom id for this primary button. + /// The emote for this primary button. + /// A builder with the newly created button. + public static ButtonBuilder CreatePrimaryButton(string label, string customId, IEmote emote = null) + => new ButtonBuilder(label, customId, emote: emote); + + /// + /// Creates a button with the style. + /// + /// The label for this secondary button. + /// The custom id for this secondary button. + /// The emote for this secondary button. + /// A builder with the newly created button. + public static ButtonBuilder CreateSecondaryButton(string label, string customId, IEmote emote = null) + => new ButtonBuilder(label, customId, ButtonStyle.Secondary, emote: emote); + + /// + /// Creates a button with the style. + /// + /// The label for this success button. + /// The custom id for this success button. + /// The emote for this success button. + /// A builder with the newly created button. + public static ButtonBuilder CreateSuccessButton(string label, string customId, IEmote emote = null) + => new ButtonBuilder(label, customId, ButtonStyle.Success, emote: emote); + + /// + /// Sets the current buttons label to the specified text. + /// + /// The text for the label. + /// + /// The current builder. + public ButtonBuilder WithLabel(string label) + { + Label = label; + return this; + } + + /// + /// Sets the current buttons style. + /// + /// The style for this builders button. + /// The current builder. + public ButtonBuilder WithStyle(ButtonStyle style) + { + Style = style; + return this; + } + + /// + /// Sets the current buttons emote. + /// + /// The emote to use for the current button. + /// The current builder. + public ButtonBuilder WithEmote(IEmote emote) + { + Emote = emote; + return this; + } + + /// + /// Sets the current buttons url. + /// + /// The url to use for the current button. + /// The current builder. + public ButtonBuilder WithUrl(string url) + { + Url = url; + return this; + } + + /// + /// Sets the custom id of the current button. + /// + /// The id to use for the current button. + /// + /// The current builder. + public ButtonBuilder WithCustomId(string id) + { + CustomId = id; + return this; + } + + /// + /// Sets whether the current button is disabled. + /// + /// Whether the current button is disabled or not. + /// The current builder. + public ButtonBuilder WithDisabled(bool isDisabled) + { + IsDisabled = isDisabled; + return this; + } + + /// + /// Builds this builder into a to be used in a . + /// + /// A to be used in a . + /// A button must contain either a or a , but not both. + /// A button must have an or a . + /// A link button must contain a URL. + /// A URL must include a protocol (http or https). + /// A non-link button must contain a custom id + public ButtonComponent Build() + { + if (string.IsNullOrEmpty(Label) && Emote == null) + throw new InvalidOperationException("A button must have an Emote or a label!"); + + if (!(string.IsNullOrEmpty(Url) ^ string.IsNullOrEmpty(CustomId))) + throw new InvalidOperationException("A button must contain either a URL or a CustomId, but not both!"); + + if (Style == ButtonStyle.Link) + { + if (string.IsNullOrEmpty(Url)) + throw new InvalidOperationException("Link buttons must have a link associated with them"); + UrlValidation.ValidateButton(Url); + } + else if (string.IsNullOrEmpty(CustomId)) + throw new InvalidOperationException("Non-link buttons must have a custom id associated with them"); + + return new ButtonComponent(Style, Label, Emote, CustomId, Url, IsDisabled); + } + } + + /// + /// Represents a class used to build 's. + /// + public class SelectMenuBuilder + { + /// + /// The max length of a . + /// + public const int MaxPlaceholderLength = 100; + + /// + /// The maximum number of values for the and properties. + /// + public const int MaxValuesCount = 25; + + /// + /// The maximum number of options a can have. + /// + public const int MaxOptionCount = 25; + + /// + /// Gets or sets the custom id of the current select menu. + /// + /// length exceeds + /// length subceeds 1. + public string CustomId + { + get => _customId; + set => _customId = value?.Length switch + { + > ComponentBuilder.MaxCustomIdLength => throw new ArgumentOutOfRangeException(nameof(value), $"Custom Id length must be less or equal to {ComponentBuilder.MaxCustomIdLength}."), + 0 => throw new ArgumentOutOfRangeException(nameof(value), "Custom Id length must be at least 1."), + _ => value + }; + } + + /// + /// Gets or sets the placeholder text of the current select menu. + /// + /// length exceeds . + /// length subceeds 1. + public string Placeholder + { + get => _placeholder; + set => _placeholder = value?.Length switch + { + > MaxPlaceholderLength => throw new ArgumentOutOfRangeException(nameof(value), $"Placeholder length must be less or equal to {MaxPlaceholderLength}."), + 0 => throw new ArgumentOutOfRangeException(nameof(value), "Placeholder length must be at least 1."), + _ => value + }; + } + + /// + /// Gets or sets the minimum values of the current select menu. + /// + /// exceeds . + public int MinValues + { + get => _minValues; + set + { + Preconditions.AtMost(value, MaxValuesCount, nameof(MinValues)); + _minValues = value; + } + } + + /// + /// Gets or sets the maximum values of the current select menu. + /// + /// exceeds . + public int MaxValues + { + get => _maxValues; + set + { + Preconditions.AtMost(value, MaxValuesCount, nameof(MaxValues)); + _maxValues = value; + } + } + + /// + /// Gets or sets a collection of for this current select menu. + /// + /// count exceeds . + /// is null. + public List Options + { + get => _options; + set + { + if (value != null) + Preconditions.AtMost(value.Count, MaxOptionCount, nameof(Options)); + else + throw new ArgumentNullException(nameof(value), $"{nameof(Options)} cannot be null."); + + _options = value; + } + } + + /// + /// Gets or sets whether the current menu is disabled. + /// + public bool IsDisabled { get; set; } + + private List _options = new List(); + private int _minValues = 1; + private int _maxValues = 1; + private string _placeholder; + private string _customId; + + /// + /// Creates a new instance of a . + /// + public SelectMenuBuilder() { } + + /// + /// Creates a new instance of a from instance of . + /// + public SelectMenuBuilder(SelectMenuComponent selectMenu) + { + Placeholder = selectMenu.Placeholder; + CustomId = selectMenu.Placeholder; + MaxValues = selectMenu.MaxValues; + MinValues = selectMenu.MinValues; + IsDisabled = selectMenu.IsDisabled; + Options = selectMenu.Options? + .Select(x => new SelectMenuOptionBuilder(x.Label, x.Value, x.Description, x.Emote, x.IsDefault)) + .ToList(); + } + + /// + /// Creates a new instance of a . + /// + /// The custom id of this select menu. + /// The options for this select menu. + /// The placeholder of this select menu. + /// The max values of this select menu. + /// The min values of this select menu. + /// Disabled this select menu or not. + public SelectMenuBuilder(string customId, List options, string placeholder = null, int maxValues = 1, int minValues = 1, bool isDisabled = false) + { + CustomId = customId; + Options = options; + Placeholder = placeholder; + IsDisabled = isDisabled; + MaxValues = maxValues; + MinValues = minValues; + } + + /// + /// Sets the field CustomId. + /// + /// The value to set the field CustomId to. + /// + /// + /// The current builder. + /// + public SelectMenuBuilder WithCustomId(string customId) + { + CustomId = customId; + return this; + } + + /// + /// Sets the field placeholder. + /// + /// The value to set the field placeholder to. + /// + /// + /// The current builder. + /// + public SelectMenuBuilder WithPlaceholder(string placeholder) + { + Placeholder = placeholder; + return this; + } + + /// + /// Sets the field minValues. + /// + /// The value to set the field minValues to. + /// + /// + /// The current builder. + /// + public SelectMenuBuilder WithMinValues(int minValues) + { + MinValues = minValues; + return this; + } + + /// + /// Sets the field maxValues. + /// + /// The value to set the field maxValues to. + /// + /// + /// The current builder. + /// + public SelectMenuBuilder WithMaxValues(int maxValues) + { + MaxValues = maxValues; + return this; + } + + /// + /// Sets the field options. + /// + /// The value to set the field options to. + /// + /// + /// The current builder. + /// + public SelectMenuBuilder WithOptions(List options) + { + Options = options; + return this; + } + + /// + /// Add one option to menu options. + /// + /// The option builder class containing the option properties. + /// Options count reached . + /// + /// The current builder. + /// + public SelectMenuBuilder AddOption(SelectMenuOptionBuilder option) + { + if (Options.Count >= MaxOptionCount) + throw new InvalidOperationException($"Options count reached {MaxOptionCount}."); + + Options.Add(option); + return this; + } + + /// + /// Add one option to menu options. + /// + /// The label for this option. + /// The value of this option. + /// The description of this option. + /// The emote of this option. + /// Render this option as selected by default or not. + /// Options count reached . + /// + /// The current builder. + /// + public SelectMenuBuilder AddOption(string label, string value, string description = null, IEmote emote = null, bool? isDefault = null) + { + AddOption(new SelectMenuOptionBuilder(label, value, description, emote, isDefault)); + return this; + } + + /// + /// Sets whether the current menu is disabled. + /// + /// Whether the current menu is disabled or not. + /// + /// The current builder. + /// + public SelectMenuBuilder WithDisabled(bool isDisabled) + { + IsDisabled = isDisabled; + return this; + } + + /// + /// Builds a + /// + /// The newly built + public SelectMenuComponent Build() + { + var options = Options?.Select(x => x.Build()).ToList(); + + return new SelectMenuComponent(CustomId, options, Placeholder, MinValues, MaxValues, IsDisabled); + } + } + + /// + /// Represents a class used to build 's. + /// + public class SelectMenuOptionBuilder + { + /// + /// The maximum length of a . + /// + public const int MaxSelectLabelLength = 100; + + /// + /// The maximum length of a . + /// + public const int MaxDescriptionLength = 100; + + /// + /// The maximum length of a . + /// + public const int MaxSelectValueLength = 100; + + /// + /// Gets or sets the label of the current select menu. + /// + /// length exceeds + /// length subceeds 1. + public string Label + { + get => _label; + set => _label = value?.Length switch + { + > MaxSelectLabelLength => throw new ArgumentOutOfRangeException(nameof(value), $"Label length must be less or equal to {MaxSelectLabelLength}."), + 0 => throw new ArgumentOutOfRangeException(nameof(value), "Label length must be at least 1."), + _ => value + }; + } + + /// + /// Gets or sets the value of the current select menu. + /// + /// length exceeds . + /// length subceeds 1. + public string Value + { + get => _value; + set => _value = value?.Length switch + { + > MaxSelectValueLength => throw new ArgumentOutOfRangeException(nameof(value), $"Value length must be less or equal to {MaxSelectValueLength}."), + 0 => throw new ArgumentOutOfRangeException(nameof(value), "Value length must be at least 1."), + _ => value + }; + } + + /// + /// Gets or sets this menu options description. + /// + /// length exceeds . + /// length subceeds 1. + public string Description + { + get => _description; + set => _description = value?.Length switch + { + > MaxDescriptionLength => throw new ArgumentOutOfRangeException(nameof(value), $"Description length must be less or equal to {MaxDescriptionLength}."), + 0 => throw new ArgumentOutOfRangeException(nameof(value), "Description length must be at least 1."), + _ => value + }; + } + + /// + /// Gets or sets the emote of this option. + /// + public IEmote Emote { get; set; } + + /// + /// Gets or sets the whether or not this option will render selected by default. + /// + public bool? IsDefault { get; set; } + + private string _label; + private string _value; + private string _description; + + /// + /// Creates a new instance of a . + /// + public SelectMenuOptionBuilder() { } + + /// + /// Creates a new instance of a . + /// + /// The label for this option. + /// The value of this option. + /// The description of this option. + /// The emote of this option. + /// Render this option as selected by default or not. + public SelectMenuOptionBuilder(string label, string value, string description = null, IEmote emote = null, bool? isDefault = null) + { + Label = label; + Value = value; + Description = description; + Emote = emote; + IsDefault = isDefault; + } + + /// + /// Creates a new instance of a from instance of a . + /// + public SelectMenuOptionBuilder(SelectMenuOption option) + { + Label = option.Label; + Value = option.Value; + Description = option.Description; + Emote = option.Emote; + IsDefault = option.IsDefault; + } + + /// + /// Sets the field label. + /// + /// The value to set the field label to. + /// + /// + /// The current builder. + /// + public SelectMenuOptionBuilder WithLabel(string label) + { + Label = label; + return this; + } + + /// + /// Sets the field value. + /// + /// The value to set the field value to. + /// + /// + /// The current builder. + /// + public SelectMenuOptionBuilder WithValue(string value) + { + Value = value; + return this; + } + + /// + /// Sets the field description. + /// + /// The value to set the field description to. + /// + /// + /// The current builder. + /// + public SelectMenuOptionBuilder WithDescription(string description) + { + Description = description; + return this; + } + + /// + /// Sets the field emote. + /// + /// The value to set the field emote to. + /// + /// The current builder. + /// + public SelectMenuOptionBuilder WithEmote(IEmote emote) + { + Emote = emote; + return this; + } + + /// + /// Sets the field default. + /// + /// The value to set the field default to. + /// + /// The current builder. + /// + public SelectMenuOptionBuilder WithDefault(bool isDefault) + { + IsDefault = isDefault; + return this; + } + + /// + /// Builds a . + /// + /// The newly built . + public SelectMenuOption Build() + { + return new SelectMenuOption(Label, Value, Description, Emote, IsDefault); + } + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentType.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentType.cs new file mode 100644 index 000000000..70bc1f301 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentType.cs @@ -0,0 +1,23 @@ +namespace Discord +{ + /// + /// Represents a type of a component. + /// + public enum ComponentType + { + /// + /// A container for other components. + /// + ActionRow = 1, + + /// + /// A clickable button. + /// + Button = 2, + + /// + /// A select menu for picking from choices. + /// + SelectMenu = 3 + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/IComponentInteraction.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/IComponentInteraction.cs new file mode 100644 index 000000000..2a46e8f18 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/IComponentInteraction.cs @@ -0,0 +1,18 @@ +namespace Discord +{ + /// + /// Represents an interaction type for Message Components. + /// + public interface IComponentInteraction : IDiscordInteraction + { + /// + /// Gets the data received with this interaction, contains the button that was clicked. + /// + new IComponentInteractionData Data { get; } + + /// + /// Gets the message that contained the trigger for this interaction. + /// + IUserMessage Message { get; } + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/IComponentInteractionData.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/IComponentInteractionData.cs new file mode 100644 index 000000000..99b9b6f6c --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/IComponentInteractionData.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; + +namespace Discord +{ + /// + /// Represents the data sent with the . + /// + public interface IComponentInteractionData : IDiscordInteractionData + { + /// + /// Gets the components Custom Id that was clicked. + /// + string CustomId { get; } + + /// + /// Gets the type of the component clicked. + /// + ComponentType Type { get; } + + /// + /// Gets the value(s) of a interaction response. + /// + IReadOnlyCollection Values { get; } + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/IMessageComponent.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/IMessageComponent.cs new file mode 100644 index 000000000..9366a44d6 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/IMessageComponent.cs @@ -0,0 +1,18 @@ +namespace Discord +{ + /// + /// Represents a message component on a message. + /// + public interface IMessageComponent + { + /// + /// Gets the of this Message Component. + /// + ComponentType Type { get; } + + /// + /// Gets the custom id of the component if possible; otherwise . + /// + string CustomId { get; } + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/MessageComponent.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/MessageComponent.cs new file mode 100644 index 000000000..720588681 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/MessageComponent.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; + +namespace Discord +{ + /// + /// Represents a component object used to send components with messages. + /// + public class MessageComponent + { + /// + /// Gets the components to be used in a message. + /// + public IReadOnlyCollection Components { get; } + + internal MessageComponent(List components) + { + Components = components; + } + + /// + /// Returns a empty . + /// + internal static MessageComponent Empty + => new MessageComponent(new List()); + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/SelectMenuComponent.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/SelectMenuComponent.cs new file mode 100644 index 000000000..229c1e148 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/SelectMenuComponent.cs @@ -0,0 +1,67 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Discord +{ + /// + /// Represents a select menu component defined at + /// + public class SelectMenuComponent : IMessageComponent + { + /// + public ComponentType Type => ComponentType.SelectMenu; + + /// + public string CustomId { get; } + + /// + /// Gets the menus options to select from. + /// + public IReadOnlyCollection Options { get; } + + /// + /// Gets the custom placeholder text if nothing is selected. + /// + public string Placeholder { get; } + + /// + /// Gets the minimum number of items that must be chosen. + /// + public int MinValues { get; } + + /// + /// Gets the maximum number of items that can be chosen. + /// + public int MaxValues { get; } + + /// + /// Gets whether this menu is disabled or not. + /// + public bool IsDisabled { get; } + + /// + /// Turns this select menu into a builder. + /// + /// + /// A newly create builder with the same properties as this select menu. + /// + public SelectMenuBuilder ToBuilder() + => new SelectMenuBuilder( + CustomId, + Options.Select(x => new SelectMenuOptionBuilder(x.Label, x.Value, x.Description, x.Emote, x.IsDefault)).ToList(), + Placeholder, + MaxValues, + MinValues, + IsDisabled); + + internal SelectMenuComponent(string customId, List options, string placeholder, int minValues, int maxValues, bool disabled) + { + CustomId = customId; + Options = options; + Placeholder = placeholder; + MinValues = minValues; + MaxValues = maxValues; + IsDisabled = disabled; + } + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/SelectMenuOption.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/SelectMenuOption.cs new file mode 100644 index 000000000..6856e1ee3 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/SelectMenuOption.cs @@ -0,0 +1,42 @@ +namespace Discord +{ + /// + /// Represents a choice for a . + /// + public class SelectMenuOption + { + /// + /// Gets the user-facing name of the option. + /// + public string Label { get; } + + /// + /// Gets the dev-define value of the option. + /// + public string Value { get; } + + /// + /// Gets a description of the option. + /// + public string Description { get; } + + /// + /// Gets the displayed with this menu option. + /// + public IEmote Emote { get; } + + /// + /// Gets whether or not this option will render as selected by default. + /// + public bool? IsDefault { get; } + + internal SelectMenuOption(string label, string value, string description, IEmote emote, bool? defaultValue) + { + Label = label; + Value = value; + Description = description; + Emote = emote; + IsDefault = defaultValue; + } + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/SlashCommands/IAutocompleteInteraction.cs b/src/Discord.Net.Core/Entities/Interactions/SlashCommands/IAutocompleteInteraction.cs new file mode 100644 index 000000000..bb5343d84 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/SlashCommands/IAutocompleteInteraction.cs @@ -0,0 +1,13 @@ +namespace Discord +{ + /// + /// Represents a . + /// + public interface IAutocompleteInteraction : IDiscordInteraction + { + /// + /// Gets the autocomplete data of this interaction. + /// + new IAutocompleteInteractionData Data { get; } + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/SlashCommands/IAutocompleteInteractionData.cs b/src/Discord.Net.Core/Entities/Interactions/SlashCommands/IAutocompleteInteractionData.cs new file mode 100644 index 000000000..e6d1e9fae --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/SlashCommands/IAutocompleteInteractionData.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; + +namespace Discord +{ + /// + /// Represents data for a slash commands autocomplete interaction. + /// + public interface IAutocompleteInteractionData : IDiscordInteractionData + { + /// + /// Gets the name of the invoked command. + /// + string CommandName { get; } + + /// + /// Gets the id of the invoked command. + /// + ulong CommandId { get; } + + /// + /// Gets the type of the invoked command. + /// + ApplicationCommandType Type { get; } + + /// + /// Gets the version of the invoked command. + /// + ulong Version { get; } + + /// + /// Gets the current autocomplete option that is actively being filled out. + /// + AutocompleteOption Current { get; } + + /// + /// Gets a collection of all the other options the executing users has filled out. + /// + IReadOnlyCollection Options { get; } + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/SlashCommands/ISlashCommandInteraction.cs b/src/Discord.Net.Core/Entities/Interactions/SlashCommands/ISlashCommandInteraction.cs new file mode 100644 index 000000000..556182987 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/SlashCommands/ISlashCommandInteraction.cs @@ -0,0 +1,13 @@ +namespace Discord +{ + /// + /// Represents a slash command interaction. + /// + public interface ISlashCommandInteraction : IDiscordInteraction + { + /// + /// Gets the data associated with this interaction. + /// + new IApplicationCommandInteractionData Data { get; } + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/SlashCommands/SlashCommandBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/SlashCommands/SlashCommandBuilder.cs new file mode 100644 index 000000000..b4fc89cc2 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/SlashCommands/SlashCommandBuilder.cs @@ -0,0 +1,640 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; + +namespace Discord +{ + /// + /// Represents a class used to build slash commands. + /// + public class SlashCommandBuilder + { + /// + /// Returns the maximum length a commands name allowed by Discord + /// + public const int MaxNameLength = 32; + /// + /// Returns the maximum length of a commands description allowed by Discord. + /// + public const int MaxDescriptionLength = 100; + /// + /// Returns the maximum count of command options allowed by Discord + /// + public const int MaxOptionsCount = 25; + + /// + /// Gets or sets the name of this slash command. + /// + public string Name + { + get => _name; + set + { + Preconditions.NotNullOrEmpty(value, nameof(value)); + Preconditions.AtLeast(value.Length, 1, nameof(value)); + Preconditions.AtMost(value.Length, MaxNameLength, nameof(value)); + + // Discord updated the docs, this regex prevents special characters like @!$%(... etc, + // https://discord.com/developers/docs/interactions/slash-commands#applicationcommand + if (!Regex.IsMatch(value, @"^[\w-]{1,32}$")) + throw new ArgumentException("Command name cannot contain any special characters or whitespaces!", nameof(value)); + + _name = value; + } + } + + /// + /// Gets or sets a 1-100 length description of this slash command + /// + public string Description + { + get => _description; + set + { + Preconditions.NotNullOrEmpty(value, nameof(Description)); + Preconditions.AtLeast(value.Length, 1, nameof(Description)); + Preconditions.AtMost(value.Length, MaxDescriptionLength, nameof(Description)); + + _description = value; + } + } + + /// + /// Gets or sets the options for this command. + /// + public List Options + { + get => _options; + set + { + Preconditions.AtMost(value?.Count ?? 0, MaxOptionsCount, nameof(value)); + _options = value; + } + } + + /// + /// Gets or sets whether the command is enabled by default when the app is added to a guild + /// + public bool IsDefaultPermission { get; set; } = true; + + private string _name; + private string _description; + private List _options; + + /// + /// Build the current builder into a class. + /// + /// A that can be used to create slash commands. + public SlashCommandProperties Build() + { + var props = new SlashCommandProperties + { + Name = Name, + Description = Description, + IsDefaultPermission = IsDefaultPermission, + }; + + if (Options != null && Options.Any()) + { + var options = new List(); + + Options.OrderByDescending(x => x.IsRequired ?? false).ToList().ForEach(x => options.Add(x.Build())); + + props.Options = options; + } + + return props; + } + + /// + /// Sets the field name. + /// + /// The value to set the field name to. + /// + /// The current builder. + /// + public SlashCommandBuilder WithName(string name) + { + Name = name; + return this; + } + + /// + /// Sets the description of the current command. + /// + /// The description of this command. + /// The current builder. + public SlashCommandBuilder WithDescription(string description) + { + Description = description; + return this; + } + + /// + /// Sets the default permission of the current command. + /// + /// The default permission value to set. + /// The current builder. + public SlashCommandBuilder WithDefaultPermission(bool value) + { + IsDefaultPermission = value; + return this; + } + + /// + /// Adds an option to the current slash command. + /// + /// The name of the option to add. + /// The type of this option. + /// The description of this option. + /// If this option is required for this command. + /// If this option is the default option. + /// If this option is set to autocomplete. + /// The options of the option to add. + /// The allowed channel types for this option. + /// The choices of this option. + /// The smallest number value the user can input. + /// The largest number value the user can input. + /// The current builder. + public SlashCommandBuilder AddOption(string name, ApplicationCommandOptionType type, + string description, bool? isRequired = null, bool? isDefault = null, bool isAutocomplete = false, double? minValue = null, double? maxValue = null, + List options = null, List channelTypes = null, params ApplicationCommandOptionChoiceProperties[] choices) + { + // Make sure the name matches the requirements from discord + Preconditions.NotNullOrEmpty(name, nameof(name)); + Preconditions.AtLeast(name.Length, 1, nameof(name)); + Preconditions.AtMost(name.Length, MaxNameLength, nameof(name)); + + // Discord updated the docs, this regex prevents special characters like @!$%( and s p a c e s.. etc, + // https://discord.com/developers/docs/interactions/slash-commands#applicationcommand + if (!Regex.IsMatch(name, @"^[\w-]{1,32}$")) + throw new ArgumentException("Command name cannot contain any special characters or whitespaces!", nameof(name)); + + // same with description + Preconditions.NotNullOrEmpty(description, nameof(description)); + Preconditions.AtLeast(description.Length, 1, nameof(description)); + Preconditions.AtMost(description.Length, MaxDescriptionLength, nameof(description)); + + // make sure theres only one option with default set to true + if (isDefault == true && Options?.Any(x => x.IsDefault == true) == true) + throw new ArgumentException("There can only be one command option with default set to true!", nameof(isDefault)); + + var option = new SlashCommandOptionBuilder + { + Name = name, + Description = description, + IsRequired = isRequired, + IsDefault = isDefault, + Options = options, + Type = type, + IsAutocomplete = isAutocomplete, + Choices = (choices ?? Array.Empty()).ToList(), + ChannelTypes = channelTypes, + MinValue = minValue, + MaxValue = maxValue, + }; + + return AddOption(option); + } + + /// + /// Adds an option to this slash command. + /// + /// The option to add. + /// The current builder. + public SlashCommandBuilder AddOption(SlashCommandOptionBuilder option) + { + Options ??= new List(); + + if (Options.Count >= MaxOptionsCount) + throw new InvalidOperationException($"Cannot have more than {MaxOptionsCount} options!"); + + Preconditions.NotNull(option, nameof(option)); + + Options.Add(option); + return this; + } + /// + /// Adds a collection of options to the current slash command. + /// + /// The collection of options to add. + /// The current builder. + public SlashCommandBuilder AddOptions(params SlashCommandOptionBuilder[] options) + { + if (options == null) + throw new ArgumentNullException(nameof(options), "Options cannot be null!"); + + if (options.Length == 0) + throw new ArgumentException("Options cannot be empty!", nameof(options)); + + Options ??= new List(); + + if (Options.Count + options.Length > MaxOptionsCount) + throw new ArgumentOutOfRangeException(nameof(options), $"Cannot have more than {MaxOptionsCount} options!"); + + Options.AddRange(options); + return this; + } + } + + /// + /// Represents a class used to build options for the . + /// + public class SlashCommandOptionBuilder + { + /// + /// The max length of a choice's name allowed by Discord. + /// + public const int ChoiceNameMaxLength = 100; + + /// + /// The maximum number of choices allowed by Discord. + /// + public const int MaxChoiceCount = 25; + + private string _name; + private string _description; + + /// + /// Gets or sets the name of this option. + /// + public string Name + { + get => _name; + set + { + if (value != null) + { + Preconditions.AtLeast(value.Length, 1, nameof(value)); + Preconditions.AtMost(value.Length, SlashCommandBuilder.MaxNameLength, nameof(value)); + if (!Regex.IsMatch(value, @"^[\w-]{1,32}$")) + throw new ArgumentException("Option name cannot contain any special characters or whitespaces!", nameof(value)); + } + + _name = value; + } + } + + /// + /// Gets or sets the description of this option. + /// + public string Description + { + get => _description; + set + { + if (value != null) + { + Preconditions.AtLeast(value.Length, 1, nameof(value)); + Preconditions.AtMost(value.Length, SlashCommandBuilder.MaxDescriptionLength, nameof(value)); + } + + _description = value; + } + } + + /// + /// Gets or sets the type of this option. + /// + public ApplicationCommandOptionType Type { get; set; } + + /// + /// Gets or sets whether or not this options is the first required option for the user to complete. only one option can be default. + /// + public bool? IsDefault { get; set; } + + /// + /// Gets or sets if the option is required. + /// + public bool? IsRequired { get; set; } = null; + + /// + /// Gets or sets whether or not this option supports autocomplete. + /// + public bool IsAutocomplete { get; set; } + + /// + /// Gets or sets the smallest number value the user can input. + /// + public double? MinValue { get; set; } + + /// + /// Gets or sets the largest number value the user can input. + /// + public double? MaxValue { get; set; } + + /// + /// Gets or sets the choices for string and int types for the user to pick from. + /// + public List Choices { get; set; } + + /// + /// Gets or sets if this option is a subcommand or subcommand group type, these nested options will be the parameters. + /// + public List Options { get; set; } + + /// + /// Gets or sets the allowed channel types for this option. + /// + public List ChannelTypes { get; set; } + + /// + /// Builds the current option. + /// + /// The built version of this option. + public ApplicationCommandOptionProperties Build() + { + bool isSubType = Type == ApplicationCommandOptionType.SubCommandGroup; + bool isIntType = Type == ApplicationCommandOptionType.Integer; + + if (isSubType && (Options == null || !Options.Any())) + throw new InvalidOperationException("SubCommands/SubCommandGroups must have at least one option"); + + if (!isSubType && Options != null && Options.Any() && Type != ApplicationCommandOptionType.SubCommand) + throw new InvalidOperationException($"Cannot have options on {Type} type"); + + if (isIntType && MinValue != null && MinValue % 1 != 0) + throw new InvalidOperationException("MinValue cannot have decimals on Integer command options."); + + if (isIntType && MaxValue != null && MaxValue % 1 != 0) + throw new InvalidOperationException("MaxValue cannot have decimals on Integer command options."); + + return new ApplicationCommandOptionProperties + { + Name = Name, + Description = Description, + IsDefault = IsDefault, + IsRequired = IsRequired, + Type = Type, + Options = Options?.Count > 0 + ? Options.OrderByDescending(x => x.IsRequired ?? false).Select(x => x.Build()).ToList() + : new List(), + Choices = Choices, + IsAutocomplete = IsAutocomplete, + ChannelTypes = ChannelTypes, + MinValue = MinValue, + MaxValue = MaxValue + }; + } + + /// + /// Adds an option to the current slash command. + /// + /// The name of the option to add. + /// The type of this option. + /// The description of this option. + /// If this option is required for this command. + /// If this option is the default option. + /// If this option supports autocomplete. + /// The options of the option to add. + /// The allowed channel types for this option. + /// The choices of this option. + /// The smallest number value the user can input. + /// The largest number value the user can input. + /// The current builder. + public SlashCommandOptionBuilder AddOption(string name, ApplicationCommandOptionType type, + string description, bool? required = null, bool isDefault = false, bool isAutocomplete = false, double? minValue = null, double? maxValue = null, + List options = null, List channelTypes = null, params ApplicationCommandOptionChoiceProperties[] choices) + { + // Make sure the name matches the requirements from discord + Preconditions.NotNullOrEmpty(name, nameof(name)); + Preconditions.AtLeast(name.Length, 1, nameof(name)); + Preconditions.AtMost(name.Length, SlashCommandBuilder.MaxNameLength, nameof(name)); + + // Discord updated the docs, this regex prevents special characters like @!$%( and s p a c e s.. etc, + // https://discord.com/developers/docs/interactions/slash-commands#applicationcommand + if (!Regex.IsMatch(name, @"^[\w-]{1,32}$")) + throw new ArgumentException("Command name cannot contain any special characters or whitespaces!", nameof(name)); + + // same with description + Preconditions.NotNullOrEmpty(description, nameof(description)); + Preconditions.AtLeast(description.Length, 1, nameof(description)); + Preconditions.AtMost(description.Length, SlashCommandBuilder.MaxDescriptionLength, nameof(description)); + + // make sure theres only one option with default set to true + if (isDefault && Options?.Any(x => x.IsDefault == true) == true) + throw new ArgumentException("There can only be one command option with default set to true!", nameof(isDefault)); + + var option = new SlashCommandOptionBuilder + { + Name = name, + Description = description, + IsRequired = required, + IsDefault = isDefault, + IsAutocomplete = isAutocomplete, + MinValue = minValue, + MaxValue = maxValue, + Options = options, + Type = type, + Choices = (choices ?? Array.Empty()).ToList(), + ChannelTypes = channelTypes + }; + + return AddOption(option); + } + /// + /// Adds a sub option to the current option. + /// + /// The sub option to add. + /// The current builder. + public SlashCommandOptionBuilder AddOption(SlashCommandOptionBuilder option) + { + Options ??= new List(); + + if (Options.Count >= SlashCommandBuilder.MaxOptionsCount) + throw new InvalidOperationException($"There can only be {SlashCommandBuilder.MaxOptionsCount} options per sub command group!"); + + Preconditions.NotNull(option, nameof(option)); + + Options.Add(option); + return this; + } + + /// + /// Adds a choice to the current option. + /// + /// The name of the choice. + /// The value of the choice. + /// The current builder. + public SlashCommandOptionBuilder AddChoice(string name, int value) + { + return AddChoiceInternal(name, value); + } + + /// + /// Adds a choice to the current option. + /// + /// The name of the choice. + /// The value of the choice. + /// The current builder. + public SlashCommandOptionBuilder AddChoice(string name, string value) + { + return AddChoiceInternal(name, value); + } + + /// + /// Adds a choice to the current option. + /// + /// The name of the choice. + /// The value of the choice. + /// The current builder. + public SlashCommandOptionBuilder AddChoice(string name, double value) + { + return AddChoiceInternal(name, value); + } + + /// + /// Adds a choice to the current option. + /// + /// The name of the choice. + /// The value of the choice. + /// The current builder. + public SlashCommandOptionBuilder AddChoice(string name, float value) + { + return AddChoiceInternal(name, value); + } + + /// + /// Adds a choice to the current option. + /// + /// The name of the choice. + /// The value of the choice. + /// The current builder. + public SlashCommandOptionBuilder AddChoice(string name, long value) + { + return AddChoiceInternal(name, value); + } + + private SlashCommandOptionBuilder AddChoiceInternal(string name, object value) + { + Choices ??= new List(); + + if (Choices.Count >= MaxChoiceCount) + throw new InvalidOperationException($"Cannot add more than {MaxChoiceCount} choices!"); + + Preconditions.NotNull(name, nameof(name)); + Preconditions.NotNull(value, nameof(value)); + + Preconditions.AtLeast(name.Length, 1, nameof(name)); + Preconditions.AtMost(name.Length, 100, nameof(name)); + + if(value is string str) + { + Preconditions.AtLeast(str.Length, 1, nameof(value)); + Preconditions.AtMost(str.Length, 100, nameof(value)); + } + + Choices.Add(new ApplicationCommandOptionChoiceProperties + { + Name = name, + Value = value + }); + + return this; + } + + /// + /// Adds a channel type to the current option. + /// + /// The to add. + /// The current builder. + public SlashCommandOptionBuilder AddChannelType(ChannelType channelType) + { + ChannelTypes ??= new List(); + + ChannelTypes.Add(channelType); + + return this; + } + + /// + /// Sets the current builders name. + /// + /// The name to set the current option builder. + /// The current builder. + public SlashCommandOptionBuilder WithName(string name) + { + Name = name; + + return this; + } + + /// + /// Sets the current builders description. + /// + /// The description to set. + /// The current builder. + public SlashCommandOptionBuilder WithDescription(string description) + { + Description = description; + return this; + } + + /// + /// Sets the current builders required field. + /// + /// The value to set. + /// The current builder. + public SlashCommandOptionBuilder WithRequired(bool value) + { + IsRequired = value; + return this; + } + + /// + /// Sets the current builders default field. + /// + /// The value to set. + /// The current builder. + public SlashCommandOptionBuilder WithDefault(bool value) + { + IsDefault = value; + return this; + } + + /// + /// Sets the current builders autocomplete field. + /// + /// The value to set. + /// The current builder. + public SlashCommandOptionBuilder WithAutocomplete(bool value) + { + IsAutocomplete = value; + return this; + } + + /// + /// Sets the current builders min value field. + /// + /// The value to set. + /// The current builder. + public SlashCommandOptionBuilder WithMinValue(double value) + { + MinValue = value; + return this; + } + + /// + /// Sets the current builders max value field. + /// + /// The value to set. + /// The current builder. + public SlashCommandOptionBuilder WithMaxValue(double value) + { + MaxValue = value; + return this; + } + + /// + /// Sets the current type of this builder. + /// + /// The type to set. + /// The current builder. + public SlashCommandOptionBuilder WithType(ApplicationCommandOptionType type) + { + Type = type; + return this; + } + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/SlashCommands/SlashCommandProperties.cs b/src/Discord.Net.Core/Entities/Interactions/SlashCommands/SlashCommandProperties.cs new file mode 100644 index 000000000..20ba2868f --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/SlashCommands/SlashCommandProperties.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; + +namespace Discord +{ + /// + /// Represents a class used to create slash commands. + /// + public class SlashCommandProperties : ApplicationCommandProperties + { + internal override ApplicationCommandType Type => ApplicationCommandType.Slash; + + /// + /// Gets or sets the discription of this command. + /// + public Optional Description { get; set; } + + /// + /// Gets or sets the options for this command. + /// + public Optional> Options { get; set; } + + internal SlashCommandProperties() { } + } +} diff --git a/src/Discord.Net.Core/Entities/Invites/TargetUserType.cs b/src/Discord.Net.Core/Entities/Invites/TargetUserType.cs index 74263b888..e1818d7a9 100644 --- a/src/Discord.Net.Core/Entities/Invites/TargetUserType.cs +++ b/src/Discord.Net.Core/Entities/Invites/TargetUserType.cs @@ -9,6 +9,10 @@ namespace Discord /// /// The invite is for a Go Live stream. /// - Stream = 1 + Stream = 1, + /// + /// The invite is for embedded application. + /// + EmbeddedApplication = 2 } } diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs index 89aaf5fde..0304120f5 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using Discord.Utils; namespace Discord { @@ -16,20 +17,20 @@ namespace Discord private EmbedThumbnail? _thumbnail; private List _fields; - /// - /// Returns the maximum number of fields allowed by Discord. + /// + /// Returns the maximum number of fields allowed by Discord. /// public const int MaxFieldCount = 25; - /// - /// Returns the maximum length of title allowed by Discord. + /// + /// Returns the maximum length of title allowed by Discord. /// public const int MaxTitleLength = 256; - /// - /// Returns the maximum length of description allowed by Discord. + /// + /// Returns the maximum length of description allowed by Discord. /// public const int MaxDescriptionLength = 4096; - /// - /// Returns the maximum length of total characters allowed by Discord. + /// + /// Returns the maximum length of total characters allowed by Discord. /// public const int MaxEmbedLength = 6000; @@ -88,9 +89,9 @@ namespace Discord } /// Gets or sets the list of of an . - /// An embed builder's fields collection is set to + /// An embed builder's fields collection is set to /// null. - /// Description length exceeds . + /// Fields count exceeds . /// /// The list of existing . public List Fields @@ -137,7 +138,7 @@ namespace Discord /// Gets the total length of all embed properties. /// /// - /// The combined length of , , , + /// The combined length of , , , /// , , and . /// public int Length @@ -166,7 +167,7 @@ namespace Discord Title = title; return this; } - /// + /// /// Sets the description of an . /// /// The description to be set. @@ -178,7 +179,7 @@ namespace Discord Description = description; return this; } - /// + /// /// Sets the URL of an . /// /// The URL to be set. @@ -190,7 +191,7 @@ namespace Discord Url = url; return this; } - /// + /// /// Sets the thumbnail URL of an . /// /// The thumbnail URL to be set. @@ -401,11 +402,29 @@ namespace Discord /// The built embed object. /// /// Total embed length exceeds . + /// Any Url must include its protocols (i.e http:// or https://). public Embed Build() { if (Length > MaxEmbedLength) throw new InvalidOperationException($"Total embed length must be less than or equal to {MaxEmbedLength}."); - + if (!string.IsNullOrEmpty(Url)) + UrlValidation.Validate(Url, true); + if (!string.IsNullOrEmpty(ThumbnailUrl)) + UrlValidation.Validate(ThumbnailUrl, true); + if (!string.IsNullOrEmpty(ImageUrl)) + UrlValidation.Validate(ImageUrl, true); + if (Author != null) + { + if (!string.IsNullOrEmpty(Author.Url)) + UrlValidation.Validate(Author.Url, true); + if (!string.IsNullOrEmpty(Author.IconUrl)) + UrlValidation.Validate(Author.IconUrl, true); + } + if(Footer != null) + { + if (!string.IsNullOrEmpty(Footer.IconUrl)) + UrlValidation.Validate(Footer.IconUrl, true); + } var fields = ImmutableArray.CreateBuilder(Fields.Count); for (int i = 0; i < Fields.Count; i++) fields.Add(Fields[i].Build()); diff --git a/src/Discord.Net.Core/Entities/Messages/FileAttachment.cs b/src/Discord.Net.Core/Entities/Messages/FileAttachment.cs new file mode 100644 index 000000000..dc5437861 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Messages/FileAttachment.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord +{ + public struct FileAttachment : IDisposable + { + public string FileName { get; set; } + public string Description { get; set; } + public bool IsSpoiler { get; set; } + +#pragma warning disable IDISP008 + public Stream Stream { get; } +#pragma warning restore IDISP008 + + private bool _isDisposed; + + /// + /// Creates a file attachment from a stream. + /// + /// The stream to create the attachment from. + /// The name of the attachment. + /// The description of the attachment. + public FileAttachment(Stream stream, string fileName, string description = null, bool isSpoiler = false) + { + _isDisposed = false; + FileName = fileName; + Description = description; + Stream = stream; + IsSpoiler = isSpoiler; + } + + /// + /// Create the file attachment from a file path. + /// + /// + /// This file path is NOT validated and is passed directly into a + /// . + /// + /// The path to the file. + /// + /// is a zero-length string, contains only white space, or contains one or more invalid + /// characters as defined by . + /// + /// is null. + /// + /// The specified path, file name, or both exceed the system-defined maximum length. For example, on + /// Windows-based platforms, paths must be less than 248 characters, and file names must be less than 260 + /// characters. + /// + /// is in an invalid format. + /// + /// The specified is invalid, (for example, it is on an unmapped drive). + /// + /// + /// specified a directory.-or- The caller does not have the required permission. + /// + /// The file specified in was not found. + /// + /// An I/O error occurred while opening the file. + public FileAttachment(string path, string description = null, bool isSpoiler = false) + { + _isDisposed = false; + Stream = File.OpenRead(path); + FileName = Path.GetFileName(path); + Description = description; + IsSpoiler = isSpoiler; + } + + public void Dispose() + { + if (!_isDisposed) + { + Stream?.Dispose(); + _isDisposed = true; + } + } + } +} diff --git a/src/Discord.Net.Core/Entities/Messages/IAttachment.cs b/src/Discord.Net.Core/Entities/Messages/IAttachment.cs index 655777998..e94e9f97c 100644 --- a/src/Discord.Net.Core/Entities/Messages/IAttachment.cs +++ b/src/Discord.Net.Core/Entities/Messages/IAttachment.cs @@ -55,5 +55,12 @@ namespace Discord /// The width of this attachment if it is a picture; otherwise null. /// int? Width { get; } + /// + /// Gets whether or not this attachment is ephemeral. + /// + /// + /// if the attachment is ephemeral; otherwise . + /// + bool Ephemeral { get; } } } diff --git a/src/Discord.Net.Core/Entities/Messages/IMessage.cs b/src/Discord.Net.Core/Entities/Messages/IMessage.cs index b5023eb59..f5f2ca007 100644 --- a/src/Discord.Net.Core/Entities/Messages/IMessage.cs +++ b/src/Discord.Net.Core/Entities/Messages/IMessage.cs @@ -53,6 +53,13 @@ namespace Discord /// string Content { get; } /// + /// Gets the clean content for this message. + /// + /// + /// A string that contains the body of the message stripped of mentions, markdown, emojis and pings; note that this field may be empty if there is an embed. + /// + string CleanContent { get; } + /// /// Gets the time this message was sent. /// /// @@ -165,12 +172,17 @@ namespace Discord IReadOnlyDictionary Reactions { get; } /// - /// Gets all stickers included in this message. + /// The 's attached to this message + /// + IReadOnlyCollection Components { get; } + + /// + /// Gets all stickers items included in this message. /// /// - /// A read-only collection of sticker objects. + /// A read-only collection of sticker item objects. /// - IReadOnlyCollection Stickers { get; } + IReadOnlyCollection Stickers { get; } /// /// Gets the flags related to this message. @@ -184,6 +196,14 @@ namespace Discord MessageFlags? Flags { get; } /// + /// Gets the interaction this message is a response to. + /// + /// + /// A if the message is a response to an interaction; otherwise . + /// + IMessageInteraction Interaction { get; } + + /// /// Adds a reaction to this message. /// /// diff --git a/src/Discord.Net.Core/Entities/Messages/IMessageInteraction.cs b/src/Discord.Net.Core/Entities/Messages/IMessageInteraction.cs new file mode 100644 index 000000000..ebd03b627 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Messages/IMessageInteraction.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord +{ + /// + /// Represents a partial within a message. + /// + public interface IMessageInteraction + { + /// + /// Gets the snowflake id of the interaction. + /// + ulong Id { get; } + + /// + /// Gets the type of the interaction. + /// + InteractionType Type { get; } + + /// + /// Gets the name of the application command used. + /// + string Name { get; } + + /// + /// Gets the who invoked the interaction. + /// + IUser User { get; } + } +} diff --git a/src/Discord.Net.Core/Entities/Messages/MessageFlags.cs b/src/Discord.Net.Core/Entities/Messages/MessageFlags.cs index 52d0f0e9e..6f9450372 100644 --- a/src/Discord.Net.Core/Entities/Messages/MessageFlags.cs +++ b/src/Discord.Net.Core/Entities/Messages/MessageFlags.cs @@ -32,5 +32,17 @@ namespace Discord /// Flag given to messages that came from the urgent message system. /// Urgent = 1 << 4, + /// + /// Flag given to messages has an associated thread, with the same id as the message + /// + HasThread = 1 << 5, + /// + /// Flag given to messages that is only visible to the user who invoked the Interaction. + /// + Ephemeral = 1 << 6, + /// + /// Flag given to messages that is an Interaction Response and the bot is "thinking" + /// + Loading = 1 << 7 } } diff --git a/src/Discord.Net.Core/Entities/Messages/MessageInteraction.cs b/src/Discord.Net.Core/Entities/Messages/MessageInteraction.cs new file mode 100644 index 000000000..cbbebd932 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Messages/MessageInteraction.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord +{ + /// + /// Represents a partial within a message. + /// + /// The type of the user. + public class MessageInteraction : IMessageInteraction where TUser : IUser + { + /// + /// Gets the snowflake id of the interaction. + /// + public ulong Id { get; } + + /// + /// Gets the type of the interaction. + /// + public InteractionType Type { get; } + + /// + /// Gets the name of the application command used. + /// + public string Name { get; } + + /// + /// Gets the who invoked the interaction. + /// + public TUser User { get; } + + internal MessageInteraction(ulong id, InteractionType type, string name, TUser user) + { + Id = id; + Type = type; + Name = name; + User = user; + } + + IUser IMessageInteraction.User => User; + } +} diff --git a/src/Discord.Net.Core/Entities/Messages/MessageProperties.cs b/src/Discord.Net.Core/Entities/Messages/MessageProperties.cs index 9504e04cb..1a4eaff2d 100644 --- a/src/Discord.Net.Core/Entities/Messages/MessageProperties.cs +++ b/src/Discord.Net.Core/Entities/Messages/MessageProperties.cs @@ -1,10 +1,12 @@ +using System.Collections.Generic; + namespace Discord { /// /// Properties that are used to modify an with the specified changes. /// /// - /// The content of a message can be cleared with if and only if an + /// The content of a message can be cleared with if and only if an /// is present. /// /// @@ -17,10 +19,25 @@ namespace Discord /// This must be less than the constant defined by . /// public Optional Content { get; set; } + /// - /// Gets or sets the embed the message should display. + /// Gets or sets a single embed for this message. /// + /// + /// This property will be added to the array, in the future please use the array rather than this property. + /// public Optional Embed { get; set; } + + /// + /// Gets or sets the embeds of the message. + /// + public Optional Embeds { get; set; } + + /// + /// Gets or sets the components for this message. + /// + public Optional Components { get; set; } + /// /// Gets or sets the flags of the message. /// @@ -33,5 +50,10 @@ namespace Discord /// Gets or sets the allowed mentions of the message. /// public Optional AllowedMentions { get; set; } + + /// + /// Gets or sets the attachments for the message. + /// + public Optional> Attachments { get; set; } } } diff --git a/src/Discord.Net.Core/Entities/Messages/MessageType.cs b/src/Discord.Net.Core/Entities/Messages/MessageType.cs index bfe763cad..b83f88434 100644 --- a/src/Discord.Net.Core/Entities/Messages/MessageType.cs +++ b/src/Discord.Net.Core/Entities/Messages/MessageType.cs @@ -58,8 +58,53 @@ namespace Discord /// ChannelFollowAdd = 12, /// + /// The message for when a guild is disqualified from discovery. + /// + GuildDiscoveryDisqualified = 14, + /// + /// The message for when a guild is requalified for discovery. + /// + GuildDiscoveryRequalified = 15, + /// + /// The message for when the initial warning is sent for the initial grace period discovery. + /// + GuildDiscoveryGracePeriodInitialWarning = 16, + /// + /// The message for when the final warning is sent for the initial grace period discovery. + /// + GuildDiscoveryGracePeriodFinalWarning = 17, + /// + /// The message for when a thread is created. + /// + ThreadCreated = 18, + /// /// The message is an inline reply. /// + /// + /// Only available in API v8. + /// Reply = 19, + /// + /// The message is an Application Command. + /// + /// + /// Only available in API v8. + /// + ApplicationCommand = 20, + /// + /// The message that starts a thread. + /// + /// + /// Only available in API v9. + /// + ThreadStarterMessage = 21, + /// + /// The message for a invite reminder. + /// + GuildInviteReminder = 22, + /// + /// The message for a context menu command. + /// + ContextMenuCommand = 23, } } diff --git a/src/Discord.Net.Core/Entities/Messages/StickerFormatType.cs b/src/Discord.Net.Core/Entities/Messages/StickerFormatType.cs new file mode 100644 index 000000000..82e6b15a4 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Messages/StickerFormatType.cs @@ -0,0 +1,25 @@ +namespace Discord +{ + /// + /// Defines the types of formats for stickers. + /// + public enum StickerFormatType + { + /// + /// Default value for a sticker format type. + /// + None = 0, + /// + /// The sticker format type is png. + /// + Png = 1, + /// + /// The sticker format type is apng. + /// + Apng = 2, + /// + /// The sticker format type is lottie. + /// + Lottie = 3 + } +} diff --git a/src/Discord.Net.Core/Entities/Messages/SticketFormatType.cs b/src/Discord.Net.Core/Entities/Messages/SticketFormatType.cs deleted file mode 100644 index d24a38534..000000000 --- a/src/Discord.Net.Core/Entities/Messages/SticketFormatType.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Discord -{ - /// Defines the types of formats for stickers. - public enum StickerFormatType - { - /// Default value for a sticker format type. - None = 0, - /// The sticker format type is png. - Png = 1, - /// The sticker format type is apng. - Apng = 2, - /// The sticker format type is lottie. - Lottie = 3, - } -} diff --git a/src/Discord.Net.Core/Entities/Messages/TimestampTag.cs b/src/Discord.Net.Core/Entities/Messages/TimestampTag.cs new file mode 100644 index 000000000..347b0daaa --- /dev/null +++ b/src/Discord.Net.Core/Entities/Messages/TimestampTag.cs @@ -0,0 +1,47 @@ +using System; + +namespace Discord +{ + /// + /// Represents a class used to make timestamps in messages. see . + /// + public class TimestampTag + { + /// + /// Gets or sets the style of the timestamp tag. + /// + public TimestampTagStyles Style { get; set; } = TimestampTagStyles.ShortDateTime; + + /// + /// Gets or sets the time for this timestamp tag. + /// + public DateTime Time { get; set; } + + /// + /// Converts the current timestamp tag to the string representation supported by discord. + /// + /// If the is null then the default 0 will be used. + /// + /// + /// A string that is compatible in a discord message, ex: <t:1625944201:f> + public override string ToString() + { + return $""; + } + + /// + /// Creates a new timestamp tag with the specified datetime object. + /// + /// The time of this timestamp tag. + /// The style for this timestamp tag. + /// The newly create timestamp tag. + public static TimestampTag FromDateTime(DateTime time, TimestampTagStyles style = TimestampTagStyles.ShortDateTime) + { + return new TimestampTag + { + Style = style, + Time = time + }; + } + } +} diff --git a/src/Discord.Net.Core/Entities/Messages/TimestampTagStyle.cs b/src/Discord.Net.Core/Entities/Messages/TimestampTagStyle.cs new file mode 100644 index 000000000..89f3c79b5 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Messages/TimestampTagStyle.cs @@ -0,0 +1,43 @@ +namespace Discord +{ + /// + /// Represents a set of styles to use with a + /// + public enum TimestampTagStyles + { + /// + /// A short time string: 16:20 + /// + ShortTime = 116, + + /// + /// A long time string: 16:20:30 + /// + LongTime = 84, + + /// + /// A short date string: 20/04/2021 + /// + ShortDate = 100, + + /// + /// A long date string: 20 April 2021 + /// + LongDate = 68, + + /// + /// A short datetime string: 20 April 2021 16:20 + /// + ShortDateTime = 102, + + /// + /// A long datetime string: Tuesday, 20 April 2021 16:20 + /// + LongDateTime = 70, + + /// + /// The relative time to the user: 2 months ago + /// + Relative = 82 + } +} diff --git a/src/Discord.Net.Core/Entities/Permissions/ApplicationCommandPermissionTarget.cs b/src/Discord.Net.Core/Entities/Permissions/ApplicationCommandPermissionTarget.cs new file mode 100644 index 000000000..9a99b34f1 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Permissions/ApplicationCommandPermissionTarget.cs @@ -0,0 +1,17 @@ +namespace Discord +{ + /// + /// Specifies the target of the permission. + /// + public enum ApplicationCommandPermissionTarget + { + /// + /// The target of the permission is a role. + /// + Role = 1, + /// + /// The target of the permission is a user. + /// + User = 2 + } +} diff --git a/src/Discord.Net.Core/Entities/Permissions/ApplicationCommandPermissions.cs b/src/Discord.Net.Core/Entities/Permissions/ApplicationCommandPermissions.cs new file mode 100644 index 000000000..28a6455e2 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Permissions/ApplicationCommandPermissions.cs @@ -0,0 +1,62 @@ +namespace Discord +{ + /// + /// Application command permissions allow you to enable or disable commands for specific users or roles within a guild. + /// + public class ApplicationCommandPermission + { + /// + /// The id of the role or user. + /// + public ulong TargetId { get; } + + /// + /// The target of this permission. + /// + public ApplicationCommandPermissionTarget TargetType { get; } + + /// + /// to allow, otherwise . + /// + public bool Permission { get; } + + internal ApplicationCommandPermission() { } + + /// + /// Creates a new . + /// + /// The id you want to target this permission value for. + /// The type of the targetId parameter. + /// The value of this permission. + public ApplicationCommandPermission(ulong targetId, ApplicationCommandPermissionTarget targetType, bool allow) + { + TargetId = targetId; + TargetType = targetType; + Permission = allow; + } + + /// + /// Creates a new targeting . + /// + /// The user you want to target this permission value for. + /// The value of this permission. + public ApplicationCommandPermission(IUser target, bool allow) + { + TargetId = target.Id; + Permission = allow; + TargetType = ApplicationCommandPermissionTarget.User; + } + + /// + /// Creates a new targeting . + /// + /// The role you want to target this permission value for. + /// The value of this permission. + public ApplicationCommandPermission(IRole target, bool allow) + { + TargetId = target.Id; + Permission = allow; + TargetType = ApplicationCommandPermissionTarget.Role; + } + } +} diff --git a/src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs b/src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs index bf08887bd..45e24b7fa 100644 --- a/src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs +++ b/src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs @@ -10,94 +10,141 @@ namespace Discord /// /// Allows creation of instant invites. /// - CreateInstantInvite = 0x00_00_00_01, + CreateInstantInvite = 0x00_00_00_00_01, /// /// Allows management and editing of channels. /// - ManageChannels = 0x00_00_00_10, + ManageChannels = 0x00_00_00_00_10, // Text /// /// Allows for the addition of reactions to messages. /// - AddReactions = 0x00_00_00_40, + AddReactions = 0x00_00_00_00_40, /// /// Allows guild members to view a channel, which includes reading messages in text channels. /// - ViewChannel = 0x00_00_04_00, + ViewChannel = 0x00_00_00_04_00, /// /// Allows for sending messages in a channel. /// - SendMessages = 0x00_00_08_00, + SendMessages = 0x00_00_00_08_00, /// /// Allows for sending of text-to-speech messages. /// - SendTTSMessages = 0x00_00_10_00, + SendTTSMessages = 0x00_00_00_10_00, /// /// Allows for deletion of other users messages. /// - ManageMessages = 0x00_00_20_00, + ManageMessages = 0x00_00_00_20_00, /// /// Allows links sent by users with this permission will be auto-embedded. /// - EmbedLinks = 0x00_00_40_00, + EmbedLinks = 0x00_00_00_40_00, /// /// Allows for uploading images and files. /// - AttachFiles = 0x00_00_80_00, + AttachFiles = 0x00_00_00_80_00, /// /// Allows for reading of message history. /// - ReadMessageHistory = 0x00_01_00_00, + ReadMessageHistory = 0x00_00_01_00_00, /// /// Allows for using the @everyone tag to notify all users in a channel, and the @here tag to notify all /// online users in a channel. /// - MentionEveryone = 0x00_02_00_00, + MentionEveryone = 0x00_00_02_00_00, /// /// Allows the usage of custom emojis from other servers. /// - UseExternalEmojis = 0x00_04_00_00, + UseExternalEmojis = 0x00_00_04_00_00, // Voice /// /// Allows for joining of a voice channel. /// - Connect = 0x00_10_00_00, + Connect = 0x00_00_10_00_00, /// /// Allows for speaking in a voice channel. /// - Speak = 0x00_20_00_00, + Speak = 0x00_00_20_00_00, /// /// Allows for muting members in a voice channel. /// - MuteMembers = 0x00_40_00_00, + MuteMembers = 0x00_00_40_00_00, /// /// Allows for deafening of members in a voice channel. /// - DeafenMembers = 0x00_80_00_00, + DeafenMembers = 0x00_00_80_00_00, /// /// Allows for moving of members between voice channels. /// - MoveMembers = 0x01_00_00_00, + MoveMembers = 0x00_01_00_00_00, /// /// Allows for using voice-activity-detection in a voice channel. /// - UseVAD = 0x02_00_00_00, - PrioritySpeaker = 0x00_00_01_00, + UseVAD = 0x00_02_00_00_00, + + /// + /// Allows for using priority speaker in a voice channel. + /// + PrioritySpeaker = 0x00_00_00_01_00, + /// /// Allows video streaming in a voice channel. /// - Stream = 0x00_00_02_00, + Stream = 0x00_00_00_02_00, // More General /// /// Allows management and editing of roles. /// - ManageRoles = 0x10_00_00_00, + ManageRoles = 0x00_10_00_00_00, /// /// Allows management and editing of webhooks. /// - ManageWebhooks = 0x20_00_00_00, + ManageWebhooks = 0x00_20_00_00_00, + + /// + /// Allows management and editing of emojis. + /// + ManageEmojis = 0x00_40_00_00_00, + + /// + /// Allows members to use slash commands in text channels. + /// + UseApplicationCommands = 0x00_80_00_00_00, + + /// + /// Allows for requesting to speak in stage channels. (This permission is under active development and may be changed or removed.) + /// + RequestToSpeak = 0x01_00_00_00_00, + + /// + /// Allows for deleting and archiving threads, and viewing all private threads + /// + ManageThreads = 0x04_00_00_00_00, + + /// + /// Allows for creating public threads. + /// + CreatePublicThreads = 0x08_00_00_00_00, + /// + /// Allows for creating private threads. + /// + CreatePrivateThreads = 0x10_00_00_00_00, + /// + /// Allows the usage of custom stickers from other servers. + /// + UseExternalStickers = 0x20_00_00_00_00, + /// + /// Allows for sending messages in threads. + /// + SendMessagesInThreads = 0x40_00_00_00_00, + /// + /// Allows for launching activities (applications with the EMBEDDED flag) in a voice channel. + /// + StartEmbeddedActivities = 0x80_00_00_00_00 + } } diff --git a/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs b/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs index d774cc51d..ee5c9984a 100644 --- a/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs @@ -7,87 +7,106 @@ namespace Discord [DebuggerDisplay("{DebuggerDisplay,nq}")] public struct ChannelPermissions { - /// Gets a blank that grants no permissions. - /// A structure that does not contain any set permissions. + /// Gets a blank that grants no permissions. + /// A structure that does not contain any set permissions. public static readonly ChannelPermissions None = new ChannelPermissions(); - /// Gets a that grants all permissions for text channels. - public static readonly ChannelPermissions Text = new ChannelPermissions(0b01100_0000000_1111111110001_010001); - /// Gets a that grants all permissions for voice channels. - public static readonly ChannelPermissions Voice = new ChannelPermissions(0b00100_1111110_0000000011100_010001); - /// Gets a that grants all permissions for category channels. + /// Gets a that grants all permissions for text channels. + public static readonly ChannelPermissions Text = new ChannelPermissions(0b0_11111_0101100_0000000_1111111110001_010001); + /// Gets a that grants all permissions for voice channels. + public static readonly ChannelPermissions Voice = new ChannelPermissions(0b1_00000_0000100_1111110_0000000011100_010001); + /// Gets a that grants all permissions for stage channels. + public static readonly ChannelPermissions Stage = new ChannelPermissions(0b0_00000_1000100_0111010_0000000010000_010001); + /// Gets a that grants all permissions for category channels. public static readonly ChannelPermissions Category = new ChannelPermissions(0b01100_1111110_1111111110001_010001); - /// Gets a that grants all permissions for direct message channels. + /// Gets a that grants all permissions for direct message channels. public static readonly ChannelPermissions DM = new ChannelPermissions(0b00000_1000110_1011100110001_000000); - /// Gets a that grants all permissions for group channels. + /// Gets a that grants all permissions for group channels. public static readonly ChannelPermissions Group = new ChannelPermissions(0b00000_1000110_0001101100000_000000); - /// Gets a that grants all permissions for a given channel type. + /// Gets a that grants all permissions for a given channel type. /// Unknown channel type. public static ChannelPermissions All(IChannel channel) { - switch (channel) + return channel switch { - case ITextChannel _: return Text; - case IVoiceChannel _: return Voice; - case ICategoryChannel _: return Category; - case IDMChannel _: return DM; - case IGroupChannel _: return Group; - default: throw new ArgumentException(message: "Unknown channel type.", paramName: nameof(channel)); - } + ITextChannel _ => Text, + IStageChannel _ => Stage, + IVoiceChannel _ => Voice, + ICategoryChannel _ => Category, + IDMChannel _ => DM, + IGroupChannel _ => Group, + _ => throw new ArgumentException(message: "Unknown channel type.", paramName: nameof(channel)), + }; } - /// Gets a packed value representing all the permissions in this . + /// Gets a packed value representing all the permissions in this . public ulong RawValue { get; } - /// If true, a user may create invites. + /// If true, a user may create invites. public bool CreateInstantInvite => Permissions.GetValue(RawValue, ChannelPermission.CreateInstantInvite); - /// If true, a user may create, delete and modify this channel. + /// If true, a user may create, delete and modify this channel. public bool ManageChannel => Permissions.GetValue(RawValue, ChannelPermission.ManageChannels); - /// If true, a user may add reactions. + /// If true, a user may add reactions. public bool AddReactions => Permissions.GetValue(RawValue, ChannelPermission.AddReactions); - /// If true, a user may view channels. + /// If true, a user may view channels. public bool ViewChannel => Permissions.GetValue(RawValue, ChannelPermission.ViewChannel); - /// If true, a user may send messages. + /// If true, a user may send messages. public bool SendMessages => Permissions.GetValue(RawValue, ChannelPermission.SendMessages); - /// If true, a user may send text-to-speech messages. + /// If true, a user may send text-to-speech messages. public bool SendTTSMessages => Permissions.GetValue(RawValue, ChannelPermission.SendTTSMessages); - /// If true, a user may delete messages. + /// If true, a user may delete messages. public bool ManageMessages => Permissions.GetValue(RawValue, ChannelPermission.ManageMessages); - /// If true, Discord will auto-embed links sent by this user. + /// If true, Discord will auto-embed links sent by this user. public bool EmbedLinks => Permissions.GetValue(RawValue, ChannelPermission.EmbedLinks); - /// If true, a user may send files. + /// If true, a user may send files. public bool AttachFiles => Permissions.GetValue(RawValue, ChannelPermission.AttachFiles); - /// If true, a user may read previous messages. + /// If true, a user may read previous messages. public bool ReadMessageHistory => Permissions.GetValue(RawValue, ChannelPermission.ReadMessageHistory); - /// If true, a user may mention @everyone. + /// If true, a user may mention @everyone. public bool MentionEveryone => Permissions.GetValue(RawValue, ChannelPermission.MentionEveryone); - /// If true, a user may use custom emoji from other guilds. + /// If true, a user may use custom emoji from other guilds. public bool UseExternalEmojis => Permissions.GetValue(RawValue, ChannelPermission.UseExternalEmojis); - /// If true, a user may connect to a voice channel. + /// If true, a user may connect to a voice channel. public bool Connect => Permissions.GetValue(RawValue, ChannelPermission.Connect); - /// If true, a user may speak in a voice channel. + /// If true, a user may speak in a voice channel. public bool Speak => Permissions.GetValue(RawValue, ChannelPermission.Speak); - /// If true, a user may mute users. + /// If true, a user may mute users. public bool MuteMembers => Permissions.GetValue(RawValue, ChannelPermission.MuteMembers); - /// If true, a user may deafen users. + /// If true, a user may deafen users. public bool DeafenMembers => Permissions.GetValue(RawValue, ChannelPermission.DeafenMembers); - /// If true, a user may move other users between voice channels. + /// If true, a user may move other users between voice channels. public bool MoveMembers => Permissions.GetValue(RawValue, ChannelPermission.MoveMembers); - /// If true, a user may use voice-activity-detection rather than push-to-talk. + /// If true, a user may use voice-activity-detection rather than push-to-talk. public bool UseVAD => Permissions.GetValue(RawValue, ChannelPermission.UseVAD); - /// If true, a user may use priority speaker in a voice channel. + /// If true, a user may use priority speaker in a voice channel. public bool PrioritySpeaker => Permissions.GetValue(RawValue, ChannelPermission.PrioritySpeaker); - /// If true, a user may stream video in a voice channel. + /// If true, a user may stream video in a voice channel. public bool Stream => Permissions.GetValue(RawValue, ChannelPermission.Stream); - /// If true, a user may adjust role permissions. This also implictly grants all other permissions. + /// If true, a user may adjust role permissions. This also implicitly grants all other permissions. public bool ManageRoles => Permissions.GetValue(RawValue, ChannelPermission.ManageRoles); - /// If true, a user may edit the webhooks for this channel. + /// If true, a user may edit the webhooks for this channel. public bool ManageWebhooks => Permissions.GetValue(RawValue, ChannelPermission.ManageWebhooks); + /// If true, a user may use application commands in this guild. + public bool UseApplicationCommands => Permissions.GetValue(RawValue, ChannelPermission.UseApplicationCommands); + /// If true, a user may request to speak in stage channels. + public bool RequestToSpeak => Permissions.GetValue(RawValue, ChannelPermission.RequestToSpeak); + /// If true, a user may manage threads in this guild. + public bool ManageThreads => Permissions.GetValue(RawValue, ChannelPermission.ManageThreads); + /// If true, a user may create public threads in this guild. + public bool CreatePublicThreads => Permissions.GetValue(RawValue, ChannelPermission.CreatePublicThreads); + /// If true, a user may create private threads in this guild. + public bool CreatePrivateThreads => Permissions.GetValue(RawValue, ChannelPermission.CreatePrivateThreads); + /// If true, a user may use external stickers in this guild. + public bool UseExternalStickers => Permissions.GetValue(RawValue, ChannelPermission.UseExternalStickers); + /// If true, a user may send messages in threads in this guild. + public bool SendMessagesInThreads => Permissions.GetValue(RawValue, ChannelPermission.SendMessagesInThreads); + /// If true, a user launch application activities in voice channels in this guild. + public bool StartEmbeddedActivities => Permissions.GetValue(RawValue, ChannelPermission.StartEmbeddedActivities); - /// Creates a new with the provided packed value. + /// Creates a new with the provided packed value. public ChannelPermissions(ulong rawValue) { RawValue = rawValue; } private ChannelPermissions(ulong initialValue, @@ -112,7 +131,15 @@ namespace Discord bool? prioritySpeaker = null, bool? stream = null, bool? manageRoles = null, - bool? manageWebhooks = null) + bool? manageWebhooks = null, + bool? useApplicationCommands = null, + bool? requestToSpeak = null, + bool? manageThreads = null, + bool? createPublicThreads = null, + bool? createPrivateThreads = null, + bool? useExternalStickers = null, + bool? sendMessagesInThreads = null, + bool? startEmbeddedActivities = null) { ulong value = initialValue; @@ -138,11 +165,19 @@ namespace Discord Permissions.SetValue(ref value, stream, ChannelPermission.Stream); Permissions.SetValue(ref value, manageRoles, ChannelPermission.ManageRoles); Permissions.SetValue(ref value, manageWebhooks, ChannelPermission.ManageWebhooks); + Permissions.SetValue(ref value, useApplicationCommands, ChannelPermission.UseApplicationCommands); + Permissions.SetValue(ref value, requestToSpeak, ChannelPermission.RequestToSpeak); + Permissions.SetValue(ref value, manageThreads, ChannelPermission.ManageThreads); + Permissions.SetValue(ref value, createPublicThreads, ChannelPermission.CreatePublicThreads); + Permissions.SetValue(ref value, createPrivateThreads, ChannelPermission.CreatePrivateThreads); + Permissions.SetValue(ref value, useExternalStickers, ChannelPermission.UseExternalStickers); + Permissions.SetValue(ref value, sendMessagesInThreads, ChannelPermission.SendMessagesInThreads); + Permissions.SetValue(ref value, startEmbeddedActivities, ChannelPermission.StartEmbeddedActivities); RawValue = value; } - /// Creates a new with the provided permissions. + /// Creates a new with the provided permissions. public ChannelPermissions( bool createInstantInvite = false, bool manageChannel = false, @@ -165,13 +200,23 @@ namespace Discord bool prioritySpeaker = false, bool stream = false, bool manageRoles = false, - bool manageWebhooks = false) + bool manageWebhooks = false, + bool useApplicationCommands = false, + bool requestToSpeak = false, + bool manageThreads = false, + bool createPublicThreads = false, + bool createPrivateThreads = false, + bool useExternalStickers = false, + bool sendMessagesInThreads = false, + bool startEmbeddedActivities = false) : this(0, createInstantInvite, manageChannel, addReactions, viewChannel, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, - speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, prioritySpeaker, stream, manageRoles, manageWebhooks) + speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, prioritySpeaker, stream, manageRoles, manageWebhooks, + useApplicationCommands, requestToSpeak, manageThreads, createPublicThreads, createPrivateThreads, useExternalStickers, sendMessagesInThreads, + startEmbeddedActivities) { } - /// Creates a new from this one, changing the provided non-null permissions. + /// Creates a new from this one, changing the provided non-null permissions. public ChannelPermissions Modify( bool? createInstantInvite = null, bool? manageChannel = null, @@ -194,7 +239,15 @@ namespace Discord bool? prioritySpeaker = null, bool? stream = null, bool? manageRoles = null, - bool? manageWebhooks = null) + bool? manageWebhooks = null, + bool? useApplicationCommands = null, + bool? requestToSpeak = null, + bool? manageThreads = null, + bool? createPublicThreads = null, + bool? createPrivateThreads = null, + bool? useExternalStickers = null, + bool? sendMessagesInThreads = null, + bool? startEmbeddedActivities = null) => new ChannelPermissions(RawValue, createInstantInvite, manageChannel, @@ -217,7 +270,15 @@ namespace Discord prioritySpeaker, stream, manageRoles, - manageWebhooks); + manageWebhooks, + useApplicationCommands, + requestToSpeak, + manageThreads, + createPublicThreads, + createPrivateThreads, + useExternalStickers, + sendMessagesInThreads, + startEmbeddedActivities); public bool Has(ChannelPermission permission) => Permissions.GetValue(RawValue, permission); diff --git a/src/Discord.Net.Core/Entities/Permissions/GuildApplicationCommandPermissions.cs b/src/Discord.Net.Core/Entities/Permissions/GuildApplicationCommandPermissions.cs new file mode 100644 index 000000000..e738fec4c --- /dev/null +++ b/src/Discord.Net.Core/Entities/Permissions/GuildApplicationCommandPermissions.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; + +namespace Discord +{ + /// + /// Returned when fetching the permissions for a command in a guild. + /// + public class GuildApplicationCommandPermission + + { + /// + /// The id of the command. + /// + public ulong CommandId { get; } + + /// + /// The id of the application the command belongs to. + /// + public ulong ApplicationId { get; } + + /// + /// The id of the guild. + /// + public ulong GuildId { get; } + + /// + /// The permissions for the command in the guild. + /// + public IReadOnlyCollection Permissions { get; } + + internal GuildApplicationCommandPermission(ulong commandId, ulong appId, ulong guildId, ApplicationCommandPermission[] permissions) + { + CommandId = commandId; + ApplicationId = appId; + GuildId = guildId; + Permissions = permissions; + } + } +} diff --git a/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs b/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs index 31bd6164a..5a5827c1d 100644 --- a/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs +++ b/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs @@ -10,7 +10,7 @@ namespace Discord /// /// Allows creation of instant invites. /// - CreateInstantInvite = 0x00_00_00_01, + CreateInstantInvite = 0x00_00_00_01, /// /// Allows kicking members. /// @@ -18,7 +18,7 @@ namespace Discord /// This permission requires the owner account to use two-factor /// authentication when used on a guild that has server-wide 2FA enabled. /// - KickMembers = 0x00_00_00_02, + KickMembers = 0x00_00_00_02, /// /// Allows banning members. /// @@ -26,7 +26,7 @@ namespace Discord /// This permission requires the owner account to use two-factor /// authentication when used on a guild that has server-wide 2FA enabled. /// - BanMembers = 0x00_00_00_04, + BanMembers = 0x00_00_00_04, /// /// Allows all permissions and bypasses channel permission overwrites. /// @@ -34,7 +34,7 @@ namespace Discord /// This permission requires the owner account to use two-factor /// authentication when used on a guild that has server-wide 2FA enabled. /// - Administrator = 0x00_00_00_08, + Administrator = 0x00_00_00_08, /// /// Allows management and editing of channels. /// @@ -42,7 +42,7 @@ namespace Discord /// This permission requires the owner account to use two-factor /// authentication when used on a guild that has server-wide 2FA enabled. /// - ManageChannels = 0x00_00_00_10, + ManageChannels = 0x00_00_00_10, /// /// Allows management and editing of the guild. /// @@ -50,27 +50,33 @@ namespace Discord /// This permission requires the owner account to use two-factor /// authentication when used on a guild that has server-wide 2FA enabled. /// - ManageGuild = 0x00_00_00_20, + ManageGuild = 0x00_00_00_20, /// /// Allows for viewing of guild insights /// - ViewGuildInsights = 0x00_08_00_00, + ViewGuildInsights = 0x00_08_00_00, // Text /// /// Allows for the addition of reactions to messages. /// - AddReactions = 0x00_00_00_40, + AddReactions = 0x00_00_00_40, /// /// Allows for viewing of audit logs. /// - ViewAuditLog = 0x00_00_00_80, - ViewChannel = 0x00_00_04_00, - SendMessages = 0x00_00_08_00, + ViewAuditLog = 0x00_00_00_80, + /// + /// Allows guild members to view a channel, which includes reading messages in text channels. + /// + ViewChannel = 0x00_00_04_00, + /// + /// Allows for sending messages in a channel + /// + SendMessages = 0x00_00_08_00, /// /// Allows for sending of text-to-speech messages. /// - SendTTSMessages = 0x00_00_10_00, + SendTTSMessages = 0x00_00_10_00, /// /// Allows for deletion of other users messages. /// @@ -78,70 +84,73 @@ namespace Discord /// This permission requires the owner account to use two-factor /// authentication when used on a guild that has server-wide 2FA enabled. /// - ManageMessages = 0x00_00_20_00, + ManageMessages = 0x00_00_20_00, /// /// Allows links sent by users with this permission will be auto-embedded. /// - EmbedLinks = 0x00_00_40_00, + EmbedLinks = 0x00_00_40_00, /// /// Allows for uploading images and files. /// - AttachFiles = 0x00_00_80_00, + AttachFiles = 0x00_00_80_00, /// /// Allows for reading of message history. /// - ReadMessageHistory = 0x00_01_00_00, + ReadMessageHistory = 0x00_01_00_00, /// /// Allows for using the @everyone tag to notify all users in a channel, and the @here tag to notify all /// online users in a channel. /// - MentionEveryone = 0x00_02_00_00, + MentionEveryone = 0x00_02_00_00, /// /// Allows the usage of custom emojis from other servers. /// - UseExternalEmojis = 0x00_04_00_00, + UseExternalEmojis = 0x00_04_00_00, // Voice /// /// Allows for joining of a voice channel. /// - Connect = 0x00_10_00_00, + Connect = 0x00_10_00_00, /// /// Allows for speaking in a voice channel. /// - Speak = 0x00_20_00_00, + Speak = 0x00_20_00_00, /// /// Allows for muting members in a voice channel. /// - MuteMembers = 0x00_40_00_00, + MuteMembers = 0x00_40_00_00, /// /// Allows for deafening of members in a voice channel. /// - DeafenMembers = 0x00_80_00_00, + DeafenMembers = 0x00_80_00_00, /// /// Allows for moving of members between voice channels. /// - MoveMembers = 0x01_00_00_00, + MoveMembers = 0x01_00_00_00, /// /// Allows for using voice-activity-detection in a voice channel. /// - UseVAD = 0x02_00_00_00, - PrioritySpeaker = 0x00_00_01_00, + UseVAD = 0x02_00_00_00, + /// + /// Allows for using priority speaker in a voice channel. + /// + PrioritySpeaker = 0x00_00_01_00, /// /// Allows video streaming in a voice channel. /// - Stream = 0x00_00_02_00, + Stream = 0x00_00_02_00, // General 2 /// /// Allows for modification of own nickname. /// - ChangeNickname = 0x04_00_00_00, + ChangeNickname = 0x04_00_00_00, /// /// Allows for modification of other users nicknames. /// - ManageNicknames = 0x08_00_00_00, + ManageNicknames = 0x08_00_00_00, /// /// Allows management and editing of roles. /// @@ -149,7 +158,7 @@ namespace Discord /// This permission requires the owner account to use two-factor /// authentication when used on a guild that has server-wide 2FA enabled. /// - ManageRoles = 0x10_00_00_00, + ManageRoles = 0x10_00_00_00, /// /// Allows management and editing of webhooks. /// @@ -157,14 +166,55 @@ namespace Discord /// This permission requires the owner account to use two-factor /// authentication when used on a guild that has server-wide 2FA enabled. /// - ManageWebhooks = 0x20_00_00_00, + ManageWebhooks = 0x20_00_00_00, /// - /// Allows management and editing of emojis. + /// Allows management and editing of emojis and stickers. /// /// /// This permission requires the owner account to use two-factor /// authentication when used on a guild that has server-wide 2FA enabled. /// - ManageEmojis = 0x40_00_00_00 + ManageEmojisAndStickers = 0x40_00_00_00, + /// + /// Allows members to use application commands like slash commands and context menus in text channels. + /// + UseApplicationCommands = 0x80_00_00_00, + /// + /// Allows for requesting to speak in stage channels. + /// + RequestToSpeak = 0x01_00_00_00_00, + /// + /// Allows for creating, editing, and deleting guild scheduled events. + /// + ManageEvents = 0x02_00_00_00_00, + /// + /// Allows for deleting and archiving threads, and viewing all private threads. + /// + /// + /// This permission requires the owner account to use two-factor + /// authentication when used on a guild that has server-wide 2FA enabled. + /// + ManageThreads = 0x04_00_00_00_00, + /// + /// Allows for creating public threads. + /// + CreatePublicThreads = 0x08_00_00_00_00, + /// + /// Allows for creating private threads. + /// + CreatePrivateThreads = 0x10_00_00_00_00, + /// + /// Allows the usage of custom stickers from other servers. + /// + UseExternalStickers = 0x20_00_00_00_00, + /// + /// Allows for sending messages in threads. + /// + SendMessagesInThreads = 0x40_00_00_00_00, + /// + /// Allows for launching activities (applications with the EMBEDDED flag) in a voice channel. + /// + StartEmbeddedActivities = 0x80_00_00_00_00 + } } diff --git a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs index b03c0e1a8..8a4ad2189 100644 --- a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; namespace Discord { @@ -10,9 +11,9 @@ namespace Discord /// Gets a blank that grants no permissions. public static readonly GuildPermissions None = new GuildPermissions(); /// Gets a that grants all guild permissions for webhook users. - public static readonly GuildPermissions Webhook = new GuildPermissions(0b00000_0000000_0001101100000_000000); + public static readonly GuildPermissions Webhook = new GuildPermissions(0b0_00000_0000000_0000000_0001101100000_000000); /// Gets a that grants all guild permissions. - public static readonly GuildPermissions All = new GuildPermissions(0b11111_1111111_1111111111111_111111); + public static readonly GuildPermissions All = new GuildPermissions(0b1_11111_1111111_1111111_1111111111111_111111); /// Gets a packed value representing all the permissions in this . public ulong RawValue { get; } @@ -81,8 +82,26 @@ namespace Discord public bool ManageRoles => Permissions.GetValue(RawValue, GuildPermission.ManageRoles); /// If true, a user may edit the webhooks for this guild. public bool ManageWebhooks => Permissions.GetValue(RawValue, GuildPermission.ManageWebhooks); - /// If true, a user may edit the emojis for this guild. - public bool ManageEmojis => Permissions.GetValue(RawValue, GuildPermission.ManageEmojis); + /// If true, a user may edit the emojis and stickers for this guild. + public bool ManageEmojisAndStickers => Permissions.GetValue(RawValue, GuildPermission.ManageEmojisAndStickers); + /// If true, a user may use slash commands in this guild. + public bool UseApplicationCommands => Permissions.GetValue(RawValue, GuildPermission.UseApplicationCommands); + /// If true, a user may request to speak in stage channels. + public bool RequestToSpeak => Permissions.GetValue(RawValue, GuildPermission.RequestToSpeak); + /// If true, a user may create, edit, and delete events. + public bool ManageEvents => Permissions.GetValue(RawValue, GuildPermission.ManageEvents); + /// If true, a user may manage threads in this guild. + public bool ManageThreads => Permissions.GetValue(RawValue, GuildPermission.ManageThreads); + /// If true, a user may create public threads in this guild. + public bool CreatePublicThreads => Permissions.GetValue(RawValue, GuildPermission.CreatePublicThreads); + /// If true, a user may create private threads in this guild. + public bool CreatePrivateThreads => Permissions.GetValue(RawValue, GuildPermission.CreatePrivateThreads); + /// If true, a user may use external stickers in this guild. + public bool UseExternalStickers => Permissions.GetValue(RawValue, GuildPermission.UseExternalStickers); + /// If true, a user may send messages in threads in this guild. + public bool SendMessagesInThreads => Permissions.GetValue(RawValue, GuildPermission.SendMessagesInThreads); + /// If true, a user launch application activities in voice channels in this guild. + public bool StartEmbeddedActivities => Permissions.GetValue(RawValue, GuildPermission.StartEmbeddedActivities); /// Creates a new with the provided packed value. public GuildPermissions(ulong rawValue) { RawValue = rawValue; } @@ -121,7 +140,16 @@ namespace Discord bool? manageNicknames = null, bool? manageRoles = null, bool? manageWebhooks = null, - bool? manageEmojis = null) + bool? manageEmojisAndStickers = null, + bool? useApplicationCommands = null, + bool? requestToSpeak = null, + bool? manageEvents = null, + bool? manageThreads = null, + bool? createPublicThreads = null, + bool? createPrivateThreads = null, + bool? useExternalStickers = null, + bool? sendMessagesInThreads = null, + bool? startEmbeddedActivities = null) { ulong value = initialValue; @@ -155,7 +183,16 @@ namespace Discord Permissions.SetValue(ref value, manageNicknames, GuildPermission.ManageNicknames); Permissions.SetValue(ref value, manageRoles, GuildPermission.ManageRoles); Permissions.SetValue(ref value, manageWebhooks, GuildPermission.ManageWebhooks); - Permissions.SetValue(ref value, manageEmojis, GuildPermission.ManageEmojis); + Permissions.SetValue(ref value, manageEmojisAndStickers, GuildPermission.ManageEmojisAndStickers); + Permissions.SetValue(ref value, useApplicationCommands, GuildPermission.UseApplicationCommands); + Permissions.SetValue(ref value, requestToSpeak, GuildPermission.RequestToSpeak); + Permissions.SetValue(ref value, manageEvents, GuildPermission.ManageEvents); + Permissions.SetValue(ref value, manageThreads, GuildPermission.ManageThreads); + Permissions.SetValue(ref value, createPublicThreads, GuildPermission.CreatePublicThreads); + Permissions.SetValue(ref value, createPrivateThreads, GuildPermission.CreatePrivateThreads); + Permissions.SetValue(ref value, useExternalStickers, GuildPermission.UseExternalStickers); + Permissions.SetValue(ref value, sendMessagesInThreads, GuildPermission.SendMessagesInThreads); + Permissions.SetValue(ref value, startEmbeddedActivities, GuildPermission.StartEmbeddedActivities); RawValue = value; } @@ -192,7 +229,16 @@ namespace Discord bool manageNicknames = false, bool manageRoles = false, bool manageWebhooks = false, - bool manageEmojis = false) + bool manageEmojisAndStickers = false, + bool useApplicationCommands = false, + bool requestToSpeak = false, + bool manageEvents = false, + bool manageThreads = false, + bool createPublicThreads = false, + bool createPrivateThreads = false, + bool useExternalStickers = false, + bool sendMessagesInThreads = false, + bool startEmbeddedActivities = false) : this(0, createInstantInvite: createInstantInvite, manageRoles: manageRoles, @@ -224,7 +270,16 @@ namespace Discord changeNickname: changeNickname, manageNicknames: manageNicknames, manageWebhooks: manageWebhooks, - manageEmojis: manageEmojis) + manageEmojisAndStickers: manageEmojisAndStickers, + useApplicationCommands: useApplicationCommands, + requestToSpeak: requestToSpeak, + manageEvents: manageEvents, + manageThreads: manageThreads, + createPublicThreads: createPublicThreads, + createPrivateThreads: createPrivateThreads, + useExternalStickers: useExternalStickers, + sendMessagesInThreads: sendMessagesInThreads, + startEmbeddedActivities: startEmbeddedActivities) { } /// Creates a new from this one, changing the provided non-null permissions. @@ -259,11 +314,22 @@ namespace Discord bool? manageNicknames = null, bool? manageRoles = null, bool? manageWebhooks = null, - bool? manageEmojis = null) + bool? manageEmojisAndStickers = null, + bool? useApplicationCommands = null, + bool? requestToSpeak = null, + bool? manageEvents = null, + bool? manageThreads = null, + bool? createPublicThreads = null, + bool? createPrivateThreads = null, + bool? useExternalStickers = null, + bool? sendMessagesInThreads = null, + bool? startEmbeddedActivities = null) => new GuildPermissions(RawValue, createInstantInvite, kickMembers, banMembers, administrator, manageChannels, manageGuild, addReactions, viewAuditLog, viewGuildInsights, viewChannel, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, moveMembers, - useVoiceActivation, prioritySpeaker, stream, changeNickname, manageNicknames, manageRoles, manageWebhooks, manageEmojis); + useVoiceActivation, prioritySpeaker, stream, changeNickname, manageNicknames, manageRoles, manageWebhooks, manageEmojisAndStickers, + useApplicationCommands, requestToSpeak, manageEvents, manageThreads, createPublicThreads, createPrivateThreads, useExternalStickers, sendMessagesInThreads, + startEmbeddedActivities); /// /// Returns a value that indicates if a specific is enabled @@ -293,6 +359,18 @@ namespace Discord return perms; } + internal void Ensure(GuildPermission permissions) + { + if (!Has(permissions)) + { + var vals = Enum.GetValues(typeof(GuildPermission)).Cast(); + var currentValues = RawValue; + var missingValues = vals.Where(x => permissions.HasFlag(x) && !Permissions.GetValue(currentValues, x)); + + throw new InvalidOperationException($"Missing required guild permission{(missingValues.Count() > 1 ? "s" : "")} {string.Join(", ", missingValues.Select(x => x.ToString()))} in order to execute this operation."); + } + } + public override string ToString() => RawValue.ToString(); private string DebuggerDisplay => $"{string.Join(", ", ToList())}"; } diff --git a/src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs b/src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs index 4f144c74b..0e634ad1a 100644 --- a/src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs @@ -1,3 +1,4 @@ +using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Diagnostics; @@ -82,6 +83,22 @@ namespace Discord public PermValue ManageRoles => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.ManageRoles); /// If True, a user may edit the webhooks for this channel. public PermValue ManageWebhooks => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.ManageWebhooks); + /// If true, a user may use slash commands in this guild. + public PermValue UseApplicationCommands => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.UseApplicationCommands); + /// If true, a user may request to speak in stage channels. + public PermValue RequestToSpeak => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.RequestToSpeak); + /// If true, a user may manage threads in this guild. + public PermValue ManageThreads => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.ManageThreads); + /// If true, a user may create public threads in this guild. + public PermValue CreatePublicThreads => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.CreatePublicThreads); + /// If true, a user may create private threads in this guild. + public PermValue CreatePrivateThreads => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.CreatePrivateThreads); + /// If true, a user may use external stickers in this guild. + public PermValue UseExternalStickers => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.UseExternalStickers); + /// If true, a user may send messages in threads in this guild. + public PermValue SendMessagesInThreads => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.SendMessagesInThreads); + /// If true, a user launch application activities in voice channels in this guild. + public PermValue StartEmbeddedActivities => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.StartEmbeddedActivities); /// Creates a new OverwritePermissions with the provided allow and deny packed values. public OverwritePermissions(ulong allowValue, ulong denyValue) @@ -119,7 +136,18 @@ namespace Discord PermValue? manageRoles = null, PermValue? manageWebhooks = null, PermValue? prioritySpeaker = null, - PermValue? stream = null) + PermValue? stream = null, + PermValue? useSlashCommands = null, + PermValue? useApplicationCommands = null, + PermValue? requestToSpeak = null, + PermValue? manageThreads = null, + PermValue? createPublicThreads = null, + PermValue? createPrivateThreads = null, + PermValue? usePublicThreads = null, + PermValue? usePrivateThreads = null, + PermValue? useExternalStickers = null, + PermValue? sendMessagesInThreads = null, + PermValue? startEmbeddedActivities = null) { Permissions.SetValue(ref allowValue, ref denyValue, createInstantInvite, ChannelPermission.CreateInstantInvite); Permissions.SetValue(ref allowValue, ref denyValue, manageChannel, ChannelPermission.ManageChannels); @@ -143,6 +171,14 @@ namespace Discord Permissions.SetValue(ref allowValue, ref denyValue, stream, ChannelPermission.Stream); Permissions.SetValue(ref allowValue, ref denyValue, manageRoles, ChannelPermission.ManageRoles); Permissions.SetValue(ref allowValue, ref denyValue, manageWebhooks, ChannelPermission.ManageWebhooks); + Permissions.SetValue(ref allowValue, ref denyValue, useApplicationCommands, ChannelPermission.UseApplicationCommands); + Permissions.SetValue(ref allowValue, ref denyValue, requestToSpeak, ChannelPermission.RequestToSpeak); + Permissions.SetValue(ref allowValue, ref denyValue, manageThreads, ChannelPermission.ManageThreads); + Permissions.SetValue(ref allowValue, ref denyValue, createPublicThreads, ChannelPermission.CreatePublicThreads); + Permissions.SetValue(ref allowValue, ref denyValue, createPrivateThreads, ChannelPermission.CreatePrivateThreads); + Permissions.SetValue(ref allowValue, ref denyValue, useExternalStickers, ChannelPermission.UseExternalStickers); + Permissions.SetValue(ref allowValue, ref denyValue, sendMessagesInThreads, ChannelPermission.SendMessagesInThreads); + Permissions.SetValue(ref allowValue, ref denyValue, startEmbeddedActivities, ChannelPermission.StartEmbeddedActivities); AllowValue = allowValue; DenyValue = denyValue; @@ -173,10 +209,23 @@ namespace Discord PermValue manageRoles = PermValue.Inherit, PermValue manageWebhooks = PermValue.Inherit, PermValue prioritySpeaker = PermValue.Inherit, - PermValue stream = PermValue.Inherit) + PermValue stream = PermValue.Inherit, + PermValue useSlashCommands = PermValue.Inherit, + PermValue useApplicationCommands = PermValue.Inherit, + PermValue requestToSpeak = PermValue.Inherit, + PermValue manageThreads = PermValue.Inherit, + PermValue createPublicThreads = PermValue.Inherit, + PermValue createPrivateThreads = PermValue.Inherit, + PermValue usePublicThreads = PermValue.Inherit, + PermValue usePrivateThreads = PermValue.Inherit, + PermValue useExternalStickers = PermValue.Inherit, + PermValue sendMessagesInThreads = PermValue.Inherit, + PermValue startEmbeddedActivities = PermValue.Inherit) : this(0, 0, createInstantInvite, manageChannel, addReactions, viewChannel, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, - moveMembers, useVoiceActivation, manageRoles, manageWebhooks, prioritySpeaker, stream) { } + moveMembers, useVoiceActivation, manageRoles, manageWebhooks, prioritySpeaker, stream, useSlashCommands, useApplicationCommands, + requestToSpeak, manageThreads, createPublicThreads, createPrivateThreads, usePublicThreads, usePrivateThreads, useExternalStickers, + sendMessagesInThreads, startEmbeddedActivities) { } /// /// Initializes a new from the current one, changing the provided @@ -204,10 +253,23 @@ namespace Discord PermValue? manageRoles = null, PermValue? manageWebhooks = null, PermValue? prioritySpeaker = null, - PermValue? stream = null) + PermValue? stream = null, + PermValue? useSlashCommands = null, + PermValue? useApplicationCommands = null, + PermValue? requestToSpeak = null, + PermValue? manageThreads = null, + PermValue? createPublicThreads = null, + PermValue? createPrivateThreads = null, + PermValue? usePublicThreads = null, + PermValue? usePrivateThreads = null, + PermValue? useExternalStickers = null, + PermValue? sendMessagesInThreads = null, + PermValue? startEmbeddedActivities = null) => new OverwritePermissions(AllowValue, DenyValue, createInstantInvite, manageChannel, addReactions, viewChannel, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, - moveMembers, useVoiceActivation, manageRoles, manageWebhooks, prioritySpeaker, stream); + moveMembers, useVoiceActivation, manageRoles, manageWebhooks, prioritySpeaker, stream, useSlashCommands, useApplicationCommands, + requestToSpeak, manageThreads, createPublicThreads, createPrivateThreads, usePublicThreads, usePrivateThreads, useExternalStickers, + sendMessagesInThreads, startEmbeddedActivities); /// /// Creates a of all the values that are allowed. diff --git a/src/Discord.Net.Core/Entities/Roles/Color.cs b/src/Discord.Net.Core/Entities/Roles/Color.cs index 7c2d152a4..ee50710e8 100644 --- a/src/Discord.Net.Core/Entities/Roles/Color.cs +++ b/src/Discord.Net.Core/Entities/Roles/Color.cs @@ -10,68 +10,70 @@ namespace Discord [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public struct Color { + /// Gets the max decimal value of color. + public const uint MaxDecimalValue = 0xFFFFFF; /// Gets the default user color value. - public static readonly Color Default = new Color(0); + public static readonly Color Default = new(0); /// Gets the teal color value. /// A color struct with the hex value of 1ABC9C. - public static readonly Color Teal = new Color(0x1ABC9C); + public static readonly Color Teal = new(0x1ABC9C); /// Gets the dark teal color value. /// A color struct with the hex value of 11806A. - public static readonly Color DarkTeal = new Color(0x11806A); + public static readonly Color DarkTeal = new(0x11806A); /// Gets the green color value. /// A color struct with the hex value of 2ECC71. - public static readonly Color Green = new Color(0x2ECC71); + public static readonly Color Green = new(0x2ECC71); /// Gets the dark green color value. /// A color struct with the hex value of 1F8B4C. - public static readonly Color DarkGreen = new Color(0x1F8B4C); + public static readonly Color DarkGreen = new(0x1F8B4C); /// Gets the blue color value. /// A color struct with the hex value of 3498DB. - public static readonly Color Blue = new Color(0x3498DB); + public static readonly Color Blue = new(0x3498DB); /// Gets the dark blue color value. /// A color struct with the hex value of 206694. - public static readonly Color DarkBlue = new Color(0x206694); + public static readonly Color DarkBlue = new(0x206694); /// Gets the purple color value. /// A color struct with the hex value of 9B59B6. - public static readonly Color Purple = new Color(0x9B59B6); + public static readonly Color Purple = new(0x9B59B6); /// Gets the dark purple color value. /// A color struct with the hex value of 71368A. - public static readonly Color DarkPurple = new Color(0x71368A); + public static readonly Color DarkPurple = new(0x71368A); /// Gets the magenta color value. /// A color struct with the hex value of E91E63. - public static readonly Color Magenta = new Color(0xE91E63); + public static readonly Color Magenta = new(0xE91E63); /// Gets the dark magenta color value. /// A color struct with the hex value of AD1457. - public static readonly Color DarkMagenta = new Color(0xAD1457); + public static readonly Color DarkMagenta = new(0xAD1457); /// Gets the gold color value. /// A color struct with the hex value of F1C40F. - public static readonly Color Gold = new Color(0xF1C40F); + public static readonly Color Gold = new(0xF1C40F); /// Gets the light orange color value. /// A color struct with the hex value of C27C0E. - public static readonly Color LightOrange = new Color(0xC27C0E); + public static readonly Color LightOrange = new(0xC27C0E); /// Gets the orange color value. /// A color struct with the hex value of E67E22. - public static readonly Color Orange = new Color(0xE67E22); + public static readonly Color Orange = new(0xE67E22); /// Gets the dark orange color value. /// A color struct with the hex value of A84300. - public static readonly Color DarkOrange = new Color(0xA84300); + public static readonly Color DarkOrange = new(0xA84300); /// Gets the red color value. /// A color struct with the hex value of E74C3C. - public static readonly Color Red = new Color(0xE74C3C); + public static readonly Color Red = new(0xE74C3C); /// Gets the dark red color value. /// A color struct with the hex value of 992D22. - public static readonly Color DarkRed = new Color(0x992D22); + public static readonly Color DarkRed = new(0x992D22); /// Gets the light grey color value. /// A color struct with the hex value of 979C9F. - public static readonly Color LightGrey = new Color(0x979C9F); + public static readonly Color LightGrey = new(0x979C9F); /// Gets the lighter grey color value. /// A color struct with the hex value of 95A5A6. - public static readonly Color LighterGrey = new Color(0x95A5A6); + public static readonly Color LighterGrey = new(0x95A5A6); /// Gets the dark grey color value. /// A color struct with the hex value of 607D8B. - public static readonly Color DarkGrey = new Color(0x607D8B); + public static readonly Color DarkGrey = new(0x607D8B); /// Gets the darker grey color value. /// A color struct with the hex value of 546E7A. - public static readonly Color DarkerGrey = new Color(0x546E7A); + public static readonly Color DarkerGrey = new(0x546E7A); /// Gets the encoded value for this color. /// @@ -91,22 +93,27 @@ namespace Discord /// Initializes a struct with the given raw value. /// /// - /// The following will create a color that has a hex value of + /// The following will create a color that has a hex value of /// #607D8B. /// /// Color darkGrey = new Color(0x607D8B); /// /// /// The raw value of the color (e.g. 0x607D8B). + /// Value exceeds . public Color(uint rawValue) { + if (rawValue > MaxDecimalValue) + throw new ArgumentException($"{nameof(RawValue)} of color cannot be greater than {MaxDecimalValue}!", nameof(rawValue)); + RawValue = rawValue; } + /// /// Initializes a struct with the given RGB bytes. /// /// - /// The following will create a color that has a value of + /// The following will create a color that has a value of /// #607D8B. /// /// Color darkGrey = new Color((byte)0b_01100000, (byte)0b_01111101, (byte)0b_10001011); @@ -115,19 +122,24 @@ namespace Discord /// The byte that represents the red color. /// The byte that represents the green color. /// The byte that represents the blue color. + /// Value exceeds . public Color(byte r, byte g, byte b) { - RawValue = - ((uint)r << 16) | - ((uint)g << 8) | - (uint)b; + uint value = ((uint)r << 16) + | ((uint)g << 8) + | (uint)b; + + if (value > MaxDecimalValue) + throw new ArgumentException($"{nameof(RawValue)} of color cannot be greater than {MaxDecimalValue}!"); + + RawValue = value; } /// /// Initializes a struct with the given RGB value. /// /// - /// The following will create a color that has a value of + /// The following will create a color that has a value of /// #607D8B. /// /// Color darkGrey = new Color(96, 125, 139); @@ -145,16 +157,15 @@ namespace Discord throw new ArgumentOutOfRangeException(nameof(g), "Value must be within [0,255]."); if (b < 0 || b > 255) throw new ArgumentOutOfRangeException(nameof(b), "Value must be within [0,255]."); - RawValue = - ((uint)r << 16) | - ((uint)g << 8) | - (uint)b; + RawValue = ((uint)r << 16) + | ((uint)g << 8) + | (uint)b; } /// /// Initializes a struct with the given RGB float value. /// /// - /// The following will create a color that has a value of + /// The following will create a color that has a value of /// #607c8c. /// /// Color darkGrey = new Color(0.38f, 0.49f, 0.55f); @@ -172,10 +183,9 @@ namespace Discord throw new ArgumentOutOfRangeException(nameof(g), "Value must be within [0,1]."); if (b < 0.0f || b > 1.0f) throw new ArgumentOutOfRangeException(nameof(b), "Value must be within [0,1]."); - RawValue = - ((uint)(r * 255.0f) << 16) | - ((uint)(g * 255.0f) << 8) | - (uint)(b * 255.0f); + RawValue = ((uint)(r * 255.0f) << 16) + | ((uint)(g * 255.0f) << 8) + | (uint)(b * 255.0f); } public static bool operator ==(Color lhs, Color rhs) @@ -184,15 +194,22 @@ namespace Discord public static bool operator !=(Color lhs, Color rhs) => lhs.RawValue != rhs.RawValue; + public static implicit operator Color(uint rawValue) + => new(rawValue); + + public static implicit operator uint(Color color) + => color.RawValue; + public override bool Equals(object obj) - => (obj is Color c && RawValue == c.RawValue); + => obj is Color c && RawValue == c.RawValue; public override int GetHashCode() => RawValue.GetHashCode(); - public static implicit operator StandardColor(Color color) => - StandardColor.FromArgb((int)color.RawValue); - public static explicit operator Color(StandardColor color) => - new Color((uint)color.ToArgb() << 8 >> 8); + public static implicit operator StandardColor(Color color) + => StandardColor.FromArgb((int)color.RawValue); + + public static explicit operator Color(StandardColor color) + => new((uint)color.ToArgb() << 8 >> 8); /// /// Gets the hexadecimal representation of the color (e.g. #000ccc). diff --git a/src/Discord.Net.Core/Entities/Roles/IRole.cs b/src/Discord.Net.Core/Entities/Roles/IRole.cs index c02322be9..59ca41e31 100644 --- a/src/Discord.Net.Core/Entities/Roles/IRole.cs +++ b/src/Discord.Net.Core/Entities/Roles/IRole.cs @@ -52,6 +52,20 @@ namespace Discord /// string Name { get; } /// + /// Gets the icon of this role. + /// + /// + /// A string containing the hash of this role's icon. + /// + string Icon { get; } + /// + /// Gets the unicode emoji of this role. + /// + /// + /// This field is mutually exclusive with , either icon is set or emoji is set. + /// + Emoji Emoji { get; } + /// /// Gets the permissions granted to members of this role. /// /// @@ -86,5 +100,13 @@ namespace Discord /// A task that represents the asynchronous modification operation. /// Task ModifyAsync(Action func, RequestOptions options = null); + + /// + /// Gets the image url of the icon role. + /// + /// + /// An image url of the icon role. + /// + string GetIconUrl(); } } diff --git a/src/Discord.Net.Core/Entities/Roles/RoleProperties.cs b/src/Discord.Net.Core/Entities/Roles/RoleProperties.cs index a58112b28..93cda8d5b 100644 --- a/src/Discord.Net.Core/Entities/Roles/RoleProperties.cs +++ b/src/Discord.Net.Core/Entities/Roles/RoleProperties.cs @@ -50,6 +50,11 @@ namespace Discord /// This value may not be set if the role is an @everyone role. /// public Optional Hoist { get; set; } + + /// + /// Gets or sets the icon of the role. + /// + public Optional Icon { get; set; } /// /// Gets or sets whether or not this role can be mentioned. /// diff --git a/src/Discord.Net.Core/Entities/Stickers/ICustomSticker.cs b/src/Discord.Net.Core/Entities/Stickers/ICustomSticker.cs new file mode 100644 index 000000000..9cba38c80 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Stickers/ICustomSticker.cs @@ -0,0 +1,59 @@ +using System; +using System.Threading.Tasks; + +namespace Discord +{ + /// + /// Represents a custom sticker within a guild. + /// + public interface ICustomSticker : ISticker + { + /// + /// Gets the users id who uploaded the sticker. + /// + /// + /// In order to get the author id, the bot needs the MANAGE_EMOJIS_AND_STICKERS permission. + /// + ulong? AuthorId { get; } + + /// + /// Gets the guild that this custom sticker is in. + /// + IGuild Guild { get; } + + /// + /// Modifies this sticker. + /// + /// + /// This method modifies this sticker with the specified properties. To see an example of this + /// method and what properties are available, please refer to . + ///
+ ///
+ /// The bot needs the MANAGE_EMOJIS_AND_STICKERS permission within the guild in order to modify stickers. + ///
+ /// + /// The following example replaces the name of the sticker with kekw. + /// + /// await sticker.ModifyAsync(x => x.Name = "kekw"); + /// + /// + /// A delegate containing the properties to modify the sticker with. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous modification operation. + /// + Task ModifyAsync(Action func, RequestOptions options = null); + + /// + /// Deletes the current sticker. + /// + /// + /// The bot needs the MANAGE_EMOJIS_AND_STICKERS permission inside the guild in order to delete stickers. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous deletion operation. + /// + Task DeleteAsync(RequestOptions options = null); + } +} diff --git a/src/Discord.Net.Core/Entities/Messages/ISticker.cs b/src/Discord.Net.Core/Entities/Stickers/ISticker.cs similarity index 71% rename from src/Discord.Net.Core/Entities/Messages/ISticker.cs rename to src/Discord.Net.Core/Entities/Stickers/ISticker.cs index e7e4405b6..9deea753f 100644 --- a/src/Discord.Net.Core/Entities/Messages/ISticker.cs +++ b/src/Discord.Net.Core/Entities/Stickers/ISticker.cs @@ -5,7 +5,7 @@ namespace Discord /// /// Represents a discord sticker. /// - public interface ISticker + public interface ISticker : IStickerItem { /// /// Gets the ID of this sticker. @@ -13,7 +13,7 @@ namespace Discord /// /// A snowflake ID associated with this sticker. /// - ulong Id { get; } + new ulong Id { get; } /// /// Gets the ID of the pack of this sticker. /// @@ -27,7 +27,7 @@ namespace Discord /// /// A with the name of this sticker. /// - string Name { get; } + new string Name { get; } /// /// Gets the description of this sticker. /// @@ -43,25 +43,29 @@ namespace Discord /// IReadOnlyCollection Tags { get; } /// - /// Gets the asset hash of this sticker. + /// Gets the type of this sticker. /// - /// - /// A with the asset hash of this sticker. - /// - string Asset { get; } - /// - /// Gets the preview asset hash of this sticker. - /// - /// - /// A with the preview asset hash of this sticker. - /// - string PreviewAsset { get; } + StickerType Type { get; } /// /// Gets the format type of this sticker. /// /// /// A with the format type of this sticker. /// - StickerFormatType FormatType { get; } + new StickerFormatType Format { get; } + + /// + /// Gets whether this guild sticker can be used, may be false due to loss of Server Boosts. + /// + bool? IsAvailable { get; } + + /// + /// Gets the standard sticker's sort order within its pack. + /// + int? SortOrder { get; } + /// + /// Gets the image url for this sticker. + /// + string GetStickerUrl(); } } diff --git a/src/Discord.Net.Core/Entities/Stickers/IStickerItem.cs b/src/Discord.Net.Core/Entities/Stickers/IStickerItem.cs new file mode 100644 index 000000000..07ea63db9 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Stickers/IStickerItem.cs @@ -0,0 +1,23 @@ +namespace Discord +{ + /// + /// Represents a partial sticker item received with a message. + /// + public interface IStickerItem + { + /// + /// The id of the sticker. + /// + ulong Id { get; } + + /// + /// The name of the sticker. + /// + string Name { get; } + + /// + /// The format of the sticker. + /// + StickerFormatType Format { get; } + } +} diff --git a/src/Discord.Net.Core/Entities/Stickers/StickerPack.cs b/src/Discord.Net.Core/Entities/Stickers/StickerPack.cs new file mode 100644 index 000000000..c0c90aa69 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Stickers/StickerPack.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; +using System.Collections.Immutable; + +namespace Discord +{ + /// + /// Represents a discord sticker pack. + /// + /// The type of the stickers within the collection. + public class StickerPack where TSticker : ISticker + { + /// + /// Gets the id of the sticker pack. + /// + public ulong Id { get; } + + /// + /// Gets a collection of the stickers in the pack. + /// + public IReadOnlyCollection Stickers { get; } + + /// + /// Gets the name of the sticker pack. + /// + public string Name { get; } + + /// + /// Gets the id of the pack's SKU. + /// + public ulong SkuId { get; } + + /// + /// Gets the id of a sticker in the pack which is shown as the pack's icon. + /// + public ulong? CoverStickerId { get; } + + /// + /// Gets the description of the sticker pack. + /// + public string Description { get; } + + /// + /// Gets the id of the sticker pack's banner image + /// + public ulong BannerAssetId { get; } + + internal StickerPack(string name, ulong id, ulong skuid, ulong? coverStickerId, string description, ulong bannerAssetId, IEnumerable stickers) + { + Name = name; + Id = id; + SkuId = skuid; + CoverStickerId = coverStickerId; + Description = description; + BannerAssetId = bannerAssetId; + + Stickers = stickers.ToImmutableArray(); + } + } +} diff --git a/src/Discord.Net.Core/Entities/Stickers/StickerProperties.cs b/src/Discord.Net.Core/Entities/Stickers/StickerProperties.cs new file mode 100644 index 000000000..5f51e5f3d --- /dev/null +++ b/src/Discord.Net.Core/Entities/Stickers/StickerProperties.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; + +namespace Discord +{ + /// + /// Represents a class used to modify stickers. + /// + public class StickerProperties + { + /// + /// Gets or sets the name of the sticker. + /// + public Optional Name { get; set; } + + /// + /// Gets or sets the description of the sticker. + /// + public Optional Description { get; set; } + + /// + /// Gets or sets the tags of the sticker. + /// + public Optional> Tags { get; set; } + } +} diff --git a/src/Discord.Net.Core/Entities/Stickers/StickerType.cs b/src/Discord.Net.Core/Entities/Stickers/StickerType.cs new file mode 100644 index 000000000..0db550772 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Stickers/StickerType.cs @@ -0,0 +1,18 @@ +namespace Discord +{ + /// + /// Represents a type of sticker.. + /// + public enum StickerType + { + /// + /// Represents a discord standard sticker, this type of sticker cannot be modified by an application. + /// + Standard = 1, + + /// + /// Represents a sticker that was created within a guild. + /// + Guild = 2 + } +} diff --git a/src/Discord.Net.Core/Entities/Users/IGuildUser.cs b/src/Discord.Net.Core/Entities/Users/IGuildUser.cs index 492cb9566..947ff8521 100644 --- a/src/Discord.Net.Core/Entities/Users/IGuildUser.cs +++ b/src/Discord.Net.Core/Entities/Users/IGuildUser.cs @@ -25,6 +25,13 @@ namespace Discord /// string Nickname { get; } /// + /// Gets the guild specific avatar for this users. + /// + /// + /// The users guild avatar hash if they have one; otherwise . + /// + string GuildAvatarId { get; } + /// /// Gets the guild-level permissions for this user. /// /// @@ -74,12 +81,17 @@ namespace Discord bool? IsPending { get; } /// + /// Gets the users position within the role hierarchy. + /// + int Hierarchy { get; } + + /// /// Gets the level permissions granted to this user to a given channel. /// /// /// The following example checks if the current user has the ability to send a message with attachment in /// this channel; if so, uploads a file via . - /// + /// /// if (currentUser?.GetPermissions(targetChannel)?.AttachFiles) /// await targetChannel.SendFileAsync("fortnite.png"); /// @@ -92,6 +104,20 @@ namespace Discord ChannelPermissions GetPermissions(IGuildChannel channel); /// + /// Gets the guild avatar URL for this user. + /// + /// + /// This property retrieves a URL for this guild user's guild specific avatar. In event that the user does not have a valid guild avatar + /// (i.e. their avatar identifier is not set), this method will return null. + /// + /// The format to return. + /// The size of the image to return in. This can be any power of two between 16 and 2048. + /// + /// + /// A string representing the user's avatar URL; null if the user does not have an avatar in place. + /// + string GetGuildAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128); + /// /// Kicks this user from this guild. /// /// The reason for the kick which will be recorded in the audit log. diff --git a/src/Discord.Net.Core/Entities/Users/IUser.cs b/src/Discord.Net.Core/Entities/Users/IUser.cs index 9596a8338..2f79450f3 100644 --- a/src/Discord.Net.Core/Entities/Users/IUser.cs +++ b/src/Discord.Net.Core/Entities/Users/IUser.cs @@ -10,19 +10,20 @@ namespace Discord /// /// Gets the identifier of this user's avatar. /// - string AvatarId { get; } + string AvatarId { get; } /// /// Gets the avatar URL for this user. /// /// /// This property retrieves a URL for this user's avatar. In event that the user does not have a valid avatar - /// (i.e. their avatar identifier is not set), this property will return null. If you wish to + /// (i.e. their avatar identifier is not set), this method will return null. If you wish to /// retrieve the default avatar for this user, consider using (see /// example). /// /// - /// The following example attempts to retrieve the user's current avatar and send it to a channel; if one is - /// not set, a default avatar for this user will be returned instead. + /// The following example attempts to retrieve the user's current avatar and send it to a channel; if one is + /// not set, a default avatar for this user will be returned instead. /// /// @@ -93,8 +94,8 @@ namespace Discord /// This method is used to obtain or create a channel used to send a direct message. /// /// In event that the current user cannot send a message to the target user, a channel can and will - /// still be created by Discord. However, attempting to send a message will yield a - /// with a 403 as its + /// still be created by Discord. However, attempting to send a message will yield a + /// with a 403 as its /// . There are currently no official workarounds by /// Discord. /// diff --git a/src/Discord.Net.Core/Entities/Users/IVoiceState.cs b/src/Discord.Net.Core/Entities/Users/IVoiceState.cs index a9b347003..c9a22761f 100644 --- a/src/Discord.Net.Core/Entities/Users/IVoiceState.cs +++ b/src/Discord.Net.Core/Entities/Users/IVoiceState.cs @@ -1,3 +1,5 @@ +using System; + namespace Discord { /// @@ -62,5 +64,9 @@ namespace Discord /// true if the user is streaming; otherwise false. /// bool IsStreaming { get; } + /// + /// Gets the time on which the user requested to speak. + /// + DateTimeOffset? RequestToSpeakTimestamp { get; } } } diff --git a/src/Discord.Net.Core/Entities/Users/UserProperties.cs b/src/Discord.Net.Core/Entities/Users/UserProperties.cs index 68232b254..4cf4162a9 100644 --- a/src/Discord.Net.Core/Entities/Users/UserProperties.cs +++ b/src/Discord.Net.Core/Entities/Users/UserProperties.cs @@ -61,9 +61,13 @@ namespace Discord /// Flag given to users that developed bots and early verified their accounts. /// EarlyVerifiedBotDeveloper = 1 << 17, - /// + /// /// Flag given to users that are discord certified moderators who has give discord's exam. /// DiscordCertifiedModerator = 1 << 18, + /// + /// Flag given to bots that use only outgoing webhooks, exclusively. + /// + BotHTTPInteractions = 1 << 19, } } diff --git a/src/Discord.Net.Core/Entities/Webhooks/IWebhook.cs b/src/Discord.Net.Core/Entities/Webhooks/IWebhook.cs index b2d017316..d5bc70d71 100644 --- a/src/Discord.Net.Core/Entities/Webhooks/IWebhook.cs +++ b/src/Discord.Net.Core/Entities/Webhooks/IWebhook.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading.Tasks; namespace Discord @@ -50,6 +50,11 @@ namespace Discord IUser Creator { get; } /// + /// Gets the ID of the application owning this webhook. + /// + ulong? ApplicationId { get; } + + /// /// Modifies this webhook. /// Task ModifyAsync(Action func, RequestOptions options = null); diff --git a/src/Discord.Net.Core/Extensions/GuildExtensions.cs b/src/Discord.Net.Core/Extensions/GuildExtensions.cs index 58b749cc4..9dd8de82e 100644 --- a/src/Discord.Net.Core/Extensions/GuildExtensions.cs +++ b/src/Discord.Net.Core/Extensions/GuildExtensions.cs @@ -20,5 +20,21 @@ namespace Discord /// A bool indicating if the guild boost messages are enabled in the system channel. public static bool GetGuildBoostMessagesEnabled(this IGuild guild) => !guild.SystemChannelFlags.HasFlag(SystemChannelMessageDeny.GuildBoost); + + /// + /// Gets if guild setup system messages are enabled. + /// + /// The guild to check. + /// A bool indicating if the guild setup messages are enabled in the system channel. + public static bool GetGuildSetupTipMessagesEnabled(this IGuild guild) + => !guild.SystemChannelFlags.HasFlag(SystemChannelMessageDeny.GuildSetupTip); + + /// + /// Gets if guild welcome messages have a reply with sticker button. + /// + /// The guild to check. + /// A bool indicating if the guild welcome messages have a reply with sticker button. + public static bool GetGuildWelcomeMessageReplyEnabled(this IGuild guild) + => !guild.SystemChannelFlags.HasFlag(SystemChannelMessageDeny.WelcomeMessageReply); } } diff --git a/src/Discord.Net.Core/Extensions/MessageExtensions.cs b/src/Discord.Net.Core/Extensions/MessageExtensions.cs index b043d7b77..c187ecd5b 100644 --- a/src/Discord.Net.Core/Extensions/MessageExtensions.cs +++ b/src/Discord.Net.Core/Extensions/MessageExtensions.cs @@ -24,7 +24,7 @@ namespace Discord /// Add multiple reactions to a message. /// /// - /// This method does not bulk add reactions! It will send a request for each reaction inculded. + /// This method does not bulk add reactions! It will send a request for each reaction included. /// /// /// @@ -34,7 +34,7 @@ namespace Discord /// /// /// The message to add reactions to. - /// An array of reactions to add to the message + /// An array of reactions to add to the message. /// The options to be used when sending the request. /// /// A task that represents the asynchronous operation for adding a reaction to this message. @@ -59,7 +59,8 @@ namespace Discord /// /// /// The message to remove reactions from. - /// An array of reactions to remove from the message + /// The user who removed the reaction. + /// An array of reactions to remove from the message. /// The options to be used when sending the request. /// /// A task that represents the asynchronous operation for removing a reaction to this message. @@ -75,21 +76,25 @@ namespace Discord /// /// Sends an inline reply that references a message. /// + /// The message that is being replied on. /// The message to be sent. /// Determines whether the message should be read aloud by Discord or not. /// The to be sent. + /// A array of s to send with this response. Max 10. /// /// Specifies if notifications are sent for mentioned users and roles in the message . /// If null, all mentioned roles and users will be notified. /// /// The options to be used when sending the request. + /// The message components to be included with this message. Used for interactions. + /// A collection of stickers to send with the message. /// /// A task that represents an asynchronous send operation for delivering the message. The task result /// contains the sent message. /// - public static async Task ReplyAsync(this IUserMessage msg, string text = null, bool isTTS = false, Embed embed = null, AllowedMentions allowedMentions = null, RequestOptions options = null) + public static async Task ReplyAsync(this IUserMessage msg, string text = null, bool isTTS = false, Embed embed = null, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null) { - return await msg.Channel.SendMessageAsync(text, isTTS, embed, options, allowedMentions, new MessageReference(messageId: msg.Id)).ConfigureAwait(false); + return await msg.Channel.SendMessageAsync(text, isTTS, embed, options, allowedMentions, new MessageReference(messageId: msg.Id), components, stickers, embeds).ConfigureAwait(false); } } } diff --git a/src/Discord.Net.Core/Extensions/ObjectExtensions.cs b/src/Discord.Net.Core/Extensions/ObjectExtensions.cs new file mode 100644 index 000000000..240fb47aa --- /dev/null +++ b/src/Discord.Net.Core/Extensions/ObjectExtensions.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord +{ + internal static class ObjectExtensions + { + public static bool IsNumericType(this object o) + { + switch (Type.GetTypeCode(o.GetType())) + { + case TypeCode.Byte: + case TypeCode.SByte: + case TypeCode.UInt16: + case TypeCode.UInt32: + case TypeCode.UInt64: + case TypeCode.Int16: + case TypeCode.Int32: + case TypeCode.Int64: + case TypeCode.Decimal: + case TypeCode.Double: + case TypeCode.Single: + return true; + default: + return false; + } + } + } +} diff --git a/src/Discord.Net.Core/Extensions/UserExtensions.cs b/src/Discord.Net.Core/Extensions/UserExtensions.cs index 3e46308e6..e268eae84 100644 --- a/src/Discord.Net.Core/Extensions/UserExtensions.cs +++ b/src/Discord.Net.Core/Extensions/UserExtensions.cs @@ -32,6 +32,8 @@ namespace Discord /// Specifies if notifications are sent for mentioned users and roles in the message . /// If null, all mentioned roles and users will be notified. /// + /// The message components to be included with this message. Used for interactions. + /// A array of s to send with this response. Max 10. /// /// A task that represents the asynchronous send operation. The task result contains the sent message. /// @@ -40,9 +42,11 @@ namespace Discord bool isTTS = false, Embed embed = null, RequestOptions options = null, - AllowedMentions allowedMentions = null) + AllowedMentions allowedMentions = null, + MessageComponent component = null, + Embed[] embeds = null) { - return await (await user.CreateDMChannelAsync().ConfigureAwait(false)).SendMessageAsync(text, isTTS, embed, options, allowedMentions).ConfigureAwait(false); + return await (await user.CreateDMChannelAsync().ConfigureAwait(false)).SendMessageAsync(text, isTTS, embed, options, allowedMentions, component: component, embeds: embeds).ConfigureAwait(false); } /// @@ -81,6 +85,8 @@ namespace Discord /// Whether the message should be read aloud by Discord or not. /// The to be sent. /// The options to be used when sending the request. + /// The message component to be included with this message. Used for interactions. + /// A array of s to send with this response. Max 10. /// /// A task that represents an asynchronous send operation for delivering the message. The task result /// contains the sent message. @@ -91,10 +97,11 @@ namespace Discord string text = null, bool isTTS = false, Embed embed = null, - RequestOptions options = null - ) + RequestOptions options = null, + MessageComponent component = null, + Embed[] embeds = null) { - return await (await user.CreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false); + return await (await user.CreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(stream, filename, text, isTTS, embed, options, component: component, embeds: embeds).ConfigureAwait(false); } /// @@ -138,6 +145,8 @@ namespace Discord /// Whether the message should be read aloud by Discord or not. /// The to be sent. /// The options to be used when sending the request. + /// The message component to be included with this message. Used for interactions. + /// A array of s to send with this response. Max 10. /// /// A task that represents an asynchronous send operation for delivering the message. The task result /// contains the sent message. @@ -147,9 +156,11 @@ namespace Discord string text = null, bool isTTS = false, Embed embed = null, - RequestOptions options = null) + RequestOptions options = null, + MessageComponent component = null, + Embed[] embeds = null) { - return await (await user.CreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); + return await (await user.CreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(filePath, text, isTTS, embed, options, component: component, embeds: embeds).ConfigureAwait(false); } /// diff --git a/src/Discord.Net.Core/Format.cs b/src/Discord.Net.Core/Format.cs index 0ab70f89c..63f9d15a6 100644 --- a/src/Discord.Net.Core/Format.cs +++ b/src/Discord.Net.Core/Format.cs @@ -1,4 +1,5 @@ using System.Text; +using System.Text.RegularExpressions; namespace Discord { @@ -14,7 +15,7 @@ namespace Discord public static string Italics(string text) => $"*{text}*"; /// Returns a markdown-formatted string with underline formatting. public static string Underline(string text) => $"__{text}__"; - /// Returns a markdown-formatted string with strikethrough formatting. + /// Returns a markdown-formatted string with strike-through formatting. public static string Strikethrough(string text) => $"~~{text}~~"; /// Returns a string with spoiler formatting. public static string Spoiler(string text) => $"||{text}||"; @@ -91,5 +92,17 @@ namespace Discord return $">>> {text}"; } + + /// + /// Remove discord supported markdown from text. + /// + /// The to remove markdown from. + /// Gets the unformatted text. + public static string StripMarkDown(string text) + { + //Remove discord supported markdown + var newText = Regex.Replace(text, @"(\*|_|`|~|>|\\)", ""); + return newText; + } } } diff --git a/src/Discord.Net.Core/GatewayIntents.cs b/src/Discord.Net.Core/GatewayIntents.cs index 6976806b2..f2a99e44c 100644 --- a/src/Discord.Net.Core/GatewayIntents.cs +++ b/src/Discord.Net.Core/GatewayIntents.cs @@ -39,13 +39,15 @@ namespace Discord DirectMessageReactions = 1 << 13, /// This intent includes TYPING_START DirectMessageTyping = 1 << 14, + /// This intent includes GUILD_SCHEDULED_EVENT_CREATE, GUILD_SCHEDULED_EVENT_UPDATE, GUILD_SCHEDULED_EVENT_DELETE, GUILD_SCHEDULED_EVENT_USER_ADD, GUILD_SCHEDULED_EVENT_USER_REMOVE + GuildScheduledEvents = 1 << 16, /// - /// This intent includes all but and - /// that are privileged must be enabled for the application. + /// This intent includes all but and + /// which are privileged and must be enabled in the Developer Portal. /// AllUnprivileged = Guilds | GuildBans | GuildEmojis | GuildIntegrations | GuildWebhooks | GuildInvites | GuildVoiceStates | GuildMessages | GuildMessageReactions | GuildMessageTyping | DirectMessages | - DirectMessageReactions | DirectMessageTyping, + DirectMessageReactions | DirectMessageTyping | GuildScheduledEvents, /// /// This intent includes all of them, including privileged ones. /// diff --git a/src/Discord.Net.Core/IDiscordClient.cs b/src/Discord.Net.Core/IDiscordClient.cs index d7d6d2856..f6981d552 100644 --- a/src/Discord.Net.Core/IDiscordClient.cs +++ b/src/Discord.Net.Core/IDiscordClient.cs @@ -142,6 +142,47 @@ namespace Discord Task> GetConnectionsAsync(RequestOptions options = null); /// + /// Gets a global application command. + /// + /// The id of the command. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains the application command if found, otherwise + /// . + /// + Task GetGlobalApplicationCommandAsync(ulong id, RequestOptions options = null); + + /// + /// Gets a collection of all global commands. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection of global + /// application commands. + /// + Task> GetGlobalApplicationCommandsAsync(RequestOptions options = null); + + /// + /// Creates a global application command. + /// + /// The properties to use when creating the command. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous creation operation. The task result contains the created application command. + /// + Task CreateGlobalApplicationCommand(ApplicationCommandProperties properties, RequestOptions options = null); + + /// + /// Bulk overwrites all global application commands. + /// + /// A collection of properties to use when creating the commands. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous creation operation. The task result contains a collection of application commands that were created. + /// + Task> BulkOverwriteGlobalApplicationCommand(ApplicationCommandProperties[] properties, RequestOptions options = null); + + /// /// Gets a guild. /// /// The guild snowflake identifier. diff --git a/src/Discord.Net.Core/Net/ApplicationCommandException.cs b/src/Discord.Net.Core/Net/ApplicationCommandException.cs new file mode 100644 index 000000000..4b4890d12 --- /dev/null +++ b/src/Discord.Net.Core/Net/ApplicationCommandException.cs @@ -0,0 +1,15 @@ +using System; +using System.Linq; + +namespace Discord.Net +{ + [Obsolete("Please use HttpException instead of this. Will be removed in next major version.", false)] + public class ApplicationCommandException : HttpException + { + public ApplicationCommandException(HttpException httpError) + : base(httpError.HttpCode, httpError.Request, httpError.DiscordCode, httpError.Reason, httpError.Errors.ToArray()) + { + + } + } +} diff --git a/src/Discord.Net.Core/Net/HttpException.cs b/src/Discord.Net.Core/Net/HttpException.cs index ff9cf91f2..07551f0e7 100644 --- a/src/Discord.Net.Core/Net/HttpException.cs +++ b/src/Discord.Net.Core/Net/HttpException.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Collections.Immutable; using System.Net; namespace Discord.Net @@ -25,7 +27,7 @@ namespace Discord.Net /// JSON error code /// from Discord, or null if none. /// - public int? DiscordCode { get; } + public DiscordErrorCode? DiscordCode { get; } /// /// Gets the reason of the exception. /// @@ -34,6 +36,10 @@ namespace Discord.Net /// Gets the request object used to send the request. /// public IRequest Request { get; } + /// + /// Gets a collection of json errors describing what went wrong with the request. + /// + public IReadOnlyCollection Errors { get; } /// /// Initializes a new instance of the class. @@ -42,13 +48,14 @@ namespace Discord.Net /// The request that was sent prior to the exception. /// The Discord status code returned. /// The reason behind the exception. - public HttpException(HttpStatusCode httpCode, IRequest request, int? discordCode = null, string reason = null) - : base(CreateMessage(httpCode, discordCode, reason)) + public HttpException(HttpStatusCode httpCode, IRequest request, DiscordErrorCode? discordCode = null, string reason = null, DiscordJsonError[] errors = null) + : base(CreateMessage(httpCode, (int?)discordCode, reason)) { HttpCode = httpCode; Request = request; DiscordCode = discordCode; Reason = reason; + Errors = errors?.ToImmutableArray() ?? ImmutableArray.Empty; } private static string CreateMessage(HttpStatusCode httpCode, int? discordCode = null, string reason = null) diff --git a/src/Discord.Net.Core/Net/Rest/IRateLimitInfo.cs b/src/Discord.Net.Core/Net/Rest/IRateLimitInfo.cs new file mode 100644 index 000000000..816f25af4 --- /dev/null +++ b/src/Discord.Net.Core/Net/Rest/IRateLimitInfo.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord +{ + /// + /// Represents a generic ratelimit info. + /// + public interface IRateLimitInfo + { + /// + /// Gets whether or not this ratelimit info is global. + /// + bool IsGlobal { get; } + + /// + /// Gets the number of requests that can be made. + /// + int? Limit { get; } + + /// + /// Gets the number of remaining requests that can be made. + /// + int? Remaining { get; } + + /// + /// Gets the total time (in seconds) of when the current rate limit bucket will reset. Can have decimals to match previous millisecond ratelimit precision. + /// + int? RetryAfter { get; } + + /// + /// Gets the at which the rate limit resets. + /// + DateTimeOffset? Reset { get; } + + /// + /// Gets the absolute time when this ratelimit resets. + /// + TimeSpan? ResetAfter { get; } + + /// + /// Gets a unique string denoting the rate limit being encountered (non-inclusive of major parameters in the route path). + /// + string Bucket { get; } + + /// + /// Gets the amount of lag for the request. This is used to denote the precise time of when the ratelimit expires. + /// + TimeSpan? Lag { get; } + + /// + /// Gets the endpoint that this ratelimit info came from. + /// + string Endpoint { get; } + } +} diff --git a/src/Discord.Net.Core/RequestOptions.cs b/src/Discord.Net.Core/RequestOptions.cs index dbb240273..46aa2681f 100644 --- a/src/Discord.Net.Core/RequestOptions.cs +++ b/src/Discord.Net.Core/RequestOptions.cs @@ -1,5 +1,7 @@ using Discord.Net; +using System; using System.Threading; +using System.Threading.Tasks; namespace Discord { @@ -14,10 +16,10 @@ namespace Discord public static RequestOptions Default => new RequestOptions(); /// - /// Gets or sets the maximum time to wait for for this request to complete. + /// Gets or sets the maximum time to wait for this request to complete. /// /// - /// Gets or set the max time, in milliseconds, to wait for for this request to complete. If + /// Gets or set the max time, in milliseconds, to wait for this request to complete. If /// null, a request will not time out. If a rate limit has been triggered for this request's bucket /// and will not be unpaused in time, this request will fail immediately. /// @@ -57,6 +59,11 @@ namespace Discord /// public bool? UseSystemClock { get; set; } + /// + /// Gets or sets the callback to execute regarding ratelimits for this request. + /// + public Func RatelimitCallback { get; set; } + internal bool IgnoreState { get; set; } internal BucketId BucketId { get; set; } internal bool IsClientBucket { get; set; } @@ -71,6 +78,17 @@ namespace Discord return options.Clone(); } + internal void ExecuteRatelimitCallback(IRateLimitInfo info) + { + if (RatelimitCallback != null) + { + _ = Task.Run(async () => + { + await RatelimitCallback(info); + }); + } + } + /// /// Initializes a new class with the default request timeout set in /// . diff --git a/src/Discord.Net.Core/Utils/Cacheable.cs b/src/Discord.Net.Core/Utils/Cacheable.cs index 1857ae7a0..4aa768292 100644 --- a/src/Discord.Net.Core/Utils/Cacheable.cs +++ b/src/Discord.Net.Core/Utils/Cacheable.cs @@ -63,4 +63,60 @@ namespace Discord /// public async Task GetOrDownloadAsync() => HasValue ? Value : await DownloadAsync().ConfigureAwait(false); } + public struct Cacheable + where TCachedEntity : IEntity, TRelationship + where TDownloadableEntity : IEntity, TRelationship + where TId : IEquatable + { + /// + /// Gets whether this entity is cached. + /// + public bool HasValue { get; } + /// + /// Gets the ID of this entity. + /// + public TId Id { get; } + /// + /// Gets the entity if it could be pulled from cache. + /// + /// + /// This value is not guaranteed to be set; in cases where the entity cannot be pulled from cache, it is + /// null. + /// + public TCachedEntity Value { get; } + private Func> DownloadFunc { get; } + + internal Cacheable(TCachedEntity value, TId id, bool hasValue, Func> downloadFunc) + { + Value = value; + Id = id; + HasValue = hasValue; + DownloadFunc = downloadFunc; + } + + /// + /// Downloads this entity. + /// + /// Thrown when used from a user account. + /// Thrown when the message is deleted. + /// + /// A task that represents the asynchronous download operation. The task result contains the downloaded + /// entity. + /// + public async Task DownloadAsync() + { + return await DownloadFunc().ConfigureAwait(false); + } + + /// + /// Returns the cached entity if it exists; otherwise downloads it. + /// + /// Thrown when used from a user account. + /// Thrown when the message is deleted and is not in cache. + /// + /// A task that represents the asynchronous operation that attempts to get the message via cache or to + /// download the message. The task result contains the downloaded entity. + /// + public async Task GetOrDownloadAsync() => HasValue ? Value : await DownloadAsync().ConfigureAwait(false); + } } diff --git a/src/Discord.Net.Core/Utils/MentionUtils.cs b/src/Discord.Net.Core/Utils/MentionUtils.cs index 6ffb7eee6..059df6b5a 100644 --- a/src/Discord.Net.Core/Utils/MentionUtils.cs +++ b/src/Discord.Net.Core/Utils/MentionUtils.cs @@ -40,6 +40,7 @@ namespace Discord /// /// Parses a provided user mention string. /// + /// The user mention. /// Invalid mention format. public static ulong ParseUser(string text) { @@ -50,6 +51,8 @@ namespace Discord /// /// Tries to parse a provided user mention string. /// + /// The user mention. + /// The UserId of the user. public static bool TryParseUser(string text, out ulong userId) { if (text.Length >= 3 && text[0] == '<' && text[1] == '@' && text[text.Length - 1] == '>') diff --git a/src/Discord.Net.Core/Utils/Optional.cs b/src/Discord.Net.Core/Utils/Optional.cs index 348179699..985be9240 100644 --- a/src/Discord.Net.Core/Utils/Optional.cs +++ b/src/Discord.Net.Core/Utils/Optional.cs @@ -7,7 +7,7 @@ namespace Discord [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public struct Optional { - public static Optional Unspecified => default(Optional); + public static Optional Unspecified => default; private readonly T _value; /// Gets the value for this parameter. @@ -43,18 +43,18 @@ namespace Discord public override int GetHashCode() => IsSpecified ? _value.GetHashCode() : 0; public override string ToString() => IsSpecified ? _value?.ToString() : null; - private string DebuggerDisplay => IsSpecified ? (_value?.ToString() ?? "") : ""; + private string DebuggerDisplay => IsSpecified ? _value?.ToString() ?? "" : ""; - public static implicit operator Optional(T value) => new Optional(value); + public static implicit operator Optional(T value) => new(value); public static explicit operator T(Optional value) => value.Value; } public static class Optional { public static Optional Create() => Optional.Unspecified; - public static Optional Create(T value) => new Optional(value); + public static Optional Create(T value) => new(value); public static T? ToNullable(this Optional val) where T : struct - => val.IsSpecified ? val.Value : (T?)null; + => val.IsSpecified ? val.Value : null; } } diff --git a/src/Discord.Net.Core/Utils/Preconditions.cs b/src/Discord.Net.Core/Utils/Preconditions.cs index 60415852c..ff8eb7c0d 100644 --- a/src/Discord.Net.Core/Utils/Preconditions.cs +++ b/src/Discord.Net.Core/Utils/Preconditions.cs @@ -4,7 +4,7 @@ namespace Discord { internal static class Preconditions { - //Objects + #region Objects /// must not be . public static void NotNull(T obj, string name, string msg = null) where T : class { if (obj == null) throw CreateNotNullException(name, msg); } /// must not be . @@ -15,8 +15,9 @@ namespace Discord if (msg == null) return new ArgumentNullException(paramName: name); else return new ArgumentNullException(paramName: name, message: msg); } + #endregion - //Strings + #region Strings /// cannot be blank. public static void NotEmpty(string obj, string name, string msg = null) { if (obj.Length == 0) throw CreateNotEmptyException(name, msg); } /// cannot be blank. @@ -58,8 +59,9 @@ namespace Discord private static ArgumentException CreateNotEmptyException(string name, string msg) => new ArgumentException(message: msg ?? "Argument cannot be blank.", paramName: name); + #endregion - //Numerics + #region Numerics /// Value may not be equal to . public static void NotEqual(sbyte obj, sbyte value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } /// Value may not be equal to . @@ -271,8 +273,9 @@ namespace Discord private static ArgumentException CreateLessThanException(string name, string msg, T value) => new ArgumentException(message: msg ?? $"Value must be less than {value}.", paramName: name); + #endregion - // Bulk Delete + #region Bulk Delete /// Messages are younger than 2 weeks. public static void YoungerThanTwoWeeks(ulong[] collection, string name) { @@ -293,5 +296,6 @@ namespace Discord throw new ArgumentException(message: "The everyone role cannot be assigned to a user.", paramName: name); } } + #endregion } } diff --git a/src/Discord.Net.Core/Utils/UrlValidation.cs b/src/Discord.Net.Core/Utils/UrlValidation.cs new file mode 100644 index 000000000..8e877bd4e --- /dev/null +++ b/src/Discord.Net.Core/Utils/UrlValidation.cs @@ -0,0 +1,42 @@ +using System; + +namespace Discord.Utils +{ + internal static class UrlValidation + { + /// + /// Not full URL validation right now. Just ensures protocol is present and that it's either http or https + /// should be used for url buttons. + /// + /// The URL to validate before sending to Discord. + /// to allow the attachment:// protocol; otherwise . + /// A URL must include a protocol (http or https). + /// true if URL is valid by our standard, false if null, throws an error upon invalid. + public static bool Validate(string url, bool allowAttachments = false) + { + if (string.IsNullOrEmpty(url)) + return false; + if (!(url.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || url.StartsWith("https://", StringComparison.OrdinalIgnoreCase) || (allowAttachments ? url.StartsWith("attachment://", StringComparison.Ordinal) : false))) + throw new InvalidOperationException($"The url {url} must include a protocol (either {(allowAttachments ? "HTTP, HTTPS, or ATTACHMENT" : "HTTP or HTTPS")})"); + return true; + } + + /// + /// Not full URL validation right now. Just Ensures the protocol is either http, https, or discord + /// should be used everything other than url buttons. + /// + /// The URL to validate before sending to discord. + /// A URL must include a protocol (either http, https, or discord). + /// true if the URL is valid by our standard, false if null, throws an error upon invalid. + public static bool ValidateButton(string url) + { + if (string.IsNullOrEmpty(url)) + return false; + if (!(url.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || + url.StartsWith("https://", StringComparison.OrdinalIgnoreCase) || + url.StartsWith("discord://", StringComparison.OrdinalIgnoreCase))) + throw new InvalidOperationException($"The url {url} must include a protocol (either HTTP, HTTPS, or DISCORD)"); + return true; + } + } +} diff --git a/src/Discord.Net.Examples/Core/Entities/Channels/IMessageChannel.Examples.cs b/src/Discord.Net.Examples/Core/Entities/Channels/IMessageChannel.Examples.cs index 5f9d4b5d6..d920e9710 100644 --- a/src/Discord.Net.Examples/Core/Entities/Channels/IMessageChannel.Examples.cs +++ b/src/Discord.Net.Examples/Core/Entities/Channels/IMessageChannel.Examples.cs @@ -108,7 +108,6 @@ namespace Discord.Net.Examples.Core.Entities.Channels using (channel.EnterTypingState()) await LongRunningAsync(); #endregion - } } } diff --git a/src/Discord.Net.Examples/Discord.Net.Examples.csproj b/src/Discord.Net.Examples/Discord.Net.Examples.csproj index ec0253428..3371432b8 100644 --- a/src/Discord.Net.Examples/Discord.Net.Examples.csproj +++ b/src/Discord.Net.Examples/Discord.Net.Examples.csproj @@ -15,7 +15,7 @@ - + diff --git a/src/Discord.Net.Rest/API/Common/ActionRowComponent.cs b/src/Discord.Net.Rest/API/Common/ActionRowComponent.cs new file mode 100644 index 000000000..9dede7e03 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/ActionRowComponent.cs @@ -0,0 +1,32 @@ +using Newtonsoft.Json; +using System.Linq; + +namespace Discord.API +{ + internal class ActionRowComponent : IMessageComponent + { + [JsonProperty("type")] + public ComponentType Type { get; set; } + + [JsonProperty("components")] + public IMessageComponent[] Components { get; set; } + + internal ActionRowComponent() { } + internal ActionRowComponent(Discord.ActionRowComponent c) + { + Type = c.Type; + Components = c.Components?.Select(x => + { + return x.Type switch + { + ComponentType.Button => new ButtonComponent(x as Discord.ButtonComponent), + ComponentType.SelectMenu => new SelectMenuComponent(x as Discord.SelectMenuComponent), + _ => null + }; + }).ToArray(); + } + + [JsonIgnore] + string IMessageComponent.CustomId => null; + } +} diff --git a/src/Discord.Net.Rest/API/Common/Application.cs b/src/Discord.Net.Rest/API/Common/Application.cs index aba3e524b..4ef6940a2 100644 --- a/src/Discord.Net.Rest/API/Common/Application.cs +++ b/src/Discord.Net.Rest/API/Common/Application.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API @@ -8,7 +7,7 @@ namespace Discord.API [JsonProperty("description")] public string Description { get; set; } [JsonProperty("rpc_origins")] - public string[] RPCOrigins { get; set; } + public Optional RPCOrigins { get; set; } [JsonProperty("name")] public string Name { get; set; } [JsonProperty("id")] @@ -19,12 +18,16 @@ namespace Discord.API public bool IsBotPublic { get; set; } [JsonProperty("bot_require_code_grant")] public bool BotRequiresCodeGrant { get; set; } + [JsonProperty("install_params")] + public Optional InstallParams { get; set; } + [JsonProperty("team")] public Team Team { get; set; } [JsonProperty("flags"), Int53] - public Optional Flags { get; set; } + public Optional Flags { get; set; } [JsonProperty("owner")] public Optional Owner { get; set; } + public Optional Tags { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/ApplicationCommand.cs b/src/Discord.Net.Rest/API/Common/ApplicationCommand.cs new file mode 100644 index 000000000..81598b96e --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/ApplicationCommand.cs @@ -0,0 +1,28 @@ +using Newtonsoft.Json; + +namespace Discord.API +{ + internal class ApplicationCommand + { + [JsonProperty("id")] + public ulong Id { get; set; } + + [JsonProperty("type")] + public ApplicationCommandType Type { get; set; } = ApplicationCommandType.Slash; // defaults to 1 which is slash. + + [JsonProperty("application_id")] + public ulong ApplicationId { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("description")] + public string Description { get; set; } + + [JsonProperty("options")] + public Optional Options { get; set; } + + [JsonProperty("default_permission")] + public Optional DefaultPermissions { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Common/ApplicationCommandInteractionData.cs b/src/Discord.Net.Rest/API/Common/ApplicationCommandInteractionData.cs new file mode 100644 index 000000000..a98ed77d6 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/ApplicationCommandInteractionData.cs @@ -0,0 +1,22 @@ +using Newtonsoft.Json; + +namespace Discord.API +{ + internal class ApplicationCommandInteractionData : IResolvable, IDiscordInteractionData + { + [JsonProperty("id")] + public ulong Id { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("options")] + public Optional Options { get; set; } + + [JsonProperty("resolved")] + public Optional Resolved { get; set; } + + [JsonProperty("type")] + public ApplicationCommandType Type { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Common/ApplicationCommandInteractionDataOption.cs b/src/Discord.Net.Rest/API/Common/ApplicationCommandInteractionDataOption.cs new file mode 100644 index 000000000..1e488c4e6 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/ApplicationCommandInteractionDataOption.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; + +namespace Discord.API +{ + internal class ApplicationCommandInteractionDataOption + { + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("type")] + public ApplicationCommandOptionType Type { get; set; } + + [JsonProperty("value")] + public Optional Value { get; set; } + + [JsonProperty("options")] + public Optional Options { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Common/ApplicationCommandInteractionDataResolved.cs b/src/Discord.Net.Rest/API/Common/ApplicationCommandInteractionDataResolved.cs new file mode 100644 index 000000000..5b4b83e23 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/ApplicationCommandInteractionDataResolved.cs @@ -0,0 +1,22 @@ +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace Discord.API +{ + internal class ApplicationCommandInteractionDataResolved + { + [JsonProperty("users")] + public Optional> Users { get; set; } + + [JsonProperty("members")] + public Optional> Members { get; set; } + + [JsonProperty("channels")] + public Optional> Channels { get; set; } + + [JsonProperty("roles")] + public Optional> Roles { get; set; } + [JsonProperty("messages")] + public Optional> Messages { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Common/ApplicationCommandOption.cs b/src/Discord.Net.Rest/API/Common/ApplicationCommandOption.cs new file mode 100644 index 000000000..1207df282 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/ApplicationCommandOption.cs @@ -0,0 +1,88 @@ +using Newtonsoft.Json; +using System.Linq; + +namespace Discord.API +{ + internal class ApplicationCommandOption + { + [JsonProperty("type")] + public ApplicationCommandOptionType Type { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("description")] + public string Description { get; set; } + + [JsonProperty("default")] + public Optional Default { get; set; } + + [JsonProperty("required")] + public Optional Required { get; set; } + + [JsonProperty("choices")] + public Optional Choices { get; set; } + + [JsonProperty("options")] + public Optional Options { get; set; } + + [JsonProperty("autocomplete")] + public Optional Autocomplete { get; set; } + + [JsonProperty("min_value")] + public Optional MinValue { get; set; } + + [JsonProperty("max_value")] + public Optional MaxValue { get; set; } + + [JsonProperty("channel_types")] + public Optional ChannelTypes { get; set; } + + public ApplicationCommandOption() { } + + public ApplicationCommandOption(IApplicationCommandOption cmd) + { + Choices = cmd.Choices.Select(x => new ApplicationCommandOptionChoice + { + Name = x.Name, + Value = x.Value + }).ToArray(); + + Options = cmd.Options.Select(x => new ApplicationCommandOption(x)).ToArray(); + + ChannelTypes = cmd.ChannelTypes.ToArray(); + + Required = cmd.IsRequired ?? Optional.Unspecified; + Default = cmd.IsDefault ?? Optional.Unspecified; + MinValue = cmd.MinValue ?? Optional.Unspecified; + MaxValue = cmd.MaxValue ?? Optional.Unspecified; + + Name = cmd.Name; + Type = cmd.Type; + Description = cmd.Description; + } + public ApplicationCommandOption(ApplicationCommandOptionProperties option) + { + Choices = option.Choices?.Select(x => new ApplicationCommandOptionChoice + { + Name = x.Name, + Value = x.Value + }).ToArray() ?? Optional.Unspecified; + + Options = option.Options?.Select(x => new ApplicationCommandOption(x)).ToArray() ?? Optional.Unspecified; + + Required = option.IsRequired ?? Optional.Unspecified; + + Default = option.IsDefault ?? Optional.Unspecified; + MinValue = option.MinValue ?? Optional.Unspecified; + MaxValue = option.MaxValue ?? Optional.Unspecified; + + ChannelTypes = option.ChannelTypes?.ToArray() ?? Optional.Unspecified; + + Name = option.Name; + Type = option.Type; + Description = option.Description; + Autocomplete = option.IsAutocomplete; + } + } +} diff --git a/src/Discord.Net.Rest/API/Common/ApplicationCommandOptionChoice.cs b/src/Discord.Net.Rest/API/Common/ApplicationCommandOptionChoice.cs new file mode 100644 index 000000000..6f84437f6 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/ApplicationCommandOptionChoice.cs @@ -0,0 +1,13 @@ +using Newtonsoft.Json; + +namespace Discord.API +{ + internal class ApplicationCommandOptionChoice + { + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("value")] + public object Value { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Common/ApplicationCommandPermissions.cs b/src/Discord.Net.Rest/API/Common/ApplicationCommandPermissions.cs new file mode 100644 index 000000000..8bde80f50 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/ApplicationCommandPermissions.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace Discord.API +{ + internal class ApplicationCommandPermissions + { + [JsonProperty("id")] + public ulong Id { get; set; } + + [JsonProperty("type")] + public ApplicationCommandPermissionTarget Type { get; set; } + + [JsonProperty("permission")] + public bool Permission { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Common/Attachment.cs b/src/Discord.Net.Rest/API/Common/Attachment.cs index 4a651d9fa..7970dc9a5 100644 --- a/src/Discord.Net.Rest/API/Common/Attachment.cs +++ b/src/Discord.Net.Rest/API/Common/Attachment.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API @@ -9,6 +8,10 @@ namespace Discord.API public ulong Id { get; set; } [JsonProperty("filename")] public string Filename { get; set; } + [JsonProperty("description")] + public Optional Description { get; set; } + [JsonProperty("content_type")] + public Optional ContentType { get; set; } [JsonProperty("size")] public int Size { get; set; } [JsonProperty("url")] @@ -19,5 +22,7 @@ namespace Discord.API public Optional Height { get; set; } [JsonProperty("width")] public Optional Width { get; set; } + [JsonProperty("ephemeral")] + public Optional Ephemeral { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/AuditLog.cs b/src/Discord.Net.Rest/API/Common/AuditLog.cs index cd8ad147d..c8bd6d3e2 100644 --- a/src/Discord.Net.Rest/API/Common/AuditLog.cs +++ b/src/Discord.Net.Rest/API/Common/AuditLog.cs @@ -1,4 +1,4 @@ -using Newtonsoft.Json; +using Newtonsoft.Json; namespace Discord.API { @@ -7,6 +7,12 @@ namespace Discord.API [JsonProperty("webhooks")] public Webhook[] Webhooks { get; set; } + [JsonProperty("threads")] + public Channel[] Threads { get; set; } + + [JsonProperty("integrations")] + public Integration[] Integrations { get; set; } + [JsonProperty("users")] public User[] Users { get; set; } diff --git a/src/Discord.Net.Rest/API/Common/AuditLogEntry.cs b/src/Discord.Net.Rest/API/Common/AuditLogEntry.cs index 7458a19cb..9626ad67e 100644 --- a/src/Discord.Net.Rest/API/Common/AuditLogEntry.cs +++ b/src/Discord.Net.Rest/API/Common/AuditLogEntry.cs @@ -1,4 +1,4 @@ -using Newtonsoft.Json; +using Newtonsoft.Json; namespace Discord.API { diff --git a/src/Discord.Net.Rest/API/Common/AutocompleteInteractionData.cs b/src/Discord.Net.Rest/API/Common/AutocompleteInteractionData.cs new file mode 100644 index 000000000..2184a0e98 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/AutocompleteInteractionData.cs @@ -0,0 +1,22 @@ +using Newtonsoft.Json; + +namespace Discord.API +{ + internal class AutocompleteInteractionData : IDiscordInteractionData + { + [JsonProperty("id")] + public ulong Id { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("type")] + public ApplicationCommandType Type { get; set; } + + [JsonProperty("version")] + public ulong Version { get; set; } + + [JsonProperty("options")] + public AutocompleteInteractionDataOption[] Options { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Common/AutocompleteInteractionDataOption.cs b/src/Discord.Net.Rest/API/Common/AutocompleteInteractionDataOption.cs new file mode 100644 index 000000000..1419f93b6 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/AutocompleteInteractionDataOption.cs @@ -0,0 +1,22 @@ +using Newtonsoft.Json; + +namespace Discord.API +{ + internal class AutocompleteInteractionDataOption + { + [JsonProperty("type")] + public ApplicationCommandOptionType Type { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("options")] + public Optional Options { get; set; } + + [JsonProperty("value")] + public Optional Value { get; set; } + + [JsonProperty("focused")] + public Optional Focused { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Common/Ban.cs b/src/Discord.Net.Rest/API/Common/Ban.cs index 202004f53..ff47c7904 100644 --- a/src/Discord.Net.Rest/API/Common/Ban.cs +++ b/src/Discord.Net.Rest/API/Common/Ban.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API diff --git a/src/Discord.Net.Rest/API/Common/ButtonComponent.cs b/src/Discord.Net.Rest/API/Common/ButtonComponent.cs new file mode 100644 index 000000000..7f737d7ad --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/ButtonComponent.cs @@ -0,0 +1,63 @@ +using Newtonsoft.Json; + +namespace Discord.API +{ + internal class ButtonComponent : IMessageComponent + { + [JsonProperty("type")] + public ComponentType Type { get; set; } + + [JsonProperty("style")] + public ButtonStyle Style { get; set; } + + [JsonProperty("label")] + public Optional Label { get; set; } + + [JsonProperty("emoji")] + public Optional Emote { get; set; } + + [JsonProperty("custom_id")] + public Optional CustomId { get; set; } + + [JsonProperty("url")] + public Optional Url { get; set; } + + [JsonProperty("disabled")] + public Optional Disabled { get; set; } + + public ButtonComponent() { } + + public ButtonComponent(Discord.ButtonComponent c) + { + Type = c.Type; + Style = c.Style; + Label = c.Label; + CustomId = c.CustomId; + Url = c.Url; + Disabled = c.IsDisabled; + + if (c.Emote != null) + { + if (c.Emote is Emote e) + { + Emote = new Emoji + { + Name = e.Name, + Animated = e.Animated, + Id = e.Id + }; + } + else + { + Emote = new Emoji + { + Name = c.Emote.Name + }; + } + } + } + + [JsonIgnore] + string IMessageComponent.CustomId => CustomId.GetValueOrDefault(); + } +} diff --git a/src/Discord.Net.Rest/API/Common/Channel.cs b/src/Discord.Net.Rest/API/Common/Channel.cs index 57a5ce9ab..afd219b63 100644 --- a/src/Discord.Net.Rest/API/Common/Channel.cs +++ b/src/Discord.Net.Rest/API/Common/Channel.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; using System; @@ -49,5 +48,21 @@ namespace Discord.API //GroupChannel [JsonProperty("icon")] public Optional Icon { get; set; } + + //ThreadChannel + [JsonProperty("member")] + public Optional ThreadMember { get; set; } + + [JsonProperty("thread_metadata")] + public Optional ThreadMetadata { get; set; } + + [JsonProperty("owner_id")] + public Optional OwnerId { get; set; } + + [JsonProperty("message_count")] + public Optional MessageCount { get; set; } + + [JsonProperty("member_count")] + public Optional MemberCount { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/ChannelThreads.cs b/src/Discord.Net.Rest/API/Common/ChannelThreads.cs new file mode 100644 index 000000000..94b2396bf --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/ChannelThreads.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace Discord.API.Rest +{ + internal class ChannelThreads + { + [JsonProperty("threads")] + public Channel[] Threads { get; set; } + + [JsonProperty("members")] + public ThreadMember[] Members { get; set; } + + [JsonProperty("has_more")] + public bool HasMore { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Common/Connection.cs b/src/Discord.Net.Rest/API/Common/Connection.cs index ad0a76ac1..bd8de3902 100644 --- a/src/Discord.Net.Rest/API/Common/Connection.cs +++ b/src/Discord.Net.Rest/API/Common/Connection.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; using System.Collections.Generic; diff --git a/src/Discord.Net.Rest/API/Common/DiscordError.cs b/src/Discord.Net.Rest/API/Common/DiscordError.cs new file mode 100644 index 000000000..ac1e5e13d --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/DiscordError.cs @@ -0,0 +1,20 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.API +{ + [JsonConverter(typeof(Discord.Net.Converters.DiscordErrorConverter))] + internal class DiscordError + { + [JsonProperty("message")] + public string Message { get; set; } + [JsonProperty("code")] + public DiscordErrorCode Code { get; set; } + [JsonProperty("errors")] + public Optional Errors { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Common/Embed.cs b/src/Discord.Net.Rest/API/Common/Embed.cs index fbf20d987..77efa12aa 100644 --- a/src/Discord.Net.Rest/API/Common/Embed.cs +++ b/src/Discord.Net.Rest/API/Common/Embed.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using System; using Newtonsoft.Json; using Discord.Net.Converters; diff --git a/src/Discord.Net.Rest/API/Common/EmbedImage.cs b/src/Discord.Net.Rest/API/Common/EmbedImage.cs index e650d99f4..6b5db0681 100644 --- a/src/Discord.Net.Rest/API/Common/EmbedImage.cs +++ b/src/Discord.Net.Rest/API/Common/EmbedImage.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API diff --git a/src/Discord.Net.Rest/API/Common/EmbedProvider.cs b/src/Discord.Net.Rest/API/Common/EmbedProvider.cs index e01261483..ed0f7c3c8 100644 --- a/src/Discord.Net.Rest/API/Common/EmbedProvider.cs +++ b/src/Discord.Net.Rest/API/Common/EmbedProvider.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API diff --git a/src/Discord.Net.Rest/API/Common/EmbedThumbnail.cs b/src/Discord.Net.Rest/API/Common/EmbedThumbnail.cs index 9c87ca46b..dd25a1a26 100644 --- a/src/Discord.Net.Rest/API/Common/EmbedThumbnail.cs +++ b/src/Discord.Net.Rest/API/Common/EmbedThumbnail.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API diff --git a/src/Discord.Net.Rest/API/Common/EmbedVideo.cs b/src/Discord.Net.Rest/API/Common/EmbedVideo.cs index 3a034d244..f668217f0 100644 --- a/src/Discord.Net.Rest/API/Common/EmbedVideo.cs +++ b/src/Discord.Net.Rest/API/Common/EmbedVideo.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API diff --git a/src/Discord.Net.Rest/API/Common/Emoji.cs b/src/Discord.Net.Rest/API/Common/Emoji.cs index 945cc6d7e..ff0baa73e 100644 --- a/src/Discord.Net.Rest/API/Common/Emoji.cs +++ b/src/Discord.Net.Rest/API/Common/Emoji.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API diff --git a/src/Discord.Net.Rest/API/Common/Error.cs b/src/Discord.Net.Rest/API/Common/Error.cs new file mode 100644 index 000000000..a2b1777a3 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/Error.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace Discord.API +{ + internal class Error + { + [JsonProperty("code")] + public string Code { get; set; } + [JsonProperty("message")] + public string Message { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Common/Game.cs b/src/Discord.Net.Rest/API/Common/Game.cs index d3a618697..105ce0d73 100644 --- a/src/Discord.Net.Rest/API/Common/Game.cs +++ b/src/Discord.Net.Rest/API/Common/Game.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using System.Runtime.Serialization; @@ -41,6 +40,8 @@ namespace Discord.API public Optional Emoji { get; set; } [JsonProperty("created_at")] public Optional CreatedAt { get; set; } + //[JsonProperty("buttons")] + //public Optional Buttons { get; set; } [OnError] internal void OnError(StreamingContext context, ErrorContext errorContext) diff --git a/src/Discord.Net.Rest/API/Common/Guild.cs b/src/Discord.Net.Rest/API/Common/Guild.cs index bd25c7e1a..d550c54a0 100644 --- a/src/Discord.Net.Rest/API/Common/Guild.cs +++ b/src/Discord.Net.Rest/API/Common/Guild.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API @@ -36,7 +35,7 @@ namespace Discord.API [JsonProperty("emojis")] public Emoji[] Emojis { get; set; } [JsonProperty("features")] - public string[] Features { get; set; } + public GuildFeatures Features { get; set; } [JsonProperty("mfa_level")] public MfaLevel MfaLevel { get; set; } [JsonProperty("application_id")] @@ -76,5 +75,13 @@ namespace Discord.API public Optional ApproximateMemberCount { get; set; } [JsonProperty("approximate_presence_count")] public Optional ApproximatePresenceCount { get; set; } + [JsonProperty("threads")] + public Optional Threads { get; set; } + [JsonProperty("nsfw_level")] + public NsfwLevel NsfwLevel { get; set; } + [JsonProperty("stickers")] + public Sticker[] Stickers { get; set; } + [JsonProperty("premium_progress_bar_enabled")] + public Optional IsBoostProgressBarEnabled { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/GuildApplicationCommandPermissions.cs b/src/Discord.Net.Rest/API/Common/GuildApplicationCommandPermissions.cs new file mode 100644 index 000000000..cc74299f7 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/GuildApplicationCommandPermissions.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; + +namespace Discord.API +{ + internal class GuildApplicationCommandPermission + { + [JsonProperty("id")] + public ulong Id { get; set; } + + [JsonProperty("application_id")] + public ulong ApplicationId { get; set; } + + [JsonProperty("guild_id")] + public ulong GuildId { get; set; } + + [JsonProperty("permissions")] + public ApplicationCommandPermissions[] Permissions { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Common/GuildMember.cs b/src/Discord.Net.Rest/API/Common/GuildMember.cs index fc2092d6c..9b888e86a 100644 --- a/src/Discord.Net.Rest/API/Common/GuildMember.cs +++ b/src/Discord.Net.Rest/API/Common/GuildMember.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; using System; @@ -10,6 +9,8 @@ namespace Discord.API public User User { get; set; } [JsonProperty("nick")] public Optional Nick { get; set; } + [JsonProperty("avatar")] + public Optional Avatar { get; set; } [JsonProperty("roles")] public Optional Roles { get; set; } [JsonProperty("joined_at")] diff --git a/src/Discord.Net.Rest/API/Common/GuildScheduledEvent.cs b/src/Discord.Net.Rest/API/Common/GuildScheduledEvent.cs new file mode 100644 index 000000000..338c24dc9 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/GuildScheduledEvent.cs @@ -0,0 +1,43 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.API +{ + internal class GuildScheduledEvent + { + [JsonProperty("id")] + public ulong Id { get; set; } + [JsonProperty("guild_id")] + public ulong GuildId { get; set; } + [JsonProperty("channel_id")] + public Optional ChannelId { get; set; } + [JsonProperty("creator_id")] + public Optional CreatorId { get; set; } + [JsonProperty("name")] + public string Name { get; set; } + [JsonProperty("description")] + public Optional Description { get; set; } + [JsonProperty("scheduled_start_time")] + public DateTimeOffset ScheduledStartTime { get; set; } + [JsonProperty("scheduled_end_time")] + public DateTimeOffset? ScheduledEndTime { get; set; } + [JsonProperty("privacy_level")] + public GuildScheduledEventPrivacyLevel PrivacyLevel { get; set; } + [JsonProperty("status")] + public GuildScheduledEventStatus Status { get; set; } + [JsonProperty("entity_type")] + public GuildScheduledEventType EntityType { get; set; } + [JsonProperty("entity_id")] + public ulong? EntityId { get; set; } + [JsonProperty("entity_metadata")] + public GuildScheduledEventEntityMetadata EntityMetadata { get; set; } + [JsonProperty("creator")] + public Optional Creator { get; set; } + [JsonProperty("user_count")] + public Optional UserCount { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Common/GuildScheduledEventEntityMetadata.cs b/src/Discord.Net.Rest/API/Common/GuildScheduledEventEntityMetadata.cs new file mode 100644 index 000000000..1db38c0ae --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/GuildScheduledEventEntityMetadata.cs @@ -0,0 +1,15 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.API +{ + internal class GuildScheduledEventEntityMetadata + { + [JsonProperty("location")] + public Optional Location { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Common/GuildScheduledEventUser.cs b/src/Discord.Net.Rest/API/Common/GuildScheduledEventUser.cs new file mode 100644 index 000000000..1b0b93763 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/GuildScheduledEventUser.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.API +{ + internal class GuildScheduledEventUser + { + [JsonProperty("user")] + public User User { get; set; } + [JsonProperty("member")] + public Optional Member { get; set; } + [JsonProperty("guild_scheduled_event_id")] + public ulong GuildScheduledEventId { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Common/GuildWidget.cs b/src/Discord.Net.Rest/API/Common/GuildWidget.cs index c15ad8aac..6b1d29cce 100644 --- a/src/Discord.Net.Rest/API/Common/GuildWidget.cs +++ b/src/Discord.Net.Rest/API/Common/GuildWidget.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API diff --git a/src/Discord.Net.Rest/API/Common/InstallParams.cs b/src/Discord.Net.Rest/API/Common/InstallParams.cs new file mode 100644 index 000000000..1fb987f30 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/InstallParams.cs @@ -0,0 +1,17 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.API +{ + internal class InstallParams + { + [JsonProperty("scopes")] + public string[] Scopes { get; set; } + [JsonProperty("permissions")] + public ulong Permission { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Common/Integration.cs b/src/Discord.Net.Rest/API/Common/Integration.cs index 821359975..47d67e149 100644 --- a/src/Discord.Net.Rest/API/Common/Integration.cs +++ b/src/Discord.Net.Rest/API/Common/Integration.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; using System; diff --git a/src/Discord.Net.Rest/API/Common/IntegrationAccount.cs b/src/Discord.Net.Rest/API/Common/IntegrationAccount.cs index 22831e795..a8d33931c 100644 --- a/src/Discord.Net.Rest/API/Common/IntegrationAccount.cs +++ b/src/Discord.Net.Rest/API/Common/IntegrationAccount.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API diff --git a/src/Discord.Net.Rest/API/Common/Interaction.cs b/src/Discord.Net.Rest/API/Common/Interaction.cs new file mode 100644 index 000000000..7f953384d --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/Interaction.cs @@ -0,0 +1,41 @@ +using Newtonsoft.Json; + +namespace Discord.API +{ + [JsonConverter(typeof(Net.Converters.InteractionConverter))] + internal class Interaction + { + [JsonProperty("id")] + public ulong Id { get; set; } + + [JsonProperty("application_id")] + public ulong ApplicationId { get; set; } + + [JsonProperty("type")] + public InteractionType Type { get; set; } + + [JsonProperty("data")] + public Optional Data { get; set; } + + [JsonProperty("guild_id")] + public Optional GuildId { get; set; } + + [JsonProperty("channel_id")] + public Optional ChannelId { get; set; } + + [JsonProperty("member")] + public Optional Member { get; set; } + + [JsonProperty("user")] + public Optional User { get; set; } + + [JsonProperty("token")] + public string Token { get; set; } + + [JsonProperty("version")] + public int Version { get; set; } + + [JsonProperty("message")] + public Optional Message { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Common/InteractionCallbackData.cs b/src/Discord.Net.Rest/API/Common/InteractionCallbackData.cs new file mode 100644 index 000000000..b07ebff49 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/InteractionCallbackData.cs @@ -0,0 +1,28 @@ +using Newtonsoft.Json; + +namespace Discord.API +{ + internal class InteractionCallbackData + { + [JsonProperty("tts")] + public Optional TTS { get; set; } + + [JsonProperty("content")] + public Optional Content { get; set; } + + [JsonProperty("embeds")] + public Optional Embeds { get; set; } + + [JsonProperty("allowed_mentions")] + public Optional AllowedMentions { get; set; } + + [JsonProperty("flags")] + public Optional Flags { get; set; } + + [JsonProperty("components")] + public Optional Components { get; set; } + + [JsonProperty("choices")] + public Optional Choices { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Common/InteractionResponse.cs b/src/Discord.Net.Rest/API/Common/InteractionResponse.cs new file mode 100644 index 000000000..93d4cd307 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/InteractionResponse.cs @@ -0,0 +1,13 @@ +using Newtonsoft.Json; + +namespace Discord.API +{ + internal class InteractionResponse + { + [JsonProperty("type")] + public InteractionResponseType Type { get; set; } + + [JsonProperty("data")] + public Optional Data { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Common/Invite.cs b/src/Discord.Net.Rest/API/Common/Invite.cs index aba267f34..f9d53bad6 100644 --- a/src/Discord.Net.Rest/API/Common/Invite.cs +++ b/src/Discord.Net.Rest/API/Common/Invite.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API diff --git a/src/Discord.Net.Rest/API/Common/InviteChannel.cs b/src/Discord.Net.Rest/API/Common/InviteChannel.cs index f8f2a34f2..d601e65fe 100644 --- a/src/Discord.Net.Rest/API/Common/InviteChannel.cs +++ b/src/Discord.Net.Rest/API/Common/InviteChannel.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API diff --git a/src/Discord.Net.Rest/API/Common/InviteGuild.cs b/src/Discord.Net.Rest/API/Common/InviteGuild.cs index 3d6d7cd74..f5c634e4e 100644 --- a/src/Discord.Net.Rest/API/Common/InviteGuild.cs +++ b/src/Discord.Net.Rest/API/Common/InviteGuild.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API diff --git a/src/Discord.Net.Rest/API/Common/InviteMetadata.cs b/src/Discord.Net.Rest/API/Common/InviteMetadata.cs index f818de699..a06d06969 100644 --- a/src/Discord.Net.Rest/API/Common/InviteMetadata.cs +++ b/src/Discord.Net.Rest/API/Common/InviteMetadata.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; using System; diff --git a/src/Discord.Net.Rest/API/Common/InviteVanity.cs b/src/Discord.Net.Rest/API/Common/InviteVanity.cs index a36ddee46..412795aa6 100644 --- a/src/Discord.Net.Rest/API/Common/InviteVanity.cs +++ b/src/Discord.Net.Rest/API/Common/InviteVanity.cs @@ -2,10 +2,20 @@ using Newtonsoft.Json; namespace Discord.API { + /// + /// Represents a vanity invite. + /// public class InviteVanity { + /// + /// The unique code for the invite link. + /// [JsonProperty("code")] public string Code { get; set; } + + /// + /// The total amount of vanity invite uses. + /// [JsonProperty("uses")] public int Uses { get; set; } } diff --git a/src/Discord.Net.Rest/API/Common/Message.cs b/src/Discord.Net.Rest/API/Common/Message.cs index 6ea2c29ff..d33a03fe5 100644 --- a/src/Discord.Net.Rest/API/Common/Message.cs +++ b/src/Discord.Net.Rest/API/Common/Message.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; using System; @@ -33,7 +32,7 @@ namespace Discord.API [JsonProperty("mention_everyone")] public Optional MentionEveryone { get; set; } [JsonProperty("mentions")] - public Optional[]> UserMentions { get; set; } + public Optional UserMentions { get; set; } [JsonProperty("mention_roles")] public Optional RoleMentions { get; set; } [JsonProperty("attachments")] @@ -58,7 +57,10 @@ namespace Discord.API public Optional AllowedMentions { get; set; } [JsonProperty("referenced_message")] public Optional ReferencedMessage { get; set; } - [JsonProperty("stickers")] - public Optional Stickers { get; set; } + [JsonProperty("components")] + public Optional Components { get; set; } + public Optional Interaction { get; set; } + [JsonProperty("sticker_items")] + public Optional StickerItems { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/MessageComponentInteractionData.cs b/src/Discord.Net.Rest/API/Common/MessageComponentInteractionData.cs new file mode 100644 index 000000000..a7760911c --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/MessageComponentInteractionData.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace Discord.API +{ + internal class MessageComponentInteractionData : IDiscordInteractionData + { + [JsonProperty("custom_id")] + public string CustomId { get; set; } + + [JsonProperty("component_type")] + public ComponentType ComponentType { get; set; } + + [JsonProperty("values")] + public Optional Values { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Common/MessageInteraction.cs b/src/Discord.Net.Rest/API/Common/MessageInteraction.cs new file mode 100644 index 000000000..48f278396 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/MessageInteraction.cs @@ -0,0 +1,21 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.API +{ + internal class MessageInteraction + { + [JsonProperty("id")] + public ulong Id { get; set; } + [JsonProperty("type")] + public InteractionType Type { get; set; } + [JsonProperty("name")] + public string Name { get; set; } + [JsonProperty("user")] + public User User { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Common/NitroStickerPacks.cs b/src/Discord.Net.Rest/API/Common/NitroStickerPacks.cs new file mode 100644 index 000000000..cc2f0d963 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/NitroStickerPacks.cs @@ -0,0 +1,11 @@ +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace Discord.API +{ + internal class NitroStickerPacks + { + [JsonProperty("sticker_packs")] + public List StickerPacks { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Common/Overwrite.cs b/src/Discord.Net.Rest/API/Common/Overwrite.cs index 3d94b0640..a1fb534d8 100644 --- a/src/Discord.Net.Rest/API/Common/Overwrite.cs +++ b/src/Discord.Net.Rest/API/Common/Overwrite.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API diff --git a/src/Discord.Net.Rest/API/Common/Presence.cs b/src/Discord.Net.Rest/API/Common/Presence.cs index b44e9185d..23f871ae6 100644 --- a/src/Discord.Net.Rest/API/Common/Presence.cs +++ b/src/Discord.Net.Rest/API/Common/Presence.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; using System; using System.Collections.Generic; diff --git a/src/Discord.Net.Rest/API/Common/PropertyErrorDescription.cs b/src/Discord.Net.Rest/API/Common/PropertyErrorDescription.cs new file mode 100644 index 000000000..145288e5d --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/PropertyErrorDescription.cs @@ -0,0 +1,17 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.API +{ + internal class ErrorDetails + { + [JsonProperty("name")] + public Optional Name { get; set; } + [JsonProperty("errors")] + public Error[] Errors { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Common/ReadState.cs b/src/Discord.Net.Rest/API/Common/ReadState.cs index 6ea6e4bd0..9a66880c6 100644 --- a/src/Discord.Net.Rest/API/Common/ReadState.cs +++ b/src/Discord.Net.Rest/API/Common/ReadState.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API diff --git a/src/Discord.Net.Rest/API/Common/Relationship.cs b/src/Discord.Net.Rest/API/Common/Relationship.cs index ecbb96f80..d17f766af 100644 --- a/src/Discord.Net.Rest/API/Common/Relationship.cs +++ b/src/Discord.Net.Rest/API/Common/Relationship.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API diff --git a/src/Discord.Net.Rest/API/Common/RelationshipType.cs b/src/Discord.Net.Rest/API/Common/RelationshipType.cs index 0ed99f396..776ba156b 100644 --- a/src/Discord.Net.Rest/API/Common/RelationshipType.cs +++ b/src/Discord.Net.Rest/API/Common/RelationshipType.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 namespace Discord.API { internal enum RelationshipType diff --git a/src/Discord.Net.Rest/API/Common/Role.cs b/src/Discord.Net.Rest/API/Common/Role.cs index c655175da..81f54ccc0 100644 --- a/src/Discord.Net.Rest/API/Common/Role.cs +++ b/src/Discord.Net.Rest/API/Common/Role.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API @@ -9,6 +8,10 @@ namespace Discord.API public ulong Id { get; set; } [JsonProperty("name")] public string Name { get; set; } + [JsonProperty("icon")] + public Optional Icon { get; set; } + [JsonProperty("unicode_emoji")] + public Optional Emoji { get; set; } [JsonProperty("color")] public uint Color { get; set; } [JsonProperty("hoist")] diff --git a/src/Discord.Net.Rest/API/Common/RoleTags.cs b/src/Discord.Net.Rest/API/Common/RoleTags.cs index 6446f2037..9ddd39a64 100644 --- a/src/Discord.Net.Rest/API/Common/RoleTags.cs +++ b/src/Discord.Net.Rest/API/Common/RoleTags.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API diff --git a/src/Discord.Net.Rest/API/Common/SelectMenuComponent.cs b/src/Discord.Net.Rest/API/Common/SelectMenuComponent.cs new file mode 100644 index 000000000..0886a8fe9 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/SelectMenuComponent.cs @@ -0,0 +1,42 @@ +using Newtonsoft.Json; +using System.Linq; + +namespace Discord.API +{ + internal class SelectMenuComponent : IMessageComponent + { + [JsonProperty("type")] + public ComponentType Type { get; set; } + + [JsonProperty("custom_id")] + public string CustomId { get; set; } + + [JsonProperty("options")] + public SelectMenuOption[] Options { get; set; } + + [JsonProperty("placeholder")] + public Optional Placeholder { get; set; } + + [JsonProperty("min_values")] + public int MinValues { get; set; } + + [JsonProperty("max_values")] + public int MaxValues { get; set; } + + [JsonProperty("disabled")] + public bool Disabled { get; set; } + + public SelectMenuComponent() { } + + public SelectMenuComponent(Discord.SelectMenuComponent component) + { + Type = component.Type; + CustomId = component.CustomId; + Options = component.Options.Select(x => new SelectMenuOption(x)).ToArray(); + Placeholder = component.Placeholder; + MinValues = component.MinValues; + MaxValues = component.MaxValues; + Disabled = component.IsDisabled; + } + } +} diff --git a/src/Discord.Net.Rest/API/Common/SelectMenuOption.cs b/src/Discord.Net.Rest/API/Common/SelectMenuOption.cs new file mode 100644 index 000000000..d0a25a829 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/SelectMenuOption.cs @@ -0,0 +1,53 @@ +using Newtonsoft.Json; + +namespace Discord.API +{ + internal class SelectMenuOption + { + [JsonProperty("label")] + public string Label { get; set; } + + [JsonProperty("value")] + public string Value { get; set; } + + [JsonProperty("description")] + public Optional Description { get; set; } + + [JsonProperty("emoji")] + public Optional Emoji { get; set; } + + [JsonProperty("default")] + public Optional Default { get; set; } + + public SelectMenuOption() { } + + public SelectMenuOption(Discord.SelectMenuOption option) + { + Label = option.Label; + Value = option.Value; + Description = option.Description; + + if (option.Emote != null) + { + if (option.Emote is Emote e) + { + Emoji = new Emoji + { + Name = e.Name, + Animated = e.Animated, + Id = e.Id + }; + } + else + { + Emoji = new Emoji + { + Name = option.Emote.Name + }; + } + } + + Default = option.IsDefault ?? Optional.Unspecified; + } + } +} diff --git a/src/Discord.Net.Rest/API/Common/StageInstance.cs b/src/Discord.Net.Rest/API/Common/StageInstance.cs new file mode 100644 index 000000000..3ec623949 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/StageInstance.cs @@ -0,0 +1,25 @@ +using Newtonsoft.Json; + +namespace Discord.API +{ + internal class StageInstance + { + [JsonProperty("id")] + public ulong Id { get; set; } + + [JsonProperty("guild_id")] + public ulong GuildId { get; set; } + + [JsonProperty("channel_id")] + public ulong ChannelId { get; set; } + + [JsonProperty("topic")] + public string Topic { get; set; } + + [JsonProperty("privacy_level")] + public StagePrivacyLevel PrivacyLevel { get; set; } + + [JsonProperty("discoverable_disabled")] + public bool DiscoverableDisabled { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Common/Sticker.cs b/src/Discord.Net.Rest/API/Common/Sticker.cs index 0d1cac974..b2c58d57c 100644 --- a/src/Discord.Net.Rest/API/Common/Sticker.cs +++ b/src/Discord.Net.Rest/API/Common/Sticker.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API @@ -12,14 +11,20 @@ namespace Discord.API [JsonProperty("name")] public string Name { get; set; } [JsonProperty("description")] - public string Desription { get; set; } + public string Description { get; set; } [JsonProperty("tags")] public Optional Tags { get; set; } - [JsonProperty("asset")] - public string Asset { get; set; } - [JsonProperty("preview_asset")] - public string PreviewAsset { get; set; } + [JsonProperty("type")] + public StickerType Type { get; set; } [JsonProperty("format_type")] public StickerFormatType FormatType { get; set; } + [JsonProperty("available")] + public bool? Available { get; set; } + [JsonProperty("guild_id")] + public Optional GuildId { get; set; } + [JsonProperty("user")] + public Optional User { get; set; } + [JsonProperty("sort_value")] + public int? SortValue { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/StickerItem.cs b/src/Discord.Net.Rest/API/Common/StickerItem.cs new file mode 100644 index 000000000..4b24f711b --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/StickerItem.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace Discord.API +{ + internal class StickerItem + { + [JsonProperty("id")] + public ulong Id { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("format_type")] + public StickerFormatType FormatType { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Common/StickerPack.cs b/src/Discord.Net.Rest/API/Common/StickerPack.cs new file mode 100644 index 000000000..3daaac5bf --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/StickerPack.cs @@ -0,0 +1,22 @@ +using Newtonsoft.Json; + +namespace Discord.API +{ + internal class StickerPack + { + [JsonProperty("id")] + public ulong Id { get; set; } + [JsonProperty("stickers")] + public Sticker[] Stickers { get; set; } + [JsonProperty("name")] + public string Name { get; set; } + [JsonProperty("sku_id")] + public ulong SkuId { get; set; } + [JsonProperty("cover_sticker_id")] + public Optional CoverStickerId { get; set; } + [JsonProperty("description")] + public string Description { get; set; } + [JsonProperty("banner_asset_id")] + public ulong BannerAssetId { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Common/Team.cs b/src/Discord.Net.Rest/API/Common/Team.cs index 852368522..b421dc18c 100644 --- a/src/Discord.Net.Rest/API/Common/Team.cs +++ b/src/Discord.Net.Rest/API/Common/Team.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API diff --git a/src/Discord.Net.Rest/API/Common/TeamMember.cs b/src/Discord.Net.Rest/API/Common/TeamMember.cs index 788f73b61..f3cba608e 100644 --- a/src/Discord.Net.Rest/API/Common/TeamMember.cs +++ b/src/Discord.Net.Rest/API/Common/TeamMember.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API diff --git a/src/Discord.Net.Rest/API/Common/ThreadMember.cs b/src/Discord.Net.Rest/API/Common/ThreadMember.cs new file mode 100644 index 000000000..3e30d2c95 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/ThreadMember.cs @@ -0,0 +1,26 @@ +using Newtonsoft.Json; +using System; + +namespace Discord.API +{ + internal class ThreadMember + { + [JsonProperty("id")] + public Optional Id { get; set; } + + [JsonProperty("user_id")] + public Optional UserId { get; set; } + + [JsonProperty("join_timestamp")] + public DateTimeOffset JoinTimestamp { get; set; } + + [JsonProperty("presence")] + public Optional Presence { get; set; } + + [JsonProperty("member")] + public Optional Member { get; set; } + + [JsonProperty("flags")] + public int Flags { get; set; } // No enum type (yet?) + } +} diff --git a/src/Discord.Net.Rest/API/Common/ThreadMetadata.cs b/src/Discord.Net.Rest/API/Common/ThreadMetadata.cs new file mode 100644 index 000000000..39e9bd13e --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/ThreadMetadata.cs @@ -0,0 +1,20 @@ +using Newtonsoft.Json; +using System; + +namespace Discord.API +{ + internal class ThreadMetadata + { + [JsonProperty("archived")] + public bool Archived { get; set; } + + [JsonProperty("auto_archive_duration")] + public ThreadArchiveDuration AutoArchiveDuration { get; set; } + + [JsonProperty("archive_timestamp")] + public DateTimeOffset ArchiveTimestamp { get; set; } + + [JsonProperty("locked")] + public Optional Locked { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Common/User.cs b/src/Discord.Net.Rest/API/Common/User.cs index d1f436afb..08fe88cb0 100644 --- a/src/Discord.Net.Rest/API/Common/User.cs +++ b/src/Discord.Net.Rest/API/Common/User.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API @@ -15,6 +14,10 @@ namespace Discord.API public Optional Bot { get; set; } [JsonProperty("avatar")] public Optional Avatar { get; set; } + [JsonProperty("banner")] + public Optional Banner { get; set; } + [JsonProperty("accent_color")] + public Optional AccentColor { get; set; } //CurrentUser [JsonProperty("verified")] diff --git a/src/Discord.Net.Rest/API/Common/UserGuild.cs b/src/Discord.Net.Rest/API/Common/UserGuild.cs index 825e9a09a..fc1fe833d 100644 --- a/src/Discord.Net.Rest/API/Common/UserGuild.cs +++ b/src/Discord.Net.Rest/API/Common/UserGuild.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API diff --git a/src/Discord.Net.Rest/API/Common/VoiceRegion.cs b/src/Discord.Net.Rest/API/Common/VoiceRegion.cs index 606af07bd..3cc66a0ef 100644 --- a/src/Discord.Net.Rest/API/Common/VoiceRegion.cs +++ b/src/Discord.Net.Rest/API/Common/VoiceRegion.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API diff --git a/src/Discord.Net.Rest/API/Common/VoiceState.cs b/src/Discord.Net.Rest/API/Common/VoiceState.cs index c7a571ed0..f7cd54a72 100644 --- a/src/Discord.Net.Rest/API/Common/VoiceState.cs +++ b/src/Discord.Net.Rest/API/Common/VoiceState.cs @@ -1,5 +1,5 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; +using System; namespace Discord.API { @@ -28,5 +28,7 @@ namespace Discord.API public bool Suppress { get; set; } [JsonProperty("self_stream")] public bool SelfStream { get; set; } + [JsonProperty("request_to_speak_timestamp")] + public Optional RequestToSpeakTimestamp { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/Webhook.cs b/src/Discord.Net.Rest/API/Common/Webhook.cs index cbd5fdad5..23b682bd3 100644 --- a/src/Discord.Net.Rest/API/Common/Webhook.cs +++ b/src/Discord.Net.Rest/API/Common/Webhook.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API @@ -21,5 +20,7 @@ namespace Discord.API [JsonProperty("user")] public Optional Creator { get; set; } + [JsonProperty("application_id")] + public ulong? ApplicationId { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Int53Attribute.cs b/src/Discord.Net.Rest/API/Int53Attribute.cs index 70ef2f185..3a21b583d 100644 --- a/src/Discord.Net.Rest/API/Int53Attribute.cs +++ b/src/Discord.Net.Rest/API/Int53Attribute.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using System; namespace Discord.API diff --git a/src/Discord.Net.Rest/API/Net/IResolvable.cs b/src/Discord.Net.Rest/API/Net/IResolvable.cs new file mode 100644 index 000000000..7485f5de8 --- /dev/null +++ b/src/Discord.Net.Rest/API/Net/IResolvable.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.API +{ + internal interface IResolvable + { + Optional Resolved { get; } + } +} diff --git a/src/Discord.Net.Rest/API/Net/MultipartFile.cs b/src/Discord.Net.Rest/API/Net/MultipartFile.cs index 604852e90..d6bc4c7ab 100644 --- a/src/Discord.Net.Rest/API/Net/MultipartFile.cs +++ b/src/Discord.Net.Rest/API/Net/MultipartFile.cs @@ -1,4 +1,4 @@ -using System.IO; +using System.IO; namespace Discord.Net.Rest { @@ -6,11 +6,13 @@ namespace Discord.Net.Rest { public Stream Stream { get; } public string Filename { get; } + public string ContentType { get; } - public MultipartFile(Stream stream, string filename) + public MultipartFile(Stream stream, string filename, string contentType = null) { Stream = stream; Filename = filename; + ContentType = contentType; } } } diff --git a/src/Discord.Net.Rest/API/Rest/CreateApplicationCommandParams.cs b/src/Discord.Net.Rest/API/Rest/CreateApplicationCommandParams.cs new file mode 100644 index 000000000..82f0befcd --- /dev/null +++ b/src/Discord.Net.Rest/API/Rest/CreateApplicationCommandParams.cs @@ -0,0 +1,31 @@ +using Newtonsoft.Json; + +namespace Discord.API.Rest +{ + internal class CreateApplicationCommandParams + { + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("type")] + public ApplicationCommandType Type { get; set; } + + [JsonProperty("description")] + public string Description { get; set; } + + [JsonProperty("options")] + public Optional Options { get; set; } + + [JsonProperty("default_permission")] + public Optional DefaultPermission { get; set; } + + public CreateApplicationCommandParams() { } + public CreateApplicationCommandParams(string name, string description, ApplicationCommandType type, ApplicationCommandOption[] options = null) + { + Name = name; + Description = description; + Options = Optional.Create(options); + Type = type; + } + } +} diff --git a/src/Discord.Net.Rest/API/Rest/CreateChannelInviteParams.cs b/src/Discord.Net.Rest/API/Rest/CreateChannelInviteParams.cs index db79bc314..852abe301 100644 --- a/src/Discord.Net.Rest/API/Rest/CreateChannelInviteParams.cs +++ b/src/Discord.Net.Rest/API/Rest/CreateChannelInviteParams.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Rest @@ -14,5 +13,11 @@ namespace Discord.API.Rest public Optional IsTemporary { get; set; } [JsonProperty("unique")] public Optional IsUnique { get; set; } + [JsonProperty("target_type")] + public Optional TargetType { get; set; } + [JsonProperty("target_user_id")] + public Optional TargetUserId { get; set; } + [JsonProperty("target_application_id")] + public Optional TargetApplicationId { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/CreateDMChannelParams.cs b/src/Discord.Net.Rest/API/Rest/CreateDMChannelParams.cs index f32796e02..0a710dd1b 100644 --- a/src/Discord.Net.Rest/API/Rest/CreateDMChannelParams.cs +++ b/src/Discord.Net.Rest/API/Rest/CreateDMChannelParams.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Rest diff --git a/src/Discord.Net.Rest/API/Rest/CreateGuildBanParams.cs b/src/Discord.Net.Rest/API/Rest/CreateGuildBanParams.cs index f0432e517..fce9df11f 100644 --- a/src/Discord.Net.Rest/API/Rest/CreateGuildBanParams.cs +++ b/src/Discord.Net.Rest/API/Rest/CreateGuildBanParams.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 namespace Discord.API.Rest { internal class CreateGuildBanParams diff --git a/src/Discord.Net.Rest/API/Rest/CreateGuildChannelParams.cs b/src/Discord.Net.Rest/API/Rest/CreateGuildChannelParams.cs index aec43dbef..57816e448 100644 --- a/src/Discord.Net.Rest/API/Rest/CreateGuildChannelParams.cs +++ b/src/Discord.Net.Rest/API/Rest/CreateGuildChannelParams.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Rest diff --git a/src/Discord.Net.Rest/API/Rest/CreateGuildEmoteParams.cs b/src/Discord.Net.Rest/API/Rest/CreateGuildEmoteParams.cs index 308199820..c81f62f4c 100644 --- a/src/Discord.Net.Rest/API/Rest/CreateGuildEmoteParams.cs +++ b/src/Discord.Net.Rest/API/Rest/CreateGuildEmoteParams.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Rest diff --git a/src/Discord.Net.Rest/API/Rest/CreateGuildIntegrationParams.cs b/src/Discord.Net.Rest/API/Rest/CreateGuildIntegrationParams.cs index 1053a0ed3..7358e5201 100644 --- a/src/Discord.Net.Rest/API/Rest/CreateGuildIntegrationParams.cs +++ b/src/Discord.Net.Rest/API/Rest/CreateGuildIntegrationParams.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Rest diff --git a/src/Discord.Net.Rest/API/Rest/CreateGuildParams.cs b/src/Discord.Net.Rest/API/Rest/CreateGuildParams.cs index cda6caedf..e89c2b119 100644 --- a/src/Discord.Net.Rest/API/Rest/CreateGuildParams.cs +++ b/src/Discord.Net.Rest/API/Rest/CreateGuildParams.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Rest diff --git a/src/Discord.Net.Rest/API/Rest/CreateGuildScheduledEventParams.cs b/src/Discord.Net.Rest/API/Rest/CreateGuildScheduledEventParams.cs new file mode 100644 index 000000000..a207d3374 --- /dev/null +++ b/src/Discord.Net.Rest/API/Rest/CreateGuildScheduledEventParams.cs @@ -0,0 +1,29 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.API.Rest +{ + internal class CreateGuildScheduledEventParams + { + [JsonProperty("channel_id")] + public Optional ChannelId { get; set; } + [JsonProperty("entity_metadata")] + public Optional EntityMetadata { get; set; } + [JsonProperty("name")] + public string Name { get; set; } + [JsonProperty("privacy_level")] + public GuildScheduledEventPrivacyLevel PrivacyLevel { get; set; } + [JsonProperty("scheduled_start_time")] + public DateTimeOffset StartTime { get; set; } + [JsonProperty("scheduled_end_time")] + public Optional EndTime { get; set; } + [JsonProperty("description")] + public Optional Description { get; set; } + [JsonProperty("entity_type")] + public GuildScheduledEventType Type { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Rest/CreateMessageParams.cs b/src/Discord.Net.Rest/API/Rest/CreateMessageParams.cs index e64532864..5996c7e83 100644 --- a/src/Discord.Net.Rest/API/Rest/CreateMessageParams.cs +++ b/src/Discord.Net.Rest/API/Rest/CreateMessageParams.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Rest @@ -11,15 +10,25 @@ namespace Discord.API.Rest [JsonProperty("nonce")] public Optional Nonce { get; set; } + [JsonProperty("tts")] public Optional IsTTS { get; set; } - [JsonProperty("embed")] - public Optional Embed { get; set; } + + [JsonProperty("embeds")] + public Optional Embeds { get; set; } + [JsonProperty("allowed_mentions")] public Optional AllowedMentions { get; set; } + [JsonProperty("message_reference")] public Optional MessageReference { get; set; } + [JsonProperty("components")] + public Optional Components { get; set; } + + [JsonProperty("sticker_ids")] + public Optional Stickers { get; set; } + public CreateMessageParams(string content) { Content = content; diff --git a/src/Discord.Net.Rest/API/Rest/CreateStageInstanceParams.cs b/src/Discord.Net.Rest/API/Rest/CreateStageInstanceParams.cs new file mode 100644 index 000000000..a1d59bb51 --- /dev/null +++ b/src/Discord.Net.Rest/API/Rest/CreateStageInstanceParams.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace Discord.API.Rest +{ + internal class CreateStageInstanceParams + { + [JsonProperty("channel_id")] + public ulong ChannelId { get; set; } + + [JsonProperty("topic")] + public string Topic { get; set; } + + [JsonProperty("privacy_level")] + public Optional PrivacyLevel { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Rest/CreateStickerParams.cs b/src/Discord.Net.Rest/API/Rest/CreateStickerParams.cs new file mode 100644 index 000000000..b330a0111 --- /dev/null +++ b/src/Discord.Net.Rest/API/Rest/CreateStickerParams.cs @@ -0,0 +1,35 @@ +using Discord.Net.Rest; +using System.Collections.Generic; +using System.IO; +namespace Discord.API.Rest +{ + internal class CreateStickerParams + { + public Stream File { get; set; } + public string Name { get; set; } + public string Description { get; set; } + public string Tags { get; set; } + public string FileName { get; set; } + + public IReadOnlyDictionary ToDictionary() + { + var d = new Dictionary + { + ["name"] = $"{Name}", + ["description"] = Description, + ["tags"] = Tags + }; + + string contentType = "image/png"; + + if (File is FileStream fileStream) + contentType = $"image/{Path.GetExtension(fileStream.Name)}"; + else if (FileName != null) + contentType = $"image/{Path.GetExtension(FileName)}"; + + d["file"] = new MultipartFile(File, FileName ?? "image", contentType.Replace(".", "")); + + return d; + } + } +} diff --git a/src/Discord.Net.Rest/API/Rest/CreateWebhookMessageParams.cs b/src/Discord.Net.Rest/API/Rest/CreateWebhookMessageParams.cs index 0a4f80a3c..bda0f7ff1 100644 --- a/src/Discord.Net.Rest/API/Rest/CreateWebhookMessageParams.cs +++ b/src/Discord.Net.Rest/API/Rest/CreateWebhookMessageParams.cs @@ -1,30 +1,84 @@ -#pragma warning disable CS1591 +using Discord.Net.Converters; +using Discord.Net.Rest; using Newtonsoft.Json; +using System.Collections.Generic; +using System.IO; +using System.Text; namespace Discord.API.Rest { [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class CreateWebhookMessageParams { + private static JsonSerializer _serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() }; + [JsonProperty("content")] - public string Content { get; } + public string Content { get; set; } [JsonProperty("nonce")] public Optional Nonce { get; set; } + [JsonProperty("tts")] public Optional IsTTS { get; set; } + [JsonProperty("embeds")] public Optional Embeds { get; set; } + [JsonProperty("username")] public Optional Username { get; set; } + [JsonProperty("avatar_url")] public Optional AvatarUrl { get; set; } + [JsonProperty("allowed_mentions")] public Optional AllowedMentions { get; set; } - public CreateWebhookMessageParams(string content) + [JsonProperty("flags")] + public Optional Flags { get; set; } + + [JsonProperty("components")] + public Optional Components { get; set; } + + [JsonProperty("file")] + public Optional File { get; set; } + + public IReadOnlyDictionary ToDictionary() { - Content = content; + var d = new Dictionary(); + + if (File.IsSpecified) + { + d["file"] = File.Value; + } + + var payload = new Dictionary + { + ["content"] = Content + }; + + if (IsTTS.IsSpecified) + payload["tts"] = IsTTS.Value.ToString(); + if (Nonce.IsSpecified) + payload["nonce"] = Nonce.Value; + if (Username.IsSpecified) + payload["username"] = Username.Value; + if (AvatarUrl.IsSpecified) + payload["avatar_url"] = AvatarUrl.Value; + if (Embeds.IsSpecified) + payload["embeds"] = Embeds.Value; + if (AllowedMentions.IsSpecified) + payload["allowed_mentions"] = AllowedMentions.Value; + if (Components.IsSpecified) + payload["components"] = Components.Value; + + var json = new StringBuilder(); + using (var text = new StringWriter(json)) + using (var writer = new JsonTextWriter(text)) + _serializer.Serialize(writer, payload); + + d["payload_json"] = json.ToString(); + + return d; } } } diff --git a/src/Discord.Net.Rest/API/Rest/CreateWebhookParams.cs b/src/Discord.Net.Rest/API/Rest/CreateWebhookParams.cs index 0d1059fab..242f451cb 100644 --- a/src/Discord.Net.Rest/API/Rest/CreateWebhookParams.cs +++ b/src/Discord.Net.Rest/API/Rest/CreateWebhookParams.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Rest diff --git a/src/Discord.Net.Rest/API/Rest/DeleteMessagesParams.cs b/src/Discord.Net.Rest/API/Rest/DeleteMessagesParams.cs index ca9d8c26e..ca6b78406 100644 --- a/src/Discord.Net.Rest/API/Rest/DeleteMessagesParams.cs +++ b/src/Discord.Net.Rest/API/Rest/DeleteMessagesParams.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Rest diff --git a/src/Discord.Net.Rest/API/Rest/GetBotGatewayResponse.cs b/src/Discord.Net.Rest/API/Rest/GetBotGatewayResponse.cs index d3285051b..3f8318cd1 100644 --- a/src/Discord.Net.Rest/API/Rest/GetBotGatewayResponse.cs +++ b/src/Discord.Net.Rest/API/Rest/GetBotGatewayResponse.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Rest diff --git a/src/Discord.Net.Rest/API/Rest/GetChannelMessagesParams.cs b/src/Discord.Net.Rest/API/Rest/GetChannelMessagesParams.cs index ea5327667..52dd84836 100644 --- a/src/Discord.Net.Rest/API/Rest/GetChannelMessagesParams.cs +++ b/src/Discord.Net.Rest/API/Rest/GetChannelMessagesParams.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 namespace Discord.API.Rest { internal class GetChannelMessagesParams diff --git a/src/Discord.Net.Rest/API/Rest/GetEventUsersParams.cs b/src/Discord.Net.Rest/API/Rest/GetEventUsersParams.cs new file mode 100644 index 000000000..db3ac666e --- /dev/null +++ b/src/Discord.Net.Rest/API/Rest/GetEventUsersParams.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.API.Rest +{ + internal class GetEventUsersParams + { + public Optional Limit { get; set; } + public Optional RelativeDirection { get; set; } + public Optional RelativeUserId { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Rest/GetGatewayResponse.cs b/src/Discord.Net.Rest/API/Rest/GetGatewayResponse.cs index ce3630170..11207633d 100644 --- a/src/Discord.Net.Rest/API/Rest/GetGatewayResponse.cs +++ b/src/Discord.Net.Rest/API/Rest/GetGatewayResponse.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Rest diff --git a/src/Discord.Net.Rest/API/Rest/GetGuildMembersParams.cs b/src/Discord.Net.Rest/API/Rest/GetGuildMembersParams.cs index 66023cb43..67d380035 100644 --- a/src/Discord.Net.Rest/API/Rest/GetGuildMembersParams.cs +++ b/src/Discord.Net.Rest/API/Rest/GetGuildMembersParams.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 namespace Discord.API.Rest { internal class GetGuildMembersParams diff --git a/src/Discord.Net.Rest/API/Rest/GetGuildPruneCountResponse.cs b/src/Discord.Net.Rest/API/Rest/GetGuildPruneCountResponse.cs index 4af85acfa..1e7fc8c7b 100644 --- a/src/Discord.Net.Rest/API/Rest/GetGuildPruneCountResponse.cs +++ b/src/Discord.Net.Rest/API/Rest/GetGuildPruneCountResponse.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Rest diff --git a/src/Discord.Net.Rest/API/Rest/GetGuildSummariesParams.cs b/src/Discord.Net.Rest/API/Rest/GetGuildSummariesParams.cs index f770ef398..1d3f70f07 100644 --- a/src/Discord.Net.Rest/API/Rest/GetGuildSummariesParams.cs +++ b/src/Discord.Net.Rest/API/Rest/GetGuildSummariesParams.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 namespace Discord.API.Rest { internal class GetGuildSummariesParams diff --git a/src/Discord.Net.Rest/API/Rest/GuildPruneParams.cs b/src/Discord.Net.Rest/API/Rest/GuildPruneParams.cs index e4c9192ad..c6caa1eb1 100644 --- a/src/Discord.Net.Rest/API/Rest/GuildPruneParams.cs +++ b/src/Discord.Net.Rest/API/Rest/GuildPruneParams.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Rest diff --git a/src/Discord.Net.Rest/API/Rest/ModifyApplicationCommandParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyApplicationCommandParams.cs new file mode 100644 index 000000000..5891c2c28 --- /dev/null +++ b/src/Discord.Net.Rest/API/Rest/ModifyApplicationCommandParams.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; + +namespace Discord.API.Rest +{ + internal class ModifyApplicationCommandParams + { + [JsonProperty("name")] + public Optional Name { get; set; } + + [JsonProperty("description")] + public Optional Description { get; set; } + + [JsonProperty("options")] + public Optional Options { get; set; } + + [JsonProperty("default_permission")] + public Optional DefaultPermission { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Rest/ModifyChannelPermissionsParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyChannelPermissionsParams.cs index 269111a61..acb81034a 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyChannelPermissionsParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyChannelPermissionsParams.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Rest diff --git a/src/Discord.Net.Rest/API/Rest/ModifyCurrentUserNickParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyCurrentUserNickParams.cs index ba44e34cf..c10f2e4ec 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyCurrentUserNickParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyCurrentUserNickParams.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Rest diff --git a/src/Discord.Net.Rest/API/Rest/ModifyCurrentUserParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyCurrentUserParams.cs index 7ba27c3a5..e28deb32b 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyCurrentUserParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyCurrentUserParams.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Rest diff --git a/src/Discord.Net.Rest/API/Rest/ModifyGuildApplicationCommandPermissions.cs b/src/Discord.Net.Rest/API/Rest/ModifyGuildApplicationCommandPermissions.cs new file mode 100644 index 000000000..a557061f3 --- /dev/null +++ b/src/Discord.Net.Rest/API/Rest/ModifyGuildApplicationCommandPermissions.cs @@ -0,0 +1,13 @@ +using Newtonsoft.Json; + +namespace Discord.API.Rest +{ + internal class ModifyGuildApplicationCommandPermissions + { + [JsonProperty("id")] + public ulong Id { get; set; } + + [JsonProperty("permissions")] + public ApplicationCommandPermissions[] Permissions { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Rest/ModifyGuildApplicationCommandPermissionsParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyGuildApplicationCommandPermissionsParams.cs new file mode 100644 index 000000000..322875b8e --- /dev/null +++ b/src/Discord.Net.Rest/API/Rest/ModifyGuildApplicationCommandPermissionsParams.cs @@ -0,0 +1,10 @@ +using Newtonsoft.Json; + +namespace Discord.API.Rest +{ + internal class ModifyGuildApplicationCommandPermissionsParams + { + [JsonProperty("permissions")] + public ApplicationCommandPermissions[] Permissions { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Rest/ModifyGuildChannelParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyGuildChannelParams.cs index e5e8a4632..dfe9cd980 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyGuildChannelParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyGuildChannelParams.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Rest diff --git a/src/Discord.Net.Rest/API/Rest/ModifyGuildChannelsParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyGuildChannelsParams.cs index f97fbda0b..91567be3d 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyGuildChannelsParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyGuildChannelsParams.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Rest diff --git a/src/Discord.Net.Rest/API/Rest/ModifyGuildEmbedParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyGuildEmbedParams.cs index 487744c65..420bdbeaf 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyGuildEmbedParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyGuildEmbedParams.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Rest diff --git a/src/Discord.Net.Rest/API/Rest/ModifyGuildEmoteParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyGuildEmoteParams.cs index a2295dd5d..08b196daa 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyGuildEmoteParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyGuildEmoteParams.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Rest diff --git a/src/Discord.Net.Rest/API/Rest/ModifyGuildIntegrationParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyGuildIntegrationParams.cs index 0a1b4f9fa..cf869c838 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyGuildIntegrationParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyGuildIntegrationParams.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Rest diff --git a/src/Discord.Net.Rest/API/Rest/ModifyGuildMemberParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyGuildMemberParams.cs index a381d6f8f..37625de09 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyGuildMemberParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyGuildMemberParams.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Rest diff --git a/src/Discord.Net.Rest/API/Rest/ModifyGuildParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyGuildParams.cs index cfb107bcd..c1a20cb83 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyGuildParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyGuildParams.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Rest @@ -36,5 +35,7 @@ namespace Discord.API.Rest public Optional SystemChannelFlags { get; set; } [JsonProperty("preferred_locale")] public string PreferredLocale { get; set; } + [JsonProperty("premium_progress_bar_enabled")] + public Optional IsBoostProgressBarEnabled { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/ModifyGuildRoleParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyGuildRoleParams.cs index 8605411c5..fbb9c3e48 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyGuildRoleParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyGuildRoleParams.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Rest @@ -14,6 +13,8 @@ namespace Discord.API.Rest public Optional Color { get; set; } [JsonProperty("hoist")] public Optional Hoist { get; set; } + [JsonProperty("icon")] + public Optional Icon { get; set; } [JsonProperty("mentionable")] public Optional Mentionable { get; set; } } diff --git a/src/Discord.Net.Rest/API/Rest/ModifyGuildRolesParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyGuildRolesParams.cs index 0e816a260..eeb724523 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyGuildRolesParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyGuildRolesParams.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Rest diff --git a/src/Discord.Net.Rest/API/Rest/ModifyGuildScheduledEventParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyGuildScheduledEventParams.cs new file mode 100644 index 000000000..3d191a0b3 --- /dev/null +++ b/src/Discord.Net.Rest/API/Rest/ModifyGuildScheduledEventParams.cs @@ -0,0 +1,31 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.API.Rest +{ + internal class ModifyGuildScheduledEventParams + { + [JsonProperty("channel_id")] + public Optional ChannelId { get; set; } + [JsonProperty("entity_metadata")] + public Optional EntityMetadata { get; set; } + [JsonProperty("name")] + public Optional Name { get; set; } + [JsonProperty("privacy_level")] + public Optional PrivacyLevel { get; set; } + [JsonProperty("scheduled_start_time")] + public Optional StartTime { get; set; } + [JsonProperty("scheduled_end_time")] + public Optional EndTime { get; set; } + [JsonProperty("description")] + public Optional Description { get; set; } + [JsonProperty("entity_type")] + public Optional Type { get; set; } + [JsonProperty("status")] + public Optional Status { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Rest/ModifyGuildWidgetParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyGuildWidgetParams.cs index 506f1dfbb..2e5658d51 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyGuildWidgetParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyGuildWidgetParams.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Rest diff --git a/src/Discord.Net.Rest/API/Rest/ModifyInteractionResponseParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyInteractionResponseParams.cs new file mode 100644 index 000000000..a2c7cbee6 --- /dev/null +++ b/src/Discord.Net.Rest/API/Rest/ModifyInteractionResponseParams.cs @@ -0,0 +1,22 @@ +using Newtonsoft.Json; + +namespace Discord.API.Rest +{ + internal class ModifyInteractionResponseParams + { + [JsonProperty("content")] + public Optional Content { get; set; } + + [JsonProperty("embeds")] + public Optional Embeds { get; set; } + + [JsonProperty("allowed_mentions")] + public Optional AllowedMentions { get; set; } + + [JsonProperty("components")] + public Optional Components { get; set; } + + [JsonProperty("flags")] + public Optional Flags { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Rest/ModifyMessageParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyMessageParams.cs index 3752df3a2..3dba45a5b 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyMessageParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyMessageParams.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Rest @@ -8,8 +7,10 @@ namespace Discord.API.Rest { [JsonProperty("content")] public Optional Content { get; set; } - [JsonProperty("embed")] - public Optional Embed { get; set; } + [JsonProperty("embeds")] + public Optional Embeds { get; set; } + [JsonProperty("components")] + public Optional Components { get; set; } [JsonProperty("flags")] public Optional Flags { get; set; } [JsonProperty("allowed_mentions")] diff --git a/src/Discord.Net.Rest/API/Rest/ModifyStageInstanceParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyStageInstanceParams.cs new file mode 100644 index 000000000..c09d8f216 --- /dev/null +++ b/src/Discord.Net.Rest/API/Rest/ModifyStageInstanceParams.cs @@ -0,0 +1,13 @@ +using Newtonsoft.Json; + +namespace Discord.API.Rest +{ + internal class ModifyStageInstanceParams + { + [JsonProperty("topic")] + public Optional Topic { get; set; } + + [JsonProperty("privacy_level")] + public Optional PrivacyLevel { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Rest/ModifyStickerParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyStickerParams.cs new file mode 100644 index 000000000..bd538c72e --- /dev/null +++ b/src/Discord.Net.Rest/API/Rest/ModifyStickerParams.cs @@ -0,0 +1,14 @@ +using Newtonsoft.Json; + +namespace Discord.API.Rest +{ + internal class ModifyStickerParams + { + [JsonProperty("name")] + public Optional Name { get; set; } + [JsonProperty("description")] + public Optional Description { get; set; } + [JsonProperty("tags")] + public Optional Tags { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Rest/ModifyTextChannelParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyTextChannelParams.cs index 94f149fc1..409d90c3f 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyTextChannelParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyTextChannelParams.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Rest diff --git a/src/Discord.Net.Rest/API/Rest/ModifyThreadParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyThreadParams.cs new file mode 100644 index 000000000..8c9216c3f --- /dev/null +++ b/src/Discord.Net.Rest/API/Rest/ModifyThreadParams.cs @@ -0,0 +1,22 @@ +using Newtonsoft.Json; + +namespace Discord.API.Rest +{ + internal class ModifyThreadParams + { + [JsonProperty("name")] + public Optional Name { get; set; } + + [JsonProperty("archived")] + public Optional Archived { get; set; } + + [JsonProperty("auto_archive_duration")] + public Optional AutoArchiveDuration { get; set; } + + [JsonProperty("locked")] + public Optional Locked { get; set; } + + [JsonProperty("rate_limit_per_user")] + public Optional Slowmode { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Rest/ModifyVoiceChannelParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyVoiceChannelParams.cs index ce36eb11f..2f8cacc69 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyVoiceChannelParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyVoiceChannelParams.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Rest @@ -10,5 +9,7 @@ namespace Discord.API.Rest public Optional Bitrate { get; set; } [JsonProperty("user_limit")] public Optional UserLimit { get; set; } + [JsonProperty("rtc_region")] + public Optional RTCRegion { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/ModifyVoiceStateParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyVoiceStateParams.cs new file mode 100644 index 000000000..1ff0f3e08 --- /dev/null +++ b/src/Discord.Net.Rest/API/Rest/ModifyVoiceStateParams.cs @@ -0,0 +1,17 @@ +using Newtonsoft.Json; +using System; + +namespace Discord.API.Rest +{ + internal class ModifyVoiceStateParams + { + [JsonProperty("channel_id")] + public ulong ChannelId { get; set; } + + [JsonProperty("suppress")] + public Optional Suppressed { get; set; } + + [JsonProperty("request_to_speak_timestamp")] + public Optional RequestToSpeakTimestamp { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Rest/ModifyWebhookMessageParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyWebhookMessageParams.cs index ba8fcbb4e..e73efaf36 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyWebhookMessageParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyWebhookMessageParams.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Rest @@ -12,5 +11,7 @@ namespace Discord.API.Rest public Optional Embeds { get; set; } [JsonProperty("allowed_mentions")] public Optional AllowedMentions { get; set; } + [JsonProperty("components")] + public Optional Components { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/ModifyWebhookParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyWebhookParams.cs index 0f2d6e33b..2e4e6a4c4 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyWebhookParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyWebhookParams.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Rest diff --git a/src/Discord.Net.Rest/API/Rest/SearchGuildMembersParams.cs b/src/Discord.Net.Rest/API/Rest/SearchGuildMembersParams.cs index 7c933ff82..56b3595fa 100644 --- a/src/Discord.Net.Rest/API/Rest/SearchGuildMembersParams.cs +++ b/src/Discord.Net.Rest/API/Rest/SearchGuildMembersParams.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 namespace Discord.API.Rest { internal class SearchGuildMembersParams diff --git a/src/Discord.Net.Rest/API/Rest/StartThreadParams.cs b/src/Discord.Net.Rest/API/Rest/StartThreadParams.cs new file mode 100644 index 000000000..a13161cd4 --- /dev/null +++ b/src/Discord.Net.Rest/API/Rest/StartThreadParams.cs @@ -0,0 +1,22 @@ +using Newtonsoft.Json; + +namespace Discord.API.Rest +{ + internal class StartThreadParams + { + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("auto_archive_duration")] + public ThreadArchiveDuration Duration { get; set; } + + [JsonProperty("type")] + public ThreadType Type { get; set; } + + [JsonProperty("invitable")] + public Optional Invitable { get; set; } + + [JsonProperty("rate_limit_per_user")] + public Optional Ratelimit { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Rest/UploadFileParams.cs b/src/Discord.Net.Rest/API/Rest/UploadFileParams.cs index dd442a6de..6340c3e38 100644 --- a/src/Discord.Net.Rest/API/Rest/UploadFileParams.cs +++ b/src/Discord.Net.Rest/API/Rest/UploadFileParams.cs @@ -1,9 +1,9 @@ -#pragma warning disable CS1591 using Discord.Net.Converters; using Discord.Net.Rest; using Newtonsoft.Json; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text; namespace Discord.API.Rest @@ -12,30 +12,27 @@ namespace Discord.API.Rest { private static JsonSerializer _serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() }; - public Stream File { get; } + public FileAttachment[] Files { get; } - public Optional Filename { get; set; } public Optional Content { get; set; } public Optional Nonce { get; set; } public Optional IsTTS { get; set; } - public Optional Embed { get; set; } + public Optional Embeds { get; set; } public Optional AllowedMentions { get; set; } public Optional MessageReference { get; set; } - public bool IsSpoiler { get; set; } = false; + public Optional MessageComponent { get; set; } + public Optional Flags { get; set; } + public Optional Stickers { get; set; } - public UploadFileParams(Stream file) + public UploadFileParams(params Discord.FileAttachment[] attachments) { - File = file; + Files = attachments; } public IReadOnlyDictionary ToDictionary() { var d = new Dictionary(); - var filename = Filename.GetValueOrDefault("unknown.dat"); - if (IsSpoiler && !filename.StartsWith(AttachmentExtensions.SpoilerPrefix)) - filename = filename.Insert(0, AttachmentExtensions.SpoilerPrefix); - d["file"] = new MultipartFile(File, filename); - + var payload = new Dictionary(); if (Content.IsSpecified) payload["content"] = Content.Value; @@ -43,14 +40,39 @@ namespace Discord.API.Rest payload["tts"] = IsTTS.Value.ToString(); if (Nonce.IsSpecified) payload["nonce"] = Nonce.Value; - if (Embed.IsSpecified) - payload["embed"] = Embed.Value; + if (Embeds.IsSpecified) + payload["embeds"] = Embeds.Value; if (AllowedMentions.IsSpecified) payload["allowed_mentions"] = AllowedMentions.Value; - if (IsSpoiler) - payload["hasSpoiler"] = IsSpoiler.ToString(); + if (MessageComponent.IsSpecified) + payload["components"] = MessageComponent.Value; if (MessageReference.IsSpecified) payload["message_reference"] = MessageReference.Value; + if (Stickers.IsSpecified) + payload["sticker_ids"] = Stickers.Value; + if (Flags.IsSpecified) + payload["flags"] = Flags; + + List attachments = new(); + + for(int n = 0; n != Files.Length; n++) + { + var attachment = Files[n]; + + var filename = attachment.FileName ?? "unknown.dat"; + if (attachment.IsSpoiler && !filename.StartsWith(AttachmentExtensions.SpoilerPrefix)) + filename = filename.Insert(0, AttachmentExtensions.SpoilerPrefix); + d[$"files[{n}]"] = new MultipartFile(attachment.Stream, filename); + + attachments.Add(new + { + id = (ulong)n, + filename = filename, + description = attachment.Description ?? Optional.Unspecified + }); + } + + payload["attachments"] = attachments; var json = new StringBuilder(); using (var text = new StringWriter(json)) diff --git a/src/Discord.Net.Rest/API/Rest/UploadWebhookFileParams.cs b/src/Discord.Net.Rest/API/Rest/UploadWebhookFileParams.cs index 8da7681ae..d925e0108 100644 --- a/src/Discord.Net.Rest/API/Rest/UploadWebhookFileParams.cs +++ b/src/Discord.Net.Rest/API/Rest/UploadWebhookFileParams.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using System.Collections.Generic; using System.IO; using System.Text; diff --git a/src/Discord.Net.Rest/BaseDiscordClient.cs b/src/Discord.Net.Rest/BaseDiscordClient.cs index 68589a4f1..525875232 100644 --- a/src/Discord.Net.Rest/BaseDiscordClient.cs +++ b/src/Discord.Net.Rest/BaseDiscordClient.cs @@ -10,6 +10,7 @@ namespace Discord.Rest { public abstract class BaseDiscordClient : IDiscordClient { + #region BaseDiscordClient public event Func Log { add { _logEvent.Add(value); } remove { _logEvent.Remove(value); } } internal readonly AsyncEvent> _logEvent = new AsyncEvent>(); @@ -34,6 +35,7 @@ namespace Discord.Rest public ISelfUser CurrentUser { get; protected set; } /// public TokenType TokenType => ApiClient.AuthTokenType; + internal bool UseInteractionSnowflakeDate { get; private set; } /// Creates a new REST-only Discord client. internal BaseDiscordClient(DiscordRestConfig config, API.DiscordRestApiClient client) @@ -46,6 +48,8 @@ namespace Discord.Rest _restLogger = LogManager.CreateLogger("Rest"); _isFirstLogin = config.DisplayInitialLog; + UseInteractionSnowflakeDate = config.UseInteractionSnowflakeDate; + ApiClient.RequestQueue.RateLimitTriggered += async (id, info, endpoint) => { if (info == null) @@ -155,8 +159,9 @@ namespace Discord.Rest /// public Task GetBotGatewayAsync(RequestOptions options = null) => ClientHelper.GetBotGatewayAsync(this, options); + #endregion - //IDiscordClient + #region IDiscordClient /// ConnectionState IDiscordClient.ConnectionState => ConnectionState.Disconnected; /// @@ -217,10 +222,24 @@ namespace Discord.Rest => Task.FromResult(null); /// + Task IDiscordClient.GetGlobalApplicationCommandAsync(ulong id, RequestOptions options) + => Task.FromResult(null); + + /// + Task> IDiscordClient.GetGlobalApplicationCommandsAsync(RequestOptions options) + => Task.FromResult>(ImmutableArray.Create()); + Task IDiscordClient.CreateGlobalApplicationCommand(ApplicationCommandProperties properties, RequestOptions options) + => Task.FromResult(null); + Task> IDiscordClient.BulkOverwriteGlobalApplicationCommand(ApplicationCommandProperties[] properties, + RequestOptions options) + => Task.FromResult>(ImmutableArray.Create()); + + /// Task IDiscordClient.StartAsync() => Task.Delay(0); /// Task IDiscordClient.StopAsync() => Task.Delay(0); + #endregion } } diff --git a/src/Discord.Net.Rest/ClientHelper.cs b/src/Discord.Net.Rest/ClientHelper.cs index 3ff8212fe..5debea27e 100644 --- a/src/Discord.Net.Rest/ClientHelper.cs +++ b/src/Discord.Net.Rest/ClientHelper.cs @@ -10,7 +10,7 @@ namespace Discord.Rest { internal static class ClientHelper { - //Applications + #region Applications public static async Task GetApplicationInfoAsync(BaseDiscordClient client, RequestOptions options) { var model = await client.ApiClient.GetMyApplicationAsync(options).ConfigureAwait(false); @@ -193,9 +193,76 @@ namespace Discord.Rest } }; } + + public static async Task> GetGlobalApplicationCommandsAsync(BaseDiscordClient client, + RequestOptions options = null) + { + var response = await client.ApiClient.GetGlobalApplicationCommandsAsync(options).ConfigureAwait(false); + + if (!response.Any()) + return Array.Empty(); + + return response.Select(x => RestGlobalCommand.Create(client, x)).ToArray(); + } + public static async Task GetGlobalApplicationCommandAsync(BaseDiscordClient client, ulong id, + RequestOptions options = null) + { + var model = await client.ApiClient.GetGlobalApplicationCommandAsync(id, options); + + return model != null ? RestGlobalCommand.Create(client, model) : null; + } + + public static async Task> GetGuildApplicationCommandsAsync(BaseDiscordClient client, ulong guildId, + RequestOptions options = null) + { + var response = await client.ApiClient.GetGuildApplicationCommandsAsync(guildId, options).ConfigureAwait(false); + + if (!response.Any()) + return ImmutableArray.Create(); + + return response.Select(x => RestGuildCommand.Create(client, x, guildId)).ToImmutableArray(); + } + public static async Task GetGuildApplicationCommandAsync(BaseDiscordClient client, ulong id, ulong guildId, + RequestOptions options = null) + { + var model = await client.ApiClient.GetGuildApplicationCommandAsync(guildId, id, options); + + return model != null ? RestGuildCommand.Create(client, model, guildId) : null; + } + public static async Task CreateGuildApplicationCommandAsync(BaseDiscordClient client, ulong guildId, ApplicationCommandProperties properties, + RequestOptions options = null) + { + var model = await InteractionHelper.CreateGuildCommandAsync(client, guildId, properties, options); + + return RestGuildCommand.Create(client, model, guildId); + } + public static async Task CreateGlobalApplicationCommandAsync(BaseDiscordClient client, ApplicationCommandProperties properties, + RequestOptions options = null) + { + var model = await InteractionHelper.CreateGlobalCommandAsync(client, properties, options); + + return RestGlobalCommand.Create(client, model); + } + public static async Task> BulkOverwriteGlobalApplicationCommandAsync(BaseDiscordClient client, ApplicationCommandProperties[] properties, + RequestOptions options = null) + { + var models = await InteractionHelper.BulkOverwriteGlobalCommandsAsync(client, properties, options); + + return models.Select(x => RestGlobalCommand.Create(client, x)).ToImmutableArray(); + } + public static async Task> BulkOverwriteGuildApplicationCommandAsync(BaseDiscordClient client, ulong guildId, + ApplicationCommandProperties[] properties, RequestOptions options = null) + { + var models = await InteractionHelper.BulkOverwriteGuildCommandsAsync(client, guildId, properties, options); + + return models.Select(x => RestGuildCommand.Create(client, x, guildId)).ToImmutableArray(); + } + public static Task AddRoleAsync(BaseDiscordClient client, ulong guildId, ulong userId, ulong roleId, RequestOptions options = null) => client.ApiClient.AddRoleAsync(guildId, userId, roleId, options); + public static Task RemoveRoleAsync(BaseDiscordClient client, ulong guildId, ulong userId, ulong roleId, RequestOptions options = null) => client.ApiClient.RemoveRoleAsync(guildId, userId, roleId, options); + #endregion } } diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index d7978db5c..abe059c64 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -1,5 +1,4 @@ -#pragma warning disable CS1591 using Discord.API.Rest; using Discord.Net; using Discord.Net.Converters; @@ -24,6 +23,7 @@ namespace Discord.API { internal class DiscordRestApiClient : IDisposable { + #region DiscordRestApiClient private static readonly ConcurrentDictionary> _bucketIdGenerators = new ConcurrentDictionary>(); public event Func SentRequest { add { _sentRequestEvent.Add(value); } remove { _sentRequestEvent.Remove(value); } } @@ -45,8 +45,7 @@ namespace Discord.API internal string AuthToken { get; private set; } internal IRestClient RestClient { get; private set; } internal ulong? CurrentUserId { get; set; } - internal bool UseSystemClock { get; set; } - + internal bool UseSystemClock { get; set; } internal JsonSerializer Serializer => _serializer; /// Unknown OAuth token type. @@ -57,7 +56,7 @@ namespace Discord.API UserAgent = userAgent; DefaultRetryMode = defaultRetryMode; _serializer = serializer ?? new JsonSerializer { ContractResolver = new DiscordContractResolver() }; - UseSystemClock = useSystemClock; + UseSystemClock = useSystemClock; RequestQueue = new RequestQueue(); _stateLock = new SemaphoreSlim(1, 1); @@ -168,15 +167,16 @@ namespace Discord.API internal virtual Task ConnectInternalAsync() => Task.Delay(0); internal virtual Task DisconnectInternalAsync(Exception ex = null) => Task.Delay(0); + #endregion - //Core + #region Core internal Task SendAsync(string method, Expression> endpointExpr, BucketIds ids, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null) => SendAsync(method, GetEndpoint(endpointExpr), GetBucketId(method, ids, endpointExpr, funcName), clientBucket, options); public async Task SendAsync(string method, string endpoint, BucketId bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) { - options = options ?? new RequestOptions(); + options ??= new RequestOptions(); options.HeaderOnly = true; options.BucketId = bucketId; @@ -190,7 +190,7 @@ namespace Discord.API public async Task SendJsonAsync(string method, string endpoint, object payload, BucketId bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) { - options = options ?? new RequestOptions(); + options ??= new RequestOptions(); options.HeaderOnly = true; options.BucketId = bucketId; @@ -205,7 +205,7 @@ namespace Discord.API public async Task SendMultipartAsync(string method, string endpoint, IReadOnlyDictionary multipartArgs, BucketId bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) { - options = options ?? new RequestOptions(); + options ??= new RequestOptions(); options.HeaderOnly = true; options.BucketId = bucketId; @@ -219,7 +219,7 @@ namespace Discord.API public async Task SendAsync(string method, string endpoint, BucketId bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) where TResponse : class { - options = options ?? new RequestOptions(); + options ??= new RequestOptions(); options.BucketId = bucketId; var request = new RestRequest(RestClient, method, endpoint, options); @@ -232,10 +232,11 @@ namespace Discord.API public async Task SendJsonAsync(string method, string endpoint, object payload, BucketId bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) where TResponse : class { - options = options ?? new RequestOptions(); + options ??= new RequestOptions(); options.BucketId = bucketId; string json = payload != null ? SerializeJson(payload) : null; + var request = new JsonRestRequest(RestClient, method, endpoint, json, options); return DeserializeJson(await SendInternalAsync(method, endpoint, request).ConfigureAwait(false)); } @@ -246,7 +247,7 @@ namespace Discord.API public async Task SendMultipartAsync(string method, string endpoint, IReadOnlyDictionary multipartArgs, BucketId bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) { - options = options ?? new RequestOptions(); + options ??= new RequestOptions(); options.BucketId = bucketId; var request = new MultipartRestRequest(RestClient, method, endpoint, multipartArgs, options); @@ -259,8 +260,8 @@ namespace Discord.API CheckState(); if (request.Options.RetryMode == null) request.Options.RetryMode = DefaultRetryMode; - if (request.Options.UseSystemClock == null) - request.Options.UseSystemClock = UseSystemClock; + if (request.Options.UseSystemClock == null) + request.Options.UseSystemClock = UseSystemClock; var stopwatch = Stopwatch.StartNew(); var responseStream = await RequestQueue.SendAsync(request).ConfigureAwait(false); @@ -271,15 +272,17 @@ namespace Discord.API return responseStream; } + #endregion - //Auth + #region Auth public async Task ValidateTokenAsync(RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); await SendAsync("GET", () => "auth/login", new BucketIds(), options: options).ConfigureAwait(false); } + #endregion - //Gateway + #region Gateway public async Task GetGatewayAsync(RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); @@ -290,8 +293,9 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); return await SendAsync("GET", () => "gateway/bot", new BucketIds(), options: options).ConfigureAwait(false); } + #endregion - //Channels + #region Channels public async Task GetChannelAsync(ulong channelId, RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); @@ -316,6 +320,7 @@ namespace Discord.API var model = await SendAsync("GET", () => $"channels/{channelId}", ids, options: options).ConfigureAwait(false); if (!model.GuildId.IsSpecified || model.GuildId.Value != guildId) return null; + return model; } catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.NotFound) { return null; } @@ -334,11 +339,16 @@ namespace Discord.API Preconditions.NotNull(args, nameof(args)); Preconditions.GreaterThan(args.Bitrate, 0, nameof(args.Bitrate)); Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name)); + Preconditions.AtMost(args.Name.Length, 100, nameof(args.Name)); + if (args.Topic.IsSpecified) + Preconditions.AtMost(args.Topic.Value.Length, 1024, nameof(args.Name)); + options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(guildId: guildId); return await SendJsonAsync("POST", () => $"guilds/{guildId}/channels", args, ids, options: options).ConfigureAwait(false); } + public async Task DeleteChannelAsync(ulong channelId, RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); @@ -362,18 +372,29 @@ namespace Discord.API Preconditions.NotEqual(channelId, 0, nameof(channelId)); Preconditions.NotNull(args, nameof(args)); Preconditions.AtLeast(args.Position, 0, nameof(args.Position)); - Preconditions.NotNullOrEmpty(args.Name, nameof(args.Name)); + Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name)); + + if(args.Name.IsSpecified) + Preconditions.AtMost(args.Name.Value.Length, 100, nameof(args.Name)); + options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(channelId: channelId); return await SendJsonAsync("PATCH", () => $"channels/{channelId}", args, ids, options: options).ConfigureAwait(false); } + public async Task ModifyGuildChannelAsync(ulong channelId, Rest.ModifyTextChannelParams args, RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); Preconditions.NotNull(args, nameof(args)); Preconditions.AtLeast(args.Position, 0, nameof(args.Position)); - Preconditions.NotNullOrEmpty(args.Name, nameof(args.Name)); + Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name)); + + if(args.Name.IsSpecified) + Preconditions.AtMost(args.Name.Value.Length, 100, nameof(args.Name)); + if(args.Topic.IsSpecified) + Preconditions.AtMost(args.Topic.Value.Length, 1024, nameof(args.Name)); + Preconditions.AtLeast(args.SlowModeInterval, 0, nameof(args.SlowModeInterval)); Preconditions.AtMost(args.SlowModeInterval, 21600, nameof(args.SlowModeInterval)); options = RequestOptions.CreateOrClone(options); @@ -381,6 +402,7 @@ namespace Discord.API var ids = new BucketIds(channelId: channelId); return await SendJsonAsync("PATCH", () => $"channels/{channelId}", args, ids, options: options).ConfigureAwait(false); } + public async Task ModifyGuildChannelAsync(ulong channelId, Rest.ModifyVoiceChannelParams args, RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); @@ -388,12 +410,13 @@ namespace Discord.API Preconditions.AtLeast(args.Bitrate, 8000, nameof(args.Bitrate)); Preconditions.AtLeast(args.UserLimit, 0, nameof(args.UserLimit)); Preconditions.AtLeast(args.Position, 0, nameof(args.Position)); - Preconditions.NotNullOrEmpty(args.Name, nameof(args.Name)); + Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name)); options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(channelId: channelId); return await SendJsonAsync("PATCH", () => $"channels/{channelId}", args, ids, options: options).ConfigureAwait(false); } + public async Task ModifyGuildChannelsAsync(ulong guildId, IEnumerable args, RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); @@ -414,6 +437,266 @@ namespace Discord.API break; } } + #endregion + + #region Threads + public async Task ModifyThreadAsync(ulong channelId, ModifyThreadParams args, RequestOptions options = null) + { + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + + var bucket = new BucketIds(channelId: channelId); + + return await SendJsonAsync("PATCH", () => $"channels/{channelId}", args, bucket, options: options); + } + + public async Task StartThreadAsync(ulong channelId, ulong messageId, StartThreadParams args, RequestOptions options = null) + { + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + Preconditions.NotEqual(messageId, 0, nameof(messageId)); + + options = RequestOptions.CreateOrClone(options); + + var bucket = new BucketIds(0, channelId); + + return await SendJsonAsync("POST", () => $"channels/{channelId}/messages/{messageId}/threads", args, bucket, options: options).ConfigureAwait(false); + } + + public async Task StartThreadAsync(ulong channelId, StartThreadParams args, RequestOptions options = null) + { + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + + options = RequestOptions.CreateOrClone(options); + + var bucket = new BucketIds(channelId: channelId); + + return await SendJsonAsync("POST", () => $"channels/{channelId}/threads", args, bucket, options: options).ConfigureAwait(false); + } + + public async Task JoinThreadAsync(ulong channelId, RequestOptions options = null) + { + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + + options = RequestOptions.CreateOrClone(options); + + var bucket = new BucketIds(channelId: channelId); + + await SendAsync("PUT", () => $"channels/{channelId}/thread-members/@me", bucket, options: options).ConfigureAwait(false); + } + + public async Task AddThreadMemberAsync(ulong channelId, ulong userId, RequestOptions options = null) + { + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + Preconditions.NotEqual(userId, 0, nameof(channelId)); + + options = RequestOptions.CreateOrClone(options); + + var bucket = new BucketIds(channelId: channelId); + + await SendAsync("PUT", () => $"channels/{channelId}/thread-members/{userId}", bucket, options: options).ConfigureAwait(false); + } + + public async Task LeaveThreadAsync(ulong channelId, RequestOptions options = null) + { + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + + options = RequestOptions.CreateOrClone(options); + + var bucket = new BucketIds(channelId: channelId); + + await SendAsync("DELETE", () => $"channels/{channelId}/thread-members/@me", bucket, options: options).ConfigureAwait(false); + } + + public async Task RemoveThreadMemberAsync(ulong channelId, ulong userId, RequestOptions options = null) + { + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + Preconditions.NotEqual(userId, 0, nameof(channelId)); + + options = RequestOptions.CreateOrClone(options); + var bucket = new BucketIds(channelId: channelId); + + await SendAsync("DELETE", () => $"channels/{channelId}/thread-members/{userId}", bucket, options: options).ConfigureAwait(false); + } + + public async Task ListThreadMembersAsync(ulong channelId, RequestOptions options = null) + { + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + + options = RequestOptions.CreateOrClone(options); + + var bucket = new BucketIds(channelId: channelId); + + return await SendAsync("GET", () => $"channels/{channelId}/thread-members", bucket, options: options).ConfigureAwait(false); + } + + public async Task GetThreadMemberAsync(ulong channelId, ulong userId, RequestOptions options = null) + { + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + Preconditions.NotEqual(userId, 0, nameof(userId)); + + options = RequestOptions.CreateOrClone(options); + + var bucket = new BucketIds(channelId: channelId); + + return await SendAsync("GET", () => $"channels/{channelId}/thread-members/{userId}", bucket, options: options).ConfigureAwait(false); + } + + public async Task GetActiveThreadsAsync(ulong channelId, RequestOptions options = null) + { + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + + options = RequestOptions.CreateOrClone(options); + + var bucket = new BucketIds(channelId: channelId); + + return await SendAsync("GET", () => $"channels/{channelId}/threads/active", bucket, options: options); + } + + public async Task GetPublicArchivedThreadsAsync(ulong channelId, DateTimeOffset? before = null, int? limit = null, RequestOptions options = null) + { + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + + options = RequestOptions.CreateOrClone(options); + + var bucket = new BucketIds(channelId: channelId); + + string query = ""; + + if (limit.HasValue) + { + query = $"?before={before.GetValueOrDefault(DateTimeOffset.UtcNow).ToString("O")}&limit={limit.Value}"; + } + else if (before.HasValue) + { + query = $"?before={before.Value.ToString("O")}"; + } + + return await SendAsync("GET", () => $"channels/{channelId}/threads/archived/public{query}", bucket, options: options); + } + + public async Task GetPrivateArchivedThreadsAsync(ulong channelId, DateTimeOffset? before = null, int? limit = null, + RequestOptions options = null) + { + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + + options = RequestOptions.CreateOrClone(options); + + var bucket = new BucketIds(channelId: channelId); + + string query = ""; + + if (limit.HasValue) + { + query = $"?before={before.GetValueOrDefault(DateTimeOffset.UtcNow).ToString("O")}&limit={limit.Value}"; + } + else if (before.HasValue) + { + query = $"?before={before.Value.ToString("O")}"; + } + + return await SendAsync("GET", () => $"channels/{channelId}/threads/archived/private{query}", bucket, options: options); + } + + public async Task GetJoinedPrivateArchivedThreadsAsync(ulong channelId, DateTimeOffset? before = null, int? limit = null, + RequestOptions options = null) + { + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + + options = RequestOptions.CreateOrClone(options); + + var bucket = new BucketIds(channelId: channelId); + + string query = ""; + + if (limit.HasValue) + { + query = $"?before={SnowflakeUtils.ToSnowflake(before.GetValueOrDefault(DateTimeOffset.UtcNow))}&limit={limit.Value}"; + } + else if (before.HasValue) + { + query = $"?before={before.Value.ToString("O")}"; + } + + return await SendAsync("GET", () => $"channels/{channelId}/users/@me/threads/archived/private{query}", bucket, options: options); + } + #endregion + + #region Stage + public async Task CreateStageInstanceAsync(CreateStageInstanceParams args, RequestOptions options = null) + { + options = RequestOptions.CreateOrClone(options); + + var bucket = new BucketIds(); + + return await SendJsonAsync("POST", () => $"stage-instances", args, bucket, options: options).ConfigureAwait(false); + } + + public async Task ModifyStageInstanceAsync(ulong channelId, ModifyStageInstanceParams args, RequestOptions options = null) + { + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + + options = RequestOptions.CreateOrClone(options); + + var bucket = new BucketIds(channelId: channelId); + + return await SendJsonAsync("PATCH", () => $"stage-instances/{channelId}", args, bucket, options: options).ConfigureAwait(false); + } + + public async Task DeleteStageInstanceAsync(ulong channelId, RequestOptions options = null) + { + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + + options = RequestOptions.CreateOrClone(options); + + try + { + await SendAsync("DELETE", $"stage-instances/{channelId}", options: options).ConfigureAwait(false); + } + catch (HttpException httpEx) when (httpEx.HttpCode == HttpStatusCode.NotFound) { } + } + + public async Task GetStageInstanceAsync(ulong channelId, RequestOptions options = null) + { + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + + options = RequestOptions.CreateOrClone(options); + + var bucket = new BucketIds(channelId: channelId); + + try + { + return await SendAsync("POST", () => $"stage-instances/{channelId}", bucket, options: options).ConfigureAwait(false); + } + catch (HttpException httpEx) when (httpEx.HttpCode == HttpStatusCode.NotFound) + { + return null; + } + } + + public async Task ModifyMyVoiceState(ulong guildId, ModifyVoiceStateParams args, RequestOptions options = null) + { + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + + options = RequestOptions.CreateOrClone(options); + + var bucket = new BucketIds(); + + await SendJsonAsync("PATCH", () => $"guilds/{guildId}/voice-states/@me", args, bucket, options: options).ConfigureAwait(false); + } + + public async Task ModifyUserVoiceState(ulong guildId, ulong userId, ModifyVoiceStateParams args, RequestOptions options = null) + { + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + Preconditions.NotEqual(userId, 0, nameof(userId)); + + options = RequestOptions.CreateOrClone(options); + + var bucket = new BucketIds(); + + await SendJsonAsync("PATCH", () => $"guilds/{guildId}/voice-states/{userId}", args, bucket, options: options).ConfigureAwait(false); + } + #endregion + + #region Roles public async Task AddRoleAsync(ulong guildId, ulong userId, ulong roleId, RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); @@ -436,8 +719,9 @@ namespace Discord.API var ids = new BucketIds(guildId: guildId); await SendAsync("DELETE", () => $"guilds/{guildId}/members/{userId}/roles/{roleId}", ids, options: options).ConfigureAwait(false); } + #endregion - //Channel Messages + #region Channel Messages public async Task GetChannelMessageAsync(ulong channelId, ulong messageId, RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); @@ -461,22 +745,12 @@ namespace Discord.API int limit = args.Limit.GetValueOrDefault(DiscordConfig.MaxMessagesPerBatch); ulong? relativeId = args.RelativeMessageId.IsSpecified ? args.RelativeMessageId.Value : (ulong?)null; - string relativeDir; - - switch (args.RelativeDirection.GetValueOrDefault(Direction.Before)) + var relativeDir = args.RelativeDirection.GetValueOrDefault(Direction.Before) switch { - case Direction.Before: - default: - relativeDir = "before"; - break; - case Direction.After: - relativeDir = "after"; - break; - case Direction.Around: - relativeDir = "around"; - break; - } - + Direction.After => "after", + Direction.Around => "around", + _ => "before", + }; var ids = new BucketIds(channelId: channelId); Expression> endpoint; if (relativeId != null) @@ -490,7 +764,7 @@ namespace Discord.API { Preconditions.NotNull(args, nameof(args)); Preconditions.NotEqual(channelId, 0, nameof(channelId)); - if (!args.Embed.IsSpecified || args.Embed.Value == null) + if ((!args.Embeds.IsSpecified || args.Embeds.Value == null || args.Embeds.Value.Length == 0) && (!args.Stickers.IsSpecified || args.Stickers.Value == null || args.Stickers.Value.Length == 0)) Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content)); if (args.Content?.Length > DiscordConfig.MaxMessageSize) @@ -627,189 +901,518 @@ namespace Discord.API break; } } - /// Message content is too long, length must be less or equal to . - public async Task ModifyMessageAsync(ulong channelId, ulong messageId, Rest.ModifyMessageParams args, RequestOptions options = null) + /// Message content is too long, length must be less or equal to . + public async Task ModifyMessageAsync(ulong channelId, ulong messageId, Rest.ModifyMessageParams args, RequestOptions options = null) + { + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + Preconditions.NotEqual(messageId, 0, nameof(messageId)); + Preconditions.NotNull(args, nameof(args)); + if (args.Content.IsSpecified && args.Content.Value?.Length > DiscordConfig.MaxMessageSize) + throw new ArgumentOutOfRangeException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); + options = RequestOptions.CreateOrClone(options); + + var ids = new BucketIds(channelId: channelId); + return await SendJsonAsync("PATCH", () => $"channels/{channelId}/messages/{messageId}", args, ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false); + } + + public async Task ModifyMessageAsync(ulong channelId, ulong messageId, Rest.UploadFileParams args, RequestOptions options = null) + { + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + Preconditions.NotEqual(messageId, 0, nameof(messageId)); + Preconditions.NotNull(args, nameof(args)); + if (args.Content.IsSpecified && args.Content.Value?.Length > DiscordConfig.MaxMessageSize) + throw new ArgumentOutOfRangeException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); + options = RequestOptions.CreateOrClone(options); + + var ids = new BucketIds(channelId: channelId); + return await SendMultipartAsync("PATCH", () => $"channels/{channelId}/messages/{messageId}", args.ToDictionary(), ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false); + } + #endregion + + #region Stickers, Reactions, Crosspost, and Acks + public async Task GetStickerAsync(ulong id, RequestOptions options = null) + { + Preconditions.NotEqual(id, 0, nameof(id)); + + options = RequestOptions.CreateOrClone(options); + + return await NullifyNotFound(SendAsync("GET", () => $"stickers/{id}", new BucketIds(), options: options)).ConfigureAwait(false); + } + public async Task GetGuildStickerAsync(ulong guildId, ulong id, RequestOptions options = null) + { + Preconditions.NotEqual(id, 0, nameof(id)); + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + + options = RequestOptions.CreateOrClone(options); + + return await NullifyNotFound(SendAsync("GET", () => $"guilds/{guildId}/stickers/{id}", new BucketIds(guildId), options: options)).ConfigureAwait(false); + } + public async Task ListGuildStickersAsync(ulong guildId, RequestOptions options = null) + { + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + + options = RequestOptions.CreateOrClone(options); + + return await SendAsync("GET", () => $"guilds/{guildId}/stickers", new BucketIds(guildId), options: options).ConfigureAwait(false); + } + public async Task ListNitroStickerPacksAsync(RequestOptions options = null) + { + options = RequestOptions.CreateOrClone(options); + + return await SendAsync("GET", () => $"sticker-packs", new BucketIds(), options: options).ConfigureAwait(false); + } + public async Task CreateGuildStickerAsync(CreateStickerParams args, ulong guildId, RequestOptions options = null) + { + Preconditions.NotNull(args, nameof(args)); + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + + options = RequestOptions.CreateOrClone(options); + + return await SendMultipartAsync("POST", () => $"guilds/{guildId}/stickers", args.ToDictionary(), new BucketIds(guildId), options: options).ConfigureAwait(false); + } + public async Task ModifyStickerAsync(ModifyStickerParams args, ulong guildId, ulong stickerId, RequestOptions options = null) + { + Preconditions.NotNull(args, nameof(args)); + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + Preconditions.NotEqual(stickerId, 0, nameof(stickerId)); + + options = RequestOptions.CreateOrClone(options); + + return await SendJsonAsync("PATCH", () => $"guilds/{guildId}/stickers/{stickerId}", args, new BucketIds(guildId), options: options).ConfigureAwait(false); + } + public async Task DeleteStickerAsync(ulong guildId, ulong stickerId, RequestOptions options = null) + { + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + Preconditions.NotEqual(stickerId, 0, nameof(stickerId)); + + options = RequestOptions.CreateOrClone(options); + + await SendAsync("DELETE", () => $"guilds/{guildId}/stickers/{stickerId}", new BucketIds(guildId), options: options).ConfigureAwait(false); + } + + public async Task AddReactionAsync(ulong channelId, ulong messageId, string emoji, RequestOptions options = null) + { + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + Preconditions.NotEqual(messageId, 0, nameof(messageId)); + Preconditions.NotNullOrWhitespace(emoji, nameof(emoji)); + + options = RequestOptions.CreateOrClone(options); + options.IsReactionBucket = true; + + var ids = new BucketIds(channelId: channelId); + + // @me is non-const to fool the ratelimiter, otherwise it will put add/remove in separate buckets + var me = "@me"; + await SendAsync("PUT", () => $"channels/{channelId}/messages/{messageId}/reactions/{emoji}/{me}", ids, options: options).ConfigureAwait(false); + } + public async Task RemoveReactionAsync(ulong channelId, ulong messageId, ulong userId, string emoji, RequestOptions options = null) + { + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + Preconditions.NotEqual(messageId, 0, nameof(messageId)); + Preconditions.NotNullOrWhitespace(emoji, nameof(emoji)); + + options = RequestOptions.CreateOrClone(options); + options.IsReactionBucket = true; + + var ids = new BucketIds(channelId: channelId); + + var user = CurrentUserId.HasValue ? (userId == CurrentUserId.Value ? "@me" : userId.ToString()) : userId.ToString(); + await SendAsync("DELETE", () => $"channels/{channelId}/messages/{messageId}/reactions/{emoji}/{user}", ids, options: options).ConfigureAwait(false); + } + public async Task RemoveAllReactionsAsync(ulong channelId, ulong messageId, RequestOptions options = null) + { + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + Preconditions.NotEqual(messageId, 0, nameof(messageId)); + + options = RequestOptions.CreateOrClone(options); + + var ids = new BucketIds(channelId: channelId); + + await SendAsync("DELETE", () => $"channels/{channelId}/messages/{messageId}/reactions", ids, options: options).ConfigureAwait(false); + } + public async Task RemoveAllReactionsForEmoteAsync(ulong channelId, ulong messageId, string emoji, RequestOptions options = null) + { + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + Preconditions.NotEqual(messageId, 0, nameof(messageId)); + Preconditions.NotNullOrWhitespace(emoji, nameof(emoji)); + + options = RequestOptions.CreateOrClone(options); + + var ids = new BucketIds(channelId: channelId); + + await SendAsync("DELETE", () => $"channels/{channelId}/messages/{messageId}/reactions/{emoji}", ids, options: options).ConfigureAwait(false); + } + public async Task> GetReactionUsersAsync(ulong channelId, ulong messageId, string emoji, GetReactionUsersParams args, RequestOptions options = null) + { + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + Preconditions.NotEqual(messageId, 0, nameof(messageId)); + Preconditions.NotNullOrWhitespace(emoji, nameof(emoji)); + Preconditions.NotNull(args, nameof(args)); + Preconditions.GreaterThan(args.Limit, 0, nameof(args.Limit)); + Preconditions.AtMost(args.Limit, DiscordConfig.MaxUserReactionsPerBatch, nameof(args.Limit)); + Preconditions.GreaterThan(args.AfterUserId, 0, nameof(args.AfterUserId)); + options = RequestOptions.CreateOrClone(options); + + int limit = args.Limit.GetValueOrDefault(DiscordConfig.MaxUserReactionsPerBatch); + ulong afterUserId = args.AfterUserId.GetValueOrDefault(0); + + var ids = new BucketIds(channelId: channelId); + Expression> endpoint = () => $"channels/{channelId}/messages/{messageId}/reactions/{emoji}?limit={limit}&after={afterUserId}"; + return await SendAsync>("GET", endpoint, ids, options: options).ConfigureAwait(false); + } + public async Task AckMessageAsync(ulong channelId, ulong messageId, RequestOptions options = null) + { + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + Preconditions.NotEqual(messageId, 0, nameof(messageId)); + options = RequestOptions.CreateOrClone(options); + + var ids = new BucketIds(channelId: channelId); + await SendAsync("POST", () => $"channels/{channelId}/messages/{messageId}/ack", ids, options: options).ConfigureAwait(false); + } + public async Task TriggerTypingIndicatorAsync(ulong channelId, RequestOptions options = null) + { + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + options = RequestOptions.CreateOrClone(options); + + var ids = new BucketIds(channelId: channelId); + await SendAsync("POST", () => $"channels/{channelId}/typing", ids, options: options).ConfigureAwait(false); + } + public async Task CrosspostAsync(ulong channelId, ulong messageId, RequestOptions options = null) + { + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + Preconditions.NotEqual(messageId, 0, nameof(messageId)); + options = RequestOptions.CreateOrClone(options); + + var ids = new BucketIds(channelId: channelId); + await SendAsync("POST", () => $"channels/{channelId}/messages/{messageId}/crosspost", ids, options: options).ConfigureAwait(false); + } + #endregion + + #region Channel Permissions + public async Task ModifyChannelPermissionsAsync(ulong channelId, ulong targetId, ModifyChannelPermissionsParams args, RequestOptions options = null) + { + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + Preconditions.NotEqual(targetId, 0, nameof(targetId)); + Preconditions.NotNull(args, nameof(args)); + options = RequestOptions.CreateOrClone(options); + + var ids = new BucketIds(channelId: channelId); + await SendJsonAsync("PUT", () => $"channels/{channelId}/permissions/{targetId}", args, ids, options: options).ConfigureAwait(false); + } + public async Task DeleteChannelPermissionAsync(ulong channelId, ulong targetId, RequestOptions options = null) + { + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + Preconditions.NotEqual(targetId, 0, nameof(targetId)); + options = RequestOptions.CreateOrClone(options); + + var ids = new BucketIds(channelId: channelId); + await SendAsync("DELETE", () => $"channels/{channelId}/permissions/{targetId}", ids, options: options).ConfigureAwait(false); + } + #endregion + + #region Channel Pins + public async Task AddPinAsync(ulong channelId, ulong messageId, RequestOptions options = null) + { + Preconditions.GreaterThan(channelId, 0, nameof(channelId)); + Preconditions.GreaterThan(messageId, 0, nameof(messageId)); + options = RequestOptions.CreateOrClone(options); + + var ids = new BucketIds(channelId: channelId); + await SendAsync("PUT", () => $"channels/{channelId}/pins/{messageId}", ids, options: options).ConfigureAwait(false); + + } + public async Task RemovePinAsync(ulong channelId, ulong messageId, RequestOptions options = null) + { + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + Preconditions.NotEqual(messageId, 0, nameof(messageId)); + options = RequestOptions.CreateOrClone(options); + + var ids = new BucketIds(channelId: channelId); + await SendAsync("DELETE", () => $"channels/{channelId}/pins/{messageId}", ids, options: options).ConfigureAwait(false); + } + public async Task> GetPinsAsync(ulong channelId, RequestOptions options = null) + { + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + options = RequestOptions.CreateOrClone(options); + + var ids = new BucketIds(channelId: channelId); + return await SendAsync>("GET", () => $"channels/{channelId}/pins", ids, options: options).ConfigureAwait(false); + } + #endregion + + #region Channel Recipients + public async Task AddGroupRecipientAsync(ulong channelId, ulong userId, RequestOptions options = null) + { + Preconditions.GreaterThan(channelId, 0, nameof(channelId)); + Preconditions.GreaterThan(userId, 0, nameof(userId)); + options = RequestOptions.CreateOrClone(options); + + var ids = new BucketIds(channelId: channelId); + await SendAsync("PUT", () => $"channels/{channelId}/recipients/{userId}", ids, options: options).ConfigureAwait(false); + + } + public async Task RemoveGroupRecipientAsync(ulong channelId, ulong userId, RequestOptions options = null) + { + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + Preconditions.NotEqual(userId, 0, nameof(userId)); + options = RequestOptions.CreateOrClone(options); + + var ids = new BucketIds(channelId: channelId); + await SendAsync("DELETE", () => $"channels/{channelId}/recipients/{userId}", ids, options: options).ConfigureAwait(false); + } + #endregion + + #region Interactions + public async Task GetGlobalApplicationCommandsAsync(RequestOptions options = null) + { + options = RequestOptions.CreateOrClone(options); + + return await SendAsync("GET", () => $"applications/{CurrentUserId}/commands", new BucketIds(), options: options).ConfigureAwait(false); + } + + public async Task GetGlobalApplicationCommandAsync(ulong id, RequestOptions options = null) + { + Preconditions.NotEqual(id, 0, nameof(id)); + + options = RequestOptions.CreateOrClone(options); + + try + { + return await SendAsync("GET", () => $"applications/{CurrentUserId}/commands/{id}", new BucketIds(), options: options).ConfigureAwait(false); + } + catch (HttpException x) when (x.HttpCode == HttpStatusCode.NotFound) { return null; } + } + + public async Task CreateGlobalApplicationCommandAsync(CreateApplicationCommandParams command, RequestOptions options = null) + { + Preconditions.NotNull(command, nameof(command)); + Preconditions.AtMost(command.Name.Length, 32, nameof(command.Name)); + Preconditions.AtLeast(command.Name.Length, 1, nameof(command.Name)); + + if (command.Type == ApplicationCommandType.Slash) + { + Preconditions.NotNullOrEmpty(command.Description, nameof(command.Description)); + Preconditions.AtMost(command.Description.Length, 100, nameof(command.Description)); + Preconditions.AtLeast(command.Description.Length, 1, nameof(command.Description)); + } + + options = RequestOptions.CreateOrClone(options); + + return await TrySendApplicationCommandAsync(SendJsonAsync("POST", () => $"applications/{CurrentUserId}/commands", command, new BucketIds(), options: options)).ConfigureAwait(false); + } + public async Task ModifyGlobalApplicationCommandAsync(ModifyApplicationCommandParams command, ulong commandId, RequestOptions options = null) + { + options = RequestOptions.CreateOrClone(options); + + return await TrySendApplicationCommandAsync(SendJsonAsync("PATCH", () => $"applications/{CurrentUserId}/commands/{commandId}", command, new BucketIds(), options: options)).ConfigureAwait(false); + } + public async Task ModifyGlobalApplicationUserCommandAsync(ModifyApplicationCommandParams command, ulong commandId, RequestOptions options = null) + { + options = RequestOptions.CreateOrClone(options); + + return await TrySendApplicationCommandAsync(SendJsonAsync("PATCH", () => $"applications/{CurrentUserId}/commands/{commandId}", command, new BucketIds(), options: options)).ConfigureAwait(false); + } + public async Task ModifyGlobalApplicationMessageCommandAsync(ModifyApplicationCommandParams command, ulong commandId, RequestOptions options = null) + { + options = RequestOptions.CreateOrClone(options); + + return await TrySendApplicationCommandAsync(SendJsonAsync("PATCH", () => $"applications/{CurrentUserId}/commands/{commandId}", command, new BucketIds(), options: options)).ConfigureAwait(false); + } + public async Task DeleteGlobalApplicationCommandAsync(ulong commandId, RequestOptions options = null) { - Preconditions.NotEqual(channelId, 0, nameof(channelId)); - Preconditions.NotEqual(messageId, 0, nameof(messageId)); - Preconditions.NotNull(args, nameof(args)); - if (args.Content.IsSpecified && args.Content.Value?.Length > DiscordConfig.MaxMessageSize) - throw new ArgumentOutOfRangeException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); options = RequestOptions.CreateOrClone(options); - var ids = new BucketIds(channelId: channelId); - return await SendJsonAsync("PATCH", () => $"channels/{channelId}/messages/{messageId}", args, ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false); + await SendAsync("DELETE", () => $"applications/{CurrentUserId}/commands/{commandId}", new BucketIds(), options: options).ConfigureAwait(false); } - public async Task AddReactionAsync(ulong channelId, ulong messageId, string emoji, RequestOptions options = null) + public async Task BulkOverwriteGlobalApplicationCommandsAsync(CreateApplicationCommandParams[] commands, RequestOptions options = null) { - Preconditions.NotEqual(channelId, 0, nameof(channelId)); - Preconditions.NotEqual(messageId, 0, nameof(messageId)); - Preconditions.NotNullOrWhitespace(emoji, nameof(emoji)); + options = RequestOptions.CreateOrClone(options); + return await TrySendApplicationCommandAsync(SendJsonAsync("PUT", () => $"applications/{CurrentUserId}/commands", commands, new BucketIds(), options: options)).ConfigureAwait(false); + } + + public async Task GetGuildApplicationCommandsAsync(ulong guildId, RequestOptions options = null) + { options = RequestOptions.CreateOrClone(options); - options.IsReactionBucket = true; - var ids = new BucketIds(channelId: channelId); + var bucket = new BucketIds(guildId: guildId); - // @me is non-const to fool the ratelimiter, otherwise it will put add/remove in separate buckets - var me = "@me"; - await SendAsync("PUT", () => $"channels/{channelId}/messages/{messageId}/reactions/{emoji}/{me}", ids, options: options).ConfigureAwait(false); + return await SendAsync("GET", () => $"applications/{CurrentUserId}/guilds/{guildId}/commands", bucket, options: options).ConfigureAwait(false); } - public async Task RemoveReactionAsync(ulong channelId, ulong messageId, ulong userId, string emoji, RequestOptions options = null) - { - Preconditions.NotEqual(channelId, 0, nameof(channelId)); - Preconditions.NotEqual(messageId, 0, nameof(messageId)); - Preconditions.NotNullOrWhitespace(emoji, nameof(emoji)); + public async Task GetGuildApplicationCommandAsync(ulong guildId, ulong commandId, RequestOptions options = null) + { options = RequestOptions.CreateOrClone(options); - options.IsReactionBucket = true; - var ids = new BucketIds(channelId: channelId); + var bucket = new BucketIds(guildId: guildId); - var user = CurrentUserId.HasValue ? (userId == CurrentUserId.Value ? "@me" : userId.ToString()) : userId.ToString(); - await SendAsync("DELETE", () => $"channels/{channelId}/messages/{messageId}/reactions/{emoji}/{user}", ids, options: options).ConfigureAwait(false); + try + { + return await SendAsync("GET", () => $"applications/{CurrentUserId}/guilds/{guildId}/commands/{commandId}", bucket, options: options); + } + catch (HttpException x) when (x.HttpCode == HttpStatusCode.NotFound) { return null; } } - public async Task RemoveAllReactionsAsync(ulong channelId, ulong messageId, RequestOptions options = null) + + public async Task CreateGuildApplicationCommandAsync(CreateApplicationCommandParams command, ulong guildId, RequestOptions options = null) { - Preconditions.NotEqual(channelId, 0, nameof(channelId)); - Preconditions.NotEqual(messageId, 0, nameof(messageId)); + Preconditions.NotNull(command, nameof(command)); + Preconditions.AtMost(command.Name.Length, 32, nameof(command.Name)); + Preconditions.AtLeast(command.Name.Length, 1, nameof(command.Name)); + + if (command.Type == ApplicationCommandType.Slash) + { + Preconditions.NotNullOrEmpty(command.Description, nameof(command.Description)); + Preconditions.AtMost(command.Description.Length, 100, nameof(command.Description)); + Preconditions.AtLeast(command.Description.Length, 1, nameof(command.Description)); + } options = RequestOptions.CreateOrClone(options); - var ids = new BucketIds(channelId: channelId); + var bucket = new BucketIds(guildId: guildId); - await SendAsync("DELETE", () => $"channels/{channelId}/messages/{messageId}/reactions", ids, options: options).ConfigureAwait(false); + return await TrySendApplicationCommandAsync(SendJsonAsync("POST", () => $"applications/{CurrentUserId}/guilds/{guildId}/commands", command, bucket, options: options)).ConfigureAwait(false); } - public async Task RemoveAllReactionsForEmoteAsync(ulong channelId, ulong messageId, string emoji, RequestOptions options = null) + public async Task ModifyGuildApplicationCommandAsync(ModifyApplicationCommandParams command, ulong guildId, ulong commandId, RequestOptions options = null) { - Preconditions.NotEqual(channelId, 0, nameof(channelId)); - Preconditions.NotEqual(messageId, 0, nameof(messageId)); - Preconditions.NotNullOrWhitespace(emoji, nameof(emoji)); - options = RequestOptions.CreateOrClone(options); - var ids = new BucketIds(channelId: channelId); + var bucket = new BucketIds(guildId: guildId); - await SendAsync("DELETE", () => $"channels/{channelId}/messages/{messageId}/reactions/{emoji}", ids, options: options).ConfigureAwait(false); + return await TrySendApplicationCommandAsync(SendJsonAsync("PATCH", () => $"applications/{CurrentUserId}/guilds/{guildId}/commands/{commandId}", command, bucket, options: options)).ConfigureAwait(false); } - public async Task> GetReactionUsersAsync(ulong channelId, ulong messageId, string emoji, GetReactionUsersParams args, RequestOptions options = null) + public async Task DeleteGuildApplicationCommandAsync(ulong guildId, ulong commandId, RequestOptions options = null) { - Preconditions.NotEqual(channelId, 0, nameof(channelId)); - Preconditions.NotEqual(messageId, 0, nameof(messageId)); - Preconditions.NotNullOrWhitespace(emoji, nameof(emoji)); - Preconditions.NotNull(args, nameof(args)); - Preconditions.GreaterThan(args.Limit, 0, nameof(args.Limit)); - Preconditions.AtMost(args.Limit, DiscordConfig.MaxUserReactionsPerBatch, nameof(args.Limit)); - Preconditions.GreaterThan(args.AfterUserId, 0, nameof(args.AfterUserId)); options = RequestOptions.CreateOrClone(options); - int limit = args.Limit.GetValueOrDefault(DiscordConfig.MaxUserReactionsPerBatch); - ulong afterUserId = args.AfterUserId.GetValueOrDefault(0); + var bucket = new BucketIds(guildId: guildId); - var ids = new BucketIds(channelId: channelId); - Expression> endpoint = () => $"channels/{channelId}/messages/{messageId}/reactions/{emoji}?limit={limit}&after={afterUserId}"; - return await SendAsync>("GET", endpoint, ids, options: options).ConfigureAwait(false); + await SendAsync("DELETE", () => $"applications/{CurrentUserId}/guilds/{guildId}/commands/{commandId}", bucket, options: options).ConfigureAwait(false); } - public async Task AckMessageAsync(ulong channelId, ulong messageId, RequestOptions options = null) + + public async Task BulkOverwriteGuildApplicationCommandsAsync(ulong guildId, CreateApplicationCommandParams[] commands, RequestOptions options = null) { - Preconditions.NotEqual(channelId, 0, nameof(channelId)); - Preconditions.NotEqual(messageId, 0, nameof(messageId)); options = RequestOptions.CreateOrClone(options); - var ids = new BucketIds(channelId: channelId); - await SendAsync("POST", () => $"channels/{channelId}/messages/{messageId}/ack", ids, options: options).ConfigureAwait(false); + var bucket = new BucketIds(guildId: guildId); + + return await TrySendApplicationCommandAsync(SendJsonAsync("PUT", () => $"applications/{CurrentUserId}/guilds/{guildId}/commands", commands, bucket, options: options)).ConfigureAwait(false); } - public async Task TriggerTypingIndicatorAsync(ulong channelId, RequestOptions options = null) + #endregion + + #region Interaction Responses + public async Task CreateInteractionResponseAsync(InteractionResponse response, ulong interactionId, string interactionToken, RequestOptions options = null) { - Preconditions.NotEqual(channelId, 0, nameof(channelId)); + if (response.Data.IsSpecified && response.Data.Value.Content.IsSpecified) + Preconditions.AtMost(response.Data.Value.Content.Value?.Length ?? 0, 2000, nameof(response.Data.Value.Content)); + options = RequestOptions.CreateOrClone(options); - var ids = new BucketIds(channelId: channelId); - await SendAsync("POST", () => $"channels/{channelId}/typing", ids, options: options).ConfigureAwait(false); + await SendJsonAsync("POST", () => $"interactions/{interactionId}/{interactionToken}/callback", response, new BucketIds(), options: options); } - public async Task CrosspostAsync(ulong channelId, ulong messageId, RequestOptions options = null) + public async Task GetInteractionResponseAsync(string interactionToken, RequestOptions options = null) { - Preconditions.NotEqual(channelId, 0, nameof(channelId)); - Preconditions.NotEqual(messageId, 0, nameof(messageId)); + Preconditions.NotNullOrEmpty(interactionToken, nameof(interactionToken)); + options = RequestOptions.CreateOrClone(options); - var ids = new BucketIds(channelId: channelId); - await SendAsync("POST", () => $"channels/{channelId}/messages/{messageId}/crosspost", ids, options: options).ConfigureAwait(false); + return await SendAsync("GET", () => $"webhooks/{CurrentUserId}/{interactionToken}/messages/@original", new BucketIds(), options: options).ConfigureAwait(false); } - - //Channel Permissions - public async Task ModifyChannelPermissionsAsync(ulong channelId, ulong targetId, ModifyChannelPermissionsParams args, RequestOptions options = null) + public async Task ModifyInteractionResponseAsync(ModifyInteractionResponseParams args, string interactionToken, RequestOptions options = null) { - Preconditions.NotEqual(channelId, 0, nameof(channelId)); - Preconditions.NotEqual(targetId, 0, nameof(targetId)); - Preconditions.NotNull(args, nameof(args)); options = RequestOptions.CreateOrClone(options); - var ids = new BucketIds(channelId: channelId); - await SendJsonAsync("PUT", () => $"channels/{channelId}/permissions/{targetId}", args, ids, options: options).ConfigureAwait(false); + return await SendJsonAsync("PATCH", () => $"webhooks/{CurrentUserId}/{interactionToken}/messages/@original", args, new BucketIds(), options: options); } - public async Task DeleteChannelPermissionAsync(ulong channelId, ulong targetId, RequestOptions options = null) + public async Task DeleteInteractionResponseAsync(string interactionToken, RequestOptions options = null) { - Preconditions.NotEqual(channelId, 0, nameof(channelId)); - Preconditions.NotEqual(targetId, 0, nameof(targetId)); options = RequestOptions.CreateOrClone(options); - var ids = new BucketIds(channelId: channelId); - await SendAsync("DELETE", () => $"channels/{channelId}/permissions/{targetId}", ids, options: options).ConfigureAwait(false); + await SendAsync("DELETE", () => $"webhooks/{CurrentUserId}/{interactionToken}/messages/@original", new BucketIds(), options: options); } - //Channel Pins - public async Task AddPinAsync(ulong channelId, ulong messageId, RequestOptions options = null) + public async Task CreateInteractionFollowupMessageAsync(CreateWebhookMessageParams args, string token, RequestOptions options = null) { - Preconditions.GreaterThan(channelId, 0, nameof(channelId)); - Preconditions.GreaterThan(messageId, 0, nameof(messageId)); + if ((!args.Embeds.IsSpecified || args.Embeds.Value == null || args.Embeds.Value.Length == 0) && !args.File.IsSpecified) + Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content)); + + if (args.Content?.Length > DiscordConfig.MaxMessageSize) + throw new ArgumentException(message: $"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", paramName: nameof(args.Content)); + options = RequestOptions.CreateOrClone(options); - var ids = new BucketIds(channelId: channelId); - await SendAsync("PUT", () => $"channels/{channelId}/pins/{messageId}", ids, options: options).ConfigureAwait(false); + if (!args.File.IsSpecified) + return await SendJsonAsync("POST", () => $"webhooks/{CurrentUserId}/{token}?wait=true", args, new BucketIds(), options: options).ConfigureAwait(false); + else + return await SendMultipartAsync("POST", () => $"webhooks/{CurrentUserId}/{token}?wait=true", args.ToDictionary(), new BucketIds(), options: options).ConfigureAwait(false); + } + + public async Task ModifyInteractionFollowupMessageAsync(ModifyInteractionResponseParams args, ulong id, string token, RequestOptions options = null) + { + Preconditions.NotNull(args, nameof(args)); + Preconditions.NotEqual(id, 0, nameof(id)); + if (args.Content.IsSpecified) + if (args.Content.Value.Length > DiscordConfig.MaxMessageSize) + throw new ArgumentException(message: $"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", paramName: nameof(args.Content)); + + options = RequestOptions.CreateOrClone(options); + + return await SendJsonAsync("PATCH", () => $"webhooks/{CurrentUserId}/{token}/messages/{id}", args, new BucketIds(), options: options).ConfigureAwait(false); } - public async Task RemovePinAsync(ulong channelId, ulong messageId, RequestOptions options = null) + + public async Task DeleteInteractionFollowupMessageAsync(ulong id, string token, RequestOptions options = null) { - Preconditions.NotEqual(channelId, 0, nameof(channelId)); - Preconditions.NotEqual(messageId, 0, nameof(messageId)); + Preconditions.NotEqual(id, 0, nameof(id)); + options = RequestOptions.CreateOrClone(options); - var ids = new BucketIds(channelId: channelId); - await SendAsync("DELETE", () => $"channels/{channelId}/pins/{messageId}", ids, options: options).ConfigureAwait(false); + await SendAsync("DELETE", () => $"webhooks/{CurrentUserId}/{token}/messages/{id}", new BucketIds(), options: options).ConfigureAwait(false); } - public async Task> GetPinsAsync(ulong channelId, RequestOptions options = null) + #endregion + + #region Application Command permissions + public async Task GetGuildApplicationCommandPermissionsAsync(ulong guildId, RequestOptions options = null) { - Preconditions.NotEqual(channelId, 0, nameof(channelId)); + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + options = RequestOptions.CreateOrClone(options); - var ids = new BucketIds(channelId: channelId); - return await SendAsync>("GET", () => $"channels/{channelId}/pins", ids, options: options).ConfigureAwait(false); + return await SendAsync("GET", () => $"applications/{CurrentUserId}/guilds/{guildId}/commands/permissions", new BucketIds(), options: options).ConfigureAwait(false); } - //Channel Recipients - public async Task AddGroupRecipientAsync(ulong channelId, ulong userId, RequestOptions options = null) + public async Task GetGuildApplicationCommandPermissionAsync(ulong guildId, ulong commandId, RequestOptions options = null) { - Preconditions.GreaterThan(channelId, 0, nameof(channelId)); - Preconditions.GreaterThan(userId, 0, nameof(userId)); + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + Preconditions.NotEqual(commandId, 0, nameof(commandId)); + options = RequestOptions.CreateOrClone(options); - var ids = new BucketIds(channelId: channelId); - await SendAsync("PUT", () => $"channels/{channelId}/recipients/{userId}", ids, options: options).ConfigureAwait(false); + return await SendAsync("GET", () => $"applications/{CurrentUserId}/guilds/{guildId}/commands/{commandId}/permissions", new BucketIds(), options: options).ConfigureAwait(false); + } + + public async Task ModifyApplicationCommandPermissionsAsync(ModifyGuildApplicationCommandPermissionsParams permissions, ulong guildId, ulong commandId, RequestOptions options = null) + { + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + Preconditions.NotEqual(commandId, 0, nameof(commandId)); + options = RequestOptions.CreateOrClone(options); + + return await SendJsonAsync("PUT", () => $"applications/{CurrentUserId}/guilds/{guildId}/commands/{commandId}/permissions", permissions, new BucketIds(), options: options).ConfigureAwait(false); } - public async Task RemoveGroupRecipientAsync(ulong channelId, ulong userId, RequestOptions options = null) + + public async Task> BatchModifyApplicationCommandPermissionsAsync(ModifyGuildApplicationCommandPermissions[] permissions, ulong guildId, RequestOptions options = null) { - Preconditions.NotEqual(channelId, 0, nameof(channelId)); - Preconditions.NotEqual(userId, 0, nameof(userId)); + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + Preconditions.NotNull(permissions, nameof(permissions)); + options = RequestOptions.CreateOrClone(options); - var ids = new BucketIds(channelId: channelId); - await SendAsync("DELETE", () => $"channels/{channelId}/recipients/{userId}", ids, options: options).ConfigureAwait(false); + return await SendJsonAsync("PUT", () => $"applications/{CurrentUserId}/guilds/{guildId}/commands/permissions", permissions, new BucketIds(), options: options).ConfigureAwait(false); } + #endregion - //Guilds + #region Guilds public async Task GetGuildAsync(ulong guildId, bool withCounts, RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); @@ -882,8 +1485,9 @@ namespace Discord.API var ids = new BucketIds(guildId: guildId); return await SendAsync("GET", () => $"guilds/{guildId}/prune?days={args.Days}{endpointRoleIds}", ids, options: options).ConfigureAwait(false); } + #endregion - //Guild Bans + #region Guild Bans public async Task> GetGuildBansAsync(ulong guildId, RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); @@ -934,8 +1538,9 @@ namespace Discord.API var ids = new BucketIds(guildId: guildId); await SendAsync("DELETE", () => $"guilds/{guildId}/bans/{userId}", ids, options: options).ConfigureAwait(false); } + #endregion - //Guild Widget + #region Guild Widget /// must not be equal to zero. public async Task GetGuildWidgetAsync(ulong guildId, RequestOptions options = null) { @@ -960,8 +1565,9 @@ namespace Discord.API var ids = new BucketIds(guildId: guildId); return await SendJsonAsync("PATCH", () => $"guilds/{guildId}/widget", args, ids, options: options).ConfigureAwait(false); } + #endregion - //Guild Integrations + #region Guild Integrations /// must not be equal to zero. public async Task> GetGuildIntegrationsAsync(ulong guildId, RequestOptions options = null) { @@ -1013,8 +1619,9 @@ namespace Discord.API var ids = new BucketIds(guildId: guildId); return await SendAsync("POST", () => $"guilds/{guildId}/integrations/{integrationId}/sync", ids, options: options).ConfigureAwait(false); } + #endregion - //Guild Invites + #region Guild Invites /// cannot be blank. /// must not be . public async Task GetInviteAsync(string inviteId, RequestOptions options = null) @@ -1079,6 +1686,12 @@ namespace Discord.API Preconditions.AtLeast(args.MaxUses, 0, nameof(args.MaxUses)); Preconditions.AtMost(args.MaxAge, 86400, nameof(args.MaxAge), "The maximum age of an invite must be less than or equal to a day (86400 seconds)."); + if (args.TargetType.IsSpecified) + { + Preconditions.NotEqual((int)args.TargetType.Value, (int)TargetUserType.Undefined, nameof(args.TargetType)); + if (args.TargetType.Value == TargetUserType.Stream) Preconditions.GreaterThan(args.TargetUserId, 0, nameof(args.TargetUserId)); + if (args.TargetType.Value == TargetUserType.EmbeddedApplication) Preconditions.GreaterThan(args.TargetApplicationId, 0, nameof(args.TargetUserId)); + } options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(channelId: channelId); @@ -1091,8 +1704,9 @@ namespace Discord.API return await SendAsync("DELETE", () => $"invites/{inviteId}", new BucketIds(), options: options).ConfigureAwait(false); } + #endregion - //Guild Members + #region Guild Members public async Task AddGuildMemberAsync(ulong guildId, ulong userId, AddGuildMemberParams args, RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); @@ -1190,8 +1804,9 @@ namespace Discord.API Expression> endpoint = () => $"guilds/{guildId}/members/search?limit={limit}&query={query}"; return await SendAsync>("GET", endpoint, ids, options: options).ConfigureAwait(false); } + #endregion - //Guild Roles + #region Guild Roles public async Task> GetGuildRolesAsync(ulong guildId, RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); @@ -1238,8 +1853,9 @@ namespace Discord.API var ids = new BucketIds(guildId: guildId); return await SendJsonAsync>("PATCH", () => $"guilds/{guildId}/roles", args, ids, options: options).ConfigureAwait(false); } + #endregion - //Guild emoji + #region Guild emoji public async Task> GetGuildEmotesAsync(ulong guildId, RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); @@ -1291,8 +1907,108 @@ namespace Discord.API var ids = new BucketIds(guildId: guildId); await SendAsync("DELETE", () => $"guilds/{guildId}/emojis/{emoteId}", ids, options: options).ConfigureAwait(false); } + #endregion + + #region Guild Events + + public async Task ListGuildScheduledEventsAsync(ulong guildId, RequestOptions options = null) + { + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + options = RequestOptions.CreateOrClone(options); + + var ids = new BucketIds(guildId: guildId); + return await SendAsync("GET", () => $"guilds/{guildId}/scheduled-events?with_user_count=true", ids, options: options).ConfigureAwait(false); + } + + public async Task GetGuildScheduledEventAsync(ulong eventId, ulong guildId, RequestOptions options = null) + { + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + Preconditions.NotEqual(eventId, 0, nameof(eventId)); + options = RequestOptions.CreateOrClone(options); + + var ids = new BucketIds(guildId: guildId); + + return await NullifyNotFound(SendAsync("GET", () => $"guilds/{guildId}/scheduled-events/{eventId}?with_user_count=true", ids, options: options)).ConfigureAwait(false); + } + + public async Task CreateGuildScheduledEventAsync(CreateGuildScheduledEventParams args, ulong guildId, RequestOptions options = null) + { + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + Preconditions.NotNull(args, nameof(args)); + + options = RequestOptions.CreateOrClone(options); + + var ids = new BucketIds(guildId: guildId); + + return await SendJsonAsync("POST", () => $"guilds/{guildId}/scheduled-events", args, ids, options: options).ConfigureAwait(false); + } + + public async Task ModifyGuildScheduledEventAsync(ModifyGuildScheduledEventParams args, ulong eventId, ulong guildId, RequestOptions options = null) + { + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + Preconditions.NotEqual(eventId, 0, nameof(eventId)); + Preconditions.NotNull(args, nameof(args)); + + options = RequestOptions.CreateOrClone(options); + + var ids = new BucketIds(guildId: guildId); + + return await SendJsonAsync("PATCH", () => $"guilds/{guildId}/scheduled-events/{eventId}", args, ids, options: options).ConfigureAwait(false); + } + + public async Task DeleteGuildScheduledEventAsync(ulong eventId, ulong guildId, RequestOptions options = null) + { + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + Preconditions.NotEqual(eventId, 0, nameof(eventId)); + + options = RequestOptions.CreateOrClone(options); + + var ids = new BucketIds(guildId: guildId); + + await SendAsync("DELETE", () => $"guilds/{guildId}/scheduled-events/{eventId}", ids, options: options).ConfigureAwait(false); + } + + public async Task GetGuildScheduledEventUsersAsync(ulong eventId, ulong guildId, int limit = 100, RequestOptions options = null) + { + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + Preconditions.NotEqual(eventId, 0, nameof(eventId)); + + options = RequestOptions.CreateOrClone(options); + + var ids = new BucketIds(guildId: guildId); + + return await SendAsync("GET", () => $"guilds/{guildId}/scheduled-events/{eventId}/users?limit={limit}&with_member=true", ids, options: options).ConfigureAwait(false); + } + + public async Task GetGuildScheduledEventUsersAsync(ulong eventId, ulong guildId, GetEventUsersParams args, RequestOptions options = null) + { + Preconditions.NotEqual(eventId, 0, nameof(eventId)); + Preconditions.NotNull(args, nameof(args)); + Preconditions.AtLeast(args.Limit, 0, nameof(args.Limit)); + Preconditions.AtMost(args.Limit, DiscordConfig.MaxMessagesPerBatch, nameof(args.Limit)); + options = RequestOptions.CreateOrClone(options); + + int limit = args.Limit.GetValueOrDefault(DiscordConfig.MaxGuildEventUsersPerBatch); + ulong? relativeId = args.RelativeUserId.IsSpecified ? args.RelativeUserId.Value : (ulong?)null; + var relativeDir = args.RelativeDirection.GetValueOrDefault(Direction.Before) switch + { + Direction.After => "after", + Direction.Around => "around", + _ => "before", + }; + var ids = new BucketIds(guildId: guildId); + Expression> endpoint; + if (relativeId != null) + endpoint = () => $"guilds/{guildId}/scheduled-events/{eventId}/users?with_member=true&limit={limit}&{relativeDir}={relativeId}"; + else + endpoint = () => $"guilds/{guildId}/scheduled-events/{eventId}/users?with_member=true&limit={limit}"; + + return await SendAsync("GET", endpoint, ids, options: options).ConfigureAwait(false); + } + + #endregion - //Users + #region Users public async Task GetUserAsync(ulong userId, RequestOptions options = null) { Preconditions.NotEqual(userId, 0, nameof(userId)); @@ -1304,8 +2020,9 @@ namespace Discord.API } catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.NotFound) { return null; } } + #endregion - //Current User/DMs + #region Current User/DMs public async Task GetMyUserAsync(RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); @@ -1364,8 +2081,9 @@ namespace Discord.API return await SendJsonAsync("POST", () => "users/@me/channels", args, new BucketIds(), options: options).ConfigureAwait(false); } + #endregion - //Voice Regions + #region Voice Regions public async Task> GetVoiceRegionsAsync(RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); @@ -1379,8 +2097,9 @@ namespace Discord.API var ids = new BucketIds(guildId: guildId); return await SendAsync>("GET", () => $"guilds/{guildId}/regions", ids, options: options).ConfigureAwait(false); } + #endregion - //Audit logs + #region Audit logs public async Task GetAuditLogsAsync(ulong guildId, GetAuditLogsParams args, RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); @@ -1409,12 +2128,13 @@ namespace Discord.API .Append(args.ActionType.Value); } - // still use string interp for the query w/o params, as this is necessary for CreateBucketId + // Still use string interp for the query w/o params, as this is necessary for CreateBucketId endpoint = () => $"guilds/{guildId}/audit-logs?limit={limit}{queryArgs.ToString()}"; return await SendAsync("GET", endpoint, ids, options: options).ConfigureAwait(false); } + #endregion - //Webhooks + #region Webhooks public async Task CreateWebhookAsync(ulong channelId, CreateWebhookParams args, RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); @@ -1477,8 +2197,9 @@ namespace Discord.API var ids = new BucketIds(channelId: channelId); return await SendAsync>("GET", () => $"channels/{channelId}/webhooks", ids, options: options).ConfigureAwait(false); } + #endregion - //Helpers + #region Helpers /// Client is not logged in. protected void CheckState() { @@ -1501,6 +2222,66 @@ namespace Discord.API return _serializer.Deserialize(reader); } + protected async Task TrySendApplicationCommandAsync(Task sendTask) + { + try + { + var result = await sendTask.ConfigureAwait(false); + + if (sendTask.Exception != null) + { + if (sendTask.Exception.InnerException is HttpException x) + { + if (x.HttpCode == HttpStatusCode.BadRequest) + { + var json = (x.Request as JsonRestRequest).Json; + throw new ApplicationCommandException(x); + } + } + + throw sendTask.Exception; + } + else + return result; + } + catch (HttpException x) + { + if (x.HttpCode == HttpStatusCode.BadRequest) + { + var json = (x.Request as JsonRestRequest).Json; + throw new ApplicationCommandException(x); + } + + throw; + } + } + + protected async Task NullifyNotFound(Task sendTask) where T : class + { + try + { + var result = await sendTask.ConfigureAwait(false); + + if (sendTask.Exception != null) + { + if (sendTask.Exception.InnerException is HttpException x) + { + if (x.HttpCode == HttpStatusCode.NotFound) + { + return null; + } + } + + throw sendTask.Exception; + } + else + return result; + } + catch (HttpException x) when (x.HttpCode == HttpStatusCode.NotFound) + { + return null; + } + } internal class BucketIds { public ulong GuildId { get; internal set; } @@ -1532,15 +2313,14 @@ namespace Discord.API internal static int? GetIndex(string name) { - switch (name) + return name switch { - case "httpMethod": return 0; - case "guildId": return 1; - case "channelId": return 2; - case "webhookId": return 3; - default: - return null; - } + "httpMethod" => 0, + "guildId" => 1, + "channelId" => 2, + "webhookId" => 3, + _ => null, + }; } } @@ -1576,7 +2356,7 @@ namespace Discord.API Array.Copy(elements, 0, methodArgs, 1, elements.Length); } - int endIndex = format.IndexOf('?'); //Dont include params + int endIndex = format.IndexOf('?'); //Don't include params if (endIndex == -1) endIndex = format.Length; @@ -1628,5 +2408,6 @@ namespace Discord.API return (expr as MemberExpression).Member.Name; } + #endregion } } diff --git a/src/Discord.Net.Rest/DiscordRestClient.cs b/src/Discord.Net.Rest/DiscordRestClient.cs index b5bdc4235..93183161b 100644 --- a/src/Discord.Net.Rest/DiscordRestClient.cs +++ b/src/Discord.Net.Rest/DiscordRestClient.cs @@ -1,7 +1,13 @@ +//using Discord.Rest.Entities.Interactions; +using Discord.Net; +using Discord.Net.Converters; +using Discord.Net.ED25519; +using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Collections.Immutable; using System.IO; +using System.Text; using System.Threading.Tasks; namespace Discord.Rest @@ -11,13 +17,15 @@ namespace Discord.Rest /// public class DiscordRestClient : BaseDiscordClient, IDiscordClient { + #region DiscordRestClient private RestApplication _applicationInfo; + internal static JsonSerializer Serializer = new JsonSerializer() { ContractResolver = new DiscordContractResolver(), NullValueHandling = NullValueHandling.Include }; /// /// Gets the logged-in user. /// public new RestSelfUser CurrentUser { get => base.CurrentUser as RestSelfUser; internal set => base.CurrentUser = value; } - + /// public DiscordRestClient() : this(new DiscordRestConfig()) { } /// @@ -29,7 +37,7 @@ namespace Discord.Rest internal DiscordRestClient(DiscordRestConfig config, API.DiscordRestApiClient api) : base(config, api) { } private static API.DiscordRestApiClient CreateApiClient(DiscordRestConfig config) - => new API.DiscordRestApiClient(config.RestClientProvider, DiscordRestConfig.UserAgent, useSystemClock: config.UseSystemClock); + => new API.DiscordRestApiClient(config.RestClientProvider, DiscordRestConfig.UserAgent, serializer: Serializer, useSystemClock: config.UseSystemClock); internal override void Dispose(bool disposing) { @@ -46,6 +54,11 @@ namespace Discord.Rest ApiClient.CurrentUserId = user.Id; base.CurrentUser = RestSelfUser.Create(this, user); } + + internal void CreateRestSelfUser(API.User user) + { + base.CurrentUser = RestSelfUser.Create(this, user); + } /// internal override Task OnLogoutAsync() { @@ -53,9 +66,73 @@ namespace Discord.Rest return Task.Delay(0); } + #region Rest interactions + + public bool IsValidHttpInteraction(string publicKey, string signature, string timestamp, string body) + => IsValidHttpInteraction(publicKey, signature, timestamp, Encoding.UTF8.GetBytes(body)); + public bool IsValidHttpInteraction(string publicKey, string signature, string timestamp, byte[] body) + { + var key = HexConverter.HexToByteArray(publicKey); + var sig = HexConverter.HexToByteArray(signature); + var tsp = Encoding.UTF8.GetBytes(timestamp); + + var message = new List(); + message.AddRange(tsp); + message.AddRange(body); + + return IsValidHttpInteraction(key, sig, message.ToArray()); + } + + private bool IsValidHttpInteraction(byte[] publicKey, byte[] signature, byte[] message) + { + return Ed25519.Verify(signature, message, publicKey); + } + + /// + /// Creates a from a http message. + /// + /// The public key of your application + /// The signature sent with the interaction. + /// The timestamp sent with the interaction. + /// The body of the http message. + /// + /// A that represents the incoming http interaction. + /// + /// Thrown when the signature doesn't match the public key. + public Task ParseHttpInteractionAsync(string publicKey, string signature, string timestamp, string body) + => ParseHttpInteractionAsync(publicKey, signature, timestamp, Encoding.UTF8.GetBytes(body)); + + /// + /// Creates a from a http message. + /// + /// The public key of your application + /// The signature sent with the interaction. + /// The timestamp sent with the interaction. + /// The body of the http message. + /// + /// A that represents the incoming http interaction. + /// + /// Thrown when the signature doesn't match the public key. + public async Task ParseHttpInteractionAsync(string publicKey, string signature, string timestamp, byte[] body) + { + if (!IsValidHttpInteraction(publicKey, signature, timestamp, body)) + { + throw new BadSignatureException(); + } + + using (var textReader = new StringReader(Encoding.UTF8.GetString(body))) + using (var jsonReader = new JsonTextReader(textReader)) + { + var model = Serializer.Deserialize(jsonReader); + return await RestInteraction.CreateAsync(this, model); + } + } + + #endregion + public async Task GetApplicationInfoAsync(RequestOptions options = null) { - return _applicationInfo ?? (_applicationInfo = await ClientHelper.GetApplicationInfoAsync(this, options).ConfigureAwait(false)); + return _applicationInfo ??= await ClientHelper.GetApplicationInfoAsync(this, options).ConfigureAwait(false); } public Task GetChannelAsync(ulong id, RequestOptions options = null) @@ -101,6 +178,24 @@ namespace Discord.Rest => ClientHelper.GetVoiceRegionAsync(this, id, options); public Task GetWebhookAsync(ulong id, RequestOptions options = null) => ClientHelper.GetWebhookAsync(this, id, options); + + public Task CreateGlobalCommand(ApplicationCommandProperties properties, RequestOptions options = null) + => ClientHelper.CreateGlobalApplicationCommandAsync(this, properties, options); + public Task CreateGuildCommand(ApplicationCommandProperties properties, ulong guildId, RequestOptions options = null) + => ClientHelper.CreateGuildApplicationCommandAsync(this, guildId, properties, options); + public Task> GetGlobalApplicationCommands(RequestOptions options = null) + => ClientHelper.GetGlobalApplicationCommandsAsync(this, options); + public Task> GetGuildApplicationCommands(ulong guildId, RequestOptions options = null) + => ClientHelper.GetGuildApplicationCommandsAsync(this, guildId, options); + public Task> BulkOverwriteGlobalCommands(ApplicationCommandProperties[] commandProperties, RequestOptions options = null) + => ClientHelper.BulkOverwriteGlobalApplicationCommandAsync(this, commandProperties, options); + public Task> BulkOverwriteGuildCommands(ApplicationCommandProperties[] commandProperties, ulong guildId, RequestOptions options = null) + => ClientHelper.BulkOverwriteGuildApplicationCommandAsync(this, guildId, commandProperties, options); + public Task> BatchEditGuildCommandPermissions(ulong guildId, IDictionary permissions, RequestOptions options = null) + => InteractionHelper.BatchEditGuildCommandPermissionsAsync(this, guildId, permissions, options); + public Task DeleteAllGlobalCommandsAsync(RequestOptions options = null) + => InteractionHelper.DeleteAllGlobalCommandsAsync(this, options); + public Task AddRoleAsync(ulong guildId, ulong userId, ulong roleId) => ClientHelper.AddRoleAsync(this, guildId, userId, roleId); public Task RemoveRoleAsync(ulong guildId, ulong userId, ulong roleId) @@ -114,7 +209,9 @@ namespace Discord.Rest => MessageHelper.RemoveAllReactionsAsync(channelId, messageId, this, options); public Task RemoveAllReactionsForEmoteAsync(ulong channelId, ulong messageId, IEmote emote, RequestOptions options = null) => MessageHelper.RemoveAllReactionsForEmoteAsync(channelId, messageId, emote, this, options); - //IDiscordClient +#endregion + + #region IDiscordClient /// async Task IDiscordClient.GetApplicationInfoAsync(RequestOptions options) => await GetApplicationInfoAsync(options).ConfigureAwait(false); @@ -198,5 +295,13 @@ namespace Discord.Rest /// async Task IDiscordClient.GetWebhookAsync(ulong id, RequestOptions options) => await GetWebhookAsync(id, options).ConfigureAwait(false); + + /// + async Task> IDiscordClient.GetGlobalApplicationCommandsAsync(RequestOptions options) + => await GetGlobalApplicationCommands(options).ConfigureAwait(false); + /// + async Task IDiscordClient.GetGlobalApplicationCommandAsync(ulong id, RequestOptions options) + => await ClientHelper.GetGlobalApplicationCommandAsync(this, id, options).ConfigureAwait(false); + #endregion } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs b/src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs index 696917203..b3aaf582c 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using Model = Discord.API.AuditLog; @@ -51,6 +51,7 @@ namespace Discord.Rest [ActionType.MessageBulkDeleted] = MessageBulkDeleteAuditLogData.Create, [ActionType.MessagePinned] = MessagePinAuditLogData.Create, [ActionType.MessageUnpinned] = MessageUnpinAuditLogData.Create, + }; public static IAuditLogData CreateData(BaseDiscordClient discord, Model log, EntryModel entry) diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/StageInfo.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/StageInfo.cs new file mode 100644 index 000000000..3700796e6 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/StageInfo.cs @@ -0,0 +1,30 @@ +namespace Discord.Rest +{ + /// + /// Represents information for a stage. + /// + public class StageInfo + { + /// + /// Gets the topic of the stage channel. + /// + public string Topic { get; } + + /// + /// Gets the privacy level of the stage channel. + /// + public StagePrivacyLevel? PrivacyLevel { get; } + + /// + /// Gets the user who started the stage channel. + /// + public IUser User { get; } + + internal StageInfo(IUser user, StagePrivacyLevel? level, string topic) + { + Topic = topic; + PrivacyLevel = level; + User = user; + } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/StageInstanceCreateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/StageInstanceCreateAuditLogData.cs new file mode 100644 index 000000000..eac99e87b --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/StageInstanceCreateAuditLogData.cs @@ -0,0 +1,50 @@ +using System.Linq; +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + /// + /// Contains a piece of audit log data related to a stage going live. + /// + public class StageInstanceCreateAuditLogData : IAuditLogData + { + /// + /// Gets the topic of the stage channel. + /// + public string Topic { get; } + + /// + /// Gets the privacy level of the stage channel. + /// + public StagePrivacyLevel PrivacyLevel { get; } + + /// + /// Gets the user who started the stage channel. + /// + public IUser User { get; } + + /// + /// Gets the Id of the stage channel. + /// + public ulong StageChannelId { get; } + + internal StageInstanceCreateAuditLogData(string topic, StagePrivacyLevel privacyLevel, IUser user, ulong channelId) + { + Topic = topic; + PrivacyLevel = privacyLevel; + User = user; + StageChannelId = channelId; + } + + internal static StageInstanceCreateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var topic = entry.Changes.FirstOrDefault(x => x.ChangedProperty == "topic").NewValue.ToObject(discord.ApiClient.Serializer); + var privacyLevel = entry.Changes.FirstOrDefault(x => x.ChangedProperty == "privacy_level").NewValue.ToObject(discord.ApiClient.Serializer); + var user = log.Users.FirstOrDefault(x => x.Id == entry.UserId); + var channelId = entry.Options.ChannelId; + + return new StageInstanceCreateAuditLogData(topic, privacyLevel, RestUser.Create(discord, user), channelId ?? 0); + } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/StageInstanceDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/StageInstanceDeleteAuditLogData.cs new file mode 100644 index 000000000..d22c56010 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/StageInstanceDeleteAuditLogData.cs @@ -0,0 +1,50 @@ +using System.Linq; +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + /// + /// Contains a piece of audit log data related to a stage instance deleted. + /// + public class StageInstanceDeleteAuditLogData + { + /// + /// Gets the topic of the stage channel. + /// + public string Topic { get; } + + /// + /// Gets the privacy level of the stage channel. + /// + public StagePrivacyLevel PrivacyLevel { get; } + + /// + /// Gets the user who started the stage channel. + /// + public IUser User { get; } + + /// + /// Gets the Id of the stage channel. + /// + public ulong StageChannelId { get; } + + internal StageInstanceDeleteAuditLogData(string topic, StagePrivacyLevel privacyLevel, IUser user, ulong channelId) + { + Topic = topic; + PrivacyLevel = privacyLevel; + User = user; + StageChannelId = channelId; + } + + internal static StageInstanceDeleteAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var topic = entry.Changes.FirstOrDefault(x => x.ChangedProperty == "topic").OldValue.ToObject(discord.ApiClient.Serializer); + var privacyLevel = entry.Changes.FirstOrDefault(x => x.ChangedProperty == "privacy_level").OldValue.ToObject(discord.ApiClient.Serializer); + var user = log.Users.FirstOrDefault(x => x.Id == entry.UserId); + var channelId = entry.Options.ChannelId; + + return new StageInstanceDeleteAuditLogData(topic, privacyLevel, RestUser.Create(discord, user), channelId ?? 0); + } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/StageInstanceUpdatedAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/StageInstanceUpdatedAuditLogData.cs new file mode 100644 index 000000000..93a0344b2 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/StageInstanceUpdatedAuditLogData.cs @@ -0,0 +1,51 @@ +using System.Linq; +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + /// + /// Contains a piece of audit log data related to a stage instance update. + /// + public class StageInstanceUpdatedAuditLogData + { + /// + /// Gets the Id of the stage channel. + /// + public ulong StageChannelId { get; } + + /// + /// Gets the stage information before the changes. + /// + public StageInfo Before { get; } + + /// + /// Gets the stage information after the changes. + /// + public StageInfo After { get; } + + internal StageInstanceUpdatedAuditLogData(ulong channelId, StageInfo before, StageInfo after) + { + StageChannelId = channelId; + Before = before; + After = after; + } + + internal static StageInstanceUpdatedAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var channelId = entry.Options.ChannelId.Value; + + var topic = entry.Changes.FirstOrDefault(x => x.ChangedProperty == "topic"); + var privacy = entry.Changes.FirstOrDefault(x => x.ChangedProperty == "privacy"); + + var user = RestUser.Create(discord, log.Users.FirstOrDefault(x => x.Id == entry.UserId)); + + var oldTopic = topic?.OldValue.ToObject(); + var newTopic = topic?.NewValue.ToObject(); + var oldPrivacy = privacy?.OldValue.ToObject(); + var newPrivacy = privacy?.NewValue.ToObject(); + + return new StageInstanceUpdatedAuditLogData(channelId, new StageInfo(user, oldPrivacy, oldTopic), new StageInfo(user, newPrivacy, newTopic)); + } + } +} diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index 22395ab3a..2956d6443 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -6,12 +6,13 @@ using System.IO; using System.Linq; using System.Threading.Tasks; using Model = Discord.API.Channel; +using StageInstance = Discord.API.StageInstance; namespace Discord.Rest { internal static class ChannelHelper { - //General + #region General public static async Task DeleteAsync(IChannel channel, BaseDiscordClient client, RequestOptions options) { @@ -76,6 +77,7 @@ namespace Discord.Rest { Bitrate = args.Bitrate, Name = args.Name, + RTCRegion = args.RTCRegion, Position = args.Position, CategoryId = args.CategoryId, UserLimit = args.UserLimit.IsSpecified ? (args.UserLimit.Value ?? 0) : Optional.Create(), @@ -92,7 +94,23 @@ namespace Discord.Rest return await client.ApiClient.ModifyGuildChannelAsync(channel.Id, apiArgs, options).ConfigureAwait(false); } - //Invites + public static async Task ModifyAsync(IStageChannel channel, BaseDiscordClient client, + Action func, RequestOptions options = null) + { + var args = new StageInstanceProperties(); + func(args); + + var apiArgs = new ModifyStageInstanceParams() + { + PrivacyLevel = args.PrivacyLevel, + Topic = args.Topic + }; + + return await client.ApiClient.ModifyStageInstanceAsync(channel.Id, apiArgs, options); + } + #endregion + + #region Invites public static async Task> GetInvitesAsync(IGuildChannel channel, BaseDiscordClient client, RequestOptions options) { @@ -120,7 +138,56 @@ namespace Discord.Rest return RestInviteMetadata.Create(client, null, channel, model); } - //Messages + /// + /// may not be equal to zero. + /// -and- + /// and must be greater than zero. + /// -and- + /// must be lesser than 86400. + /// + public static async Task CreateInviteToStreamAsync(IGuildChannel channel, BaseDiscordClient client, + int? maxAge, int? maxUses, bool isTemporary, bool isUnique, IUser user, + RequestOptions options) + { + var args = new API.Rest.CreateChannelInviteParams + { + IsTemporary = isTemporary, + IsUnique = isUnique, + MaxAge = maxAge ?? 0, + MaxUses = maxUses ?? 0, + TargetType = TargetUserType.Stream, + TargetUserId = user.Id + }; + var model = await client.ApiClient.CreateChannelInviteAsync(channel.Id, args, options).ConfigureAwait(false); + return RestInviteMetadata.Create(client, null, channel, model); + } + + /// + /// may not be equal to zero. + /// -and- + /// and must be greater than zero. + /// -and- + /// must be lesser than 86400. + /// + public static async Task CreateInviteToApplicationAsync(IGuildChannel channel, BaseDiscordClient client, + int? maxAge, int? maxUses, bool isTemporary, bool isUnique, ulong applicationId, + RequestOptions options) + { + var args = new API.Rest.CreateChannelInviteParams + { + IsTemporary = isTemporary, + IsUnique = isUnique, + MaxAge = maxAge ?? 0, + MaxUses = maxUses ?? 0, + TargetType = TargetUserType.EmbeddedApplication, + TargetApplicationId = applicationId + }; + var model = await client.ApiClient.CreateChannelInviteAsync(channel.Id, args, options).ConfigureAwait(false); + return RestInviteMetadata.Create(client, null, channel, model); + } + #endregion + + #region Messages public static async Task GetMessageAsync(IMessageChannel channel, BaseDiscordClient client, ulong id, RequestOptions options) { @@ -200,10 +267,15 @@ namespace Discord.Rest /// Message content is too long, length must be less or equal to . public static async Task SendMessageAsync(IMessageChannel channel, BaseDiscordClient client, - string text, bool isTTS, Embed embed, AllowedMentions allowedMentions, MessageReference messageReference, RequestOptions options) + string text, bool isTTS, Embed embed, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent component, ISticker[] stickers, RequestOptions options, Embed[] embeds) { + embeds ??= Array.Empty(); + if (embed != null) + embeds = new[] { embed }.Concat(embeds).ToArray(); + Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); + Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed."); // check that user flag and user Id list are exclusive, same with role flag and role Id list if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue) @@ -221,7 +293,20 @@ namespace Discord.Rest } } - var args = new CreateMessageParams(text) { IsTTS = isTTS, Embed = embed?.ToModel(), AllowedMentions = allowedMentions?.ToModel(), MessageReference = messageReference?.ToModel() }; + if (stickers != null) + { + Preconditions.AtMost(stickers.Length, 3, nameof(stickers), "A max of 3 stickers are allowed."); + } + + var args = new CreateMessageParams(text) + { + IsTTS = isTTS, + Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional.Unspecified, + AllowedMentions = allowedMentions?.ToModel(), + MessageReference = messageReference?.ToModel(), + Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified, + Stickers = stickers?.Any() ?? false ? stickers.Select(x => x.Id).ToArray() : Optional.Unspecified + }; var model = await client.ApiClient.CreateMessageAsync(channel.Id, args, options).ConfigureAwait(false); return RestUserMessage.Create(client, channel, client.CurrentUser, model); } @@ -251,19 +336,40 @@ namespace Discord.Rest /// An I/O error occurred while opening the file. /// Message content is too long, length must be less or equal to . public static async Task SendFileAsync(IMessageChannel channel, BaseDiscordClient client, - string filePath, string text, bool isTTS, Embed embed, AllowedMentions allowedMentions, MessageReference messageReference, RequestOptions options, bool isSpoiler) + string filePath, string text, bool isTTS, Embed embed, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent component, ISticker[] stickers, RequestOptions options, bool isSpoiler, Embed[] embeds) { string filename = Path.GetFileName(filePath); using (var file = File.OpenRead(filePath)) - return await SendFileAsync(channel, client, file, filename, text, isTTS, embed, allowedMentions, messageReference, options, isSpoiler).ConfigureAwait(false); + return await SendFileAsync(channel, client, file, filename, text, isTTS, embed, allowedMentions, messageReference, component, stickers, options, isSpoiler, embeds).ConfigureAwait(false); } /// Message content is too long, length must be less or equal to . - public static async Task SendFileAsync(IMessageChannel channel, BaseDiscordClient client, - Stream stream, string filename, string text, bool isTTS, Embed embed, AllowedMentions allowedMentions, MessageReference messageReference, RequestOptions options, bool isSpoiler) + public static Task SendFileAsync(IMessageChannel channel, BaseDiscordClient client, + Stream stream, string filename, string text, bool isTTS, Embed embed, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent component, ISticker[] stickers, RequestOptions options, bool isSpoiler, Embed[] embeds) + { + return SendFileAsync(channel, client, new FileAttachment(stream, filename, isSpoiler: isSpoiler), text, isTTS, embed, allowedMentions, messageReference, component, stickers, options, embeds); + } + + /// Message content is too long, length must be less or equal to . + public static Task SendFileAsync(IMessageChannel channel, BaseDiscordClient client, + FileAttachment attachment, string text, bool isTTS, Embed embed, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent component, ISticker[] stickers, RequestOptions options, Embed[] embeds) + => SendFilesAsync(channel, client, new[] { attachment }, text, isTTS, embed, allowedMentions, messageReference, component, stickers, options, embeds); + + public static async Task SendFilesAsync(IMessageChannel channel, BaseDiscordClient client, + IEnumerable attachments, string text, bool isTTS, Embed embed, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent component, ISticker[] stickers, RequestOptions options, Embed[] embeds) { + embeds ??= Array.Empty(); + if (embed != null) + embeds = new[] { embed }.Concat(embeds).ToArray(); + Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); + Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed."); + + foreach(var attachment in attachments) + { + Preconditions.NotNullOrEmpty(attachment.FileName, nameof(attachment.FileName), "File Name must not be empty or null"); + } // check that user flag and user Id list are exclusive, same with role flag and role Id list if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue) @@ -281,7 +387,12 @@ namespace Discord.Rest } } - var args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS, Embed = embed?.ToModel() ?? Optional.Unspecified, AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, MessageReference = messageReference?.ToModel() ?? Optional.Unspecified, IsSpoiler = isSpoiler }; + if (stickers != null) + { + Preconditions.AtMost(stickers.Length, 3, nameof(stickers), "A max of 3 stickers are allowed."); + } + + var args = new UploadFileParams(attachments.ToArray()) { Content = text, IsTTS = isTTS, Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional.Unspecified, AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, MessageReference = messageReference?.ToModel() ?? Optional.Unspecified, MessageComponent = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified, Stickers = stickers?.Any() ?? false ? stickers.Select(x => x.Id).ToArray() : Optional.Unspecified }; var model = await client.ApiClient.UploadFileAsync(channel.Id, args, options).ConfigureAwait(false); return RestUserMessage.Create(client, channel, client.CurrentUser, model); } @@ -323,8 +434,9 @@ namespace Discord.Rest await client.ApiClient.DeleteMessagesAsync(channel.Id, args, options).ConfigureAwait(false); } } + #endregion - //Permission Overwrites + #region Permission Overwrites public static async Task AddPermissionOverwriteAsync(IGuildChannel channel, BaseDiscordClient client, IUser user, OverwritePermissions perms, RequestOptions options) { @@ -347,8 +459,9 @@ namespace Discord.Rest { await client.ApiClient.DeleteChannelPermissionAsync(channel.Id, role.Id, options).ConfigureAwait(false); } + #endregion - //Users + #region Users /// Resolving permissions requires the parent guild to be downloaded. public static async Task GetUserAsync(IGuildChannel channel, IGuild guild, BaseDiscordClient client, ulong id, RequestOptions options) @@ -393,8 +506,9 @@ namespace Discord.Rest count: limit ); } + #endregion - //Typing + #region Typing public static async Task TriggerTypingAsync(IMessageChannel channel, BaseDiscordClient client, RequestOptions options = null) { @@ -403,8 +517,9 @@ namespace Discord.Rest public static IDisposable EnterTypingState(IMessageChannel channel, BaseDiscordClient client, RequestOptions options) => new TypingNotifier(channel, options); + #endregion - //Webhooks + #region Webhooks public static async Task CreateWebhookAsync(ITextChannel channel, BaseDiscordClient client, string name, Stream avatar, RequestOptions options) { var args = new CreateWebhookParams { Name = name }; @@ -427,7 +542,9 @@ namespace Discord.Rest return models.Select(x => RestWebhook.Create(client, channel, x)) .ToImmutableArray(); } - // Categories + #endregion + + #region Categories public static async Task GetCategoryAsync(INestedChannel channel, BaseDiscordClient client, RequestOptions options) { // if no category id specified, return null @@ -441,7 +558,8 @@ namespace Discord.Rest public static async Task SyncPermissionsAsync(INestedChannel channel, BaseDiscordClient client, RequestOptions options) { var category = await GetCategoryAsync(channel, client, options).ConfigureAwait(false); - if (category == null) throw new InvalidOperationException("This channel does not have a parent channel."); + if (category == null) + throw new InvalidOperationException("This channel does not have a parent channel."); var apiArgs = new ModifyGuildChannelParams { @@ -456,5 +574,6 @@ namespace Discord.Rest }; await client.ApiClient.ModifyGuildChannelAsync(channel.Id, apiArgs, options).ConfigureAwait(false); } + #endregion } } diff --git a/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs b/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs index 09404d836..159735798 100644 --- a/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs @@ -25,17 +25,20 @@ namespace Discord.Rest /// If null, all mentioned roles and users will be notified. /// /// The message references to be included. Used to reply to specific messages. + /// The message components to be included with this message. Used for interactions. + /// A collection of stickers to send with the message. + /// A array of s to send with this response. Max 10. /// /// A task that represents an asynchronous send operation for delivering the message. The task result /// contains the sent message. /// - new Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null); + new Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null); /// /// Sends a file to this message channel with an optional caption. /// /// - /// This method follows the same behavior as described in - /// . Please visit + /// This method follows the same behavior as described in + /// . Please visit /// its documentation for more details on this method. /// /// The file path of the file. @@ -49,16 +52,19 @@ namespace Discord.Rest /// If null, all mentioned roles and users will be notified. /// /// The message references to be included. Used to reply to specific messages. + /// The message components to be included with this message. Used for interactions. + /// A collection of stickers to send with the message. + /// A array of s to send with this response. Max 10. /// /// A task that represents an asynchronous send operation for delivering the message. The task result /// contains the sent message. /// - new Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null); + new Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null); /// /// Sends a file to this message channel with an optional caption. /// /// - /// This method follows the same behavior as described in . + /// This method follows the same behavior as described in . /// Please visit its documentation for more details on this method. /// /// The of the file to be sent. @@ -73,11 +79,14 @@ namespace Discord.Rest /// If null, all mentioned roles and users will be notified. /// /// The message references to be included. Used to reply to specific messages. + /// The message components to be included with this message. Used for interactions. + /// A collection of stickers to send with the message. + /// A array of s to send with this response. Max 10. /// /// A task that represents an asynchronous send operation for delivering the message. The task result /// contains the sent message. /// - new Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null); + new Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null); /// /// Gets a message from this message channel. diff --git a/src/Discord.Net.Rest/Entities/Channels/RestCategoryChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestCategoryChannel.cs index 177bde21d..9f944501b 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestCategoryChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestCategoryChannel.cs @@ -12,6 +12,7 @@ namespace Discord.Rest [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class RestCategoryChannel : RestGuildChannel, ICategoryChannel { + #region RestCategoryChannel internal RestCategoryChannel(BaseDiscordClient discord, IGuild guild, ulong id) : base(discord, guild, id) { @@ -24,8 +25,9 @@ namespace Discord.Rest } private string DebuggerDisplay => $"{Name} ({Id}, Category)"; + #endregion - //IChannel + #region IChannel /// /// This method is not supported with category channels. IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) @@ -34,5 +36,6 @@ namespace Discord.Rest /// This method is not supported with category channels. Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => throw new NotSupportedException(); + #endregion } } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs index 6f6a1f0d3..83c6d8bfb 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs @@ -11,6 +11,7 @@ namespace Discord.Rest /// public class RestChannel : RestEntity, IChannel, IUpdateable { + #region RestChannel /// public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); @@ -21,40 +22,55 @@ namespace Discord.Rest /// Unexpected channel type. internal static RestChannel Create(BaseDiscordClient discord, Model model) { - switch (model.Type) + return model.Type switch { - case ChannelType.News: - case ChannelType.Text: - case ChannelType.Voice: - return RestGuildChannel.Create(discord, new RestGuild(discord, model.GuildId.Value), model); - case ChannelType.DM: - case ChannelType.Group: - return CreatePrivate(discord, model) as RestChannel; - case ChannelType.Category: - return RestCategoryChannel.Create(discord, new RestGuild(discord, model.GuildId.Value), model); - default: - return new RestChannel(discord, model.Id); - } + ChannelType.News or + ChannelType.Text or + ChannelType.Voice or + ChannelType.Stage or + ChannelType.NewsThread or + ChannelType.PrivateThread or + ChannelType.PublicThread + => RestGuildChannel.Create(discord, new RestGuild(discord, model.GuildId.Value), model), + ChannelType.DM or ChannelType.Group => CreatePrivate(discord, model) as RestChannel, + ChannelType.Category => RestCategoryChannel.Create(discord, new RestGuild(discord, model.GuildId.Value), model), + _ => new RestChannel(discord, model.Id), + }; + } + internal static RestChannel Create(BaseDiscordClient discord, Model model, IGuild guild) + { + return model.Type switch + { + ChannelType.News or + ChannelType.Text or + ChannelType.Voice or + ChannelType.Stage or + ChannelType.NewsThread or + ChannelType.PrivateThread or + ChannelType.PublicThread + => RestGuildChannel.Create(discord, guild, model), + ChannelType.DM or ChannelType.Group => CreatePrivate(discord, model) as RestChannel, + ChannelType.Category => RestCategoryChannel.Create(discord, guild, model), + _ => new RestChannel(discord, model.Id), + }; } /// Unexpected channel type. internal static IRestPrivateChannel CreatePrivate(BaseDiscordClient discord, Model model) { - switch (model.Type) + return model.Type switch { - case ChannelType.DM: - return RestDMChannel.Create(discord, model); - case ChannelType.Group: - return RestGroupChannel.Create(discord, model); - default: - throw new InvalidOperationException($"Unexpected channel type: {model.Type}"); - } + ChannelType.DM => RestDMChannel.Create(discord, model), + ChannelType.Group => RestGroupChannel.Create(discord, model), + _ => throw new InvalidOperationException($"Unexpected channel type: {model.Type}"), + }; } internal virtual void Update(Model model) { } /// public virtual Task UpdateAsync(RequestOptions options = null) => Task.Delay(0); + #endregion - //IChannel + #region IChannel /// string IChannel.Name => null; @@ -64,5 +80,6 @@ namespace Discord.Rest /// IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => AsyncEnumerable.Empty>(); //Overridden + #endregion } } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs index 6ccfd204c..1b91c6e62 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs @@ -15,6 +15,7 @@ namespace Discord.Rest [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class RestDMChannel : RestChannel, IDMChannel, IRestPrivateChannel, IRestMessageChannel { + #region RestDMChannel /// /// Gets the current logged-in user. /// @@ -93,8 +94,8 @@ namespace Discord.Rest /// /// Message content is too long, length must be less or equal to . - public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null) - => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, allowedMentions, messageReference, options); + public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null) + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, allowedMentions, messageReference, component, stickers, options, embeds); /// /// @@ -121,12 +122,22 @@ namespace Discord.Rest /// is in an invalid format. /// An I/O error occurred while opening the file. /// Message content is too long, length must be less or equal to . - public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null) - => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, allowedMentions, messageReference, options, isSpoiler); + public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null) + => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, allowedMentions, messageReference, component, stickers, options, isSpoiler, embeds); /// /// Message content is too long, length must be less or equal to . - public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null) - => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, allowedMentions, messageReference, options, isSpoiler); + public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null) + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, allowedMentions, messageReference, component, stickers, options, isSpoiler, embeds); + + /// + /// Message content is too long, length must be less or equal to . + public Task SendFileAsync(FileAttachment attachment, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null) + => ChannelHelper.SendFileAsync(this, Discord, attachment, text, isTTS, embed, allowedMentions, messageReference, component, stickers, options, embeds); + + /// + /// Message content is too long, length must be less or equal to . + public Task SendFilesAsync(IEnumerable attachments, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null) + => ChannelHelper.SendFilesAsync(this, Discord, attachments, text, isTTS, embed, allowedMentions, messageReference, component, stickers, options, embeds); /// public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null) @@ -154,20 +165,24 @@ namespace Discord.Rest /// public override string ToString() => $"@{Recipient}"; private string DebuggerDisplay => $"@{Recipient} ({Id}, DM)"; + #endregion - //IDMChannel + #region IDMChannel /// IUser IDMChannel.Recipient => Recipient; + #endregion - //IRestPrivateChannel + #region IRestPrivateChannel /// IReadOnlyCollection IRestPrivateChannel.Recipients => ImmutableArray.Create(Recipient); + #endregion - //IPrivateChannel + #region IPrivateChannel /// IReadOnlyCollection IPrivateChannel.Recipients => ImmutableArray.Create(Recipient); + #endregion - //IMessageChannel + #region IMessageChannel /// async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options) { @@ -204,16 +219,23 @@ namespace Discord.Rest async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) => await GetPinnedMessagesAsync(options).ConfigureAwait(false); /// - async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageReference messageReference) - => await SendFileAsync(filePath, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference).ConfigureAwait(false); + async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent component, ISticker[] stickers, Embed[] embeds) + => await SendFileAsync(filePath, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference, component, stickers, embeds).ConfigureAwait(false); + /// + async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent component, ISticker[] stickers, Embed[] embeds) + => await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference, component, stickers, embeds).ConfigureAwait(false); + /// + async Task IMessageChannel.SendFileAsync(FileAttachment attachment, string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent component, ISticker[] stickers, Embed[] embeds) + => await SendFileAsync(attachment, text, isTTS, embed, options, allowedMentions, messageReference, component, stickers, embeds).ConfigureAwait(false); /// - async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageReference messageReference) - => await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference).ConfigureAwait(false); + async Task IMessageChannel.SendFilesAsync(IEnumerable attachments, string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent component, ISticker[] stickers, Embed[] embeds) + => await SendFilesAsync(attachments, text, isTTS, embed, options, allowedMentions, messageReference, component, stickers, embeds).ConfigureAwait(false); /// - async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference) - => await SendMessageAsync(text, isTTS, embed, options, allowedMentions, messageReference).ConfigureAwait(false); + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent component, ISticker[] stickers, Embed[] embeds) + => await SendMessageAsync(text, isTTS, embed, options, allowedMentions, messageReference, component, stickers, embeds).ConfigureAwait(false); + #endregion - //IChannel + #region IChannel /// string IChannel.Name => $"@{Recipient}"; @@ -223,5 +245,6 @@ namespace Discord.Rest /// IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => ImmutableArray.Create>(Users).ToAsyncEnumerable(); + #endregion } } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs index 2b0ab8b42..83ff3f558 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs @@ -16,6 +16,7 @@ namespace Discord.Rest [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class RestGroupChannel : RestChannel, IGroupChannel, IRestPrivateChannel, IRestMessageChannel, IRestAudioChannel { + #region RestGroupChannel private string _iconId; private ImmutableDictionary _users; @@ -99,8 +100,8 @@ namespace Discord.Rest /// /// Message content is too long, length must be less or equal to . - public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null) - => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, allowedMentions, messageReference, options); + public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null) + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, allowedMentions, messageReference, component, stickers, options, embeds); /// /// @@ -127,13 +128,20 @@ namespace Discord.Rest /// is in an invalid format. /// An I/O error occurred while opening the file. /// Message content is too long, length must be less or equal to . - public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null) - => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, allowedMentions, messageReference, options, isSpoiler); + public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null) + => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, allowedMentions, messageReference, component, stickers, options, isSpoiler, embeds); /// /// Message content is too long, length must be less or equal to . - public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null) - => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, allowedMentions, messageReference, options, isSpoiler); - + public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null) + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, allowedMentions, messageReference, component, stickers, options, isSpoiler, embeds); + /// + /// Message content is too long, length must be less or equal to . + public Task SendFileAsync(FileAttachment attachment, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null) + => ChannelHelper.SendFileAsync(this, Discord, attachment, text, isTTS, embed, allowedMentions, messageReference, component, stickers, options, embeds); + /// + /// Message content is too long, length must be less or equal to . + public Task SendFilesAsync(IEnumerable attachments, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null) + => ChannelHelper.SendFilesAsync(this, Discord, attachments, text, isTTS, embed, allowedMentions, messageReference, component, stickers, options, embeds); /// public Task TriggerTypingAsync(RequestOptions options = null) => ChannelHelper.TriggerTypingAsync(this, Discord, options); @@ -143,14 +151,17 @@ namespace Discord.Rest public override string ToString() => Name; private string DebuggerDisplay => $"{Name} ({Id}, Group)"; + #endregion - //ISocketPrivateChannel + #region ISocketPrivateChannel IReadOnlyCollection IRestPrivateChannel.Recipients => Recipients; + #endregion - //IPrivateChannel + #region IPrivateChannel IReadOnlyCollection IPrivateChannel.Recipients => Recipients; + #endregion - //IMessageChannel + #region IMessageChannel async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -182,25 +193,31 @@ namespace Discord.Rest async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) => await GetPinnedMessagesAsync(options).ConfigureAwait(false); - async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageReference messageReference) - => await SendFileAsync(filePath, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference).ConfigureAwait(false); - - async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageReference messageReference) - => await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference).ConfigureAwait(false); + async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent component, ISticker[] stickers, Embed[] embeds) + => await SendFileAsync(filePath, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference, component, stickers, embeds).ConfigureAwait(false); - async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference) - => await SendMessageAsync(text, isTTS, embed, options, allowedMentions, messageReference).ConfigureAwait(false); + async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent component, ISticker[] stickers, Embed[] embeds) + => await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference, component, stickers, embeds).ConfigureAwait(false); + async Task IMessageChannel.SendFileAsync(FileAttachment attachment, string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent component, ISticker[] stickers, Embed[] embeds) + => await SendFileAsync(attachment, text, isTTS, embed, options, allowedMentions, messageReference, component, stickers, embeds).ConfigureAwait(false); + async Task IMessageChannel.SendFilesAsync(IEnumerable attachments, string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent component, ISticker[] stickers, Embed[] embeds) + => await SendFilesAsync(attachments, text, isTTS, embed, options, allowedMentions, messageReference, component, stickers, embeds).ConfigureAwait(false); + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent component, ISticker[] stickers, Embed[] embeds) + => await SendMessageAsync(text, isTTS, embed, options, allowedMentions, messageReference, component, stickers, embeds).ConfigureAwait(false); + #endregion - //IAudioChannel + #region IAudioChannel /// /// Connecting to a group channel is not supported. Task IAudioChannel.ConnectAsync(bool selfDeaf, bool selfMute, bool external) { throw new NotSupportedException(); } Task IAudioChannel.DisconnectAsync() { throw new NotSupportedException(); } + #endregion - //IChannel + #region IChannel Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetUser(id)); IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => ImmutableArray.Create>(Users).ToAsyncEnumerable(); + #endregion } } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs index fdfee39ea..bc9d4110a 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs @@ -12,6 +12,7 @@ namespace Discord.Rest /// public class RestGuildChannel : RestChannel, IGuildChannel { + #region RestGuildChannel private ImmutableArray _overwrites; /// @@ -32,30 +33,34 @@ namespace Discord.Rest } internal static RestGuildChannel Create(BaseDiscordClient discord, IGuild guild, Model model) { - switch (model.Type) + return model.Type switch { - case ChannelType.News: - return RestNewsChannel.Create(discord, guild, model); - case ChannelType.Text: - return RestTextChannel.Create(discord, guild, model); - case ChannelType.Voice: - return RestVoiceChannel.Create(discord, guild, model); - case ChannelType.Category: - return RestCategoryChannel.Create(discord, guild, model); - default: - return new RestGuildChannel(discord, guild, model.Id); - } + ChannelType.News => RestNewsChannel.Create(discord, guild, model), + ChannelType.Text => RestTextChannel.Create(discord, guild, model), + ChannelType.Voice => RestVoiceChannel.Create(discord, guild, model), + ChannelType.Stage => RestStageChannel.Create(discord, guild, model), + ChannelType.Category => RestCategoryChannel.Create(discord, guild, model), + ChannelType.PublicThread or ChannelType.PrivateThread or ChannelType.NewsThread => RestThreadChannel.Create(discord, guild, model), + _ => new RestGuildChannel(discord, guild, model.Id), + }; } internal override void Update(Model model) { Name = model.Name.Value; - Position = model.Position.Value; - var overwrites = model.PermissionOverwrites.Value; - var newOverwrites = ImmutableArray.CreateBuilder(overwrites.Length); - for (int i = 0; i < overwrites.Length; i++) - newOverwrites.Add(overwrites[i].ToEntity()); - _overwrites = newOverwrites.ToImmutable(); + if (model.Position.IsSpecified) + { + Position = model.Position.Value; + } + + if (model.PermissionOverwrites.IsSpecified) + { + var overwrites = model.PermissionOverwrites.Value; + var newOverwrites = ImmutableArray.CreateBuilder(overwrites.Length); + for (int i = 0; i < overwrites.Length; i++) + newOverwrites.Add(overwrites[i].ToEntity()); + _overwrites = newOverwrites.ToImmutable(); + } } /// @@ -187,8 +192,9 @@ namespace Discord.Rest /// A string that is the name of this channel. /// public override string ToString() => Name; + #endregion - //IGuildChannel + #region IGuildChannel /// IGuild IGuildChannel.Guild { @@ -225,13 +231,15 @@ namespace Discord.Rest /// Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(null); //Overridden in Text/Voice + #endregion - //IChannel + #region IChannel /// IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => AsyncEnumerable.Empty>(); //Overridden in Text/Voice /// Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(null); //Overridden in Text/Voice + #endregion } } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestStageChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestStageChannel.cs new file mode 100644 index 000000000..c01df96fd --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Channels/RestStageChannel.cs @@ -0,0 +1,150 @@ +using Discord.API; +using Discord.API.Rest; +using System; +using System.Threading.Tasks; +using Model = Discord.API.Channel; + +namespace Discord.Rest +{ + /// + /// Represents a REST-based stage channel in a guild. + /// + public class RestStageChannel : RestVoiceChannel, IStageChannel + { + /// + public string Topic { get; private set; } + + /// + public StagePrivacyLevel? PrivacyLevel { get; private set; } + + /// + public bool? IsDiscoverableDisabled { get; private set; } + + /// + public bool IsLive { get; private set; } + internal RestStageChannel(BaseDiscordClient discord, IGuild guild, ulong id) + : base(discord, guild, id) { } + + internal new static RestStageChannel Create(BaseDiscordClient discord, IGuild guild, Model model) + { + var entity = new RestStageChannel(discord, guild, model.Id); + entity.Update(model); + return entity; + } + + internal void Update(StageInstance model, bool isLive = false) + { + IsLive = isLive; + if(isLive) + { + Topic = model.Topic; + PrivacyLevel = model.PrivacyLevel; + IsDiscoverableDisabled = model.DiscoverableDisabled; + } + else + { + Topic = null; + PrivacyLevel = null; + IsDiscoverableDisabled = null; + } + } + + /// + public async Task ModifyInstanceAsync(Action func, RequestOptions options = null) + { + var model = await ChannelHelper.ModifyAsync(this, Discord, func, options); + + Update(model, true); + } + + /// + public async Task StartStageAsync(string topic, StagePrivacyLevel privacyLevel = StagePrivacyLevel.GuildOnly, RequestOptions options = null) + { + var args = new CreateStageInstanceParams + { + ChannelId = Id, + PrivacyLevel = privacyLevel, + Topic = topic + }; + + var model = await Discord.ApiClient.CreateStageInstanceAsync(args, options); + + Update(model, true); + } + + /// + public async Task StopStageAsync(RequestOptions options = null) + { + await Discord.ApiClient.DeleteStageInstanceAsync(Id, options); + + Update(null); + } + + /// + public override async Task UpdateAsync(RequestOptions options = null) + { + await base.UpdateAsync(options); + + var model = await Discord.ApiClient.GetStageInstanceAsync(Id, options); + + Update(model, model != null); + } + + /// + public Task RequestToSpeakAsync(RequestOptions options = null) + { + var args = new ModifyVoiceStateParams + { + ChannelId = Id, + RequestToSpeakTimestamp = DateTimeOffset.UtcNow + }; + return Discord.ApiClient.ModifyMyVoiceState(Guild.Id, args, options); + } + + /// + public Task BecomeSpeakerAsync(RequestOptions options = null) + { + var args = new ModifyVoiceStateParams + { + ChannelId = Id, + Suppressed = false + }; + return Discord.ApiClient.ModifyMyVoiceState(Guild.Id, args, options); + } + + /// + public Task StopSpeakingAsync(RequestOptions options = null) + { + var args = new ModifyVoiceStateParams + { + ChannelId = Id, + Suppressed = true + }; + return Discord.ApiClient.ModifyMyVoiceState(Guild.Id, args, options); + } + + /// + public Task MoveToSpeakerAsync(IGuildUser user, RequestOptions options = null) + { + var args = new ModifyVoiceStateParams + { + ChannelId = Id, + Suppressed = false + }; + + return Discord.ApiClient.ModifyUserVoiceState(Guild.Id, user.Id, args); + } + + /// + public Task RemoveFromSpeakerAsync(IGuildUser user, RequestOptions options = null) + { + var args = new ModifyVoiceStateParams + { + ChannelId = Id, + Suppressed = true + }; + + return Discord.ApiClient.ModifyUserVoiceState(Guild.Id, user.Id, args); + } + } +} diff --git a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs index c6d0b0509..f14bc2ecf 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs @@ -14,10 +14,11 @@ namespace Discord.Rest [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class RestTextChannel : RestGuildChannel, IRestMessageChannel, ITextChannel { + #region RestTextChannel /// public string Topic { get; private set; } /// - public virtual int SlowModeInterval { get; private set; } + public virtual int SlowModeInterval { get; private set; } /// public ulong? CategoryId { get; private set; } @@ -41,14 +42,14 @@ namespace Discord.Rest { base.Update(model); CategoryId = model.CategoryId; - Topic = model.Topic.Value; + Topic = model.Topic.GetValueOrDefault(); if (model.SlowMode.IsSpecified) SlowModeInterval = model.SlowMode.Value; IsNsfw = model.Nsfw.GetValueOrDefault(); } /// - public async Task ModifyAsync(Action func, RequestOptions options = null) + public virtual async Task ModifyAsync(Action func, RequestOptions options = null) { var model = await ChannelHelper.ModifyAsync(this, Discord, func, options).ConfigureAwait(false); Update(model); @@ -78,7 +79,7 @@ namespace Discord.Rest /// /// /// A paged collection containing a collection of guild users that can access this channel. Flattening the - /// paginated response into a collection of users with + /// paginated response into a collection of users with /// is required if you wish to access the users. /// public IAsyncEnumerable> GetUsersAsync(RequestOptions options = null) @@ -102,8 +103,8 @@ namespace Discord.Rest /// /// Message content is too long, length must be less or equal to . - public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null) - => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, allowedMentions, messageReference, options); + public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null) + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, allowedMentions, messageReference, component, stickers, options, embeds); /// /// @@ -130,13 +131,23 @@ namespace Discord.Rest /// is in an invalid format. /// An I/O error occurred while opening the file. /// Message content is too long, length must be less or equal to . - public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null) - => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, allowedMentions, messageReference, options, isSpoiler); + public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null) + => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, allowedMentions, messageReference, component, stickers, options, isSpoiler, embeds); /// /// Message content is too long, length must be less or equal to . - public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null) - => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, allowedMentions, messageReference, options, isSpoiler); + public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null) + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, allowedMentions, messageReference, component, stickers, options, isSpoiler, embeds); + + /// + /// Message content is too long, length must be less or equal to . + public Task SendFileAsync(FileAttachment attachment, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null) + => ChannelHelper.SendFileAsync(this, Discord, attachment, text, isTTS, embed, allowedMentions, messageReference, component, stickers, options, embeds); + + /// + /// Message content is too long, length must be less or equal to . + public Task SendFilesAsync(IEnumerable attachments, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null) + => ChannelHelper.SendFilesAsync(this, Discord, attachments, text, isTTS, embed, allowedMentions, messageReference, component, stickers, options, embeds); /// public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null) @@ -173,7 +184,7 @@ namespace Discord.Rest /// A task that represents the asynchronous creation operation. The task result contains the newly created /// webhook. /// - public Task CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null) + public virtual Task CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null) => ChannelHelper.CreateWebhookAsync(this, Discord, name, avatar, options); /// /// Gets a webhook available in this text channel. @@ -184,7 +195,7 @@ namespace Discord.Rest /// A task that represents the asynchronous get operation. The task result contains a webhook associated /// with the identifier; null if the webhook is not found. /// - public Task GetWebhookAsync(ulong id, RequestOptions options = null) + public virtual Task GetWebhookAsync(ulong id, RequestOptions options = null) => ChannelHelper.GetWebhookAsync(this, Discord, id, options); /// /// Gets the webhooks available in this text channel. @@ -194,7 +205,7 @@ namespace Discord.Rest /// A task that represents the asynchronous get operation. The task result contains a read-only collection /// of webhooks that is available in this channel. /// - public Task> GetWebhooksAsync(RequestOptions options = null) + public virtual Task> GetWebhooksAsync(RequestOptions options = null) => ChannelHelper.GetWebhooksAsync(this, Discord, options); /// @@ -205,23 +216,66 @@ namespace Discord.Rest /// A task that represents the asynchronous get operation. The task result contains the category channel /// representing the parent of this channel; null if none is set. /// - public Task GetCategoryAsync(RequestOptions options = null) + public virtual Task GetCategoryAsync(RequestOptions options = null) => ChannelHelper.GetCategoryAsync(this, Discord, options); /// public Task SyncPermissionsAsync(RequestOptions options = null) => ChannelHelper.SyncPermissionsAsync(this, Discord, options); + #endregion - //Invites + #region Invites /// - public async Task CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) + public virtual async Task CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) => await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, options).ConfigureAwait(false); + public virtual Task CreateInviteToApplicationAsync(ulong applicationId, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null) + => throw new NotImplementedException(); + public virtual Task CreateInviteToStreamAsync(IUser user, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null) + => throw new NotImplementedException(); /// - public async Task> GetInvitesAsync(RequestOptions options = null) + public virtual async Task> GetInvitesAsync(RequestOptions options = null) => await ChannelHelper.GetInvitesAsync(this, Discord, options).ConfigureAwait(false); private string DebuggerDisplay => $"{Name} ({Id}, Text)"; - //ITextChannel + /// + /// Creates a thread within this . + /// + /// + /// When is the thread type will be based off of the + /// channel its created in. When called on a , it creates a . + /// When called on a , it creates a . The id of the created + /// thread will be the same as the id of the message, and as such a message can only have a + /// single thread created from it. + /// + /// The name of the thread. + /// + /// The type of the thread. + /// + /// Note: This parameter is not used if the parameter is not specified. + /// + /// + /// + /// The duration on which this thread archives after. + /// + /// Note: Options and + /// are only available for guilds that are boosted. You can check in the to see if the + /// guild has the THREE_DAY_THREAD_ARCHIVE and SEVEN_DAY_THREAD_ARCHIVE. + /// + /// + /// The message which to start the thread from. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous create operation. The task result contains a + /// + public async Task CreateThreadAsync(string name, ThreadType type = ThreadType.PublicThread, + ThreadArchiveDuration autoArchiveDuration = ThreadArchiveDuration.OneDay, IMessage message = null, bool? invitable = null, int? slowmode = null, RequestOptions options = null) + { + var model = await ThreadHelper.CreateThreadAsync(Discord, this, name, type, autoArchiveDuration, message, invitable, slowmode, options); + return RestThreadChannel.Create(Discord, Guild, model); + } + #endregion + + #region ITextChannel /// async Task ITextChannel.CreateWebhookAsync(string name, Stream avatar, RequestOptions options) => await CreateWebhookAsync(name, avatar, options).ConfigureAwait(false); @@ -232,7 +286,11 @@ namespace Discord.Rest async Task> ITextChannel.GetWebhooksAsync(RequestOptions options) => await GetWebhooksAsync(options).ConfigureAwait(false); - //IMessageChannel + async Task ITextChannel.CreateThreadAsync(string name, ThreadType type, ThreadArchiveDuration autoArchiveDuration, IMessage message, bool? invitable, int? slowmode, RequestOptions options) + => await CreateThreadAsync(name, type, autoArchiveDuration, message, invitable, slowmode, options); + #endregion + + #region IMessageChannel /// async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options) { @@ -271,17 +329,27 @@ namespace Discord.Rest => await GetPinnedMessagesAsync(options).ConfigureAwait(false); /// - async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageReference messageReference) - => await SendFileAsync(filePath, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference).ConfigureAwait(false); + async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent component, ISticker[] stickers, Embed[] embeds) + => await SendFileAsync(filePath, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference, component, stickers, embeds).ConfigureAwait(false); /// - async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageReference messageReference) - => await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference).ConfigureAwait(false); + async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent component, ISticker[] stickers, Embed[] embeds) + => await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference, component, stickers, embeds).ConfigureAwait(false); + + /// + async Task IMessageChannel.SendFileAsync(FileAttachment attachment, string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent component, ISticker[] stickers, Embed[] embeds) + => await SendFileAsync(attachment, text, isTTS, embed, options, allowedMentions, messageReference, component, stickers, embeds).ConfigureAwait(false); + + /// + async Task IMessageChannel.SendFilesAsync(IEnumerable attachments, string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent component, ISticker[] stickers, Embed[] embeds) + => await SendFilesAsync(attachments, text, isTTS, embed, options, allowedMentions, messageReference, component, stickers, embeds).ConfigureAwait(false); + /// - async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference) - => await SendMessageAsync(text, isTTS, embed, options, allowedMentions, messageReference).ConfigureAwait(false); + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent component, ISticker[] stickers, Embed[] embeds) + => await SendMessageAsync(text, isTTS, embed, options, allowedMentions, messageReference, component, stickers, embeds).ConfigureAwait(false); + #endregion - //IGuildChannel + #region IGuildChannel /// async Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) { @@ -298,8 +366,9 @@ namespace Discord.Rest else return AsyncEnumerable.Empty>(); } + #endregion - //IChannel + #region IChannel /// async Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) { @@ -316,8 +385,9 @@ namespace Discord.Rest else return AsyncEnumerable.Empty>(); } + #endregion - // INestedChannel + #region INestedChannel /// async Task INestedChannel.GetCategoryAsync(CacheMode mode, RequestOptions options) { @@ -325,5 +395,6 @@ namespace Discord.Rest return (await Guild.GetChannelAsync(CategoryId.Value, mode, options).ConfigureAwait(false)) as ICategoryChannel; return null; } + #endregion } } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestThreadChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestThreadChannel.cs new file mode 100644 index 000000000..63071b9a5 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Channels/RestThreadChannel.cs @@ -0,0 +1,223 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; +using System.Threading.Tasks; +using Model = Discord.API.Channel; + +namespace Discord.Rest +{ + /// + /// Represents a thread channel received over REST. + /// + public class RestThreadChannel : RestTextChannel, IThreadChannel + { + public ThreadType Type { get; private set; } + /// + public bool HasJoined { get; private set; } + + /// + public bool IsArchived { get; private set; } + + /// + public ThreadArchiveDuration AutoArchiveDuration { get; private set; } + + /// + public DateTimeOffset ArchiveTimestamp { get; private set; } + + /// + public bool IsLocked { get; private set; } + + /// + public int MemberCount { get; private set; } + + /// + public int MessageCount { get; private set; } + + /// + /// Gets the parent text channel id. + /// + public ulong ParentChannelId { get; private set; } + + internal RestThreadChannel(BaseDiscordClient discord, IGuild guild, ulong id) + : base(discord, guild, id) { } + + internal new static RestThreadChannel Create(BaseDiscordClient discord, IGuild guild, Model model) + { + var entity = new RestThreadChannel(discord, guild, model.Id); + entity.Update(model); + return entity; + } + + internal override void Update(Model model) + { + base.Update(model); + + HasJoined = model.ThreadMember.IsSpecified; + + if (model.ThreadMetadata.IsSpecified) + { + IsArchived = model.ThreadMetadata.Value.Archived; + AutoArchiveDuration = model.ThreadMetadata.Value.AutoArchiveDuration; + ArchiveTimestamp = model.ThreadMetadata.Value.ArchiveTimestamp; + IsLocked = model.ThreadMetadata.Value.Locked.GetValueOrDefault(false); + } + + MemberCount = model.MemberCount.GetValueOrDefault(0); + MessageCount = model.MessageCount.GetValueOrDefault(0); + Type = (ThreadType)model.Type; + ParentChannelId = model.CategoryId.Value; + } + + /// + /// Gets a user within this thread. + /// + /// The id of the user to fetch. + /// The options to be used when sending the request. + /// + /// A task representing the asynchronous get operation. The task returns a + /// if found, otherwise . + /// + public new Task GetUserAsync(ulong userId, RequestOptions options = null) + => ThreadHelper.GetUserAsync(userId, this, Discord, options); + + /// + /// Gets a collection of users within this thread. + /// + /// The options to be used when sending the request. + /// + /// A task representing the asynchronous get operation. The task returns a + /// of 's. + /// + public new async Task> GetUsersAsync(RequestOptions options = null) + => (await ThreadHelper.GetUsersAsync(this, Discord, options).ConfigureAwait(false)).ToImmutableArray(); + + /// + public override async Task ModifyAsync(Action func, RequestOptions options = null) + { + var model = await ThreadHelper.ModifyAsync(this, Discord, func, options); + Update(model); + } + + /// + /// + /// This method is not supported in threads. + /// + public override Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, RequestOptions options = null) + => throw new NotSupportedException("This method is not supported in threads."); + + /// + /// + /// This method is not supported in threads. + /// + public override Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions, RequestOptions options = null) + => throw new NotSupportedException("This method is not supported in threads."); + + /// + /// + /// This method is not supported in threads. + /// + public override Task CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) + => throw new NotSupportedException("This method is not supported in threads."); + + /// + /// + /// This method is not supported in threads. + /// + public override Task CreateInviteToApplicationAsync(ulong applicationId, int? maxAge, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) + => throw new NotSupportedException("This method is not supported in threads."); + + /// + /// + /// This method is not supported in threads. + /// + public override Task CreateInviteToStreamAsync(IUser user, int? maxAge, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) + => throw new NotSupportedException("This method is not supported in threads."); + + /// + /// + /// This method is not supported in threads. + /// + public override Task CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null) + => throw new NotSupportedException("This method is not supported in threads."); + + /// + /// + /// This method is not supported in threads. + /// + public override Task GetCategoryAsync(RequestOptions options = null) + => throw new NotSupportedException("This method is not supported in threads."); + + /// + /// + /// This method is not supported in threads. + /// + public override Task> GetInvitesAsync(RequestOptions options = null) + => throw new NotSupportedException("This method is not supported in threads."); + + /// + /// + /// This method is not supported in threads. + /// + public override OverwritePermissions? GetPermissionOverwrite(IRole role) + => throw new NotSupportedException("This method is not supported in threads."); + + /// + /// + /// This method is not supported in threads. + /// + public override OverwritePermissions? GetPermissionOverwrite(IUser user) + => throw new NotSupportedException("This method is not supported in threads."); + + /// + /// + /// This method is not supported in threads. + /// + public override Task GetWebhookAsync(ulong id, RequestOptions options = null) + => throw new NotSupportedException("This method is not supported in threads."); + + /// + /// + /// This method is not supported in threads. + /// + public override Task> GetWebhooksAsync(RequestOptions options = null) + => throw new NotSupportedException("This method is not supported in threads."); + + /// + /// + /// This method is not supported in threads. + /// + public override Task RemovePermissionOverwriteAsync(IRole role, RequestOptions options = null) + => throw new NotSupportedException("This method is not supported in threads."); + + /// + /// + /// This method is not supported in threads. + /// + public override Task RemovePermissionOverwriteAsync(IUser user, RequestOptions options = null) + => throw new NotSupportedException("This method is not supported in threads."); + + /// + /// + /// This method is not supported in threads. + /// + public override IReadOnlyCollection PermissionOverwrites + => throw new NotSupportedException("This method is not supported in threads."); + + /// + public Task JoinAsync(RequestOptions options = null) + => Discord.ApiClient.JoinThreadAsync(Id, options); + + /// + public Task LeaveAsync(RequestOptions options = null) + => Discord.ApiClient.LeaveThreadAsync(Id, options); + + /// + public Task AddUserAsync(IGuildUser user, RequestOptions options = null) + => Discord.ApiClient.AddThreadMemberAsync(Id, user.Id, options); + + /// + public Task RemoveUserAsync(IGuildUser user, RequestOptions options = null) + => Discord.ApiClient.RemoveThreadMemberAsync(Id, user.Id, options); + } +} diff --git a/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs index e958f2c03..48fc11dcb 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs @@ -14,6 +14,7 @@ namespace Discord.Rest [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class RestVoiceChannel : RestGuildChannel, IVoiceChannel, IRestAudioChannel { + #region RestVoiceChannel /// public int Bitrate { get; private set; } /// @@ -39,8 +40,12 @@ namespace Discord.Rest { base.Update(model); CategoryId = model.CategoryId; - Bitrate = model.Bitrate.Value; - UserLimit = model.UserLimit.Value != 0 ? model.UserLimit.Value : (int?)null; + + if(model.Bitrate.IsSpecified) + Bitrate = model.Bitrate.Value; + + if(model.UserLimit.IsSpecified) + UserLimit = model.UserLimit.Value != 0 ? model.UserLimit.Value : (int?)null; } /// @@ -63,32 +68,42 @@ namespace Discord.Rest /// public Task SyncPermissionsAsync(RequestOptions options = null) => ChannelHelper.SyncPermissionsAsync(this, Discord, options); + #endregion - //Invites + #region Invites /// public async Task CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) => await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, options).ConfigureAwait(false); /// + public async Task CreateInviteToApplicationAsync(ulong applicationId, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null) + => await ChannelHelper.CreateInviteToApplicationAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, applicationId, options).ConfigureAwait(false); + /// + public async Task CreateInviteToStreamAsync(IUser user, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null) + => await ChannelHelper.CreateInviteToStreamAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, user, options).ConfigureAwait(false); + /// public async Task> GetInvitesAsync(RequestOptions options = null) => await ChannelHelper.GetInvitesAsync(this, Discord, options).ConfigureAwait(false); private string DebuggerDisplay => $"{Name} ({Id}, Voice)"; + #endregion - //IAudioChannel + #region IAudioChannel /// /// Connecting to a REST-based channel is not supported. Task IAudioChannel.ConnectAsync(bool selfDeaf, bool selfMute, bool external) { throw new NotSupportedException(); } Task IAudioChannel.DisconnectAsync() { throw new NotSupportedException(); } + #endregion - //IGuildChannel + #region IGuildChannel /// Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(null); /// IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => AsyncEnumerable.Empty>(); + #endregion - // INestedChannel + #region INestedChannel /// async Task INestedChannel.GetCategoryAsync(CacheMode mode, RequestOptions options) { @@ -96,5 +111,6 @@ namespace Discord.Rest return (await Guild.GetChannelAsync(CategoryId.Value, mode, options).ConfigureAwait(false)) as ICategoryChannel; return null; } + #endregion } } diff --git a/src/Discord.Net.Rest/Entities/Channels/ThreadHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ThreadHelper.cs new file mode 100644 index 000000000..917410f98 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Channels/ThreadHelper.cs @@ -0,0 +1,74 @@ +using Discord.API.Rest; +using System; +using System.Linq; +using System.Threading.Tasks; +using Model = Discord.API.Channel; + +namespace Discord.Rest +{ + internal static class ThreadHelper + { + public static async Task CreateThreadAsync(BaseDiscordClient client, ITextChannel channel, string name, ThreadType type = ThreadType.PublicThread, + ThreadArchiveDuration autoArchiveDuration = ThreadArchiveDuration.OneDay, IMessage message = null, bool? invitable = null, int? slowmode = null, RequestOptions options = null) + { + var features = channel.Guild.Features; + if (autoArchiveDuration == ThreadArchiveDuration.OneWeek && !features.HasFeature(GuildFeature.SevenDayThreadArchive)) + throw new ArgumentException($"The guild {channel.Guild.Name} does not have the SEVEN_DAY_THREAD_ARCHIVE feature!", nameof(autoArchiveDuration)); + + if (autoArchiveDuration == ThreadArchiveDuration.ThreeDays && !features.HasFeature(GuildFeature.ThreeDayThreadArchive)) + throw new ArgumentException($"The guild {channel.Guild.Name} does not have the THREE_DAY_THREAD_ARCHIVE feature!", nameof(autoArchiveDuration)); + + if (type == ThreadType.PrivateThread && !features.HasFeature(GuildFeature.PrivateThreads)) + throw new ArgumentException($"The guild {channel.Guild.Name} does not have the PRIVATE_THREADS feature!", nameof(type)); + + var args = new StartThreadParams + { + Name = name, + Duration = autoArchiveDuration, + Type = type, + Invitable = invitable.HasValue ? invitable.Value : Optional.Unspecified, + Ratelimit = slowmode.HasValue ? slowmode.Value : Optional.Unspecified, + }; + + Model model; + + if (message != null) + model = await client.ApiClient.StartThreadAsync(channel.Id, message.Id, args, options).ConfigureAwait(false); + else + model = await client.ApiClient.StartThreadAsync(channel.Id, args, options).ConfigureAwait(false); + + return model; + } + + public static async Task ModifyAsync(IThreadChannel channel, BaseDiscordClient client, + Action func, + RequestOptions options) + { + var args = new TextChannelProperties(); + func(args); + var apiArgs = new ModifyThreadParams + { + Name = args.Name, + Archived = args.Archived, + AutoArchiveDuration = args.AutoArchiveDuration, + Locked = args.Locked, + Slowmode = args.SlowModeInterval + }; + return await client.ApiClient.ModifyThreadAsync(channel.Id, apiArgs, options).ConfigureAwait(false); + } + + public static async Task GetUsersAsync(IThreadChannel channel, BaseDiscordClient client, RequestOptions options = null) + { + var users = await client.ApiClient.ListThreadMembersAsync(channel.Id, options); + + return users.Select(x => RestThreadUser.Create(client, channel.Guild, x, channel)).ToArray(); + } + + public static async Task GetUserAsync(ulong userId, IThreadChannel channel, BaseDiscordClient client, RequestOptions options = null) + { + var model = await client.ApiClient.GetThreadMemberAsync(channel.Id, userId, options).ConfigureAwait(false); + + return RestThreadUser.Create(client, channel.Guild, model, channel); + } + } +} diff --git a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs index 58a4ea2c8..2cdbbb7b5 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs @@ -8,12 +8,13 @@ using WidgetModel = Discord.API.GuildWidget; using Model = Discord.API.Guild; using RoleModel = Discord.API.Role; using ImageModel = Discord.API.Image; +using System.IO; namespace Discord.Rest { internal static class GuildHelper { - //General + #region General /// is null. public static async Task ModifyAsync(IGuild guild, BaseDiscordClient client, Action func, RequestOptions options) @@ -35,7 +36,8 @@ namespace Discord.Rest Banner = args.Banner.IsSpecified ? args.Banner.Value?.ToModel() : Optional.Create(), VerificationLevel = args.VerificationLevel, ExplicitContentFilter = args.ExplicitContentFilter, - SystemChannelFlags = args.SystemChannelFlags + SystemChannelFlags = args.SystemChannelFlags, + IsBoostProgressBarEnabled = args.IsBoostProgressBarEnabled }; if (args.AfkChannel.IsSpecified) @@ -122,8 +124,9 @@ namespace Discord.Rest { await client.ApiClient.DeleteGuildAsync(guild.Id, options).ConfigureAwait(false); } + #endregion - //Bans + #region Bans public static async Task> GetBansAsync(IGuild guild, BaseDiscordClient client, RequestOptions options) { @@ -147,8 +150,9 @@ namespace Discord.Rest { await client.ApiClient.RemoveGuildBanAsync(guild.Id, userId, options).ConfigureAwait(false); } + #endregion - //Channels + #region Channels public static async Task GetChannelAsync(IGuild guild, BaseDiscordClient client, ulong id, RequestOptions options) { @@ -220,6 +224,34 @@ namespace Discord.Rest var model = await client.ApiClient.CreateGuildChannelAsync(guild.Id, args, options).ConfigureAwait(false); return RestVoiceChannel.Create(client, guild, model); } + public static async Task CreateStageChannelAsync(IGuild guild, BaseDiscordClient client, + string name, RequestOptions options, Action func = null) + { + if (name == null) + throw new ArgumentNullException(paramName: nameof(name)); + + var props = new VoiceChannelProperties(); + func?.Invoke(props); + + var args = new CreateGuildChannelParams(name, ChannelType.Stage) + { + CategoryId = props.CategoryId, + Bitrate = props.Bitrate, + UserLimit = props.UserLimit, + Position = props.Position, + Overwrites = props.PermissionOverwrites.IsSpecified + ? props.PermissionOverwrites.Value.Select(overwrite => new API.Overwrite + { + TargetId = overwrite.TargetId, + TargetType = overwrite.TargetType, + Allow = overwrite.Permissions.AllowValue.ToString(), + Deny = overwrite.Permissions.DenyValue.ToString() + }).ToArray() + : Optional.Create(), + }; + var model = await client.ApiClient.CreateGuildChannelAsync(guild.Id, args, options).ConfigureAwait(false); + return RestStageChannel.Create(client, guild, model); + } /// is null. public static async Task CreateCategoryChannelAsync(IGuild guild, BaseDiscordClient client, string name, RequestOptions options, Action func = null) @@ -246,16 +278,18 @@ namespace Discord.Rest var model = await client.ApiClient.CreateGuildChannelAsync(guild.Id, args, options).ConfigureAwait(false); return RestCategoryChannel.Create(client, guild, model); } + #endregion - //Voice Regions + #region Voice Regions public static async Task> GetVoiceRegionsAsync(IGuild guild, BaseDiscordClient client, RequestOptions options) { var models = await client.ApiClient.GetGuildVoiceRegionsAsync(guild.Id, options).ConfigureAwait(false); return models.Select(x => RestVoiceRegion.Create(client, x)).ToImmutableArray(); } + #endregion - //Integrations + #region Integrations public static async Task> GetIntegrationsAsync(IGuild guild, BaseDiscordClient client, RequestOptions options) { @@ -269,8 +303,24 @@ namespace Discord.Rest var model = await client.ApiClient.CreateGuildIntegrationAsync(guild.Id, args, options).ConfigureAwait(false); return RestGuildIntegration.Create(client, guild, model); } + #endregion + + #region Interactions + public static async Task> GetSlashCommandsAsync(IGuild guild, BaseDiscordClient client, + RequestOptions options) + { + var models = await client.ApiClient.GetGuildApplicationCommandsAsync(guild.Id, options); + return models.Select(x => RestGuildCommand.Create(client, x, guild.Id)).ToImmutableArray(); + } + public static async Task GetSlashCommandAsync(IGuild guild, ulong id, BaseDiscordClient client, + RequestOptions options) + { + var model = await client.ApiClient.GetGuildApplicationCommandAsync(guild.Id, id, options); + return RestGuildCommand.Create(client, model, guild.Id); + } + #endregion - //Invites + #region Invites public static async Task> GetInvitesAsync(IGuild guild, BaseDiscordClient client, RequestOptions options) { @@ -286,8 +336,9 @@ namespace Discord.Rest inviteModel.Uses = vanityModel.Uses; return RestInviteMetadata.Create(client, guild, null, inviteModel); } + #endregion - //Roles + #region Roles /// is null. public static async Task CreateRoleAsync(IGuild guild, BaseDiscordClient client, string name, GuildPermissions? permissions, Color? color, bool isHoisted, bool isMentionable, RequestOptions options) @@ -307,8 +358,9 @@ namespace Discord.Rest return RestRole.Create(client, guild, model); } + #endregion - //Users + #region Users public static async Task AddGuildUserAsync(IGuild guild, BaseDiscordClient client, ulong userId, string accessToken, Action func, RequestOptions options) { @@ -427,8 +479,9 @@ namespace Discord.Rest var models = await client.ApiClient.SearchGuildMembersAsync(guild.Id, apiArgs, options).ConfigureAwait(false); return models.Select(x => RestGuildUser.Create(client, guild, x)).ToImmutableArray(); } + #endregion - // Audit logs + #region Audit logs public static IAsyncEnumerable> GetAuditLogsAsync(IGuild guild, BaseDiscordClient client, ulong? from, int? limit, RequestOptions options, ulong? userId = null, ActionType? actionType = null) { @@ -460,8 +513,9 @@ namespace Discord.Rest count: limit ); } + #endregion - //Webhooks + #region Webhooks public static async Task GetWebhookAsync(IGuild guild, BaseDiscordClient client, ulong id, RequestOptions options) { var model = await client.ApiClient.GetWebhookAsync(id, options: options).ConfigureAwait(false); @@ -474,8 +528,9 @@ namespace Discord.Rest var models = await client.ApiClient.GetGuildWebhooksAsync(guild.Id, options).ConfigureAwait(false); return models.Select(x => RestWebhook.Create(client, guild, x)).ToImmutableArray(); } + #endregion - //Emotes + #region Emotes public static async Task> GetEmotesAsync(IGuild guild, BaseDiscordClient client, RequestOptions options) { var models = await client.ApiClient.GetGuildEmotesAsync(guild.Id, options).ConfigureAwait(false); @@ -521,5 +576,308 @@ namespace Discord.Rest } public static Task DeleteEmoteAsync(IGuild guild, BaseDiscordClient client, ulong id, RequestOptions options) => client.ApiClient.DeleteGuildEmoteAsync(guild.Id, id, options); + + public static async Task CreateStickerAsync(BaseDiscordClient client, IGuild guild, string name, string description, IEnumerable tags, + Image image, RequestOptions options = null) + { + Preconditions.NotNull(name, nameof(name)); + Preconditions.NotNull(description, nameof(description)); + + Preconditions.AtLeast(name.Length, 2, nameof(name)); + Preconditions.AtLeast(description.Length, 2, nameof(description)); + + Preconditions.AtMost(name.Length, 30, nameof(name)); + Preconditions.AtMost(description.Length, 100, nameof(name)); + + var apiArgs = new CreateStickerParams() + { + Name = name, + Description = description, + File = image.Stream, + Tags = string.Join(", ", tags) + }; + + return await client.ApiClient.CreateGuildStickerAsync(apiArgs, guild.Id, options).ConfigureAwait(false); + } + + public static async Task CreateStickerAsync(BaseDiscordClient client, IGuild guild, string name, string description, IEnumerable tags, + Stream file, string filename, RequestOptions options = null) + { + Preconditions.NotNull(name, nameof(name)); + Preconditions.NotNull(description, nameof(description)); + Preconditions.NotNull(file, nameof(file)); + Preconditions.NotNull(filename, nameof(filename)); + + Preconditions.AtLeast(name.Length, 2, nameof(name)); + Preconditions.AtLeast(description.Length, 2, nameof(description)); + + Preconditions.AtMost(name.Length, 30, nameof(name)); + Preconditions.AtMost(description.Length, 100, nameof(name)); + + var apiArgs = new CreateStickerParams() + { + Name = name, + Description = description, + File = file, + Tags = string.Join(", ", tags), + FileName = filename + }; + + return await client.ApiClient.CreateGuildStickerAsync(apiArgs, guild.Id, options).ConfigureAwait(false); + } + + public static async Task ModifyStickerAsync(BaseDiscordClient client, ulong guildId, ISticker sticker, Action func, + RequestOptions options = null) + { + if (func == null) + throw new ArgumentNullException(paramName: nameof(func)); + + var props = new StickerProperties(); + func(props); + + var apiArgs = new ModifyStickerParams() + { + Description = props.Description, + Name = props.Name, + Tags = props.Tags.IsSpecified ? + string.Join(", ", props.Tags.Value) : + Optional.Unspecified + }; + + return await client.ApiClient.ModifyStickerAsync(apiArgs, guildId, sticker.Id, options).ConfigureAwait(false); + } + + public static async Task DeleteStickerAsync(BaseDiscordClient client, ulong guildId, ISticker sticker, RequestOptions options = null) + => await client.ApiClient.DeleteStickerAsync(guildId, sticker.Id, options).ConfigureAwait(false); + #endregion + + #region Events + + public static async Task> GetEventUsersAsync(BaseDiscordClient client, IGuildScheduledEvent guildEvent, int limit = 100, RequestOptions options = null) + { + var models = await client.ApiClient.GetGuildScheduledEventUsersAsync(guildEvent.Id, guildEvent.Guild.Id, limit, options).ConfigureAwait(false); + + return models.Select(x => RestUser.Create(client, guildEvent.Guild, x)).ToImmutableArray(); + } + + public static IAsyncEnumerable> GetEventUsersAsync(BaseDiscordClient client, IGuildScheduledEvent guildEvent, + ulong? fromUserId, int? limit, RequestOptions options) + { + return new PagedAsyncEnumerable( + DiscordConfig.MaxGuildEventUsersPerBatch, + async (info, ct) => + { + var args = new GetEventUsersParams + { + Limit = info.PageSize, + RelativeDirection = Direction.After, + }; + if (info.Position != null) + args.RelativeUserId = info.Position.Value; + var models = await client.ApiClient.GetGuildScheduledEventUsersAsync(guildEvent.Id, guildEvent.Guild.Id, args, options).ConfigureAwait(false); + return models + .Select(x => RestUser.Create(client, guildEvent.Guild, x)) + .ToImmutableArray(); + }, + nextPage: (info, lastPage) => + { + if (lastPage.Count != DiscordConfig.MaxGuildEventUsersPerBatch) + return false; + info.Position = lastPage.Max(x => x.Id); + return true; + }, + start: fromUserId, + count: limit + ); + } + + public static IAsyncEnumerable> GetEventUsersAsync(BaseDiscordClient client, IGuildScheduledEvent guildEvent, + ulong? fromUserId, Direction dir, int limit, RequestOptions options = null) + { + if (dir == Direction.Around && limit > DiscordConfig.MaxMessagesPerBatch) + { + int around = limit / 2; + if (fromUserId.HasValue) + return GetEventUsersAsync(client, guildEvent, fromUserId.Value + 1, Direction.Before, around + 1, options) //Need to include the message itself + .Concat(GetEventUsersAsync(client, guildEvent, fromUserId, Direction.After, around, options)); + else //Shouldn't happen since there's no public overload for ulong? and Direction + return GetEventUsersAsync(client, guildEvent, null, Direction.Before, around + 1, options); + } + + return new PagedAsyncEnumerable( + DiscordConfig.MaxGuildEventUsersPerBatch, + async (info, ct) => + { + var args = new GetEventUsersParams + { + RelativeDirection = dir, + Limit = info.PageSize + }; + if (info.Position != null) + args.RelativeUserId = info.Position.Value; + + var models = await client.ApiClient.GetGuildScheduledEventUsersAsync(guildEvent.Id, guildEvent.Guild.Id, args, options).ConfigureAwait(false); + var builder = ImmutableArray.CreateBuilder(); + foreach (var model in models) + { + builder.Add(RestUser.Create(client, guildEvent.Guild, model)); + } + return builder.ToImmutable(); + }, + nextPage: (info, lastPage) => + { + if (lastPage.Count != DiscordConfig.MaxGuildEventUsersPerBatch) + return false; + if (dir == Direction.Before) + info.Position = lastPage.Min(x => x.Id); + else + info.Position = lastPage.Max(x => x.Id); + return true; + }, + start: fromUserId, + count: limit + ); + } + + public static async Task ModifyGuildEventAsync(BaseDiscordClient client, Action func, + IGuildScheduledEvent guildEvent, RequestOptions options = null) + { + var args = new GuildScheduledEventsProperties(); + + func(args); + + if (args.Status.IsSpecified) + { + switch (args.Status.Value) + { + case GuildScheduledEventStatus.Active when guildEvent.Status != GuildScheduledEventStatus.Scheduled: + case GuildScheduledEventStatus.Completed when guildEvent.Status != GuildScheduledEventStatus.Active: + case GuildScheduledEventStatus.Cancelled when guildEvent.Status != GuildScheduledEventStatus.Scheduled: + throw new ArgumentException($"Cannot set event to {args.Status.Value} when events status is {guildEvent.Status}"); + } + } + + if (args.Type.IsSpecified) + { + // taken from https://discord.com/developers/docs/resources/guild-scheduled-event#modify-guild-scheduled-event + switch (args.Type.Value) + { + case GuildScheduledEventType.External: + if (!args.Location.IsSpecified) + throw new ArgumentException("Location must be specified for external events."); + if (!args.EndTime.IsSpecified) + throw new ArgumentException("End time must be specified for external events."); + if (!args.ChannelId.IsSpecified) + throw new ArgumentException("Channel id must be set to null!"); + if (args.ChannelId.Value != null) + throw new ArgumentException("Channel id must be set to null!"); + break; + } + } + + var apiArgs = new ModifyGuildScheduledEventParams() + { + ChannelId = args.ChannelId, + Description = args.Description, + EndTime = args.EndTime, + Name = args.Name, + PrivacyLevel = args.PrivacyLevel, + StartTime = args.StartTime, + Status = args.Status, + Type = args.Type + }; + + if(args.Location.IsSpecified) + { + apiArgs.EntityMetadata = new API.GuildScheduledEventEntityMetadata() + { + Location = args.Location, + }; + } + + return await client.ApiClient.ModifyGuildScheduledEventAsync(apiArgs, guildEvent.Id, guildEvent.Guild.Id, options).ConfigureAwait(false); + } + + public static async Task GetGuildEventAsync(BaseDiscordClient client, ulong id, IGuild guild, RequestOptions options = null) + { + var model = await client.ApiClient.GetGuildScheduledEventAsync(id, guild.Id, options).ConfigureAwait(false); + + if (model == null) + return null; + + return RestGuildEvent.Create(client, guild, model); + } + + public static async Task> GetGuildEventsAsync(BaseDiscordClient client, IGuild guild, RequestOptions options = null) + { + var models = await client.ApiClient.ListGuildScheduledEventsAsync(guild.Id, options).ConfigureAwait(false); + + return models.Select(x => RestGuildEvent.Create(client, guild, x)).ToImmutableArray(); + } + + public static async Task CreateGuildEventAsync(BaseDiscordClient client, IGuild guild, + string name, + GuildScheduledEventPrivacyLevel privacyLevel, + DateTimeOffset startTime, + GuildScheduledEventType type, + string description = null, + DateTimeOffset? endTime = null, + ulong? channelId = null, + string location = null, + RequestOptions options = null) + { + if(location != null) + { + Preconditions.AtMost(location.Length, 100, nameof(location)); + } + + switch (type) + { + case GuildScheduledEventType.Stage or GuildScheduledEventType.Voice when channelId == null: + throw new ArgumentException($"{nameof(channelId)} must not be null when type is {type}", nameof(channelId)); + case GuildScheduledEventType.External when channelId != null: + throw new ArgumentException($"{nameof(channelId)} must be null when using external event type", nameof(channelId)); + case GuildScheduledEventType.External when location == null: + throw new ArgumentException($"{nameof(location)} must not be null when using external event type", nameof(location)); + case GuildScheduledEventType.External when endTime == null: + throw new ArgumentException($"{nameof(endTime)} must not be null when using external event type", nameof(endTime)); + } + + if (startTime <= DateTimeOffset.Now) + throw new ArgumentOutOfRangeException(nameof(startTime), "The start time for an event cannot be in the past"); + + if (endTime != null && endTime <= startTime) + throw new ArgumentOutOfRangeException(nameof(endTime), $"{nameof(endTime)} cannot be before the start time"); + + var apiArgs = new CreateGuildScheduledEventParams() + { + ChannelId = channelId ?? Optional.Unspecified, + Description = description ?? Optional.Unspecified, + EndTime = endTime ?? Optional.Unspecified, + Name = name, + PrivacyLevel = privacyLevel, + StartTime = startTime, + Type = type + }; + + if(location != null) + { + apiArgs.EntityMetadata = new API.GuildScheduledEventEntityMetadata() + { + Location = location + }; + } + + var model = await client.ApiClient.CreateGuildScheduledEventAsync(apiArgs, guild.Id, options).ConfigureAwait(false); + + return RestGuildEvent.Create(client, guild, client.CurrentUser, model); + } + + public static async Task DeleteEventAsync(BaseDiscordClient client, IGuildScheduledEvent guildEvent, RequestOptions options = null) + { + await client.ApiClient.DeleteGuildScheduledEventAsync(guildEvent.Id, guildEvent.Guild.Id, options).ConfigureAwait(false); + } + + #endregion } } diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestBan.cs b/src/Discord.Net.Rest/Entities/Guilds/RestBan.cs index ec8f60ae5..d77d3b626 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestBan.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestBan.cs @@ -9,6 +9,7 @@ namespace Discord.Rest [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class RestBan : IBan { + #region RestBan /// /// Gets the banned user. /// @@ -37,9 +38,11 @@ namespace Discord.Rest /// public override string ToString() => User.ToString(); private string DebuggerDisplay => $"{User}: {Reason}"; +#endregion - //IBan + #region IBan /// IUser IBan.User => User; + #endregion } } diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs index ea703a26a..9b0b66633 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Threading.Tasks; using WidgetModel = Discord.API.GuildWidget; using Model = Discord.API.Guild; +using System.IO; namespace Discord.Rest { @@ -17,9 +18,10 @@ namespace Discord.Rest [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class RestGuild : RestEntity, IGuild, IUpdateable { + #region RestGuild private ImmutableDictionary _roles; private ImmutableArray _emotes; - private ImmutableArray _features; + private ImmutableArray _stickers; /// public string Name { get; private set; } @@ -83,9 +85,14 @@ namespace Discord.Rest public int? ApproximateMemberCount { get; private set; } /// public int? ApproximatePresenceCount { get; private set; } - + /// + public NsfwLevel NsfwLevel { get; private set; } + /// + public bool IsBoostProgressBarEnabled { get; private set; } /// public CultureInfo PreferredCulture { get; private set; } + /// + public GuildFeatures Features { get; private set; } /// public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); @@ -97,7 +104,7 @@ namespace Discord.Rest /// public string DiscoverySplashUrl => CDN.GetGuildDiscoverySplashUrl(Id, DiscoverySplashId); /// - public string BannerUrl => CDN.GetGuildBannerUrl(Id, BannerId); + public string BannerUrl => CDN.GetGuildBannerUrl(Id, BannerId, ImageFormat.Auto); /// /// Gets the built-in role containing all users in this guild. @@ -110,8 +117,7 @@ namespace Discord.Rest public IReadOnlyCollection Roles => _roles.ToReadOnlyCollection(); /// public IReadOnlyCollection Emotes => _emotes; - /// - public IReadOnlyCollection Features => _features; + public IReadOnlyCollection Stickers => _stickers; internal RestGuild(BaseDiscordClient client, ulong id) : base(client, id) @@ -151,6 +157,7 @@ namespace Discord.Rest SystemChannelFlags = model.SystemChannelFlags; Description = model.Description; PremiumSubscriptionCount = model.PremiumSubscriptionCount.GetValueOrDefault(); + NsfwLevel = model.NsfwLevel; if (model.MaxPresences.IsSpecified) MaxPresences = model.MaxPresences.Value ?? 25000; if (model.MaxMembers.IsSpecified) @@ -163,6 +170,8 @@ namespace Discord.Rest ApproximateMemberCount = model.ApproximateMemberCount.Value; if (model.ApproximatePresenceCount.IsSpecified) ApproximatePresenceCount = model.ApproximatePresenceCount.Value; + if (model.IsBoostProgressBarEnabled.IsSpecified) + IsBoostProgressBarEnabled = model.IsBoostProgressBarEnabled.Value; if (model.Emojis != null) { @@ -174,10 +183,7 @@ namespace Discord.Rest else _emotes = ImmutableArray.Create(); - if (model.Features != null) - _features = model.Features.ToImmutableArray(); - else - _features = ImmutableArray.Create(); + Features = model.Features; var roles = ImmutableDictionary.CreateBuilder(); if (model.Roles != null) @@ -187,6 +193,23 @@ namespace Discord.Rest } _roles = roles.ToImmutable(); + if (model.Stickers != null) + { + var stickers = ImmutableArray.CreateBuilder(); + for (int i = 0; i < model.Stickers.Length; i++) + { + var sticker = model.Stickers[i]; + + var entity = CustomSticker.Create(Discord, sticker, this, sticker.User.IsSpecified ? sticker.User.Value.Id : null); + + stickers.Add(entity); + } + + _stickers = stickers.ToImmutable(); + } + else + _stickers = ImmutableArray.Create(); + Available = true; } internal void Update(WidgetModel model) @@ -194,8 +217,9 @@ namespace Discord.Rest WidgetChannelId = model.ChannelId; IsWidgetEnabled = model.Enabled; } + #endregion - //General + #region General /// public async Task UpdateAsync(RequestOptions options = null) => Update(await Discord.ApiClient.GetGuildAsync(Id, false, options).ConfigureAwait(false)); @@ -254,9 +278,44 @@ namespace Discord.Rest /// public Task LeaveAsync(RequestOptions options = null) => GuildHelper.LeaveAsync(this, Discord, options); + #endregion + + #region Interactions + /// + /// Deletes all slash commands in the current guild. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous delete operation. + /// + public Task DeleteSlashCommandsAsync(RequestOptions options = null) + => InteractionHelper.DeleteAllGuildCommandsAsync(Discord, Id, options); + + /// + /// Gets a collection of slash commands created by the current user in this guild. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection of + /// slash commands created by the current user. + /// + public Task> GetSlashCommandsAsync(RequestOptions options = null) + => GuildHelper.GetSlashCommandsAsync(this, Discord, options); + + /// + /// Gets a slash command in the current guild. + /// + /// The unique identifier of the slash command. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a + /// slash command created by the current user. + /// + public Task GetSlashCommandAsync(ulong id, RequestOptions options = null) + => GuildHelper.GetSlashCommandAsync(this, id, Discord, options); + #endregion - //Bans - //Bans + #region Bans /// /// Gets a collection of all users banned in this guild. /// @@ -304,8 +363,9 @@ namespace Discord.Rest /// public Task RemoveBanAsync(ulong userId, RequestOptions options = null) => GuildHelper.RemoveBanAsync(this, Discord, userId, options); + #endregion - //Channels + #region Channels /// /// Gets a collection of all channels in this guild. /// @@ -359,6 +419,35 @@ namespace Discord.Rest } /// + /// Gets a thread channel in this guild. + /// + /// The snowflake identifier for the thread channel. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains the thread channel associated + /// with the specified ; if none is found. + /// + public async Task GetThreadChannelAsync(ulong id, RequestOptions options = null) + { + var channel = await GuildHelper.GetChannelAsync(this, Discord, id, options).ConfigureAwait(false); + return channel as RestThreadChannel; + } + + /// + /// Gets a collection of all thread in this guild. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection of + /// threads found within this guild. + /// + public async Task> GetThreadChannelsAsync(RequestOptions options = null) + { + var channels = await GuildHelper.GetChannelsAsync(this, Discord, options).ConfigureAwait(false); + return channels.OfType().ToImmutableArray(); + } + + /// /// Gets a voice channel in this guild. /// /// The snowflake identifier for the voice channel. @@ -386,6 +475,34 @@ namespace Discord.Rest var channels = await GuildHelper.GetChannelsAsync(this, Discord, options).ConfigureAwait(false); return channels.OfType().ToImmutableArray(); } + /// + /// Gets a stage channel in this guild + /// + /// The snowflake identifier for the stage channel. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains the stage channel associated + /// with the specified ; if none is found. + /// + public async Task GetStageChannelAsync(ulong id, RequestOptions options = null) + { + var channel = await GuildHelper.GetChannelAsync(this, Discord, id, options).ConfigureAwait(false); + return channel as RestStageChannel; + } + + /// + /// Gets a collection of all stage channels in this guild. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection of + /// stage channels found within this guild. + /// + public async Task> GetStageChannelsAsync(RequestOptions options = null) + { + var channels = await GuildHelper.GetChannelsAsync(this, Discord, options).ConfigureAwait(false); + return channels.OfType().ToImmutableArray(); + } /// /// Gets a collection of all category channels in this guild. @@ -460,7 +577,7 @@ namespace Discord.Rest /// The options to be used when sending the request. /// /// A task that represents the asynchronous get operation. The task result contains the text channel - /// where guild notices such as welcome messages and boost events are poste; if none is found. + /// where guild notices such as welcome messages and boost events are post; if none is found. /// public async Task GetSystemChannelAsync(RequestOptions options = null) { @@ -493,11 +610,11 @@ namespace Discord.Rest } /// - /// Gets the text channel channel where admins and moderators of Community guilds receive notices from Discord. + /// Gets the text channel where admins and moderators of Community guilds receive notices from Discord. /// /// The options to be used when sending the request. /// - /// A task that represents the asynchronous get operation. The task result contains the text channel channel where + /// A task that represents the asynchronous get operation. The task result contains the text channel where /// admins and moderators of Community guilds receive notices from Discord; if none is set. /// public async Task GetPublicUpdatesChannelAsync(RequestOptions options = null) @@ -549,6 +666,18 @@ namespace Discord.Rest public Task CreateVoiceChannelAsync(string name, Action func = null, RequestOptions options = null) => GuildHelper.CreateVoiceChannelAsync(this, Discord, name, options, func); /// + /// Creates a new stage channel in this guild. + /// + /// The new name for the stage channel. + /// The delegate containing the properties to be applied to the channel upon its creation. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous creation operation. The task result contains the newly created + /// stage channel. + /// + public Task CreateStageChannelAsync(string name, Action func = null, RequestOptions options = null) + => GuildHelper.CreateStageChannelAsync(this, Discord, name, options, func); + /// /// Creates a category channel with the provided name. /// /// The name of the new channel. @@ -571,14 +700,16 @@ namespace Discord.Rest /// public Task> GetVoiceRegionsAsync(RequestOptions options = null) => GuildHelper.GetVoiceRegionsAsync(this, Discord, options); + #endregion - //Integrations + #region Integrations public Task> GetIntegrationsAsync(RequestOptions options = null) => GuildHelper.GetIntegrationsAsync(this, Discord, options); public Task CreateIntegrationAsync(ulong id, string type, RequestOptions options = null) => GuildHelper.CreateIntegrationAsync(this, Discord, id, type, options); + #endregion - //Invites + #region Invites /// /// Gets a collection of all invites in this guild. /// @@ -598,8 +729,9 @@ namespace Discord.Rest /// public Task GetVanityInviteAsync(RequestOptions options = null) => GuildHelper.GetVanityInviteAsync(this, Discord, options); + #endregion - //Roles + #region Roles /// /// Gets a role in this guild. /// @@ -639,8 +771,9 @@ namespace Discord.Rest _roles = _roles.Add(role.Id, role); return role; } + #endregion - //Users + #region Users /// /// Gets a collection of all users in this guild. /// @@ -734,8 +867,9 @@ namespace Discord.Rest /// public Task> SearchUsersAsync(string query, int limit = DiscordConfig.MaxUsersPerBatch, RequestOptions options = null) => GuildHelper.SearchUsersAsync(this, Discord, query, limit, options); + #endregion - //Audit logs + #region Audit logs /// /// Gets the specified number of audit log entries for this guild. /// @@ -750,8 +884,9 @@ namespace Discord.Rest /// public IAsyncEnumerable> GetAuditLogsAsync(int limit, RequestOptions options = null, ulong? beforeId = null, ulong? userId = null, ActionType? actionType = null) => GuildHelper.GetAuditLogsAsync(this, Discord, beforeId, limit, options, userId: userId, actionType: actionType); + #endregion - //Webhooks + #region Webhooks /// /// Gets a webhook found within this guild. /// @@ -774,6 +909,59 @@ namespace Discord.Rest /// public Task> GetWebhooksAsync(RequestOptions options = null) => GuildHelper.GetWebhooksAsync(this, Discord, options); + #endregion + + #region Interactions + /// + /// Gets this guilds slash commands + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection + /// of application commands found within the guild. + /// + public async Task> GetApplicationCommandsAsync (RequestOptions options = null) + => await ClientHelper.GetGuildApplicationCommandsAsync(Discord, Id, options).ConfigureAwait(false); + /// + /// Gets an application command within this guild with the specified id. + /// + /// The id of the application command to get. + /// The options to be used when sending the request. + /// + /// A ValueTask that represents the asynchronous get operation. The task result contains a + /// if found, otherwise . + /// + public async Task GetApplicationCommandAsync(ulong id, RequestOptions options = null) + => await ClientHelper.GetGuildApplicationCommandAsync(Discord, id, Id, options); + /// + /// Creates an application command within this guild. + /// + /// The properties to use when creating the command. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous creation operation. The task result contains the command that was created. + /// + public async Task CreateApplicationCommandAsync(ApplicationCommandProperties properties, RequestOptions options = null) + { + var model = await InteractionHelper.CreateGuildCommandAsync(Discord, Id, properties, options); + + return RestGuildCommand.Create(Discord, model, Id); + } + /// + /// Overwrites the application commands within this guild. + /// + /// A collection of properties to use when creating the commands. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous creation operation. The task result contains a collection of commands that was created. + /// + public async Task> BulkOverwriteApplicationCommandsAsync(ApplicationCommandProperties[] properties, + RequestOptions options = null) + { + var models = await InteractionHelper.BulkOverwriteGuildCommandsAsync(Discord, Id, properties, options); + + return models.Select(x => RestGuildCommand.Create(Discord, x, Id)).ToImmutableArray(); + } /// /// Returns the name of the guild. @@ -783,8 +971,9 @@ namespace Discord.Rest /// public override string ToString() => Name; private string DebuggerDisplay => $"{Name} ({Id})"; + #endregion - //Emotes + #region Emotes /// public Task> GetEmotesAsync(RequestOptions options = null) => GuildHelper.GetEmotesAsync(this, Discord, options); @@ -798,11 +987,188 @@ namespace Discord.Rest /// is . public Task ModifyEmoteAsync(GuildEmote emote, Action func, RequestOptions options = null) => GuildHelper.ModifyEmoteAsync(this, Discord, emote.Id, func, options); + /// + /// Moves the user to the voice channel. + /// + /// The user to move. + /// the channel where the user gets moved to. + /// A task that represents the asynchronous operation for moving a user. + public Task MoveAsync(IGuildUser user, IVoiceChannel targetChannel) + => user.ModifyAsync(x => x.Channel = new Optional(targetChannel)); /// public Task DeleteEmoteAsync(GuildEmote emote, RequestOptions options = null) => GuildHelper.DeleteEmoteAsync(this, Discord, emote.Id, options); + #endregion + + #region Stickers + /// + /// Creates a new sticker in this guild. + /// + /// The name of the sticker. + /// The description of the sticker. + /// The tags of the sticker. + /// The image of the new emote. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous creation operation. The task result contains the created sticker. + /// + public async Task CreateStickerAsync(string name, string description, IEnumerable tags, Image image, RequestOptions options = null) + { + var model = await GuildHelper.CreateStickerAsync(Discord, this, name, description, tags, image, options).ConfigureAwait(false); + + return CustomSticker.Create(Discord, model, this, model.User.IsSpecified ? model.User.Value.Id : null); + } + /// + /// Creates a new sticker in this guild + /// + /// The name of the sticker. + /// The description of the sticker. + /// The tags of the sticker. + /// The path of the file to upload. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous creation operation. The task result contains the created sticker. + /// + public Task CreateStickerAsync(string name, string description, IEnumerable tags, string path, + RequestOptions options = null) + { + var fs = File.OpenRead(path); + return CreateStickerAsync(name, description, tags, fs, Path.GetFileName(fs.Name), options); + } + /// + /// Creates a new sticker in this guild + /// + /// The name of the sticker. + /// The description of the sticker. + /// The tags of the sticker. + /// The stream containing the file data. + /// The name of the file with the extension, ex: image.png. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous creation operation. The task result contains the created sticker. + /// + public async Task CreateStickerAsync(string name, string description, IEnumerable tags, Stream stream, + string filename, RequestOptions options = null) + { + var model = await GuildHelper.CreateStickerAsync(Discord, this, name, description, tags, stream, filename, options).ConfigureAwait(false); + + return CustomSticker.Create(Discord, model, this, model.User.IsSpecified ? model.User.Value.Id : null); + } + /// + /// Gets a specific sticker within this guild. + /// + /// The id of the sticker to get. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains the sticker found with the + /// specified ; if none is found. + /// + public async Task GetStickerAsync(ulong id, RequestOptions options = null) + { + var model = await Discord.ApiClient.GetGuildStickerAsync(Id, id, options).ConfigureAwait(false); + + if (model == null) + return null; + + return CustomSticker.Create(Discord, model, this, model.User.IsSpecified ? model.User.Value.Id : null); + } + /// + /// Gets a collection of all stickers within this guild. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection + /// of stickers found within the guild. + /// + public async Task> GetStickersAsync(RequestOptions options = null) + { + var models = await Discord.ApiClient.ListGuildStickersAsync(Id, options).ConfigureAwait(false); + + if (models.Length == 0) + return null; - //IGuild + List stickers = new List(); + + foreach(var model in models) + { + var entity = CustomSticker.Create(Discord, model, this, model.User.IsSpecified ? model.User.Value.Id : null); + stickers.Add(entity); + } + + return stickers.ToImmutableArray(); + } + /// + /// Deletes a sticker within this guild. + /// + /// The sticker to delete. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous removal operation. + /// + public Task DeleteStickerAsync(CustomSticker sticker, RequestOptions options = null) + => sticker.DeleteAsync(options); + #endregion + + #region Guild Events + + /// + /// Gets an event within this guild. + /// + /// The snowflake identifier for the event. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. + /// + public Task GetEventAsync(ulong id, RequestOptions options = null) + => GuildHelper.GetGuildEventAsync(Discord, id, this, options); + + /// + /// Gets all active events within this guild. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. + /// + public Task> GetEventsAsync(RequestOptions options = null) + => GuildHelper.GetGuildEventsAsync(Discord, this, options); + + /// + /// Creates an event within this guild. + /// + /// The name of the event. + /// The privacy level of the event. + /// The start time of the event. + /// The type of the event. + /// The description of the event. + /// The end time of the event. + /// + /// The channel id of the event. + /// + /// The event must have a type of or + /// in order to use this property. + /// + /// + /// A collection of speakers for the event. + /// The location of the event; links are supported + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous create operation. + /// + public Task CreateEventAsync( + string name, + DateTimeOffset startTime, + GuildScheduledEventType type, + GuildScheduledEventPrivacyLevel privacyLevel = GuildScheduledEventPrivacyLevel.Private, + string description = null, + DateTimeOffset? endTime = null, + ulong? channelId = null, + string location = null, + RequestOptions options = null) + => GuildHelper.CreateGuildEventAsync(Discord, this, name, privacyLevel, startTime, type, description, endTime, channelId, location, options); + + #endregion + + #region IGuild /// bool IGuild.Available => Available; /// @@ -812,6 +1178,20 @@ namespace Discord.Rest /// IReadOnlyCollection IGuild.Roles => Roles; + IReadOnlyCollection IGuild.Stickers => Stickers; + + /// + async Task IGuild.CreateEventAsync(string name, DateTimeOffset startTime, GuildScheduledEventType type, GuildScheduledEventPrivacyLevel privacyLevel, string description, DateTimeOffset? endTime, ulong? channelId, string location, RequestOptions options) + => await CreateEventAsync(name, startTime, type, privacyLevel, description, endTime, channelId, location, options).ConfigureAwait(false); + + /// + async Task IGuild.GetEventAsync(ulong id, RequestOptions options) + => await GetEventAsync(id, options).ConfigureAwait(false); + + /// + async Task> IGuild.GetEventsAsync(RequestOptions options) + => await GetEventsAsync(options).ConfigureAwait(false); + /// async Task> IGuild.GetBansAsync(RequestOptions options) => await GetBansAsync(options).ConfigureAwait(false); @@ -855,6 +1235,22 @@ namespace Discord.Rest return null; } /// + async Task IGuild.GetThreadChannelAsync(ulong id, CacheMode mode, RequestOptions options) + { + if (mode == CacheMode.AllowDownload) + return await GetThreadChannelAsync(id, options).ConfigureAwait(false); + else + return null; + } + /// + async Task> IGuild.GetThreadChannelsAsync(CacheMode mode, RequestOptions options) + { + if (mode == CacheMode.AllowDownload) + return await GetThreadChannelsAsync(options).ConfigureAwait(false); + else + return null; + } + /// async Task> IGuild.GetVoiceChannelsAsync(CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -871,6 +1267,22 @@ namespace Discord.Rest return null; } /// + async Task IGuild.GetStageChannelAsync(ulong id, CacheMode mode, RequestOptions options ) + { + if (mode == CacheMode.AllowDownload) + return await GetStageChannelAsync(id, options).ConfigureAwait(false); + else + return null; + } + /// + async Task> IGuild.GetStageChannelsAsync(CacheMode mode, RequestOptions options) + { + if (mode == CacheMode.AllowDownload) + return await GetStageChannelsAsync(options).ConfigureAwait(false); + else + return null; + } + /// async Task IGuild.GetVoiceChannelAsync(ulong id, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -933,6 +1345,9 @@ namespace Discord.Rest async Task IGuild.CreateVoiceChannelAsync(string name, Action func, RequestOptions options) => await CreateVoiceChannelAsync(name, func, options).ConfigureAwait(false); /// + async Task IGuild.CreateStageChannelAsync(string name, Action func, RequestOptions options) + => await CreateStageChannelAsync(name, func, options).ConfigureAwait(false); + /// async Task IGuild.CreateCategoryAsync(string name, Action func, RequestOptions options) => await CreateCategoryChannelAsync(name, func, options).ConfigureAwait(false); @@ -968,6 +1383,13 @@ namespace Discord.Rest async Task IGuild.AddGuildUserAsync(ulong userId, string accessToken, Action func, RequestOptions options) => await AddGuildUserAsync(userId, accessToken, func, options); + /// + /// Disconnects the user from its current voice channel + /// + /// The user to disconnect. + /// A task that represents the asynchronous operation for disconnecting a user. + async Task IGuild.DisconnectAsync(IGuildUser user) => await user.ModifyAsync(x => x.Channel = new Optional()); + /// async Task IGuild.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) { @@ -1028,5 +1450,54 @@ namespace Discord.Rest /// async Task> IGuild.GetWebhooksAsync(RequestOptions options) => await GetWebhooksAsync(options).ConfigureAwait(false); + /// + async Task> IGuild.GetApplicationCommandsAsync (RequestOptions options) + => await GetApplicationCommandsAsync(options).ConfigureAwait(false); + /// + async Task IGuild.CreateStickerAsync(string name, string description, IEnumerable tags, Image image, RequestOptions options) + => await CreateStickerAsync(name, description, tags, image, options); + /// + async Task IGuild.CreateStickerAsync(string name, string description, IEnumerable tags, Stream stream, string filename, RequestOptions options) + => await CreateStickerAsync(name, description, tags, stream, filename, options); + /// + async Task IGuild.CreateStickerAsync(string name, string description, IEnumerable tags, string path, RequestOptions options) + => await CreateStickerAsync(name, description, tags, path, options); + /// + async Task IGuild.GetStickerAsync(ulong id, CacheMode mode, RequestOptions options) + { + if (mode != CacheMode.AllowDownload) + return null; + + return await GetStickerAsync(id, options); + } + /// + async Task> IGuild.GetStickersAsync(CacheMode mode, RequestOptions options) + { + if (mode != CacheMode.AllowDownload) + return null; + + return await GetStickersAsync(options); + } + /// + Task IGuild.DeleteStickerAsync(ICustomSticker sticker, RequestOptions options) + => sticker.DeleteAsync(); + /// + async Task IGuild.CreateApplicationCommandAsync(ApplicationCommandProperties properties, RequestOptions options) + => await CreateApplicationCommandAsync(properties, options); + /// + async Task> IGuild.BulkOverwriteApplicationCommandsAsync(ApplicationCommandProperties[] properties, + RequestOptions options) + => await BulkOverwriteApplicationCommandsAsync(properties, options); + /// + async Task IGuild.GetApplicationCommandAsync(ulong id, CacheMode mode, RequestOptions options) + { + if (mode == CacheMode.AllowDownload) + { + return await GetApplicationCommandAsync(id, options); + } + else + return null; + } + #endregion } } diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuildEvent.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuildEvent.cs new file mode 100644 index 000000000..d3ec11fc6 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuildEvent.cs @@ -0,0 +1,188 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Model = Discord.API.GuildScheduledEvent; + +namespace Discord.Rest +{ + public class RestGuildEvent : RestEntity, IGuildScheduledEvent + { + /// + public IGuild Guild { get; private set; } + + /// + public ulong? ChannelId { get; private set; } + + /// + public IUser Creator { get; private set; } + + /// + public ulong CreatorId { get; private set; } + + /// + public string Name { get; private set; } + + /// + public string Description { get; private set; } + + /// + public DateTimeOffset StartTime { get; private set; } + + /// + public DateTimeOffset? EndTime { get; private set; } + + /// + public GuildScheduledEventPrivacyLevel PrivacyLevel { get; private set; } + + /// + public GuildScheduledEventStatus Status { get; private set; } + + /// + public GuildScheduledEventType Type { get; private set; } + + /// + public ulong? EntityId { get; private set; } + + /// + public string Location { get; private set; } + + /// + public int? UserCount { get; private set; } + + internal RestGuildEvent(BaseDiscordClient client, IGuild guild, ulong id) + : base(client, id) + { + Guild = guild; + } + + internal static RestGuildEvent Create(BaseDiscordClient client, IGuild guild, Model model) + { + var entity = new RestGuildEvent(client, guild, model.Id); + entity.Update(model); + return entity; + } + + internal static RestGuildEvent Create(BaseDiscordClient client, IGuild guild, IUser creator, Model model) + { + var entity = new RestGuildEvent(client, guild, model.Id); + entity.Update(model, creator); + return entity; + } + + internal void Update(Model model, IUser creator) + { + Update(model); + Creator = creator; + CreatorId = creator.Id; + } + + internal void Update(Model model) + { + if (model.Creator.IsSpecified) + { + Creator = RestUser.Create(Discord, model.Creator.Value); + } + + CreatorId = model.CreatorId.ToNullable() ?? 0; // should be changed? + ChannelId = model.ChannelId.IsSpecified ? model.ChannelId.Value : null; + Name = model.Name; + Description = model.Description.GetValueOrDefault(); + StartTime = model.ScheduledStartTime; + EndTime = model.ScheduledEndTime; + PrivacyLevel = model.PrivacyLevel; + Status = model.Status; + Type = model.EntityType; + EntityId = model.EntityId; + Location = model.EntityMetadata?.Location.GetValueOrDefault(); + UserCount = model.UserCount.ToNullable(); + } + + /// + public Task StartAsync(RequestOptions options = null) + => ModifyAsync(x => x.Status = GuildScheduledEventStatus.Active); + + /// + public Task EndAsync(RequestOptions options = null) + => ModifyAsync(x => x.Status = Status == GuildScheduledEventStatus.Scheduled + ? GuildScheduledEventStatus.Cancelled + : GuildScheduledEventStatus.Completed); + + /// + public Task DeleteAsync(RequestOptions options = null) + => GuildHelper.DeleteEventAsync(Discord, this, options); + + /// + public async Task ModifyAsync(Action func, RequestOptions options = null) + { + var model = await GuildHelper.ModifyGuildEventAsync(Discord, func, this, options).ConfigureAwait(false); + Update(model); + } + + /// + /// Gets a collection of N users interested in the event. + /// + /// + /// + /// The returned collection is an asynchronous enumerable object; one must call + /// to access the individual messages as a + /// collection. + /// + /// This method will attempt to fetch all users that are interested in the event. + /// The library will attempt to split up the requests according to and . + /// In other words, if there are 300 users, and the constant + /// is 100, the request will be split into 3 individual requests; thus returning 3 individual asynchronous + /// responses, hence the need of flattening. + /// + /// The options to be used when sending the request. + /// + /// Paged collection of users. + /// + public IAsyncEnumerable> GetUsersAsync(RequestOptions options = null) + => GuildHelper.GetEventUsersAsync(Discord, this, null, null, options); + + /// + /// Gets a collection of N users interested in the event. + /// + /// + /// + /// The returned collection is an asynchronous enumerable object; one must call + /// to access the individual users as a + /// collection. + /// + /// + /// Do not fetch too many users at once! This may cause unwanted preemptive rate limit or even actual + /// rate limit, causing your bot to freeze! + /// + /// This method will attempt to fetch the number of users specified under around + /// the user depending on the . The library will + /// attempt to split up the requests according to your and + /// . In other words, should the user request 500 users, + /// and the constant is 100, the request will + /// be split into 5 individual requests; thus returning 5 individual asynchronous responses, hence the need + /// of flattening. + /// + /// The ID of the starting user to get the users from. + /// The direction of the users to be gotten from. + /// The numbers of users to be gotten from. + /// The options to be used when sending the request. + /// + /// Paged collection of users. + /// + public IAsyncEnumerable> GetUsersAsync(ulong fromUserId, Direction dir, int limit = DiscordConfig.MaxGuildEventUsersPerBatch, RequestOptions options = null) + => GuildHelper.GetEventUsersAsync(Discord, this, fromUserId, dir, limit, options); + + #region IGuildScheduledEvent + + /// + IAsyncEnumerable> IGuildScheduledEvent.GetUsersAsync(RequestOptions options) + => GetUsersAsync(options); + /// + IAsyncEnumerable> IGuildScheduledEvent.GetUsersAsync(ulong fromUserId, Direction dir, int limit, RequestOptions options) + => GetUsersAsync(fromUserId, dir, limit, options); + + #endregion + } +} diff --git a/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestCommandBase.cs b/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestCommandBase.cs new file mode 100644 index 000000000..a9efb6de1 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestCommandBase.cs @@ -0,0 +1,345 @@ +using Discord.Net.Rest; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using DataModel = Discord.API.ApplicationCommandInteractionData; +using Model = Discord.API.Interaction; + +namespace Discord.Rest +{ + /// + /// Represents a REST-based base command interaction. + /// + public class RestCommandBase : RestInteraction + { + /// + /// Gets the name of the invoked command. + /// + public string CommandName + => Data.Name; + + /// + /// Gets the id of the invoked command. + /// + public ulong CommandId + => Data.Id; + + /// + /// The data associated with this interaction. + /// + internal new RestCommandBaseData Data { get; private set; } + + + internal override bool _hasResponded { get; set; } + + private object _lock = new object(); + + internal RestCommandBase(DiscordRestClient client, Model model) + : base(client, model.Id) + { + } + + internal new static async Task CreateAsync(DiscordRestClient client, Model model) + { + var entity = new RestCommandBase(client, model); + await entity.UpdateAsync(client, model).ConfigureAwait(false); + return entity; + } + + internal override async Task UpdateAsync(DiscordRestClient client, Model model) + { + await base.UpdateAsync(client, model).ConfigureAwait(false); + } + + /// + /// Responds to an Interaction with type . + /// + /// The text of the message to be sent. + /// A array of embeds to send with this response. Max 10. + /// if the message should be read out by a text-to-speech reader, otherwise . + /// if the response should be hidden to everyone besides the invoker of the command, otherwise . + /// The allowed mentions for this response. + /// The request options for this response. + /// A to be sent with this response. + /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. + /// Message content is too long, length must be less or equal to . + /// The parameters provided were invalid or the token was invalid. + /// + /// A string that contains json to write back to the incoming http request. + /// + public override string Respond( + string text = null, + Embed[] embeds = null, + bool isTTS = false, + bool ephemeral = false, + AllowedMentions allowedMentions = null, + RequestOptions options = null, + MessageComponent component = null, + Embed embed = null) + { + if (!IsValidToken) + throw new InvalidOperationException("Interaction token is no longer valid"); + + if (!InteractionHelper.CanSendResponse(this)) + throw new TimeoutException($"Cannot respond to an interaction after {InteractionHelper.ResponseTimeLimit} seconds!"); + + embeds ??= Array.Empty(); + if (embed != null) + embeds = new[] { embed }.Concat(embeds).ToArray(); + + Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); + Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); + Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed."); + + // check that user flag and user Id list are exclusive, same with role flag and role Id list + if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue) + { + if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users) && + allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0) + { + throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(allowedMentions)); + } + + if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles) && + allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0) + { + throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(allowedMentions)); + } + } + + var response = new API.InteractionResponse + { + Type = InteractionResponseType.ChannelMessageWithSource, + Data = new API.InteractionCallbackData + { + Content = text, + AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, + Embeds = embeds.Select(x => x.ToModel()).ToArray(), + TTS = isTTS, + Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified, + Flags = ephemeral ? MessageFlags.Ephemeral : Optional.Unspecified + } + }; + + lock (_lock) + { + if (_hasResponded) + { + throw new InvalidOperationException("Cannot respond twice to the same interaction"); + } + } + + lock (_lock) + { + _hasResponded = true; + } + + return SerializePayload(response); + } + + /// + /// Sends a followup message for this interaction. + /// + /// The text of the message to be sent. + /// A array of embeds to send with this response. Max 10. + /// if the message should be read out by a text-to-speech reader, otherwise . + /// if the response should be hidden to everyone besides the invoker of the command, otherwise . + /// The allowed mentions for this response. + /// The request options for this response. + /// A to be sent with this response. + /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. + /// + /// The sent message. + /// + public override async Task FollowupAsync( + string text = null, + Embed[] embeds = null, + bool isTTS = false, + bool ephemeral = false, + AllowedMentions allowedMentions = null, + RequestOptions options = null, + MessageComponent component = null, + Embed embed = null) + { + if (!IsValidToken) + throw new InvalidOperationException("Interaction token is no longer valid"); + + embeds ??= Array.Empty(); + if (embed != null) + embeds = new[] { embed }.Concat(embeds).ToArray(); + + Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); + Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); + Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed."); + + var args = new API.Rest.CreateWebhookMessageParams + { + Content = text, + AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, + IsTTS = isTTS, + Embeds = embeds.Select(x => x.ToModel()).ToArray(), + Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified + }; + + if (ephemeral) + args.Flags = MessageFlags.Ephemeral; + + return await InteractionHelper.SendFollowupAsync(Discord, args, Token, Channel, options); + } + + /// + /// Sends a followup message for this interaction. + /// + /// The text of the message to be sent. + /// The file to upload. + /// The file name of the attachment. + /// A array of embeds to send with this response. Max 10. + /// if the message should be read out by a text-to-speech reader, otherwise . + /// if the response should be hidden to everyone besides the invoker of the command, otherwise . + /// The allowed mentions for this response. + /// The request options for this response. + /// A to be sent with this response. + /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. + /// + /// The sent message. + /// + public override async Task FollowupWithFileAsync( + Stream fileStream, + string fileName, + string text = null, + Embed[] embeds = null, + bool isTTS = false, + bool ephemeral = false, + AllowedMentions allowedMentions = null, + RequestOptions options = null, + MessageComponent component = null, + Embed embed = null) + { + if (!IsValidToken) + throw new InvalidOperationException("Interaction token is no longer valid"); + + embeds ??= Array.Empty(); + if (embed != null) + embeds = new[] { embed }.Concat(embeds).ToArray(); + + Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); + Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); + Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed."); + Preconditions.NotNull(fileStream, nameof(fileStream), "File Stream must have data"); + Preconditions.NotNullOrEmpty(fileName, nameof(fileName), "File Name must not be empty or null"); + + var args = new API.Rest.CreateWebhookMessageParams + { + Content = text, + AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, + IsTTS = isTTS, + Embeds = embeds.Select(x => x.ToModel()).ToArray(), + Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified, + File = fileStream is not null ? new MultipartFile(fileStream, fileName) : Optional.Unspecified + }; + + if (ephemeral) + args.Flags = MessageFlags.Ephemeral; + + return await InteractionHelper.SendFollowupAsync(Discord, args, Token, Channel, options); + } + + /// + /// Sends a followup message for this interaction. + /// + /// The text of the message to be sent. + /// The file to upload. + /// The file name of the attachment. + /// A array of embeds to send with this response. Max 10. + /// if the message should be read out by a text-to-speech reader, otherwise . + /// if the response should be hidden to everyone besides the invoker of the command, otherwise . + /// The allowed mentions for this response. + /// The request options for this response. + /// A to be sent with this response. + /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. + /// + /// The sent message. + /// + public override async Task FollowupWithFileAsync( + string filePath, + string text = null, + string fileName = null, + Embed[] embeds = null, + bool isTTS = false, + bool ephemeral = false, + AllowedMentions allowedMentions = null, + RequestOptions options = null, + MessageComponent component = null, + Embed embed = null) + { + if (!IsValidToken) + throw new InvalidOperationException("Interaction token is no longer valid"); + + embeds ??= Array.Empty(); + if (embed != null) + embeds = new[] { embed }.Concat(embeds).ToArray(); + + Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); + Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); + Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed."); + Preconditions.NotNullOrEmpty(filePath, nameof(filePath), "Path must exist"); + + fileName ??= Path.GetFileName(filePath); + Preconditions.NotNullOrEmpty(fileName, nameof(fileName), "File Name must not be empty or null"); + + var args = new API.Rest.CreateWebhookMessageParams + { + Content = text, + AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, + IsTTS = isTTS, + Embeds = embeds.Select(x => x.ToModel()).ToArray(), + Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified, + File = !string.IsNullOrEmpty(filePath) ? new MultipartFile(new MemoryStream(File.ReadAllBytes(filePath), false), fileName) : Optional.Unspecified + }; + + if (ephemeral) + args.Flags = MessageFlags.Ephemeral; + + return await InteractionHelper.SendFollowupAsync(Discord, args, Token, Channel, options); + } + + /// + /// Acknowledges this interaction with the . + /// + /// + /// A string that contains json to write back to the incoming http request. + /// + public override string Defer(bool ephemeral = false, RequestOptions options = null) + { + if (!InteractionHelper.CanSendResponse(this)) + throw new TimeoutException($"Cannot defer an interaction after {InteractionHelper.ResponseTimeLimit} seconds!"); + + var response = new API.InteractionResponse + { + Type = InteractionResponseType.DeferredChannelMessageWithSource, + Data = new API.InteractionCallbackData + { + Flags = ephemeral ? MessageFlags.Ephemeral : Optional.Unspecified + } + }; + + lock (_lock) + { + if (_hasResponded) + { + throw new InvalidOperationException("Cannot respond or defer twice to the same interaction"); + } + } + + lock (_lock) + { + _hasResponded = true; + } + + return SerializePayload(response); + } + } +} diff --git a/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestCommandBaseData.cs b/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestCommandBaseData.cs new file mode 100644 index 000000000..4227c802a --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestCommandBaseData.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Model = Discord.API.ApplicationCommandInteractionData; + +namespace Discord.Rest +{ + /// + /// Represents the base data tied with the interaction. + /// + public class RestCommandBaseData : RestEntity, IApplicationCommandInteractionData where TOption : IApplicationCommandInteractionDataOption + { + /// + public string Name { get; private set; } + + /// + /// Gets a collection of received with this interaction. + /// + public virtual IReadOnlyCollection Options { get; internal set; } + + internal RestResolvableData ResolvableData; + + internal RestCommandBaseData(BaseDiscordClient client, Model model) + : base(client, model.Id) + { + } + + internal static async Task CreateAsync(DiscordRestClient client, Model model, RestGuild guild, IRestMessageChannel channel) + { + var entity = new RestCommandBaseData(client, model); + await entity.UpdateAsync(client, model, guild, channel).ConfigureAwait(false); + return entity; + } + + internal virtual async Task UpdateAsync(DiscordRestClient client, Model model, RestGuild guild, IRestMessageChannel channel) + { + Name = model.Name; + if (model.Resolved.IsSpecified && ResolvableData == null) + { + ResolvableData = new RestResolvableData(); + await ResolvableData.PopulateAsync(client, guild, channel, model).ConfigureAwait(false); + } + } + + IReadOnlyCollection IApplicationCommandInteractionData.Options + => (IReadOnlyCollection)Options; + } + + /// + /// Represents the base data tied with the interaction. + /// + public class RestCommandBaseData : RestCommandBaseData + { + internal RestCommandBaseData(DiscordRestClient client, Model model) + : base(client, model) { } + } +} diff --git a/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestResolvableData.cs b/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestResolvableData.cs new file mode 100644 index 000000000..710207ef9 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestResolvableData.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.Rest +{ + internal class RestResolvableData where T : API.IResolvable + { + internal readonly Dictionary GuildMembers + = new Dictionary(); + internal readonly Dictionary Users + = new Dictionary(); + internal readonly Dictionary Channels + = new Dictionary(); + internal readonly Dictionary Roles + = new Dictionary(); + internal readonly Dictionary Messages + = new Dictionary(); + + internal async Task PopulateAsync(DiscordRestClient discord, RestGuild guild, IRestMessageChannel channel, T model) + { + var resolved = model.Resolved.Value; + + if (resolved.Users.IsSpecified) + { + foreach (var user in resolved.Users.Value) + { + var restUser = RestUser.Create(discord, user.Value); + + Users.Add(ulong.Parse(user.Key), restUser); + } + } + + if (resolved.Channels.IsSpecified) + { + var channels = await guild.GetChannelsAsync().ConfigureAwait(false); + + foreach (var channelModel in resolved.Channels.Value) + { + var restChannel = channels.FirstOrDefault(x => x.Id == channelModel.Value.Id); + + restChannel.Update(channelModel.Value); + + Channels.Add(ulong.Parse(channelModel.Key), restChannel); + } + } + + if (resolved.Members.IsSpecified) + { + foreach (var member in resolved.Members.Value) + { + // pull the adjacent user model + member.Value.User = resolved.Users.Value.FirstOrDefault(x => x.Key == member.Key).Value; + var restMember = RestGuildUser.Create(discord, guild, member.Value); + + GuildMembers.Add(ulong.Parse(member.Key), restMember); + } + } + + if (resolved.Roles.IsSpecified) + { + foreach (var role in resolved.Roles.Value) + { + var restRole = RestRole.Create(discord, guild, role.Value); + + Roles.Add(ulong.Parse(role.Key), restRole); + } + } + + if (resolved.Messages.IsSpecified) + { + foreach (var msg in resolved.Messages.Value) + { + channel ??= (IRestMessageChannel)(Channels.FirstOrDefault(x => x.Key == msg.Value.ChannelId).Value ?? await discord.GetChannelAsync(msg.Value.ChannelId).ConfigureAwait(false)); + + RestUser author; + + if (msg.Value.Author.IsSpecified) + { + author = RestUser.Create(discord, msg.Value.Author.Value); + } + else + { + author = RestGuildUser.Create(discord, guild, msg.Value.Member.Value); + } + + var message = RestMessage.Create(discord, channel, author, msg.Value); + + Messages.Add(message.Id, message); + } + } + } + } +} diff --git a/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/MessageCommands/RestMessageCommand.cs b/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/MessageCommands/RestMessageCommand.cs new file mode 100644 index 000000000..53055cac3 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/MessageCommands/RestMessageCommand.cs @@ -0,0 +1,45 @@ +using System.Threading.Tasks; +using DataModel = Discord.API.ApplicationCommandInteractionData; +using Model = Discord.API.Interaction; + +namespace Discord.Rest +{ + /// + /// Represents a REST-based message command interaction. + /// + public class RestMessageCommand : RestCommandBase, IMessageCommandInteraction, IDiscordInteraction + { + /// + /// The data associated with this interaction. + /// + public new RestMessageCommandData Data { get; private set; } + + internal RestMessageCommand(DiscordRestClient client, Model model) + : base(client, model) + { + + } + + internal new static async Task CreateAsync(DiscordRestClient client, Model model) + { + var entity = new RestMessageCommand(client, model); + await entity.UpdateAsync(client, model).ConfigureAwait(false); + return entity; + } + + internal override async Task UpdateAsync(DiscordRestClient client, Model model) + { + await base.UpdateAsync(client, model).ConfigureAwait(false); + + var dataModel = model.Data.IsSpecified + ? (DataModel)model.Data.Value + : null; + + Data = await RestMessageCommandData.CreateAsync(client, dataModel, Guild, Channel).ConfigureAwait(false); + } + + //IMessageCommandInteraction + /// + IMessageCommandInteractionData IMessageCommandInteraction.Data => Data; + } +} diff --git a/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/MessageCommands/RestMessageCommandData.cs b/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/MessageCommands/RestMessageCommandData.cs new file mode 100644 index 000000000..8eadab617 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/MessageCommands/RestMessageCommandData.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Model = Discord.API.ApplicationCommandInteractionData; + +namespace Discord.Rest +{ + /// + /// Represents the data for a . + /// + public class RestMessageCommandData : RestCommandBaseData, IMessageCommandInteractionData, IDiscordInteractionData + { + /// + /// Gets the message associated with this message command. + /// + public RestMessage Message + => ResolvableData?.Messages.FirstOrDefault().Value; + + /// + /// + /// Note Not implemented for + /// + public override IReadOnlyCollection Options + => throw new System.NotImplementedException(); + + internal RestMessageCommandData(DiscordRestClient client, Model model) + : base(client, model) { } + + internal new static async Task CreateAsync(DiscordRestClient client, Model model, RestGuild guild, IRestMessageChannel channel) + { + var entity = new RestMessageCommandData(client, model); + await entity.UpdateAsync(client, model, guild, channel).ConfigureAwait(false); + return entity; + } + + //IMessageCommandInteractionData + /// + IMessage IMessageCommandInteractionData.Message => Message; + } +} diff --git a/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/UserCommands/RestUserCommand.cs b/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/UserCommands/RestUserCommand.cs new file mode 100644 index 000000000..58f1ed375 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/UserCommands/RestUserCommand.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using DataModel = Discord.API.ApplicationCommandInteractionData; +using Model = Discord.API.Interaction; + +namespace Discord.Rest +{ + /// + /// Represents a REST-based user command. + /// + public class RestUserCommand : RestCommandBase, IUserCommandInteraction, IDiscordInteraction + { + /// + /// Gets the data associated with this interaction. + /// + public new RestUserCommandData Data { get; private set; } + + internal RestUserCommand(DiscordRestClient client, Model model) + : base(client, model) + { + } + + internal new static async Task CreateAsync(DiscordRestClient client, Model model) + { + var entity = new RestUserCommand(client, model); + await entity.UpdateAsync(client, model).ConfigureAwait(false); + return entity; + } + + internal override async Task UpdateAsync(DiscordRestClient client, Model model) + { + await base.UpdateAsync(client, model).ConfigureAwait(false); + + var dataModel = model.Data.IsSpecified + ? (DataModel)model.Data.Value + : null; + + Data = await RestUserCommandData.CreateAsync(client, dataModel, Guild, Channel).ConfigureAwait(false); + } + + //IUserCommandInteractionData + /// + IUserCommandInteractionData IUserCommandInteraction.Data => Data; + } +} diff --git a/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/UserCommands/RestUserCommandData.cs b/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/UserCommands/RestUserCommandData.cs new file mode 100644 index 000000000..7563eecc7 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/UserCommands/RestUserCommandData.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Model = Discord.API.ApplicationCommandInteractionData; + +namespace Discord.Rest +{ + /// + /// Represents the data for a . + /// + public class RestUserCommandData : RestCommandBaseData, IUserCommandInteractionData, IDiscordInteractionData + { + /// + /// Gets the user who this command targets. + /// + public RestUser Member + => (RestUser)ResolvableData.GuildMembers.Values.FirstOrDefault() ?? ResolvableData.Users.Values.FirstOrDefault(); + + /// + /// + /// Note Not implemented for + /// + public override IReadOnlyCollection Options + => throw new System.NotImplementedException(); + + internal RestUserCommandData(DiscordRestClient client, Model model) + : base(client, model) { } + + internal new static async Task CreateAsync(DiscordRestClient client, Model model, RestGuild guild, IRestMessageChannel channel) + { + var entity = new RestUserCommandData(client, model); + await entity.UpdateAsync(client, model, guild, channel).ConfigureAwait(false); + return entity; + } + + //IUserCommandInteractionData + /// + IUser IUserCommandInteractionData.User => Member; + } +} diff --git a/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs b/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs new file mode 100644 index 000000000..7cfc6a2ec --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs @@ -0,0 +1,542 @@ +using Discord.API; +using Discord.API.Rest; +using Discord.Net; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading.Tasks; + +namespace Discord.Rest +{ + internal static class InteractionHelper + { + public const double ResponseTimeLimit = 3; + public const double ResponseAndFollowupLimit = 15; + + #region InteractionHelper + public static bool CanSendResponse(IDiscordInteraction interaction) + { + return (DateTime.UtcNow - interaction.CreatedAt).TotalSeconds < ResponseTimeLimit; + } + public static bool CanRespondOrFollowup(IDiscordInteraction interaction) + { + return (DateTime.UtcNow - interaction.CreatedAt).TotalMinutes <= ResponseAndFollowupLimit; + } + + public static Task DeleteAllGuildCommandsAsync(BaseDiscordClient client, ulong guildId, RequestOptions options = null) + { + return client.ApiClient.BulkOverwriteGuildApplicationCommandsAsync(guildId, Array.Empty(), options); + } + + public static Task DeleteAllGlobalCommandsAsync(BaseDiscordClient client, RequestOptions options = null) + { + return client.ApiClient.BulkOverwriteGlobalApplicationCommandsAsync(Array.Empty(), options); + } + + public static Task SendInteractionResponseAsync(BaseDiscordClient client, InteractionResponse response, + ulong interactionId, string interactionToken, RequestOptions options = null) + { + return client.ApiClient.CreateInteractionResponseAsync(response, interactionId, interactionToken, options); + } + + public static async Task GetOriginalResponseAsync(BaseDiscordClient client, IMessageChannel channel, + IDiscordInteraction interaction, RequestOptions options = null) + { + var model = await client.ApiClient.GetInteractionResponseAsync(interaction.Token, options).ConfigureAwait(false); + return RestInteractionMessage.Create(client, model, interaction.Token, channel); + } + + public static async Task SendFollowupAsync(BaseDiscordClient client, CreateWebhookMessageParams args, + string token, IMessageChannel channel, RequestOptions options = null) + { + var model = await client.ApiClient.CreateInteractionFollowupMessageAsync(args, token, options).ConfigureAwait(false); + + var entity = RestFollowupMessage.Create(client, model, token, channel); + return entity; + } + #endregion + + #region Global commands + public static async Task GetGlobalCommandAsync(BaseDiscordClient client, ulong id, + RequestOptions options = null) + { + var model = await client.ApiClient.GetGlobalApplicationCommandAsync(id, options).ConfigureAwait(false); + + return RestGlobalCommand.Create(client, model); + } + public static Task CreateGlobalCommandAsync(BaseDiscordClient client, + Action func, RequestOptions options = null) where TArg : ApplicationCommandProperties + { + var args = Activator.CreateInstance(typeof(TArg)); + func((TArg)args); + return CreateGlobalCommandAsync(client, (TArg)args, options); + } + public static async Task CreateGlobalCommandAsync(BaseDiscordClient client, + ApplicationCommandProperties arg, RequestOptions options = null) + { + Preconditions.NotNullOrEmpty(arg.Name, nameof(arg.Name)); + + var model = new CreateApplicationCommandParams + { + Name = arg.Name.Value, + Type = arg.Type, + DefaultPermission = arg.IsDefaultPermission.IsSpecified + ? arg.IsDefaultPermission.Value + : Optional.Unspecified + }; + + if (arg is SlashCommandProperties slashProps) + { + Preconditions.NotNullOrEmpty(slashProps.Description, nameof(slashProps.Description)); + + model.Description = slashProps.Description.Value; + + model.Options = slashProps.Options.IsSpecified + ? slashProps.Options.Value.Select(x => new ApplicationCommandOption(x)).ToArray() + : Optional.Unspecified; + } + + return await client.ApiClient.CreateGlobalApplicationCommandAsync(model, options).ConfigureAwait(false); + } + + public static async Task BulkOverwriteGlobalCommandsAsync(BaseDiscordClient client, + ApplicationCommandProperties[] args, RequestOptions options = null) + { + Preconditions.NotNull(args, nameof(args)); + + var models = new List(); + + foreach (var arg in args) + { + Preconditions.NotNullOrEmpty(arg.Name, nameof(arg.Name)); + + var model = new CreateApplicationCommandParams + { + Name = arg.Name.Value, + Type = arg.Type, + DefaultPermission = arg.IsDefaultPermission.IsSpecified + ? arg.IsDefaultPermission.Value + : Optional.Unspecified + }; + + if (arg is SlashCommandProperties slashProps) + { + Preconditions.NotNullOrEmpty(slashProps.Description, nameof(slashProps.Description)); + + model.Description = slashProps.Description.Value; + + model.Options = slashProps.Options.IsSpecified + ? slashProps.Options.Value.Select(x => new ApplicationCommandOption(x)).ToArray() + : Optional.Unspecified; + } + + models.Add(model); + } + + return await client.ApiClient.BulkOverwriteGlobalApplicationCommandsAsync(models.ToArray(), options).ConfigureAwait(false); + } + + public static async Task> BulkOverwriteGuildCommandsAsync(BaseDiscordClient client, ulong guildId, + ApplicationCommandProperties[] args, RequestOptions options = null) + { + Preconditions.NotNull(args, nameof(args)); + + var models = new List(); + + foreach (var arg in args) + { + Preconditions.NotNullOrEmpty(arg.Name, nameof(arg.Name)); + + var model = new CreateApplicationCommandParams + { + Name = arg.Name.Value, + Type = arg.Type, + DefaultPermission = arg.IsDefaultPermission.IsSpecified + ? arg.IsDefaultPermission.Value + : Optional.Unspecified + }; + + if (arg is SlashCommandProperties slashProps) + { + Preconditions.NotNullOrEmpty(slashProps.Description, nameof(slashProps.Description)); + + model.Description = slashProps.Description.Value; + + model.Options = slashProps.Options.IsSpecified + ? slashProps.Options.Value.Select(x => new ApplicationCommandOption(x)).ToArray() + : Optional.Unspecified; + } + + models.Add(model); + } + + return await client.ApiClient.BulkOverwriteGuildApplicationCommandsAsync(guildId, models.ToArray(), options).ConfigureAwait(false); + } + + private static TArg GetApplicationCommandProperties(IApplicationCommand command) + where TArg : ApplicationCommandProperties + { + bool isBaseClass = typeof(TArg) == typeof(ApplicationCommandProperties); + + switch (true) + { + case true when (typeof(TArg) == typeof(SlashCommandProperties) || isBaseClass) && command.Type == ApplicationCommandType.Slash: + return new SlashCommandProperties() as TArg; + case true when (typeof(TArg) == typeof(MessageCommandProperties) || isBaseClass) && command.Type == ApplicationCommandType.Message: + return new MessageCommandProperties() as TArg; + case true when (typeof(TArg) == typeof(UserCommandProperties) || isBaseClass) && command.Type == ApplicationCommandType.User: + return new UserCommandProperties() as TArg; + default: + throw new InvalidOperationException($"Cannot modify application command of type {command.Type} with the parameter type {typeof(TArg).FullName}"); + } + } + + public static Task ModifyGlobalCommandAsync(BaseDiscordClient client, IApplicationCommand command, + Action func, RequestOptions options = null) where TArg : ApplicationCommandProperties + { + var arg = GetApplicationCommandProperties(command); + func(arg); + return ModifyGlobalCommandAsync(client, command, arg, options); + } + + public static async Task ModifyGlobalCommandAsync(BaseDiscordClient client, IApplicationCommand command, + ApplicationCommandProperties args, RequestOptions options = null) + { + if (args.Name.IsSpecified) + { + Preconditions.AtMost(args.Name.Value.Length, 32, nameof(args.Name)); + Preconditions.AtLeast(args.Name.Value.Length, 1, nameof(args.Name)); + } + + var model = new ModifyApplicationCommandParams + { + Name = args.Name, + DefaultPermission = args.IsDefaultPermission.IsSpecified + ? args.IsDefaultPermission.Value + : Optional.Unspecified + }; + + if (args is SlashCommandProperties slashProps) + { + if (slashProps.Description.IsSpecified) + { + Preconditions.AtMost(slashProps.Description.Value.Length, 100, nameof(slashProps.Description)); + Preconditions.AtLeast(slashProps.Description.Value.Length, 1, nameof(slashProps.Description)); + } + + if (slashProps.Options.IsSpecified) + { + if (slashProps.Options.Value.Count > 10) + throw new ArgumentException("Option count must be 10 or less"); + } + + model.Description = slashProps.Description; + + model.Options = slashProps.Options.IsSpecified + ? slashProps.Options.Value.Select(x => new ApplicationCommandOption(x)).ToArray() + : Optional.Unspecified; + } + + return await client.ApiClient.ModifyGlobalApplicationCommandAsync(model, command.Id, options).ConfigureAwait(false); + } + + public static async Task DeleteGlobalCommandAsync(BaseDiscordClient client, IApplicationCommand command, RequestOptions options = null) + { + Preconditions.NotNull(command, nameof(command)); + Preconditions.NotEqual(command.Id, 0, nameof(command.Id)); + + await client.ApiClient.DeleteGlobalApplicationCommandAsync(command.Id, options).ConfigureAwait(false); + } + #endregion + + #region Guild Commands + public static Task CreateGuildCommandAsync(BaseDiscordClient client, ulong guildId, + Action func, RequestOptions options) where TArg : ApplicationCommandProperties + { + var args = Activator.CreateInstance(typeof(TArg)); + func((TArg)args); + return CreateGuildCommandAsync(client, guildId, (TArg)args, options); + } + + public static async Task CreateGuildCommandAsync(BaseDiscordClient client, ulong guildId, + ApplicationCommandProperties arg, RequestOptions options = null) + { + var model = new CreateApplicationCommandParams + { + Name = arg.Name.Value, + Type = arg.Type, + DefaultPermission = arg.IsDefaultPermission.IsSpecified + ? arg.IsDefaultPermission.Value + : Optional.Unspecified + }; + + if (arg is SlashCommandProperties slashProps) + { + Preconditions.NotNullOrEmpty(slashProps.Description, nameof(slashProps.Description)); + + model.Description = slashProps.Description.Value; + + model.Options = slashProps.Options.IsSpecified + ? slashProps.Options.Value.Select(x => new ApplicationCommandOption(x)).ToArray() + : Optional.Unspecified; + } + + return await client.ApiClient.CreateGuildApplicationCommandAsync(model, guildId, options).ConfigureAwait(false); + } + + public static Task ModifyGuildCommandAsync(BaseDiscordClient client, IApplicationCommand command, ulong guildId, + Action func, RequestOptions options = null) where TArg : ApplicationCommandProperties + { + var arg = GetApplicationCommandProperties(command); + func(arg); + return ModifyGuildCommandAsync(client, command, guildId, arg, options); + } + + public static async Task ModifyGuildCommandAsync(BaseDiscordClient client, IApplicationCommand command, ulong guildId, + ApplicationCommandProperties arg, RequestOptions options = null) + { + var model = new ModifyApplicationCommandParams + { + Name = arg.Name, + DefaultPermission = arg.IsDefaultPermission.IsSpecified + ? arg.IsDefaultPermission.Value + : Optional.Unspecified + }; + + if (arg is SlashCommandProperties slashProps) + { + Preconditions.NotNullOrEmpty(slashProps.Description, nameof(slashProps.Description)); + + model.Description = slashProps.Description.Value; + + model.Options = slashProps.Options.IsSpecified + ? slashProps.Options.Value.Select(x => new ApplicationCommandOption(x)).ToArray() + : Optional.Unspecified; + } + + return await client.ApiClient.ModifyGuildApplicationCommandAsync(model, guildId, command.Id, options).ConfigureAwait(false); + } + + public static async Task DeleteGuildCommandAsync(BaseDiscordClient client, ulong guildId, IApplicationCommand command, RequestOptions options = null) + { + Preconditions.NotNull(command, nameof(command)); + Preconditions.NotEqual(command.Id, 0, nameof(command.Id)); + + await client.ApiClient.DeleteGuildApplicationCommandAsync(guildId, command.Id, options).ConfigureAwait(false); + } + + public static Task DeleteUnknownApplicationCommandAsync(BaseDiscordClient client, ulong? guildId, IApplicationCommand command, RequestOptions options = null) + { + return guildId.HasValue + ? DeleteGuildCommandAsync(client, guildId.Value, command, options) + : DeleteGlobalCommandAsync(client, command, options); + } + #endregion + + #region Responses + public static async Task ModifyFollowupMessageAsync(BaseDiscordClient client, RestFollowupMessage message, Action func, + RequestOptions options = null) + { + var args = new MessageProperties(); + func(args); + + var embed = args.Embed; + var embeds = args.Embeds; + + bool hasText = args.Content.IsSpecified ? !string.IsNullOrEmpty(args.Content.Value) : !string.IsNullOrEmpty(message.Content); + bool hasEmbeds = embed.IsSpecified && embed.Value != null || embeds.IsSpecified && embeds.Value?.Length > 0 || message.Embeds.Any(); + bool hasComponents = args.Components.IsSpecified && args.Components.Value != null; + + if (!hasComponents && !hasText && !hasEmbeds) + Preconditions.NotNullOrEmpty(args.Content.IsSpecified ? args.Content.Value : string.Empty, nameof(args.Content)); + + var apiEmbeds = embed.IsSpecified || embeds.IsSpecified ? new List() : null; + + if (embed.IsSpecified && embed.Value != null) + { + apiEmbeds.Add(embed.Value.ToModel()); + } + + if (embeds.IsSpecified && embeds.Value != null) + { + apiEmbeds.AddRange(embeds.Value.Select(x => x.ToModel())); + } + + Preconditions.AtMost(apiEmbeds?.Count ?? 0, 10, nameof(args.Embeds), "A max of 10 embeds are allowed."); + + var apiArgs = new ModifyInteractionResponseParams + { + Content = args.Content, + Embeds = apiEmbeds?.ToArray() ?? Optional.Unspecified, + AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value.ToModel() : Optional.Unspecified, + Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional.Unspecified + }; + + return await client.ApiClient.ModifyInteractionFollowupMessageAsync(apiArgs, message.Id, message.Token, options).ConfigureAwait(false); + } + public static async Task DeleteFollowupMessageAsync(BaseDiscordClient client, RestFollowupMessage message, RequestOptions options = null) + => await client.ApiClient.DeleteInteractionFollowupMessageAsync(message.Id, message.Token, options); + public static async Task ModifyInteractionResponseAsync(BaseDiscordClient client, string token, Action func, + RequestOptions options = null) + { + var args = new MessageProperties(); + func(args); + + var embed = args.Embed; + var embeds = args.Embeds; + + bool hasText = !string.IsNullOrEmpty(args.Content.GetValueOrDefault()); + bool hasEmbeds = embed.IsSpecified && embed.Value != null || embeds.IsSpecified && embeds.Value?.Length > 0; + bool hasComponents = args.Components.IsSpecified && args.Components.Value != null; + + if (!hasComponents && !hasText && !hasEmbeds) + Preconditions.NotNullOrEmpty(args.Content.IsSpecified ? args.Content.Value : string.Empty, nameof(args.Content)); + + var apiEmbeds = embed.IsSpecified || embeds.IsSpecified ? new List() : null; + + if (embed.IsSpecified && embed.Value != null) + { + apiEmbeds.Add(embed.Value.ToModel()); + } + + if (embeds.IsSpecified && embeds.Value != null) + { + apiEmbeds.AddRange(embeds.Value.Select(x => x.ToModel())); + } + + Preconditions.AtMost(apiEmbeds?.Count ?? 0, 10, nameof(args.Embeds), "A max of 10 embeds are allowed."); + + var apiArgs = new ModifyInteractionResponseParams + { + Content = args.Content, + Embeds = apiEmbeds?.ToArray() ?? Optional.Unspecified, + AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional.Unspecified, + Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional.Unspecified, + Flags = args.Flags + }; + + return await client.ApiClient.ModifyInteractionResponseAsync(apiArgs, token, options).ConfigureAwait(false); + } + + public static async Task DeleteInteractionResponseAsync(BaseDiscordClient client, RestInteractionMessage message, RequestOptions options = null) + => await client.ApiClient.DeleteInteractionFollowupMessageAsync(message.Id, message.Token, options); + + public static Task SendAutocompleteResultAsync(BaseDiscordClient client, IEnumerable result, ulong interactionId, + string interactionToken, RequestOptions options) + { + result ??= Array.Empty(); + + Preconditions.AtMost(result.Count(), 20, nameof(result), "A maximum of 20 choices are allowed!"); + + var apiArgs = new InteractionResponse + { + Type = InteractionResponseType.ApplicationCommandAutocompleteResult, + Data = new InteractionCallbackData + { + Choices = result.Any() + ? result.Select(x => new ApplicationCommandOptionChoice { Name = x.Name, Value = x.Value }).ToArray() + : Array.Empty() + } + }; + + return client.ApiClient.CreateInteractionResponseAsync(apiArgs, interactionId, interactionToken, options); + } + #endregion + + #region Guild permissions + public static async Task> GetGuildCommandPermissionsAsync(BaseDiscordClient client, + ulong guildId, RequestOptions options) + { + var models = await client.ApiClient.GetGuildApplicationCommandPermissionsAsync(guildId, options); + return models.Select(x => + new GuildApplicationCommandPermission(x.Id, x.ApplicationId, guildId, x.Permissions.Select( + y => new ApplicationCommandPermission(y.Id, y.Type, y.Permission)) + .ToArray()) + ).ToArray(); + } + + public static async Task GetGuildCommandPermissionAsync(BaseDiscordClient client, + ulong guildId, ulong commandId, RequestOptions options) + { + try + { + var model = await client.ApiClient.GetGuildApplicationCommandPermissionAsync(guildId, commandId, options); + return new GuildApplicationCommandPermission(model.Id, model.ApplicationId, guildId, model.Permissions.Select( + y => new ApplicationCommandPermission(y.Id, y.Type, y.Permission)).ToArray()); + } + catch (HttpException x) + { + if (x.HttpCode == HttpStatusCode.NotFound) + return null; + throw; + } + } + + public static async Task ModifyGuildCommandPermissionsAsync(BaseDiscordClient client, ulong guildId, ulong commandId, + ApplicationCommandPermission[] args, RequestOptions options) + { + Preconditions.NotNull(args, nameof(args)); + Preconditions.AtMost(args.Length, 10, nameof(args)); + Preconditions.AtLeast(args.Length, 0, nameof(args)); + + var permissionsList = new List(); + + foreach (var arg in args) + { + var permissions = new ApplicationCommandPermissions + { + Id = arg.TargetId, + Permission = arg.Permission, + Type = arg.TargetType + }; + + permissionsList.Add(permissions); + } + + var model = new ModifyGuildApplicationCommandPermissionsParams + { + Permissions = permissionsList.ToArray() + }; + + var apiModel = await client.ApiClient.ModifyApplicationCommandPermissionsAsync(model, guildId, commandId, options); + + return new GuildApplicationCommandPermission(apiModel.Id, apiModel.ApplicationId, guildId, apiModel.Permissions.Select( + x => new ApplicationCommandPermission(x.Id, x.Type, x.Permission)).ToArray()); + } + + public static async Task> BatchEditGuildCommandPermissionsAsync(BaseDiscordClient client, ulong guildId, + IDictionary args, RequestOptions options) + { + Preconditions.NotNull(args, nameof(args)); + Preconditions.NotEqual(args.Count, 0, nameof(args)); + + var models = new List(); + + foreach (var arg in args) + { + Preconditions.AtMost(arg.Value.Length, 10, nameof(args)); + + var model = new ModifyGuildApplicationCommandPermissions + { + Id = arg.Key, + Permissions = arg.Value.Select(x => new ApplicationCommandPermissions + { + Id = x.TargetId, + Permission = x.Permission, + Type = x.TargetType + }).ToArray() + }; + + models.Add(model); + } + + var apiModels = await client.ApiClient.BatchModifyApplicationCommandPermissionsAsync(models.ToArray(), guildId, options); + + return apiModels.Select( + x => new GuildApplicationCommandPermission(x.Id, x.ApplicationId, x.GuildId, x.Permissions.Select( + y => new ApplicationCommandPermission(y.Id, y.Type, y.Permission)).ToArray())).ToArray(); + } + #endregion + } +} diff --git a/src/Discord.Net.Rest/Entities/Interactions/MessageComponents/RestMessageComponent.cs b/src/Discord.Net.Rest/Entities/Interactions/MessageComponents/RestMessageComponent.cs new file mode 100644 index 000000000..eb47e15aa --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Interactions/MessageComponents/RestMessageComponent.cs @@ -0,0 +1,452 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Model = Discord.API.Interaction; +using DataModel = Discord.API.MessageComponentInteractionData; +using System.IO; +using Discord.Net.Rest; + +namespace Discord.Rest +{ + /// + /// Represents a REST-based message component. + /// + internal class RestMessageComponent : RestInteraction, IComponentInteraction, IDiscordInteraction + { + /// + /// Gets the data received with this interaction, contains the button that was clicked. + /// + public new RestMessageComponentData Data { get; } + + /// + /// Gets the message that contained the trigger for this interaction. + /// + public RestUserMessage Message { get; private set; } + + private object _lock = new object(); + internal override bool _hasResponded { get; set; } = false; + + internal RestMessageComponent(BaseDiscordClient client, Model model) + : base(client, model.Id) + { + var dataModel = model.Data.IsSpecified + ? (DataModel)model.Data.Value + : null; + + Data = new RestMessageComponentData(dataModel); + } + + internal new static async Task CreateAsync(DiscordRestClient client, Model model) + { + var entity = new RestMessageComponent(client, model); + await entity.UpdateAsync(client, model).ConfigureAwait(false); + return entity; + } + internal override async Task UpdateAsync(DiscordRestClient discord, Model model) + { + await base.UpdateAsync(discord, model).ConfigureAwait(false); + + if (model.Message.IsSpecified && model.ChannelId.IsSpecified) + { + if (Message == null) + { + Message = RestUserMessage.Create(Discord, Channel, User, model.Message.Value); + } + } + } + /// + /// Responds to an Interaction with type . + /// + /// The text of the message to be sent. + /// A array of embeds to send with this response. Max 10. + /// if the message should be read out by a text-to-speech reader, otherwise . + /// if the response should be hidden to everyone besides the invoker of the command, otherwise . + /// The allowed mentions for this response. + /// The request options for this response. + /// A to be sent with this response. + /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. + /// + /// A string that contains json to write back to the incoming http request. + /// + public override string Respond( + string text = null, + Embed[] embeds = null, + bool isTTS = false, + bool ephemeral = false, + AllowedMentions allowedMentions = null, + RequestOptions options = null, + MessageComponent component = null, + Embed embed = null) + { + if (!IsValidToken) + throw new InvalidOperationException("Interaction token is no longer valid"); + + if (!InteractionHelper.CanSendResponse(this)) + throw new TimeoutException($"Cannot respond to an interaction after {InteractionHelper.ResponseTimeLimit} seconds!"); + + embeds ??= Array.Empty(); + if (embed != null) + embeds = new[] { embed }.Concat(embeds).ToArray(); + + Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); + Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); + Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed."); + + // check that user flag and user Id list are exclusive, same with role flag and role Id list + if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue) + { + if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users) && + allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0) + { + throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(allowedMentions)); + } + + if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles) && + allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0) + { + throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(allowedMentions)); + } + } + + var response = new API.InteractionResponse + { + Type = InteractionResponseType.ChannelMessageWithSource, + Data = new API.InteractionCallbackData + { + Content = text ?? Optional.Unspecified, + AllowedMentions = allowedMentions?.ToModel(), + Embeds = embeds.Select(x => x.ToModel()).ToArray(), + TTS = isTTS, + Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified + } + }; + + if (ephemeral) + response.Data.Value.Flags = MessageFlags.Ephemeral; + + lock (_lock) + { + if (_hasResponded) + { + throw new InvalidOperationException("Cannot respond, update, or defer twice to the same interaction"); + } + } + + lock (_lock) + { + _hasResponded = true; + } + + return SerializePayload(response); + } + + /// + /// Updates the message which this component resides in with the type + /// + /// A delegate containing the properties to modify the message with. + /// The request options for this request. + /// A string that contains json to write back to the incoming http request. + public string Update(Action func, RequestOptions options = null) + { + var args = new MessageProperties(); + func(args); + + if (!IsValidToken) + throw new InvalidOperationException("Interaction token is no longer valid"); + + if (!InteractionHelper.CanSendResponse(this)) + throw new TimeoutException($"Cannot respond to an interaction after {InteractionHelper.ResponseTimeLimit} seconds!"); + + if (args.AllowedMentions.IsSpecified) + { + var allowedMentions = args.AllowedMentions.Value; + Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions), "A max of 100 role Ids are allowed."); + Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions), "A max of 100 user Ids are allowed."); + } + + var embed = args.Embed; + var embeds = args.Embeds; + + bool hasText = args.Content.IsSpecified ? !string.IsNullOrEmpty(args.Content.Value) : !string.IsNullOrEmpty(Message.Content); + bool hasEmbeds = embed.IsSpecified && embed.Value != null || embeds.IsSpecified && embeds.Value?.Length > 0 || Message.Embeds.Any(); + + if (!hasText && !hasEmbeds) + Preconditions.NotNullOrEmpty(args.Content.IsSpecified ? args.Content.Value : string.Empty, nameof(args.Content)); + + var apiEmbeds = embed.IsSpecified || embeds.IsSpecified ? new List() : null; + + if (embed.IsSpecified && embed.Value != null) + { + apiEmbeds.Add(embed.Value.ToModel()); + } + + if (embeds.IsSpecified && embeds.Value != null) + { + apiEmbeds.AddRange(embeds.Value.Select(x => x.ToModel())); + } + + Preconditions.AtMost(apiEmbeds?.Count ?? 0, 10, nameof(args.Embeds), "A max of 10 embeds are allowed."); + + // check that user flag and user Id list are exclusive, same with role flag and role Id list + if (args.AllowedMentions.IsSpecified && args.AllowedMentions.Value != null && args.AllowedMentions.Value.AllowedTypes.HasValue) + { + var allowedMentions = args.AllowedMentions.Value; + if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users) + && allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0) + { + throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(args.AllowedMentions)); + } + + if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles) + && allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0) + { + throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(args.AllowedMentions)); + } + } + + var response = new API.InteractionResponse + { + Type = InteractionResponseType.UpdateMessage, + Data = new API.InteractionCallbackData + { + Content = args.Content, + AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional.Unspecified, + Embeds = apiEmbeds?.ToArray() ?? Optional.Unspecified, + Components = args.Components.IsSpecified + ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Array.Empty() + : Optional.Unspecified, + Flags = args.Flags.IsSpecified ? args.Flags.Value ?? Optional.Unspecified : Optional.Unspecified + } + }; + + lock (_lock) + { + if (_hasResponded) + { + throw new InvalidOperationException("Cannot respond, update, or defer twice to the same interaction"); + } + } + + lock (_lock) + { + _hasResponded = true; + } + + return SerializePayload(response); + } + + /// + /// Sends a followup message for this interaction. + /// + /// The text of the message to be sent. + /// A array of embeds to send with this response. Max 10. + /// if the message should be read out by a text-to-speech reader, otherwise . + /// if the response should be hidden to everyone besides the invoker of the command, otherwise . + /// The allowed mentions for this response. + /// The request options for this response. + /// A to be sent with this response. + /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. + /// + /// The sent message. + /// + public override async Task FollowupAsync( + string text = null, + Embed[] embeds = null, + bool isTTS = false, + bool ephemeral = false, + AllowedMentions allowedMentions = null, + RequestOptions options = null, + MessageComponent component = null, + Embed embed = null) + { + if (!IsValidToken) + throw new InvalidOperationException("Interaction token is no longer valid"); + + embeds ??= Array.Empty(); + if (embed != null) + embeds = new[] { embed }.Concat(embeds).ToArray(); + + Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); + Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); + Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed."); + + var args = new API.Rest.CreateWebhookMessageParams + { + Content = text, + AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, + IsTTS = isTTS, + Embeds = embeds.Select(x => x.ToModel()).ToArray(), + Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified + }; + + if (ephemeral) + args.Flags = MessageFlags.Ephemeral; + + return await InteractionHelper.SendFollowupAsync(Discord, args, Token, Message.Channel, options).ConfigureAwait(false); + } + + /// + public override async Task FollowupWithFileAsync( + Stream fileStream, + string fileName, + string text = null, + Embed[] embeds = null, + bool isTTS = false, + bool ephemeral = false, + AllowedMentions allowedMentions = null, + RequestOptions options = null, + MessageComponent component = null, + Embed embed = null) + { + if (!IsValidToken) + throw new InvalidOperationException("Interaction token is no longer valid"); + + embeds ??= Array.Empty(); + if (embed != null) + embeds = new[] { embed }.Concat(embeds).ToArray(); + + Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); + Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); + Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed."); + Preconditions.NotNull(fileStream, nameof(fileStream), "File Stream must have data"); + Preconditions.NotNullOrWhitespace(fileName, nameof(fileName), "File Name must not be empty or null"); + + var args = new API.Rest.CreateWebhookMessageParams + { + Content = text, + AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, + IsTTS = isTTS, + Embeds = embeds.Select(x => x.ToModel()).ToArray(), + Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified, + File = fileStream is not null ? new MultipartFile(fileStream, fileName) : Optional.Unspecified + }; + + if (ephemeral) + args.Flags = MessageFlags.Ephemeral; + + return await InteractionHelper.SendFollowupAsync(Discord, args, Token, Message.Channel, options).ConfigureAwait(false); + } + + /// + public override async Task FollowupWithFileAsync( + string filePath, + string text = null, + string fileName = null, + Embed[] embeds = null, + bool isTTS = false, + bool ephemeral = false, + AllowedMentions allowedMentions = null, + RequestOptions options = null, + MessageComponent component = null, + Embed embed = null) + { + if (!IsValidToken) + throw new InvalidOperationException("Interaction token is no longer valid"); + + embeds ??= Array.Empty(); + if (embed != null) + embeds = new[] { embed }.Concat(embeds).ToArray(); + + Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); + Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); + Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed."); + Preconditions.NotNullOrWhitespace(filePath, nameof(filePath), "Path must exist"); + + var args = new API.Rest.CreateWebhookMessageParams + { + Content = text, + AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, + IsTTS = isTTS, + Embeds = embeds.Select(x => x.ToModel()).ToArray(), + Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified, + File = !string.IsNullOrEmpty(filePath) ? new MultipartFile(new MemoryStream(File.ReadAllBytes(filePath), false), fileName) : Optional.Unspecified + }; + + if (ephemeral) + args.Flags = MessageFlags.Ephemeral; + + return await InteractionHelper.SendFollowupAsync(Discord, args, Token, Message.Channel, options).ConfigureAwait(false); + } + + /// + /// Defers an interaction and responds with type 5 () + /// + /// to send this message ephemerally, otherwise . + /// The request options for this request. + /// + /// A string that contains json to write back to the incoming http request. + /// + public string DeferLoading(bool ephemeral = false, RequestOptions options = null) + { + if (!InteractionHelper.CanSendResponse(this)) + throw new TimeoutException($"Cannot defer an interaction after {InteractionHelper.ResponseTimeLimit} seconds of no response/acknowledgement"); + + var response = new API.InteractionResponse + { + Type = InteractionResponseType.DeferredChannelMessageWithSource, + Data = ephemeral ? new API.InteractionCallbackData { Flags = MessageFlags.Ephemeral } : Optional.Unspecified + }; + + lock (_lock) + { + if (_hasResponded) + { + throw new InvalidOperationException("Cannot respond or defer twice to the same interaction"); + } + } + + lock (_lock) + { + _hasResponded = true; + } + + return SerializePayload(response); + } + + /// + /// + /// + /// + /// + /// + /// A string that contains json to write back to the incoming http request. + /// + /// + /// + public override string Defer(bool ephemeral = false, RequestOptions options = null) + { + if (!InteractionHelper.CanSendResponse(this)) + throw new TimeoutException($"Cannot defer an interaction after {InteractionHelper.ResponseTimeLimit} seconds of no response/acknowledgement"); + + var response = new API.InteractionResponse + { + Type = InteractionResponseType.DeferredUpdateMessage, + Data = ephemeral ? new API.InteractionCallbackData { Flags = MessageFlags.Ephemeral } : Optional.Unspecified + }; + + lock (_lock) + { + if (_hasResponded) + { + throw new InvalidOperationException("Cannot respond or defer twice to the same interaction"); + } + } + + lock (_lock) + { + _hasResponded = true; + } + + return SerializePayload(response); + } + + //IComponentInteraction + /// + IComponentInteractionData IComponentInteraction.Data => Data; + + /// + IUserMessage IComponentInteraction.Message => Message; + } +} diff --git a/src/Discord.Net.Rest/Entities/Interactions/MessageComponents/RestMessageComponentData.cs b/src/Discord.Net.Rest/Entities/Interactions/MessageComponents/RestMessageComponentData.cs new file mode 100644 index 000000000..e865c208c --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Interactions/MessageComponents/RestMessageComponentData.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Model = Discord.API.MessageComponentInteractionData; + +namespace Discord.Rest +{ + /// + /// Represents data for a . + /// + public class RestMessageComponentData : IComponentInteractionData, IDiscordInteractionData + { + /// + /// Gets the components Custom Id that was clicked. + /// + public string CustomId { get; } + + /// + /// Gets the type of the component clicked. + /// + public ComponentType Type { get; } + + /// + /// Gets the value(s) of a interaction response. + /// + public IReadOnlyCollection Values { get; } + + internal RestMessageComponentData(Model model) + { + CustomId = model.CustomId; + Type = model.ComponentType; + Values = model.Values.GetValueOrDefault(); + } + } +} diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommand.cs b/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommand.cs new file mode 100644 index 000000000..c3edaf6ff --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommand.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; +using Model = Discord.API.ApplicationCommand; + +namespace Discord.Rest +{ + /// + /// Represents a Rest-based implementation of the . + /// + public abstract class RestApplicationCommand : RestEntity, IApplicationCommand + { + /// + public ulong ApplicationId { get; private set; } + + /// + public ApplicationCommandType Type { get; private set; } + + /// + public string Name { get; private set; } + + /// + public string Description { get; private set; } + + /// + public bool IsDefaultPermission { get; private set; } + + /// + /// The options of this command. + /// + public IReadOnlyCollection Options { get; private set; } + + /// + public DateTimeOffset CreatedAt + => SnowflakeUtils.FromSnowflake(Id); + + internal RestApplicationCommand(BaseDiscordClient client, ulong id) + : base(client, id) { } + + internal static RestApplicationCommand Create(BaseDiscordClient client, Model model, ulong? guildId) + { + return guildId.HasValue + ? RestGuildCommand.Create(client, model, guildId.Value) + : RestGlobalCommand.Create(client, model); + } + + internal virtual void Update(Model model) + { + Type = model.Type; + ApplicationId = model.ApplicationId; + Name = model.Name; + Description = model.Description; + IsDefaultPermission = model.DefaultPermissions.GetValueOrDefault(true); + + Options = model.Options.IsSpecified + ? model.Options.Value.Select(RestApplicationCommandOption.Create).ToImmutableArray() + : ImmutableArray.Create(); + } + + /// + public abstract Task DeleteAsync(RequestOptions options = null); + + /// + public Task ModifyAsync(Action func, RequestOptions options = null) + { + return ModifyAsync(func, options); + } + + /// + public abstract Task ModifyAsync(Action func, RequestOptions options = null) + where TArg : ApplicationCommandProperties; + + IReadOnlyCollection IApplicationCommand.Options => Options; + } +} diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandChoice.cs b/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandChoice.cs new file mode 100644 index 000000000..a40491a2c --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandChoice.cs @@ -0,0 +1,22 @@ +using Model = Discord.API.ApplicationCommandOptionChoice; + +namespace Discord.Rest +{ + /// + /// Represents a Rest-based implementation of . + /// + public class RestApplicationCommandChoice : IApplicationCommandOptionChoice + { + /// + public string Name { get; } + + /// + public object Value { get; } + + internal RestApplicationCommandChoice(Model model) + { + Name = model.Name; + Value = model.Value; + } + } +} diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandOption.cs b/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandOption.cs new file mode 100644 index 000000000..d5c261e0b --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandOption.cs @@ -0,0 +1,98 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Model = Discord.API.ApplicationCommandOption; + +namespace Discord.Rest +{ + /// + /// Represents a Rest-based implementation of . + /// + public class RestApplicationCommandOption : IApplicationCommandOption + { + #region RestApplicationCommandOption + /// + public ApplicationCommandOptionType Type { get; private set; } + + /// + public string Name { get; private set; } + + /// + public string Description { get; private set; } + + /// + public bool? IsDefault { get; private set; } + + /// + public bool? IsRequired { get; private set; } + + /// + public double? MinValue { get; private set; } + + /// + public double? MaxValue { get; private set; } + + /// + /// A collection of 's for this command. + /// + public IReadOnlyCollection Choices { get; private set; } + + /// + /// A collection of 's for this command. + /// + public IReadOnlyCollection Options { get; private set; } + + /// + /// The allowed channel types for this option. + /// + public IReadOnlyCollection ChannelTypes { get; private set; } + + internal RestApplicationCommandOption() { } + + internal static RestApplicationCommandOption Create(Model model) + { + var options = new RestApplicationCommandOption(); + options.Update(model); + return options; + } + + internal void Update(Model model) + { + Type = model.Type; + Name = model.Name; + Description = model.Description; + + if (model.Default.IsSpecified) + IsDefault = model.Default.Value; + + if (model.Required.IsSpecified) + IsRequired = model.Required.Value; + + if (model.MinValue.IsSpecified) + MinValue = model.MinValue.Value; + + if (model.MaxValue.IsSpecified) + MaxValue = model.MaxValue.Value; + + Options = model.Options.IsSpecified + ? model.Options.Value.Select(Create).ToImmutableArray() + : ImmutableArray.Create(); + + Choices = model.Choices.IsSpecified + ? model.Choices.Value.Select(x => new RestApplicationCommandChoice(x)).ToImmutableArray() + : ImmutableArray.Create(); + + ChannelTypes = model.ChannelTypes.IsSpecified + ? model.ChannelTypes.Value.ToImmutableArray() + : ImmutableArray.Create(); + } + #endregion + + #region IApplicationCommandOption + IReadOnlyCollection IApplicationCommandOption.Options + => Options; + IReadOnlyCollection IApplicationCommandOption.Choices + => Choices; + #endregion + } +} diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestGlobalCommand.cs b/src/Discord.Net.Rest/Entities/Interactions/RestGlobalCommand.cs new file mode 100644 index 000000000..c319bcf34 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Interactions/RestGlobalCommand.cs @@ -0,0 +1,40 @@ +using System; +using System.Threading.Tasks; +using Model = Discord.API.ApplicationCommand; + +namespace Discord.Rest +{ + /// + /// Represents a Rest-based global application command. + /// + public class RestGlobalCommand : RestApplicationCommand + { + internal RestGlobalCommand(BaseDiscordClient client, ulong id) + : base(client, id) { } + + internal static RestGlobalCommand Create(BaseDiscordClient client, Model model) + { + var entity = new RestGlobalCommand(client, model.Id); + entity.Update(model); + return entity; + } + + /// + public override async Task DeleteAsync(RequestOptions options = null) + => await InteractionHelper.DeleteGlobalCommandAsync(Discord, this).ConfigureAwait(false); + + /// + /// Modifies this . + /// + /// The delegate containing the properties to modify the command with. + /// The options to be used when sending the request. + /// + /// The modified command. + /// + public override async Task ModifyAsync(Action func, RequestOptions options = null) + { + var cmd = await InteractionHelper.ModifyGlobalCommandAsync(Discord, this, func, options).ConfigureAwait(false); + Update(cmd); + } + } +} diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestGuildCommand.cs b/src/Discord.Net.Rest/Entities/Interactions/RestGuildCommand.cs new file mode 100644 index 000000000..00804e57e --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Interactions/RestGuildCommand.cs @@ -0,0 +1,83 @@ +using System; +using System.Threading.Tasks; +using Model = Discord.API.ApplicationCommand; + +namespace Discord.Rest +{ + /// + /// Represents a Rest-based guild application command. + /// + public class RestGuildCommand : RestApplicationCommand + { + /// + /// The guild Id where this command originates. + /// + public ulong GuildId { get; private set; } + + internal RestGuildCommand(BaseDiscordClient client, ulong id, ulong guildId) + : base(client, id) + { + GuildId = guildId; + } + + internal static RestGuildCommand Create(BaseDiscordClient client, Model model, ulong guildId) + { + var entity = new RestGuildCommand(client, model.Id, guildId); + entity.Update(model); + return entity; + } + + /// + public override async Task DeleteAsync(RequestOptions options = null) + => await InteractionHelper.DeleteGuildCommandAsync(Discord, GuildId, this).ConfigureAwait(false); + + /// + /// Modifies this . + /// + /// The delegate containing the properties to modify the command with. + /// The options to be used when sending the request. + /// + /// The modified command + /// + public override async Task ModifyAsync(Action func, RequestOptions options = null) + { + var model = await InteractionHelper.ModifyGuildCommandAsync(Discord, this, GuildId, func, options).ConfigureAwait(false); + Update(model); + } + + /// + /// Gets this commands permissions inside of the current guild. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a + /// object defining the permissions of the current slash command. + /// + public Task GetCommandPermission(RequestOptions options = null) + => InteractionHelper.GetGuildCommandPermissionAsync(Discord, GuildId, Id, options); + + /// + /// Modifies the current command permissions for this guild command. + /// + /// The permissions to overwrite. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous modification operation. The task result contains a + /// object containing the modified permissions. + /// + public Task ModifyCommandPermissions(ApplicationCommandPermission[] permissions, RequestOptions options = null) + => InteractionHelper.ModifyGuildCommandPermissionsAsync(Discord, GuildId, Id, permissions, options); + + /// + /// Gets the guild that this slash command resides in. + /// + /// if you want the approximate member and presence counts for the guild, otherwise . + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a + /// . + /// + public Task GetGuild(bool withCounts = false, RequestOptions options = null) + => ClientHelper.GetGuildAsync(Discord, GuildId, withCounts, options); + } +} diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestInteraction.cs b/src/Discord.Net.Rest/Entities/Interactions/RestInteraction.cs new file mode 100644 index 000000000..103c43ffb --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Interactions/RestInteraction.cs @@ -0,0 +1,224 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Model = Discord.API.Interaction; +using DataModel = Discord.API.ApplicationCommandInteractionData; +using Newtonsoft.Json; + +namespace Discord.Rest +{ + /// + /// Represents a REST-based interaction. + /// + public abstract class RestInteraction : RestEntity, IDiscordInteraction + { + /// + public InteractionType Type { get; private set; } + + /// + public IDiscordInteractionData Data { get; private set; } + + /// + public string Token { get; private set; } + + /// + public int Version { get; private set; } + + /// + /// Gets the user who invoked the interaction. + /// + public RestUser User { get; private set; } + + /// + public DateTimeOffset CreatedAt { get; private set; } + + internal abstract bool _hasResponded { get; set; } + + /// + /// if the token is valid for replying to, otherwise . + /// + public bool IsValidToken + => InteractionHelper.CanRespondOrFollowup(this); + + /// + /// Gets the channel that this interaction was executed in. + /// + public IRestMessageChannel Channel { get; private set; } + + /// + /// Gets the guild this interaction was executed in. + /// + public RestGuild Guild { get; private set; } + + internal RestInteraction(BaseDiscordClient discord, ulong id) + : base(discord, id) + { + CreatedAt = discord.UseInteractionSnowflakeDate + ? SnowflakeUtils.FromSnowflake(Id) + : DateTime.UtcNow; + } + + internal static async Task CreateAsync(DiscordRestClient client, Model model) + { + if(model.Type == InteractionType.Ping) + { + return await RestPingInteraction.CreateAsync(client, model); + } + + if (model.Type == InteractionType.ApplicationCommand) + { + var dataModel = model.Data.IsSpecified + ? (DataModel)model.Data.Value + : null; + + if (dataModel == null) + return null; + + return dataModel.Type switch + { + ApplicationCommandType.Slash => await RestSlashCommand.CreateAsync(client, model).ConfigureAwait(false), + ApplicationCommandType.Message => await RestMessageCommand.CreateAsync(client, model).ConfigureAwait(false), + ApplicationCommandType.User => await RestUserCommand.CreateAsync(client, model).ConfigureAwait(false), + _ => null + }; + } + + if (model.Type == InteractionType.MessageComponent) + return await RestMessageComponent.CreateAsync(client, model).ConfigureAwait(false); + + if (model.Type == InteractionType.ApplicationCommandAutocomplete) + return await RestAutocompleteInteraction.CreateAsync(client, model).ConfigureAwait(false); + + return null; + } + + internal virtual async Task UpdateAsync(DiscordRestClient discord, Model model) + { + Data = model.Data.IsSpecified + ? model.Data.Value + : null; + Token = model.Token; + Version = model.Version; + Type = model.Type; + + if(Guild == null && model.GuildId.IsSpecified) + { + Guild = await discord.GetGuildAsync(model.GuildId.Value); + } + + if (User == null) + { + if (model.Member.IsSpecified && model.GuildId.IsSpecified) + { + User = RestGuildUser.Create(Discord, Guild, model.Member.Value); + } + else + { + User = RestUser.Create(Discord, model.User.Value); + } + } + + if(Channel == null && model.ChannelId.IsSpecified) + { + Channel = (IRestMessageChannel)await discord.GetChannelAsync(model.ChannelId.Value); + } + } + + internal string SerializePayload(object payload) + { + var json = new StringBuilder(); + using (var text = new StringWriter(json)) + using (var writer = new JsonTextWriter(text)) + DiscordRestClient.Serializer.Serialize(writer, payload); + + return json.ToString(); + } + + /// + public abstract string Defer(bool ephemeral = false, RequestOptions options = null); + /// + public abstract Task FollowupAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null); + /// + /// Gets the original response for this interaction. + /// + /// The request options for this request. + /// A that represents the initial response. + public Task GetOriginalResponseAsync(RequestOptions options = null) + => InteractionHelper.GetOriginalResponseAsync(Discord, Channel, this, options); + + /// + /// Edits original response for this interaction. + /// + /// A delegate containing the properties to modify the message with. + /// The request options for this request. + /// A that represents the initial response. + public async Task ModifyOriginalResponseAsync(Action func, RequestOptions options = null) + { + var model = await InteractionHelper.ModifyInteractionResponseAsync(Discord, Token, func, options); + return RestInteractionMessage.Create(Discord, model, Token, Channel); + } + /// + public abstract string Respond(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null); + /// + /// Sends a followup message for this interaction. + /// + /// The text of the message to be sent. + /// The file to upload. + /// The file name of the attachment. + /// A array of embeds to send with this response. Max 10. + /// if the message should be read out by a text-to-speech reader, otherwise . + /// if the response should be hidden to everyone besides the invoker of the command, otherwise . + /// The allowed mentions for this response. + /// The request options for this response. + /// A to be sent with this response. + /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. + /// + /// The sent message. + /// + public abstract Task FollowupWithFileAsync(Stream fileStream, string fileName, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, + AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null); + /// + /// Sends a followup message for this interaction. + /// + /// The text of the message to be sent. + /// The file to upload. + /// The file name of the attachment. + /// A array of embeds to send with this response. Max 10. + /// if the message should be read out by a text-to-speech reader, otherwise . + /// if the response should be hidden to everyone besides the invoker of the command, otherwise . + /// The allowed mentions for this response. + /// The request options for this response. + /// A to be sent with this response. + /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. + /// + /// The sent message. + /// + public abstract Task FollowupWithFileAsync(string filePath, string text = null, string fileName = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, + AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null); + + #region IDiscordInteraction + /// + Task IDiscordInteraction.RespondAsync(string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, RequestOptions options, MessageComponent component, Embed embed) + => Task.FromResult(Respond(text, embeds, isTTS, ephemeral, allowedMentions, options, component, embed)); + + Task IDiscordInteraction.DeferAsync(bool ephemeral, RequestOptions options) + => Task.FromResult(Defer(ephemeral, options)); + + /// + async Task IDiscordInteraction.FollowupAsync(string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, + RequestOptions options, MessageComponent component, Embed embed) + => await FollowupAsync(text, embeds, isTTS, ephemeral, allowedMentions, options, component, embed).ConfigureAwait(false); + + /// + async Task IDiscordInteraction.GetOriginalResponseAsync(RequestOptions options) + => await GetOriginalResponseAsync(options).ConfigureAwait(false); + + /// + async Task IDiscordInteraction.ModifyOriginalResponseAsync(Action func, RequestOptions options) + => await ModifyOriginalResponseAsync(func, options).ConfigureAwait(false); + #endregion + } +} diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestPingInteraction.cs b/src/Discord.Net.Rest/Entities/Interactions/RestPingInteraction.cs new file mode 100644 index 000000000..f979a4df2 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Interactions/RestPingInteraction.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Model = Discord.API.Interaction; + +namespace Discord.Rest +{ + /// + /// Represents a REST-based ping interaction. + /// + public class RestPingInteraction : RestInteraction, IDiscordInteraction + { + internal override bool _hasResponded { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + + internal RestPingInteraction(BaseDiscordClient client, ulong id) + : base(client, id) + { + } + + internal static new async Task CreateAsync(DiscordRestClient client, Model model) + { + var entity = new RestPingInteraction(client, model.Id); + await entity.UpdateAsync(client, model); + return entity; + } + + public string AcknowledgePing() + { + var model = new API.InteractionResponse() + { + Type = InteractionResponseType.Pong + }; + + return SerializePayload(model); + } + + public override string Defer(bool ephemeral = false, RequestOptions options = null) => throw new NotSupportedException(); + public override Task FollowupAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null) => throw new NotSupportedException(); + public override Task FollowupWithFileAsync(Stream fileStream, string fileName, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null) => throw new NotSupportedException(); + public override Task FollowupWithFileAsync(string filePath, string text = null, string fileName = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null) => throw new NotSupportedException(); + public override string Respond(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null) => throw new NotSupportedException(); + } +} diff --git a/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestAutocompleteInteraction.cs b/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestAutocompleteInteraction.cs new file mode 100644 index 000000000..3b879cd4e --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestAutocompleteInteraction.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Model = Discord.API.Interaction; +using DataModel = Discord.API.AutocompleteInteractionData; +using System.IO; + +namespace Discord.Rest +{ + /// + /// Represents a REST-based autocomplete interaction. + /// + public class RestAutocompleteInteraction : RestInteraction, IAutocompleteInteraction, IDiscordInteraction + { + /// + /// Gets the autocomplete data of this interaction. + /// + public new RestAutocompleteInteractionData Data { get; } + + internal override bool _hasResponded { get; set; } + private object _lock = new object(); + + internal RestAutocompleteInteraction(DiscordRestClient client, Model model) + : base(client, model.Id) + { + var dataModel = model.Data.IsSpecified + ? (DataModel)model.Data.Value + : null; + + if (dataModel != null) + Data = new RestAutocompleteInteractionData(dataModel); + } + + internal new static async Task CreateAsync(DiscordRestClient client, Model model) + { + var entity = new RestAutocompleteInteraction(client, model); + await entity.UpdateAsync(client, model).ConfigureAwait(false); + return entity; + } + + /// + /// Responds to this interaction with a set of choices. + /// + /// + /// The set of choices for the user to pick from. + /// + /// A max of 20 choices are allowed. Passing for this argument will show the executing user that + /// there is no choices for their autocompleted input. + /// + /// + /// The request options for this response. + /// + /// A string that contains json to write back to the incoming http request. + /// + public string Respond(IEnumerable result, RequestOptions options = null) + { + if (!InteractionHelper.CanSendResponse(this)) + throw new TimeoutException($"Cannot respond to an interaction after {InteractionHelper.ResponseTimeLimit} seconds!"); + + lock (_lock) + { + if (_hasResponded) + { + throw new InvalidOperationException("Cannot respond twice to the same interaction"); + } + } + + lock (_lock) + { + _hasResponded = true; + } + + var model = new API.InteractionResponse + { + Type = InteractionResponseType.ApplicationCommandAutocompleteResult, + Data = new API.InteractionCallbackData + { + Choices = result.Any() + ? result.Select(x => new API.ApplicationCommandOptionChoice { Name = x.Name, Value = x.Value }).ToArray() + : Array.Empty() + } + }; + + return SerializePayload(model); + } + + /// + /// Responds to this interaction with a set of choices. + /// + /// The request options for this response. + /// + /// The set of choices for the user to pick from. + /// + /// A max of 20 choices are allowed. Passing for this argument will show the executing user that + /// there is no choices for their autocompleted input. + /// + /// + /// + /// A string that contains json to write back to the incoming http request. + /// + public string Respond(RequestOptions options = null, params AutocompleteResult[] result) + => Respond(result, options); + + /// + [Obsolete("Autocomplete interactions cannot be deferred!", true)] + public override string Defer(bool ephemeral = false, RequestOptions options = null) + => throw new NotSupportedException("Autocomplete interactions cannot be deferred!"); + + /// + [Obsolete("Autocomplete interactions cannot have followups!", true)] + public override Task FollowupAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null) + => throw new NotSupportedException("Autocomplete interactions cannot be deferred!"); + + /// + [Obsolete("Autocomplete interactions cannot have followups!", true)] + public override Task FollowupWithFileAsync(Stream fileStream, string fileName, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null) + => throw new NotSupportedException("Autocomplete interactions cannot be deferred!"); + + /// + [Obsolete("Autocomplete interactions cannot have followups!", true)] + public override Task FollowupWithFileAsync(string filePath, string text = null, string fileName = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null) + => throw new NotSupportedException("Autocomplete interactions cannot be deferred!"); + + /// + [Obsolete("Autocomplete interactions cannot have normal responses!", true)] + public override string Respond(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null) + => throw new NotSupportedException("Autocomplete interactions cannot be deferred!"); + + //IAutocompleteInteraction + /// + IAutocompleteInteractionData IAutocompleteInteraction.Data => Data; + } +} diff --git a/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestAutocompleteInteractionData.cs b/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestAutocompleteInteractionData.cs new file mode 100644 index 000000000..135eb88ea --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestAutocompleteInteractionData.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using DataModel = Discord.API.AutocompleteInteractionData; + +namespace Discord.Rest +{ + /// + /// Represents the data for a . + /// + public class RestAutocompleteInteractionData : IAutocompleteInteractionData + { + /// + /// Gets the name of the invoked command. + /// + public string CommandName { get; } + + /// + /// Gets the id of the invoked command. + /// + public ulong CommandId { get; } + + /// + /// Gets the type of the invoked command. + /// + public ApplicationCommandType Type { get; } + + /// + /// Gets the version of the invoked command. + /// + public ulong Version { get; } + + /// + /// Gets the current autocomplete option that is actively being filled out. + /// + public AutocompleteOption Current { get; } + + /// + /// Gets a collection of all the other options the executing users has filled out. + /// + public IReadOnlyCollection Options { get; } + + internal RestAutocompleteInteractionData(DataModel model) + { + var options = model.Options.SelectMany(GetOptions); + + Current = options.FirstOrDefault(x => x.Focused); + Options = options.ToImmutableArray(); + + if (Options.Count == 1 && Current == null) + Current = Options.FirstOrDefault(); + + CommandName = model.Name; + CommandId = model.Id; + Type = model.Type; + Version = model.Version; + } + + private List GetOptions(API.AutocompleteInteractionDataOption model) + { + var options = new List(); + + options.Add(new AutocompleteOption(model.Type, model.Name, model.Value.GetValueOrDefault(null), model.Focused.GetValueOrDefault(false))); + + if (model.Options.IsSpecified) + { + options.AddRange(model.Options.Value.SelectMany(GetOptions)); + } + + return options; + } + } +} diff --git a/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestSlashCommand.cs b/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestSlashCommand.cs new file mode 100644 index 000000000..785e39a12 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestSlashCommand.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using DataModel = Discord.API.ApplicationCommandInteractionData; +using Model = Discord.API.Interaction; + +namespace Discord.Rest +{ + /// + /// Represents a REST-based slash command. + /// + public class RestSlashCommand : RestCommandBase, ISlashCommandInteraction, IDiscordInteraction + { + /// + /// Gets the data associated with this interaction. + /// + public new RestSlashCommandData Data { get; private set; } + + internal RestSlashCommand(DiscordRestClient client, Model model) + : base(client, model) + { + } + + internal new static async Task CreateAsync(DiscordRestClient client, Model model) + { + var entity = new RestSlashCommand(client, model); + await entity.UpdateAsync(client, model).ConfigureAwait(false); + return entity; + } + + internal override async Task UpdateAsync(DiscordRestClient client, Model model) + { + await base.UpdateAsync(client, model).ConfigureAwait(false); + + var dataModel = model.Data.IsSpecified + ? (DataModel)model.Data.Value + : null; + + Data = await RestSlashCommandData.CreateAsync(client, dataModel, Guild, Channel).ConfigureAwait(false); + } + + //ISlashCommandInteraction + /// + IApplicationCommandInteractionData ISlashCommandInteraction.Data => Data; + } +} diff --git a/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestSlashCommandData.cs b/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestSlashCommandData.cs new file mode 100644 index 000000000..f967cc628 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestSlashCommandData.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Model = Discord.API.ApplicationCommandInteractionData; + +namespace Discord.Rest +{ + + public class RestSlashCommandData : RestCommandBaseData, IDiscordInteractionData + { + internal RestSlashCommandData(DiscordRestClient client, Model model) + : base(client, model) { } + + internal static new async Task CreateAsync(DiscordRestClient client, Model model, RestGuild guild, IRestMessageChannel channel) + { + var entity = new RestSlashCommandData(client, model); + await entity.UpdateAsync(client, model, guild, channel).ConfigureAwait(false); + return entity; + } + internal override async Task UpdateAsync(DiscordRestClient client, Model model, RestGuild guild, IRestMessageChannel channel) + { + await base.UpdateAsync(client, model, guild, channel).ConfigureAwait(false); + + Options = model.Options.IsSpecified + ? model.Options.Value.Select(x => new RestSlashCommandDataOption(this, x)).ToImmutableArray() + : ImmutableArray.Create(); + } + } +} diff --git a/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestSlashCommandDataOption.cs b/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestSlashCommandDataOption.cs new file mode 100644 index 000000000..bb931f68e --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestSlashCommandDataOption.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Model = Discord.API.ApplicationCommandInteractionDataOption; + + +namespace Discord.Rest +{ + /// + /// Represents a REST-based option for a slash command. + /// + public class RestSlashCommandDataOption : IApplicationCommandInteractionDataOption + { + #region RestSlashCommandDataOption + /// + public string Name { get; private set; } + + /// + public object Value { get; private set; } + + /// + public ApplicationCommandOptionType Type { get; private set; } + + /// + /// Gets a collection of sub command options received for this sub command group. + /// + public IReadOnlyCollection Options { get; private set; } + + internal RestSlashCommandDataOption() { } + internal RestSlashCommandDataOption(RestSlashCommandData data, Model model) + { + Name = model.Name; + Type = model.Type; + + if (model.Value.IsSpecified) + { + switch (Type) + { + case ApplicationCommandOptionType.User: + case ApplicationCommandOptionType.Role: + case ApplicationCommandOptionType.Channel: + case ApplicationCommandOptionType.Mentionable: + if (ulong.TryParse($"{model.Value.Value}", out var valueId)) + { + switch (Type) + { + case ApplicationCommandOptionType.User: + { + var guildUser = data.ResolvableData.GuildMembers.FirstOrDefault(x => x.Key == valueId).Value; + + if (guildUser != null) + Value = guildUser; + else + Value = data.ResolvableData.Users.FirstOrDefault(x => x.Key == valueId).Value; + } + break; + case ApplicationCommandOptionType.Channel: + Value = data.ResolvableData.Channels.FirstOrDefault(x => x.Key == valueId).Value; + break; + case ApplicationCommandOptionType.Role: + Value = data.ResolvableData.Roles.FirstOrDefault(x => x.Key == valueId).Value; + break; + case ApplicationCommandOptionType.Mentionable: + { + if (data.ResolvableData.GuildMembers.Any(x => x.Key == valueId) || data.ResolvableData.Users.Any(x => x.Key == valueId)) + { + var guildUser = data.ResolvableData.GuildMembers.FirstOrDefault(x => x.Key == valueId).Value; + + if (guildUser != null) + Value = guildUser; + else + Value = data.ResolvableData.Users.FirstOrDefault(x => x.Key == valueId).Value; + } + else if (data.ResolvableData.Roles.Any(x => x.Key == valueId)) + { + Value = data.ResolvableData.Roles.FirstOrDefault(x => x.Key == valueId).Value; + } + } + break; + default: + Value = model.Value.Value; + break; + } + } + break; + case ApplicationCommandOptionType.String: + Value = model.Value.ToString(); + break; + case ApplicationCommandOptionType.Integer: + { + if (model.Value.Value is long val) + Value = val; + else if (long.TryParse(model.Value.Value.ToString(), out long res)) + Value = res; + } + break; + case ApplicationCommandOptionType.Boolean: + { + if (model.Value.Value is bool val) + Value = val; + else if (bool.TryParse(model.Value.Value.ToString(), out bool res)) + Value = res; + } + break; + case ApplicationCommandOptionType.Number: + { + if (model.Value.Value is int val) + Value = val; + else if (double.TryParse(model.Value.Value.ToString(), out double res)) + Value = res; + } + break; + } + } + + Options = model.Options.IsSpecified + ? model.Options.Value.Select(x => new RestSlashCommandDataOption(data, x)).ToImmutableArray() + : ImmutableArray.Create(); + } + #endregion + + #region Converters + public static explicit operator bool(RestSlashCommandDataOption option) + => (bool)option.Value; + public static explicit operator int(RestSlashCommandDataOption option) + => (int)option.Value; + public static explicit operator string(RestSlashCommandDataOption option) + => option.Value.ToString(); + #endregion + + #region IApplicationCommandInteractionDataOption + IReadOnlyCollection IApplicationCommandInteractionDataOption.Options + => Options; + #endregion + } +} diff --git a/src/Discord.Net.Rest/Entities/Messages/Attachment.cs b/src/Discord.Net.Rest/Entities/Messages/Attachment.cs index 1cd73518a..4e4849c51 100644 --- a/src/Discord.Net.Rest/Entities/Messages/Attachment.cs +++ b/src/Discord.Net.Rest/Entities/Messages/Attachment.cs @@ -21,8 +21,10 @@ namespace Discord public int? Height { get; } /// public int? Width { get; } + /// + public bool Ephemeral { get; } - internal Attachment(ulong id, string filename, string url, string proxyUrl, int size, int? height, int? width) + internal Attachment(ulong id, string filename, string url, string proxyUrl, int size, int? height, int? width, bool? ephemeral) { Id = id; Filename = filename; @@ -31,12 +33,14 @@ namespace Discord Size = size; Height = height; Width = width; + Ephemeral = ephemeral.GetValueOrDefault(false); } internal static Attachment Create(Model model) { return new Attachment(model.Id, model.Filename, model.Url, model.ProxyUrl, model.Size, model.Height.IsSpecified ? model.Height.Value : (int?)null, - model.Width.IsSpecified ? model.Width.Value : (int?)null); + model.Width.IsSpecified ? model.Width.Value : (int?)null, + model.Ephemeral.ToNullable()); } /// diff --git a/src/Discord.Net.Rest/Entities/Messages/CustomSticker.cs b/src/Discord.Net.Rest/Entities/Messages/CustomSticker.cs new file mode 100644 index 000000000..6fd0f7700 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Messages/CustomSticker.cs @@ -0,0 +1,74 @@ +using System; +using System.Diagnostics; +using System.Threading.Tasks; +using Model = Discord.API.Sticker; + +namespace Discord.Rest +{ + /// + /// Represents a Rest-based custom sticker within a guild. + /// + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public class CustomSticker : Sticker, ICustomSticker + { + /// + /// Gets the users id who uploaded the sticker. + /// + /// + /// In order to get the author id, the bot needs the MANAGE_EMOJIS_AND_STICKERS permission. + /// + public ulong? AuthorId { get; private set; } + + /// + /// Gets the guild that this custom sticker is in. + /// + /// + /// Note: This property can be if the sticker wasn't fetched from a guild. + /// + public RestGuild Guild { get; private set; } + + private ulong GuildId { get; set; } + + internal CustomSticker(BaseDiscordClient client, ulong id, RestGuild guild, ulong? authorId = null) + : base(client, id) + { + AuthorId = authorId; + Guild = guild; + } + internal CustomSticker(BaseDiscordClient client, ulong id, ulong guildId, ulong? authorId = null) + : base(client, id) + { + AuthorId = authorId; + GuildId = guildId; + } + + internal static CustomSticker Create(BaseDiscordClient client, Model model, RestGuild guild, ulong? authorId = null) + { + var entity = new CustomSticker(client, model.Id, guild, authorId); + entity.Update(model); + return entity; + } + + internal static CustomSticker Create(BaseDiscordClient client, Model model, ulong guildId, ulong? authorId = null) + { + var entity = new CustomSticker(client, model.Id, guildId, authorId); + entity.Update(model); + return entity; + } + + /// + public Task DeleteAsync(RequestOptions options = null) + => GuildHelper.DeleteStickerAsync(Discord, GuildId, this, options); + + /// + public async Task ModifyAsync(Action func, RequestOptions options = null) + { + var model = await GuildHelper.ModifyStickerAsync(Discord, GuildId, this, func, options); + Update(model); + } + + private string DebuggerDisplay => Guild != null ? $"{Name} in {Guild.Name} ({Id})" : $"{Name} ({Id})"; + + IGuild ICustomSticker.Guild => Guild; + } +} diff --git a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs index 31252466b..309500c96 100644 --- a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs +++ b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs @@ -1,3 +1,4 @@ +using Discord.API; using Discord.API.Rest; using System; using System.Collections.Generic; @@ -24,18 +25,25 @@ namespace Discord.Rest /// Only the author of a message may modify the message. /// Message content is too long, length must be less or equal to . - public static async Task ModifyAsync(IMessage msg, BaseDiscordClient client, Action func, + public static Task ModifyAsync(IMessage msg, BaseDiscordClient client, Action func, + RequestOptions options) + => ModifyAsync(msg.Channel.Id, msg.Id, client, func, options); + + public static async Task ModifyAsync(ulong channelId, ulong msgId, BaseDiscordClient client, Action func, RequestOptions options) { var args = new MessageProperties(); func(args); - if (msg.Author.Id != client.CurrentUser.Id && (args.Content.IsSpecified || args.Embed.IsSpecified || args.AllowedMentions.IsSpecified)) - throw new InvalidOperationException("Only the author of a message may modify the message content, embed, or allowed mentions."); + var embed = args.Embed; + var embeds = args.Embeds; - bool hasText = args.Content.IsSpecified ? !string.IsNullOrEmpty(args.Content.Value) : !string.IsNullOrEmpty(msg.Content); - bool hasEmbed = args.Embed.IsSpecified ? args.Embed.Value != null : msg.Embeds.Any(); - if (!hasText && !hasEmbed) + bool hasText = args.Content.IsSpecified && string.IsNullOrEmpty(args.Content.Value); + bool hasEmbeds = embed.IsSpecified && embed.Value != null || embeds.IsSpecified && embeds.Value?.Length > 0; + bool hasComponents = args.Components.IsSpecified && args.Components.Value != null; + bool hasAttachments = args.Attachments.IsSpecified; + + if (!hasComponents && !hasText && !hasEmbeds && !hasAttachments) Preconditions.NotNullOrEmpty(args.Content.IsSpecified ? args.Content.Value : string.Empty, nameof(args.Content)); if (args.AllowedMentions.IsSpecified) @@ -43,6 +51,7 @@ namespace Discord.Rest AllowedMentions allowedMentions = args.AllowedMentions.Value; Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); + Preconditions.AtMost(args.Embeds.Value?.Length ?? 0, 10, nameof(args.Embeds), "A max of 10 embeds are allowed."); // check that user flag and user Id list are exclusive, same with role flag and role Id list if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue) @@ -61,56 +70,45 @@ namespace Discord.Rest } } - var apiArgs = new API.Rest.ModifyMessageParams + var apiEmbeds = embed.IsSpecified || embeds.IsSpecified ? new List() : null; + + if (embed.IsSpecified && embed.Value != null) { - Content = args.Content, - Embed = args.Embed.IsSpecified ? args.Embed.Value.ToModel() : Optional.Create(), - Flags = args.Flags.IsSpecified ? args.Flags.Value : Optional.Create(), - AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value.ToModel() : Optional.Create(), - }; - return await client.ApiClient.ModifyMessageAsync(msg.Channel.Id, msg.Id, apiArgs, options).ConfigureAwait(false); - } + apiEmbeds.Add(embed.Value.ToModel()); + } - public static async Task ModifyAsync(ulong channelId, ulong msgId, BaseDiscordClient client, Action func, - RequestOptions options) - { - var args = new MessageProperties(); - func(args); + if (embeds.IsSpecified && embeds.Value != null) + { + apiEmbeds.AddRange(embeds.Value.Select(x => x.ToModel())); + } - if ((args.Content.IsSpecified && string.IsNullOrEmpty(args.Content.Value)) && (args.Embed.IsSpecified && args.Embed.Value == null)) - Preconditions.NotNullOrEmpty(args.Content.IsSpecified ? args.Content.Value : string.Empty, nameof(args.Content)); + Preconditions.AtMost(apiEmbeds?.Count ?? 0, 10, nameof(args.Embeds), "A max of 10 embeds are allowed."); - if (args.AllowedMentions.IsSpecified) + if(!args.Attachments.IsSpecified) { - AllowedMentions allowedMentions = args.AllowedMentions.Value; - Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); - Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); - - // check that user flag and user Id list are exclusive, same with role flag and role Id list - if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue) + var apiArgs = new API.Rest.ModifyMessageParams { - if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users) && - allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0) - { - throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(allowedMentions)); - } - - if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles) && - allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0) - { - throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(allowedMentions)); - } - } + Content = args.Content, + Embeds = apiEmbeds?.ToArray() ?? Optional.Unspecified, + Flags = args.Flags.IsSpecified ? args.Flags.Value : Optional.Create(), + AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value.ToModel() : Optional.Create(), + Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Array.Empty() : Optional.Unspecified, + }; + return await client.ApiClient.ModifyMessageAsync(channelId, msgId, apiArgs, options).ConfigureAwait(false); } - - var apiArgs = new API.Rest.ModifyMessageParams + else { - Content = args.Content, - Embed = args.Embed.IsSpecified ? args.Embed.Value.ToModel() : Optional.Create(), - Flags = args.Flags.IsSpecified ? args.Flags.Value : Optional.Create(), - AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value.ToModel() : Optional.Create(), - }; - return await client.ApiClient.ModifyMessageAsync(channelId, msgId, apiArgs, options).ConfigureAwait(false); + var apiArgs = new UploadFileParams(args.Attachments.Value.ToArray()) + { + Content = args.Content, + Embeds = apiEmbeds?.ToArray() ?? Optional.Unspecified, + Flags = args.Flags.IsSpecified ? args.Flags.Value : Optional.Create(), + AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value.ToModel() : Optional.Create(), + MessageComponent = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Array.Empty() : Optional.Unspecified + }; + + return await client.ApiClient.ModifyMessageAsync(channelId, msgId, apiArgs, options).ConfigureAwait(false); + } } public static Task DeleteAsync(IMessage msg, BaseDiscordClient client, RequestOptions options) @@ -203,6 +201,12 @@ namespace Discord.Rest return System.Web.HttpUtility.UrlEncode(text); #endif } + public static string SanitizeMessage(IMessage message) + { + var newContent = MentionUtils.Resolve(message, 0, TagHandling.FullName, TagHandling.FullName, TagHandling.FullName, TagHandling.FullName, TagHandling.FullName); + newContent = Format.StripMarkDown(newContent); + return newContent; + } public static async Task PinAsync(IMessage msg, BaseDiscordClient client, RequestOptions options) @@ -301,7 +305,7 @@ namespace Discord.Rest tags.Add(new Tag(TagType.Emoji, index, content.Length, emoji.Id, emoji)); else //Bad Tag { - index = index + 1; + index++; continue; } index = endIndex + 1; diff --git a/src/Discord.Net.Rest/Entities/Messages/RestFollowupMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestFollowupMessage.cs new file mode 100644 index 000000000..693d36e56 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Messages/RestFollowupMessage.cs @@ -0,0 +1,78 @@ +using System; +using System.Threading.Tasks; +using Model = Discord.API.Message; + +namespace Discord.Rest +{ + /// + /// Represents a REST-based follow up message sent by a bot responding to a slash command. + /// + public class RestFollowupMessage : RestUserMessage + { + // Token used to delete/modify this followup message + internal string Token { get; } + + internal RestFollowupMessage(BaseDiscordClient discord, ulong id, IUser author, string token, IMessageChannel channel) + : base(discord, id, channel, author, MessageSource.Bot) + { + Token = token; + } + + internal static RestFollowupMessage Create(BaseDiscordClient discord, Model model, string token, IMessageChannel channel) + { + var entity = new RestFollowupMessage(discord, model.Id, model.Author.IsSpecified ? RestUser.Create(discord, model.Author.Value) : discord.CurrentUser, token, channel); + entity.Update(model); + return entity; + } + + internal new void Update(Model model) + { + base.Update(model); + } + + /// + /// Deletes this object and all of it's children. + /// + /// A task that represents the asynchronous delete operation. + public Task DeleteAsync() + => InteractionHelper.DeleteFollowupMessageAsync(Discord, this); + + /// + /// Modifies this interaction followup message. + /// + /// + /// This method modifies this message with the specified properties. To see an example of this + /// method and what properties are available, please refer to . + /// + /// + /// The following example replaces the content of the message with Hello World!. + /// + /// await msg.ModifyAsync(x => x.Content = "Hello World!"); + /// + /// + /// A delegate containing the properties to modify the message with. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous modification operation. + /// + /// The token used to modify/delete this message expired. + /// /// Something went wrong during the request. + public new async Task ModifyAsync(Action func, RequestOptions options = null) + { + try + { + var model = await InteractionHelper.ModifyFollowupMessageAsync(Discord, this, func, options).ConfigureAwait(false); + Update(model); + } + catch (Net.HttpException x) + { + if (x.HttpCode == System.Net.HttpStatusCode.NotFound) + { + throw new InvalidOperationException("The token of this message has expired!", x); + } + + throw; + } + } + } +} diff --git a/src/Discord.Net.Rest/Entities/Messages/RestInteractionMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestInteractionMessage.cs new file mode 100644 index 000000000..26beb03b6 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Messages/RestInteractionMessage.cs @@ -0,0 +1,78 @@ +using System; +using System.Threading.Tasks; +using Model = Discord.API.Message; + +namespace Discord.Rest +{ + /// + /// Represents the initial REST-based response to a slash command. + /// + public class RestInteractionMessage : RestUserMessage + { + // Token used to delete/modify this followup message + internal string Token { get; } + + internal RestInteractionMessage(BaseDiscordClient discord, ulong id, IUser author, string token, IMessageChannel channel) + : base(discord, id, channel, author, MessageSource.Bot) + { + Token = token; + } + + internal static RestInteractionMessage Create(BaseDiscordClient discord, Model model, string token, IMessageChannel channel) + { + var entity = new RestInteractionMessage(discord, model.Id, model.Author.IsSpecified ? RestUser.Create(discord, model.Author.Value) : discord.CurrentUser, token, channel); + entity.Update(model); + return entity; + } + + internal new void Update(Model model) + { + base.Update(model); + } + + /// + /// Deletes this object and all of it's children. + /// + /// A task that represents the asynchronous delete operation. + public Task DeleteAsync() + => InteractionHelper.DeleteInteractionResponseAsync(Discord, this); + + /// + /// Modifies this interaction response + /// + /// + /// This method modifies this message with the specified properties. To see an example of this + /// method and what properties are available, please refer to . + /// + /// + /// The following example replaces the content of the message with Hello World!. + /// + /// await msg.ModifyAsync(x => x.Content = "Hello World!"); + /// + /// + /// A delegate containing the properties to modify the message with. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous modification operation. + /// + /// The token used to modify/delete this message expired. + /// /// Something went wrong during the request. + public new async Task ModifyAsync(Action func, RequestOptions options = null) + { + try + { + var model = await InteractionHelper.ModifyInteractionResponseAsync(Discord, Token, func, options).ConfigureAwait(false); + Update(model); + } + catch (Net.HttpException x) + { + if (x.HttpCode == System.Net.HttpStatusCode.NotFound) + { + throw new InvalidOperationException("The token of this message has expired!", x); + } + + throw; + } + } + } +} diff --git a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs index 0c54743a6..c48a60aac 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs @@ -1,3 +1,4 @@ +using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -14,6 +15,7 @@ namespace Discord.Rest { private long _timestampTicks; private ImmutableArray _reactions = ImmutableArray.Create(); + private ImmutableArray _userMentions = ImmutableArray.Create(); /// public IMessageChannel Channel { get; } @@ -28,6 +30,9 @@ namespace Discord.Rest public string Content { get; private set; } /// + public string CleanContent => MessageHelper.SanitizeMessage(this); + + /// public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); /// public virtual bool IsTTS => false; @@ -52,14 +57,10 @@ namespace Discord.Rest public virtual IReadOnlyCollection MentionedChannelIds => ImmutableArray.Create(); /// public virtual IReadOnlyCollection MentionedRoleIds => ImmutableArray.Create(); - /// - /// Gets a collection of the mentioned users in the message. - /// - public virtual IReadOnlyCollection MentionedUsers => ImmutableArray.Create(); /// public virtual IReadOnlyCollection Tags => ImmutableArray.Create(); /// - public virtual IReadOnlyCollection Stickers => ImmutableArray.Create(); + public virtual IReadOnlyCollection Stickers => ImmutableArray.Create(); /// public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks); @@ -69,11 +70,23 @@ namespace Discord.Rest public MessageApplication Application { get; private set; } /// public MessageReference Reference { get; private set; } + + /// + /// Gets the interaction this message is a response to. + /// + public MessageInteraction Interaction { get; private set; } /// public MessageFlags? Flags { get; private set; } /// public MessageType Type { get; private set; } + /// + public IReadOnlyCollection Components { get; private set; } + /// + /// Gets a collection of the mentioned users in the message. + /// + public IReadOnlyCollection MentionedUsers => _userMentions; + internal RestMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, IUser author, MessageSource source) : base(discord, id) { @@ -83,7 +96,10 @@ namespace Discord.Rest } internal static RestMessage Create(BaseDiscordClient discord, IMessageChannel channel, IUser author, Model model) { - if (model.Type == MessageType.Default || model.Type == MessageType.Reply) + if (model.Type == MessageType.Default || + model.Type == MessageType.Reply || + model.Type == MessageType.ApplicationCommand || + model.Type == MessageType.ThreadStarterMessage) return RestUserMessage.Create(discord, channel, author, model); else return RestSystemMessage.Create(discord, channel, author, model); @@ -121,7 +137,7 @@ namespace Discord.Rest }; } - if(model.Reference.IsSpecified) + if (model.Reference.IsSpecified) { // Creates a new Reference from the API model Reference = new MessageReference @@ -132,6 +148,56 @@ namespace Discord.Rest }; } + if (model.Components.IsSpecified) + { + Components = model.Components.Value.Select(x => new ActionRowComponent(x.Components.Select(y => + { + switch (y.Type) + { + case ComponentType.Button: + { + var parsed = (API.ButtonComponent)y; + return new Discord.ButtonComponent( + parsed.Style, + parsed.Label.GetValueOrDefault(), + parsed.Emote.IsSpecified + ? parsed.Emote.Value.Id.HasValue + ? new Emote(parsed.Emote.Value.Id.Value, parsed.Emote.Value.Name, parsed.Emote.Value.Animated.GetValueOrDefault()) + : new Emoji(parsed.Emote.Value.Name) + : null, + parsed.CustomId.GetValueOrDefault(), + parsed.Url.GetValueOrDefault(), + parsed.Disabled.GetValueOrDefault()); + } + case ComponentType.SelectMenu: + { + var parsed = (API.SelectMenuComponent)y; + return new SelectMenuComponent( + parsed.CustomId, + parsed.Options.Select(z => new SelectMenuOption( + z.Label, + z.Value, + z.Description.GetValueOrDefault(), + z.Emoji.IsSpecified + ? z.Emoji.Value.Id.HasValue + ? new Emote(z.Emoji.Value.Id.Value, z.Emoji.Value.Name, z.Emoji.Value.Animated.GetValueOrDefault()) + : new Emoji(z.Emoji.Value.Name) + : null, + z.Default.ToNullable())).ToList(), + parsed.Placeholder.GetValueOrDefault(), + parsed.MinValues, + parsed.MaxValues, + parsed.Disabled + ); + } + default: + return null; + } + }).ToList())).ToImmutableArray(); + } + else + Components = new List(); + if (model.Flags.IsSpecified) Flags = model.Flags.Value; @@ -150,8 +216,31 @@ namespace Discord.Rest } else _reactions = ImmutableArray.Create(); - } + if (model.Interaction.IsSpecified) + { + Interaction = new MessageInteraction(model.Interaction.Value.Id, + model.Interaction.Value.Type, + model.Interaction.Value.Name, + RestUser.Create(Discord, model.Interaction.Value.User)); + } + + if (model.UserMentions.IsSpecified) + { + var value = model.UserMentions.Value; + if (value.Length > 0) + { + var newMentions = ImmutableArray.CreateBuilder(value.Length); + for (int i = 0; i < value.Length; i++) + { + var val = value[i]; + if (val != null) + newMentions.Add(RestUser.Create(Discord, val)); + } + _userMentions = newMentions.ToImmutable(); + } + } + } /// public async Task UpdateAsync(RequestOptions options = null) { @@ -177,8 +266,15 @@ namespace Discord.Rest IReadOnlyCollection IMessage.Embeds => Embeds; /// IReadOnlyCollection IMessage.MentionedUserIds => MentionedUsers.Select(x => x.Id).ToImmutableArray(); + + /// + IReadOnlyCollection IMessage.Components => Components; + + /// + IMessageInteraction IMessage.Interaction => Interaction; + /// - IReadOnlyCollection IMessage.Stickers => Stickers; + IReadOnlyCollection IMessage.Stickers => Stickers; /// public IReadOnlyDictionary Reactions => _reactions.ToDictionary(x => x.Emote, x => new ReactionMetadata { ReactionCount = x.Count, IsMe = x.Me }); diff --git a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs index aa6b44da6..083a8e72c 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs @@ -20,8 +20,7 @@ namespace Discord.Rest private ImmutableArray _embeds = ImmutableArray.Create(); private ImmutableArray _tags = ImmutableArray.Create(); private ImmutableArray _roleMentionIds = ImmutableArray.Create(); - private ImmutableArray _userMentions = ImmutableArray.Create(); - private ImmutableArray _stickers = ImmutableArray.Create(); + private ImmutableArray _stickers = ImmutableArray.Create(); /// public override bool IsTTS => _isTTS; @@ -42,11 +41,9 @@ namespace Discord.Rest /// public override IReadOnlyCollection MentionedRoleIds => _roleMentionIds; /// - public override IReadOnlyCollection MentionedUsers => _userMentions; - /// public override IReadOnlyCollection Tags => _tags; /// - public override IReadOnlyCollection Stickers => _stickers; + public override IReadOnlyCollection Stickers => _stickers; /// public IUserMessage ReferencedMessage => _referencedMessage; @@ -104,28 +101,12 @@ namespace Discord.Rest _embeds = ImmutableArray.Create(); } - if (model.UserMentions.IsSpecified) - { - var value = model.UserMentions.Value; - if (value.Length > 0) - { - var newMentions = ImmutableArray.CreateBuilder(value.Length); - for (int i = 0; i < value.Length; i++) - { - var val = value[i]; - if (val.Object != null) - newMentions.Add(RestUser.Create(Discord, val.Object)); - } - _userMentions = newMentions.ToImmutable(); - } - } - var guildId = (Channel as IGuildChannel)?.GuildId; var guild = guildId != null ? (Discord as IDiscordClient).GetGuildAsync(guildId.Value, CacheMode.CacheOnly).Result : null; if (model.Content.IsSpecified) { var text = model.Content.Value; - _tags = MessageHelper.ParseTags(text, null, guild, _userMentions); + _tags = MessageHelper.ParseTags(text, null, guild, MentionedUsers); model.Content = text; } @@ -136,18 +117,18 @@ namespace Discord.Rest _referencedMessage = RestUserMessage.Create(Discord, Channel, refMsgAuthor, refMsg); } - if (model.Stickers.IsSpecified) + if (model.StickerItems.IsSpecified) { - var value = model.Stickers.Value; + var value = model.StickerItems.Value; if (value.Length > 0) { - var stickers = ImmutableArray.CreateBuilder(value.Length); + var stickers = ImmutableArray.CreateBuilder(value.Length); for (int i = 0; i < value.Length; i++) - stickers.Add(Sticker.Create(value[i])); + stickers.Add(new StickerItem(Discord, value[i])); _stickers = stickers.ToImmutable(); } else - _stickers = ImmutableArray.Create(); + _stickers = ImmutableArray.Create(); } } diff --git a/src/Discord.Net.Rest/Entities/Messages/Sticker.cs b/src/Discord.Net.Rest/Entities/Messages/Sticker.cs index 5482bed74..accdbe66a 100644 --- a/src/Discord.Net.Rest/Entities/Messages/Sticker.cs +++ b/src/Discord.Net.Rest/Entities/Messages/Sticker.cs @@ -1,46 +1,55 @@ +using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using Model = Discord.API.Sticker; -namespace Discord +namespace Discord.Rest { /// [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public class Sticker : ISticker + public class Sticker : RestEntity, ISticker { /// - public ulong Id { get; } + public ulong PackId { get; protected set; } /// - public ulong PackId { get; } + public string Name { get; protected set; } /// - public string Name { get; } + public string Description { get; protected set; } /// - public string Description { get; } + public IReadOnlyCollection Tags { get; protected set; } /// - public IReadOnlyCollection Tags { get; } + public StickerType Type { get; protected set; } /// - public string Asset { get; } + public bool? IsAvailable { get; protected set; } /// - public string PreviewAsset { get; } + public int? SortOrder { get; protected set; } /// - public StickerFormatType FormatType { get; } + public StickerFormatType Format { get; protected set; } - internal Sticker(ulong id, ulong packId, string name, string description, string[] tags, string asset, string previewAsset, StickerFormatType formatType) + /// + public string GetStickerUrl() + => CDN.GetStickerUrl(Id, Format); + + internal Sticker(BaseDiscordClient client, ulong id) + : base(client, id) { } + internal static Sticker Create(BaseDiscordClient client, Model model) { - Id = id; - PackId = packId; - Name = name; - Description = description; - Tags = tags.ToReadOnlyCollection(); - Asset = asset; - PreviewAsset = previewAsset; - FormatType = formatType; + var entity = new Sticker(client, model.Id); + entity.Update(model); + return entity; } - internal static Sticker Create(Model model) + + internal void Update(Model model) { - return new Sticker(model.Id, model.PackId, model.Name, model.Desription, - model.Tags.IsSpecified ? model.Tags.Value.Split(',') : new string[0], - model.Asset, model.PreviewAsset, model.FormatType); + PackId = model.PackId; + Name = model.Name; + Description = model.Description; + Tags = model.Tags.IsSpecified ? model.Tags.Value.Split(',').Select(x => x.Trim()).ToArray() : Array.Empty(); + Type = model.Type; + SortOrder = model.SortValue; + IsAvailable = model.Available; + Format = model.FormatType; } private string DebuggerDisplay => $"{Name} ({Id})"; diff --git a/src/Discord.Net.Rest/Entities/Messages/StickerItem.cs b/src/Discord.Net.Rest/Entities/Messages/StickerItem.cs new file mode 100644 index 000000000..0ce4f634b --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Messages/StickerItem.cs @@ -0,0 +1,39 @@ +using System.Threading.Tasks; +using Model = Discord.API.StickerItem; + +namespace Discord.Rest +{ + /// + /// Represents a partial sticker received in a message. + /// + public class StickerItem : RestEntity, IStickerItem + { + /// + public string Name { get; } + + /// + public StickerFormatType Format { get; } + + internal StickerItem(BaseDiscordClient client, Model model) + : base(client, model.Id) + { + Name = model.Name; + Format = model.FormatType; + } + + /// + /// Resolves this sticker item by fetching the from the API. + /// + /// + /// A task representing the download operation, the result of the task is a sticker object. + /// + public async Task ResolveStickerAsync() + { + var model = await Discord.ApiClient.GetStickerAsync(Id); + + return model.GuildId.IsSpecified + ? CustomSticker.Create(Discord, model, model.GuildId.Value, model.User.IsSpecified ? model.User.Value.Id : null) + : Sticker.Create(Discord, model); + } + } +} diff --git a/src/Discord.Net.Rest/Entities/RestApplication.cs b/src/Discord.Net.Rest/Entities/RestApplication.cs index 5c2f872cf..beec52433 100644 --- a/src/Discord.Net.Rest/Entities/RestApplication.cs +++ b/src/Discord.Net.Rest/Entities/RestApplication.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics; using System.Threading.Tasks; using Model = Discord.API.Application; @@ -18,9 +20,9 @@ namespace Discord.Rest /// public string Description { get; private set; } /// - public string[] RPCOrigins { get; private set; } + public IReadOnlyCollection RPCOrigins { get; private set; } /// - public ulong Flags { get; private set; } + public ApplicationFlags Flags { get; private set; } /// public bool IsBotPublic { get; private set; } /// @@ -36,6 +38,10 @@ namespace Discord.Rest /// public string IconUrl => CDN.GetApplicationIconUrl(Id, _iconId); + public ApplicationInstallParams InstallParams { get; private set; } + + public IReadOnlyCollection Tags { get; private set; } + internal RestApplication(BaseDiscordClient discord, ulong id) : base(discord, id) { @@ -49,14 +55,17 @@ namespace Discord.Rest internal void Update(Model model) { Description = model.Description; - RPCOrigins = model.RPCOrigins; + RPCOrigins = model.RPCOrigins.IsSpecified ? model.RPCOrigins.Value.ToImmutableArray() : ImmutableArray.Empty; Name = model.Name; _iconId = model.Icon; IsBotPublic = model.IsBotPublic; BotRequiresCodeGrant = model.BotRequiresCodeGrant; + Tags = model.Tags.GetValueOrDefault(null)?.ToImmutableArray() ?? ImmutableArray.Empty; + var installParams = model.InstallParams.GetValueOrDefault(null); + InstallParams = new ApplicationInstallParams(installParams?.Scopes ?? new string[0], (GuildPermission?)installParams?.Permission ?? null); if (model.Flags.IsSpecified) - Flags = model.Flags.Value; //TODO: Do we still need this? + Flags = model.Flags.Value; if (model.Owner.IsSpecified) Owner = RestUser.Create(Discord, model.Owner.Value); if (model.Team != null) diff --git a/src/Discord.Net.Rest/Entities/Roles/RestRole.cs b/src/Discord.Net.Rest/Entities/Roles/RestRole.cs index aa33ae7e5..a2ad4fd77 100644 --- a/src/Discord.Net.Rest/Entities/Roles/RestRole.cs +++ b/src/Discord.Net.Rest/Entities/Roles/RestRole.cs @@ -11,6 +11,7 @@ namespace Discord.Rest [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class RestRole : RestEntity, IRole { + #region RestRole internal IGuild Guild { get; } /// public Color Color { get; private set; } @@ -23,6 +24,10 @@ namespace Discord.Rest /// public string Name { get; private set; } /// + public string Icon { get; private set; } + /// /> + public Emoji Emoji { get; private set; } + /// public GuildPermissions Permissions { get; private set; } /// public int Position { get; private set; } @@ -60,11 +65,21 @@ namespace Discord.Rest Permissions = new GuildPermissions(model.Permissions); if (model.Tags.IsSpecified) Tags = model.Tags.Value.ToEntity(); + + if (model.Icon.IsSpecified) + { + Icon = model.Icon.Value; + } + + if (model.Emoji.IsSpecified) + { + Emoji = new Emoji(model.Emoji.Value); + } } /// public async Task ModifyAsync(Action func, RequestOptions options = null) - { + { var model = await RoleHelper.ModifyAsync(this, Discord, func, options).ConfigureAwait(false); Update(model); } @@ -73,6 +88,10 @@ namespace Discord.Rest => RoleHelper.DeleteAsync(this, Discord, options); /// + public string GetIconUrl() + => CDN.GetGuildRoleIconUrl(Id, Icon); + + /// public int CompareTo(IRole role) => RoleUtils.Compare(this, role); /// @@ -83,8 +102,9 @@ namespace Discord.Rest /// public override string ToString() => Name; private string DebuggerDisplay => $"{Name} ({Id})"; + #endregion - //IRole + #region IRole /// IGuild IRole.Guild { @@ -95,5 +115,6 @@ namespace Discord.Rest throw new InvalidOperationException("Unable to return this entity's parent unless it was fetched through that object."); } } + #endregion } } diff --git a/src/Discord.Net.Rest/Entities/Roles/RoleHelper.cs b/src/Discord.Net.Rest/Entities/Roles/RoleHelper.cs index 73ab7ca31..d8552f869 100644 --- a/src/Discord.Net.Rest/Entities/Roles/RoleHelper.cs +++ b/src/Discord.Net.Rest/Entities/Roles/RoleHelper.cs @@ -7,7 +7,7 @@ namespace Discord.Rest { internal static class RoleHelper { - //General + #region General public static async Task DeleteAsync(IRole role, BaseDiscordClient client, RequestOptions options) { @@ -18,13 +18,20 @@ namespace Discord.Rest { var args = new RoleProperties(); func(args); + + if (args.Icon.IsSpecified) + { + role.Guild.Features.EnsureFeature(GuildFeature.RoleIcons); + } + var apiArgs = new API.Rest.ModifyGuildRoleParams { Color = args.Color.IsSpecified ? args.Color.Value.RawValue : Optional.Create(), Hoist = args.Hoist, Mentionable = args.Mentionable, Name = args.Name, - Permissions = args.Permissions.IsSpecified ? args.Permissions.Value.RawValue.ToString() : Optional.Create() + Permissions = args.Permissions.IsSpecified ? args.Permissions.Value.RawValue.ToString() : Optional.Create(), + Icon = args.Icon.IsSpecified ? args.Icon.Value.ToModel() : Optional.Unspecified }; var model = await client.ApiClient.ModifyGuildRoleAsync(role.Guild.Id, role.Id, apiArgs, options).ConfigureAwait(false); @@ -36,5 +43,6 @@ namespace Discord.Rest } return model; } + #endregion } } diff --git a/src/Discord.Net.Rest/Entities/Users/RestGroupUser.cs b/src/Discord.Net.Rest/Entities/Users/RestGroupUser.cs index 55e9843eb..40e45b135 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestGroupUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestGroupUser.cs @@ -1,3 +1,4 @@ +using System; using System.Diagnostics; using Model = Discord.API.User; @@ -9,6 +10,7 @@ namespace Discord.Rest [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class RestGroupUser : RestUser, IGroupUser { + #region RestGroupUser internal RestGroupUser(BaseDiscordClient discord, ulong id) : base(discord, id) { @@ -19,8 +21,9 @@ namespace Discord.Rest entity.Update(model); return entity; } +#endregion - //IVoiceState + #region IVoiceState /// bool IVoiceState.IsDeafened => false; /// @@ -37,5 +40,8 @@ namespace Discord.Rest string IVoiceState.VoiceSessionId => null; /// bool IVoiceState.IsStreaming => false; + /// + DateTimeOffset? IVoiceState.RequestToSpeakTimestamp => null; + #endregion } } diff --git a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs index 6e6bbe09c..2e184d32e 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs @@ -14,12 +14,15 @@ namespace Discord.Rest [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class RestGuildUser : RestUser, IGuildUser { + #region RestGuildUser private long? _premiumSinceTicks; private long? _joinedAtTicks; private ImmutableArray _roleIds; /// public string Nickname { get; private set; } + /// + public string GuildAvatarId { get; private set; } internal IGuild Guild { get; private set; } /// public bool IsDeafened { get; private set; } @@ -31,6 +34,18 @@ namespace Discord.Rest public ulong GuildId => Guild.Id; /// public bool? IsPending { get; private set; } + /// + public int Hierarchy + { + get + { + if (Guild.OwnerId == Id) + return int.MaxValue; + + var orderedRoles = Guild.Roles.OrderByDescending(x => x.Position); + return orderedRoles.Where(x => RoleIds.Contains(x.Id)).Max(x => x.Position); + } + } /// /// Resolving permissions requires the parent guild to be downloaded. @@ -67,6 +82,8 @@ namespace Discord.Rest _joinedAtTicks = model.JoinedAt.Value.UtcTicks; if (model.Nick.IsSpecified) Nickname = model.Nick.Value; + if (model.Avatar.IsSpecified) + GuildAvatarId = model.Avatar.Value; if (model.Deaf.IsSpecified) IsDeafened = model.Deaf.Value; if (model.Mute.IsSpecified) @@ -144,7 +161,11 @@ namespace Discord.Rest return new ChannelPermissions(Permissions.ResolveChannel(Guild, this, channel, guildPerms.RawValue)); } - //IGuildUser + public string GetGuildAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) + => CDN.GetGuildUserAvatarUrl(Id, GuildId, GuildAvatarId, size, format); +#endregion + + #region IGuildUser /// IGuild IGuildUser.Guild { @@ -155,8 +176,9 @@ namespace Discord.Rest throw new InvalidOperationException("Unable to return this entity's parent unless it was fetched through that object."); } } + #endregion - //IVoiceState + #region IVoiceState /// bool IVoiceState.IsSelfDeafened => false; /// @@ -169,5 +191,8 @@ namespace Discord.Rest string IVoiceState.VoiceSessionId => null; /// bool IVoiceState.IsStreaming => false; + /// + DateTimeOffset? IVoiceState.RequestToSpeakTimestamp => null; + #endregion } } diff --git a/src/Discord.Net.Rest/Entities/Users/RestThreadUser.cs b/src/Discord.Net.Rest/Entities/Users/RestThreadUser.cs new file mode 100644 index 000000000..82830dafd --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Users/RestThreadUser.cs @@ -0,0 +1,56 @@ +using System; +using System.Threading.Tasks; +using Model = Discord.API.ThreadMember; + +namespace Discord.Rest +{ + /// + /// Represents a thread user received over the REST api. + /// + public class RestThreadUser : RestEntity + { + /// + /// Gets the this user is in. + /// + public IThreadChannel Thread { get; } + + /// + /// Gets the timestamp for when this user joined this thread. + /// + public DateTimeOffset JoinedAt { get; private set; } + + /// + /// Gets the guild this user is in. + /// + public IGuild Guild { get; } + + internal RestThreadUser(BaseDiscordClient discord, IGuild guild, IThreadChannel channel, ulong id) + : base(discord, id) + { + Guild = guild; + Thread = channel; + } + + internal static RestThreadUser Create(BaseDiscordClient client, IGuild guild, Model model, IThreadChannel channel) + { + var entity = new RestThreadUser(client, guild, channel, model.UserId.Value); + entity.Update(model); + return entity; + } + + internal void Update(Model model) + { + JoinedAt = model.JoinTimestamp; + } + + /// + /// Gets the guild user for this thread user. + /// + /// + /// A task representing the asynchronous get operation. The task returns a + /// that represents the current thread user. + /// + public Task GetGuildUser() + => Guild.GetUserAsync(Id); + } +} diff --git a/src/Discord.Net.Rest/Entities/Users/RestUser.cs b/src/Discord.Net.Rest/Entities/Users/RestUser.cs index 7bc1447fe..872bab392 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestUser.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Globalization; using System.Threading.Tasks; using Model = Discord.API.User; +using EventUserModel = Discord.API.GuildScheduledEventUser; namespace Discord.Rest { @@ -13,6 +14,7 @@ namespace Discord.Rest [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class RestUser : RestEntity, IUser, IUpdateable { + #region RestUser /// public bool IsBot { get; private set; } /// @@ -22,6 +24,10 @@ namespace Discord.Rest /// public string AvatarId { get; private set; } /// + public string BannerId { get; private set; } + /// + public Color? AccentColor { get; private set; } + /// public UserProperties? PublicFlags { get; private set; } /// @@ -57,10 +63,26 @@ namespace Discord.Rest entity.Update(model); return entity; } + internal static RestUser Create(BaseDiscordClient discord, IGuild guild, EventUserModel model) + { + if (model.Member.IsSpecified) + { + var member = model.Member.Value; + member.User = model.User; + return RestGuildUser.Create(discord, guild, member); + } + else + return RestUser.Create(discord, model.User); + } + internal virtual void Update(Model model) { if (model.Avatar.IsSpecified) AvatarId = model.Avatar.Value; + if (model.Banner.IsSpecified) + BannerId = model.Banner.Value; + if (model.AccentColor.IsSpecified) + AccentColor = model.AccentColor.Value; if (model.Discriminator.IsSpecified) DiscriminatorValue = ushort.Parse(model.Discriminator.Value, NumberStyles.None, CultureInfo.InvariantCulture); if (model.Bot.IsSpecified) @@ -93,6 +115,10 @@ namespace Discord.Rest => CDN.GetUserAvatarUrl(Id, AvatarId, size, format); /// + public string GetBannerUrl(ImageFormat format = ImageFormat.Auto, ushort size = 256) + => CDN.GetUserBannerUrl(Id, BannerId, size, format); + + /// public string GetDefaultAvatarUrl() => CDN.GetDefaultUserAvatarUrl(DiscriminatorValue); @@ -104,10 +130,12 @@ namespace Discord.Rest /// public override string ToString() => $"{Username}#{Discriminator}"; private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")})"; + #endregion - //IUser + #region IUser /// async Task IUser.CreateDMChannelAsync(RequestOptions options) => await CreateDMChannelAsync(options).ConfigureAwait(false); + #endregion } } diff --git a/src/Discord.Net.Rest/Entities/Users/RestWebhookUser.cs b/src/Discord.Net.Rest/Entities/Users/RestWebhookUser.cs index 2131fec93..2cd19da41 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestWebhookUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestWebhookUser.cs @@ -10,6 +10,7 @@ namespace Discord.Rest [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class RestWebhookUser : RestUser, IWebhookUser { + #region RestWebhookUser /// public ulong WebhookId { get; } internal IGuild Guild { get; } @@ -33,8 +34,9 @@ namespace Discord.Rest entity.Update(model); return entity; } +#endregion - //IGuildUser + #region IGuildUser /// IGuild IGuildUser.Guild { @@ -52,8 +54,14 @@ namespace Discord.Rest /// string IGuildUser.Nickname => null; /// + string IGuildUser.GuildAvatarId => null; + /// + string IGuildUser.GetGuildAvatarUrl(ImageFormat format, ushort size) => null; + /// bool? IGuildUser.IsPending => null; /// + int IGuildUser.Hierarchy => 0; + /// GuildPermissions IGuildUser.GuildPermissions => GuildPermissions.Webhook; /// @@ -89,8 +97,9 @@ namespace Discord.Rest /// Task IGuildUser.RemoveRolesAsync(IEnumerable roles, RequestOptions options) => throw new NotSupportedException("Roles are not supported on webhook users."); + #endregion - //IVoiceState + #region IVoiceState /// bool IVoiceState.IsDeafened => false; /// @@ -107,5 +116,8 @@ namespace Discord.Rest string IVoiceState.VoiceSessionId => null; /// bool IVoiceState.IsStreaming => false; + /// + DateTimeOffset? IVoiceState.RequestToSpeakTimestamp => null; + #endregion } } diff --git a/src/Discord.Net.Rest/Entities/Webhooks/RestWebhook.cs b/src/Discord.Net.Rest/Entities/Webhooks/RestWebhook.cs index 9baddf003..f40b786cd 100644 --- a/src/Discord.Net.Rest/Entities/Webhooks/RestWebhook.cs +++ b/src/Discord.Net.Rest/Entities/Webhooks/RestWebhook.cs @@ -8,6 +8,7 @@ namespace Discord.Rest [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class RestWebhook : RestEntity, IWebhook, IUpdateable { + #region RestWebhook internal IGuild Guild { get; private set; } internal ITextChannel Channel { get; private set; } @@ -24,6 +25,8 @@ namespace Discord.Rest public ulong? GuildId { get; private set; } /// public IUser Creator { get; private set; } + /// + public ulong? ApplicationId { get; private set; } /// public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); @@ -66,6 +69,8 @@ namespace Discord.Rest GuildId = model.GuildId.Value; if (model.Name.IsSpecified) Name = model.Name.Value; + + ApplicationId = model.ApplicationId; } /// @@ -91,8 +96,9 @@ namespace Discord.Rest public override string ToString() => $"Webhook: {Name}:{Id}"; private string DebuggerDisplay => $"Webhook: {Name} ({Id})"; + #endregion - //IWebhook + #region IWebhook /// IGuild IWebhook.Guild => Guild ?? throw new InvalidOperationException("Unable to return this entity's parent unless it was fetched through that object."); @@ -102,5 +108,6 @@ namespace Discord.Rest /// Task IWebhook.ModifyAsync(Action func, RequestOptions options) => ModifyAsync(func, options); + #endregion } } diff --git a/src/Discord.Net.Rest/Entities/Webhooks/WebhookHelper.cs b/src/Discord.Net.Rest/Entities/Webhooks/WebhookHelper.cs index 50e9cab78..0b61b6c22 100644 --- a/src/Discord.Net.Rest/Entities/Webhooks/WebhookHelper.cs +++ b/src/Discord.Net.Rest/Entities/Webhooks/WebhookHelper.cs @@ -33,6 +33,5 @@ namespace Discord.Rest { await client.ApiClient.DeleteWebhookAsync(webhook.Id, options).ConfigureAwait(false); } - } } diff --git a/src/Discord.Net.Rest/Extensions/EntityExtensions.cs b/src/Discord.Net.Rest/Extensions/EntityExtensions.cs index f8676c783..61fe330df 100644 --- a/src/Discord.Net.Rest/Extensions/EntityExtensions.cs +++ b/src/Discord.Net.Rest/Extensions/EntityExtensions.cs @@ -39,7 +39,7 @@ namespace Discord.Rest return new RoleTags( model.BotId.IsSpecified ? model.BotId.Value : null, model.IntegrationId.IsSpecified ? model.IntegrationId.Value : null, - model.IsPremiumSubscriber.IsSpecified ? true : false); + model.IsPremiumSubscriber.GetValueOrDefault(false) ?? false); } public static API.Embed ToModel(this Embed entity) { @@ -68,6 +68,7 @@ namespace Discord.Rest model.Video = entity.Video.Value.ToModel(); return model; } + public static API.AllowedMentions ToModel(this AllowedMentions entity) { return new API.AllowedMentions() diff --git a/src/Discord.Net.Rest/Net/BadSignatureException.cs b/src/Discord.Net.Rest/Net/BadSignatureException.cs new file mode 100644 index 000000000..08672df8e --- /dev/null +++ b/src/Discord.Net.Rest/Net/BadSignatureException.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.Rest +{ + public class BadSignatureException : Exception + { + internal BadSignatureException() : base("Failed to verify authenticity of message: public key doesnt match signature") + { + + } + } +} diff --git a/src/Discord.Net.Rest/Net/Converters/ArrayConverter.cs b/src/Discord.Net.Rest/Net/Converters/ArrayConverter.cs index 3cededb7b..ce2e9b1f7 100644 --- a/src/Discord.Net.Rest/Net/Converters/ArrayConverter.cs +++ b/src/Discord.Net.Rest/Net/Converters/ArrayConverter.cs @@ -1,4 +1,4 @@ -using Newtonsoft.Json; +using Newtonsoft.Json; using System; using System.Collections.Generic; diff --git a/src/Discord.Net.Rest/Net/Converters/DiscordContractResolver.cs b/src/Discord.Net.Rest/Net/Converters/DiscordContractResolver.cs index 931c0c4c9..91ba22460 100644 --- a/src/Discord.Net.Rest/Net/Converters/DiscordContractResolver.cs +++ b/src/Discord.Net.Rest/Net/Converters/DiscordContractResolver.cs @@ -10,9 +10,10 @@ namespace Discord.Net.Converters { internal class DiscordContractResolver : DefaultContractResolver { + #region DiscordContractResolver private static readonly TypeInfo _ienumerable = typeof(IEnumerable).GetTypeInfo(); - private static readonly MethodInfo _shouldSerialize = typeof(DiscordContractResolver).GetTypeInfo().GetDeclaredMethod("ShouldSerialize"); - + private static readonly MethodInfo _shouldSerialize = typeof(DiscordContractResolver).GetTypeInfo().GetDeclaredMethod("ShouldSerialize"); + protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) { var property = base.CreateProperty(member, memberSerialization); @@ -57,8 +58,9 @@ namespace Discord.Net.Converters else if (genericType == typeof(EntityOrId<>)) return MakeGenericConverter(property, propInfo, typeof(UInt64EntityOrIdConverter<>), type.GenericTypeArguments[0], depth); } + #endregion - //Primitives + #region Primitives bool hasInt53 = propInfo.GetCustomAttribute() != null; if (!hasInt53) { @@ -81,6 +83,14 @@ namespace Discord.Net.Converters //Special if (type == typeof(API.Image)) return ImageConverter.Instance; + if (typeof(IMessageComponent).IsAssignableFrom(type)) + return MessageComponentConverter.Instance; + if (type == typeof(API.Interaction)) + return InteractionConverter.Instance; + if (type == typeof(API.DiscordError)) + return DiscordErrorConverter.Instance; + if (type == typeof(GuildFeatures)) + return GuildFeaturesConverter.Instance; //Entities var typeInfo = type.GetTypeInfo(); @@ -103,5 +113,6 @@ namespace Discord.Net.Converters var innerConverter = GetConverter(property, propInfo, innerType, depth + 1); return genericType.DeclaredConstructors.First().Invoke(new object[] { innerConverter }) as JsonConverter; } + #endregion } } diff --git a/src/Discord.Net.Rest/Net/Converters/DiscordErrorConverter.cs b/src/Discord.Net.Rest/Net/Converters/DiscordErrorConverter.cs new file mode 100644 index 000000000..772ddc6b2 --- /dev/null +++ b/src/Discord.Net.Rest/Net/Converters/DiscordErrorConverter.cs @@ -0,0 +1,88 @@ +using Discord.API; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.Net.Converters +{ + internal class DiscordErrorConverter : JsonConverter + { + public static DiscordErrorConverter Instance + => new DiscordErrorConverter(); + + public override bool CanConvert(Type objectType) => objectType == typeof(DiscordError); + + public override bool CanRead => true; + public override bool CanWrite => false; + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var obj = JObject.Load(reader); + var err = new API.DiscordError(); + + + var result = obj.GetValue("errors", StringComparison.OrdinalIgnoreCase); + result?.Parent.Remove(); + + // Populate the remaining properties. + using (var subReader = obj.CreateReader()) + { + serializer.Populate(subReader, err); + } + + if (result != null) + { + var innerReader = result.CreateReader(); + + var errors = ReadErrors(innerReader); + err.Errors = errors.ToArray(); + } + + return err; + } + + private List ReadErrors(JsonReader reader, string path = "") + { + List errs = new List(); + var obj = JObject.Load(reader); + var props = obj.Properties(); + foreach (var prop in props) + { + if (prop.Name == "_errors" && path == "") // root level error + { + errs.Add(new ErrorDetails() + { + Name = Optional.Unspecified, + Errors = prop.Value.ToObject() + }); + } + else if (prop.Name == "_errors") // path errors (not root level) + { + errs.Add(new ErrorDetails() + { + Name = path, + Errors = prop.Value.ToObject() + }); + } + else if(int.TryParse(prop.Name, out var i)) // array value + { + var r = prop.Value.CreateReader(); + errs.AddRange(ReadErrors(r, path + $"[{i}]")); + } + else // property name + { + var r = prop.Value.CreateReader(); + errs.AddRange(ReadErrors(r, path + $"{(path != "" ? "." : "")}{prop.Name[0].ToString().ToUpper() + new string(prop.Name.Skip(1).ToArray())}")); + } + } + + return errs; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotImplementedException(); + } +} diff --git a/src/Discord.Net.Rest/Net/Converters/EmbedTypeConverter.cs b/src/Discord.Net.Rest/Net/Converters/EmbedTypeConverter.cs index 1e03fb698..cacd2e2e1 100644 --- a/src/Discord.Net.Rest/Net/Converters/EmbedTypeConverter.cs +++ b/src/Discord.Net.Rest/Net/Converters/EmbedTypeConverter.cs @@ -13,28 +13,19 @@ namespace Discord.Net.Converters public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { - switch ((string)reader.Value) + return (string)reader.Value switch { - case "rich": - return EmbedType.Rich; - case "link": - return EmbedType.Link; - case "video": - return EmbedType.Video; - case "image": - return EmbedType.Image; - case "gifv": - return EmbedType.Gifv; - case "article": - return EmbedType.Article; - case "tweet": - return EmbedType.Tweet; - case "html": - return EmbedType.Html; - case "application_news": // TODO 2.2 EmbedType.News - default: - return EmbedType.Unknown; - } + "rich" => EmbedType.Rich, + "link" => EmbedType.Link, + "video" => EmbedType.Video, + "image" => EmbedType.Image, + "gifv" => EmbedType.Gifv, + "article" => EmbedType.Article, + "tweet" => EmbedType.Tweet, + "html" => EmbedType.Html, + // TODO 2.2 EmbedType.News + _ => EmbedType.Unknown, + }; } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) diff --git a/src/Discord.Net.Rest/Net/Converters/GuildFeaturesConverter.cs b/src/Discord.Net.Rest/Net/Converters/GuildFeaturesConverter.cs new file mode 100644 index 000000000..9f82b440b --- /dev/null +++ b/src/Discord.Net.Rest/Net/Converters/GuildFeaturesConverter.cs @@ -0,0 +1,60 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace Discord.Net.Converters +{ + internal class GuildFeaturesConverter : JsonConverter + { + public static GuildFeaturesConverter Instance + => new GuildFeaturesConverter(); + + public override bool CanConvert(Type objectType) => true; + public override bool CanWrite => false; + public override bool CanRead => true; + + + private Regex _readRegex = new Regex(@"_(\w)"); + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var obj = JToken.Load(reader); + var arr = obj.ToObject(); + + GuildFeature features = GuildFeature.None; + List experimental = new(); + + foreach(var item in arr) + { + var name = _readRegex.Replace(item.ToLower(), (x) => + { + return x.Groups[1].Value.ToUpper(); + }); + + name = name[0].ToString().ToUpper() + new string(name.Skip(1).ToArray()); + + try + { + var result = (GuildFeature)Enum.Parse(typeof(GuildFeature), name); + + features |= result; + } + catch + { + experimental.Add(item); + } + } + + return new GuildFeatures(features, experimental.ToArray()); + } + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Discord.Net.Rest/Net/Converters/InteractionConverter.cs b/src/Discord.Net.Rest/Net/Converters/InteractionConverter.cs new file mode 100644 index 000000000..f7235841d --- /dev/null +++ b/src/Discord.Net.Rest/Net/Converters/InteractionConverter.cs @@ -0,0 +1,70 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; + +namespace Discord.Net.Converters +{ + internal class InteractionConverter : JsonConverter + { + public static InteractionConverter Instance => new InteractionConverter(); + + public override bool CanRead => true; + public override bool CanWrite => false; + public override bool CanConvert(Type objectType) => true; + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) + return null; + + var obj = JObject.Load(reader); + var interaction = new API.Interaction(); + + + // Remove the data property for manual deserialization + var result = obj.GetValue("data", StringComparison.OrdinalIgnoreCase); + result?.Parent.Remove(); + + // Populate the remaining properties. + using (var subReader = obj.CreateReader()) + { + serializer.Populate(subReader, interaction); + } + + // Process the Result property + if (result != null) + { + switch (interaction.Type) + { + case InteractionType.ApplicationCommand: + { + var appCommandData = new API.ApplicationCommandInteractionData(); + serializer.Populate(result.CreateReader(), appCommandData); + interaction.Data = appCommandData; + } + break; + case InteractionType.MessageComponent: + { + var messageComponent = new API.MessageComponentInteractionData(); + serializer.Populate(result.CreateReader(), messageComponent); + interaction.Data = messageComponent; + } + break; + case InteractionType.ApplicationCommandAutocomplete: + { + var autocompleteData = new API.AutocompleteInteractionData(); + serializer.Populate(result.CreateReader(), autocompleteData); + interaction.Data = autocompleteData; + } + break; + } + } + else + interaction.Data = Optional.Unspecified; + + return interaction; + } + + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotImplementedException(); + } +} diff --git a/src/Discord.Net.Rest/Net/Converters/MessageComponentConverter.cs b/src/Discord.Net.Rest/Net/Converters/MessageComponentConverter.cs new file mode 100644 index 000000000..0bf11a369 --- /dev/null +++ b/src/Discord.Net.Rest/Net/Converters/MessageComponentConverter.cs @@ -0,0 +1,40 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; + +namespace Discord.Net.Converters +{ + internal class MessageComponentConverter : JsonConverter + { + public static MessageComponentConverter Instance => new MessageComponentConverter(); + + public override bool CanRead => true; + public override bool CanWrite => false; + public override bool CanConvert(Type objectType) => true; + public override void WriteJson(JsonWriter writer, + object value, JsonSerializer serializer) + { + serializer.Serialize(writer, value); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var jsonObject = JObject.Load(reader); + var messageComponent = default(IMessageComponent); + switch ((ComponentType)jsonObject["type"].Value()) + { + case ComponentType.ActionRow: + messageComponent = new API.ActionRowComponent(); + break; + case ComponentType.Button: + messageComponent = new API.ButtonComponent(); + break; + case ComponentType.SelectMenu: + messageComponent = new API.SelectMenuComponent(); + break; + } + serializer.Populate(jsonObject.CreateReader(), messageComponent); + return messageComponent; + } + } +} diff --git a/src/Discord.Net.Rest/Net/Converters/UnixTimestampConverter.cs b/src/Discord.Net.Rest/Net/Converters/UnixTimestampConverter.cs index 0b50cb166..876254fb9 100644 --- a/src/Discord.Net.Rest/Net/Converters/UnixTimestampConverter.cs +++ b/src/Discord.Net.Rest/Net/Converters/UnixTimestampConverter.cs @@ -27,7 +27,7 @@ namespace Discord.Net.Converters public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { - throw new NotImplementedException(); + writer.WriteValue(((DateTimeOffset)value).ToString("O")); } } } diff --git a/src/Discord.Net.Rest/Net/Converters/UserStatusConverter.cs b/src/Discord.Net.Rest/Net/Converters/UserStatusConverter.cs index c0a287c16..8a13e79a5 100644 --- a/src/Discord.Net.Rest/Net/Converters/UserStatusConverter.cs +++ b/src/Discord.Net.Rest/Net/Converters/UserStatusConverter.cs @@ -1,4 +1,4 @@ -using Newtonsoft.Json; +using Newtonsoft.Json; using System; namespace Discord.Net.Converters @@ -13,21 +13,15 @@ namespace Discord.Net.Converters public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { - switch ((string)reader.Value) + return (string)reader.Value switch { - case "online": - return UserStatus.Online; - case "idle": - return UserStatus.Idle; - case "dnd": - return UserStatus.DoNotDisturb; - case "invisible": - return UserStatus.Invisible; //Should never happen - case "offline": - return UserStatus.Offline; - default: - throw new JsonSerializationException("Unknown user status"); - } + "online" => UserStatus.Online, + "idle" => UserStatus.Idle, + "dnd" => UserStatus.DoNotDisturb, + "invisible" => UserStatus.Invisible,//Should never happen + "offline" => UserStatus.Offline, + _ => throw new JsonSerializationException("Unknown user status"), + }; } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) diff --git a/src/Discord.Net.Rest/Net/DefaultRestClient.cs b/src/Discord.Net.Rest/Net/DefaultRestClient.cs index b5036d94e..1db743609 100644 --- a/src/Discord.Net.Rest/Net/DefaultRestClient.cs +++ b/src/Discord.Net.Rest/Net/DefaultRestClient.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using System.Net; using System.Net.Http; +using System.Net.Http.Headers; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -101,7 +102,7 @@ namespace Discord.Net.Rest switch (p.Value) { #pragma warning disable IDISP004 - case string stringValue: { content.Add(new StringContent(stringValue), p.Key); continue; } + case string stringValue: { content.Add(new StringContent(stringValue, Encoding.UTF8, "text/plain"), p.Key); continue; } case byte[] byteArrayValue: { content.Add(new ByteArrayContent(byteArrayValue), p.Key); continue; } case Stream streamValue: { content.Add(new StreamContent(streamValue), p.Key); continue; } case MultipartFile fileValue: @@ -116,8 +117,16 @@ namespace Discord.Net.Rest stream = memoryStream; #pragma warning restore IDISP001 } - content.Add(new StreamContent(stream), p.Key, fileValue.Filename); + + var streamContent = new StreamContent(stream); + var extension = fileValue.Filename.Split('.').Last(); + + if(fileValue.ContentType != null) + streamContent.Headers.ContentType = new MediaTypeHeaderValue(fileValue.ContentType); + + content.Add(streamContent, p.Key, fileValue.Filename); #pragma warning restore IDISP004 + continue; } default: throw new InvalidOperationException($"Unsupported param type \"{p.Value.GetType().Name}\"."); @@ -148,15 +157,15 @@ namespace Discord.Net.Rest private static readonly HttpMethod Patch = new HttpMethod("PATCH"); private HttpMethod GetMethod(string method) { - switch (method) + return method switch { - case "DELETE": return HttpMethod.Delete; - case "GET": return HttpMethod.Get; - case "PATCH": return Patch; - case "POST": return HttpMethod.Post; - case "PUT": return HttpMethod.Put; - default: throw new ArgumentOutOfRangeException(nameof(method), $"Unknown HttpMethod: {method}"); - } + "DELETE" => HttpMethod.Delete, + "GET" => HttpMethod.Get, + "PATCH" => Patch, + "POST" => HttpMethod.Post, + "PUT" => HttpMethod.Put, + _ => throw new ArgumentOutOfRangeException(nameof(method), $"Unknown HttpMethod: {method}"), + }; } } } diff --git a/src/Discord.Net.Rest/Net/ED25519/Array16.cs b/src/Discord.Net.Rest/Net/ED25519/Array16.cs new file mode 100644 index 000000000..fca8616c5 --- /dev/null +++ b/src/Discord.Net.Rest/Net/ED25519/Array16.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; + +namespace Discord.Net.ED25519 +{ + // Array16 Salsa20 state + // Array16 SHA-512 block + internal struct Array16 + { + public T x0; + public T x1; + public T x2; + public T x3; + public T x4; + public T x5; + public T x6; + public T x7; + public T x8; + public T x9; + public T x10; + public T x11; + public T x12; + public T x13; + public T x14; + public T x15; + } +} diff --git a/src/Discord.Net.Rest/Net/ED25519/Array8.cs b/src/Discord.Net.Rest/Net/ED25519/Array8.cs new file mode 100644 index 000000000..b563ac213 --- /dev/null +++ b/src/Discord.Net.Rest/Net/ED25519/Array8.cs @@ -0,0 +1,18 @@ +using System; + +namespace Discord.Net.ED25519 +{ + // Array8 Poly1305 key + // Array8 SHA-512 state/output + internal struct Array8 + { + public T x0; + public T x1; + public T x2; + public T x3; + public T x4; + public T x5; + public T x6; + public T x7; + } +} diff --git a/src/Discord.Net.Rest/Net/ED25519/ByteIntegerConverter.cs b/src/Discord.Net.Rest/Net/ED25519/ByteIntegerConverter.cs new file mode 100644 index 000000000..40c7624ba --- /dev/null +++ b/src/Discord.Net.Rest/Net/ED25519/ByteIntegerConverter.cs @@ -0,0 +1,55 @@ +using System; + +namespace Discord.Net.ED25519 +{ + // Loops? Arrays? Never heard of that stuff + // Library avoids unnecessary heap allocations and unsafe code + // so this ugly code becomes necessary :( + internal static class ByteIntegerConverter + { + public static ulong LoadBigEndian64(byte[] buf, int offset) + { + return + (ulong)(buf[offset + 7]) + | (((ulong)(buf[offset + 6])) << 8) + | (((ulong)(buf[offset + 5])) << 16) + | (((ulong)(buf[offset + 4])) << 24) + | (((ulong)(buf[offset + 3])) << 32) + | (((ulong)(buf[offset + 2])) << 40) + | (((ulong)(buf[offset + 1])) << 48) + | (((ulong)(buf[offset + 0])) << 56); + } + + public static void StoreBigEndian64(byte[] buf, int offset, ulong value) + { + buf[offset + 7] = unchecked((byte)value); + buf[offset + 6] = unchecked((byte)(value >> 8)); + buf[offset + 5] = unchecked((byte)(value >> 16)); + buf[offset + 4] = unchecked((byte)(value >> 24)); + buf[offset + 3] = unchecked((byte)(value >> 32)); + buf[offset + 2] = unchecked((byte)(value >> 40)); + buf[offset + 1] = unchecked((byte)(value >> 48)); + buf[offset + 0] = unchecked((byte)(value >> 56)); + } + + public static void Array16LoadBigEndian64(out Array16 output, byte[] input, int inputOffset) + { + output.x0 = LoadBigEndian64(input, inputOffset + 0); + output.x1 = LoadBigEndian64(input, inputOffset + 8); + output.x2 = LoadBigEndian64(input, inputOffset + 16); + output.x3 = LoadBigEndian64(input, inputOffset + 24); + output.x4 = LoadBigEndian64(input, inputOffset + 32); + output.x5 = LoadBigEndian64(input, inputOffset + 40); + output.x6 = LoadBigEndian64(input, inputOffset + 48); + output.x7 = LoadBigEndian64(input, inputOffset + 56); + output.x8 = LoadBigEndian64(input, inputOffset + 64); + output.x9 = LoadBigEndian64(input, inputOffset + 72); + output.x10 = LoadBigEndian64(input, inputOffset + 80); + output.x11 = LoadBigEndian64(input, inputOffset + 88); + output.x12 = LoadBigEndian64(input, inputOffset + 96); + output.x13 = LoadBigEndian64(input, inputOffset + 104); + output.x14 = LoadBigEndian64(input, inputOffset + 112); + output.x15 = LoadBigEndian64(input, inputOffset + 120); + } + } +} diff --git a/src/Discord.Net.Rest/Net/ED25519/CryptoBytes.cs b/src/Discord.Net.Rest/Net/ED25519/CryptoBytes.cs new file mode 100644 index 000000000..cfd64104d --- /dev/null +++ b/src/Discord.Net.Rest/Net/ED25519/CryptoBytes.cs @@ -0,0 +1,272 @@ +using System; +using System.Linq; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace Discord.Net.ED25519 +{ + internal class CryptoBytes + { + /// + /// Comparison of two arrays. + /// + /// The runtime of this method does not depend on the contents of the arrays. Using constant time + /// prevents timing attacks that allow an attacker to learn if the arrays have a common prefix. + /// + /// It is important to use such a constant time comparison when verifying MACs. + /// + /// Byte array + /// Byte array + /// True if arrays are equal + public static bool ConstantTimeEquals(byte[] x, byte[] y) + { + if (x.Length != y.Length) + return false; + return InternalConstantTimeEquals(x, 0, y, 0, x.Length) != 0; + } + + /// + /// Comparison of two array segments. + /// + /// The runtime of this method does not depend on the contents of the arrays. Using constant time + /// prevents timing attacks that allow an attacker to learn if the arrays have a common prefix. + /// + /// It is important to use such a constant time comparison when verifying MACs. + /// + /// Byte array segment + /// Byte array segment + /// True if contents of x and y are equal + public static bool ConstantTimeEquals(ArraySegment x, ArraySegment y) + { + if (x.Count != y.Count) + return false; + return InternalConstantTimeEquals(x.Array, x.Offset, y.Array, y.Offset, x.Count) != 0; + } + + /// + /// Comparison of two byte sequences. + /// + /// The runtime of this method does not depend on the contents of the arrays. Using constant time + /// prevents timing attacks that allow an attacker to learn if the arrays have a common prefix. + /// + /// It is important to use such a constant time comparison when verifying MACs. + /// + /// Byte array + /// Offset of byte sequence in the x array + /// Byte array + /// Offset of byte sequence in the y array + /// Lengh of byte sequence + /// True if sequences are equal + public static bool ConstantTimeEquals(byte[] x, int xOffset, byte[] y, int yOffset, int length) + { + return InternalConstantTimeEquals(x, xOffset, y, yOffset, length) != 0; + } + + private static uint InternalConstantTimeEquals(byte[] x, int xOffset, byte[] y, int yOffset, int length) + { + int differentbits = 0; + for (int i = 0; i < length; i++) + differentbits |= x[xOffset + i] ^ y[yOffset + i]; + return (1 & (unchecked((uint)differentbits - 1) >> 8)); + } + + /// + /// Overwrites the contents of the array, wiping the previous content. + /// + /// Byte array + public static void Wipe(byte[] data) + { + InternalWipe(data, 0, data.Length); + } + + /// + /// Overwrites the contents of the array, wiping the previous content. + /// + /// Byte array + /// Index of byte sequence + /// Length of byte sequence + public static void Wipe(byte[] data, int offset, int length) + { + InternalWipe(data, offset, length); + } + + /// + /// Overwrites the contents of the array segment, wiping the previous content. + /// + /// Byte array segment + public static void Wipe(ArraySegment data) + { + InternalWipe(data.Array, data.Offset, data.Count); + } + + // Secure wiping is hard + // * the GC can move around and copy memory + // Perhaps this can be avoided by using unmanaged memory or by fixing the position of the array in memory + // * Swap files and error dumps can contain secret information + // It seems possible to lock memory in RAM, no idea about error dumps + // * Compiler could optimize out the wiping if it knows that data won't be read back + // I hope this is enough, suppressing inlining + // but perhaps `RtlSecureZeroMemory` is needed + [MethodImpl(MethodImplOptions.NoInlining)] + internal static void InternalWipe(byte[] data, int offset, int count) + { + Array.Clear(data, offset, count); + } + + // shallow wipe of structs + [MethodImpl(MethodImplOptions.NoInlining)] + internal static void InternalWipe(ref T data) + where T : struct + { + data = default(T); + } + + /// + /// Constant-time conversion of the bytes array to an upper-case hex string. + /// Please see http://stackoverflow.com/a/14333437/445517 for the detailed explanation + /// + /// Byte array + /// Hex representation of byte array + public static string ToHexStringUpper(byte[] data) + { + if (data == null) + return null; + char[] c = new char[data.Length * 2]; + int b; + for (int i = 0; i < data.Length; i++) + { + b = data[i] >> 4; + c[i * 2] = (char)(55 + b + (((b - 10) >> 31) & -7)); + b = data[i] & 0xF; + c[i * 2 + 1] = (char)(55 + b + (((b - 10) >> 31) & -7)); + } + return new string(c); + } + + /// + /// Constant-time conversion of the bytes array to an lower-case hex string. + /// Please see http://stackoverflow.com/a/14333437/445517 for the detailed explanation. + /// + /// Byte array + /// Hex representation of byte array + public static string ToHexStringLower(byte[] data) + { + if (data == null) + return null; + char[] c = new char[data.Length * 2]; + int b; + for (int i = 0; i < data.Length; i++) + { + b = data[i] >> 4; + c[i * 2] = (char)(87 + b + (((b - 10) >> 31) & -39)); + b = data[i] & 0xF; + c[i * 2 + 1] = (char)(87 + b + (((b - 10) >> 31) & -39)); + } + return new string(c); + } + + /// + /// Converts the hex string to bytes. Case insensitive. + /// + /// Hex encoded byte sequence + /// Byte array + public static byte[] FromHexString(string hexString) + { + if (hexString == null) + return null; + if (hexString.Length % 2 != 0) + throw new FormatException("The hex string is invalid because it has an odd length"); + var result = new byte[hexString.Length / 2]; + for (int i = 0; i < result.Length; i++) + result[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16); + return result; + } + + /// + /// Encodes the bytes with the Base64 encoding. + /// More compact than hex, but it is case-sensitive and uses the special characters `+`, `/` and `=`. + /// + /// Byte array + /// Base 64 encoded data + public static string ToBase64String(byte[] data) + { + if (data == null) + return null; + return Convert.ToBase64String(data); + } + + /// + /// Decodes a Base64 encoded string back to bytes. + /// + /// Base 64 encoded data + /// Byte array + public static byte[] FromBase64String(string base64String) + { + if (base64String == null) + return null; + return Convert.FromBase64String(base64String); + } + + private const string strDigits = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; + + /// + /// Encode a byte sequence as a base58-encoded string + /// + /// Byte sequence + /// Encoding result + public static string Base58Encode(byte[] input) + { + // Decode byte[] to BigInteger + BigInteger intData = 0; + for (int i = 0; i < input.Length; i++) + { + intData = intData * 256 + input[i]; + } + + // Encode BigInteger to Base58 string + string result = ""; + while (intData > 0) + { + int remainder = (int)(intData % 58); + intData /= 58; + result = strDigits[remainder] + result; + } + + // Append `1` for each leading 0 byte + for (int i = 0; i < input.Length && input[i] == 0; i++) + { + result = '1' + result; + } + return result; + } + + /// + /// // Decode a base58-encoded string into byte array + /// + /// Base58 data string + /// Byte array + public static byte[] Base58Decode(string input) + { + // Decode Base58 string to BigInteger + BigInteger intData = 0; + for (int i = 0; i < input.Length; i++) + { + int digit = strDigits.IndexOf(input[i]); //Slow + if (digit < 0) + throw new FormatException(string.Format("Invalid Base58 character `{0}` at position {1}", input[i], i)); + intData = intData * 58 + digit; + } + + // Encode BigInteger to byte[] + // Leading zero bytes get encoded as leading `1` characters + int leadingZeroCount = input.TakeWhile(c => c == '1').Count(); + var leadingZeros = Enumerable.Repeat((byte)0, leadingZeroCount); + var bytesWithoutLeadingZeros = + intData.ToByteArray() + .Reverse()// to big endian + .SkipWhile(b => b == 0);//strip sign byte + var result = leadingZeros.Concat(bytesWithoutLeadingZeros).ToArray(); + return result; + } + } +} diff --git a/src/Discord.Net.Rest/Net/ED25519/Ed25519.cs b/src/Discord.Net.Rest/Net/ED25519/Ed25519.cs new file mode 100644 index 000000000..109620efd --- /dev/null +++ b/src/Discord.Net.Rest/Net/ED25519/Ed25519.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.Net.ED25519 +{ + internal static class Ed25519 + { + /// + /// Public Keys are 32 byte values. All possible values of this size a valid. + /// + public const int PublicKeySize = 32; + /// + /// Signatures are 64 byte values + /// + public const int SignatureSize = 64; + /// + /// Private key seeds are 32 byte arbitrary values. This is the form that should be generated and stored. + /// + public const int PrivateKeySeedSize = 32; + /// + /// A 64 byte expanded form of private key. This form is used internally to improve performance + /// + public const int ExpandedPrivateKeySize = 32 * 2; + + /// + /// Verify Ed25519 signature + /// + /// Signature bytes + /// Message + /// Public key + /// True if signature is valid, false if it's not + public static bool Verify(ArraySegment signature, ArraySegment message, ArraySegment publicKey) + { + if (signature.Count != SignatureSize) + throw new ArgumentException($"Sizeof signature doesnt match defined size of {SignatureSize}"); + + if (publicKey.Count != PublicKeySize) + throw new ArgumentException($"Sizeof public key doesnt match defined size of {PublicKeySize}"); + + return Ed25519Operations.crypto_sign_verify(signature.Array, signature.Offset, message.Array, message.Offset, message.Count, publicKey.Array, publicKey.Offset); + } + + /// + /// Verify Ed25519 signature + /// + /// Signature bytes + /// Message + /// Public key + /// True if signature is valid, false if it's not + public static bool Verify(byte[] signature, byte[] message, byte[] publicKey) + { + Preconditions.NotNull(signature, nameof(signature)); + Preconditions.NotNull(message, nameof(message)); + Preconditions.NotNull(publicKey, nameof(publicKey)); + if (signature.Length != SignatureSize) + throw new ArgumentException($"Sizeof signature doesnt match defined size of {SignatureSize}"); + + if (publicKey.Length != PublicKeySize) + throw new ArgumentException($"Sizeof public key doesnt match defined size of {PublicKeySize}"); + + return Ed25519Operations.crypto_sign_verify(signature, 0, message, 0, message.Length, publicKey, 0); + } + } +} diff --git a/src/Discord.Net.Rest/Net/ED25519/Ed25519Operations.cs b/src/Discord.Net.Rest/Net/ED25519/Ed25519Operations.cs new file mode 100644 index 000000000..4d5ece1e5 --- /dev/null +++ b/src/Discord.Net.Rest/Net/ED25519/Ed25519Operations.cs @@ -0,0 +1,45 @@ +using Discord.Net.ED25519.Ed25519Ref10; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.Net.ED25519 +{ + internal class Ed25519Operations + { + public static bool crypto_sign_verify( + byte[] sig, int sigoffset, + byte[] m, int moffset, int mlen, + byte[] pk, int pkoffset) + { + byte[] h; + byte[] checkr = new byte[32]; + GroupElementP3 A; + GroupElementP2 R; + + if ((sig[sigoffset + 63] & 224) != 0) + return false; + if (GroupOperations.ge_frombytes_negate_vartime(out A, pk, pkoffset) != 0) + return false; + + var hasher = new Sha512(); + hasher.Update(sig, sigoffset, 32); + hasher.Update(pk, pkoffset, 32); + hasher.Update(m, moffset, mlen); + h = hasher.Finalize(); + + ScalarOperations.sc_reduce(h); + + var sm32 = new byte[32]; + Array.Copy(sig, sigoffset + 32, sm32, 0, 32); + GroupOperations.ge_double_scalarmult_vartime(out R, h, ref A, sm32); + GroupOperations.ge_tobytes(checkr, 0, ref R); + var result = CryptoBytes.ConstantTimeEquals(checkr, 0, sig, sigoffset, 32); + CryptoBytes.Wipe(h); + CryptoBytes.Wipe(checkr); + return result; + } + } +} diff --git a/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/FieldElement.cs b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/FieldElement.cs new file mode 100644 index 000000000..d612ff5be --- /dev/null +++ b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/FieldElement.cs @@ -0,0 +1,23 @@ +using System; + +namespace Discord.Net.ED25519.Ed25519Ref10 +{ + internal struct FieldElement + { + internal int x0, x1, x2, x3, x4, x5, x6, x7, x8, x9; + + internal FieldElement(params int[] elements) + { + x0 = elements[0]; + x1 = elements[1]; + x2 = elements[2]; + x3 = elements[3]; + x4 = elements[4]; + x5 = elements[5]; + x6 = elements[6]; + x7 = elements[7]; + x8 = elements[8]; + x9 = elements[9]; + } + } +} diff --git a/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/GroupElement.cs b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/GroupElement.cs new file mode 100644 index 000000000..d54b5ada7 --- /dev/null +++ b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/GroupElement.cs @@ -0,0 +1,63 @@ +using System; + +namespace Discord.Net.ED25519.Ed25519Ref10 +{ + /* + ge means group element. + + Here the group is the set of pairs (x,y) of field elements (see fe.h) + satisfying -x^2 + y^2 = 1 + d x^2y^2 + where d = -121665/121666. + + Representations: + ge_p2 (projective): (X:Y:Z) satisfying x=X/Z, y=Y/Z + ge_p3 (extended): (X:Y:Z:T) satisfying x=X/Z, y=Y/Z, XY=ZT + ge_p1p1 (completed): ((X:Z),(Y:T)) satisfying x=X/Z, y=Y/T + ge_precomp (Duif): (y+x,y-x,2dxy) + */ + + internal struct GroupElementP2 + { + public FieldElement X; + public FieldElement Y; + public FieldElement Z; + } ; + + internal struct GroupElementP3 + { + public FieldElement X; + public FieldElement Y; + public FieldElement Z; + public FieldElement T; + } ; + + internal struct GroupElementP1P1 + { + public FieldElement X; + public FieldElement Y; + public FieldElement Z; + public FieldElement T; + } ; + + internal struct GroupElementPreComp + { + public FieldElement yplusx; + public FieldElement yminusx; + public FieldElement xy2d; + + public GroupElementPreComp(FieldElement yplusx, FieldElement yminusx, FieldElement xy2d) + { + this.yplusx = yplusx; + this.yminusx = yminusx; + this.xy2d = xy2d; + } + } ; + + internal struct GroupElementCached + { + public FieldElement YplusX; + public FieldElement YminusX; + public FieldElement Z; + public FieldElement T2d; + } ; +} diff --git a/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/base.cs b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/base.cs new file mode 100644 index 000000000..2a25504c9 --- /dev/null +++ b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/base.cs @@ -0,0 +1,1355 @@ +namespace Discord.Net.ED25519.Ed25519Ref10 +{ + internal static partial class LookupTables + { + /* base[i][j] = (j+1)*256^i*B */ + //32*8 + internal static GroupElementPreComp[][] Base = new GroupElementPreComp[][] + { + new[]{ + new GroupElementPreComp( + new FieldElement( 25967493,-14356035,29566456,3660896,-12694345,4014787,27544626,-11754271,-6079156,2047605 ), + new FieldElement( -12545711,934262,-2722910,3049990,-727428,9406986,12720692,5043384,19500929,-15469378 ), + new FieldElement( -8738181,4489570,9688441,-14785194,10184609,-12363380,29287919,11864899,-24514362,-4438546 ) + ), + new GroupElementPreComp( + new FieldElement( -12815894,-12976347,-21581243,11784320,-25355658,-2750717,-11717903,-3814571,-358445,-10211303 ), + new FieldElement( -21703237,6903825,27185491,6451973,-29577724,-9554005,-15616551,11189268,-26829678,-5319081 ), + new FieldElement( 26966642,11152617,32442495,15396054,14353839,-12752335,-3128826,-9541118,-15472047,-4166697 ) + ), + new GroupElementPreComp( + new FieldElement( 15636291,-9688557,24204773,-7912398,616977,-16685262,27787600,-14772189,28944400,-1550024 ), + new FieldElement( 16568933,4717097,-11556148,-1102322,15682896,-11807043,16354577,-11775962,7689662,11199574 ), + new FieldElement( 30464156,-5976125,-11779434,-15670865,23220365,15915852,7512774,10017326,-17749093,-9920357 ) + ), + new GroupElementPreComp( + new FieldElement( -17036878,13921892,10945806,-6033431,27105052,-16084379,-28926210,15006023,3284568,-6276540 ), + new FieldElement( 23599295,-8306047,-11193664,-7687416,13236774,10506355,7464579,9656445,13059162,10374397 ), + new FieldElement( 7798556,16710257,3033922,2874086,28997861,2835604,32406664,-3839045,-641708,-101325 ) + ), + new GroupElementPreComp( + new FieldElement( 10861363,11473154,27284546,1981175,-30064349,12577861,32867885,14515107,-15438304,10819380 ), + new FieldElement( 4708026,6336745,20377586,9066809,-11272109,6594696,-25653668,12483688,-12668491,5581306 ), + new FieldElement( 19563160,16186464,-29386857,4097519,10237984,-4348115,28542350,13850243,-23678021,-15815942 ) + ), + new GroupElementPreComp( + new FieldElement( -15371964,-12862754,32573250,4720197,-26436522,5875511,-19188627,-15224819,-9818940,-12085777 ), + new FieldElement( -8549212,109983,15149363,2178705,22900618,4543417,3044240,-15689887,1762328,14866737 ), + new FieldElement( -18199695,-15951423,-10473290,1707278,-17185920,3916101,-28236412,3959421,27914454,4383652 ) + ), + new GroupElementPreComp( + new FieldElement( 5153746,9909285,1723747,-2777874,30523605,5516873,19480852,5230134,-23952439,-15175766 ), + new FieldElement( -30269007,-3463509,7665486,10083793,28475525,1649722,20654025,16520125,30598449,7715701 ), + new FieldElement( 28881845,14381568,9657904,3680757,-20181635,7843316,-31400660,1370708,29794553,-1409300 ) + ), + new GroupElementPreComp( + new FieldElement( 14499471,-2729599,-33191113,-4254652,28494862,14271267,30290735,10876454,-33154098,2381726 ), + new FieldElement( -7195431,-2655363,-14730155,462251,-27724326,3941372,-6236617,3696005,-32300832,15351955 ), + new FieldElement( 27431194,8222322,16448760,-3907995,-18707002,11938355,-32961401,-2970515,29551813,10109425 ) + ), + }, + new[]{ + new GroupElementPreComp( + new FieldElement( -13657040,-13155431,-31283750,11777098,21447386,6519384,-2378284,-1627556,10092783,-4764171 ), + new FieldElement( 27939166,14210322,4677035,16277044,-22964462,-12398139,-32508754,12005538,-17810127,12803510 ), + new FieldElement( 17228999,-15661624,-1233527,300140,-1224870,-11714777,30364213,-9038194,18016357,4397660 ) + ), + new GroupElementPreComp( + new FieldElement( -10958843,-7690207,4776341,-14954238,27850028,-15602212,-26619106,14544525,-17477504,982639 ), + new FieldElement( 29253598,15796703,-2863982,-9908884,10057023,3163536,7332899,-4120128,-21047696,9934963 ), + new FieldElement( 5793303,16271923,-24131614,-10116404,29188560,1206517,-14747930,4559895,-30123922,-10897950 ) + ), + new GroupElementPreComp( + new FieldElement( -27643952,-11493006,16282657,-11036493,28414021,-15012264,24191034,4541697,-13338309,5500568 ), + new FieldElement( 12650548,-1497113,9052871,11355358,-17680037,-8400164,-17430592,12264343,10874051,13524335 ), + new FieldElement( 25556948,-3045990,714651,2510400,23394682,-10415330,33119038,5080568,-22528059,5376628 ) + ), + new GroupElementPreComp( + new FieldElement( -26088264,-4011052,-17013699,-3537628,-6726793,1920897,-22321305,-9447443,4535768,1569007 ), + new FieldElement( -2255422,14606630,-21692440,-8039818,28430649,8775819,-30494562,3044290,31848280,12543772 ), + new FieldElement( -22028579,2943893,-31857513,6777306,13784462,-4292203,-27377195,-2062731,7718482,14474653 ) + ), + new GroupElementPreComp( + new FieldElement( 2385315,2454213,-22631320,46603,-4437935,-15680415,656965,-7236665,24316168,-5253567 ), + new FieldElement( 13741529,10911568,-33233417,-8603737,-20177830,-1033297,33040651,-13424532,-20729456,8321686 ), + new FieldElement( 21060490,-2212744,15712757,-4336099,1639040,10656336,23845965,-11874838,-9984458,608372 ) + ), + new GroupElementPreComp( + new FieldElement( -13672732,-15087586,-10889693,-7557059,-6036909,11305547,1123968,-6780577,27229399,23887 ), + new FieldElement( -23244140,-294205,-11744728,14712571,-29465699,-2029617,12797024,-6440308,-1633405,16678954 ), + new FieldElement( -29500620,4770662,-16054387,14001338,7830047,9564805,-1508144,-4795045,-17169265,4904953 ) + ), + new GroupElementPreComp( + new FieldElement( 24059557,14617003,19037157,-15039908,19766093,-14906429,5169211,16191880,2128236,-4326833 ), + new FieldElement( -16981152,4124966,-8540610,-10653797,30336522,-14105247,-29806336,916033,-6882542,-2986532 ), + new FieldElement( -22630907,12419372,-7134229,-7473371,-16478904,16739175,285431,2763829,15736322,4143876 ) + ), + new GroupElementPreComp( + new FieldElement( 2379352,11839345,-4110402,-5988665,11274298,794957,212801,-14594663,23527084,-16458268 ), + new FieldElement( 33431127,-11130478,-17838966,-15626900,8909499,8376530,-32625340,4087881,-15188911,-14416214 ), + new FieldElement( 1767683,7197987,-13205226,-2022635,-13091350,448826,5799055,4357868,-4774191,-16323038 ) + ), + }, + new[]{ + new GroupElementPreComp( + new FieldElement( 6721966,13833823,-23523388,-1551314,26354293,-11863321,23365147,-3949732,7390890,2759800 ), + new FieldElement( 4409041,2052381,23373853,10530217,7676779,-12885954,21302353,-4264057,1244380,-12919645 ), + new FieldElement( -4421239,7169619,4982368,-2957590,30256825,-2777540,14086413,9208236,15886429,16489664 ) + ), + new GroupElementPreComp( + new FieldElement( 1996075,10375649,14346367,13311202,-6874135,-16438411,-13693198,398369,-30606455,-712933 ), + new FieldElement( -25307465,9795880,-2777414,14878809,-33531835,14780363,13348553,12076947,-30836462,5113182 ), + new FieldElement( -17770784,11797796,31950843,13929123,-25888302,12288344,-30341101,-7336386,13847711,5387222 ) + ), + new GroupElementPreComp( + new FieldElement( -18582163,-3416217,17824843,-2340966,22744343,-10442611,8763061,3617786,-19600662,10370991 ), + new FieldElement( 20246567,-14369378,22358229,-543712,18507283,-10413996,14554437,-8746092,32232924,16763880 ), + new FieldElement( 9648505,10094563,26416693,14745928,-30374318,-6472621,11094161,15689506,3140038,-16510092 ) + ), + new GroupElementPreComp( + new FieldElement( -16160072,5472695,31895588,4744994,8823515,10365685,-27224800,9448613,-28774454,366295 ), + new FieldElement( 19153450,11523972,-11096490,-6503142,-24647631,5420647,28344573,8041113,719605,11671788 ), + new FieldElement( 8678025,2694440,-6808014,2517372,4964326,11152271,-15432916,-15266516,27000813,-10195553 ) + ), + new GroupElementPreComp( + new FieldElement( -15157904,7134312,8639287,-2814877,-7235688,10421742,564065,5336097,6750977,-14521026 ), + new FieldElement( 11836410,-3979488,26297894,16080799,23455045,15735944,1695823,-8819122,8169720,16220347 ), + new FieldElement( -18115838,8653647,17578566,-6092619,-8025777,-16012763,-11144307,-2627664,-5990708,-14166033 ) + ), + new GroupElementPreComp( + new FieldElement( -23308498,-10968312,15213228,-10081214,-30853605,-11050004,27884329,2847284,2655861,1738395 ), + new FieldElement( -27537433,-14253021,-25336301,-8002780,-9370762,8129821,21651608,-3239336,-19087449,-11005278 ), + new FieldElement( 1533110,3437855,23735889,459276,29970501,11335377,26030092,5821408,10478196,8544890 ) + ), + new GroupElementPreComp( + new FieldElement( 32173121,-16129311,24896207,3921497,22579056,-3410854,19270449,12217473,17789017,-3395995 ), + new FieldElement( -30552961,-2228401,-15578829,-10147201,13243889,517024,15479401,-3853233,30460520,1052596 ), + new FieldElement( -11614875,13323618,32618793,8175907,-15230173,12596687,27491595,-4612359,3179268,-9478891 ) + ), + new GroupElementPreComp( + new FieldElement( 31947069,-14366651,-4640583,-15339921,-15125977,-6039709,-14756777,-16411740,19072640,-9511060 ), + new FieldElement( 11685058,11822410,3158003,-13952594,33402194,-4165066,5977896,-5215017,473099,5040608 ), + new FieldElement( -20290863,8198642,-27410132,11602123,1290375,-2799760,28326862,1721092,-19558642,-3131606 ) + ), + }, + new[]{ + new GroupElementPreComp( + new FieldElement( 7881532,10687937,7578723,7738378,-18951012,-2553952,21820786,8076149,-27868496,11538389 ), + new FieldElement( -19935666,3899861,18283497,-6801568,-15728660,-11249211,8754525,7446702,-5676054,5797016 ), + new FieldElement( -11295600,-3793569,-15782110,-7964573,12708869,-8456199,2014099,-9050574,-2369172,-5877341 ) + ), + new GroupElementPreComp( + new FieldElement( -22472376,-11568741,-27682020,1146375,18956691,16640559,1192730,-3714199,15123619,10811505 ), + new FieldElement( 14352098,-3419715,-18942044,10822655,32750596,4699007,-70363,15776356,-28886779,-11974553 ), + new FieldElement( -28241164,-8072475,-4978962,-5315317,29416931,1847569,-20654173,-16484855,4714547,-9600655 ) + ), + new GroupElementPreComp( + new FieldElement( 15200332,8368572,19679101,15970074,-31872674,1959451,24611599,-4543832,-11745876,12340220 ), + new FieldElement( 12876937,-10480056,33134381,6590940,-6307776,14872440,9613953,8241152,15370987,9608631 ), + new FieldElement( -4143277,-12014408,8446281,-391603,4407738,13629032,-7724868,15866074,-28210621,-8814099 ) + ), + new GroupElementPreComp( + new FieldElement( 26660628,-15677655,8393734,358047,-7401291,992988,-23904233,858697,20571223,8420556 ), + new FieldElement( 14620715,13067227,-15447274,8264467,14106269,15080814,33531827,12516406,-21574435,-12476749 ), + new FieldElement( 236881,10476226,57258,-14677024,6472998,2466984,17258519,7256740,8791136,15069930 ) + ), + new GroupElementPreComp( + new FieldElement( 1276410,-9371918,22949635,-16322807,-23493039,-5702186,14711875,4874229,-30663140,-2331391 ), + new FieldElement( 5855666,4990204,-13711848,7294284,-7804282,1924647,-1423175,-7912378,-33069337,9234253 ), + new FieldElement( 20590503,-9018988,31529744,-7352666,-2706834,10650548,31559055,-11609587,18979186,13396066 ) + ), + new GroupElementPreComp( + new FieldElement( 24474287,4968103,22267082,4407354,24063882,-8325180,-18816887,13594782,33514650,7021958 ), + new FieldElement( -11566906,-6565505,-21365085,15928892,-26158305,4315421,-25948728,-3916677,-21480480,12868082 ), + new FieldElement( -28635013,13504661,19988037,-2132761,21078225,6443208,-21446107,2244500,-12455797,-8089383 ) + ), + new GroupElementPreComp( + new FieldElement( -30595528,13793479,-5852820,319136,-25723172,-6263899,33086546,8957937,-15233648,5540521 ), + new FieldElement( -11630176,-11503902,-8119500,-7643073,2620056,1022908,-23710744,-1568984,-16128528,-14962807 ), + new FieldElement( 23152971,775386,27395463,14006635,-9701118,4649512,1689819,892185,-11513277,-15205948 ) + ), + new GroupElementPreComp( + new FieldElement( 9770129,9586738,26496094,4324120,1556511,-3550024,27453819,4763127,-19179614,5867134 ), + new FieldElement( -32765025,1927590,31726409,-4753295,23962434,-16019500,27846559,5931263,-29749703,-16108455 ), + new FieldElement( 27461885,-2977536,22380810,1815854,-23033753,-3031938,7283490,-15148073,-19526700,7734629 ) + ), + }, + new[]{ + new GroupElementPreComp( + new FieldElement( -8010264,-9590817,-11120403,6196038,29344158,-13430885,7585295,-3176626,18549497,15302069 ), + new FieldElement( -32658337,-6171222,-7672793,-11051681,6258878,13504381,10458790,-6418461,-8872242,8424746 ), + new FieldElement( 24687205,8613276,-30667046,-3233545,1863892,-1830544,19206234,7134917,-11284482,-828919 ) + ), + new GroupElementPreComp( + new FieldElement( 11334899,-9218022,8025293,12707519,17523892,-10476071,10243738,-14685461,-5066034,16498837 ), + new FieldElement( 8911542,6887158,-9584260,-6958590,11145641,-9543680,17303925,-14124238,6536641,10543906 ), + new FieldElement( -28946384,15479763,-17466835,568876,-1497683,11223454,-2669190,-16625574,-27235709,8876771 ) + ), + new GroupElementPreComp( + new FieldElement( -25742899,-12566864,-15649966,-846607,-33026686,-796288,-33481822,15824474,-604426,-9039817 ), + new FieldElement( 10330056,70051,7957388,-9002667,9764902,15609756,27698697,-4890037,1657394,3084098 ), + new FieldElement( 10477963,-7470260,12119566,-13250805,29016247,-5365589,31280319,14396151,-30233575,15272409 ) + ), + new GroupElementPreComp( + new FieldElement( -12288309,3169463,28813183,16658753,25116432,-5630466,-25173957,-12636138,-25014757,1950504 ), + new FieldElement( -26180358,9489187,11053416,-14746161,-31053720,5825630,-8384306,-8767532,15341279,8373727 ), + new FieldElement( 28685821,7759505,-14378516,-12002860,-31971820,4079242,298136,-10232602,-2878207,15190420 ) + ), + new GroupElementPreComp( + new FieldElement( -32932876,13806336,-14337485,-15794431,-24004620,10940928,8669718,2742393,-26033313,-6875003 ), + new FieldElement( -1580388,-11729417,-25979658,-11445023,-17411874,-10912854,9291594,-16247779,-12154742,6048605 ), + new FieldElement( -30305315,14843444,1539301,11864366,20201677,1900163,13934231,5128323,11213262,9168384 ) + ), + new GroupElementPreComp( + new FieldElement( -26280513,11007847,19408960,-940758,-18592965,-4328580,-5088060,-11105150,20470157,-16398701 ), + new FieldElement( -23136053,9282192,14855179,-15390078,-7362815,-14408560,-22783952,14461608,14042978,5230683 ), + new FieldElement( 29969567,-2741594,-16711867,-8552442,9175486,-2468974,21556951,3506042,-5933891,-12449708 ) + ), + new GroupElementPreComp( + new FieldElement( -3144746,8744661,19704003,4581278,-20430686,6830683,-21284170,8971513,-28539189,15326563 ), + new FieldElement( -19464629,10110288,-17262528,-3503892,-23500387,1355669,-15523050,15300988,-20514118,9168260 ), + new FieldElement( -5353335,4488613,-23803248,16314347,7780487,-15638939,-28948358,9601605,33087103,-9011387 ) + ), + new GroupElementPreComp( + new FieldElement( -19443170,-15512900,-20797467,-12445323,-29824447,10229461,-27444329,-15000531,-5996870,15664672 ), + new FieldElement( 23294591,-16632613,-22650781,-8470978,27844204,11461195,13099750,-2460356,18151676,13417686 ), + new FieldElement( -24722913,-4176517,-31150679,5988919,-26858785,6685065,1661597,-12551441,15271676,-15452665 ) + ), + }, + new[]{ + new GroupElementPreComp( + new FieldElement( 11433042,-13228665,8239631,-5279517,-1985436,-725718,-18698764,2167544,-6921301,-13440182 ), + new FieldElement( -31436171,15575146,30436815,12192228,-22463353,9395379,-9917708,-8638997,12215110,12028277 ), + new FieldElement( 14098400,6555944,23007258,5757252,-15427832,-12950502,30123440,4617780,-16900089,-655628 ) + ), + new GroupElementPreComp( + new FieldElement( -4026201,-15240835,11893168,13718664,-14809462,1847385,-15819999,10154009,23973261,-12684474 ), + new FieldElement( -26531820,-3695990,-1908898,2534301,-31870557,-16550355,18341390,-11419951,32013174,-10103539 ), + new FieldElement( -25479301,10876443,-11771086,-14625140,-12369567,1838104,21911214,6354752,4425632,-837822 ) + ), + new GroupElementPreComp( + new FieldElement( -10433389,-14612966,22229858,-3091047,-13191166,776729,-17415375,-12020462,4725005,14044970 ), + new FieldElement( 19268650,-7304421,1555349,8692754,-21474059,-9910664,6347390,-1411784,-19522291,-16109756 ), + new FieldElement( -24864089,12986008,-10898878,-5558584,-11312371,-148526,19541418,8180106,9282262,10282508 ) + ), + new GroupElementPreComp( + new FieldElement( -26205082,4428547,-8661196,-13194263,4098402,-14165257,15522535,8372215,5542595,-10702683 ), + new FieldElement( -10562541,14895633,26814552,-16673850,-17480754,-2489360,-2781891,6993761,-18093885,10114655 ), + new FieldElement( -20107055,-929418,31422704,10427861,-7110749,6150669,-29091755,-11529146,25953725,-106158 ) + ), + new GroupElementPreComp( + new FieldElement( -4234397,-8039292,-9119125,3046000,2101609,-12607294,19390020,6094296,-3315279,12831125 ), + new FieldElement( -15998678,7578152,5310217,14408357,-33548620,-224739,31575954,6326196,7381791,-2421839 ), + new FieldElement( -20902779,3296811,24736065,-16328389,18374254,7318640,6295303,8082724,-15362489,12339664 ) + ), + new GroupElementPreComp( + new FieldElement( 27724736,2291157,6088201,-14184798,1792727,5857634,13848414,15768922,25091167,14856294 ), + new FieldElement( -18866652,8331043,24373479,8541013,-701998,-9269457,12927300,-12695493,-22182473,-9012899 ), + new FieldElement( -11423429,-5421590,11632845,3405020,30536730,-11674039,-27260765,13866390,30146206,9142070 ) + ), + new GroupElementPreComp( + new FieldElement( 3924129,-15307516,-13817122,-10054960,12291820,-668366,-27702774,9326384,-8237858,4171294 ), + new FieldElement( -15921940,16037937,6713787,16606682,-21612135,2790944,26396185,3731949,345228,-5462949 ), + new FieldElement( -21327538,13448259,25284571,1143661,20614966,-8849387,2031539,-12391231,-16253183,-13582083 ) + ), + new GroupElementPreComp( + new FieldElement( 31016211,-16722429,26371392,-14451233,-5027349,14854137,17477601,3842657,28012650,-16405420 ), + new FieldElement( -5075835,9368966,-8562079,-4600902,-15249953,6970560,-9189873,16292057,-8867157,3507940 ), + new FieldElement( 29439664,3537914,23333589,6997794,-17555561,-11018068,-15209202,-15051267,-9164929,6580396 ) + ), + }, + new[]{ + new GroupElementPreComp( + new FieldElement( -12185861,-7679788,16438269,10826160,-8696817,-6235611,17860444,-9273846,-2095802,9304567 ), + new FieldElement( 20714564,-4336911,29088195,7406487,11426967,-5095705,14792667,-14608617,5289421,-477127 ), + new FieldElement( -16665533,-10650790,-6160345,-13305760,9192020,-1802462,17271490,12349094,26939669,-3752294 ) + ), + new GroupElementPreComp( + new FieldElement( -12889898,9373458,31595848,16374215,21471720,13221525,-27283495,-12348559,-3698806,117887 ), + new FieldElement( 22263325,-6560050,3984570,-11174646,-15114008,-566785,28311253,5358056,-23319780,541964 ), + new FieldElement( 16259219,3261970,2309254,-15534474,-16885711,-4581916,24134070,-16705829,-13337066,-13552195 ) + ), + new GroupElementPreComp( + new FieldElement( 9378160,-13140186,-22845982,-12745264,28198281,-7244098,-2399684,-717351,690426,14876244 ), + new FieldElement( 24977353,-314384,-8223969,-13465086,28432343,-1176353,-13068804,-12297348,-22380984,6618999 ), + new FieldElement( -1538174,11685646,12944378,13682314,-24389511,-14413193,8044829,-13817328,32239829,-5652762 ) + ), + new GroupElementPreComp( + new FieldElement( -18603066,4762990,-926250,8885304,-28412480,-3187315,9781647,-10350059,32779359,5095274 ), + new FieldElement( -33008130,-5214506,-32264887,-3685216,9460461,-9327423,-24601656,14506724,21639561,-2630236 ), + new FieldElement( -16400943,-13112215,25239338,15531969,3987758,-4499318,-1289502,-6863535,17874574,558605 ) + ), + new GroupElementPreComp( + new FieldElement( -13600129,10240081,9171883,16131053,-20869254,9599700,33499487,5080151,2085892,5119761 ), + new FieldElement( -22205145,-2519528,-16381601,414691,-25019550,2170430,30634760,-8363614,-31999993,-5759884 ), + new FieldElement( -6845704,15791202,8550074,-1312654,29928809,-12092256,27534430,-7192145,-22351378,12961482 ) + ), + new GroupElementPreComp( + new FieldElement( -24492060,-9570771,10368194,11582341,-23397293,-2245287,16533930,8206996,-30194652,-5159638 ), + new FieldElement( -11121496,-3382234,2307366,6362031,-135455,8868177,-16835630,7031275,7589640,8945490 ), + new FieldElement( -32152748,8917967,6661220,-11677616,-1192060,-15793393,7251489,-11182180,24099109,-14456170 ) + ), + new GroupElementPreComp( + new FieldElement( 5019558,-7907470,4244127,-14714356,-26933272,6453165,-19118182,-13289025,-6231896,-10280736 ), + new FieldElement( 10853594,10721687,26480089,5861829,-22995819,1972175,-1866647,-10557898,-3363451,-6441124 ), + new FieldElement( -17002408,5906790,221599,-6563147,7828208,-13248918,24362661,-2008168,-13866408,7421392 ) + ), + new GroupElementPreComp( + new FieldElement( 8139927,-6546497,32257646,-5890546,30375719,1886181,-21175108,15441252,28826358,-4123029 ), + new FieldElement( 6267086,9695052,7709135,-16603597,-32869068,-1886135,14795160,-7840124,13746021,-1742048 ), + new FieldElement( 28584902,7787108,-6732942,-15050729,22846041,-7571236,-3181936,-363524,4771362,-8419958 ) + ), + }, + new[]{ + new GroupElementPreComp( + new FieldElement( 24949256,6376279,-27466481,-8174608,-18646154,-9930606,33543569,-12141695,3569627,11342593 ), + new FieldElement( 26514989,4740088,27912651,3697550,19331575,-11472339,6809886,4608608,7325975,-14801071 ), + new FieldElement( -11618399,-14554430,-24321212,7655128,-1369274,5214312,-27400540,10258390,-17646694,-8186692 ) + ), + new GroupElementPreComp( + new FieldElement( 11431204,15823007,26570245,14329124,18029990,4796082,-31446179,15580664,9280358,-3973687 ), + new FieldElement( -160783,-10326257,-22855316,-4304997,-20861367,-13621002,-32810901,-11181622,-15545091,4387441 ), + new FieldElement( -20799378,12194512,3937617,-5805892,-27154820,9340370,-24513992,8548137,20617071,-7482001 ) + ), + new GroupElementPreComp( + new FieldElement( -938825,-3930586,-8714311,16124718,24603125,-6225393,-13775352,-11875822,24345683,10325460 ), + new FieldElement( -19855277,-1568885,-22202708,8714034,14007766,6928528,16318175,-1010689,4766743,3552007 ), + new FieldElement( -21751364,-16730916,1351763,-803421,-4009670,3950935,3217514,14481909,10988822,-3994762 ) + ), + new GroupElementPreComp( + new FieldElement( 15564307,-14311570,3101243,5684148,30446780,-8051356,12677127,-6505343,-8295852,13296005 ), + new FieldElement( -9442290,6624296,-30298964,-11913677,-4670981,-2057379,31521204,9614054,-30000824,12074674 ), + new FieldElement( 4771191,-135239,14290749,-13089852,27992298,14998318,-1413936,-1556716,29832613,-16391035 ) + ), + new GroupElementPreComp( + new FieldElement( 7064884,-7541174,-19161962,-5067537,-18891269,-2912736,25825242,5293297,-27122660,13101590 ), + new FieldElement( -2298563,2439670,-7466610,1719965,-27267541,-16328445,32512469,-5317593,-30356070,-4190957 ), + new FieldElement( -30006540,10162316,-33180176,3981723,-16482138,-13070044,14413974,9515896,19568978,9628812 ) + ), + new GroupElementPreComp( + new FieldElement( 33053803,199357,15894591,1583059,27380243,-4580435,-17838894,-6106839,-6291786,3437740 ), + new FieldElement( -18978877,3884493,19469877,12726490,15913552,13614290,-22961733,70104,7463304,4176122 ), + new FieldElement( -27124001,10659917,11482427,-16070381,12771467,-6635117,-32719404,-5322751,24216882,5944158 ) + ), + new GroupElementPreComp( + new FieldElement( 8894125,7450974,-2664149,-9765752,-28080517,-12389115,19345746,14680796,11632993,5847885 ), + new FieldElement( 26942781,-2315317,9129564,-4906607,26024105,11769399,-11518837,6367194,-9727230,4782140 ), + new FieldElement( 19916461,-4828410,-22910704,-11414391,25606324,-5972441,33253853,8220911,6358847,-1873857 ) + ), + new GroupElementPreComp( + new FieldElement( 801428,-2081702,16569428,11065167,29875704,96627,7908388,-4480480,-13538503,1387155 ), + new FieldElement( 19646058,5720633,-11416706,12814209,11607948,12749789,14147075,15156355,-21866831,11835260 ), + new FieldElement( 19299512,1155910,28703737,14890794,2925026,7269399,26121523,15467869,-26560550,5052483 ) + ), + }, + new[]{ + new GroupElementPreComp( + new FieldElement( -3017432,10058206,1980837,3964243,22160966,12322533,-6431123,-12618185,12228557,-7003677 ), + new FieldElement( 32944382,14922211,-22844894,5188528,21913450,-8719943,4001465,13238564,-6114803,8653815 ), + new FieldElement( 22865569,-4652735,27603668,-12545395,14348958,8234005,24808405,5719875,28483275,2841751 ) + ), + new GroupElementPreComp( + new FieldElement( -16420968,-1113305,-327719,-12107856,21886282,-15552774,-1887966,-315658,19932058,-12739203 ), + new FieldElement( -11656086,10087521,-8864888,-5536143,-19278573,-3055912,3999228,13239134,-4777469,-13910208 ), + new FieldElement( 1382174,-11694719,17266790,9194690,-13324356,9720081,20403944,11284705,-14013818,3093230 ) + ), + new GroupElementPreComp( + new FieldElement( 16650921,-11037932,-1064178,1570629,-8329746,7352753,-302424,16271225,-24049421,-6691850 ), + new FieldElement( -21911077,-5927941,-4611316,-5560156,-31744103,-10785293,24123614,15193618,-21652117,-16739389 ), + new FieldElement( -9935934,-4289447,-25279823,4372842,2087473,10399484,31870908,14690798,17361620,11864968 ) + ), + new GroupElementPreComp( + new FieldElement( -11307610,6210372,13206574,5806320,-29017692,-13967200,-12331205,-7486601,-25578460,-16240689 ), + new FieldElement( 14668462,-12270235,26039039,15305210,25515617,4542480,10453892,6577524,9145645,-6443880 ), + new FieldElement( 5974874,3053895,-9433049,-10385191,-31865124,3225009,-7972642,3936128,-5652273,-3050304 ) + ), + new GroupElementPreComp( + new FieldElement( 30625386,-4729400,-25555961,-12792866,-20484575,7695099,17097188,-16303496,-27999779,1803632 ), + new FieldElement( -3553091,9865099,-5228566,4272701,-5673832,-16689700,14911344,12196514,-21405489,7047412 ), + new FieldElement( 20093277,9920966,-11138194,-5343857,13161587,12044805,-32856851,4124601,-32343828,-10257566 ) + ), + new GroupElementPreComp( + new FieldElement( -20788824,14084654,-13531713,7842147,19119038,-13822605,4752377,-8714640,-21679658,2288038 ), + new FieldElement( -26819236,-3283715,29965059,3039786,-14473765,2540457,29457502,14625692,-24819617,12570232 ), + new FieldElement( -1063558,-11551823,16920318,12494842,1278292,-5869109,-21159943,-3498680,-11974704,4724943 ) + ), + new GroupElementPreComp( + new FieldElement( 17960970,-11775534,-4140968,-9702530,-8876562,-1410617,-12907383,-8659932,-29576300,1903856 ), + new FieldElement( 23134274,-14279132,-10681997,-1611936,20684485,15770816,-12989750,3190296,26955097,14109738 ), + new FieldElement( 15308788,5320727,-30113809,-14318877,22902008,7767164,29425325,-11277562,31960942,11934971 ) + ), + new GroupElementPreComp( + new FieldElement( -27395711,8435796,4109644,12222639,-24627868,14818669,20638173,4875028,10491392,1379718 ), + new FieldElement( -13159415,9197841,3875503,-8936108,-1383712,-5879801,33518459,16176658,21432314,12180697 ), + new FieldElement( -11787308,11500838,13787581,-13832590,-22430679,10140205,1465425,12689540,-10301319,-13872883 ) + ), + }, + new[]{ + new GroupElementPreComp( + new FieldElement( 5414091,-15386041,-21007664,9643570,12834970,1186149,-2622916,-1342231,26128231,6032912 ), + new FieldElement( -26337395,-13766162,32496025,-13653919,17847801,-12669156,3604025,8316894,-25875034,-10437358 ), + new FieldElement( 3296484,6223048,24680646,-12246460,-23052020,5903205,-8862297,-4639164,12376617,3188849 ) + ), + new GroupElementPreComp( + new FieldElement( 29190488,-14659046,27549113,-1183516,3520066,-10697301,32049515,-7309113,-16109234,-9852307 ), + new FieldElement( -14744486,-9309156,735818,-598978,-20407687,-5057904,25246078,-15795669,18640741,-960977 ), + new FieldElement( -6928835,-16430795,10361374,5642961,4910474,12345252,-31638386,-494430,10530747,1053335 ) + ), + new GroupElementPreComp( + new FieldElement( -29265967,-14186805,-13538216,-12117373,-19457059,-10655384,-31462369,-2948985,24018831,15026644 ), + new FieldElement( -22592535,-3145277,-2289276,5953843,-13440189,9425631,25310643,13003497,-2314791,-15145616 ), + new FieldElement( -27419985,-603321,-8043984,-1669117,-26092265,13987819,-27297622,187899,-23166419,-2531735 ) + ), + new GroupElementPreComp( + new FieldElement( -21744398,-13810475,1844840,5021428,-10434399,-15911473,9716667,16266922,-5070217,726099 ), + new FieldElement( 29370922,-6053998,7334071,-15342259,9385287,2247707,-13661962,-4839461,30007388,-15823341 ), + new FieldElement( -936379,16086691,23751945,-543318,-1167538,-5189036,9137109,730663,9835848,4555336 ) + ), + new GroupElementPreComp( + new FieldElement( -23376435,1410446,-22253753,-12899614,30867635,15826977,17693930,544696,-11985298,12422646 ), + new FieldElement( 31117226,-12215734,-13502838,6561947,-9876867,-12757670,-5118685,-4096706,29120153,13924425 ), + new FieldElement( -17400879,-14233209,19675799,-2734756,-11006962,-5858820,-9383939,-11317700,7240931,-237388 ) + ), + new GroupElementPreComp( + new FieldElement( -31361739,-11346780,-15007447,-5856218,-22453340,-12152771,1222336,4389483,3293637,-15551743 ), + new FieldElement( -16684801,-14444245,11038544,11054958,-13801175,-3338533,-24319580,7733547,12796905,-6335822 ), + new FieldElement( -8759414,-10817836,-25418864,10783769,-30615557,-9746811,-28253339,3647836,3222231,-11160462 ) + ), + new GroupElementPreComp( + new FieldElement( 18606113,1693100,-25448386,-15170272,4112353,10045021,23603893,-2048234,-7550776,2484985 ), + new FieldElement( 9255317,-3131197,-12156162,-1004256,13098013,-9214866,16377220,-2102812,-19802075,-3034702 ), + new FieldElement( -22729289,7496160,-5742199,11329249,19991973,-3347502,-31718148,9936966,-30097688,-10618797 ) + ), + new GroupElementPreComp( + new FieldElement( 21878590,-5001297,4338336,13643897,-3036865,13160960,19708896,5415497,-7360503,-4109293 ), + new FieldElement( 27736861,10103576,12500508,8502413,-3413016,-9633558,10436918,-1550276,-23659143,-8132100 ), + new FieldElement( 19492550,-12104365,-29681976,-852630,-3208171,12403437,30066266,8367329,13243957,8709688 ) + ), + }, + new[]{ + new GroupElementPreComp( + new FieldElement( 12015105,2801261,28198131,10151021,24818120,-4743133,-11194191,-5645734,5150968,7274186 ), + new FieldElement( 2831366,-12492146,1478975,6122054,23825128,-12733586,31097299,6083058,31021603,-9793610 ), + new FieldElement( -2529932,-2229646,445613,10720828,-13849527,-11505937,-23507731,16354465,15067285,-14147707 ) + ), + new GroupElementPreComp( + new FieldElement( 7840942,14037873,-33364863,15934016,-728213,-3642706,21403988,1057586,-19379462,-12403220 ), + new FieldElement( 915865,-16469274,15608285,-8789130,-24357026,6060030,-17371319,8410997,-7220461,16527025 ), + new FieldElement( 32922597,-556987,20336074,-16184568,10903705,-5384487,16957574,52992,23834301,6588044 ) + ), + new GroupElementPreComp( + new FieldElement( 32752030,11232950,3381995,-8714866,22652988,-10744103,17159699,16689107,-20314580,-1305992 ), + new FieldElement( -4689649,9166776,-25710296,-10847306,11576752,12733943,7924251,-2752281,1976123,-7249027 ), + new FieldElement( 21251222,16309901,-2983015,-6783122,30810597,12967303,156041,-3371252,12331345,-8237197 ) + ), + new GroupElementPreComp( + new FieldElement( 8651614,-4477032,-16085636,-4996994,13002507,2950805,29054427,-5106970,10008136,-4667901 ), + new FieldElement( 31486080,15114593,-14261250,12951354,14369431,-7387845,16347321,-13662089,8684155,-10532952 ), + new FieldElement( 19443825,11385320,24468943,-9659068,-23919258,2187569,-26263207,-6086921,31316348,14219878 ) + ), + new GroupElementPreComp( + new FieldElement( -28594490,1193785,32245219,11392485,31092169,15722801,27146014,6992409,29126555,9207390 ), + new FieldElement( 32382935,1110093,18477781,11028262,-27411763,-7548111,-4980517,10843782,-7957600,-14435730 ), + new FieldElement( 2814918,7836403,27519878,-7868156,-20894015,-11553689,-21494559,8550130,28346258,1994730 ) + ), + new GroupElementPreComp( + new FieldElement( -19578299,8085545,-14000519,-3948622,2785838,-16231307,-19516951,7174894,22628102,8115180 ), + new FieldElement( -30405132,955511,-11133838,-15078069,-32447087,-13278079,-25651578,3317160,-9943017,930272 ), + new FieldElement( -15303681,-6833769,28856490,1357446,23421993,1057177,24091212,-1388970,-22765376,-10650715 ) + ), + new GroupElementPreComp( + new FieldElement( -22751231,-5303997,-12907607,-12768866,-15811511,-7797053,-14839018,-16554220,-1867018,8398970 ), + new FieldElement( -31969310,2106403,-4736360,1362501,12813763,16200670,22981545,-6291273,18009408,-15772772 ), + new FieldElement( -17220923,-9545221,-27784654,14166835,29815394,7444469,29551787,-3727419,19288549,1325865 ) + ), + new GroupElementPreComp( + new FieldElement( 15100157,-15835752,-23923978,-1005098,-26450192,15509408,12376730,-3479146,33166107,-8042750 ), + new FieldElement( 20909231,13023121,-9209752,16251778,-5778415,-8094914,12412151,10018715,2213263,-13878373 ), + new FieldElement( 32529814,-11074689,30361439,-16689753,-9135940,1513226,22922121,6382134,-5766928,8371348 ) + ), + }, + new[]{ + new GroupElementPreComp( + new FieldElement( 9923462,11271500,12616794,3544722,-29998368,-1721626,12891687,-8193132,-26442943,10486144 ), + new FieldElement( -22597207,-7012665,8587003,-8257861,4084309,-12970062,361726,2610596,-23921530,-11455195 ), + new FieldElement( 5408411,-1136691,-4969122,10561668,24145918,14240566,31319731,-4235541,19985175,-3436086 ) + ), + new GroupElementPreComp( + new FieldElement( -13994457,16616821,14549246,3341099,32155958,13648976,-17577068,8849297,65030,8370684 ), + new FieldElement( -8320926,-12049626,31204563,5839400,-20627288,-1057277,-19442942,6922164,12743482,-9800518 ), + new FieldElement( -2361371,12678785,28815050,4759974,-23893047,4884717,23783145,11038569,18800704,255233 ) + ), + new GroupElementPreComp( + new FieldElement( -5269658,-1773886,13957886,7990715,23132995,728773,13393847,9066957,19258688,-14753793 ), + new FieldElement( -2936654,-10827535,-10432089,14516793,-3640786,4372541,-31934921,2209390,-1524053,2055794 ), + new FieldElement( 580882,16705327,5468415,-2683018,-30926419,-14696000,-7203346,-8994389,-30021019,7394435 ) + ), + new GroupElementPreComp( + new FieldElement( 23838809,1822728,-15738443,15242727,8318092,-3733104,-21672180,-3492205,-4821741,14799921 ), + new FieldElement( 13345610,9759151,3371034,-16137791,16353039,8577942,31129804,13496856,-9056018,7402518 ), + new FieldElement( 2286874,-4435931,-20042458,-2008336,-13696227,5038122,11006906,-15760352,8205061,1607563 ) + ), + new GroupElementPreComp( + new FieldElement( 14414086,-8002132,3331830,-3208217,22249151,-5594188,18364661,-2906958,30019587,-9029278 ), + new FieldElement( -27688051,1585953,-10775053,931069,-29120221,-11002319,-14410829,12029093,9944378,8024 ), + new FieldElement( 4368715,-3709630,29874200,-15022983,-20230386,-11410704,-16114594,-999085,-8142388,5640030 ) + ), + new GroupElementPreComp( + new FieldElement( 10299610,13746483,11661824,16234854,7630238,5998374,9809887,-16694564,15219798,-14327783 ), + new FieldElement( 27425505,-5719081,3055006,10660664,23458024,595578,-15398605,-1173195,-18342183,9742717 ), + new FieldElement( 6744077,2427284,26042789,2720740,-847906,1118974,32324614,7406442,12420155,1994844 ) + ), + new GroupElementPreComp( + new FieldElement( 14012521,-5024720,-18384453,-9578469,-26485342,-3936439,-13033478,-10909803,24319929,-6446333 ), + new FieldElement( 16412690,-4507367,10772641,15929391,-17068788,-4658621,10555945,-10484049,-30102368,-4739048 ), + new FieldElement( 22397382,-7767684,-9293161,-12792868,17166287,-9755136,-27333065,6199366,21880021,-12250760 ) + ), + new GroupElementPreComp( + new FieldElement( -4283307,5368523,-31117018,8163389,-30323063,3209128,16557151,8890729,8840445,4957760 ), + new FieldElement( -15447727,709327,-6919446,-10870178,-29777922,6522332,-21720181,12130072,-14796503,5005757 ), + new FieldElement( -2114751,-14308128,23019042,15765735,-25269683,6002752,10183197,-13239326,-16395286,-2176112 ) + ), + }, + new[]{ + new GroupElementPreComp( + new FieldElement( -19025756,1632005,13466291,-7995100,-23640451,16573537,-32013908,-3057104,22208662,2000468 ), + new FieldElement( 3065073,-1412761,-25598674,-361432,-17683065,-5703415,-8164212,11248527,-3691214,-7414184 ), + new FieldElement( 10379208,-6045554,8877319,1473647,-29291284,-12507580,16690915,2553332,-3132688,16400289 ) + ), + new GroupElementPreComp( + new FieldElement( 15716668,1254266,-18472690,7446274,-8448918,6344164,-22097271,-7285580,26894937,9132066 ), + new FieldElement( 24158887,12938817,11085297,-8177598,-28063478,-4457083,-30576463,64452,-6817084,-2692882 ), + new FieldElement( 13488534,7794716,22236231,5989356,25426474,-12578208,2350710,-3418511,-4688006,2364226 ) + ), + new GroupElementPreComp( + new FieldElement( 16335052,9132434,25640582,6678888,1725628,8517937,-11807024,-11697457,15445875,-7798101 ), + new FieldElement( 29004207,-7867081,28661402,-640412,-12794003,-7943086,31863255,-4135540,-278050,-15759279 ), + new FieldElement( -6122061,-14866665,-28614905,14569919,-10857999,-3591829,10343412,-6976290,-29828287,-10815811 ) + ), + new GroupElementPreComp( + new FieldElement( 27081650,3463984,14099042,-4517604,1616303,-6205604,29542636,15372179,17293797,960709 ), + new FieldElement( 20263915,11434237,-5765435,11236810,13505955,-10857102,-16111345,6493122,-19384511,7639714 ), + new FieldElement( -2830798,-14839232,25403038,-8215196,-8317012,-16173699,18006287,-16043750,29994677,-15808121 ) + ), + new GroupElementPreComp( + new FieldElement( 9769828,5202651,-24157398,-13631392,-28051003,-11561624,-24613141,-13860782,-31184575,709464 ), + new FieldElement( 12286395,13076066,-21775189,-1176622,-25003198,4057652,-32018128,-8890874,16102007,13205847 ), + new FieldElement( 13733362,5599946,10557076,3195751,-5557991,8536970,-25540170,8525972,10151379,10394400 ) + ), + new GroupElementPreComp( + new FieldElement( 4024660,-16137551,22436262,12276534,-9099015,-2686099,19698229,11743039,-33302334,8934414 ), + new FieldElement( -15879800,-4525240,-8580747,-2934061,14634845,-698278,-9449077,3137094,-11536886,11721158 ), + new FieldElement( 17555939,-5013938,8268606,2331751,-22738815,9761013,9319229,8835153,-9205489,-1280045 ) + ), + new GroupElementPreComp( + new FieldElement( -461409,-7830014,20614118,16688288,-7514766,-4807119,22300304,505429,6108462,-6183415 ), + new FieldElement( -5070281,12367917,-30663534,3234473,32617080,-8422642,29880583,-13483331,-26898490,-7867459 ), + new FieldElement( -31975283,5726539,26934134,10237677,-3173717,-605053,24199304,3795095,7592688,-14992079 ) + ), + new GroupElementPreComp( + new FieldElement( 21594432,-14964228,17466408,-4077222,32537084,2739898,6407723,12018833,-28256052,4298412 ), + new FieldElement( -20650503,-11961496,-27236275,570498,3767144,-1717540,13891942,-1569194,13717174,10805743 ), + new FieldElement( -14676630,-15644296,15287174,11927123,24177847,-8175568,-796431,14860609,-26938930,-5863836 ) + ), + }, + new[]{ + new GroupElementPreComp( + new FieldElement( 12962541,5311799,-10060768,11658280,18855286,-7954201,13286263,-12808704,-4381056,9882022 ), + new FieldElement( 18512079,11319350,-20123124,15090309,18818594,5271736,-22727904,3666879,-23967430,-3299429 ), + new FieldElement( -6789020,-3146043,16192429,13241070,15898607,-14206114,-10084880,-6661110,-2403099,5276065 ) + ), + new GroupElementPreComp( + new FieldElement( 30169808,-5317648,26306206,-11750859,27814964,7069267,7152851,3684982,1449224,13082861 ), + new FieldElement( 10342826,3098505,2119311,193222,25702612,12233820,23697382,15056736,-21016438,-8202000 ), + new FieldElement( -33150110,3261608,22745853,7948688,19370557,-15177665,-26171976,6482814,-10300080,-11060101 ) + ), + new GroupElementPreComp( + new FieldElement( 32869458,-5408545,25609743,15678670,-10687769,-15471071,26112421,2521008,-22664288,6904815 ), + new FieldElement( 29506923,4457497,3377935,-9796444,-30510046,12935080,1561737,3841096,-29003639,-6657642 ), + new FieldElement( 10340844,-6630377,-18656632,-2278430,12621151,-13339055,30878497,-11824370,-25584551,5181966 ) + ), + new GroupElementPreComp( + new FieldElement( 25940115,-12658025,17324188,-10307374,-8671468,15029094,24396252,-16450922,-2322852,-12388574 ), + new FieldElement( -21765684,9916823,-1300409,4079498,-1028346,11909559,1782390,12641087,20603771,-6561742 ), + new FieldElement( -18882287,-11673380,24849422,11501709,13161720,-4768874,1925523,11914390,4662781,7820689 ) + ), + new GroupElementPreComp( + new FieldElement( 12241050,-425982,8132691,9393934,32846760,-1599620,29749456,12172924,16136752,15264020 ), + new FieldElement( -10349955,-14680563,-8211979,2330220,-17662549,-14545780,10658213,6671822,19012087,3772772 ), + new FieldElement( 3753511,-3421066,10617074,2028709,14841030,-6721664,28718732,-15762884,20527771,12988982 ) + ), + new GroupElementPreComp( + new FieldElement( -14822485,-5797269,-3707987,12689773,-898983,-10914866,-24183046,-10564943,3299665,-12424953 ), + new FieldElement( -16777703,-15253301,-9642417,4978983,3308785,8755439,6943197,6461331,-25583147,8991218 ), + new FieldElement( -17226263,1816362,-1673288,-6086439,31783888,-8175991,-32948145,7417950,-30242287,1507265 ) + ), + new GroupElementPreComp( + new FieldElement( 29692663,6829891,-10498800,4334896,20945975,-11906496,-28887608,8209391,14606362,-10647073 ), + new FieldElement( -3481570,8707081,32188102,5672294,22096700,1711240,-33020695,9761487,4170404,-2085325 ), + new FieldElement( -11587470,14855945,-4127778,-1531857,-26649089,15084046,22186522,16002000,-14276837,-8400798 ) + ), + new GroupElementPreComp( + new FieldElement( -4811456,13761029,-31703877,-2483919,-3312471,7869047,-7113572,-9620092,13240845,10965870 ), + new FieldElement( -7742563,-8256762,-14768334,-13656260,-23232383,12387166,4498947,14147411,29514390,4302863 ), + new FieldElement( -13413405,-12407859,20757302,-13801832,14785143,8976368,-5061276,-2144373,17846988,-13971927 ) + ), + }, + new[]{ + new GroupElementPreComp( + new FieldElement( -2244452,-754728,-4597030,-1066309,-6247172,1455299,-21647728,-9214789,-5222701,12650267 ), + new FieldElement( -9906797,-16070310,21134160,12198166,-27064575,708126,387813,13770293,-19134326,10958663 ), + new FieldElement( 22470984,12369526,23446014,-5441109,-21520802,-9698723,-11772496,-11574455,-25083830,4271862 ) + ), + new GroupElementPreComp( + new FieldElement( -25169565,-10053642,-19909332,15361595,-5984358,2159192,75375,-4278529,-32526221,8469673 ), + new FieldElement( 15854970,4148314,-8893890,7259002,11666551,13824734,-30531198,2697372,24154791,-9460943 ), + new FieldElement( 15446137,-15806644,29759747,14019369,30811221,-9610191,-31582008,12840104,24913809,9815020 ) + ), + new GroupElementPreComp( + new FieldElement( -4709286,-5614269,-31841498,-12288893,-14443537,10799414,-9103676,13438769,18735128,9466238 ), + new FieldElement( 11933045,9281483,5081055,-5183824,-2628162,-4905629,-7727821,-10896103,-22728655,16199064 ), + new FieldElement( 14576810,379472,-26786533,-8317236,-29426508,-10812974,-102766,1876699,30801119,2164795 ) + ), + new GroupElementPreComp( + new FieldElement( 15995086,3199873,13672555,13712240,-19378835,-4647646,-13081610,-15496269,-13492807,1268052 ), + new FieldElement( -10290614,-3659039,-3286592,10948818,23037027,3794475,-3470338,-12600221,-17055369,3565904 ), + new FieldElement( 29210088,-9419337,-5919792,-4952785,10834811,-13327726,-16512102,-10820713,-27162222,-14030531 ) + ), + new GroupElementPreComp( + new FieldElement( -13161890,15508588,16663704,-8156150,-28349942,9019123,-29183421,-3769423,2244111,-14001979 ), + new FieldElement( -5152875,-3800936,-9306475,-6071583,16243069,14684434,-25673088,-16180800,13491506,4641841 ), + new FieldElement( 10813417,643330,-19188515,-728916,30292062,-16600078,27548447,-7721242,14476989,-12767431 ) + ), + new GroupElementPreComp( + new FieldElement( 10292079,9984945,6481436,8279905,-7251514,7032743,27282937,-1644259,-27912810,12651324 ), + new FieldElement( -31185513,-813383,22271204,11835308,10201545,15351028,17099662,3988035,21721536,-3148940 ), + new FieldElement( 10202177,-6545839,-31373232,-9574638,-32150642,-8119683,-12906320,3852694,13216206,14842320 ) + ), + new GroupElementPreComp( + new FieldElement( -15815640,-10601066,-6538952,-7258995,-6984659,-6581778,-31500847,13765824,-27434397,9900184 ), + new FieldElement( 14465505,-13833331,-32133984,-14738873,-27443187,12990492,33046193,15796406,-7051866,-8040114 ), + new FieldElement( 30924417,-8279620,6359016,-12816335,16508377,9071735,-25488601,15413635,9524356,-7018878 ) + ), + new GroupElementPreComp( + new FieldElement( 12274201,-13175547,32627641,-1785326,6736625,13267305,5237659,-5109483,15663516,4035784 ), + new FieldElement( -2951309,8903985,17349946,601635,-16432815,-4612556,-13732739,-15889334,-22258478,4659091 ), + new FieldElement( -16916263,-4952973,-30393711,-15158821,20774812,15897498,5736189,15026997,-2178256,-13455585 ) + ), + }, + new[]{ + new GroupElementPreComp( + new FieldElement( -8858980,-2219056,28571666,-10155518,-474467,-10105698,-3801496,278095,23440562,-290208 ), + new FieldElement( 10226241,-5928702,15139956,120818,-14867693,5218603,32937275,11551483,-16571960,-7442864 ), + new FieldElement( 17932739,-12437276,-24039557,10749060,11316803,7535897,22503767,5561594,-3646624,3898661 ) + ), + new GroupElementPreComp( + new FieldElement( 7749907,-969567,-16339731,-16464,-25018111,15122143,-1573531,7152530,21831162,1245233 ), + new FieldElement( 26958459,-14658026,4314586,8346991,-5677764,11960072,-32589295,-620035,-30402091,-16716212 ), + new FieldElement( -12165896,9166947,33491384,13673479,29787085,13096535,6280834,14587357,-22338025,13987525 ) + ), + new GroupElementPreComp( + new FieldElement( -24349909,7778775,21116000,15572597,-4833266,-5357778,-4300898,-5124639,-7469781,-2858068 ), + new FieldElement( 9681908,-6737123,-31951644,13591838,-6883821,386950,31622781,6439245,-14581012,4091397 ), + new FieldElement( -8426427,1470727,-28109679,-1596990,3978627,-5123623,-19622683,12092163,29077877,-14741988 ) + ), + new GroupElementPreComp( + new FieldElement( 5269168,-6859726,-13230211,-8020715,25932563,1763552,-5606110,-5505881,-20017847,2357889 ), + new FieldElement( 32264008,-15407652,-5387735,-1160093,-2091322,-3946900,23104804,-12869908,5727338,189038 ), + new FieldElement( 14609123,-8954470,-6000566,-16622781,-14577387,-7743898,-26745169,10942115,-25888931,-14884697 ) + ), + new GroupElementPreComp( + new FieldElement( 20513500,5557931,-15604613,7829531,26413943,-2019404,-21378968,7471781,13913677,-5137875 ), + new FieldElement( -25574376,11967826,29233242,12948236,-6754465,4713227,-8940970,14059180,12878652,8511905 ), + new FieldElement( -25656801,3393631,-2955415,-7075526,-2250709,9366908,-30223418,6812974,5568676,-3127656 ) + ), + new GroupElementPreComp( + new FieldElement( 11630004,12144454,2116339,13606037,27378885,15676917,-17408753,-13504373,-14395196,8070818 ), + new FieldElement( 27117696,-10007378,-31282771,-5570088,1127282,12772488,-29845906,10483306,-11552749,-1028714 ), + new FieldElement( 10637467,-5688064,5674781,1072708,-26343588,-6982302,-1683975,9177853,-27493162,15431203 ) + ), + new GroupElementPreComp( + new FieldElement( 20525145,10892566,-12742472,12779443,-29493034,16150075,-28240519,14943142,-15056790,-7935931 ), + new FieldElement( -30024462,5626926,-551567,-9981087,753598,11981191,25244767,-3239766,-3356550,9594024 ), + new FieldElement( -23752644,2636870,-5163910,-10103818,585134,7877383,11345683,-6492290,13352335,-10977084 ) + ), + new GroupElementPreComp( + new FieldElement( -1931799,-5407458,3304649,-12884869,17015806,-4877091,-29783850,-7752482,-13215537,-319204 ), + new FieldElement( 20239939,6607058,6203985,3483793,-18386976,-779229,-20723742,15077870,-22750759,14523817 ), + new FieldElement( 27406042,-6041657,27423596,-4497394,4996214,10002360,-28842031,-4545494,-30172742,-4805667 ) + ), + }, + new[]{ + new GroupElementPreComp( + new FieldElement( 11374242,12660715,17861383,-12540833,10935568,1099227,-13886076,-9091740,-27727044,11358504 ), + new FieldElement( -12730809,10311867,1510375,10778093,-2119455,-9145702,32676003,11149336,-26123651,4985768 ), + new FieldElement( -19096303,341147,-6197485,-239033,15756973,-8796662,-983043,13794114,-19414307,-15621255 ) + ), + new GroupElementPreComp( + new FieldElement( 6490081,11940286,25495923,-7726360,8668373,-8751316,3367603,6970005,-1691065,-9004790 ), + new FieldElement( 1656497,13457317,15370807,6364910,13605745,8362338,-19174622,-5475723,-16796596,-5031438 ), + new FieldElement( -22273315,-13524424,-64685,-4334223,-18605636,-10921968,-20571065,-7007978,-99853,-10237333 ) + ), + new GroupElementPreComp( + new FieldElement( 17747465,10039260,19368299,-4050591,-20630635,-16041286,31992683,-15857976,-29260363,-5511971 ), + new FieldElement( 31932027,-4986141,-19612382,16366580,22023614,88450,11371999,-3744247,4882242,-10626905 ), + new FieldElement( 29796507,37186,19818052,10115756,-11829032,3352736,18551198,3272828,-5190932,-4162409 ) + ), + new GroupElementPreComp( + new FieldElement( 12501286,4044383,-8612957,-13392385,-32430052,5136599,-19230378,-3529697,330070,-3659409 ), + new FieldElement( 6384877,2899513,17807477,7663917,-2358888,12363165,25366522,-8573892,-271295,12071499 ), + new FieldElement( -8365515,-4042521,25133448,-4517355,-6211027,2265927,-32769618,1936675,-5159697,3829363 ) + ), + new GroupElementPreComp( + new FieldElement( 28425966,-5835433,-577090,-4697198,-14217555,6870930,7921550,-6567787,26333140,14267664 ), + new FieldElement( -11067219,11871231,27385719,-10559544,-4585914,-11189312,10004786,-8709488,-21761224,8930324 ), + new FieldElement( -21197785,-16396035,25654216,-1725397,12282012,11008919,1541940,4757911,-26491501,-16408940 ) + ), + new GroupElementPreComp( + new FieldElement( 13537262,-7759490,-20604840,10961927,-5922820,-13218065,-13156584,6217254,-15943699,13814990 ), + new FieldElement( -17422573,15157790,18705543,29619,24409717,-260476,27361681,9257833,-1956526,-1776914 ), + new FieldElement( -25045300,-10191966,15366585,15166509,-13105086,8423556,-29171540,12361135,-18685978,4578290 ) + ), + new GroupElementPreComp( + new FieldElement( 24579768,3711570,1342322,-11180126,-27005135,14124956,-22544529,14074919,21964432,8235257 ), + new FieldElement( -6528613,-2411497,9442966,-5925588,12025640,-1487420,-2981514,-1669206,13006806,2355433 ), + new FieldElement( -16304899,-13605259,-6632427,-5142349,16974359,-10911083,27202044,1719366,1141648,-12796236 ) + ), + new GroupElementPreComp( + new FieldElement( -12863944,-13219986,-8318266,-11018091,-6810145,-4843894,13475066,-3133972,32674895,13715045 ), + new FieldElement( 11423335,-5468059,32344216,8962751,24989809,9241752,-13265253,16086212,-28740881,-15642093 ), + new FieldElement( -1409668,12530728,-6368726,10847387,19531186,-14132160,-11709148,7791794,-27245943,4383347 ) + ), + }, + new[]{ + new GroupElementPreComp( + new FieldElement( -28970898,5271447,-1266009,-9736989,-12455236,16732599,-4862407,-4906449,27193557,6245191 ), + new FieldElement( -15193956,5362278,-1783893,2695834,4960227,12840725,23061898,3260492,22510453,8577507 ), + new FieldElement( -12632451,11257346,-32692994,13548177,-721004,10879011,31168030,13952092,-29571492,-3635906 ) + ), + new GroupElementPreComp( + new FieldElement( 3877321,-9572739,32416692,5405324,-11004407,-13656635,3759769,11935320,5611860,8164018 ), + new FieldElement( -16275802,14667797,15906460,12155291,-22111149,-9039718,32003002,-8832289,5773085,-8422109 ), + new FieldElement( -23788118,-8254300,1950875,8937633,18686727,16459170,-905725,12376320,31632953,190926 ) + ), + new GroupElementPreComp( + new FieldElement( -24593607,-16138885,-8423991,13378746,14162407,6901328,-8288749,4508564,-25341555,-3627528 ), + new FieldElement( 8884438,-5884009,6023974,10104341,-6881569,-4941533,18722941,-14786005,-1672488,827625 ), + new FieldElement( -32720583,-16289296,-32503547,7101210,13354605,2659080,-1800575,-14108036,-24878478,1541286 ) + ), + new GroupElementPreComp( + new FieldElement( 2901347,-1117687,3880376,-10059388,-17620940,-3612781,-21802117,-3567481,20456845,-1885033 ), + new FieldElement( 27019610,12299467,-13658288,-1603234,-12861660,-4861471,-19540150,-5016058,29439641,15138866 ), + new FieldElement( 21536104,-6626420,-32447818,-10690208,-22408077,5175814,-5420040,-16361163,7779328,109896 ) + ), + new GroupElementPreComp( + new FieldElement( 30279744,14648750,-8044871,6425558,13639621,-743509,28698390,12180118,23177719,-554075 ), + new FieldElement( 26572847,3405927,-31701700,12890905,-19265668,5335866,-6493768,2378492,4439158,-13279347 ), + new FieldElement( -22716706,3489070,-9225266,-332753,18875722,-1140095,14819434,-12731527,-17717757,-5461437 ) + ), + new GroupElementPreComp( + new FieldElement( -5056483,16566551,15953661,3767752,-10436499,15627060,-820954,2177225,8550082,-15114165 ), + new FieldElement( -18473302,16596775,-381660,15663611,22860960,15585581,-27844109,-3582739,-23260460,-8428588 ), + new FieldElement( -32480551,15707275,-8205912,-5652081,29464558,2713815,-22725137,15860482,-21902570,1494193 ) + ), + new GroupElementPreComp( + new FieldElement( -19562091,-14087393,-25583872,-9299552,13127842,759709,21923482,16529112,8742704,12967017 ), + new FieldElement( -28464899,1553205,32536856,-10473729,-24691605,-406174,-8914625,-2933896,-29903758,15553883 ), + new FieldElement( 21877909,3230008,9881174,10539357,-4797115,2841332,11543572,14513274,19375923,-12647961 ) + ), + new GroupElementPreComp( + new FieldElement( 8832269,-14495485,13253511,5137575,5037871,4078777,24880818,-6222716,2862653,9455043 ), + new FieldElement( 29306751,5123106,20245049,-14149889,9592566,8447059,-2077124,-2990080,15511449,4789663 ), + new FieldElement( -20679756,7004547,8824831,-9434977,-4045704,-3750736,-5754762,108893,23513200,16652362 ) + ), + }, + new[]{ + new GroupElementPreComp( + new FieldElement( -33256173,4144782,-4476029,-6579123,10770039,-7155542,-6650416,-12936300,-18319198,10212860 ), + new FieldElement( 2756081,8598110,7383731,-6859892,22312759,-1105012,21179801,2600940,-9988298,-12506466 ), + new FieldElement( -24645692,13317462,-30449259,-15653928,21365574,-10869657,11344424,864440,-2499677,-16710063 ) + ), + new GroupElementPreComp( + new FieldElement( -26432803,6148329,-17184412,-14474154,18782929,-275997,-22561534,211300,2719757,4940997 ), + new FieldElement( -1323882,3911313,-6948744,14759765,-30027150,7851207,21690126,8518463,26699843,5276295 ), + new FieldElement( -13149873,-6429067,9396249,365013,24703301,-10488939,1321586,149635,-15452774,7159369 ) + ), + new GroupElementPreComp( + new FieldElement( 9987780,-3404759,17507962,9505530,9731535,-2165514,22356009,8312176,22477218,-8403385 ), + new FieldElement( 18155857,-16504990,19744716,9006923,15154154,-10538976,24256460,-4864995,-22548173,9334109 ), + new FieldElement( 2986088,-4911893,10776628,-3473844,10620590,-7083203,-21413845,14253545,-22587149,536906 ) + ), + new GroupElementPreComp( + new FieldElement( 4377756,8115836,24567078,15495314,11625074,13064599,7390551,10589625,10838060,-15420424 ), + new FieldElement( -19342404,867880,9277171,-3218459,-14431572,-1986443,19295826,-15796950,6378260,699185 ), + new FieldElement( 7895026,4057113,-7081772,-13077756,-17886831,-323126,-716039,15693155,-5045064,-13373962 ) + ), + new GroupElementPreComp( + new FieldElement( -7737563,-5869402,-14566319,-7406919,11385654,13201616,31730678,-10962840,-3918636,-9669325 ), + new FieldElement( 10188286,-15770834,-7336361,13427543,22223443,14896287,30743455,7116568,-21786507,5427593 ), + new FieldElement( 696102,13206899,27047647,-10632082,15285305,-9853179,10798490,-4578720,19236243,12477404 ) + ), + new GroupElementPreComp( + new FieldElement( -11229439,11243796,-17054270,-8040865,-788228,-8167967,-3897669,11180504,-23169516,7733644 ), + new FieldElement( 17800790,-14036179,-27000429,-11766671,23887827,3149671,23466177,-10538171,10322027,15313801 ), + new FieldElement( 26246234,11968874,32263343,-5468728,6830755,-13323031,-15794704,-101982,-24449242,10890804 ) + ), + new GroupElementPreComp( + new FieldElement( -31365647,10271363,-12660625,-6267268,16690207,-13062544,-14982212,16484931,25180797,-5334884 ), + new FieldElement( -586574,10376444,-32586414,-11286356,19801893,10997610,2276632,9482883,316878,13820577 ), + new FieldElement( -9882808,-4510367,-2115506,16457136,-11100081,11674996,30756178,-7515054,30696930,-3712849 ) + ), + new GroupElementPreComp( + new FieldElement( 32988917,-9603412,12499366,7910787,-10617257,-11931514,-7342816,-9985397,-32349517,7392473 ), + new FieldElement( -8855661,15927861,9866406,-3649411,-2396914,-16655781,-30409476,-9134995,25112947,-2926644 ), + new FieldElement( -2504044,-436966,25621774,-5678772,15085042,-5479877,-24884878,-13526194,5537438,-13914319 ) + ), + }, + new[]{ + new GroupElementPreComp( + new FieldElement( -11225584,2320285,-9584280,10149187,-33444663,5808648,-14876251,-1729667,31234590,6090599 ), + new FieldElement( -9633316,116426,26083934,2897444,-6364437,-2688086,609721,15878753,-6970405,-9034768 ), + new FieldElement( -27757857,247744,-15194774,-9002551,23288161,-10011936,-23869595,6503646,20650474,1804084 ) + ), + new GroupElementPreComp( + new FieldElement( -27589786,15456424,8972517,8469608,15640622,4439847,3121995,-10329713,27842616,-202328 ), + new FieldElement( -15306973,2839644,22530074,10026331,4602058,5048462,28248656,5031932,-11375082,12714369 ), + new FieldElement( 20807691,-7270825,29286141,11421711,-27876523,-13868230,-21227475,1035546,-19733229,12796920 ) + ), + new GroupElementPreComp( + new FieldElement( 12076899,-14301286,-8785001,-11848922,-25012791,16400684,-17591495,-12899438,3480665,-15182815 ), + new FieldElement( -32361549,5457597,28548107,7833186,7303070,-11953545,-24363064,-15921875,-33374054,2771025 ), + new FieldElement( -21389266,421932,26597266,6860826,22486084,-6737172,-17137485,-4210226,-24552282,15673397 ) + ), + new GroupElementPreComp( + new FieldElement( -20184622,2338216,19788685,-9620956,-4001265,-8740893,-20271184,4733254,3727144,-12934448 ), + new FieldElement( 6120119,814863,-11794402,-622716,6812205,-15747771,2019594,7975683,31123697,-10958981 ), + new FieldElement( 30069250,-11435332,30434654,2958439,18399564,-976289,12296869,9204260,-16432438,9648165 ) + ), + new GroupElementPreComp( + new FieldElement( 32705432,-1550977,30705658,7451065,-11805606,9631813,3305266,5248604,-26008332,-11377501 ), + new FieldElement( 17219865,2375039,-31570947,-5575615,-19459679,9219903,294711,15298639,2662509,-16297073 ), + new FieldElement( -1172927,-7558695,-4366770,-4287744,-21346413,-8434326,32087529,-1222777,32247248,-14389861 ) + ), + new GroupElementPreComp( + new FieldElement( 14312628,1221556,17395390,-8700143,-4945741,-8684635,-28197744,-9637817,-16027623,-13378845 ), + new FieldElement( -1428825,-9678990,-9235681,6549687,-7383069,-468664,23046502,9803137,17597934,2346211 ), + new FieldElement( 18510800,15337574,26171504,981392,-22241552,7827556,-23491134,-11323352,3059833,-11782870 ) + ), + new GroupElementPreComp( + new FieldElement( 10141598,6082907,17829293,-1947643,9830092,13613136,-25556636,-5544586,-33502212,3592096 ), + new FieldElement( 33114168,-15889352,-26525686,-13343397,33076705,8716171,1151462,1521897,-982665,-6837803 ), + new FieldElement( -32939165,-4255815,23947181,-324178,-33072974,-12305637,-16637686,3891704,26353178,693168 ) + ), + new GroupElementPreComp( + new FieldElement( 30374239,1595580,-16884039,13186931,4600344,406904,9585294,-400668,31375464,14369965 ), + new FieldElement( -14370654,-7772529,1510301,6434173,-18784789,-6262728,32732230,-13108839,17901441,16011505 ), + new FieldElement( 18171223,-11934626,-12500402,15197122,-11038147,-15230035,-19172240,-16046376,8764035,12309598 ) + ), + }, + new[]{ + new GroupElementPreComp( + new FieldElement( 5975908,-5243188,-19459362,-9681747,-11541277,14015782,-23665757,1228319,17544096,-10593782 ), + new FieldElement( 5811932,-1715293,3442887,-2269310,-18367348,-8359541,-18044043,-15410127,-5565381,12348900 ), + new FieldElement( -31399660,11407555,25755363,6891399,-3256938,14872274,-24849353,8141295,-10632534,-585479 ) + ), + new GroupElementPreComp( + new FieldElement( -12675304,694026,-5076145,13300344,14015258,-14451394,-9698672,-11329050,30944593,1130208 ), + new FieldElement( 8247766,-6710942,-26562381,-7709309,-14401939,-14648910,4652152,2488540,23550156,-271232 ), + new FieldElement( 17294316,-3788438,7026748,15626851,22990044,113481,2267737,-5908146,-408818,-137719 ) + ), + new GroupElementPreComp( + new FieldElement( 16091085,-16253926,18599252,7340678,2137637,-1221657,-3364161,14550936,3260525,-7166271 ), + new FieldElement( -4910104,-13332887,18550887,10864893,-16459325,-7291596,-23028869,-13204905,-12748722,2701326 ), + new FieldElement( -8574695,16099415,4629974,-16340524,-20786213,-6005432,-10018363,9276971,11329923,1862132 ) + ), + new GroupElementPreComp( + new FieldElement( 14763076,-15903608,-30918270,3689867,3511892,10313526,-21951088,12219231,-9037963,-940300 ), + new FieldElement( 8894987,-3446094,6150753,3013931,301220,15693451,-31981216,-2909717,-15438168,11595570 ), + new FieldElement( 15214962,3537601,-26238722,-14058872,4418657,-15230761,13947276,10730794,-13489462,-4363670 ) + ), + new GroupElementPreComp( + new FieldElement( -2538306,7682793,32759013,263109,-29984731,-7955452,-22332124,-10188635,977108,699994 ), + new FieldElement( -12466472,4195084,-9211532,550904,-15565337,12917920,19118110,-439841,-30534533,-14337913 ), + new FieldElement( 31788461,-14507657,4799989,7372237,8808585,-14747943,9408237,-10051775,12493932,-5409317 ) + ), + new GroupElementPreComp( + new FieldElement( -25680606,5260744,-19235809,-6284470,-3695942,16566087,27218280,2607121,29375955,6024730 ), + new FieldElement( 842132,-2794693,-4763381,-8722815,26332018,-12405641,11831880,6985184,-9940361,2854096 ), + new FieldElement( -4847262,-7969331,2516242,-5847713,9695691,-7221186,16512645,960770,12121869,16648078 ) + ), + new GroupElementPreComp( + new FieldElement( -15218652,14667096,-13336229,2013717,30598287,-464137,-31504922,-7882064,20237806,2838411 ), + new FieldElement( -19288047,4453152,15298546,-16178388,22115043,-15972604,12544294,-13470457,1068881,-12499905 ), + new FieldElement( -9558883,-16518835,33238498,13506958,30505848,-1114596,-8486907,-2630053,12521378,4845654 ) + ), + new GroupElementPreComp( + new FieldElement( -28198521,10744108,-2958380,10199664,7759311,-13088600,3409348,-873400,-6482306,-12885870 ), + new FieldElement( -23561822,6230156,-20382013,10655314,-24040585,-11621172,10477734,-1240216,-3113227,13974498 ), + new FieldElement( 12966261,15550616,-32038948,-1615346,21025980,-629444,5642325,7188737,18895762,12629579 ) + ), + }, + new[]{ + new GroupElementPreComp( + new FieldElement( 14741879,-14946887,22177208,-11721237,1279741,8058600,11758140,789443,32195181,3895677 ), + new FieldElement( 10758205,15755439,-4509950,9243698,-4879422,6879879,-2204575,-3566119,-8982069,4429647 ), + new FieldElement( -2453894,15725973,-20436342,-10410672,-5803908,-11040220,-7135870,-11642895,18047436,-15281743 ) + ), + new GroupElementPreComp( + new FieldElement( -25173001,-11307165,29759956,11776784,-22262383,-15820455,10993114,-12850837,-17620701,-9408468 ), + new FieldElement( 21987233,700364,-24505048,14972008,-7774265,-5718395,32155026,2581431,-29958985,8773375 ), + new FieldElement( -25568350,454463,-13211935,16126715,25240068,8594567,20656846,12017935,-7874389,-13920155 ) + ), + new GroupElementPreComp( + new FieldElement( 6028182,6263078,-31011806,-11301710,-818919,2461772,-31841174,-5468042,-1721788,-2776725 ), + new FieldElement( -12278994,16624277,987579,-5922598,32908203,1248608,7719845,-4166698,28408820,6816612 ), + new FieldElement( -10358094,-8237829,19549651,-12169222,22082623,16147817,20613181,13982702,-10339570,5067943 ) + ), + new GroupElementPreComp( + new FieldElement( -30505967,-3821767,12074681,13582412,-19877972,2443951,-19719286,12746132,5331210,-10105944 ), + new FieldElement( 30528811,3601899,-1957090,4619785,-27361822,-15436388,24180793,-12570394,27679908,-1648928 ), + new FieldElement( 9402404,-13957065,32834043,10838634,-26580150,-13237195,26653274,-8685565,22611444,-12715406 ) + ), + new GroupElementPreComp( + new FieldElement( 22190590,1118029,22736441,15130463,-30460692,-5991321,19189625,-4648942,4854859,6622139 ), + new FieldElement( -8310738,-2953450,-8262579,-3388049,-10401731,-271929,13424426,-3567227,26404409,13001963 ), + new FieldElement( -31241838,-15415700,-2994250,8939346,11562230,-12840670,-26064365,-11621720,-15405155,11020693 ) + ), + new GroupElementPreComp( + new FieldElement( 1866042,-7949489,-7898649,-10301010,12483315,13477547,3175636,-12424163,28761762,1406734 ), + new FieldElement( -448555,-1777666,13018551,3194501,-9580420,-11161737,24760585,-4347088,25577411,-13378680 ), + new FieldElement( -24290378,4759345,-690653,-1852816,2066747,10693769,-29595790,9884936,-9368926,4745410 ) + ), + new GroupElementPreComp( + new FieldElement( -9141284,6049714,-19531061,-4341411,-31260798,9944276,-15462008,-11311852,10931924,-11931931 ), + new FieldElement( -16561513,14112680,-8012645,4817318,-8040464,-11414606,-22853429,10856641,-20470770,13434654 ), + new FieldElement( 22759489,-10073434,-16766264,-1871422,13637442,-10168091,1765144,-12654326,28445307,-5364710 ) + ), + new GroupElementPreComp( + new FieldElement( 29875063,12493613,2795536,-3786330,1710620,15181182,-10195717,-8788675,9074234,1167180 ), + new FieldElement( -26205683,11014233,-9842651,-2635485,-26908120,7532294,-18716888,-9535498,3843903,9367684 ), + new FieldElement( -10969595,-6403711,9591134,9582310,11349256,108879,16235123,8601684,-139197,4242895 ) + ), + }, + new[]{ + new GroupElementPreComp( + new FieldElement( 22092954,-13191123,-2042793,-11968512,32186753,-11517388,-6574341,2470660,-27417366,16625501 ), + new FieldElement( -11057722,3042016,13770083,-9257922,584236,-544855,-7770857,2602725,-27351616,14247413 ), + new FieldElement( 6314175,-10264892,-32772502,15957557,-10157730,168750,-8618807,14290061,27108877,-1180880 ) + ), + new GroupElementPreComp( + new FieldElement( -8586597,-7170966,13241782,10960156,-32991015,-13794596,33547976,-11058889,-27148451,981874 ), + new FieldElement( 22833440,9293594,-32649448,-13618667,-9136966,14756819,-22928859,-13970780,-10479804,-16197962 ), + new FieldElement( -7768587,3326786,-28111797,10783824,19178761,14905060,22680049,13906969,-15933690,3797899 ) + ), + new GroupElementPreComp( + new FieldElement( 21721356,-4212746,-12206123,9310182,-3882239,-13653110,23740224,-2709232,20491983,-8042152 ), + new FieldElement( 9209270,-15135055,-13256557,-6167798,-731016,15289673,25947805,15286587,30997318,-6703063 ), + new FieldElement( 7392032,16618386,23946583,-8039892,-13265164,-1533858,-14197445,-2321576,17649998,-250080 ) + ), + new GroupElementPreComp( + new FieldElement( -9301088,-14193827,30609526,-3049543,-25175069,-1283752,-15241566,-9525724,-2233253,7662146 ), + new FieldElement( -17558673,1763594,-33114336,15908610,-30040870,-12174295,7335080,-8472199,-3174674,3440183 ), + new FieldElement( -19889700,-5977008,-24111293,-9688870,10799743,-16571957,40450,-4431835,4862400,1133 ) + ), + new GroupElementPreComp( + new FieldElement( -32856209,-7873957,-5422389,14860950,-16319031,7956142,7258061,311861,-30594991,-7379421 ), + new FieldElement( -3773428,-1565936,28985340,7499440,24445838,9325937,29727763,16527196,18278453,15405622 ), + new FieldElement( -4381906,8508652,-19898366,-3674424,-5984453,15149970,-13313598,843523,-21875062,13626197 ) + ), + new GroupElementPreComp( + new FieldElement( 2281448,-13487055,-10915418,-2609910,1879358,16164207,-10783882,3953792,13340839,15928663 ), + new FieldElement( 31727126,-7179855,-18437503,-8283652,2875793,-16390330,-25269894,-7014826,-23452306,5964753 ), + new FieldElement( 4100420,-5959452,-17179337,6017714,-18705837,12227141,-26684835,11344144,2538215,-7570755 ) + ), + new GroupElementPreComp( + new FieldElement( -9433605,6123113,11159803,-2156608,30016280,14966241,-20474983,1485421,-629256,-15958862 ), + new FieldElement( -26804558,4260919,11851389,9658551,-32017107,16367492,-20205425,-13191288,11659922,-11115118 ), + new FieldElement( 26180396,10015009,-30844224,-8581293,5418197,9480663,2231568,-10170080,33100372,-1306171 ) + ), + new GroupElementPreComp( + new FieldElement( 15121113,-5201871,-10389905,15427821,-27509937,-15992507,21670947,4486675,-5931810,-14466380 ), + new FieldElement( 16166486,-9483733,-11104130,6023908,-31926798,-1364923,2340060,-16254968,-10735770,-10039824 ), + new FieldElement( 28042865,-3557089,-12126526,12259706,-3717498,-6945899,6766453,-8689599,18036436,5803270 ) + ), + }, + new[]{ + new GroupElementPreComp( + new FieldElement( -817581,6763912,11803561,1585585,10958447,-2671165,23855391,4598332,-6159431,-14117438 ), + new FieldElement( -31031306,-14256194,17332029,-2383520,31312682,-5967183,696309,50292,-20095739,11763584 ), + new FieldElement( -594563,-2514283,-32234153,12643980,12650761,14811489,665117,-12613632,-19773211,-10713562 ) + ), + new GroupElementPreComp( + new FieldElement( 30464590,-11262872,-4127476,-12734478,19835327,-7105613,-24396175,2075773,-17020157,992471 ), + new FieldElement( 18357185,-6994433,7766382,16342475,-29324918,411174,14578841,8080033,-11574335,-10601610 ), + new FieldElement( 19598397,10334610,12555054,2555664,18821899,-10339780,21873263,16014234,26224780,16452269 ) + ), + new GroupElementPreComp( + new FieldElement( -30223925,5145196,5944548,16385966,3976735,2009897,-11377804,-7618186,-20533829,3698650 ), + new FieldElement( 14187449,3448569,-10636236,-10810935,-22663880,-3433596,7268410,-10890444,27394301,12015369 ), + new FieldElement( 19695761,16087646,28032085,12999827,6817792,11427614,20244189,-1312777,-13259127,-3402461 ) + ), + new GroupElementPreComp( + new FieldElement( 30860103,12735208,-1888245,-4699734,-16974906,2256940,-8166013,12298312,-8550524,-10393462 ), + new FieldElement( -5719826,-11245325,-1910649,15569035,26642876,-7587760,-5789354,-15118654,-4976164,12651793 ), + new FieldElement( -2848395,9953421,11531313,-5282879,26895123,-12697089,-13118820,-16517902,9768698,-2533218 ) + ), + new GroupElementPreComp( + new FieldElement( -24719459,1894651,-287698,-4704085,15348719,-8156530,32767513,12765450,4940095,10678226 ), + new FieldElement( 18860224,15980149,-18987240,-1562570,-26233012,-11071856,-7843882,13944024,-24372348,16582019 ), + new FieldElement( -15504260,4970268,-29893044,4175593,-20993212,-2199756,-11704054,15444560,-11003761,7989037 ) + ), + new GroupElementPreComp( + new FieldElement( 31490452,5568061,-2412803,2182383,-32336847,4531686,-32078269,6200206,-19686113,-14800171 ), + new FieldElement( -17308668,-15879940,-31522777,-2831,-32887382,16375549,8680158,-16371713,28550068,-6857132 ), + new FieldElement( -28126887,-5688091,16837845,-1820458,-6850681,12700016,-30039981,4364038,1155602,5988841 ) + ), + new GroupElementPreComp( + new FieldElement( 21890435,-13272907,-12624011,12154349,-7831873,15300496,23148983,-4470481,24618407,8283181 ), + new FieldElement( -33136107,-10512751,9975416,6841041,-31559793,16356536,3070187,-7025928,1466169,10740210 ), + new FieldElement( -1509399,-15488185,-13503385,-10655916,32799044,909394,-13938903,-5779719,-32164649,-15327040 ) + ), + new GroupElementPreComp( + new FieldElement( 3960823,-14267803,-28026090,-15918051,-19404858,13146868,15567327,951507,-3260321,-573935 ), + new FieldElement( 24740841,5052253,-30094131,8961361,25877428,6165135,-24368180,14397372,-7380369,-6144105 ), + new FieldElement( -28888365,3510803,-28103278,-1158478,-11238128,-10631454,-15441463,-14453128,-1625486,-6494814 ) + ), + }, + new[]{ + new GroupElementPreComp( + new FieldElement( 793299,-9230478,8836302,-6235707,-27360908,-2369593,33152843,-4885251,-9906200,-621852 ), + new FieldElement( 5666233,525582,20782575,-8038419,-24538499,14657740,16099374,1468826,-6171428,-15186581 ), + new FieldElement( -4859255,-3779343,-2917758,-6748019,7778750,11688288,-30404353,-9871238,-1558923,-9863646 ) + ), + new GroupElementPreComp( + new FieldElement( 10896332,-7719704,824275,472601,-19460308,3009587,25248958,14783338,-30581476,-15757844 ), + new FieldElement( 10566929,12612572,-31944212,11118703,-12633376,12362879,21752402,8822496,24003793,14264025 ), + new FieldElement( 27713862,-7355973,-11008240,9227530,27050101,2504721,23886875,-13117525,13958495,-5732453 ) + ), + new GroupElementPreComp( + new FieldElement( -23481610,4867226,-27247128,3900521,29838369,-8212291,-31889399,-10041781,7340521,-15410068 ), + new FieldElement( 4646514,-8011124,-22766023,-11532654,23184553,8566613,31366726,-1381061,-15066784,-10375192 ), + new FieldElement( -17270517,12723032,-16993061,14878794,21619651,-6197576,27584817,3093888,-8843694,3849921 ) + ), + new GroupElementPreComp( + new FieldElement( -9064912,2103172,25561640,-15125738,-5239824,9582958,32477045,-9017955,5002294,-15550259 ), + new FieldElement( -12057553,-11177906,21115585,-13365155,8808712,-12030708,16489530,13378448,-25845716,12741426 ), + new FieldElement( -5946367,10645103,-30911586,15390284,-3286982,-7118677,24306472,15852464,28834118,-7646072 ) + ), + new GroupElementPreComp( + new FieldElement( -17335748,-9107057,-24531279,9434953,-8472084,-583362,-13090771,455841,20461858,5491305 ), + new FieldElement( 13669248,-16095482,-12481974,-10203039,-14569770,-11893198,-24995986,11293807,-28588204,-9421832 ), + new FieldElement( 28497928,6272777,-33022994,14470570,8906179,-1225630,18504674,-14165166,29867745,-8795943 ) + ), + new GroupElementPreComp( + new FieldElement( -16207023,13517196,-27799630,-13697798,24009064,-6373891,-6367600,-13175392,22853429,-4012011 ), + new FieldElement( 24191378,16712145,-13931797,15217831,14542237,1646131,18603514,-11037887,12876623,-2112447 ), + new FieldElement( 17902668,4518229,-411702,-2829247,26878217,5258055,-12860753,608397,16031844,3723494 ) + ), + new GroupElementPreComp( + new FieldElement( -28632773,12763728,-20446446,7577504,33001348,-13017745,17558842,-7872890,23896954,-4314245 ), + new FieldElement( -20005381,-12011952,31520464,605201,2543521,5991821,-2945064,7229064,-9919646,-8826859 ), + new FieldElement( 28816045,298879,-28165016,-15920938,19000928,-1665890,-12680833,-2949325,-18051778,-2082915 ) + ), + new GroupElementPreComp( + new FieldElement( 16000882,-344896,3493092,-11447198,-29504595,-13159789,12577740,16041268,-19715240,7847707 ), + new FieldElement( 10151868,10572098,27312476,7922682,14825339,4723128,-32855931,-6519018,-10020567,3852848 ), + new FieldElement( -11430470,15697596,-21121557,-4420647,5386314,15063598,16514493,-15932110,29330899,-15076224 ) + ), + }, + new[]{ + new GroupElementPreComp( + new FieldElement( -25499735,-4378794,-15222908,-6901211,16615731,2051784,3303702,15490,-27548796,12314391 ), + new FieldElement( 15683520,-6003043,18109120,-9980648,15337968,-5997823,-16717435,15921866,16103996,-3731215 ), + new FieldElement( -23169824,-10781249,13588192,-1628807,-3798557,-1074929,-19273607,5402699,-29815713,-9841101 ) + ), + new GroupElementPreComp( + new FieldElement( 23190676,2384583,-32714340,3462154,-29903655,-1529132,-11266856,8911517,-25205859,2739713 ), + new FieldElement( 21374101,-3554250,-33524649,9874411,15377179,11831242,-33529904,6134907,4931255,11987849 ), + new FieldElement( -7732,-2978858,-16223486,7277597,105524,-322051,-31480539,13861388,-30076310,10117930 ) + ), + new GroupElementPreComp( + new FieldElement( -29501170,-10744872,-26163768,13051539,-25625564,5089643,-6325503,6704079,12890019,15728940 ), + new FieldElement( -21972360,-11771379,-951059,-4418840,14704840,2695116,903376,-10428139,12885167,8311031 ), + new FieldElement( -17516482,5352194,10384213,-13811658,7506451,13453191,26423267,4384730,1888765,-5435404 ) + ), + new GroupElementPreComp( + new FieldElement( -25817338,-3107312,-13494599,-3182506,30896459,-13921729,-32251644,-12707869,-19464434,-3340243 ), + new FieldElement( -23607977,-2665774,-526091,4651136,5765089,4618330,6092245,14845197,17151279,-9854116 ), + new FieldElement( -24830458,-12733720,-15165978,10367250,-29530908,-265356,22825805,-7087279,-16866484,16176525 ) + ), + new GroupElementPreComp( + new FieldElement( -23583256,6564961,20063689,3798228,-4740178,7359225,2006182,-10363426,-28746253,-10197509 ), + new FieldElement( -10626600,-4486402,-13320562,-5125317,3432136,-6393229,23632037,-1940610,32808310,1099883 ), + new FieldElement( 15030977,5768825,-27451236,-2887299,-6427378,-15361371,-15277896,-6809350,2051441,-15225865 ) + ), + new GroupElementPreComp( + new FieldElement( -3362323,-7239372,7517890,9824992,23555850,295369,5148398,-14154188,-22686354,16633660 ), + new FieldElement( 4577086,-16752288,13249841,-15304328,19958763,-14537274,18559670,-10759549,8402478,-9864273 ), + new FieldElement( -28406330,-1051581,-26790155,-907698,-17212414,-11030789,9453451,-14980072,17983010,9967138 ) + ), + new GroupElementPreComp( + new FieldElement( -25762494,6524722,26585488,9969270,24709298,1220360,-1677990,7806337,17507396,3651560 ), + new FieldElement( -10420457,-4118111,14584639,15971087,-15768321,8861010,26556809,-5574557,-18553322,-11357135 ), + new FieldElement( 2839101,14284142,4029895,3472686,14402957,12689363,-26642121,8459447,-5605463,-7621941 ) + ), + new GroupElementPreComp( + new FieldElement( -4839289,-3535444,9744961,2871048,25113978,3187018,-25110813,-849066,17258084,-7977739 ), + new FieldElement( 18164541,-10595176,-17154882,-1542417,19237078,-9745295,23357533,-15217008,26908270,12150756 ), + new FieldElement( -30264870,-7647865,5112249,-7036672,-1499807,-6974257,43168,-5537701,-32302074,16215819 ) + ), + }, + new[]{ + new GroupElementPreComp( + new FieldElement( -6898905,9824394,-12304779,-4401089,-31397141,-6276835,32574489,12532905,-7503072,-8675347 ), + new FieldElement( -27343522,-16515468,-27151524,-10722951,946346,16291093,254968,7168080,21676107,-1943028 ), + new FieldElement( 21260961,-8424752,-16831886,-11920822,-23677961,3968121,-3651949,-6215466,-3556191,-7913075 ) + ), + new GroupElementPreComp( + new FieldElement( 16544754,13250366,-16804428,15546242,-4583003,12757258,-2462308,-8680336,-18907032,-9662799 ), + new FieldElement( -2415239,-15577728,18312303,4964443,-15272530,-12653564,26820651,16690659,25459437,-4564609 ), + new FieldElement( -25144690,11425020,28423002,-11020557,-6144921,-15826224,9142795,-2391602,-6432418,-1644817 ) + ), + new GroupElementPreComp( + new FieldElement( -23104652,6253476,16964147,-3768872,-25113972,-12296437,-27457225,-16344658,6335692,7249989 ), + new FieldElement( -30333227,13979675,7503222,-12368314,-11956721,-4621693,-30272269,2682242,25993170,-12478523 ), + new FieldElement( 4364628,5930691,32304656,-10044554,-8054781,15091131,22857016,-10598955,31820368,15075278 ) + ), + new GroupElementPreComp( + new FieldElement( 31879134,-8918693,17258761,90626,-8041836,-4917709,24162788,-9650886,-17970238,12833045 ), + new FieldElement( 19073683,14851414,-24403169,-11860168,7625278,11091125,-19619190,2074449,-9413939,14905377 ), + new FieldElement( 24483667,-11935567,-2518866,-11547418,-1553130,15355506,-25282080,9253129,27628530,-7555480 ) + ), + new GroupElementPreComp( + new FieldElement( 17597607,8340603,19355617,552187,26198470,-3176583,4593324,-9157582,-14110875,15297016 ), + new FieldElement( 510886,14337390,-31785257,16638632,6328095,2713355,-20217417,-11864220,8683221,2921426 ), + new FieldElement( 18606791,11874196,27155355,-5281482,-24031742,6265446,-25178240,-1278924,4674690,13890525 ) + ), + new GroupElementPreComp( + new FieldElement( 13609624,13069022,-27372361,-13055908,24360586,9592974,14977157,9835105,4389687,288396 ), + new FieldElement( 9922506,-519394,13613107,5883594,-18758345,-434263,-12304062,8317628,23388070,16052080 ), + new FieldElement( 12720016,11937594,-31970060,-5028689,26900120,8561328,-20155687,-11632979,-14754271,-10812892 ) + ), + new GroupElementPreComp( + new FieldElement( 15961858,14150409,26716931,-665832,-22794328,13603569,11829573,7467844,-28822128,929275 ), + new FieldElement( 11038231,-11582396,-27310482,-7316562,-10498527,-16307831,-23479533,-9371869,-21393143,2465074 ), + new FieldElement( 20017163,-4323226,27915242,1529148,12396362,15675764,13817261,-9658066,2463391,-4622140 ) + ), + new GroupElementPreComp( + new FieldElement( -16358878,-12663911,-12065183,4996454,-1256422,1073572,9583558,12851107,4003896,12673717 ), + new FieldElement( -1731589,-15155870,-3262930,16143082,19294135,13385325,14741514,-9103726,7903886,2348101 ), + new FieldElement( 24536016,-16515207,12715592,-3862155,1511293,10047386,-3842346,-7129159,-28377538,10048127 ) + ), + }, + new[]{ + new GroupElementPreComp( + new FieldElement( -12622226,-6204820,30718825,2591312,-10617028,12192840,18873298,-7297090,-32297756,15221632 ), + new FieldElement( -26478122,-11103864,11546244,-1852483,9180880,7656409,-21343950,2095755,29769758,6593415 ), + new FieldElement( -31994208,-2907461,4176912,3264766,12538965,-868111,26312345,-6118678,30958054,8292160 ) + ), + new GroupElementPreComp( + new FieldElement( 31429822,-13959116,29173532,15632448,12174511,-2760094,32808831,3977186,26143136,-3148876 ), + new FieldElement( 22648901,1402143,-22799984,13746059,7936347,365344,-8668633,-1674433,-3758243,-2304625 ), + new FieldElement( -15491917,8012313,-2514730,-12702462,-23965846,-10254029,-1612713,-1535569,-16664475,8194478 ) + ), + new GroupElementPreComp( + new FieldElement( 27338066,-7507420,-7414224,10140405,-19026427,-6589889,27277191,8855376,28572286,3005164 ), + new FieldElement( 26287124,4821776,25476601,-4145903,-3764513,-15788984,-18008582,1182479,-26094821,-13079595 ), + new FieldElement( -7171154,3178080,23970071,6201893,-17195577,-4489192,-21876275,-13982627,32208683,-1198248 ) + ), + new GroupElementPreComp( + new FieldElement( -16657702,2817643,-10286362,14811298,6024667,13349505,-27315504,-10497842,-27672585,-11539858 ), + new FieldElement( 15941029,-9405932,-21367050,8062055,31876073,-238629,-15278393,-1444429,15397331,-4130193 ), + new FieldElement( 8934485,-13485467,-23286397,-13423241,-32446090,14047986,31170398,-1441021,-27505566,15087184 ) + ), + new GroupElementPreComp( + new FieldElement( -18357243,-2156491,24524913,-16677868,15520427,-6360776,-15502406,11461896,16788528,-5868942 ), + new FieldElement( -1947386,16013773,21750665,3714552,-17401782,-16055433,-3770287,-10323320,31322514,-11615635 ), + new FieldElement( 21426655,-5650218,-13648287,-5347537,-28812189,-4920970,-18275391,-14621414,13040862,-12112948 ) + ), + new GroupElementPreComp( + new FieldElement( 11293895,12478086,-27136401,15083750,-29307421,14748872,14555558,-13417103,1613711,4896935 ), + new FieldElement( -25894883,15323294,-8489791,-8057900,25967126,-13425460,2825960,-4897045,-23971776,-11267415 ), + new FieldElement( -15924766,-5229880,-17443532,6410664,3622847,10243618,20615400,12405433,-23753030,-8436416 ) + ), + new GroupElementPreComp( + new FieldElement( -7091295,12556208,-20191352,9025187,-17072479,4333801,4378436,2432030,23097949,-566018 ), + new FieldElement( 4565804,-16025654,20084412,-7842817,1724999,189254,24767264,10103221,-18512313,2424778 ), + new FieldElement( 366633,-11976806,8173090,-6890119,30788634,5745705,-7168678,1344109,-3642553,12412659 ) + ), + new GroupElementPreComp( + new FieldElement( -24001791,7690286,14929416,-168257,-32210835,-13412986,24162697,-15326504,-3141501,11179385 ), + new FieldElement( 18289522,-14724954,8056945,16430056,-21729724,7842514,-6001441,-1486897,-18684645,-11443503 ), + new FieldElement( 476239,6601091,-6152790,-9723375,17503545,-4863900,27672959,13403813,11052904,5219329 ) + ), + }, + new[]{ + new GroupElementPreComp( + new FieldElement( 20678546,-8375738,-32671898,8849123,-5009758,14574752,31186971,-3973730,9014762,-8579056 ), + new FieldElement( -13644050,-10350239,-15962508,5075808,-1514661,-11534600,-33102500,9160280,8473550,-3256838 ), + new FieldElement( 24900749,14435722,17209120,-15292541,-22592275,9878983,-7689309,-16335821,-24568481,11788948 ) + ), + new GroupElementPreComp( + new FieldElement( -3118155,-11395194,-13802089,14797441,9652448,-6845904,-20037437,10410733,-24568470,-1458691 ), + new FieldElement( -15659161,16736706,-22467150,10215878,-9097177,7563911,11871841,-12505194,-18513325,8464118 ), + new FieldElement( -23400612,8348507,-14585951,-861714,-3950205,-6373419,14325289,8628612,33313881,-8370517 ) + ), + new GroupElementPreComp( + new FieldElement( -20186973,-4967935,22367356,5271547,-1097117,-4788838,-24805667,-10236854,-8940735,-5818269 ), + new FieldElement( -6948785,-1795212,-32625683,-16021179,32635414,-7374245,15989197,-12838188,28358192,-4253904 ), + new FieldElement( -23561781,-2799059,-32351682,-1661963,-9147719,10429267,-16637684,4072016,-5351664,5596589 ) + ), + new GroupElementPreComp( + new FieldElement( -28236598,-3390048,12312896,6213178,3117142,16078565,29266239,2557221,1768301,15373193 ), + new FieldElement( -7243358,-3246960,-4593467,-7553353,-127927,-912245,-1090902,-4504991,-24660491,3442910 ), + new FieldElement( -30210571,5124043,14181784,8197961,18964734,-11939093,22597931,7176455,-18585478,13365930 ) + ), + new GroupElementPreComp( + new FieldElement( -7877390,-1499958,8324673,4690079,6261860,890446,24538107,-8570186,-9689599,-3031667 ), + new FieldElement( 25008904,-10771599,-4305031,-9638010,16265036,15721635,683793,-11823784,15723479,-15163481 ), + new FieldElement( -9660625,12374379,-27006999,-7026148,-7724114,-12314514,11879682,5400171,519526,-1235876 ) + ), + new GroupElementPreComp( + new FieldElement( 22258397,-16332233,-7869817,14613016,-22520255,-2950923,-20353881,7315967,16648397,7605640 ), + new FieldElement( -8081308,-8464597,-8223311,9719710,19259459,-15348212,23994942,-5281555,-9468848,4763278 ), + new FieldElement( -21699244,9220969,-15730624,1084137,-25476107,-2852390,31088447,-7764523,-11356529,728112 ) + ), + new GroupElementPreComp( + new FieldElement( 26047220,-11751471,-6900323,-16521798,24092068,9158119,-4273545,-12555558,-29365436,-5498272 ), + new FieldElement( 17510331,-322857,5854289,8403524,17133918,-3112612,-28111007,12327945,10750447,10014012 ), + new FieldElement( -10312768,3936952,9156313,-8897683,16498692,-994647,-27481051,-666732,3424691,7540221 ) + ), + new GroupElementPreComp( + new FieldElement( 30322361,-6964110,11361005,-4143317,7433304,4989748,-7071422,-16317219,-9244265,15258046 ), + new FieldElement( 13054562,-2779497,19155474,469045,-12482797,4566042,5631406,2711395,1062915,-5136345 ), + new FieldElement( -19240248,-11254599,-29509029,-7499965,-5835763,13005411,-6066489,12194497,32960380,1459310 ) + ), + }, + new[]{ + new GroupElementPreComp( + new FieldElement( 19852034,7027924,23669353,10020366,8586503,-6657907,394197,-6101885,18638003,-11174937 ), + new FieldElement( 31395534,15098109,26581030,8030562,-16527914,-5007134,9012486,-7584354,-6643087,-5442636 ), + new FieldElement( -9192165,-2347377,-1997099,4529534,25766844,607986,-13222,9677543,-32294889,-6456008 ) + ), + new GroupElementPreComp( + new FieldElement( -2444496,-149937,29348902,8186665,1873760,12489863,-30934579,-7839692,-7852844,-8138429 ), + new FieldElement( -15236356,-15433509,7766470,746860,26346930,-10221762,-27333451,10754588,-9431476,5203576 ), + new FieldElement( 31834314,14135496,-770007,5159118,20917671,-16768096,-7467973,-7337524,31809243,7347066 ) + ), + new GroupElementPreComp( + new FieldElement( -9606723,-11874240,20414459,13033986,13716524,-11691881,19797970,-12211255,15192876,-2087490 ), + new FieldElement( -12663563,-2181719,1168162,-3804809,26747877,-14138091,10609330,12694420,33473243,-13382104 ), + new FieldElement( 33184999,11180355,15832085,-11385430,-1633671,225884,15089336,-11023903,-6135662,14480053 ) + ), + new GroupElementPreComp( + new FieldElement( 31308717,-5619998,31030840,-1897099,15674547,-6582883,5496208,13685227,27595050,8737275 ), + new FieldElement( -20318852,-15150239,10933843,-16178022,8335352,-7546022,-31008351,-12610604,26498114,66511 ), + new FieldElement( 22644454,-8761729,-16671776,4884562,-3105614,-13559366,30540766,-4286747,-13327787,-7515095 ) + ), + new GroupElementPreComp( + new FieldElement( -28017847,9834845,18617207,-2681312,-3401956,-13307506,8205540,13585437,-17127465,15115439 ), + new FieldElement( 23711543,-672915,31206561,-8362711,6164647,-9709987,-33535882,-1426096,8236921,16492939 ), + new FieldElement( -23910559,-13515526,-26299483,-4503841,25005590,-7687270,19574902,10071562,6708380,-6222424 ) + ), + new GroupElementPreComp( + new FieldElement( 2101391,-4930054,19702731,2367575,-15427167,1047675,5301017,9328700,29955601,-11678310 ), + new FieldElement( 3096359,9271816,-21620864,-15521844,-14847996,-7592937,-25892142,-12635595,-9917575,6216608 ), + new FieldElement( -32615849,338663,-25195611,2510422,-29213566,-13820213,24822830,-6146567,-26767480,7525079 ) + ), + new GroupElementPreComp( + new FieldElement( -23066649,-13985623,16133487,-7896178,-3389565,778788,-910336,-2782495,-19386633,11994101 ), + new FieldElement( 21691500,-13624626,-641331,-14367021,3285881,-3483596,-25064666,9718258,-7477437,13381418 ), + new FieldElement( 18445390,-4202236,14979846,11622458,-1727110,-3582980,23111648,-6375247,28535282,15779576 ) + ), + new GroupElementPreComp( + new FieldElement( 30098053,3089662,-9234387,16662135,-21306940,11308411,-14068454,12021730,9955285,-16303356 ), + new FieldElement( 9734894,-14576830,-7473633,-9138735,2060392,11313496,-18426029,9924399,20194861,13380996 ), + new FieldElement( -26378102,-7965207,-22167821,15789297,-18055342,-6168792,-1984914,15707771,26342023,10146099 ) + ), + }, + new[]{ + new GroupElementPreComp( + new FieldElement( -26016874,-219943,21339191,-41388,19745256,-2878700,-29637280,2227040,21612326,-545728 ), + new FieldElement( -13077387,1184228,23562814,-5970442,-20351244,-6348714,25764461,12243797,-20856566,11649658 ), + new FieldElement( -10031494,11262626,27384172,2271902,26947504,-15997771,39944,6114064,33514190,2333242 ) + ), + new GroupElementPreComp( + new FieldElement( -21433588,-12421821,8119782,7219913,-21830522,-9016134,-6679750,-12670638,24350578,-13450001 ), + new FieldElement( -4116307,-11271533,-23886186,4843615,-30088339,690623,-31536088,-10406836,8317860,12352766 ), + new FieldElement( 18200138,-14475911,-33087759,-2696619,-23702521,-9102511,-23552096,-2287550,20712163,6719373 ) + ), + new GroupElementPreComp( + new FieldElement( 26656208,6075253,-7858556,1886072,-28344043,4262326,11117530,-3763210,26224235,-3297458 ), + new FieldElement( -17168938,-14854097,-3395676,-16369877,-19954045,14050420,21728352,9493610,18620611,-16428628 ), + new FieldElement( -13323321,13325349,11432106,5964811,18609221,6062965,-5269471,-9725556,-30701573,-16479657 ) + ), + new GroupElementPreComp( + new FieldElement( -23860538,-11233159,26961357,1640861,-32413112,-16737940,12248509,-5240639,13735342,1934062 ), + new FieldElement( 25089769,6742589,17081145,-13406266,21909293,-16067981,-15136294,-3765346,-21277997,5473616 ), + new FieldElement( 31883677,-7961101,1083432,-11572403,22828471,13290673,-7125085,12469656,29111212,-5451014 ) + ), + new GroupElementPreComp( + new FieldElement( 24244947,-15050407,-26262976,2791540,-14997599,16666678,24367466,6388839,-10295587,452383 ), + new FieldElement( -25640782,-3417841,5217916,16224624,19987036,-4082269,-24236251,-5915248,15766062,8407814 ), + new FieldElement( -20406999,13990231,15495425,16395525,5377168,15166495,-8917023,-4388953,-8067909,2276718 ) + ), + new GroupElementPreComp( + new FieldElement( 30157918,12924066,-17712050,9245753,19895028,3368142,-23827587,5096219,22740376,-7303417 ), + new FieldElement( 2041139,-14256350,7783687,13876377,-25946985,-13352459,24051124,13742383,-15637599,13295222 ), + new FieldElement( 33338237,-8505733,12532113,7977527,9106186,-1715251,-17720195,-4612972,-4451357,-14669444 ) + ), + new GroupElementPreComp( + new FieldElement( -20045281,5454097,-14346548,6447146,28862071,1883651,-2469266,-4141880,7770569,9620597 ), + new FieldElement( 23208068,7979712,33071466,8149229,1758231,-10834995,30945528,-1694323,-33502340,-14767970 ), + new FieldElement( 1439958,-16270480,-1079989,-793782,4625402,10647766,-5043801,1220118,30494170,-11440799 ) + ), + new GroupElementPreComp( + new FieldElement( -5037580,-13028295,-2970559,-3061767,15640974,-6701666,-26739026,926050,-1684339,-13333647 ), + new FieldElement( 13908495,-3549272,30919928,-6273825,-21521863,7989039,9021034,9078865,3353509,4033511 ), + new FieldElement( -29663431,-15113610,32259991,-344482,24295849,-12912123,23161163,8839127,27485041,7356032 ) + ), + }, + new[]{ + new GroupElementPreComp( + new FieldElement( 9661027,705443,11980065,-5370154,-1628543,14661173,-6346142,2625015,28431036,-16771834 ), + new FieldElement( -23839233,-8311415,-25945511,7480958,-17681669,-8354183,-22545972,14150565,15970762,4099461 ), + new FieldElement( 29262576,16756590,26350592,-8793563,8529671,-11208050,13617293,-9937143,11465739,8317062 ) + ), + new GroupElementPreComp( + new FieldElement( -25493081,-6962928,32500200,-9419051,-23038724,-2302222,14898637,3848455,20969334,-5157516 ), + new FieldElement( -20384450,-14347713,-18336405,13884722,-33039454,2842114,-21610826,-3649888,11177095,14989547 ), + new FieldElement( -24496721,-11716016,16959896,2278463,12066309,10137771,13515641,2581286,-28487508,9930240 ) + ), + new GroupElementPreComp( + new FieldElement( -17751622,-2097826,16544300,-13009300,-15914807,-14949081,18345767,-13403753,16291481,-5314038 ), + new FieldElement( -33229194,2553288,32678213,9875984,8534129,6889387,-9676774,6957617,4368891,9788741 ), + new FieldElement( 16660756,7281060,-10830758,12911820,20108584,-8101676,-21722536,-8613148,16250552,-11111103 ) + ), + new GroupElementPreComp( + new FieldElement( -19765507,2390526,-16551031,14161980,1905286,6414907,4689584,10604807,-30190403,4782747 ), + new FieldElement( -1354539,14736941,-7367442,-13292886,7710542,-14155590,-9981571,4383045,22546403,437323 ), + new FieldElement( 31665577,-12180464,-16186830,1491339,-18368625,3294682,27343084,2786261,-30633590,-14097016 ) + ), + new GroupElementPreComp( + new FieldElement( -14467279,-683715,-33374107,7448552,19294360,14334329,-19690631,2355319,-19284671,-6114373 ), + new FieldElement( 15121312,-15796162,6377020,-6031361,-10798111,-12957845,18952177,15496498,-29380133,11754228 ), + new FieldElement( -2637277,-13483075,8488727,-14303896,12728761,-1622493,7141596,11724556,22761615,-10134141 ) + ), + new GroupElementPreComp( + new FieldElement( 16918416,11729663,-18083579,3022987,-31015732,-13339659,-28741185,-12227393,32851222,11717399 ), + new FieldElement( 11166634,7338049,-6722523,4531520,-29468672,-7302055,31474879,3483633,-1193175,-4030831 ), + new FieldElement( -185635,9921305,31456609,-13536438,-12013818,13348923,33142652,6546660,-19985279,-3948376 ) + ), + new GroupElementPreComp( + new FieldElement( -32460596,11266712,-11197107,-7899103,31703694,3855903,-8537131,-12833048,-30772034,-15486313 ), + new FieldElement( -18006477,12709068,3991746,-6479188,-21491523,-10550425,-31135347,-16049879,10928917,3011958 ), + new FieldElement( -6957757,-15594337,31696059,334240,29576716,14796075,-30831056,-12805180,18008031,10258577 ) + ), + new GroupElementPreComp( + new FieldElement( -22448644,15655569,7018479,-4410003,-30314266,-1201591,-1853465,1367120,25127874,6671743 ), + new FieldElement( 29701166,-14373934,-10878120,9279288,-17568,13127210,21382910,11042292,25838796,4642684 ), + new FieldElement( -20430234,14955537,-24126347,8124619,-5369288,-5990470,30468147,-13900640,18423289,4177476 ) + ) + } + }; + } +} diff --git a/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/base2.cs b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/base2.cs new file mode 100644 index 000000000..c86de62a1 --- /dev/null +++ b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/base2.cs @@ -0,0 +1,50 @@ +using System; + +namespace Discord.Net.ED25519.Ed25519Ref10 +{ + internal static partial class LookupTables + { + internal static readonly GroupElementPreComp[] Base2 = new GroupElementPreComp[]{ + new GroupElementPreComp( + new FieldElement( 25967493,-14356035,29566456,3660896,-12694345,4014787,27544626,-11754271,-6079156,2047605 ), + new FieldElement( -12545711,934262,-2722910,3049990,-727428,9406986,12720692,5043384,19500929,-15469378 ), + new FieldElement( -8738181,4489570,9688441,-14785194,10184609,-12363380,29287919,11864899,-24514362,-4438546 ) + ), + new GroupElementPreComp( + new FieldElement( 15636291,-9688557,24204773,-7912398,616977,-16685262,27787600,-14772189,28944400,-1550024 ), + new FieldElement( 16568933,4717097,-11556148,-1102322,15682896,-11807043,16354577,-11775962,7689662,11199574 ), + new FieldElement( 30464156,-5976125,-11779434,-15670865,23220365,15915852,7512774,10017326,-17749093,-9920357 ) + ), + new GroupElementPreComp( + new FieldElement( 10861363,11473154,27284546,1981175,-30064349,12577861,32867885,14515107,-15438304,10819380 ), + new FieldElement( 4708026,6336745,20377586,9066809,-11272109,6594696,-25653668,12483688,-12668491,5581306 ), + new FieldElement( 19563160,16186464,-29386857,4097519,10237984,-4348115,28542350,13850243,-23678021,-15815942 ) + ), + new GroupElementPreComp( + new FieldElement( 5153746,9909285,1723747,-2777874,30523605,5516873,19480852,5230134,-23952439,-15175766 ), + new FieldElement( -30269007,-3463509,7665486,10083793,28475525,1649722,20654025,16520125,30598449,7715701 ), + new FieldElement( 28881845,14381568,9657904,3680757,-20181635,7843316,-31400660,1370708,29794553,-1409300 ) + ), + new GroupElementPreComp( + new FieldElement( -22518993,-6692182,14201702,-8745502,-23510406,8844726,18474211,-1361450,-13062696,13821877 ), + new FieldElement( -6455177,-7839871,3374702,-4740862,-27098617,-10571707,31655028,-7212327,18853322,-14220951 ), + new FieldElement( 4566830,-12963868,-28974889,-12240689,-7602672,-2830569,-8514358,-10431137,2207753,-3209784 ) + ), + new GroupElementPreComp( + new FieldElement( -25154831,-4185821,29681144,7868801,-6854661,-9423865,-12437364,-663000,-31111463,-16132436 ), + new FieldElement( 25576264,-2703214,7349804,-11814844,16472782,9300885,3844789,15725684,171356,6466918 ), + new FieldElement( 23103977,13316479,9739013,-16149481,817875,-15038942,8965339,-14088058,-30714912,16193877 ) + ), + new GroupElementPreComp( + new FieldElement( -33521811,3180713,-2394130,14003687,-16903474,-16270840,17238398,4729455,-18074513,9256800 ), + new FieldElement( -25182317,-4174131,32336398,5036987,-21236817,11360617,22616405,9761698,-19827198,630305 ), + new FieldElement( -13720693,2639453,-24237460,-7406481,9494427,-5774029,-6554551,-15960994,-2449256,-14291300 ) + ), + new GroupElementPreComp( + new FieldElement( -3151181,-5046075,9282714,6866145,-31907062,-863023,-18940575,15033784,25105118,-7894876 ), + new FieldElement( -24326370,15950226,-31801215,-14592823,-11662737,-5090925,1573892,-2625887,2198790,-15804619 ), + new FieldElement( -3099351,10324967,-2241613,7453183,-5446979,-2735503,-13812022,-16236442,-32461234,-12290683 ) + ) + }; + } +} diff --git a/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/d.cs b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/d.cs new file mode 100644 index 000000000..b5a957307 --- /dev/null +++ b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/d.cs @@ -0,0 +1,9 @@ +using System; + +namespace Discord.Net.ED25519.Ed25519Ref10 +{ + internal static partial class LookupTables + { + internal static FieldElement d = new FieldElement(-10913610, 13857413, -15372611, 6949391, 114729, -8787816, -6275908, -3247719, -18696448, -12055116); + } +} diff --git a/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/d2.cs b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/d2.cs new file mode 100644 index 000000000..5c6bb61e9 --- /dev/null +++ b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/d2.cs @@ -0,0 +1,9 @@ +using System; + +namespace Discord.Net.ED25519.Ed25519Ref10 +{ + internal static partial class LookupTables + { + internal static FieldElement d2 = new FieldElement(-21827239, -5839606, -30745221, 13898782, 229458, 15978800, -12551817, -6495438, 29715968, 9444199); + } +} diff --git a/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_0.cs b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_0.cs new file mode 100644 index 000000000..632c1b942 --- /dev/null +++ b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_0.cs @@ -0,0 +1,12 @@ +using System; + +namespace Discord.Net.ED25519.Ed25519Ref10 +{ + internal static partial class FieldOperations + { + public static void fe_0(out FieldElement h) + { + h = default(FieldElement); + } + } +} diff --git a/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_1.cs b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_1.cs new file mode 100644 index 000000000..dfed794b5 --- /dev/null +++ b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_1.cs @@ -0,0 +1,13 @@ +using System; + +namespace Discord.Net.ED25519.Ed25519Ref10 +{ + internal static partial class FieldOperations + { + public static void fe_1(out FieldElement h) + { + h = default(FieldElement); + h.x0 = 1; + } + } +} diff --git a/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_add.cs b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_add.cs new file mode 100644 index 000000000..7eb6b9ff1 --- /dev/null +++ b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_add.cs @@ -0,0 +1,64 @@ +using System; + +namespace Discord.Net.ED25519.Ed25519Ref10 +{ + internal static partial class FieldOperations + { + /* + h = f + g + Can overlap h with f or g. + + Preconditions: + |f| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. + |g| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. + + Postconditions: + |h| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. + */ + //void fe_add(fe h,const fe f,const fe g) + internal static void fe_add(out FieldElement h, ref FieldElement f, ref FieldElement g) + { + int f0 = f.x0; + int f1 = f.x1; + int f2 = f.x2; + int f3 = f.x3; + int f4 = f.x4; + int f5 = f.x5; + int f6 = f.x6; + int f7 = f.x7; + int f8 = f.x8; + int f9 = f.x9; + int g0 = g.x0; + int g1 = g.x1; + int g2 = g.x2; + int g3 = g.x3; + int g4 = g.x4; + int g5 = g.x5; + int g6 = g.x6; + int g7 = g.x7; + int g8 = g.x8; + int g9 = g.x9; + int h0 = f0 + g0; + int h1 = f1 + g1; + int h2 = f2 + g2; + int h3 = f3 + g3; + int h4 = f4 + g4; + int h5 = f5 + g5; + int h6 = f6 + g6; + int h7 = f7 + g7; + int h8 = f8 + g8; + int h9 = f9 + g9; + + h.x0 = h0; + h.x1 = h1; + h.x2 = h2; + h.x3 = h3; + h.x4 = h4; + h.x5 = h5; + h.x6 = h6; + h.x7 = h7; + h.x8 = h8; + h.x9 = h9; + } + } +} diff --git a/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_cmov.cs b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_cmov.cs new file mode 100644 index 000000000..765650694 --- /dev/null +++ b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_cmov.cs @@ -0,0 +1,71 @@ +using System; + +namespace Discord.Net.ED25519.Ed25519Ref10 +{ + internal static partial class FieldOperations + { + /* + Replace (f,g) with (g,g) if b == 1; + replace (f,g) with (f,g) if b == 0. + + Preconditions: b in {0,1}. + */ + + //void fe_cmov(fe f,const fe g,unsigned int b) + internal static void fe_cmov(ref FieldElement f, ref FieldElement g, int b) + { + int f0 = f.x0; + int f1 = f.x1; + int f2 = f.x2; + int f3 = f.x3; + int f4 = f.x4; + int f5 = f.x5; + int f6 = f.x6; + int f7 = f.x7; + int f8 = f.x8; + int f9 = f.x9; + int g0 = g.x0; + int g1 = g.x1; + int g2 = g.x2; + int g3 = g.x3; + int g4 = g.x4; + int g5 = g.x5; + int g6 = g.x6; + int g7 = g.x7; + int g8 = g.x8; + int g9 = g.x9; + int x0 = f0 ^ g0; + int x1 = f1 ^ g1; + int x2 = f2 ^ g2; + int x3 = f3 ^ g3; + int x4 = f4 ^ g4; + int x5 = f5 ^ g5; + int x6 = f6 ^ g6; + int x7 = f7 ^ g7; + int x8 = f8 ^ g8; + int x9 = f9 ^ g9; + + b = -b; + x0 &= b; + x1 &= b; + x2 &= b; + x3 &= b; + x4 &= b; + x5 &= b; + x6 &= b; + x7 &= b; + x8 &= b; + x9 &= b; + f.x0 = f0 ^ x0; + f.x1 = f1 ^ x1; + f.x2 = f2 ^ x2; + f.x3 = f3 ^ x3; + f.x4 = f4 ^ x4; + f.x5 = f5 ^ x5; + f.x6 = f6 ^ x6; + f.x7 = f7 ^ x7; + f.x8 = f8 ^ x8; + f.x9 = f9 ^ x9; + } + } +} diff --git a/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_cswap.cs b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_cswap.cs new file mode 100644 index 000000000..50815dbfa --- /dev/null +++ b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_cswap.cs @@ -0,0 +1,79 @@ +using System; + +namespace Discord.Net.ED25519.Ed25519Ref10 +{ + internal static partial class FieldOperations + { + /* + Replace (f,g) with (g,f) if b == 1; + replace (f,g) with (f,g) if b == 0. + + Preconditions: b in {0,1}. + */ + public static void fe_cswap(ref FieldElement f, ref FieldElement g, uint b) + { + int f0 = f.x0; + int f1 = f.x1; + int f2 = f.x2; + int f3 = f.x3; + int f4 = f.x4; + int f5 = f.x5; + int f6 = f.x6; + int f7 = f.x7; + int f8 = f.x8; + int f9 = f.x9; + int g0 = g.x0; + int g1 = g.x1; + int g2 = g.x2; + int g3 = g.x3; + int g4 = g.x4; + int g5 = g.x5; + int g6 = g.x6; + int g7 = g.x7; + int g8 = g.x8; + int g9 = g.x9; + int x0 = f0 ^ g0; + int x1 = f1 ^ g1; + int x2 = f2 ^ g2; + int x3 = f3 ^ g3; + int x4 = f4 ^ g4; + int x5 = f5 ^ g5; + int x6 = f6 ^ g6; + int x7 = f7 ^ g7; + int x8 = f8 ^ g8; + int x9 = f9 ^ g9; + + int negb = unchecked((int)-b); + x0 &= negb; + x1 &= negb; + x2 &= negb; + x3 &= negb; + x4 &= negb; + x5 &= negb; + x6 &= negb; + x7 &= negb; + x8 &= negb; + x9 &= negb; + f.x0 = f0 ^ x0; + f.x1 = f1 ^ x1; + f.x2 = f2 ^ x2; + f.x3 = f3 ^ x3; + f.x4 = f4 ^ x4; + f.x5 = f5 ^ x5; + f.x6 = f6 ^ x6; + f.x7 = f7 ^ x7; + f.x8 = f8 ^ x8; + f.x9 = f9 ^ x9; + g.x0 = g0 ^ x0; + g.x1 = g1 ^ x1; + g.x2 = g2 ^ x2; + g.x3 = g3 ^ x3; + g.x4 = g4 ^ x4; + g.x5 = g5 ^ x5; + g.x6 = g6 ^ x6; + g.x7 = g7 ^ x7; + g.x8 = g8 ^ x8; + g.x9 = g9 ^ x9; + } + } +} diff --git a/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_frombytes.cs b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_frombytes.cs new file mode 100644 index 000000000..3689ff952 --- /dev/null +++ b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_frombytes.cs @@ -0,0 +1,102 @@ +using System; + +namespace Discord.Net.ED25519.Ed25519Ref10 +{ + internal static partial class FieldOperations + { + private static long load_3(byte[] data, int offset) + { + uint result; + result = data[offset + 0]; + result |= (uint)data[offset + 1] << 8; + result |= (uint)data[offset + 2] << 16; + return (long)(ulong)result; + } + + private static long load_4(byte[] data, int offset) + { + uint result; + result = data[offset + 0]; + result |= (uint)data[offset + 1] << 8; + result |= (uint)data[offset + 2] << 16; + result |= (uint)data[offset + 3] << 24; + return (long)(ulong)result; + } + + // Ignores top bit of h. + internal static void fe_frombytes(out FieldElement h, byte[] data, int offset) + { + var h0 = load_4(data, offset); + var h1 = load_3(data, offset + 4) << 6; + var h2 = load_3(data, offset + 7) << 5; + var h3 = load_3(data, offset + 10) << 3; + var h4 = load_3(data, offset + 13) << 2; + var h5 = load_4(data, offset + 16); + var h6 = load_3(data, offset + 20) << 7; + var h7 = load_3(data, offset + 23) << 5; + var h8 = load_3(data, offset + 26) << 4; + var h9 = (load_3(data, offset + 29) & 8388607) << 2; + + var carry9 = (h9 + (1 << 24)) >> 25; h0 += carry9 * 19; h9 -= carry9 << 25; + var carry1 = (h1 + (1 << 24)) >> 25; h2 += carry1; h1 -= carry1 << 25; + var carry3 = (h3 + (1 << 24)) >> 25; h4 += carry3; h3 -= carry3 << 25; + var carry5 = (h5 + (1 << 24)) >> 25; h6 += carry5; h5 -= carry5 << 25; + var carry7 = (h7 + (1 << 24)) >> 25; h8 += carry7; h7 -= carry7 << 25; + + var carry0 = (h0 + (1 << 25)) >> 26; h1 += carry0; h0 -= carry0 << 26; + var carry2 = (h2 + (1 << 25)) >> 26; h3 += carry2; h2 -= carry2 << 26; + var carry4 = (h4 + (1 << 25)) >> 26; h5 += carry4; h4 -= carry4 << 26; + var carry6 = (h6 + (1 << 25)) >> 26; h7 += carry6; h6 -= carry6 << 26; + var carry8 = (h8 + (1 << 25)) >> 26; h9 += carry8; h8 -= carry8 << 26; + + h.x0 = (int)h0; + h.x1 = (int)h1; + h.x2 = (int)h2; + h.x3 = (int)h3; + h.x4 = (int)h4; + h.x5 = (int)h5; + h.x6 = (int)h6; + h.x7 = (int)h7; + h.x8 = (int)h8; + h.x9 = (int)h9; + } + + // does NOT ignore top bit + internal static void fe_frombytes2(out FieldElement h, byte[] data, int offset) + { + var h0 = load_4(data, offset); + var h1 = load_3(data, offset + 4) << 6; + var h2 = load_3(data, offset + 7) << 5; + var h3 = load_3(data, offset + 10) << 3; + var h4 = load_3(data, offset + 13) << 2; + var h5 = load_4(data, offset + 16); + var h6 = load_3(data, offset + 20) << 7; + var h7 = load_3(data, offset + 23) << 5; + var h8 = load_3(data, offset + 26) << 4; + var h9 = load_3(data, offset + 29) << 2; + + var carry9 = (h9 + (1 << 24)) >> 25; h0 += carry9 * 19; h9 -= carry9 << 25; + var carry1 = (h1 + (1 << 24)) >> 25; h2 += carry1; h1 -= carry1 << 25; + var carry3 = (h3 + (1 << 24)) >> 25; h4 += carry3; h3 -= carry3 << 25; + var carry5 = (h5 + (1 << 24)) >> 25; h6 += carry5; h5 -= carry5 << 25; + var carry7 = (h7 + (1 << 24)) >> 25; h8 += carry7; h7 -= carry7 << 25; + + var carry0 = (h0 + (1 << 25)) >> 26; h1 += carry0; h0 -= carry0 << 26; + var carry2 = (h2 + (1 << 25)) >> 26; h3 += carry2; h2 -= carry2 << 26; + var carry4 = (h4 + (1 << 25)) >> 26; h5 += carry4; h4 -= carry4 << 26; + var carry6 = (h6 + (1 << 25)) >> 26; h7 += carry6; h6 -= carry6 << 26; + var carry8 = (h8 + (1 << 25)) >> 26; h9 += carry8; h8 -= carry8 << 26; + + h.x0 = (int)h0; + h.x1 = (int)h1; + h.x2 = (int)h2; + h.x3 = (int)h3; + h.x4 = (int)h4; + h.x5 = (int)h5; + h.x6 = (int)h6; + h.x7 = (int)h7; + h.x8 = (int)h8; + h.x9 = (int)h9; + } + } +} diff --git a/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_invert.cs b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_invert.cs new file mode 100644 index 000000000..943133e07 --- /dev/null +++ b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_invert.cs @@ -0,0 +1,128 @@ +using System; + +namespace Discord.Net.ED25519.Ed25519Ref10 +{ + internal static partial class FieldOperations + { + internal static void fe_invert(out FieldElement result, ref FieldElement z) + { + FieldElement t0, t1, t2, t3; + int i; + + /* qhasm: z2 = z1^2^1 */ + /* asm 1: fe_sq(>z2=fe#1,z2=fe#1,>z2=fe#1); */ + /* asm 2: fe_sq(>z2=t0,z2=t0,>z2=t0); */ + fe_sq(out t0, ref z); //for (i = 1; i < 1; ++i) fe_sq(out t0, ref t0); + + /* qhasm: z8 = z2^2^2 */ + /* asm 1: fe_sq(>z8=fe#2,z8=fe#2,>z8=fe#2); */ + /* asm 2: fe_sq(>z8=t1,z8=t1,>z8=t1); */ + fe_sq(out t1, ref t0); for (i = 1; i < 2; ++i) fe_sq(out t1, ref t1); + + /* qhasm: z9 = z1*z8 */ + /* asm 1: fe_mul(>z9=fe#2,z9=t1,z11=fe#1,z11=t0,z22=fe#3,z22=fe#3,>z22=fe#3); */ + /* asm 2: fe_sq(>z22=t2,z22=t2,>z22=t2); */ + fe_sq(out t2, ref t0); //for (i = 1; i < 1; ++i) fe_sq(out t2, ref t2); + + /* qhasm: z_5_0 = z9*z22 */ + /* asm 1: fe_mul(>z_5_0=fe#2,z_5_0=t1,z_10_5=fe#3,z_10_5=fe#3,>z_10_5=fe#3); */ + /* asm 2: fe_sq(>z_10_5=t2,z_10_5=t2,>z_10_5=t2); */ + fe_sq(out t2, ref t1); for (i = 1; i < 5; ++i) fe_sq(out t2, ref t2); + + /* qhasm: z_10_0 = z_10_5*z_5_0 */ + /* asm 1: fe_mul(>z_10_0=fe#2,z_10_0=t1,z_20_10=fe#3,z_20_10=fe#3,>z_20_10=fe#3); */ + /* asm 2: fe_sq(>z_20_10=t2,z_20_10=t2,>z_20_10=t2); */ + fe_sq(out t2, ref t1); for (i = 1; i < 10; ++i) fe_sq(out t2, ref t2); + + /* qhasm: z_20_0 = z_20_10*z_10_0 */ + /* asm 1: fe_mul(>z_20_0=fe#3,z_20_0=t2,z_40_20=fe#4,z_40_20=fe#4,>z_40_20=fe#4); */ + /* asm 2: fe_sq(>z_40_20=t3,z_40_20=t3,>z_40_20=t3); */ + fe_sq(out t3, ref t2); for (i = 1; i < 20; ++i) fe_sq(out t3, ref t3); + + /* qhasm: z_40_0 = z_40_20*z_20_0 */ + /* asm 1: fe_mul(>z_40_0=fe#3,z_40_0=t2,z_50_10=fe#3,z_50_10=fe#3,>z_50_10=fe#3); */ + /* asm 2: fe_sq(>z_50_10=t2,z_50_10=t2,>z_50_10=t2); */ + fe_sq(out t2, ref t2); for (i = 1; i < 10; ++i) fe_sq(out t2, ref t2); + + /* qhasm: z_50_0 = z_50_10*z_10_0 */ + /* asm 1: fe_mul(>z_50_0=fe#2,z_50_0=t1,z_100_50=fe#3,z_100_50=fe#3,>z_100_50=fe#3); */ + /* asm 2: fe_sq(>z_100_50=t2,z_100_50=t2,>z_100_50=t2); */ + fe_sq(out t2, ref t1); for (i = 1; i < 50; ++i) fe_sq(out t2, ref t2); + + /* qhasm: z_100_0 = z_100_50*z_50_0 */ + /* asm 1: fe_mul(>z_100_0=fe#3,z_100_0=t2,z_200_100=fe#4,z_200_100=fe#4,>z_200_100=fe#4); */ + /* asm 2: fe_sq(>z_200_100=t3,z_200_100=t3,>z_200_100=t3); */ + fe_sq(out t3, ref t2); for (i = 1; i < 100; ++i) fe_sq(out t3, ref t3); + + /* qhasm: z_200_0 = z_200_100*z_100_0 */ + /* asm 1: fe_mul(>z_200_0=fe#3,z_200_0=t2,z_250_50=fe#3,z_250_50=fe#3,>z_250_50=fe#3); */ + /* asm 2: fe_sq(>z_250_50=t2,z_250_50=t2,>z_250_50=t2); */ + fe_sq(out t2, ref t2); for (i = 1; i < 50; ++i) fe_sq(out t2, ref t2); + + /* qhasm: z_250_0 = z_250_50*z_50_0 */ + /* asm 1: fe_mul(>z_250_0=fe#2,z_250_0=t1,z_255_5=fe#2,z_255_5=fe#2,>z_255_5=fe#2); */ + /* asm 2: fe_sq(>z_255_5=t1,z_255_5=t1,>z_255_5=t1); */ + fe_sq(out t1, ref t1); for (i = 1; i < 5; ++i) fe_sq(out t1, ref t1); + + /* qhasm: z_255_21 = z_255_5*z11 */ + /* asm 1: fe_mul(>z_255_21=fe#12,z_255_21=out,> 31) ^ 1); + } + } +} \ No newline at end of file diff --git a/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_mul.cs b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_mul.cs new file mode 100644 index 000000000..4774cd5d5 --- /dev/null +++ b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_mul.cs @@ -0,0 +1,263 @@ +using System; + +namespace Discord.Net.ED25519.Ed25519Ref10 +{ + internal static partial class FieldOperations + { + /* + h = f * g + Can overlap h with f or g. + + Preconditions: + |f| bounded by 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc. + |g| bounded by 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc. + + Postconditions: + |h| bounded by 1.01*2^25,1.01*2^24,1.01*2^25,1.01*2^24,etc. + */ + + /* + Notes on implementation strategy: + + Using schoolbook multiplication. + Karatsuba would save a little in some cost models. + + Most multiplications by 2 and 19 are 32-bit precomputations; + cheaper than 64-bit postcomputations. + + There is one remaining multiplication by 19 in the carry chain; + one *19 precomputation can be merged into this, + but the resulting data flow is considerably less clean. + + There are 12 carries below. + 10 of them are 2-way parallelizable and vectorizable. + Can get away with 11 carries, but then data flow is much deeper. + + With tighter constraints on inputs can squeeze carries into int32. + */ + + internal static void fe_mul(out FieldElement h, ref FieldElement f, ref FieldElement g) + { + int f0 = f.x0; + int f1 = f.x1; + int f2 = f.x2; + int f3 = f.x3; + int f4 = f.x4; + int f5 = f.x5; + int f6 = f.x6; + int f7 = f.x7; + int f8 = f.x8; + int f9 = f.x9; + int g0 = g.x0; + int g1 = g.x1; + int g2 = g.x2; + int g3 = g.x3; + int g4 = g.x4; + int g5 = g.x5; + int g6 = g.x6; + int g7 = g.x7; + int g8 = g.x8; + int g9 = g.x9; + + int g1_19 = 19 * g1; /* 1.959375*2^29 */ + int g2_19 = 19 * g2; /* 1.959375*2^30; still ok */ + int g3_19 = 19 * g3; + int g4_19 = 19 * g4; + int g5_19 = 19 * g5; + int g6_19 = 19 * g6; + int g7_19 = 19 * g7; + int g8_19 = 19 * g8; + int g9_19 = 19 * g9; + + int f1_2 = 2 * f1; + int f3_2 = 2 * f3; + int f5_2 = 2 * f5; + int f7_2 = 2 * f7; + int f9_2 = 2 * f9; + + long f0g0 = f0 * (long)g0; + long f0g1 = f0 * (long)g1; + long f0g2 = f0 * (long)g2; + long f0g3 = f0 * (long)g3; + long f0g4 = f0 * (long)g4; + long f0g5 = f0 * (long)g5; + long f0g6 = f0 * (long)g6; + long f0g7 = f0 * (long)g7; + long f0g8 = f0 * (long)g8; + long f0g9 = f0 * (long)g9; + long f1g0 = f1 * (long)g0; + long f1g1_2 = f1_2 * (long)g1; + long f1g2 = f1 * (long)g2; + long f1g3_2 = f1_2 * (long)g3; + long f1g4 = f1 * (long)g4; + long f1g5_2 = f1_2 * (long)g5; + long f1g6 = f1 * (long)g6; + long f1g7_2 = f1_2 * (long)g7; + long f1g8 = f1 * (long)g8; + long f1g9_38 = f1_2 * (long)g9_19; + long f2g0 = f2 * (long)g0; + long f2g1 = f2 * (long)g1; + long f2g2 = f2 * (long)g2; + long f2g3 = f2 * (long)g3; + long f2g4 = f2 * (long)g4; + long f2g5 = f2 * (long)g5; + long f2g6 = f2 * (long)g6; + long f2g7 = f2 * (long)g7; + long f2g8_19 = f2 * (long)g8_19; + long f2g9_19 = f2 * (long)g9_19; + long f3g0 = f3 * (long)g0; + long f3g1_2 = f3_2 * (long)g1; + long f3g2 = f3 * (long)g2; + long f3g3_2 = f3_2 * (long)g3; + long f3g4 = f3 * (long)g4; + long f3g5_2 = f3_2 * (long)g5; + long f3g6 = f3 * (long)g6; + long f3g7_38 = f3_2 * (long)g7_19; + long f3g8_19 = f3 * (long)g8_19; + long f3g9_38 = f3_2 * (long)g9_19; + long f4g0 = f4 * (long)g0; + long f4g1 = f4 * (long)g1; + long f4g2 = f4 * (long)g2; + long f4g3 = f4 * (long)g3; + long f4g4 = f4 * (long)g4; + long f4g5 = f4 * (long)g5; + long f4g6_19 = f4 * (long)g6_19; + long f4g7_19 = f4 * (long)g7_19; + long f4g8_19 = f4 * (long)g8_19; + long f4g9_19 = f4 * (long)g9_19; + long f5g0 = f5 * (long)g0; + long f5g1_2 = f5_2 * (long)g1; + long f5g2 = f5 * (long)g2; + long f5g3_2 = f5_2 * (long)g3; + long f5g4 = f5 * (long)g4; + long f5g5_38 = f5_2 * (long)g5_19; + long f5g6_19 = f5 * (long)g6_19; + long f5g7_38 = f5_2 * (long)g7_19; + long f5g8_19 = f5 * (long)g8_19; + long f5g9_38 = f5_2 * (long)g9_19; + long f6g0 = f6 * (long)g0; + long f6g1 = f6 * (long)g1; + long f6g2 = f6 * (long)g2; + long f6g3 = f6 * (long)g3; + long f6g4_19 = f6 * (long)g4_19; + long f6g5_19 = f6 * (long)g5_19; + long f6g6_19 = f6 * (long)g6_19; + long f6g7_19 = f6 * (long)g7_19; + long f6g8_19 = f6 * (long)g8_19; + long f6g9_19 = f6 * (long)g9_19; + long f7g0 = f7 * (long)g0; + long f7g1_2 = f7_2 * (long)g1; + long f7g2 = f7 * (long)g2; + long f7g3_38 = f7_2 * (long)g3_19; + long f7g4_19 = f7 * (long)g4_19; + long f7g5_38 = f7_2 * (long)g5_19; + long f7g6_19 = f7 * (long)g6_19; + long f7g7_38 = f7_2 * (long)g7_19; + long f7g8_19 = f7 * (long)g8_19; + long f7g9_38 = f7_2 * (long)g9_19; + long f8g0 = f8 * (long)g0; + long f8g1 = f8 * (long)g1; + long f8g2_19 = f8 * (long)g2_19; + long f8g3_19 = f8 * (long)g3_19; + long f8g4_19 = f8 * (long)g4_19; + long f8g5_19 = f8 * (long)g5_19; + long f8g6_19 = f8 * (long)g6_19; + long f8g7_19 = f8 * (long)g7_19; + long f8g8_19 = f8 * (long)g8_19; + long f8g9_19 = f8 * (long)g9_19; + long f9g0 = f9 * (long)g0; + long f9g1_38 = f9_2 * (long)g1_19; + long f9g2_19 = f9 * (long)g2_19; + long f9g3_38 = f9_2 * (long)g3_19; + long f9g4_19 = f9 * (long)g4_19; + long f9g5_38 = f9_2 * (long)g5_19; + long f9g6_19 = f9 * (long)g6_19; + long f9g7_38 = f9_2 * (long)g7_19; + long f9g8_19 = f9 * (long)g8_19; + long f9g9_38 = f9_2 * (long)g9_19; + + long h0 = f0g0 + f1g9_38 + f2g8_19 + f3g7_38 + f4g6_19 + f5g5_38 + f6g4_19 + f7g3_38 + f8g2_19 + f9g1_38; + long h1 = f0g1 + f1g0 + f2g9_19 + f3g8_19 + f4g7_19 + f5g6_19 + f6g5_19 + f7g4_19 + f8g3_19 + f9g2_19; + long h2 = f0g2 + f1g1_2 + f2g0 + f3g9_38 + f4g8_19 + f5g7_38 + f6g6_19 + f7g5_38 + f8g4_19 + f9g3_38; + long h3 = f0g3 + f1g2 + f2g1 + f3g0 + f4g9_19 + f5g8_19 + f6g7_19 + f7g6_19 + f8g5_19 + f9g4_19; + long h4 = f0g4 + f1g3_2 + f2g2 + f3g1_2 + f4g0 + f5g9_38 + f6g8_19 + f7g7_38 + f8g6_19 + f9g5_38; + long h5 = f0g5 + f1g4 + f2g3 + f3g2 + f4g1 + f5g0 + f6g9_19 + f7g8_19 + f8g7_19 + f9g6_19; + long h6 = f0g6 + f1g5_2 + f2g4 + f3g3_2 + f4g2 + f5g1_2 + f6g0 + f7g9_38 + f8g8_19 + f9g7_38; + long h7 = f0g7 + f1g6 + f2g5 + f3g4 + f4g3 + f5g2 + f6g1 + f7g0 + f8g9_19 + f9g8_19; + long h8 = f0g8 + f1g7_2 + f2g6 + f3g5_2 + f4g4 + f5g3_2 + f6g2 + f7g1_2 + f8g0 + f9g9_38; + long h9 = f0g9 + f1g8 + f2g7 + f3g6 + f4g5 + f5g4 + f6g3 + f7g2 + f8g1 + f9g0; + + long carry0; + long carry1; + long carry2; + long carry3; + long carry4; + long carry5; + long carry6; + long carry7; + long carry8; + long carry9; + + /* + |h0| <= (1.65*1.65*2^52*(1+19+19+19+19)+1.65*1.65*2^50*(38+38+38+38+38)) + i.e. |h0| <= 1.4*2^60; narrower ranges for h2, h4, h6, h8 + |h1| <= (1.65*1.65*2^51*(1+1+19+19+19+19+19+19+19+19)) + i.e. |h1| <= 1.7*2^59; narrower ranges for h3, h5, h7, h9 + */ + + carry0 = (h0 + (long)(1 << 25)) >> 26; h1 += carry0; h0 -= carry0 << 26; + carry4 = (h4 + (long)(1 << 25)) >> 26; h5 += carry4; h4 -= carry4 << 26; + /* |h0| <= 2^25 */ + /* |h4| <= 2^25 */ + /* |h1| <= 1.71*2^59 */ + /* |h5| <= 1.71*2^59 */ + + carry1 = (h1 + (long)(1 << 24)) >> 25; h2 += carry1; h1 -= carry1 << 25; + carry5 = (h5 + (long)(1 << 24)) >> 25; h6 += carry5; h5 -= carry5 << 25; + /* |h1| <= 2^24; from now on fits into int32 */ + /* |h5| <= 2^24; from now on fits into int32 */ + /* |h2| <= 1.41*2^60 */ + /* |h6| <= 1.41*2^60 */ + + carry2 = (h2 + (long)(1 << 25)) >> 26; h3 += carry2; h2 -= carry2 << 26; + carry6 = (h6 + (long)(1 << 25)) >> 26; h7 += carry6; h6 -= carry6 << 26; + /* |h2| <= 2^25; from now on fits into int32 unchanged */ + /* |h6| <= 2^25; from now on fits into int32 unchanged */ + /* |h3| <= 1.71*2^59 */ + /* |h7| <= 1.71*2^59 */ + + carry3 = (h3 + (long)(1 << 24)) >> 25; h4 += carry3; h3 -= carry3 << 25; + carry7 = (h7 + (long)(1 << 24)) >> 25; h8 += carry7; h7 -= carry7 << 25; + /* |h3| <= 2^24; from now on fits into int32 unchanged */ + /* |h7| <= 2^24; from now on fits into int32 unchanged */ + /* |h4| <= 1.72*2^34 */ + /* |h8| <= 1.41*2^60 */ + + carry4 = (h4 + (long)(1 << 25)) >> 26; h5 += carry4; h4 -= carry4 << 26; + carry8 = (h8 + (long)(1 << 25)) >> 26; h9 += carry8; h8 -= carry8 << 26; + /* |h4| <= 2^25; from now on fits into int32 unchanged */ + /* |h8| <= 2^25; from now on fits into int32 unchanged */ + /* |h5| <= 1.01*2^24 */ + /* |h9| <= 1.71*2^59 */ + + carry9 = (h9 + (long)(1 << 24)) >> 25; h0 += carry9 * 19; h9 -= carry9 << 25; + /* |h9| <= 2^24; from now on fits into int32 unchanged */ + /* |h0| <= 1.1*2^39 */ + + carry0 = (h0 + (long)(1 << 25)) >> 26; h1 += carry0; h0 -= carry0 << 26; + /* |h0| <= 2^25; from now on fits into int32 unchanged */ + /* |h1| <= 1.01*2^24 */ + + h.x0 = (int)h0; + h.x1 = (int)h1; + h.x2 = (int)h2; + h.x3 = (int)h3; + h.x4 = (int)h4; + h.x5 = (int)h5; + h.x6 = (int)h6; + h.x7 = (int)h7; + h.x8 = (int)h8; + h.x9 = (int)h9; + } + } +} \ No newline at end of file diff --git a/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_mul121666.cs b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_mul121666.cs new file mode 100644 index 000000000..2bbd3f688 --- /dev/null +++ b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_mul121666.cs @@ -0,0 +1,67 @@ +using System; + +namespace Discord.Net.ED25519.Ed25519Ref10 +{ + internal static partial class FieldOperations + { + + /* + h = f * 121666 + Can overlap h with f. + + Preconditions: + |f| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. + + Postconditions: + |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. + */ + + public static void fe_mul121666(out FieldElement h, ref FieldElement f) + { + int f0 = f.x0; + int f1 = f.x1; + int f2 = f.x2; + int f3 = f.x3; + int f4 = f.x4; + int f5 = f.x5; + int f6 = f.x6; + int f7 = f.x7; + int f8 = f.x8; + int f9 = f.x9; + + var h0 = f0 * 121666L; + var h1 = f1 * 121666L; + var h2 = f2 * 121666L; + var h3 = f3 * 121666L; + var h4 = f4 * 121666L; + var h5 = f5 * 121666L; + var h6 = f6 * 121666L; + var h7 = f7 * 121666L; + var h8 = f8 * 121666L; + var h9 = f9 * 121666L; + + var carry9 = (h9 + (1 << 24)) >> 25; h0 += carry9 * 19; h9 -= carry9 << 25; + var carry1 = (h1 + (1 << 24)) >> 25; h2 += carry1; h1 -= carry1 << 25; + var carry3 = (h3 + (1 << 24)) >> 25; h4 += carry3; h3 -= carry3 << 25; + var carry5 = (h5 + (1 << 24)) >> 25; h6 += carry5; h5 -= carry5 << 25; + var carry7 = (h7 + (1 << 24)) >> 25; h8 += carry7; h7 -= carry7 << 25; + + var carry0 = (h0 + (1 << 25)) >> 26; h1 += carry0; h0 -= carry0 << 26; + var carry2 = (h2 + (1 << 25)) >> 26; h3 += carry2; h2 -= carry2 << 26; + var carry4 = (h4 + (1 << 25)) >> 26; h5 += carry4; h4 -= carry4 << 26; + var carry6 = (h6 + (1 << 25)) >> 26; h7 += carry6; h6 -= carry6 << 26; + var carry8 = (h8 + (1 << 25)) >> 26; h9 += carry8; h8 -= carry8 << 26; + + h.x0 = (int)h0; + h.x1 = (int)h1; + h.x2 = (int)h2; + h.x3 = (int)h3; + h.x4 = (int)h4; + h.x5 = (int)h5; + h.x6 = (int)h6; + h.x7 = (int)h7; + h.x8 = (int)h8; + h.x9 = (int)h9; + } + } +} diff --git a/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_neg.cs b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_neg.cs new file mode 100644 index 000000000..9b3d18139 --- /dev/null +++ b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_neg.cs @@ -0,0 +1,51 @@ +using System; + +namespace Discord.Net.ED25519.Ed25519Ref10 +{ + internal static partial class FieldOperations + { + /* + h = -f + + Preconditions: + |f| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. + + Postconditions: + |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. + */ + internal static void fe_neg(out FieldElement h, ref FieldElement f) + { + int f0 = f.x0; + int f1 = f.x1; + int f2 = f.x2; + int f3 = f.x3; + int f4 = f.x4; + int f5 = f.x5; + int f6 = f.x6; + int f7 = f.x7; + int f8 = f.x8; + int f9 = f.x9; + int h0 = -f0; + int h1 = -f1; + int h2 = -f2; + int h3 = -f3; + int h4 = -f4; + int h5 = -f5; + int h6 = -f6; + int h7 = -f7; + int h8 = -f8; + int h9 = -f9; + + h.x0 = h0; + h.x1 = h1; + h.x2 = h2; + h.x3 = h3; + h.x4 = h4; + h.x5 = h5; + h.x6 = h6; + h.x7 = h7; + h.x8 = h8; + h.x9 = h9; + } + } +} \ No newline at end of file diff --git a/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_pow22523.cs b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_pow22523.cs new file mode 100644 index 000000000..63bb33b59 --- /dev/null +++ b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_pow22523.cs @@ -0,0 +1,125 @@ +using System; + +namespace Discord.Net.ED25519.Ed25519Ref10 +{ + internal static partial class FieldOperations + { + internal static void fe_pow22523(out FieldElement result, ref FieldElement z) + { + FieldElement t0, t1, t2; + int i; + + /* qhasm: z2 = z1^2^1 */ + /* asm 1: fe_sq(>z2=fe#1,z2=fe#1,>z2=fe#1); */ + /* asm 2: fe_sq(>z2=t0,z2=t0,>z2=t0); */ + fe_sq(out t0, ref z); //for (i = 1; i < 1; ++i) fe_sq(out t0, ref t0); + + /* qhasm: z8 = z2^2^2 */ + /* asm 1: fe_sq(>z8=fe#2,z8=fe#2,>z8=fe#2); */ + /* asm 2: fe_sq(>z8=t1,z8=t1,>z8=t1); */ + fe_sq(out t1, ref t0); for (i = 1; i < 2; ++i) fe_sq(out t1, ref t1); + + /* qhasm: z9 = z1*z8 */ + /* asm 1: fe_mul(>z9=fe#2,z9=t1,z11=fe#1,z11=t0,z22=fe#1,z22=fe#1,>z22=fe#1); */ + /* asm 2: fe_sq(>z22=t0,z22=t0,>z22=t0); */ + fe_sq(out t0, ref t0); //for (i = 1; i < 1; ++i) fe_sq(out t0, ref t0); + + /* qhasm: z_5_0 = z9*z22 */ + /* asm 1: fe_mul(>z_5_0=fe#1,z_5_0=t0,z_10_5=fe#2,z_10_5=fe#2,>z_10_5=fe#2); */ + /* asm 2: fe_sq(>z_10_5=t1,z_10_5=t1,>z_10_5=t1); */ + fe_sq(out t1, ref t0); for (i = 1; i < 5; ++i) fe_sq(out t1, ref t1); + + /* qhasm: z_10_0 = z_10_5*z_5_0 */ + /* asm 1: fe_mul(>z_10_0=fe#1,z_10_0=t0,z_20_10=fe#2,z_20_10=fe#2,>z_20_10=fe#2); */ + /* asm 2: fe_sq(>z_20_10=t1,z_20_10=t1,>z_20_10=t1); */ + fe_sq(out t1, ref t0); for (i = 1; i < 10; ++i) fe_sq(out t1, ref t1); + + /* qhasm: z_20_0 = z_20_10*z_10_0 */ + /* asm 1: fe_mul(>z_20_0=fe#2,z_20_0=t1,z_40_20=fe#3,z_40_20=fe#3,>z_40_20=fe#3); */ + /* asm 2: fe_sq(>z_40_20=t2,z_40_20=t2,>z_40_20=t2); */ + fe_sq(out t2, ref t1); for (i = 1; i < 20; ++i) fe_sq(out t2, ref t2); + + /* qhasm: z_40_0 = z_40_20*z_20_0 */ + /* asm 1: fe_mul(>z_40_0=fe#2,z_40_0=t1,z_50_10=fe#2,z_50_10=fe#2,>z_50_10=fe#2); */ + /* asm 2: fe_sq(>z_50_10=t1,z_50_10=t1,>z_50_10=t1); */ + fe_sq(out t1, ref t1); for (i = 1; i < 10; ++i) fe_sq(out t1, ref t1); + + /* qhasm: z_50_0 = z_50_10*z_10_0 */ + /* asm 1: fe_mul(>z_50_0=fe#1,z_50_0=t0,z_100_50=fe#2,z_100_50=fe#2,>z_100_50=fe#2); */ + /* asm 2: fe_sq(>z_100_50=t1,z_100_50=t1,>z_100_50=t1); */ + fe_sq(out t1, ref t0); for (i = 1; i < 50; ++i) fe_sq(out t1, ref t1); + + /* qhasm: z_100_0 = z_100_50*z_50_0 */ + /* asm 1: fe_mul(>z_100_0=fe#2,z_100_0=t1,z_200_100=fe#3,z_200_100=fe#3,>z_200_100=fe#3); */ + /* asm 2: fe_sq(>z_200_100=t2,z_200_100=t2,>z_200_100=t2); */ + fe_sq(out t2, ref t1); for (i = 1; i < 100; ++i) fe_sq(out t2, ref t2); + + /* qhasm: z_200_0 = z_200_100*z_100_0 */ + /* asm 1: fe_mul(>z_200_0=fe#2,z_200_0=t1,z_250_50=fe#2,z_250_50=fe#2,>z_250_50=fe#2); */ + /* asm 2: fe_sq(>z_250_50=t1,z_250_50=t1,>z_250_50=t1); */ + fe_sq(out t1, ref t1); for (i = 1; i < 50; ++i) fe_sq(out t1, ref t1); + + /* qhasm: z_250_0 = z_250_50*z_50_0 */ + /* asm 1: fe_mul(>z_250_0=fe#1,z_250_0=t0,z_252_2=fe#1,z_252_2=fe#1,>z_252_2=fe#1); */ + /* asm 2: fe_sq(>z_252_2=t0,z_252_2=t0,>z_252_2=t0); */ + fe_sq(out t0, ref t0); for (i = 1; i < 2; ++i) fe_sq(out t0, ref t0); + + /* qhasm: z_252_3 = z_252_2*z1 */ + /* asm 1: fe_mul(>z_252_3=fe#12,z_252_3=out,> 26; h1 += carry0; h0 -= carry0 << 26; + var carry4 = (h4 + (1 << 25)) >> 26; h5 += carry4; h4 -= carry4 << 26; + var carry1 = (h1 + (1 << 24)) >> 25; h2 += carry1; h1 -= carry1 << 25; + var carry5 = (h5 + (1 << 24)) >> 25; h6 += carry5; h5 -= carry5 << 25; + var carry2 = (h2 + (1 << 25)) >> 26; h3 += carry2; h2 -= carry2 << 26; + var carry6 = (h6 + (1 << 25)) >> 26; h7 += carry6; h6 -= carry6 << 26; + var carry3 = (h3 + (1 << 24)) >> 25; h4 += carry3; h3 -= carry3 << 25; + var carry7 = (h7 + (1 << 24)) >> 25; h8 += carry7; h7 -= carry7 << 25; + + carry4 = (h4 + (1 << 25)) >> 26; h5 += carry4; h4 -= carry4 << 26; + + var carry8 = (h8 + (1 << 25)) >> 26; h9 += carry8; h8 -= carry8 << 26; + var carry9 = (h9 + (1 << 24)) >> 25; h0 += carry9 * 19; h9 -= carry9 << 25; + + carry0 = (h0 + (1 << 25)) >> 26; h1 += carry0; h0 -= carry0 << 26; + + h.x0 = (int)h0; + h.x1 = (int)h1; + h.x2 = (int)h2; + h.x3 = (int)h3; + h.x4 = (int)h4; + h.x5 = (int)h5; + h.x6 = (int)h6; + h.x7 = (int)h7; + h.x8 = (int)h8; + h.x9 = (int)h9; + } + } +} \ No newline at end of file diff --git a/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_sq2.cs b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_sq2.cs new file mode 100644 index 000000000..d1c2ee33d --- /dev/null +++ b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_sq2.cs @@ -0,0 +1,154 @@ +using System; + +namespace Discord.Net.ED25519.Ed25519Ref10 +{ + internal static partial class FieldOperations + { + /* +h = 2 * f * f +Can overlap h with f. + +Preconditions: + |f| bounded by 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc. + +Postconditions: + |h| bounded by 1.01*2^25,1.01*2^24,1.01*2^25,1.01*2^24,etc. +*/ + + /* + See fe_mul.c for discussion of implementation strategy. + */ + internal static void fe_sq2(out FieldElement h, ref FieldElement f) + { + int f0 = f.x0; + int f1 = f.x1; + int f2 = f.x2; + int f3 = f.x3; + int f4 = f.x4; + int f5 = f.x5; + int f6 = f.x6; + int f7 = f.x7; + int f8 = f.x8; + int f9 = f.x9; + + int f0_2 = 2 * f0; + int f1_2 = 2 * f1; + int f2_2 = 2 * f2; + int f3_2 = 2 * f3; + int f4_2 = 2 * f4; + int f5_2 = 2 * f5; + int f6_2 = 2 * f6; + int f7_2 = 2 * f7; + int f5_38 = 38 * f5; /* 1.959375*2^30 */ + int f6_19 = 19 * f6; /* 1.959375*2^30 */ + int f7_38 = 38 * f7; /* 1.959375*2^30 */ + int f8_19 = 19 * f8; /* 1.959375*2^30 */ + int f9_38 = 38 * f9; /* 1.959375*2^30 */ + + var f0f0 = f0 * (long)f0; + var f0f1_2 = f0_2 * (long)f1; + var f0f2_2 = f0_2 * (long)f2; + var f0f3_2 = f0_2 * (long)f3; + var f0f4_2 = f0_2 * (long)f4; + var f0f5_2 = f0_2 * (long)f5; + var f0f6_2 = f0_2 * (long)f6; + var f0f7_2 = f0_2 * (long)f7; + var f0f8_2 = f0_2 * (long)f8; + var f0f9_2 = f0_2 * (long)f9; + var f1f1_2 = f1_2 * (long)f1; + var f1f2_2 = f1_2 * (long)f2; + var f1f3_4 = f1_2 * (long)f3_2; + var f1f4_2 = f1_2 * (long)f4; + var f1f5_4 = f1_2 * (long)f5_2; + var f1f6_2 = f1_2 * (long)f6; + var f1f7_4 = f1_2 * (long)f7_2; + var f1f8_2 = f1_2 * (long)f8; + var f1f9_76 = f1_2 * (long)f9_38; + var f2f2 = f2 * (long)f2; + var f2f3_2 = f2_2 * (long)f3; + var f2f4_2 = f2_2 * (long)f4; + var f2f5_2 = f2_2 * (long)f5; + var f2f6_2 = f2_2 * (long)f6; + var f2f7_2 = f2_2 * (long)f7; + var f2f8_38 = f2_2 * (long)f8_19; + var f2f9_38 = f2 * (long)f9_38; + var f3f3_2 = f3_2 * (long)f3; + var f3f4_2 = f3_2 * (long)f4; + var f3f5_4 = f3_2 * (long)f5_2; + var f3f6_2 = f3_2 * (long)f6; + var f3f7_76 = f3_2 * (long)f7_38; + var f3f8_38 = f3_2 * (long)f8_19; + var f3f9_76 = f3_2 * (long)f9_38; + var f4f4 = f4 * (long)f4; + var f4f5_2 = f4_2 * (long)f5; + var f4f6_38 = f4_2 * (long)f6_19; + var f4f7_38 = f4 * (long)f7_38; + var f4f8_38 = f4_2 * (long)f8_19; + var f4f9_38 = f4 * (long)f9_38; + var f5f5_38 = f5 * (long)f5_38; + var f5f6_38 = f5_2 * (long)f6_19; + var f5f7_76 = f5_2 * (long)f7_38; + var f5f8_38 = f5_2 * (long)f8_19; + var f5f9_76 = f5_2 * (long)f9_38; + var f6f6_19 = f6 * (long)f6_19; + var f6f7_38 = f6 * (long)f7_38; + var f6f8_38 = f6_2 * (long)f8_19; + var f6f9_38 = f6 * (long)f9_38; + var f7f7_38 = f7 * (long)f7_38; + var f7f8_38 = f7_2 * (long)f8_19; + var f7f9_76 = f7_2 * (long)f9_38; + var f8f8_19 = f8 * (long)f8_19; + var f8f9_38 = f8 * (long)f9_38; + var f9f9_38 = f9 * (long)f9_38; + + var h0 = f0f0 + f1f9_76 + f2f8_38 + f3f7_76 + f4f6_38 + f5f5_38; + var h1 = f0f1_2 + f2f9_38 + f3f8_38 + f4f7_38 + f5f6_38; + var h2 = f0f2_2 + f1f1_2 + f3f9_76 + f4f8_38 + f5f7_76 + f6f6_19; + var h3 = f0f3_2 + f1f2_2 + f4f9_38 + f5f8_38 + f6f7_38; + var h4 = f0f4_2 + f1f3_4 + f2f2 + f5f9_76 + f6f8_38 + f7f7_38; + var h5 = f0f5_2 + f1f4_2 + f2f3_2 + f6f9_38 + f7f8_38; + var h6 = f0f6_2 + f1f5_4 + f2f4_2 + f3f3_2 + f7f9_76 + f8f8_19; + var h7 = f0f7_2 + f1f6_2 + f2f5_2 + f3f4_2 + f8f9_38; + var h8 = f0f8_2 + f1f7_4 + f2f6_2 + f3f5_4 + f4f4 + f9f9_38; + var h9 = f0f9_2 + f1f8_2 + f2f7_2 + f3f6_2 + f4f5_2; + + h0 += h0; + h1 += h1; + h2 += h2; + h3 += h3; + h4 += h4; + h5 += h5; + h6 += h6; + h7 += h7; + h8 += h8; + h9 += h9; + + var carry0 = (h0 + (1 << 25)) >> 26; h1 += carry0; h0 -= carry0 << 26; + var carry4 = (h4 + (1 << 25)) >> 26; h5 += carry4; h4 -= carry4 << 26; + var carry1 = (h1 + (1 << 24)) >> 25; h2 += carry1; h1 -= carry1 << 25; + var carry5 = (h5 + (1 << 24)) >> 25; h6 += carry5; h5 -= carry5 << 25; + var carry2 = (h2 + (1 << 25)) >> 26; h3 += carry2; h2 -= carry2 << 26; + var carry6 = (h6 + (1 << 25)) >> 26; h7 += carry6; h6 -= carry6 << 26; + var carry3 = (h3 + (1 << 24)) >> 25; h4 += carry3; h3 -= carry3 << 25; + var carry7 = (h7 + (1 << 24)) >> 25; h8 += carry7; h7 -= carry7 << 25; + + carry4 = (h4 + (1 << 25)) >> 26; h5 += carry4; h4 -= carry4 << 26; + + var carry8 = (h8 + (1 << 25)) >> 26; h9 += carry8; h8 -= carry8 << 26; + var carry9 = (h9 + (1 << 24)) >> 25; h0 += carry9 * 19; h9 -= carry9 << 25; + + carry0 = (h0 + (1 << 25)) >> 26; h1 += carry0; h0 -= carry0 << 26; + + h.x0 = (int)h0; + h.x1 = (int)h1; + h.x2 = (int)h2; + h.x3 = (int)h3; + h.x4 = (int)h4; + h.x5 = (int)h5; + h.x6 = (int)h6; + h.x7 = (int)h7; + h.x8 = (int)h8; + h.x9 = (int)h9; + } + } +} \ No newline at end of file diff --git a/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_sub.cs b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_sub.cs new file mode 100644 index 000000000..f76e6d752 --- /dev/null +++ b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_sub.cs @@ -0,0 +1,66 @@ +using System; + +namespace Discord.Net.ED25519.Ed25519Ref10 +{ + internal static partial class FieldOperations + { + /* + h = f - g + Can overlap h with f or g. + + Preconditions: + |f| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. + |g| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. + + Postconditions: + |h| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. + */ + + internal static void fe_sub(out FieldElement h, ref FieldElement f, ref FieldElement g) + { + int f0 = f.x0; + int f1 = f.x1; + int f2 = f.x2; + int f3 = f.x3; + int f4 = f.x4; + int f5 = f.x5; + int f6 = f.x6; + int f7 = f.x7; + int f8 = f.x8; + int f9 = f.x9; + + int g0 = g.x0; + int g1 = g.x1; + int g2 = g.x2; + int g3 = g.x3; + int g4 = g.x4; + int g5 = g.x5; + int g6 = g.x6; + int g7 = g.x7; + int g8 = g.x8; + int g9 = g.x9; + + int h0 = f0 - g0; + int h1 = f1 - g1; + int h2 = f2 - g2; + int h3 = f3 - g3; + int h4 = f4 - g4; + int h5 = f5 - g5; + int h6 = f6 - g6; + int h7 = f7 - g7; + int h8 = f8 - g8; + int h9 = f9 - g9; + + h.x0 = h0; + h.x1 = h1; + h.x2 = h2; + h.x3 = h3; + h.x4 = h4; + h.x5 = h5; + h.x6 = h6; + h.x7 = h7; + h.x8 = h8; + h.x9 = h9; + } + } +} \ No newline at end of file diff --git a/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_tobytes.cs b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_tobytes.cs new file mode 100644 index 000000000..601f88f28 --- /dev/null +++ b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/fe_tobytes.cs @@ -0,0 +1,145 @@ +using System; + +namespace Discord.Net.ED25519.Ed25519Ref10 +{ + internal static partial class FieldOperations + { + /* + Preconditions: + |h| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. + + Write p=2^255-19; q=floor(h/p). + Basic claim: q = floor(2^(-255)(h + 19 2^(-25)h9 + 2^(-1))). + + Proof: + Have |h|<=p so |q|<=1 so |19^2 2^(-255) q|<1/4. + Also have |h-2^230 h9|<2^231 so |19 2^(-255)(h-2^230 h9)|<1/4. + + Write y=2^(-1)-19^2 2^(-255)q-19 2^(-255)(h-2^230 h9). + Then 0> 0); + s[offset + 1] = (byte) (h0 >> 8); + s[offset + 2] = (byte) (h0 >> 16); + s[offset + 3] = (byte) ((h0 >> 24) | (h1 << 2)); + s[offset + 4] = (byte) (h1 >> 6); + s[offset + 5] = (byte) (h1 >> 14); + s[offset + 6] = (byte) ((h1 >> 22) | (h2 << 3)); + s[offset + 7] = (byte) (h2 >> 5); + s[offset + 8] = (byte) (h2 >> 13); + s[offset + 9] = (byte) ((h2 >> 21) | (h3 << 5)); + s[offset + 10] = (byte) (h3 >> 3); + s[offset + 11] = (byte) (h3 >> 11); + s[offset + 12] = (byte) ((h3 >> 19) | (h4 << 6)); + s[offset + 13] = (byte) (h4 >> 2); + s[offset + 14] = (byte) (h4 >> 10); + s[offset + 15] = (byte) (h4 >> 18); + s[offset + 16] = (byte) (h5 >> 0); + s[offset + 17] = (byte) (h5 >> 8); + s[offset + 18] = (byte) (h5 >> 16); + s[offset + 19] = (byte) ((h5 >> 24) | (h6 << 1)); + s[offset + 20] = (byte) (h6 >> 7); + s[offset + 21] = (byte) (h6 >> 15); + s[offset + 22] = (byte) ((h6 >> 23) | (h7 << 3)); + s[offset + 23] = (byte) (h7 >> 5); + s[offset + 24] = (byte) (h7 >> 13); + s[offset + 25] = (byte) ((h7 >> 21) | (h8 << 4)); + s[offset + 26] = (byte) (h8 >> 4); + s[offset + 27] = (byte) (h8 >> 12); + s[offset + 28] = (byte) ((h8 >> 20) | (h9 << 6)); + s[offset + 29] = (byte) (h9 >> 2); + s[offset + 30] = (byte) (h9 >> 10); + s[offset + 31] = (byte) (h9 >> 18); + } + } + + internal static void fe_reduce(out FieldElement hr, ref FieldElement h) + { + int h0 = h.x0; + int h1 = h.x1; + int h2 = h.x2; + int h3 = h.x3; + int h4 = h.x4; + int h5 = h.x5; + int h6 = h.x6; + int h7 = h.x7; + int h8 = h.x8; + int h9 = h.x9; + + int q; + + q = (19 * h9 + (1 << 24)) >> 25; + q = (h0 + q) >> 26; + q = (h1 + q) >> 25; + q = (h2 + q) >> 26; + q = (h3 + q) >> 25; + q = (h4 + q) >> 26; + q = (h5 + q) >> 25; + q = (h6 + q) >> 26; + q = (h7 + q) >> 25; + q = (h8 + q) >> 26; + q = (h9 + q) >> 25; + + /* Goal: Output h-(2^255-19)q, which is between 0 and 2^255-20. */ + h0 += 19 * q; + /* Goal: Output h-2^255 q, which is between 0 and 2^255-20. */ + + var carry0 = h0 >> 26; h1 += carry0; h0 -= carry0 << 26; + var carry1 = h1 >> 25; h2 += carry1; h1 -= carry1 << 25; + var carry2 = h2 >> 26; h3 += carry2; h2 -= carry2 << 26; + var carry3 = h3 >> 25; h4 += carry3; h3 -= carry3 << 25; + var carry4 = h4 >> 26; h5 += carry4; h4 -= carry4 << 26; + var carry5 = h5 >> 25; h6 += carry5; h5 -= carry5 << 25; + var carry6 = h6 >> 26; h7 += carry6; h6 -= carry6 << 26; + var carry7 = h7 >> 25; h8 += carry7; h7 -= carry7 << 25; + var carry8 = h8 >> 26; h9 += carry8; h8 -= carry8 << 26; + var carry9 = h9 >> 25; h9 -= carry9 << 25; + /* h10 = carry9 */ + + hr.x0 = h0; + hr.x1 = h1; + hr.x2 = h2; + hr.x3 = h3; + hr.x4 = h4; + hr.x5 = h5; + hr.x6 = h6; + hr.x7 = h7; + hr.x8 = h8; + hr.x9 = h9; + } + } +} \ No newline at end of file diff --git a/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_add.cs b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_add.cs new file mode 100644 index 000000000..de8e08f12 --- /dev/null +++ b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_add.cs @@ -0,0 +1,73 @@ +using System; + +namespace Discord.Net.ED25519.Ed25519Ref10 +{ + internal static partial class GroupOperations + { + /* + r = p + q + */ + + internal static void ge_add(out GroupElementP1P1 r, ref GroupElementP3 p, ref GroupElementCached q) + { + FieldElement t0; + + /* qhasm: YpX1 = Y1+X1 */ + /* asm 1: fe_add(>YpX1=fe#1,YpX1=r.X,YmX1=fe#2,YmX1=r.Y,A=fe#3,A=r.Z,B=fe#2,B=r.Y,C=fe#4,C=r.T,ZZ=fe#1,ZZ=r.X,D=fe#5,D=t0,X3=fe#1,X3=r.X,Y3=fe#2,Y3=r.Y,Z3=fe#3,Z3=r.Z,T3=fe#4,T3=r.T,> 3] >> (i & 7))); + + for (int i = 0; i < 256; ++i) + { + if (r[i] != 0) + { + for (int b = 1; b <= 6 && (i + b) < 256; ++b) + { + if (r[i + b] != 0) + { + if (r[i] + (r[i + b] << b) <= 15) + { + r[i] += (sbyte)(r[i + b] << b); r[i + b] = 0; + } + else if (r[i] - (r[i + b] << b) >= -15) + { + r[i] -= (sbyte)(r[i + b] << b); + for (int k = i + b; k < 256; ++k) + { + if (r[k] == 0) + { + r[k] = 1; + break; + } + r[k] = 0; + } + } + else + break; + } + } + } + } + } + + /* + r = a * A + b * B + where a = a[0]+256*a[1]+...+256^31 a[31]. + and b = b[0]+256*b[1]+...+256^31 b[31]. + B is the Ed25519 base point (x,4/5) with x positive. + */ + + public static void ge_double_scalarmult_vartime(out GroupElementP2 r, byte[] a, ref GroupElementP3 A, byte[] b) + { + GroupElementPreComp[] Bi = LookupTables.Base2; + // todo: Perhaps remove these allocations? + sbyte[] aslide = new sbyte[256]; + sbyte[] bslide = new sbyte[256]; + GroupElementCached[] Ai = new GroupElementCached[8]; /* A,3A,5A,7A,9A,11A,13A,15A */ + GroupElementP1P1 t; + GroupElementP3 u; + GroupElementP3 A2; + int i; + + slide(aslide, a); + slide(bslide, b); + + ge_p3_to_cached(out Ai[0], ref A); + ge_p3_dbl(out t, ref A); ge_p1p1_to_p3(out A2, ref t); + ge_add(out t, ref A2, ref Ai[0]); ge_p1p1_to_p3(out u, ref t); ge_p3_to_cached(out Ai[1], ref u); + ge_add(out t, ref A2, ref Ai[1]); ge_p1p1_to_p3(out u, ref t); ge_p3_to_cached(out Ai[2], ref u); + ge_add(out t, ref A2, ref Ai[2]); ge_p1p1_to_p3(out u, ref t); ge_p3_to_cached(out Ai[3], ref u); + ge_add(out t, ref A2, ref Ai[3]); ge_p1p1_to_p3(out u, ref t); ge_p3_to_cached(out Ai[4], ref u); + ge_add(out t, ref A2, ref Ai[4]); ge_p1p1_to_p3(out u, ref t); ge_p3_to_cached(out Ai[5], ref u); + ge_add(out t, ref A2, ref Ai[5]); ge_p1p1_to_p3(out u, ref t); ge_p3_to_cached(out Ai[6], ref u); + ge_add(out t, ref A2, ref Ai[6]); ge_p1p1_to_p3(out u, ref t); ge_p3_to_cached(out Ai[7], ref u); + + ge_p2_0(out r); + + for (i = 255; i >= 0; --i) + { + if ((aslide[i] != 0) || (bslide[i] != 0)) break; + } + + for (; i >= 0; --i) + { + ge_p2_dbl(out t, ref r); + + if (aslide[i] > 0) + { + ge_p1p1_to_p3(out u, ref t); + ge_add(out t, ref u, ref Ai[aslide[i] / 2]); + } + else if (aslide[i] < 0) + { + ge_p1p1_to_p3(out u, ref t); + ge_sub(out t, ref u, ref Ai[(-aslide[i]) / 2]); + } + + if (bslide[i] > 0) + { + ge_p1p1_to_p3(out u, ref t); + ge_madd(out t, ref u, ref Bi[bslide[i] / 2]); + } + else if (bslide[i] < 0) + { + ge_p1p1_to_p3(out u, ref t); + ge_msub(out t, ref u, ref Bi[(-bslide[i]) / 2]); + } + + ge_p1p1_to_p2(out r, ref t); + } + } + + } +} \ No newline at end of file diff --git a/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_frombytes.cs b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_frombytes.cs new file mode 100644 index 000000000..2e7abe9d4 --- /dev/null +++ b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_frombytes.cs @@ -0,0 +1,50 @@ +using System; + +namespace Discord.Net.ED25519.Ed25519Ref10 +{ + internal static partial class GroupOperations + { + public static int ge_frombytes_negate_vartime(out GroupElementP3 h, byte[] data, int offset) + { + FieldElement u, v, v3, vxx, check; + + FieldOperations.fe_frombytes(out h.Y, data, offset); + FieldOperations.fe_1(out h.Z); + FieldOperations.fe_sq(out u, ref h.Y); + FieldOperations.fe_mul(out v, ref u, ref LookupTables.d); + FieldOperations.fe_sub(out u, ref u, ref h.Z); /* u = y^2-1 */ + FieldOperations.fe_add(out v, ref v, ref h.Z); /* v = dy^2+1 */ + + FieldOperations.fe_sq(out v3, ref v); + FieldOperations.fe_mul(out v3, ref v3, ref v); /* v3 = v^3 */ + FieldOperations.fe_sq(out h.X, ref v3); + FieldOperations.fe_mul(out h.X, ref h.X, ref v); + FieldOperations.fe_mul(out h.X, ref h.X, ref u); /* x = uv^7 */ + + FieldOperations.fe_pow22523(out h.X, ref h.X); /* x = (uv^7)^((q-5)/8) */ + FieldOperations.fe_mul(out h.X, ref h.X, ref v3); + FieldOperations.fe_mul(out h.X, ref h.X, ref u); /* x = uv^3(uv^7)^((q-5)/8) */ + + FieldOperations.fe_sq(out vxx, ref h.X); + FieldOperations.fe_mul(out vxx, ref vxx, ref v); + FieldOperations.fe_sub(out check, ref vxx, ref u); /* vx^2-u */ + if (FieldOperations.fe_isnonzero(ref check) != 0) + { + FieldOperations.fe_add(out check, ref vxx, ref u); /* vx^2+u */ + if (FieldOperations.fe_isnonzero(ref check) != 0) + { + h = default(GroupElementP3); + return -1; + } + FieldOperations.fe_mul(out h.X, ref h.X, ref LookupTables.sqrtm1); + } + + if (FieldOperations.fe_isnegative(ref h.X) == (data[offset + 31] >> 7)) + FieldOperations.fe_neg(out h.X, ref h.X); + + FieldOperations.fe_mul(out h.T, ref h.X, ref h.Y); + return 0; + } + + } +} \ No newline at end of file diff --git a/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_madd.cs b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_madd.cs new file mode 100644 index 000000000..547e17d86 --- /dev/null +++ b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_madd.cs @@ -0,0 +1,69 @@ +using System; + +namespace Discord.Net.ED25519.Ed25519Ref10 +{ + internal static partial class GroupOperations + { + /* + r = p + q + */ + public static void ge_madd(out GroupElementP1P1 r, ref GroupElementP3 p, ref GroupElementPreComp q) + { + FieldElement t0; + + /* qhasm: YpX1 = Y1+X1 */ + /* asm 1: fe_add(>YpX1=fe#1,YpX1=r.X,YmX1=fe#2,YmX1=r.Y,A=fe#3,A=r.Z,B=fe#2,B=r.Y,C=fe#4,C=r.T,D=fe#5,D=t0,X3=fe#1,X3=r.X,Y3=fe#2,Y3=r.Y,Z3=fe#3,Z3=r.Z,T3=fe#4,T3=r.T,YpX1=fe#1,YpX1=r.X,YmX1=fe#2,YmX1=r.Y,A=fe#3,A=r.Z,B=fe#2,B=r.Y,C=fe#4,C=r.T,D=fe#5,D=t0,X3=fe#1,X3=r.X,Y3=fe#2,Y3=r.Y,Z3=fe#3,Z3=r.Z,T3=fe#4,T3=r.T,XX=fe#1,XX=r.X,YY=fe#3,YY=r.Z,B=fe#4,B=r.T,A=fe#2,A=r.Y,AA=fe#5,AA=t0,Y3=fe#2,Y3=r.Y,Z3=fe#3,Z3=r.Z,X3=fe#1,X3=r.X,T3=fe#4,T3=r.T,>= 31; /* 1: yes; 0: no */ + return (byte)y; + } + + static byte negative(sbyte b) + { + var x = unchecked((ulong)b); /* 18446744073709551361..18446744073709551615: yes; 0..255: no */ + x >>= 63; /* 1: yes; 0: no */ + return (byte)x; + } + + static void cmov(ref GroupElementPreComp t, ref GroupElementPreComp u, byte b) + { + FieldOperations.fe_cmov(ref t.yplusx, ref u.yplusx, b); + FieldOperations.fe_cmov(ref t.yminusx, ref u.yminusx, b); + FieldOperations.fe_cmov(ref t.xy2d, ref u.xy2d, b); + } + + static void select(out GroupElementPreComp t, int pos, sbyte b) + { + GroupElementPreComp minust; + var bnegative = negative(b); + var babs = (byte)(b - (((-bnegative) & b) << 1)); + + ge_precomp_0(out t); + var table = LookupTables.Base[pos]; + cmov(ref t, ref table[0], equal(babs, 1)); + cmov(ref t, ref table[1], equal(babs, 2)); + cmov(ref t, ref table[2], equal(babs, 3)); + cmov(ref t, ref table[3], equal(babs, 4)); + cmov(ref t, ref table[4], equal(babs, 5)); + cmov(ref t, ref table[5], equal(babs, 6)); + cmov(ref t, ref table[6], equal(babs, 7)); + cmov(ref t, ref table[7], equal(babs, 8)); + minust.yplusx = t.yminusx; + minust.yminusx = t.yplusx; + FieldOperations.fe_neg(out minust.xy2d, ref t.xy2d); + cmov(ref t, ref minust, bnegative); + } + + /* + h = a * B + where a = a[0]+256*a[1]+...+256^31 a[31] + B is the Ed25519 base point (x,4/5) with x positive. + + Preconditions: + a[31] <= 127 + */ + + public static void ge_scalarmult_base(out GroupElementP3 h, byte[] a, int offset) + { + // todo: Perhaps remove this allocation + var e = new sbyte[64]; + sbyte carry; + + GroupElementP1P1 r; + GroupElementP2 s; + GroupElementPreComp t; + + for (int i = 0; i < 32; ++i) + { + e[2 * i + 0] = (sbyte)((a[offset + i] >> 0) & 15); + e[2 * i + 1] = (sbyte)((a[offset + i] >> 4) & 15); + } + /* each e[i] is between 0 and 15 */ + /* e[63] is between 0 and 7 */ + + carry = 0; + for (int i = 0; i < 63; ++i) + { + e[i] += carry; + carry = (sbyte)(e[i] + 8); + carry >>= 4; + e[i] -= (sbyte)(carry << 4); + } + e[63] += carry; + /* each e[i] is between -8 and 8 */ + + ge_p3_0(out h); + for (int i = 1; i < 64; i += 2) + { + select(out t, i / 2, e[i]); + ge_madd(out r, ref h, ref t); ge_p1p1_to_p3(out h, ref r); + } + + ge_p3_dbl(out r, ref h); ge_p1p1_to_p2(out s, ref r); + ge_p2_dbl(out r, ref s); ge_p1p1_to_p2(out s, ref r); + ge_p2_dbl(out r, ref s); ge_p1p1_to_p2(out s, ref r); + ge_p2_dbl(out r, ref s); ge_p1p1_to_p3(out h, ref r); + + for (int i = 0; i < 64; i += 2) + { + select(out t, i / 2, e[i]); + ge_madd(out r, ref h, ref t); ge_p1p1_to_p3(out h, ref r); + } + } + + } +} \ No newline at end of file diff --git a/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_sub.cs b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_sub.cs new file mode 100644 index 000000000..c0b9ba5a2 --- /dev/null +++ b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/ge_sub.cs @@ -0,0 +1,74 @@ +using System; + +namespace Discord.Net.ED25519.Ed25519Ref10 +{ + internal static partial class GroupOperations + { + /* + r = p - q + */ + + public static void ge_sub(out GroupElementP1P1 r, ref GroupElementP3 p, ref GroupElementCached q) + { + FieldElement t0; + + /* qhasm: YpX1 = Y1+X1 */ + /* asm 1: fe_add(>YpX1=fe#1,YpX1=r.X,YmX1=fe#2,YmX1=r.Y,A=fe#3,A=r.Z,B=fe#2,B=r.Y,C=fe#4,C=r.T,ZZ=fe#1,ZZ=r.X,D=fe#5,D=t0,X3=fe#1,X3=r.X,Y3=fe#2,Y3=r.Y,Z3=fe#3,Z3=r.Z,T3=fe#4,T3=r.T,> 5); + long a2 = 2097151 & (load_3(a, 5) >> 2); + long a3 = 2097151 & (load_4(a, 7) >> 7); + long a4 = 2097151 & (load_4(a, 10) >> 4); + long a5 = 2097151 & (load_3(a, 13) >> 1); + long a6 = 2097151 & (load_4(a, 15) >> 6); + long a7 = 2097151 & (load_3(a, 18) >> 3); + long a8 = 2097151 & load_3(a, 21); + long a9 = 2097151 & (load_4(a, 23) >> 5); + long a10 = 2097151 & (load_3(a, 26) >> 2); + long a11 = (load_4(a, 28) >> 7); + long b0 = 2097151 & load_3(b, 0); + long b1 = 2097151 & (load_4(b, 2) >> 5); + long b2 = 2097151 & (load_3(b, 5) >> 2); + long b3 = 2097151 & (load_4(b, 7) >> 7); + long b4 = 2097151 & (load_4(b, 10) >> 4); + long b5 = 2097151 & (load_3(b, 13) >> 1); + long b6 = 2097151 & (load_4(b, 15) >> 6); + long b7 = 2097151 & (load_3(b, 18) >> 3); + long b8 = 2097151 & load_3(b, 21); + long b9 = 2097151 & (load_4(b, 23) >> 5); + long b10 = 2097151 & (load_3(b, 26) >> 2); + long b11 = (load_4(b, 28) >> 7); + long c0 = 2097151 & load_3(c, 0); + long c1 = 2097151 & (load_4(c, 2) >> 5); + long c2 = 2097151 & (load_3(c, 5) >> 2); + long c3 = 2097151 & (load_4(c, 7) >> 7); + long c4 = 2097151 & (load_4(c, 10) >> 4); + long c5 = 2097151 & (load_3(c, 13) >> 1); + long c6 = 2097151 & (load_4(c, 15) >> 6); + long c7 = 2097151 & (load_3(c, 18) >> 3); + long c8 = 2097151 & load_3(c, 21); + long c9 = 2097151 & (load_4(c, 23) >> 5); + long c10 = 2097151 & (load_3(c, 26) >> 2); + long c11 = (load_4(c, 28) >> 7); + long s0; + long s1; + long s2; + long s3; + long s4; + long s5; + long s6; + long s7; + long s8; + long s9; + long s10; + long s11; + long s12; + long s13; + long s14; + long s15; + long s16; + long s17; + long s18; + long s19; + long s20; + long s21; + long s22; + long s23; + long carry0; + long carry1; + long carry2; + long carry3; + long carry4; + long carry5; + long carry6; + long carry7; + long carry8; + long carry9; + long carry10; + long carry11; + long carry12; + long carry13; + long carry14; + long carry15; + long carry16; + long carry17; + long carry18; + long carry19; + long carry20; + long carry21; + long carry22; + + s0 = c0 + a0 * b0; + s1 = c1 + a0 * b1 + a1 * b0; + s2 = c2 + a0 * b2 + a1 * b1 + a2 * b0; + s3 = c3 + a0 * b3 + a1 * b2 + a2 * b1 + a3 * b0; + s4 = c4 + a0 * b4 + a1 * b3 + a2 * b2 + a3 * b1 + a4 * b0; + s5 = c5 + a0 * b5 + a1 * b4 + a2 * b3 + a3 * b2 + a4 * b1 + a5 * b0; + s6 = c6 + a0 * b6 + a1 * b5 + a2 * b4 + a3 * b3 + a4 * b2 + a5 * b1 + a6 * b0; + s7 = c7 + a0 * b7 + a1 * b6 + a2 * b5 + a3 * b4 + a4 * b3 + a5 * b2 + a6 * b1 + a7 * b0; + s8 = c8 + a0 * b8 + a1 * b7 + a2 * b6 + a3 * b5 + a4 * b4 + a5 * b3 + a6 * b2 + a7 * b1 + a8 * b0; + s9 = c9 + a0 * b9 + a1 * b8 + a2 * b7 + a3 * b6 + a4 * b5 + a5 * b4 + a6 * b3 + a7 * b2 + a8 * b1 + a9 * b0; + s10 = c10 + a0 * b10 + a1 * b9 + a2 * b8 + a3 * b7 + a4 * b6 + a5 * b5 + a6 * b4 + a7 * b3 + a8 * b2 + a9 * b1 + a10 * b0; + s11 = c11 + a0 * b11 + a1 * b10 + a2 * b9 + a3 * b8 + a4 * b7 + a5 * b6 + a6 * b5 + a7 * b4 + a8 * b3 + a9 * b2 + a10 * b1 + a11 * b0; + s12 = a1 * b11 + a2 * b10 + a3 * b9 + a4 * b8 + a5 * b7 + a6 * b6 + a7 * b5 + a8 * b4 + a9 * b3 + a10 * b2 + a11 * b1; + s13 = a2 * b11 + a3 * b10 + a4 * b9 + a5 * b8 + a6 * b7 + a7 * b6 + a8 * b5 + a9 * b4 + a10 * b3 + a11 * b2; + s14 = a3 * b11 + a4 * b10 + a5 * b9 + a6 * b8 + a7 * b7 + a8 * b6 + a9 * b5 + a10 * b4 + a11 * b3; + s15 = a4 * b11 + a5 * b10 + a6 * b9 + a7 * b8 + a8 * b7 + a9 * b6 + a10 * b5 + a11 * b4; + s16 = a5 * b11 + a6 * b10 + a7 * b9 + a8 * b8 + a9 * b7 + a10 * b6 + a11 * b5; + s17 = a6 * b11 + a7 * b10 + a8 * b9 + a9 * b8 + a10 * b7 + a11 * b6; + s18 = a7 * b11 + a8 * b10 + a9 * b9 + a10 * b8 + a11 * b7; + s19 = a8 * b11 + a9 * b10 + a10 * b9 + a11 * b8; + s20 = a9 * b11 + a10 * b10 + a11 * b9; + s21 = a10 * b11 + a11 * b10; + s22 = a11 * b11; + s23 = 0; + + carry0 = (s0 + (1 << 20)) >> 21; s1 += carry0; s0 -= carry0 << 21; + carry2 = (s2 + (1 << 20)) >> 21; s3 += carry2; s2 -= carry2 << 21; + carry4 = (s4 + (1 << 20)) >> 21; s5 += carry4; s4 -= carry4 << 21; + carry6 = (s6 + (1 << 20)) >> 21; s7 += carry6; s6 -= carry6 << 21; + carry8 = (s8 + (1 << 20)) >> 21; s9 += carry8; s8 -= carry8 << 21; + carry10 = (s10 + (1 << 20)) >> 21; s11 += carry10; s10 -= carry10 << 21; + carry12 = (s12 + (1 << 20)) >> 21; s13 += carry12; s12 -= carry12 << 21; + carry14 = (s14 + (1 << 20)) >> 21; s15 += carry14; s14 -= carry14 << 21; + carry16 = (s16 + (1 << 20)) >> 21; s17 += carry16; s16 -= carry16 << 21; + carry18 = (s18 + (1 << 20)) >> 21; s19 += carry18; s18 -= carry18 << 21; + carry20 = (s20 + (1 << 20)) >> 21; s21 += carry20; s20 -= carry20 << 21; + carry22 = (s22 + (1 << 20)) >> 21; s23 += carry22; s22 -= carry22 << 21; + + carry1 = (s1 + (1 << 20)) >> 21; s2 += carry1; s1 -= carry1 << 21; + carry3 = (s3 + (1 << 20)) >> 21; s4 += carry3; s3 -= carry3 << 21; + carry5 = (s5 + (1 << 20)) >> 21; s6 += carry5; s5 -= carry5 << 21; + carry7 = (s7 + (1 << 20)) >> 21; s8 += carry7; s7 -= carry7 << 21; + carry9 = (s9 + (1 << 20)) >> 21; s10 += carry9; s9 -= carry9 << 21; + carry11 = (s11 + (1 << 20)) >> 21; s12 += carry11; s11 -= carry11 << 21; + carry13 = (s13 + (1 << 20)) >> 21; s14 += carry13; s13 -= carry13 << 21; + carry15 = (s15 + (1 << 20)) >> 21; s16 += carry15; s15 -= carry15 << 21; + carry17 = (s17 + (1 << 20)) >> 21; s18 += carry17; s17 -= carry17 << 21; + carry19 = (s19 + (1 << 20)) >> 21; s20 += carry19; s19 -= carry19 << 21; + carry21 = (s21 + (1 << 20)) >> 21; s22 += carry21; s21 -= carry21 << 21; + + s11 += s23 * 666643; + s12 += s23 * 470296; + s13 += s23 * 654183; + s14 -= s23 * 997805; + s15 += s23 * 136657; + s16 -= s23 * 683901; + s23 = 0; + + s10 += s22 * 666643; + s11 += s22 * 470296; + s12 += s22 * 654183; + s13 -= s22 * 997805; + s14 += s22 * 136657; + s15 -= s22 * 683901; + s22 = 0; + + s9 += s21 * 666643; + s10 += s21 * 470296; + s11 += s21 * 654183; + s12 -= s21 * 997805; + s13 += s21 * 136657; + s14 -= s21 * 683901; + s21 = 0; + + s8 += s20 * 666643; + s9 += s20 * 470296; + s10 += s20 * 654183; + s11 -= s20 * 997805; + s12 += s20 * 136657; + s13 -= s20 * 683901; + s20 = 0; + + s7 += s19 * 666643; + s8 += s19 * 470296; + s9 += s19 * 654183; + s10 -= s19 * 997805; + s11 += s19 * 136657; + s12 -= s19 * 683901; + s19 = 0; + + s6 += s18 * 666643; + s7 += s18 * 470296; + s8 += s18 * 654183; + s9 -= s18 * 997805; + s10 += s18 * 136657; + s11 -= s18 * 683901; + s18 = 0; + + carry6 = (s6 + (1 << 20)) >> 21; s7 += carry6; s6 -= carry6 << 21; + carry8 = (s8 + (1 << 20)) >> 21; s9 += carry8; s8 -= carry8 << 21; + carry10 = (s10 + (1 << 20)) >> 21; s11 += carry10; s10 -= carry10 << 21; + carry12 = (s12 + (1 << 20)) >> 21; s13 += carry12; s12 -= carry12 << 21; + carry14 = (s14 + (1 << 20)) >> 21; s15 += carry14; s14 -= carry14 << 21; + carry16 = (s16 + (1 << 20)) >> 21; s17 += carry16; s16 -= carry16 << 21; + + carry7 = (s7 + (1 << 20)) >> 21; s8 += carry7; s7 -= carry7 << 21; + carry9 = (s9 + (1 << 20)) >> 21; s10 += carry9; s9 -= carry9 << 21; + carry11 = (s11 + (1 << 20)) >> 21; s12 += carry11; s11 -= carry11 << 21; + carry13 = (s13 + (1 << 20)) >> 21; s14 += carry13; s13 -= carry13 << 21; + carry15 = (s15 + (1 << 20)) >> 21; s16 += carry15; s15 -= carry15 << 21; + + s5 += s17 * 666643; + s6 += s17 * 470296; + s7 += s17 * 654183; + s8 -= s17 * 997805; + s9 += s17 * 136657; + s10 -= s17 * 683901; + s17 = 0; + + s4 += s16 * 666643; + s5 += s16 * 470296; + s6 += s16 * 654183; + s7 -= s16 * 997805; + s8 += s16 * 136657; + s9 -= s16 * 683901; + s16 = 0; + + s3 += s15 * 666643; + s4 += s15 * 470296; + s5 += s15 * 654183; + s6 -= s15 * 997805; + s7 += s15 * 136657; + s8 -= s15 * 683901; + s15 = 0; + + s2 += s14 * 666643; + s3 += s14 * 470296; + s4 += s14 * 654183; + s5 -= s14 * 997805; + s6 += s14 * 136657; + s7 -= s14 * 683901; + s14 = 0; + + s1 += s13 * 666643; + s2 += s13 * 470296; + s3 += s13 * 654183; + s4 -= s13 * 997805; + s5 += s13 * 136657; + s6 -= s13 * 683901; + s13 = 0; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + + carry0 = (s0 + (1 << 20)) >> 21; s1 += carry0; s0 -= carry0 << 21; + carry2 = (s2 + (1 << 20)) >> 21; s3 += carry2; s2 -= carry2 << 21; + carry4 = (s4 + (1 << 20)) >> 21; s5 += carry4; s4 -= carry4 << 21; + carry6 = (s6 + (1 << 20)) >> 21; s7 += carry6; s6 -= carry6 << 21; + carry8 = (s8 + (1 << 20)) >> 21; s9 += carry8; s8 -= carry8 << 21; + carry10 = (s10 + (1 << 20)) >> 21; s11 += carry10; s10 -= carry10 << 21; + + carry1 = (s1 + (1 << 20)) >> 21; s2 += carry1; s1 -= carry1 << 21; + carry3 = (s3 + (1 << 20)) >> 21; s4 += carry3; s3 -= carry3 << 21; + carry5 = (s5 + (1 << 20)) >> 21; s6 += carry5; s5 -= carry5 << 21; + carry7 = (s7 + (1 << 20)) >> 21; s8 += carry7; s7 -= carry7 << 21; + carry9 = (s9 + (1 << 20)) >> 21; s10 += carry9; s9 -= carry9 << 21; + carry11 = (s11 + (1 << 20)) >> 21; s12 += carry11; s11 -= carry11 << 21; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + + carry0 = s0 >> 21; s1 += carry0; s0 -= carry0 << 21; + carry1 = s1 >> 21; s2 += carry1; s1 -= carry1 << 21; + carry2 = s2 >> 21; s3 += carry2; s2 -= carry2 << 21; + carry3 = s3 >> 21; s4 += carry3; s3 -= carry3 << 21; + carry4 = s4 >> 21; s5 += carry4; s4 -= carry4 << 21; + carry5 = s5 >> 21; s6 += carry5; s5 -= carry5 << 21; + carry6 = s6 >> 21; s7 += carry6; s6 -= carry6 << 21; + carry7 = s7 >> 21; s8 += carry7; s7 -= carry7 << 21; + carry8 = s8 >> 21; s9 += carry8; s8 -= carry8 << 21; + carry9 = s9 >> 21; s10 += carry9; s9 -= carry9 << 21; + carry10 = s10 >> 21; s11 += carry10; s10 -= carry10 << 21; + carry11 = s11 >> 21; s12 += carry11; s11 -= carry11 << 21; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + + carry0 = s0 >> 21; s1 += carry0; s0 -= carry0 << 21; + carry1 = s1 >> 21; s2 += carry1; s1 -= carry1 << 21; + carry2 = s2 >> 21; s3 += carry2; s2 -= carry2 << 21; + carry3 = s3 >> 21; s4 += carry3; s3 -= carry3 << 21; + carry4 = s4 >> 21; s5 += carry4; s4 -= carry4 << 21; + carry5 = s5 >> 21; s6 += carry5; s5 -= carry5 << 21; + carry6 = s6 >> 21; s7 += carry6; s6 -= carry6 << 21; + carry7 = s7 >> 21; s8 += carry7; s7 -= carry7 << 21; + carry8 = s8 >> 21; s9 += carry8; s8 -= carry8 << 21; + carry9 = s9 >> 21; s10 += carry9; s9 -= carry9 << 21; + carry10 = s10 >> 21; s11 += carry10; s10 -= carry10 << 21; + + unchecked + { + s[0] = (byte)(s0 >> 0); + s[1] = (byte)(s0 >> 8); + s[2] = (byte)((s0 >> 16) | (s1 << 5)); + s[3] = (byte)(s1 >> 3); + s[4] = (byte)(s1 >> 11); + s[5] = (byte)((s1 >> 19) | (s2 << 2)); + s[6] = (byte)(s2 >> 6); + s[7] = (byte)((s2 >> 14) | (s3 << 7)); + s[8] = (byte)(s3 >> 1); + s[9] = (byte)(s3 >> 9); + s[10] = (byte)((s3 >> 17) | (s4 << 4)); + s[11] = (byte)(s4 >> 4); + s[12] = (byte)(s4 >> 12); + s[13] = (byte)((s4 >> 20) | (s5 << 1)); + s[14] = (byte)(s5 >> 7); + s[15] = (byte)((s5 >> 15) | (s6 << 6)); + s[16] = (byte)(s6 >> 2); + s[17] = (byte)(s6 >> 10); + s[18] = (byte)((s6 >> 18) | (s7 << 3)); + s[19] = (byte)(s7 >> 5); + s[20] = (byte)(s7 >> 13); + s[21] = (byte)(s8 >> 0); + s[22] = (byte)(s8 >> 8); + s[23] = (byte)((s8 >> 16) | (s9 << 5)); + s[24] = (byte)(s9 >> 3); + s[25] = (byte)(s9 >> 11); + s[26] = (byte)((s9 >> 19) | (s10 << 2)); + s[27] = (byte)(s10 >> 6); + s[28] = (byte)((s10 >> 14) | (s11 << 7)); + s[29] = (byte)(s11 >> 1); + s[30] = (byte)(s11 >> 9); + s[31] = (byte)(s11 >> 17); + } + } + } +} \ No newline at end of file diff --git a/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/sc_reduce.cs b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/sc_reduce.cs new file mode 100644 index 000000000..d3554455f --- /dev/null +++ b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/sc_reduce.cs @@ -0,0 +1,264 @@ +using System; + +namespace Discord.Net.ED25519.Ed25519Ref10 +{ + internal static partial class ScalarOperations + { + /* + Input: + s[0]+256*s[1]+...+256^63*s[63] = s + + Output: + s[0]+256*s[1]+...+256^31*s[31] = s mod l + where l = 2^252 + 27742317777372353535851937790883648493. + Overwrites s in place. + */ + + public static void sc_reduce(byte[] s) + { + long s0 = 2097151 & load_3(s, 0); + long s1 = 2097151 & (load_4(s, 2) >> 5); + long s2 = 2097151 & (load_3(s, 5) >> 2); + long s3 = 2097151 & (load_4(s, 7) >> 7); + long s4 = 2097151 & (load_4(s, 10) >> 4); + long s5 = 2097151 & (load_3(s, 13) >> 1); + long s6 = 2097151 & (load_4(s, 15) >> 6); + long s7 = 2097151 & (load_3(s, 18) >> 3); + long s8 = 2097151 & load_3(s, 21); + long s9 = 2097151 & (load_4(s, 23) >> 5); + long s10 = 2097151 & (load_3(s, 26) >> 2); + long s11 = 2097151 & (load_4(s, 28) >> 7); + long s12 = 2097151 & (load_4(s, 31) >> 4); + long s13 = 2097151 & (load_3(s, 34) >> 1); + long s14 = 2097151 & (load_4(s, 36) >> 6); + long s15 = 2097151 & (load_3(s, 39) >> 3); + long s16 = 2097151 & load_3(s, 42); + long s17 = 2097151 & (load_4(s, 44) >> 5); + long s18 = 2097151 & (load_3(s, 47) >> 2); + long s19 = 2097151 & (load_4(s, 49) >> 7); + long s20 = 2097151 & (load_4(s, 52) >> 4); + long s21 = 2097151 & (load_3(s, 55) >> 1); + long s22 = 2097151 & (load_4(s, 57) >> 6); + long s23 = (load_4(s, 60) >> 3); + + long carry0; + long carry1; + long carry2; + long carry3; + long carry4; + long carry5; + long carry6; + long carry7; + long carry8; + long carry9; + long carry10; + long carry11; + long carry12; + long carry13; + long carry14; + long carry15; + long carry16; + + s11 += s23 * 666643; + s12 += s23 * 470296; + s13 += s23 * 654183; + s14 -= s23 * 997805; + s15 += s23 * 136657; + s16 -= s23 * 683901; + s23 = 0; + + s10 += s22 * 666643; + s11 += s22 * 470296; + s12 += s22 * 654183; + s13 -= s22 * 997805; + s14 += s22 * 136657; + s15 -= s22 * 683901; + s22 = 0; + + s9 += s21 * 666643; + s10 += s21 * 470296; + s11 += s21 * 654183; + s12 -= s21 * 997805; + s13 += s21 * 136657; + s14 -= s21 * 683901; + s21 = 0; + + s8 += s20 * 666643; + s9 += s20 * 470296; + s10 += s20 * 654183; + s11 -= s20 * 997805; + s12 += s20 * 136657; + s13 -= s20 * 683901; + s20 = 0; + + s7 += s19 * 666643; + s8 += s19 * 470296; + s9 += s19 * 654183; + s10 -= s19 * 997805; + s11 += s19 * 136657; + s12 -= s19 * 683901; + s19 = 0; + + s6 += s18 * 666643; + s7 += s18 * 470296; + s8 += s18 * 654183; + s9 -= s18 * 997805; + s10 += s18 * 136657; + s11 -= s18 * 683901; + s18 = 0; + + carry6 = (s6 + (1 << 20)) >> 21; s7 += carry6; s6 -= carry6 << 21; + carry8 = (s8 + (1 << 20)) >> 21; s9 += carry8; s8 -= carry8 << 21; + carry10 = (s10 + (1 << 20)) >> 21; s11 += carry10; s10 -= carry10 << 21; + carry12 = (s12 + (1 << 20)) >> 21; s13 += carry12; s12 -= carry12 << 21; + carry14 = (s14 + (1 << 20)) >> 21; s15 += carry14; s14 -= carry14 << 21; + carry16 = (s16 + (1 << 20)) >> 21; s17 += carry16; s16 -= carry16 << 21; + + carry7 = (s7 + (1 << 20)) >> 21; s8 += carry7; s7 -= carry7 << 21; + carry9 = (s9 + (1 << 20)) >> 21; s10 += carry9; s9 -= carry9 << 21; + carry11 = (s11 + (1 << 20)) >> 21; s12 += carry11; s11 -= carry11 << 21; + carry13 = (s13 + (1 << 20)) >> 21; s14 += carry13; s13 -= carry13 << 21; + carry15 = (s15 + (1 << 20)) >> 21; s16 += carry15; s15 -= carry15 << 21; + + s5 += s17 * 666643; + s6 += s17 * 470296; + s7 += s17 * 654183; + s8 -= s17 * 997805; + s9 += s17 * 136657; + s10 -= s17 * 683901; + s17 = 0; + + s4 += s16 * 666643; + s5 += s16 * 470296; + s6 += s16 * 654183; + s7 -= s16 * 997805; + s8 += s16 * 136657; + s9 -= s16 * 683901; + s16 = 0; + + s3 += s15 * 666643; + s4 += s15 * 470296; + s5 += s15 * 654183; + s6 -= s15 * 997805; + s7 += s15 * 136657; + s8 -= s15 * 683901; + s15 = 0; + + s2 += s14 * 666643; + s3 += s14 * 470296; + s4 += s14 * 654183; + s5 -= s14 * 997805; + s6 += s14 * 136657; + s7 -= s14 * 683901; + s14 = 0; + + s1 += s13 * 666643; + s2 += s13 * 470296; + s3 += s13 * 654183; + s4 -= s13 * 997805; + s5 += s13 * 136657; + s6 -= s13 * 683901; + s13 = 0; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + + carry0 = (s0 + (1 << 20)) >> 21; s1 += carry0; s0 -= carry0 << 21; + carry2 = (s2 + (1 << 20)) >> 21; s3 += carry2; s2 -= carry2 << 21; + carry4 = (s4 + (1 << 20)) >> 21; s5 += carry4; s4 -= carry4 << 21; + carry6 = (s6 + (1 << 20)) >> 21; s7 += carry6; s6 -= carry6 << 21; + carry8 = (s8 + (1 << 20)) >> 21; s9 += carry8; s8 -= carry8 << 21; + carry10 = (s10 + (1 << 20)) >> 21; s11 += carry10; s10 -= carry10 << 21; + + carry1 = (s1 + (1 << 20)) >> 21; s2 += carry1; s1 -= carry1 << 21; + carry3 = (s3 + (1 << 20)) >> 21; s4 += carry3; s3 -= carry3 << 21; + carry5 = (s5 + (1 << 20)) >> 21; s6 += carry5; s5 -= carry5 << 21; + carry7 = (s7 + (1 << 20)) >> 21; s8 += carry7; s7 -= carry7 << 21; + carry9 = (s9 + (1 << 20)) >> 21; s10 += carry9; s9 -= carry9 << 21; + carry11 = (s11 + (1 << 20)) >> 21; s12 += carry11; s11 -= carry11 << 21; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + + carry0 = s0 >> 21; s1 += carry0; s0 -= carry0 << 21; + carry1 = s1 >> 21; s2 += carry1; s1 -= carry1 << 21; + carry2 = s2 >> 21; s3 += carry2; s2 -= carry2 << 21; + carry3 = s3 >> 21; s4 += carry3; s3 -= carry3 << 21; + carry4 = s4 >> 21; s5 += carry4; s4 -= carry4 << 21; + carry5 = s5 >> 21; s6 += carry5; s5 -= carry5 << 21; + carry6 = s6 >> 21; s7 += carry6; s6 -= carry6 << 21; + carry7 = s7 >> 21; s8 += carry7; s7 -= carry7 << 21; + carry8 = s8 >> 21; s9 += carry8; s8 -= carry8 << 21; + carry9 = s9 >> 21; s10 += carry9; s9 -= carry9 << 21; + carry10 = s10 >> 21; s11 += carry10; s10 -= carry10 << 21; + carry11 = s11 >> 21; s12 += carry11; s11 -= carry11 << 21; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + + carry0 = s0 >> 21; s1 += carry0; s0 -= carry0 << 21; + carry1 = s1 >> 21; s2 += carry1; s1 -= carry1 << 21; + carry2 = s2 >> 21; s3 += carry2; s2 -= carry2 << 21; + carry3 = s3 >> 21; s4 += carry3; s3 -= carry3 << 21; + carry4 = s4 >> 21; s5 += carry4; s4 -= carry4 << 21; + carry5 = s5 >> 21; s6 += carry5; s5 -= carry5 << 21; + carry6 = s6 >> 21; s7 += carry6; s6 -= carry6 << 21; + carry7 = s7 >> 21; s8 += carry7; s7 -= carry7 << 21; + carry8 = s8 >> 21; s9 += carry8; s8 -= carry8 << 21; + carry9 = s9 >> 21; s10 += carry9; s9 -= carry9 << 21; + carry10 = s10 >> 21; s11 += carry10; s10 -= carry10 << 21; + + unchecked + { + s[0] = (byte)(s0 >> 0); + s[1] = (byte)(s0 >> 8); + s[2] = (byte)((s0 >> 16) | (s1 << 5)); + s[3] = (byte)(s1 >> 3); + s[4] = (byte)(s1 >> 11); + s[5] = (byte)((s1 >> 19) | (s2 << 2)); + s[6] = (byte)(s2 >> 6); + s[7] = (byte)((s2 >> 14) | (s3 << 7)); + s[8] = (byte)(s3 >> 1); + s[9] = (byte)(s3 >> 9); + s[10] = (byte)((s3 >> 17) | (s4 << 4)); + s[11] = (byte)(s4 >> 4); + s[12] = (byte)(s4 >> 12); + s[13] = (byte)((s4 >> 20) | (s5 << 1)); + s[14] = (byte)(s5 >> 7); + s[15] = (byte)((s5 >> 15) | (s6 << 6)); + s[16] = (byte)(s6 >> 2); + s[17] = (byte)(s6 >> 10); + s[18] = (byte)((s6 >> 18) | (s7 << 3)); + s[19] = (byte)(s7 >> 5); + s[20] = (byte)(s7 >> 13); + s[21] = (byte)(s8 >> 0); + s[22] = (byte)(s8 >> 8); + s[23] = (byte)((s8 >> 16) | (s9 << 5)); + s[24] = (byte)(s9 >> 3); + s[25] = (byte)(s9 >> 11); + s[26] = (byte)((s9 >> 19) | (s10 << 2)); + s[27] = (byte)(s10 >> 6); + s[28] = (byte)((s10 >> 14) | (s11 << 7)); + s[29] = (byte)(s11 >> 1); + s[30] = (byte)(s11 >> 9); + s[31] = (byte)(s11 >> 17); + } + } + + } +} \ No newline at end of file diff --git a/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/scalarmult.cs b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/scalarmult.cs new file mode 100644 index 000000000..3a7d8feea --- /dev/null +++ b/src/Discord.Net.Rest/Net/ED25519/Ed25519Ref10/scalarmult.cs @@ -0,0 +1,153 @@ +using System; + +namespace Discord.Net.ED25519.Ed25519Ref10 +{ + public static class MontgomeryOperations + { + public static void scalarmult( + byte[] q, int qoffset, + byte[] n, int noffset, + byte[] p, int poffset) + { + FieldElement p0, q0; + FieldOperations.fe_frombytes2(out p0, p, poffset); + scalarmult(out q0, n, noffset, ref p0); + FieldOperations.fe_tobytes(q, qoffset, ref q0); + } + + internal static void scalarmult( + out FieldElement q, + byte[] n, int noffset, + ref FieldElement p) + { + byte[] e = new byte[32];//ToDo: remove allocation + FieldElement x1, x2, x3; + FieldElement z2, z3; + FieldElement tmp0, tmp1; + + for (int i = 0; i < 32; ++i) + e[i] = n[noffset + i]; + ScalarOperations.sc_clamp(e, 0); + x1 = p; + FieldOperations.fe_1(out x2); + FieldOperations.fe_0(out z2); + x3 = x1; + FieldOperations.fe_1(out z3); + + uint swap = 0; + for (int pos = 254; pos >= 0; --pos) + { + uint b = (uint)(e[pos / 8] >> (pos & 7)); + b &= 1; + swap ^= b; + FieldOperations.fe_cswap(ref x2, ref x3, swap); + FieldOperations.fe_cswap(ref z2, ref z3, swap); + swap = b; + + /* qhasm: enter ladder */ + + /* qhasm: D = X3-Z3 */ + /* asm 1: fe_sub(>D=fe#5,D=tmp0,B=fe#6,B=tmp1,A=fe#1,A=x2,C=fe#2,C=z2,DA=fe#4,DA=z3,CB=fe#2,CB=z2,BB=fe#5,BB=tmp0,AA=fe#6,AA=tmp1,t0=fe#3,t0=x3,t1=fe#2,t1=z2,X4=fe#1,X4=x2,E=fe#6,E=tmp1,t2=fe#2,t2=z2,t3=fe#4,t3=z3,X5=fe#3,X5=x3,t4=fe#5,t4=tmp0,Z5=fe#4,x1,Z5=z3,x1,Z4=fe#2,Z4=z2, _state; + private readonly byte[] _buffer; + private ulong _totalBytes; + public const int BlockSize = 128; + private static readonly byte[] _padding = new byte[] { 0x80 }; + + /// + /// Allocation and initialization of the new SHA-512 object. + /// + public Sha512() + { + _buffer = new byte[BlockSize];//todo: remove allocation + Init(); + } + + /// + /// Performs an initialization of internal SHA-512 state. + /// + public void Init() + { + Sha512Internal.Sha512Init(out _state); + _totalBytes = 0; + } + + /// + /// Updates internal state with data from the provided array segment. + /// + /// Array segment + public void Update(ArraySegment data) + { + Update(data.Array, data.Offset, data.Count); + } + + /// + /// Updates internal state with data from the provided array. + /// + /// Array of bytes + /// Offset of byte sequence + /// Sequence length + public void Update(byte[] data, int index, int length) + { + + Array16 block; + int bytesInBuffer = (int)_totalBytes & (BlockSize - 1); + _totalBytes += (uint)length; + + if (_totalBytes >= ulong.MaxValue / 8) + throw new InvalidOperationException("Too much data"); + // Fill existing buffer + if (bytesInBuffer != 0) + { + var toCopy = Math.Min(BlockSize - bytesInBuffer, length); + Buffer.BlockCopy(data, index, _buffer, bytesInBuffer, toCopy); + index += toCopy; + length -= toCopy; + bytesInBuffer += toCopy; + if (bytesInBuffer == BlockSize) + { + ByteIntegerConverter.Array16LoadBigEndian64(out block, _buffer, 0); + Sha512Internal.Core(out _state, ref _state, ref block); + CryptoBytes.InternalWipe(_buffer, 0, _buffer.Length); + bytesInBuffer = 0; + } + } + // Hash complete blocks without copying + while (length >= BlockSize) + { + ByteIntegerConverter.Array16LoadBigEndian64(out block, data, index); + Sha512Internal.Core(out _state, ref _state, ref block); + index += BlockSize; + length -= BlockSize; + } + // Copy remainder into buffer + if (length > 0) + { + Buffer.BlockCopy(data, index, _buffer, bytesInBuffer, length); + } + } + + /// + /// Finalizes SHA-512 hashing + /// + /// Output buffer + public void Finalize(ArraySegment output) + { + Preconditions.NotNull(output.Array, nameof(output)); + if (output.Count != 64) + throw new ArgumentException("Output should be 64 in length"); + + Update(_padding, 0, _padding.Length); + Array16 block; + ByteIntegerConverter.Array16LoadBigEndian64(out block, _buffer, 0); + CryptoBytes.InternalWipe(_buffer, 0, _buffer.Length); + int bytesInBuffer = (int)_totalBytes & (BlockSize - 1); + if (bytesInBuffer > BlockSize - 16) + { + Sha512Internal.Core(out _state, ref _state, ref block); + block = default(Array16); + } + block.x15 = (_totalBytes - 1) * 8; + Sha512Internal.Core(out _state, ref _state, ref block); + + ByteIntegerConverter.StoreBigEndian64(output.Array, output.Offset + 0, _state.x0); + ByteIntegerConverter.StoreBigEndian64(output.Array, output.Offset + 8, _state.x1); + ByteIntegerConverter.StoreBigEndian64(output.Array, output.Offset + 16, _state.x2); + ByteIntegerConverter.StoreBigEndian64(output.Array, output.Offset + 24, _state.x3); + ByteIntegerConverter.StoreBigEndian64(output.Array, output.Offset + 32, _state.x4); + ByteIntegerConverter.StoreBigEndian64(output.Array, output.Offset + 40, _state.x5); + ByteIntegerConverter.StoreBigEndian64(output.Array, output.Offset + 48, _state.x6); + ByteIntegerConverter.StoreBigEndian64(output.Array, output.Offset + 56, _state.x7); + _state = default(Array8); + } + + /// + /// Finalizes SHA-512 hashing. + /// + /// Hash bytes + public byte[] Finalize() + { + var result = new byte[64]; + Finalize(new ArraySegment(result)); + return result; + } + + /// + /// Calculates SHA-512 hash value for the given bytes array. + /// + /// Data bytes array + /// Hash bytes + public static byte[] Hash(byte[] data) + { + return Hash(data, 0, data.Length); + } + + /// + /// Calculates SHA-512 hash value for the given bytes array. + /// + /// Data bytes array + /// Offset of byte sequence + /// Sequence length + /// Hash bytes + public static byte[] Hash(byte[] data, int index, int length) + { + var hasher = new Sha512(); + hasher.Update(data, index, length); + return hasher.Finalize(); + } + } +} diff --git a/src/Discord.Net.Rest/Net/ED25519/Sha512Internal.cs b/src/Discord.Net.Rest/Net/ED25519/Sha512Internal.cs new file mode 100644 index 000000000..df8842d8d --- /dev/null +++ b/src/Discord.Net.Rest/Net/ED25519/Sha512Internal.cs @@ -0,0 +1,447 @@ +using System; +using System.Collections.Generic; + +namespace Discord.Net.ED25519 +{ + internal static class Sha512Internal + { + private static readonly ulong[] K = new ulong[] + { + 0x428a2f98d728ae22,0x7137449123ef65cd,0xb5c0fbcfec4d3b2f,0xe9b5dba58189dbbc, + 0x3956c25bf348b538,0x59f111f1b605d019,0x923f82a4af194f9b,0xab1c5ed5da6d8118, + 0xd807aa98a3030242,0x12835b0145706fbe,0x243185be4ee4b28c,0x550c7dc3d5ffb4e2, + 0x72be5d74f27b896f,0x80deb1fe3b1696b1,0x9bdc06a725c71235,0xc19bf174cf692694, + 0xe49b69c19ef14ad2,0xefbe4786384f25e3,0x0fc19dc68b8cd5b5,0x240ca1cc77ac9c65, + 0x2de92c6f592b0275,0x4a7484aa6ea6e483,0x5cb0a9dcbd41fbd4,0x76f988da831153b5, + 0x983e5152ee66dfab,0xa831c66d2db43210,0xb00327c898fb213f,0xbf597fc7beef0ee4, + 0xc6e00bf33da88fc2,0xd5a79147930aa725,0x06ca6351e003826f,0x142929670a0e6e70, + 0x27b70a8546d22ffc,0x2e1b21385c26c926,0x4d2c6dfc5ac42aed,0x53380d139d95b3df, + 0x650a73548baf63de,0x766a0abb3c77b2a8,0x81c2c92e47edaee6,0x92722c851482353b, + 0xa2bfe8a14cf10364,0xa81a664bbc423001,0xc24b8b70d0f89791,0xc76c51a30654be30, + 0xd192e819d6ef5218,0xd69906245565a910,0xf40e35855771202a,0x106aa07032bbd1b8, + 0x19a4c116b8d2d0c8,0x1e376c085141ab53,0x2748774cdf8eeb99,0x34b0bcb5e19b48a8, + 0x391c0cb3c5c95a63,0x4ed8aa4ae3418acb,0x5b9cca4f7763e373,0x682e6ff3d6b2b8a3, + 0x748f82ee5defb2fc,0x78a5636f43172f60,0x84c87814a1f0ab72,0x8cc702081a6439ec, + 0x90befffa23631e28,0xa4506cebde82bde9,0xbef9a3f7b2c67915,0xc67178f2e372532b, + 0xca273eceea26619c,0xd186b8c721c0c207,0xeada7dd6cde0eb1e,0xf57d4f7fee6ed178, + 0x06f067aa72176fba,0x0a637dc5a2c898a6,0x113f9804bef90dae,0x1b710b35131c471b, + 0x28db77f523047d84,0x32caab7b40c72493,0x3c9ebe0a15c9bebc,0x431d67c49c100d4c, + 0x4cc5d4becb3e42b6,0x597f299cfc657e2a,0x5fcb6fab3ad6faec,0x6c44198c4a475817 + }; + + internal static void Sha512Init(out Array8 state) + { + state.x0 = 0x6a09e667f3bcc908; + state.x1 = 0xbb67ae8584caa73b; + state.x2 = 0x3c6ef372fe94f82b; + state.x3 = 0xa54ff53a5f1d36f1; + state.x4 = 0x510e527fade682d1; + state.x5 = 0x9b05688c2b3e6c1f; + state.x6 = 0x1f83d9abfb41bd6b; + state.x7 = 0x5be0cd19137e2179; + } + + internal static void Core(out Array8 outputState, ref Array8 inputState, ref Array16 input) + { + unchecked + { + var a = inputState.x0; + var b = inputState.x1; + var c = inputState.x2; + var d = inputState.x3; + var e = inputState.x4; + var f = inputState.x5; + var g = inputState.x6; + var h = inputState.x7; + + var w0 = input.x0; + var w1 = input.x1; + var w2 = input.x2; + var w3 = input.x3; + var w4 = input.x4; + var w5 = input.x5; + var w6 = input.x6; + var w7 = input.x7; + var w8 = input.x8; + var w9 = input.x9; + var w10 = input.x10; + var w11 = input.x11; + var w12 = input.x12; + var w13 = input.x13; + var w14 = input.x14; + var w15 = input.x15; + + int t = 0; + while (true) + { + ulong t1, t2; + + {//0 + t1 = h + + ((e >> 14) ^ (e << (64 - 14)) ^ (e >> 18) ^ (e << (64 - 18)) ^ (e >> 41) ^ (e << (64 - 41))) + + //Sigma1(e) + ((e & f) ^ (~e & g)) + //Ch(e,f,g) + K[t] + w0; + t2 = ((a >> 28) ^ (a << (64 - 28)) ^ (a >> 34) ^ (a << (64 - 34)) ^ (a >> 39) ^ (a << (64 - 39))) + + //Sigma0(a) + ((a & b) ^ (a & c) ^ (b & c)); //Maj(a,b,c) + h = g; + g = f; + f = e; + e = d + t1; + d = c; + c = b; + b = a; + a = t1 + t2; + t++; + } + {//1 + t1 = h + + ((e >> 14) ^ (e << (64 - 14)) ^ (e >> 18) ^ (e << (64 - 18)) ^ (e >> 41) ^ (e << (64 - 41))) + + //Sigma1(e) + ((e & f) ^ (~e & g)) + //Ch(e,f,g) + K[t] + w1; + t2 = ((a >> 28) ^ (a << (64 - 28)) ^ (a >> 34) ^ (a << (64 - 34)) ^ (a >> 39) ^ (a << (64 - 39))) + + //Sigma0(a) + ((a & b) ^ (a & c) ^ (b & c)); //Maj(a,b,c) + h = g; + g = f; + f = e; + e = d + t1; + d = c; + c = b; + b = a; + a = t1 + t2; + t++; + } + {//2 + t1 = h + + ((e >> 14) ^ (e << (64 - 14)) ^ (e >> 18) ^ (e << (64 - 18)) ^ (e >> 41) ^ (e << (64 - 41))) + + //Sigma1(e) + ((e & f) ^ (~e & g)) + //Ch(e,f,g) + K[t] + w2; + t2 = ((a >> 28) ^ (a << (64 - 28)) ^ (a >> 34) ^ (a << (64 - 34)) ^ (a >> 39) ^ (a << (64 - 39))) + + //Sigma0(a) + ((a & b) ^ (a & c) ^ (b & c)); //Maj(a,b,c) + h = g; + g = f; + f = e; + e = d + t1; + d = c; + c = b; + b = a; + a = t1 + t2; + t++; + } + {//3 + t1 = h + + ((e >> 14) ^ (e << (64 - 14)) ^ (e >> 18) ^ (e << (64 - 18)) ^ (e >> 41) ^ (e << (64 - 41))) + + //Sigma1(e) + ((e & f) ^ (~e & g)) + //Ch(e,f,g) + K[t] + w3; + t2 = ((a >> 28) ^ (a << (64 - 28)) ^ (a >> 34) ^ (a << (64 - 34)) ^ (a >> 39) ^ (a << (64 - 39))) + + //Sigma0(a) + ((a & b) ^ (a & c) ^ (b & c)); //Maj(a,b,c) + h = g; + g = f; + f = e; + e = d + t1; + d = c; + c = b; + b = a; + a = t1 + t2; + t++; + } + {//4 + t1 = h + + ((e >> 14) ^ (e << (64 - 14)) ^ (e >> 18) ^ (e << (64 - 18)) ^ (e >> 41) ^ (e << (64 - 41))) + + //Sigma1(e) + ((e & f) ^ (~e & g)) + //Ch(e,f,g) + K[t] + w4; + t2 = ((a >> 28) ^ (a << (64 - 28)) ^ (a >> 34) ^ (a << (64 - 34)) ^ (a >> 39) ^ (a << (64 - 39))) + + //Sigma0(a) + ((a & b) ^ (a & c) ^ (b & c)); //Maj(a,b,c) + h = g; + g = f; + f = e; + e = d + t1; + d = c; + c = b; + b = a; + a = t1 + t2; + t++; + } + {//5 + t1 = h + + ((e >> 14) ^ (e << (64 - 14)) ^ (e >> 18) ^ (e << (64 - 18)) ^ (e >> 41) ^ (e << (64 - 41))) + + //Sigma1(e) + ((e & f) ^ (~e & g)) + //Ch(e,f,g) + K[t] + w5; + t2 = ((a >> 28) ^ (a << (64 - 28)) ^ (a >> 34) ^ (a << (64 - 34)) ^ (a >> 39) ^ (a << (64 - 39))) + + //Sigma0(a) + ((a & b) ^ (a & c) ^ (b & c)); //Maj(a,b,c) + h = g; + g = f; + f = e; + e = d + t1; + d = c; + c = b; + b = a; + a = t1 + t2; + t++; + } + {//6 + t1 = h + + ((e >> 14) ^ (e << (64 - 14)) ^ (e >> 18) ^ (e << (64 - 18)) ^ (e >> 41) ^ (e << (64 - 41))) + + //Sigma1(e) + ((e & f) ^ (~e & g)) + //Ch(e,f,g) + K[t] + w6; + t2 = ((a >> 28) ^ (a << (64 - 28)) ^ (a >> 34) ^ (a << (64 - 34)) ^ (a >> 39) ^ (a << (64 - 39))) + + //Sigma0(a) + ((a & b) ^ (a & c) ^ (b & c)); //Maj(a,b,c) + h = g; + g = f; + f = e; + e = d + t1; + d = c; + c = b; + b = a; + a = t1 + t2; + t++; + } + {//7 + t1 = h + + ((e >> 14) ^ (e << (64 - 14)) ^ (e >> 18) ^ (e << (64 - 18)) ^ (e >> 41) ^ (e << (64 - 41))) + + //Sigma1(e) + ((e & f) ^ (~e & g)) + //Ch(e,f,g) + K[t] + w7; + t2 = ((a >> 28) ^ (a << (64 - 28)) ^ (a >> 34) ^ (a << (64 - 34)) ^ (a >> 39) ^ (a << (64 - 39))) + + //Sigma0(a) + ((a & b) ^ (a & c) ^ (b & c)); //Maj(a,b,c) + h = g; + g = f; + f = e; + e = d + t1; + d = c; + c = b; + b = a; + a = t1 + t2; + t++; + } + {//8 + t1 = h + + ((e >> 14) ^ (e << (64 - 14)) ^ (e >> 18) ^ (e << (64 - 18)) ^ (e >> 41) ^ (e << (64 - 41))) + + //Sigma1(e) + ((e & f) ^ (~e & g)) + //Ch(e,f,g) + K[t] + w8; + t2 = ((a >> 28) ^ (a << (64 - 28)) ^ (a >> 34) ^ (a << (64 - 34)) ^ (a >> 39) ^ (a << (64 - 39))) + + //Sigma0(a) + ((a & b) ^ (a & c) ^ (b & c)); //Maj(a,b,c) + h = g; + g = f; + f = e; + e = d + t1; + d = c; + c = b; + b = a; + a = t1 + t2; + t++; + } + {//9 + t1 = h + + ((e >> 14) ^ (e << (64 - 14)) ^ (e >> 18) ^ (e << (64 - 18)) ^ (e >> 41) ^ (e << (64 - 41))) + + //Sigma1(e) + ((e & f) ^ (~e & g)) + //Ch(e,f,g) + K[t] + w9; + t2 = ((a >> 28) ^ (a << (64 - 28)) ^ (a >> 34) ^ (a << (64 - 34)) ^ (a >> 39) ^ (a << (64 - 39))) + + //Sigma0(a) + ((a & b) ^ (a & c) ^ (b & c)); //Maj(a,b,c) + h = g; + g = f; + f = e; + e = d + t1; + d = c; + c = b; + b = a; + a = t1 + t2; + t++; + } + {//10 + t1 = h + + ((e >> 14) ^ (e << (64 - 14)) ^ (e >> 18) ^ (e << (64 - 18)) ^ (e >> 41) ^ (e << (64 - 41))) + + //Sigma1(e) + ((e & f) ^ (~e & g)) + //Ch(e,f,g) + K[t] + w10; + t2 = ((a >> 28) ^ (a << (64 - 28)) ^ (a >> 34) ^ (a << (64 - 34)) ^ (a >> 39) ^ (a << (64 - 39))) + + //Sigma0(a) + ((a & b) ^ (a & c) ^ (b & c)); //Maj(a,b,c) + h = g; + g = f; + f = e; + e = d + t1; + d = c; + c = b; + b = a; + a = t1 + t2; + t++; + } + {//11 + t1 = h + + ((e >> 14) ^ (e << (64 - 14)) ^ (e >> 18) ^ (e << (64 - 18)) ^ (e >> 41) ^ (e << (64 - 41))) + + //Sigma1(e) + ((e & f) ^ (~e & g)) + //Ch(e,f,g) + K[t] + w11; + t2 = ((a >> 28) ^ (a << (64 - 28)) ^ (a >> 34) ^ (a << (64 - 34)) ^ (a >> 39) ^ (a << (64 - 39))) + + //Sigma0(a) + ((a & b) ^ (a & c) ^ (b & c)); //Maj(a,b,c) + h = g; + g = f; + f = e; + e = d + t1; + d = c; + c = b; + b = a; + a = t1 + t2; + t++; + } + {//12 + t1 = h + + ((e >> 14) ^ (e << (64 - 14)) ^ (e >> 18) ^ (e << (64 - 18)) ^ (e >> 41) ^ (e << (64 - 41))) + + //Sigma1(e) + ((e & f) ^ (~e & g)) + //Ch(e,f,g) + K[t] + w12; + t2 = ((a >> 28) ^ (a << (64 - 28)) ^ (a >> 34) ^ (a << (64 - 34)) ^ (a >> 39) ^ (a << (64 - 39))) + + //Sigma0(a) + ((a & b) ^ (a & c) ^ (b & c)); //Maj(a,b,c) + h = g; + g = f; + f = e; + e = d + t1; + d = c; + c = b; + b = a; + a = t1 + t2; + t++; + } + {//13 + t1 = h + + ((e >> 14) ^ (e << (64 - 14)) ^ (e >> 18) ^ (e << (64 - 18)) ^ (e >> 41) ^ (e << (64 - 41))) + + //Sigma1(e) + ((e & f) ^ (~e & g)) + //Ch(e,f,g) + K[t] + w13; + t2 = ((a >> 28) ^ (a << (64 - 28)) ^ (a >> 34) ^ (a << (64 - 34)) ^ (a >> 39) ^ (a << (64 - 39))) + + //Sigma0(a) + ((a & b) ^ (a & c) ^ (b & c)); //Maj(a,b,c) + h = g; + g = f; + f = e; + e = d + t1; + d = c; + c = b; + b = a; + a = t1 + t2; + t++; + } + {//14 + t1 = h + + ((e >> 14) ^ (e << (64 - 14)) ^ (e >> 18) ^ (e << (64 - 18)) ^ (e >> 41) ^ (e << (64 - 41))) + + //Sigma1(e) + ((e & f) ^ (~e & g)) + //Ch(e,f,g) + K[t] + w14; + t2 = ((a >> 28) ^ (a << (64 - 28)) ^ (a >> 34) ^ (a << (64 - 34)) ^ (a >> 39) ^ (a << (64 - 39))) + + //Sigma0(a) + ((a & b) ^ (a & c) ^ (b & c)); //Maj(a,b,c) + h = g; + g = f; + f = e; + e = d + t1; + d = c; + c = b; + b = a; + a = t1 + t2; + t++; + } + {//15 + t1 = h + + ((e >> 14) ^ (e << (64 - 14)) ^ (e >> 18) ^ (e << (64 - 18)) ^ (e >> 41) ^ (e << (64 - 41))) + + //Sigma1(e) + ((e & f) ^ (~e & g)) + //Ch(e,f,g) + K[t] + w15; + t2 = ((a >> 28) ^ (a << (64 - 28)) ^ (a >> 34) ^ (a << (64 - 34)) ^ (a >> 39) ^ (a << (64 - 39))) + + //Sigma0(a) + ((a & b) ^ (a & c) ^ (b & c)); //Maj(a,b,c) + h = g; + g = f; + f = e; + e = d + t1; + d = c; + c = b; + b = a; + a = t1 + t2; + t++; + } + if (t == 80) + break; + + w0 += ((w14 >> 19) ^ (w14 << (64 - 19)) ^ (w14 >> 61) ^ (w14 << (64 - 61)) ^ (w14 >> 6)) + + w9 + + ((w1 >> 1) ^ (w1 << (64 - 1)) ^ (w1 >> 8) ^ (w1 << (64 - 8)) ^ (w1 >> 7)); + w1 += ((w15 >> 19) ^ (w15 << (64 - 19)) ^ (w15 >> 61) ^ (w15 << (64 - 61)) ^ (w15 >> 6)) + + w10 + + ((w2 >> 1) ^ (w2 << (64 - 1)) ^ (w2 >> 8) ^ (w2 << (64 - 8)) ^ (w2 >> 7)); + w2 += ((w0 >> 19) ^ (w0 << (64 - 19)) ^ (w0 >> 61) ^ (w0 << (64 - 61)) ^ (w0 >> 6)) + + w11 + + ((w3 >> 1) ^ (w3 << (64 - 1)) ^ (w3 >> 8) ^ (w3 << (64 - 8)) ^ (w3 >> 7)); + w3 += ((w1 >> 19) ^ (w1 << (64 - 19)) ^ (w1 >> 61) ^ (w1 << (64 - 61)) ^ (w1 >> 6)) + + w12 + + ((w4 >> 1) ^ (w4 << (64 - 1)) ^ (w4 >> 8) ^ (w4 << (64 - 8)) ^ (w4 >> 7)); + w4 += ((w2 >> 19) ^ (w2 << (64 - 19)) ^ (w2 >> 61) ^ (w2 << (64 - 61)) ^ (w2 >> 6)) + + w13 + + ((w5 >> 1) ^ (w5 << (64 - 1)) ^ (w5 >> 8) ^ (w5 << (64 - 8)) ^ (w5 >> 7)); + w5 += ((w3 >> 19) ^ (w3 << (64 - 19)) ^ (w3 >> 61) ^ (w3 << (64 - 61)) ^ (w3 >> 6)) + + w14 + + ((w6 >> 1) ^ (w6 << (64 - 1)) ^ (w6 >> 8) ^ (w6 << (64 - 8)) ^ (w6 >> 7)); + w6 += ((w4 >> 19) ^ (w4 << (64 - 19)) ^ (w4 >> 61) ^ (w4 << (64 - 61)) ^ (w4 >> 6)) + + w15 + + ((w7 >> 1) ^ (w7 << (64 - 1)) ^ (w7 >> 8) ^ (w7 << (64 - 8)) ^ (w7 >> 7)); + w7 += ((w5 >> 19) ^ (w5 << (64 - 19)) ^ (w5 >> 61) ^ (w5 << (64 - 61)) ^ (w5 >> 6)) + + w0 + + ((w8 >> 1) ^ (w8 << (64 - 1)) ^ (w8 >> 8) ^ (w8 << (64 - 8)) ^ (w8 >> 7)); + w8 += ((w6 >> 19) ^ (w6 << (64 - 19)) ^ (w6 >> 61) ^ (w6 << (64 - 61)) ^ (w6 >> 6)) + + w1 + + ((w9 >> 1) ^ (w9 << (64 - 1)) ^ (w9 >> 8) ^ (w9 << (64 - 8)) ^ (w9 >> 7)); + w9 += ((w7 >> 19) ^ (w7 << (64 - 19)) ^ (w7 >> 61) ^ (w7 << (64 - 61)) ^ (w7 >> 6)) + + w2 + + ((w10 >> 1) ^ (w10 << (64 - 1)) ^ (w10 >> 8) ^ (w10 << (64 - 8)) ^ (w10 >> 7)); + w10 += ((w8 >> 19) ^ (w8 << (64 - 19)) ^ (w8 >> 61) ^ (w8 << (64 - 61)) ^ (w8 >> 6)) + + w3 + + ((w11 >> 1) ^ (w11 << (64 - 1)) ^ (w11 >> 8) ^ (w11 << (64 - 8)) ^ (w11 >> 7)); + w11 += ((w9 >> 19) ^ (w9 << (64 - 19)) ^ (w9 >> 61) ^ (w9 << (64 - 61)) ^ (w9 >> 6)) + + w4 + + ((w12 >> 1) ^ (w12 << (64 - 1)) ^ (w12 >> 8) ^ (w12 << (64 - 8)) ^ (w12 >> 7)); + w12 += ((w10 >> 19) ^ (w10 << (64 - 19)) ^ (w10 >> 61) ^ (w10 << (64 - 61)) ^ (w10 >> 6)) + + w5 + + ((w13 >> 1) ^ (w13 << (64 - 1)) ^ (w13 >> 8) ^ (w13 << (64 - 8)) ^ (w13 >> 7)); + w13 += ((w11 >> 19) ^ (w11 << (64 - 19)) ^ (w11 >> 61) ^ (w11 << (64 - 61)) ^ (w11 >> 6)) + + w6 + + ((w14 >> 1) ^ (w14 << (64 - 1)) ^ (w14 >> 8) ^ (w14 << (64 - 8)) ^ (w14 >> 7)); + w14 += ((w12 >> 19) ^ (w12 << (64 - 19)) ^ (w12 >> 61) ^ (w12 << (64 - 61)) ^ (w12 >> 6)) + + w7 + + ((w15 >> 1) ^ (w15 << (64 - 1)) ^ (w15 >> 8) ^ (w15 << (64 - 8)) ^ (w15 >> 7)); + w15 += ((w13 >> 19) ^ (w13 << (64 - 19)) ^ (w13 >> 61) ^ (w13 << (64 - 61)) ^ (w13 >> 6)) + + w8 + + ((w0 >> 1) ^ (w0 << (64 - 1)) ^ (w0 >> 8) ^ (w0 << (64 - 8)) ^ (w0 >> 7)); + } + + outputState.x0 = inputState.x0 + a; + outputState.x1 = inputState.x1 + b; + outputState.x2 = inputState.x2 + c; + outputState.x3 = inputState.x3 + d; + outputState.x4 = inputState.x4 + e; + outputState.x5 = inputState.x5 + f; + outputState.x6 = inputState.x6 + g; + outputState.x7 = inputState.x7 + h; + } + } + } +} diff --git a/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs b/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs index 3fb45e55d..d9f4ba57a 100644 --- a/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs +++ b/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs @@ -1,3 +1,4 @@ +using Discord.API; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; @@ -5,6 +6,7 @@ using System; using System.Diagnostics; #endif using System.IO; +using System.Linq; using System.Net; using System.Threading; using System.Threading.Tasks; @@ -65,7 +67,9 @@ namespace Discord.Net.Queue try { var response = await request.SendAsync().ConfigureAwait(false); - info = new RateLimitInfo(response.Headers); + info = new RateLimitInfo(response.Headers, request.Endpoint); + + request.Options.ExecuteRatelimitCallback(info); if (response.StatusCode < (HttpStatusCode)200 || response.StatusCode >= (HttpStatusCode)300) { @@ -97,8 +101,7 @@ namespace Discord.Net.Queue continue; //Retry default: - int? code = null; - string reason = null; + API.DiscordError error = null; if (response.Stream != null) { try @@ -106,14 +109,14 @@ namespace Discord.Net.Queue using (var reader = new StreamReader(response.Stream)) using (var jsonReader = new JsonTextReader(reader)) { - var json = JToken.Load(jsonReader); - try { code = json.Value("code"); } catch { }; - try { reason = json.Value("message"); } catch { }; + error = Discord.Rest.DiscordRestClient.Serializer.Deserialize(jsonReader); } } catch { } } - throw new HttpException(response.StatusCode, request, code, reason); + throw new HttpException(response.StatusCode, request, error?.Code, error.Message, error.Errors.IsSpecified + ? error.Errors.Value.Select(x => new DiscordJsonError(x.Name.GetValueOrDefault("root"), x.Errors.Select(y => new DiscordError(y.Code, y.Message)).ToArray())).ToArray() + : null); } } else @@ -351,7 +354,7 @@ namespace Discord.Net.Queue if (info.Limit.HasValue && WindowCount != info.Limit.Value) { WindowCount = info.Limit.Value; - _semaphore = info.Remaining.Value; + _semaphore = is429 ? 0 : info.Remaining.Value; #if DEBUG_LIMITS Debug.WriteLine($"[{id}] Upgraded Semaphore to {info.Remaining.Value}/{WindowCount}"); #endif @@ -368,12 +371,12 @@ namespace Discord.Net.Queue if (info.RetryAfter.HasValue) { //RetryAfter is more accurate than Reset, where available - resetTick = DateTimeOffset.UtcNow.AddMilliseconds(info.RetryAfter.Value); + resetTick = DateTimeOffset.UtcNow.AddSeconds(info.RetryAfter.Value); #if DEBUG_LIMITS Debug.WriteLine($"[{id}] Retry-After: {info.RetryAfter.Value} ({info.RetryAfter.Value} ms)"); #endif } - else if (info.ResetAfter.HasValue && (request.Options.UseSystemClock.HasValue ? !request.Options.UseSystemClock.Value : false)) + else if (info.ResetAfter.HasValue && (request.Options.UseSystemClock.HasValue && !request.Options.UseSystemClock.Value)) { resetTick = DateTimeOffset.UtcNow.Add(info.ResetAfter.Value); #if DEBUG_LIMITS @@ -431,7 +434,7 @@ namespace Discord.Net.Queue if (!hasQueuedReset || resetTick > _resetTick) { _resetTick = resetTick; - LastAttemptAt = resetTick.Value; //Make sure we dont destroy this until after its been reset + LastAttemptAt = resetTick.Value; //Make sure we don't destroy this until after its been reset #if DEBUG_LIMITS Debug.WriteLine($"[{id}] Reset in {(int)Math.Ceiling((resetTick - DateTimeOffset.UtcNow).Value.TotalMilliseconds)} ms"); #endif @@ -452,7 +455,7 @@ namespace Discord.Net.Queue lock (_lock) { millis = (int)Math.Ceiling((_resetTick.Value - DateTimeOffset.UtcNow).TotalMilliseconds); - if (millis <= 0) //Make sure we havent gotten a more accurate reset time + if (millis <= 0) //Make sure we haven't gotten a more accurate reset time { #if DEBUG_LIMITS Debug.WriteLine($"[{id}] * Reset *"); diff --git a/src/Discord.Net.Rest/Net/RateLimitInfo.cs b/src/Discord.Net.Rest/Net/RateLimitInfo.cs index 6a7df7b01..c08f30c7b 100644 --- a/src/Discord.Net.Rest/Net/RateLimitInfo.cs +++ b/src/Discord.Net.Rest/Net/RateLimitInfo.cs @@ -4,19 +4,42 @@ using System.Globalization; namespace Discord.Net { - internal struct RateLimitInfo + /// + /// Represents a REST-Based ratelimit info. + /// + public struct RateLimitInfo : IRateLimitInfo { + /// public bool IsGlobal { get; } + + /// public int? Limit { get; } + + /// public int? Remaining { get; } + + /// public int? RetryAfter { get; } + + /// public DateTimeOffset? Reset { get; } + + /// public TimeSpan? ResetAfter { get; } + + /// public string Bucket { get; } + + /// public TimeSpan? Lag { get; } - internal RateLimitInfo(Dictionary headers) + /// + public string Endpoint { get; } + + internal RateLimitInfo(Dictionary headers, string endpoint) { + Endpoint = endpoint; + IsGlobal = headers.TryGetValue("X-RateLimit-Global", out string temp) && bool.TryParse(temp, out var isGlobal) && isGlobal; Limit = headers.TryGetValue("X-RateLimit-Limit", out temp) && @@ -28,7 +51,7 @@ namespace Discord.Net RetryAfter = headers.TryGetValue("Retry-After", out temp) && int.TryParse(temp, NumberStyles.None, CultureInfo.InvariantCulture, out var retryAfter) ? retryAfter : (int?)null; ResetAfter = headers.TryGetValue("X-RateLimit-Reset-After", out temp) && - double.TryParse(temp, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var resetAfter) ? TimeSpan.FromMilliseconds((long)(resetAfter * 1000)) : (TimeSpan?)null; + double.TryParse(temp, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var resetAfter) ? TimeSpan.FromSeconds(resetAfter) : (TimeSpan?)null; Bucket = headers.TryGetValue("X-RateLimit-Bucket", out temp) ? temp : null; Lag = headers.TryGetValue("Date", out temp) && DateTimeOffset.TryParse(temp, CultureInfo.InvariantCulture, DateTimeStyles.None, out var date) ? DateTimeOffset.UtcNow - date : (TimeSpan?)null; diff --git a/src/Discord.Net.Rest/Utils/HexConverter.cs b/src/Discord.Net.Rest/Utils/HexConverter.cs new file mode 100644 index 000000000..ebd959dcb --- /dev/null +++ b/src/Discord.Net.Rest/Utils/HexConverter.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.Rest +{ + internal class HexConverter + { + public static byte[] HexToByteArray(string hex) + { + if (hex.Length % 2 == 1) + throw new Exception("The binary key cannot have an odd number of digits"); + + byte[] arr = new byte[hex.Length >> 1]; + + for (int i = 0; i < hex.Length >> 1; ++i) + { + arr[i] = (byte)((GetHexVal(hex[i << 1]) << 4) + (GetHexVal(hex[(i << 1) + 1]))); + } + + return arr; + } + private static int GetHexVal(char hex) + { + int val = (int)hex; + //For uppercase A-F letters: + //return val - (val < 58 ? 48 : 55); + //For lowercase a-f letters: + //return val - (val < 58 ? 48 : 87); + //Or the two combined, but a bit slower: + return val - (val < 58 ? 48 : (val < 97 ? 55 : 87)); + } + } +} diff --git a/src/Discord.Net.WebSocket/API/Gateway/ApplicationCommandCreatedUpdatedEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/ApplicationCommandCreatedUpdatedEvent.cs new file mode 100644 index 000000000..91dcbde11 --- /dev/null +++ b/src/Discord.Net.WebSocket/API/Gateway/ApplicationCommandCreatedUpdatedEvent.cs @@ -0,0 +1,10 @@ +using Newtonsoft.Json; + +namespace Discord.API.Gateway +{ + internal class ApplicationCommandCreatedUpdatedEvent : ApplicationCommand + { + [JsonProperty("guild_id")] + public Optional GuildId { get; set; } + } +} diff --git a/src/Discord.Net.WebSocket/API/Gateway/ExtendedGuild.cs b/src/Discord.Net.WebSocket/API/Gateway/ExtendedGuild.cs index 910f6d909..04ee38c0b 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/ExtendedGuild.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/ExtendedGuild.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; using System; @@ -8,18 +7,29 @@ namespace Discord.API.Gateway { [JsonProperty("unavailable")] public bool? Unavailable { get; set; } + [JsonProperty("member_count")] public int MemberCount { get; set; } + [JsonProperty("large")] public bool Large { get; set; } [JsonProperty("presences")] public Presence[] Presences { get; set; } + [JsonProperty("members")] public GuildMember[] Members { get; set; } + [JsonProperty("channels")] public Channel[] Channels { get; set; } + [JsonProperty("joined_at")] public DateTimeOffset JoinedAt { get; set; } + + [JsonProperty("threads")] + public new Channel[] Threads { get; set; } + + [JsonProperty("guild_scheduled_events")] + public GuildScheduledEvent[] GuildScheduledEvents { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/GatewayOpCode.cs b/src/Discord.Net.WebSocket/API/Gateway/GatewayOpCode.cs index 13a2bb462..6f8bf48d4 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/GatewayOpCode.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/GatewayOpCode.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 namespace Discord.API.Gateway { internal enum GatewayOpCode : byte @@ -10,7 +9,7 @@ namespace Discord.API.Gateway /// C→S - Used to associate a connection with a token and specify configuration. Identify = 2, /// C→S - Used to update client's status and current game id. - StatusUpdate = 3, + PresenceUpdate = 3, /// C→S - Used to join a particular voice channel. VoiceStateUpdate = 4, /// C→S - Used to ensure the guild's voice server is alive. diff --git a/src/Discord.Net.WebSocket/API/Gateway/GuildBanEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/GuildBanEvent.cs index 59a3304dd..a8a72e791 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/GuildBanEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/GuildBanEvent.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Gateway diff --git a/src/Discord.Net.WebSocket/API/Gateway/GuildEmojiUpdateEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/GuildEmojiUpdateEvent.cs index 715341dc5..33c10e648 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/GuildEmojiUpdateEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/GuildEmojiUpdateEvent.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Gateway diff --git a/src/Discord.Net.WebSocket/API/Gateway/GuildJoinRequestDeleteEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/GuildJoinRequestDeleteEvent.cs new file mode 100644 index 000000000..cb6fc5f40 --- /dev/null +++ b/src/Discord.Net.WebSocket/API/Gateway/GuildJoinRequestDeleteEvent.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace Discord.API.Gateway +{ + internal class GuildJoinRequestDeleteEvent + { + [JsonProperty("user_id")] + public ulong UserId { get; set; } + [JsonProperty("guild_id")] + public ulong GuildId { get; set; } + } +} diff --git a/src/Discord.Net.WebSocket/API/Gateway/GuildMemberAddEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/GuildMemberAddEvent.cs index 350652faf..dd42978fc 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/GuildMemberAddEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/GuildMemberAddEvent.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Gateway diff --git a/src/Discord.Net.WebSocket/API/Gateway/GuildMemberRemoveEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/GuildMemberRemoveEvent.cs index 501408a7f..ec7df8fd3 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/GuildMemberRemoveEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/GuildMemberRemoveEvent.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Gateway diff --git a/src/Discord.Net.WebSocket/API/Gateway/GuildMemberUpdateEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/GuildMemberUpdateEvent.cs index a234d6da5..0f6fa6f37 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/GuildMemberUpdateEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/GuildMemberUpdateEvent.cs @@ -1,10 +1,13 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; +using System; namespace Discord.API.Gateway { internal class GuildMemberUpdateEvent : GuildMember { + [JsonProperty("joined_at")] + public new DateTimeOffset? JoinedAt { get; set; } + [JsonProperty("guild_id")] public ulong GuildId { get; set; } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/GuildMembersChunkEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/GuildMembersChunkEvent.cs index e401d7fa1..26114bf54 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/GuildMembersChunkEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/GuildMembersChunkEvent.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Gateway diff --git a/src/Discord.Net.WebSocket/API/Gateway/GuildRoleCreateEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/GuildRoleCreateEvent.cs index 3409b1c91..3b02164d5 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/GuildRoleCreateEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/GuildRoleCreateEvent.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Gateway diff --git a/src/Discord.Net.WebSocket/API/Gateway/GuildRoleDeleteEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/GuildRoleDeleteEvent.cs index dbdaeff67..d9bdb9892 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/GuildRoleDeleteEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/GuildRoleDeleteEvent.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Gateway diff --git a/src/Discord.Net.WebSocket/API/Gateway/GuildRoleUpdateEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/GuildRoleUpdateEvent.cs index b04ecb182..bb6a39620 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/GuildRoleUpdateEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/GuildRoleUpdateEvent.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Gateway diff --git a/src/Discord.Net.WebSocket/API/Gateway/GuildScheduledEventUserAddRemoveEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/GuildScheduledEventUserAddRemoveEvent.cs new file mode 100644 index 000000000..3fc959125 --- /dev/null +++ b/src/Discord.Net.WebSocket/API/Gateway/GuildScheduledEventUserAddRemoveEvent.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.API.Gateway +{ + internal class GuildScheduledEventUserAddRemoveEvent + { + [JsonProperty("guild_scheduled_event_id")] + public ulong EventId { get; set; } + [JsonProperty("guild_id")] + public ulong GuildId { get; set; } + [JsonProperty("user_id")] + public ulong UserId { get; set; } + } +} diff --git a/src/Discord.Net.WebSocket/API/Gateway/GuildStickerUpdateEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/GuildStickerUpdateEvent.cs new file mode 100644 index 000000000..f0ecd3a4f --- /dev/null +++ b/src/Discord.Net.WebSocket/API/Gateway/GuildStickerUpdateEvent.cs @@ -0,0 +1,13 @@ +using Newtonsoft.Json; + +namespace Discord.API.Gateway +{ + internal class GuildStickerUpdateEvent + { + [JsonProperty("guild_id")] + public ulong GuildId { get; set; } + + [JsonProperty("stickers")] + public Sticker[] Stickers { get; set; } + } +} diff --git a/src/Discord.Net.WebSocket/API/Gateway/GuildSyncEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/GuildSyncEvent.cs index 6b2e6c02f..ba4c1ca60 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/GuildSyncEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/GuildSyncEvent.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Gateway diff --git a/src/Discord.Net.WebSocket/API/Gateway/HelloEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/HelloEvent.cs index e1ed9463c..a53a96fd8 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/HelloEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/HelloEvent.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Gateway diff --git a/src/Discord.Net.WebSocket/API/Gateway/IdentifyParams.cs b/src/Discord.Net.WebSocket/API/Gateway/IdentifyParams.cs index bb54d4cdd..96c7cb32f 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/IdentifyParams.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/IdentifyParams.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; using System.Collections.Generic; @@ -16,7 +15,7 @@ namespace Discord.API.Gateway [JsonProperty("shard")] public Optional ShardingParams { get; set; } [JsonProperty("presence")] - public Optional Presence { get; set; } + public Optional Presence { get; set; } [JsonProperty("intents")] public Optional Intents { get; set; } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/InviteCreatedEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/InviteCreatedEvent.cs new file mode 100644 index 000000000..1613cdfa6 --- /dev/null +++ b/src/Discord.Net.WebSocket/API/Gateway/InviteCreatedEvent.cs @@ -0,0 +1,32 @@ +using Discord.API; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.API.Gateway +{ + internal class InviteCreatedEvent + { + [JsonProperty("channel_id")] + public ulong ChannelID { get; set; } + [JsonProperty("code")] + public string InviteCode { get; set; } + [JsonProperty("timestamp")] + public Optional RawTimestamp { get; set; } + [JsonProperty("guild_id")] + public ulong? GuildID { get; set; } + [JsonProperty("inviter")] + public Optional Inviter { get; set; } + [JsonProperty("max_age")] + public int RawAge { get; set; } + [JsonProperty("max_uses")] + public int MaxUsers { get; set; } + [JsonProperty("temporary")] + public bool TempInvite { get; set; } + [JsonProperty("uses")] + public int Uses { get; set; } + } +} diff --git a/src/Discord.Net.WebSocket/API/Gateway/InviteDeletedEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/InviteDeletedEvent.cs new file mode 100644 index 000000000..6bdd337f5 --- /dev/null +++ b/src/Discord.Net.WebSocket/API/Gateway/InviteDeletedEvent.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.WebSocket +{ + internal class InviteDeletedEvent + { + [JsonProperty("channel_id")] + public ulong ChannelID { get; set; } + [JsonProperty("guild_id")] + public Optional GuildID { get; set; } + [JsonProperty("code")] + public string Code { get; set; } + } +} diff --git a/src/Discord.Net.WebSocket/API/Gateway/MessageDeleteBulkEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/MessageDeleteBulkEvent.cs index a4cf7d7eb..c503e636d 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/MessageDeleteBulkEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/MessageDeleteBulkEvent.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; using System.Collections.Generic; diff --git a/src/Discord.Net.WebSocket/API/Gateway/ReadyEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/ReadyEvent.cs index ab92d8c36..5cd75dbee 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/ReadyEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/ReadyEvent.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Gateway diff --git a/src/Discord.Net.WebSocket/API/Gateway/RecipientEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/RecipientEvent.cs index 336ffd029..778b5708c 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/RecipientEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/RecipientEvent.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Gateway diff --git a/src/Discord.Net.WebSocket/API/Gateway/RequestMembersParams.cs b/src/Discord.Net.WebSocket/API/Gateway/RequestMembersParams.cs index 6a8d283ed..f7a63e330 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/RequestMembersParams.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/RequestMembersParams.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; using System.Collections.Generic; diff --git a/src/Discord.Net.WebSocket/API/Gateway/ResumeParams.cs b/src/Discord.Net.WebSocket/API/Gateway/ResumeParams.cs index ffb46327b..826e8fadd 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/ResumeParams.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/ResumeParams.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Gateway diff --git a/src/Discord.Net.WebSocket/API/Gateway/ResumedEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/ResumedEvent.cs index d1347beae..870ae7366 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/ResumedEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/ResumedEvent.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Gateway diff --git a/src/Discord.Net.WebSocket/API/Gateway/StatusUpdateParams.cs b/src/Discord.Net.WebSocket/API/Gateway/StatusUpdateParams.cs index 5fec8b4bd..cbde225d2 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/StatusUpdateParams.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/StatusUpdateParams.cs @@ -1,18 +1,18 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Gateway { [JsonObject(MemberSerialization = MemberSerialization.OptIn)] - internal class StatusUpdateParams + internal class PresenceUpdateParams + { [JsonProperty("status")] public UserStatus Status { get; set; } - [JsonProperty("since"), Int53] + [JsonProperty("since", NullValueHandling = NullValueHandling.Include), Int53] public long? IdleSince { get; set; } [JsonProperty("afk")] public bool IsAFK { get; set; } - [JsonProperty("game")] - public Game Game { get; set; } + [JsonProperty("activities")] + public object[] Activities { get; set; } // TODO, change to interface later } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/ThreadListSyncEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/ThreadListSyncEvent.cs new file mode 100644 index 000000000..5084f6c95 --- /dev/null +++ b/src/Discord.Net.WebSocket/API/Gateway/ThreadListSyncEvent.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; + +namespace Discord.API.Gateway +{ + internal class ThreadListSyncEvent + { + [JsonProperty("guild_id")] + public ulong GuildId { get; set; } + + [JsonProperty("channel_ids")] + public Optional ChannelIds { get; set; } + + [JsonProperty("threads")] + public Channel[] Threads { get; set; } + + [JsonProperty("members")] + public ThreadMember[] Members { get; set; } + } +} diff --git a/src/Discord.Net.WebSocket/API/Gateway/ThreadMembersUpdate.cs b/src/Discord.Net.WebSocket/API/Gateway/ThreadMembersUpdate.cs new file mode 100644 index 000000000..83d2c0edd --- /dev/null +++ b/src/Discord.Net.WebSocket/API/Gateway/ThreadMembersUpdate.cs @@ -0,0 +1,22 @@ +using Newtonsoft.Json; + +namespace Discord.API.Gateway +{ + internal class ThreadMembersUpdated + { + [JsonProperty("id")] + public ulong Id { get; set; } + + [JsonProperty("guild_id")] + public ulong GuildId { get; set; } + + [JsonProperty("member_count")] + public int MemberCount { get; set; } + + [JsonProperty("added_members")] + public Optional AddedMembers { get; set; } + + [JsonProperty("removed_member_ids")] + public Optional RemovedMemberIds { get; set; } + } +} diff --git a/src/Discord.Net.WebSocket/API/Gateway/TypingStartEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/TypingStartEvent.cs index 5ceae4b7a..729ea176f 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/TypingStartEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/TypingStartEvent.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Gateway diff --git a/src/Discord.Net.WebSocket/API/Gateway/VoiceServerUpdateEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/VoiceServerUpdateEvent.cs index 29167c1cc..8df3f0108 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/VoiceServerUpdateEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/VoiceServerUpdateEvent.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Gateway diff --git a/src/Discord.Net.WebSocket/API/Gateway/VoiceStateUpdateParams.cs b/src/Discord.Net.WebSocket/API/Gateway/VoiceStateUpdateParams.cs index 521160126..ad21b14f1 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/VoiceStateUpdateParams.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/VoiceStateUpdateParams.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Gateway diff --git a/src/Discord.Net.WebSocket/API/Gateway/WebhookUpdateEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/WebhookUpdateEvent.cs index e5c7afe41..c1e6d5385 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/WebhookUpdateEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/WebhookUpdateEvent.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Gateway diff --git a/src/Discord.Net.WebSocket/API/SocketFrame.cs b/src/Discord.Net.WebSocket/API/SocketFrame.cs index fae741432..11c82ec44 100644 --- a/src/Discord.Net.WebSocket/API/SocketFrame.cs +++ b/src/Discord.Net.WebSocket/API/SocketFrame.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API diff --git a/src/Discord.Net.WebSocket/API/Voice/IdentifyParams.cs b/src/Discord.Net.WebSocket/API/Voice/IdentifyParams.cs index d446867e1..508b70d70 100644 --- a/src/Discord.Net.WebSocket/API/Voice/IdentifyParams.cs +++ b/src/Discord.Net.WebSocket/API/Voice/IdentifyParams.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Voice diff --git a/src/Discord.Net.WebSocket/API/Voice/ReadyEvent.cs b/src/Discord.Net.WebSocket/API/Voice/ReadyEvent.cs index 7188cd8f7..fb910573a 100644 --- a/src/Discord.Net.WebSocket/API/Voice/ReadyEvent.cs +++ b/src/Discord.Net.WebSocket/API/Voice/ReadyEvent.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; using System; @@ -15,7 +14,7 @@ namespace Discord.API.Voice [JsonProperty("modes")] public string[] Modes { get; set; } [JsonProperty("heartbeat_interval")] - [Obsolete("This field is errorneous and should not be used", true)] + [Obsolete("This field is erroneous and should not be used", true)] public int HeartbeatInterval { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Voice/SelectProtocolParams.cs b/src/Discord.Net.WebSocket/API/Voice/SelectProtocolParams.cs index 8c577e5b5..2e9bd157a 100644 --- a/src/Discord.Net.WebSocket/API/Voice/SelectProtocolParams.cs +++ b/src/Discord.Net.WebSocket/API/Voice/SelectProtocolParams.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Voice diff --git a/src/Discord.Net.WebSocket/API/Voice/SessionDescriptionEvent.cs b/src/Discord.Net.WebSocket/API/Voice/SessionDescriptionEvent.cs index 45befadcf..043b9fe86 100644 --- a/src/Discord.Net.WebSocket/API/Voice/SessionDescriptionEvent.cs +++ b/src/Discord.Net.WebSocket/API/Voice/SessionDescriptionEvent.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Voice diff --git a/src/Discord.Net.WebSocket/API/Voice/SpeakingEvent.cs b/src/Discord.Net.WebSocket/API/Voice/SpeakingEvent.cs index 0272a8f53..c1746e9ce 100644 --- a/src/Discord.Net.WebSocket/API/Voice/SpeakingEvent.cs +++ b/src/Discord.Net.WebSocket/API/Voice/SpeakingEvent.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Voice diff --git a/src/Discord.Net.WebSocket/API/Voice/SpeakingParams.cs b/src/Discord.Net.WebSocket/API/Voice/SpeakingParams.cs index abdf90667..e03bfc751 100644 --- a/src/Discord.Net.WebSocket/API/Voice/SpeakingParams.cs +++ b/src/Discord.Net.WebSocket/API/Voice/SpeakingParams.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Voice diff --git a/src/Discord.Net.WebSocket/API/Voice/UdpProtocolInfo.cs b/src/Discord.Net.WebSocket/API/Voice/UdpProtocolInfo.cs index 6f4719e7e..5e69a0370 100644 --- a/src/Discord.Net.WebSocket/API/Voice/UdpProtocolInfo.cs +++ b/src/Discord.Net.WebSocket/API/Voice/UdpProtocolInfo.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Voice diff --git a/src/Discord.Net.WebSocket/API/Voice/VoiceCloseCode.cs b/src/Discord.Net.WebSocket/API/Voice/VoiceCloseCode.cs new file mode 100644 index 000000000..e35227050 --- /dev/null +++ b/src/Discord.Net.WebSocket/API/Voice/VoiceCloseCode.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.API.Voice +{ + /// + /// Represents generic op codes for voice disconnect. + /// + public enum VoiceCloseCode + { + /// + /// You sent an invalid opcode. + /// + UnknownOpcode = 4001, + /// + /// You sent an invalid payload in your identifying to the Gateway. + /// + DecodeFailure = 4002, + /// + /// You sent a payload before identifying with the Gateway. + /// + NotAuthenticated = 4003, + /// + /// The token you sent in your identify payload is incorrect. + /// + AuthenticationFailed = 4004, + /// + /// You sent more than one identify payload. Stahp. + /// + AlreadyAuthenticated = 4005, + /// + /// Your session is no longer valid. + /// + SessionNolongerValid = 4006, + /// + /// Your session has timed out. + /// + SessionTimeout = 4009, + /// + /// We can't find the server you're trying to connect to. + /// + ServerNotFound = 4011, + /// + /// We didn't recognize the protocol you sent. + /// + UnknownProtocol = 4012, + /// + /// Channel was deleted, you were kicked, voice server changed, or the main gateway session was dropped. Should not reconnect. + /// + Disconnected = 4014, + /// + /// The server crashed. Our bad! Try resuming. + /// + VoiceServerCrashed = 4015, + /// + /// We didn't recognize your encryption. + /// + UnknownEncryptionMode = 4016, + } +} diff --git a/src/Discord.Net.WebSocket/API/Voice/VoiceOpCode.cs b/src/Discord.Net.WebSocket/API/Voice/VoiceOpCode.cs index 67afe6173..94006505a 100644 --- a/src/Discord.Net.WebSocket/API/Voice/VoiceOpCode.cs +++ b/src/Discord.Net.WebSocket/API/Voice/VoiceOpCode.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 namespace Discord.API.Voice { internal enum VoiceOpCode : byte diff --git a/src/Discord.Net.WebSocket/Audio/Streams/BufferedWriteStream.cs b/src/Discord.Net.WebSocket/Audio/Streams/BufferedWriteStream.cs index a2de252a2..25afde784 100644 --- a/src/Discord.Net.WebSocket/Audio/Streams/BufferedWriteStream.cs +++ b/src/Discord.Net.WebSocket/Audio/Streams/BufferedWriteStream.cs @@ -125,7 +125,7 @@ namespace Discord.Audio.Streams timestamp += OpusEncoder.FrameSamplesPerChannel; } #if DEBUG - var _ = _logger?.DebugAsync("Buffer underrun"); + var _ = _logger?.DebugAsync("Buffer under run"); #endif } } diff --git a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs index fcaa793fc..4ad25d4d5 100644 --- a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs +++ b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs @@ -1,3 +1,4 @@ +using Discord.Rest; using System; using System.Collections.Generic; using System.Threading.Tasks; @@ -6,7 +7,7 @@ namespace Discord.WebSocket { public partial class BaseSocketClient { - //Channels + #region Channels /// Fired when a channel is created. /// /// @@ -23,7 +24,7 @@ namespace Discord.WebSocket /// /// - public event Func ChannelCreated + public event Func ChannelCreated { add { _channelCreatedEvent.Add(value); } remove { _channelCreatedEvent.Remove(value); } @@ -45,7 +46,8 @@ namespace Discord.WebSocket /// /// - public event Func ChannelDestroyed { + public event Func ChannelDestroyed + { add { _channelDestroyedEvent.Add(value); } remove { _channelDestroyedEvent.Remove(value); } } @@ -67,13 +69,15 @@ namespace Discord.WebSocket /// /// - public event Func ChannelUpdated { + public event Func ChannelUpdated + { add { _channelUpdatedEvent.Add(value); } remove { _channelUpdatedEvent.Remove(value); } - } + } internal readonly AsyncEvent> _channelUpdatedEvent = new AsyncEvent>(); + #endregion - //Messages + #region Messages /// Fired when a message is received. /// /// @@ -92,7 +96,8 @@ namespace Discord.WebSocket /// /// - public event Func MessageReceived { + public event Func MessageReceived + { add { _messageReceivedEvent.Add(value); } remove { _messageReceivedEvent.Remove(value); } } @@ -124,7 +129,9 @@ namespace Discord.WebSocket /// /// - public event Func, Cacheable, Task> MessageDeleted { + + public event Func, Cacheable, Task> MessageDeleted + { add { _messageDeletedEvent.Add(value); } remove { _messageDeletedEvent.Remove(value); } } @@ -182,7 +189,8 @@ namespace Discord.WebSocket /// parameter. /// /// - public event Func, SocketMessage, ISocketMessageChannel, Task> MessageUpdated { + public event Func, SocketMessage, ISocketMessageChannel, Task> MessageUpdated + { add { _messageUpdatedEvent.Add(value); } remove { _messageUpdatedEvent.Remove(value); } } @@ -217,19 +225,22 @@ namespace Discord.WebSocket /// /// - public event Func, Cacheable, SocketReaction, Task> ReactionAdded { + public event Func, Cacheable, SocketReaction, Task> ReactionAdded + { add { _reactionAddedEvent.Add(value); } remove { _reactionAddedEvent.Remove(value); } } internal readonly AsyncEvent, Cacheable, SocketReaction, Task>> _reactionAddedEvent = new AsyncEvent, Cacheable, SocketReaction, Task>>(); /// Fired when a reaction is removed from a message. - public event Func, Cacheable, SocketReaction, Task> ReactionRemoved { + public event Func, Cacheable, SocketReaction, Task> ReactionRemoved + { add { _reactionRemovedEvent.Add(value); } remove { _reactionRemovedEvent.Remove(value); } } internal readonly AsyncEvent, Cacheable, SocketReaction, Task>> _reactionRemovedEvent = new AsyncEvent, Cacheable, SocketReaction, Task>>(); /// Fired when all reactions to a message are cleared. - public event Func, Cacheable, Task> ReactionsCleared { + public event Func, Cacheable, Task> ReactionsCleared + { add { _reactionsClearedEvent.Add(value); } remove { _reactionsClearedEvent.Remove(value); } } @@ -256,104 +267,200 @@ namespace Discord.WebSocket remove { _reactionsRemovedForEmoteEvent.Remove(value); } } internal readonly AsyncEvent, Cacheable, IEmote, Task>> _reactionsRemovedForEmoteEvent = new AsyncEvent, Cacheable, IEmote, Task>>(); + #endregion - //Roles + #region Roles /// Fired when a role is created. - public event Func RoleCreated { + public event Func RoleCreated + { add { _roleCreatedEvent.Add(value); } remove { _roleCreatedEvent.Remove(value); } } internal readonly AsyncEvent> _roleCreatedEvent = new AsyncEvent>(); /// Fired when a role is deleted. - public event Func RoleDeleted { + public event Func RoleDeleted + { add { _roleDeletedEvent.Add(value); } remove { _roleDeletedEvent.Remove(value); } } internal readonly AsyncEvent> _roleDeletedEvent = new AsyncEvent>(); /// Fired when a role is updated. - public event Func RoleUpdated { + public event Func RoleUpdated + { add { _roleUpdatedEvent.Add(value); } remove { _roleUpdatedEvent.Remove(value); } } internal readonly AsyncEvent> _roleUpdatedEvent = new AsyncEvent>(); + #endregion - //Guilds + #region Guilds /// Fired when the connected account joins a guild. - public event Func JoinedGuild { + public event Func JoinedGuild + { add { _joinedGuildEvent.Add(value); } remove { _joinedGuildEvent.Remove(value); } } internal readonly AsyncEvent> _joinedGuildEvent = new AsyncEvent>(); /// Fired when the connected account leaves a guild. - public event Func LeftGuild { + public event Func LeftGuild + { add { _leftGuildEvent.Add(value); } remove { _leftGuildEvent.Remove(value); } } internal readonly AsyncEvent> _leftGuildEvent = new AsyncEvent>(); /// Fired when a guild becomes available. - public event Func GuildAvailable { + public event Func GuildAvailable + { add { _guildAvailableEvent.Add(value); } remove { _guildAvailableEvent.Remove(value); } } internal readonly AsyncEvent> _guildAvailableEvent = new AsyncEvent>(); /// Fired when a guild becomes unavailable. - public event Func GuildUnavailable { + public event Func GuildUnavailable + { add { _guildUnavailableEvent.Add(value); } remove { _guildUnavailableEvent.Remove(value); } } internal readonly AsyncEvent> _guildUnavailableEvent = new AsyncEvent>(); /// Fired when offline guild members are downloaded. - public event Func GuildMembersDownloaded { + public event Func GuildMembersDownloaded + { add { _guildMembersDownloadedEvent.Add(value); } remove { _guildMembersDownloadedEvent.Remove(value); } } internal readonly AsyncEvent> _guildMembersDownloadedEvent = new AsyncEvent>(); /// Fired when a guild is updated. - public event Func GuildUpdated { + public event Func GuildUpdated + { add { _guildUpdatedEvent.Add(value); } remove { _guildUpdatedEvent.Remove(value); } } internal readonly AsyncEvent> _guildUpdatedEvent = new AsyncEvent>(); + /// Fired when a user leaves without agreeing to the member screening + public event Func, SocketGuild, Task> GuildJoinRequestDeleted + { + add { _guildJoinRequestDeletedEvent.Add(value); } + remove { _guildJoinRequestDeletedEvent.Remove(value); } + } + internal readonly AsyncEvent, SocketGuild, Task>> _guildJoinRequestDeletedEvent = new AsyncEvent, SocketGuild, Task>>(); + #endregion + + #region Guild Events + + /// + /// Fired when a guild event is created. + /// + public event Func GuildScheduledEventCreated + { + add { _guildScheduledEventCreated.Add(value); } + remove { _guildScheduledEventCreated.Remove(value); } + } + internal readonly AsyncEvent> _guildScheduledEventCreated = new AsyncEvent>(); + + /// + /// Fired when a guild event is updated. + /// + public event Func, SocketGuildEvent, Task> GuildScheduledEventUpdated + { + add { _guildScheduledEventUpdated.Add(value); } + remove { _guildScheduledEventUpdated.Remove(value); } + } + internal readonly AsyncEvent, SocketGuildEvent, Task>> _guildScheduledEventUpdated = new AsyncEvent, SocketGuildEvent, Task>>(); + + + /// + /// Fired when a guild event is cancelled. + /// + public event Func GuildScheduledEventCancelled + { + add { _guildScheduledEventCancelled.Add(value); } + remove { _guildScheduledEventCancelled.Remove(value); } + } + internal readonly AsyncEvent> _guildScheduledEventCancelled = new AsyncEvent>(); + + /// + /// Fired when a guild event is completed. + /// + public event Func GuildScheduledEventCompleted + { + add { _guildScheduledEventCompleted.Add(value); } + remove { _guildScheduledEventCompleted.Remove(value); } + } + internal readonly AsyncEvent> _guildScheduledEventCompleted = new AsyncEvent>(); + + /// + /// Fired when a guild event is started. + /// + public event Func GuildScheduledEventStarted + { + add { _guildScheduledEventStarted.Add(value); } + remove { _guildScheduledEventStarted.Remove(value); } + } + internal readonly AsyncEvent> _guildScheduledEventStarted = new AsyncEvent>(); + + public event Func, SocketGuildEvent, Task> GuildScheduledEventUserAdd + { + add { _guildScheduledEventUserAdd.Add(value); } + remove { _guildScheduledEventUserAdd.Remove(value); } + } + internal readonly AsyncEvent, SocketGuildEvent, Task>> _guildScheduledEventUserAdd = new AsyncEvent, SocketGuildEvent, Task>>(); + + public event Func, SocketGuildEvent, Task> GuildScheduledEventUserRemove + { + add { _guildScheduledEventUserRemove.Add(value); } + remove { _guildScheduledEventUserRemove.Remove(value); } + } + internal readonly AsyncEvent, SocketGuildEvent, Task>> _guildScheduledEventUserRemove = new AsyncEvent, SocketGuildEvent, Task>>(); + - //Users + #endregion + + #region Users /// Fired when a user joins a guild. - public event Func UserJoined { + public event Func UserJoined + { add { _userJoinedEvent.Add(value); } remove { _userJoinedEvent.Remove(value); } } internal readonly AsyncEvent> _userJoinedEvent = new AsyncEvent>(); /// Fired when a user leaves a guild. - public event Func UserLeft { + public event Func UserLeft + { add { _userLeftEvent.Add(value); } remove { _userLeftEvent.Remove(value); } } internal readonly AsyncEvent> _userLeftEvent = new AsyncEvent>(); /// Fired when a user is banned from a guild. - public event Func UserBanned { + public event Func UserBanned + { add { _userBannedEvent.Add(value); } remove { _userBannedEvent.Remove(value); } } internal readonly AsyncEvent> _userBannedEvent = new AsyncEvent>(); /// Fired when a user is unbanned from a guild. - public event Func UserUnbanned { + public event Func UserUnbanned + { add { _userUnbannedEvent.Add(value); } remove { _userUnbannedEvent.Remove(value); } } internal readonly AsyncEvent> _userUnbannedEvent = new AsyncEvent>(); /// Fired when a user is updated. - public event Func UserUpdated { + public event Func UserUpdated + { add { _userUpdatedEvent.Add(value); } remove { _userUpdatedEvent.Remove(value); } } internal readonly AsyncEvent> _userUpdatedEvent = new AsyncEvent>(); /// Fired when a guild member is updated, or a member presence is updated. - public event Func, SocketGuildUser, Task> GuildMemberUpdated { + public event Func, SocketGuildUser, Task> GuildMemberUpdated + { add { _guildMemberUpdatedEvent.Add(value); } remove { _guildMemberUpdatedEvent.Remove(value); } } - internal readonly AsyncEvent, SocketGuildUser, Task>> _guildMemberUpdatedEvent = new AsyncEvent, SocketGuildUser, Task>>(); + internal readonly AsyncEvent, SocketGuildUser, Task>> _guildMemberUpdatedEvent = new AsyncEvent, SocketGuildUser, Task>>(); /// Fired when a user joins, leaves, or moves voice channels. - public event Func UserVoiceStateUpdated { + public event Func UserVoiceStateUpdated + { add { _userVoiceStateUpdatedEvent.Add(value); } remove { _userVoiceStateUpdatedEvent.Remove(value); } } @@ -361,36 +468,41 @@ namespace Discord.WebSocket /// Fired when the bot connects to a Discord voice server. public event Func VoiceServerUpdated { - add { _voiceServerUpdatedEvent.Add(value); } + add { _voiceServerUpdatedEvent.Add(value); } remove { _voiceServerUpdatedEvent.Remove(value); } } internal readonly AsyncEvent> _voiceServerUpdatedEvent = new AsyncEvent>(); /// Fired when the connected account is updated. - public event Func CurrentUserUpdated { + public event Func CurrentUserUpdated + { add { _selfUpdatedEvent.Add(value); } remove { _selfUpdatedEvent.Remove(value); } } internal readonly AsyncEvent> _selfUpdatedEvent = new AsyncEvent>(); /// Fired when a user starts typing. - public event Func, Cacheable, Task> UserIsTyping { + public event Func, Cacheable, Task> UserIsTyping + { add { _userIsTypingEvent.Add(value); } remove { _userIsTypingEvent.Remove(value); } } internal readonly AsyncEvent, Cacheable, Task>> _userIsTypingEvent = new AsyncEvent, Cacheable, Task>>(); /// Fired when a user joins a group channel. - public event Func RecipientAdded { + public event Func RecipientAdded + { add { _recipientAddedEvent.Add(value); } remove { _recipientAddedEvent.Remove(value); } } internal readonly AsyncEvent> _recipientAddedEvent = new AsyncEvent>(); /// Fired when a user is removed from a group channel. - public event Func RecipientRemoved { + public event Func RecipientRemoved + { add { _recipientRemovedEvent.Add(value); } remove { _recipientRemovedEvent.Remove(value); } } internal readonly AsyncEvent> _recipientRemovedEvent = new AsyncEvent>(); + #endregion - //Invites + #region Invites /// /// Fired when an invite is created. /// @@ -431,5 +543,292 @@ namespace Discord.WebSocket remove { _inviteDeletedEvent.Remove(value); } } internal readonly AsyncEvent> _inviteDeletedEvent = new AsyncEvent>(); + #endregion + + #region Interactions + /// + /// Fired when an Interaction is created. This event covers all types of interactions including but not limited to: buttons, select menus, slash commands, autocompletes. + /// + /// + /// + /// This event is fired when an interaction is created. The event handler must return a + /// and accept a as its parameter. + /// + /// + /// The interaction created will be passed into the parameter. + /// + /// + public event Func InteractionCreated + { + add { _interactionCreatedEvent.Add(value); } + remove { _interactionCreatedEvent.Remove(value); } + } + internal readonly AsyncEvent> _interactionCreatedEvent = new AsyncEvent>(); + + /// + /// Fired when a button is clicked and its interaction is received. + /// + public event Func ButtonExecuted + { + add => _buttonExecuted.Add(value); + remove => _buttonExecuted.Remove(value); + } + internal readonly AsyncEvent> _buttonExecuted = new AsyncEvent>(); + + /// + /// Fired when a select menu is used and its interaction is received. + /// + public event Func SelectMenuExecuted + { + add => _selectMenuExecuted.Add(value); + remove => _selectMenuExecuted.Remove(value); + } + internal readonly AsyncEvent> _selectMenuExecuted = new AsyncEvent>(); + /// + /// Fired when a slash command is used and its interaction is received. + /// + public event Func SlashCommandExecuted + { + add => _slashCommandExecuted.Add(value); + remove => _slashCommandExecuted.Remove(value); + } + internal readonly AsyncEvent> _slashCommandExecuted = new AsyncEvent>(); + + /// + /// Fired when a user command is used and its interaction is received. + /// + public event Func UserCommandExecuted + { + add => _userCommandExecuted.Add(value); + remove => _userCommandExecuted.Remove(value); + } + internal readonly AsyncEvent> _userCommandExecuted = new AsyncEvent>(); + + /// + /// Fired when a message command is used and its interaction is received. + /// + public event Func MessageCommandExecuted + { + add => _messageCommandExecuted.Add(value); + remove => _messageCommandExecuted.Remove(value); + } + internal readonly AsyncEvent> _messageCommandExecuted = new AsyncEvent>(); + /// + /// Fired when an autocomplete is used and its interaction is received. + /// + public event Func AutocompleteExecuted + { + add => _autocompleteExecuted.Add(value); + remove => _autocompleteExecuted.Remove(value); + } + internal readonly AsyncEvent> _autocompleteExecuted = new AsyncEvent>(); + + /// + /// Fired when a guild application command is created. + /// + /// + /// + /// This event is fired when an application command is created. The event handler must return a + /// and accept a as its parameter. + /// + /// + /// The command that was deleted will be passed into the parameter. + /// + /// + /// This event is an undocumented discord event and may break at any time, its not recommended to rely on this event + /// + /// + public event Func ApplicationCommandCreated + { + add { _applicationCommandCreated.Add(value); } + remove { _applicationCommandCreated.Remove(value); } + } + internal readonly AsyncEvent> _applicationCommandCreated = new AsyncEvent>(); + + /// + /// Fired when a guild application command is updated. + /// + /// + /// + /// This event is fired when an application command is updated. The event handler must return a + /// and accept a as its parameter. + /// + /// + /// The command that was deleted will be passed into the parameter. + /// + /// + /// This event is an undocumented discord event and may break at any time, its not recommended to rely on this event + /// + /// + public event Func ApplicationCommandUpdated + { + add { _applicationCommandUpdated.Add(value); } + remove { _applicationCommandUpdated.Remove(value); } + } + internal readonly AsyncEvent> _applicationCommandUpdated = new AsyncEvent>(); + + /// + /// Fired when a guild application command is deleted. + /// + /// + /// + /// This event is fired when an application command is deleted. The event handler must return a + /// and accept a as its parameter. + /// + /// + /// The command that was deleted will be passed into the parameter. + /// + /// + /// This event is an undocumented discord event and may break at any time, its not recommended to rely on this event + /// + /// + public event Func ApplicationCommandDeleted + { + add { _applicationCommandDeleted.Add(value); } + remove { _applicationCommandDeleted.Remove(value); } + } + internal readonly AsyncEvent> _applicationCommandDeleted = new AsyncEvent>(); + + /// + /// Fired when a thread is created within a guild, or when the current user is added to a thread. + /// + public event Func ThreadCreated + { + add { _threadCreated.Add(value); } + remove { _threadCreated.Remove(value); } + } + internal readonly AsyncEvent> _threadCreated = new AsyncEvent>(); + + /// + /// Fired when a thread is updated within a guild. + /// + public event Func, SocketThreadChannel, Task> ThreadUpdated + { + add { _threadUpdated.Add(value); } + remove { _threadUpdated.Remove(value); } + } + + internal readonly AsyncEvent, SocketThreadChannel, Task>> _threadUpdated = new(); + + /// + /// Fired when a thread is deleted. + /// + public event Func, Task> ThreadDeleted + { + add { _threadDeleted.Add(value); } + remove { _threadDeleted.Remove(value); } + } + internal readonly AsyncEvent, Task>> _threadDeleted = new AsyncEvent, Task>>(); + + /// + /// Fired when a user joins a thread + /// + public event Func ThreadMemberJoined + { + add { _threadMemberJoined.Add(value); } + remove { _threadMemberJoined.Remove(value); } + } + internal readonly AsyncEvent> _threadMemberJoined = new AsyncEvent>(); + + /// + /// Fired when a user leaves a thread + /// + public event Func ThreadMemberLeft + { + add { _threadMemberLeft.Add(value); } + remove { _threadMemberLeft.Remove(value); } + } + internal readonly AsyncEvent> _threadMemberLeft = new AsyncEvent>(); + + /// + /// Fired when a stage is started. + /// + public event Func StageStarted + { + add { _stageStarted.Add(value); } + remove { _stageStarted.Remove(value); } + } + internal readonly AsyncEvent> _stageStarted = new AsyncEvent>(); + + /// + /// Fired when a stage ends. + /// + public event Func StageEnded + { + add { _stageEnded.Add(value); } + remove { _stageEnded.Remove(value); } + } + internal readonly AsyncEvent> _stageEnded = new AsyncEvent>(); + + /// + /// Fired when a stage is updated. + /// + public event Func StageUpdated + { + add { _stageUpdated.Add(value); } + remove { _stageUpdated.Remove(value); } + } + internal readonly AsyncEvent> _stageUpdated = new AsyncEvent>(); + + /// + /// Fired when a user requests to speak within a stage channel. + /// + public event Func RequestToSpeak + { + add { _requestToSpeak.Add(value); } + remove { _requestToSpeak.Remove(value); } + } + internal readonly AsyncEvent> _requestToSpeak = new AsyncEvent>(); + + /// + /// Fired when a speaker is added in a stage channel. + /// + public event Func SpeakerAdded + { + add { _speakerAdded.Add(value); } + remove { _speakerAdded.Remove(value); } + } + internal readonly AsyncEvent> _speakerAdded = new AsyncEvent>(); + + /// + /// Fired when a speaker is removed from a stage channel. + /// + public event Func SpeakerRemoved + { + add { _speakerRemoved.Add(value); } + remove { _speakerRemoved.Remove(value); } + } + internal readonly AsyncEvent> _speakerRemoved = new AsyncEvent>(); + + /// + /// Fired when a sticker in a guild is created. + /// + public event Func GuildStickerCreated + { + add { _guildStickerCreated.Add(value); } + remove { _guildStickerCreated.Remove(value); } + } + internal readonly AsyncEvent> _guildStickerCreated = new AsyncEvent>(); + + /// + /// Fired when a sticker in a guild is updated. + /// + public event Func GuildStickerUpdated + { + add { _guildStickerUpdated.Add(value); } + remove { _guildStickerUpdated.Remove(value); } + } + internal readonly AsyncEvent> _guildStickerUpdated = new AsyncEvent>(); + + /// + /// Fired when a sticker in a guild is deleted. + /// + public event Func GuildStickerDeleted + { + add { _guildStickerDeleted.Add(value); } + remove { _guildStickerDeleted.Remove(value); } + } + internal readonly AsyncEvent> _guildStickerDeleted = new AsyncEvent>(); + #endregion } } diff --git a/src/Discord.Net.WebSocket/BaseSocketClient.cs b/src/Discord.Net.WebSocket/BaseSocketClient.cs index 1cfe6c8bf..9e25ab382 100644 --- a/src/Discord.Net.WebSocket/BaseSocketClient.cs +++ b/src/Discord.Net.WebSocket/BaseSocketClient.cs @@ -12,6 +12,7 @@ namespace Discord.WebSocket /// public abstract partial class BaseSocketClient : BaseDiscordClient, IDiscordClient { + #region BaseSocketClient protected readonly DiscordSocketConfig BaseConfig; /// @@ -45,6 +46,10 @@ namespace Discord.WebSocket internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; /// + /// Gets a collection of default stickers. + /// + public abstract IReadOnlyCollection> DefaultStickerPacks { get; } + /// /// Gets the current logged-in user. /// public virtual new SocketSelfUser CurrentUser { get => base.CurrentUser as SocketSelfUser; protected set => base.CurrentUser = value; } @@ -75,7 +80,7 @@ namespace Discord.WebSocket : base(config, client) => BaseConfig = config; private static DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) => new DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent, - useSystemClock: config.UseSystemClock); + useSystemClock: config.UseSystemClock); /// /// Gets a Discord application information for the logged-in user. @@ -268,8 +273,19 @@ namespace Discord.WebSocket /// public Task GetInviteAsync(string inviteId, RequestOptions options = null) => ClientHelper.GetInviteAsync(this, inviteId, options ?? RequestOptions.Default); - - // IDiscordClient + /// + /// Gets a sticker. + /// + /// Whether or not to allow downloading from the api. + /// The id of the sticker to get. + /// The options to be used when sending the request. + /// + /// A if found, otherwise . + /// + public abstract Task GetStickerAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); +#endregion + + #region IDiscordClient /// async Task IDiscordClient.GetApplicationInfoAsync(RequestOptions options) => await GetApplicationInfoAsync(options).ConfigureAwait(false); @@ -317,5 +333,6 @@ namespace Discord.WebSocket { return await GetVoiceRegionsAsync().ConfigureAwait(false); } + #endregion } } diff --git a/src/Discord.Net.WebSocket/ClientState.cs b/src/Discord.Net.WebSocket/ClientState.cs index f2e370d02..7129feb48 100644 --- a/src/Discord.Net.WebSocket/ClientState.cs +++ b/src/Discord.Net.WebSocket/ClientState.cs @@ -16,12 +16,14 @@ namespace Discord.WebSocket private readonly ConcurrentDictionary _guilds; private readonly ConcurrentDictionary _users; private readonly ConcurrentHashSet _groupChannels; + private readonly ConcurrentDictionary _commands; internal IReadOnlyCollection Channels => _channels.ToReadOnlyCollection(); internal IReadOnlyCollection DMChannels => _dmChannels.ToReadOnlyCollection(); internal IReadOnlyCollection GroupChannels => _groupChannels.Select(x => GetChannel(x) as SocketGroupChannel).ToReadOnlyCollection(_groupChannels); internal IReadOnlyCollection Guilds => _guilds.ToReadOnlyCollection(); internal IReadOnlyCollection Users => _users.ToReadOnlyCollection(); + internal IReadOnlyCollection Commands => _commands.ToReadOnlyCollection(); internal IReadOnlyCollection PrivateChannels => _dmChannels.Select(x => x.Value as ISocketPrivateChannel).Concat( @@ -37,6 +39,7 @@ namespace Discord.WebSocket _guilds = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(guildCount * CollectionMultiplier)); _users = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(estimatedUsersCount * CollectionMultiplier)); _groupChannels = new ConcurrentHashSet(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(10 * CollectionMultiplier)); + _commands = new ConcurrentDictionary(); } internal SocketChannel GetChannel(ulong id) @@ -139,5 +142,33 @@ namespace Discord.WebSocket foreach (var guild in _guilds.Values) guild.PurgeGuildUserCache(); } + + internal SocketApplicationCommand GetCommand(ulong id) + { + if (_commands.TryGetValue(id, out SocketApplicationCommand command)) + return command; + return null; + } + internal void AddCommand(SocketApplicationCommand command) + { + _commands[command.Id] = command; + } + internal SocketApplicationCommand GetOrAddCommand(ulong id, Func commandFactory) + { + return _commands.GetOrAdd(id, commandFactory); + } + internal SocketApplicationCommand RemoveCommand(ulong id) + { + if (_commands.TryRemove(id, out SocketApplicationCommand command)) + return command; + return null; + } + internal void PurgeCommands(Func precondition) + { + var ids = _commands.Where(x => precondition(x.Value)).Select(x => x.Key); + + foreach (var id in ids) + _commands.TryRemove(id, out var _); + } } } diff --git a/src/Discord.Net.WebSocket/Commands/ShardedCommandContext.cs b/src/Discord.Net.WebSocket/Commands/ShardedCommandContext.cs index f970c32fc..905cd01a1 100644 --- a/src/Discord.Net.WebSocket/Commands/ShardedCommandContext.cs +++ b/src/Discord.Net.WebSocket/Commands/ShardedCommandContext.cs @@ -5,6 +5,7 @@ namespace Discord.Commands /// The sharded variant of , which may contain the client, user, guild, channel, and message. public class ShardedCommandContext : SocketCommandContext, ICommandContext { + #region ShardedCommandContext /// Gets the that the command is executed with. public new DiscordShardedClient Client { get; } @@ -17,9 +18,11 @@ namespace Discord.Commands /// Gets the shard ID of the command context. private static int GetShardId(DiscordShardedClient client, IGuild guild) => guild == null ? 0 : client.GetShardIdFor(guild); +#endregion - //ICommandContext + #region ICommandContext /// IDiscordClient ICommandContext.Client => Client; + #endregion } } diff --git a/src/Discord.Net.WebSocket/Commands/SocketCommandContext.cs b/src/Discord.Net.WebSocket/Commands/SocketCommandContext.cs index f4d517909..d7180873b 100644 --- a/src/Discord.Net.WebSocket/Commands/SocketCommandContext.cs +++ b/src/Discord.Net.WebSocket/Commands/SocketCommandContext.cs @@ -7,6 +7,7 @@ namespace Discord.Commands /// public class SocketCommandContext : ICommandContext { + #region SocketCommandContext /// /// Gets the that the command is executed with. /// @@ -46,8 +47,9 @@ namespace Discord.Commands User = msg.Author; Message = msg; } +#endregion - //ICommandContext + #region ICommandContext /// IDiscordClient ICommandContext.Client => Client; /// @@ -58,5 +60,6 @@ namespace Discord.Commands IUser ICommandContext.User => User; /// IUserMessage ICommandContext.Message => Message; + #endregion } } diff --git a/src/Discord.Net.WebSocket/ConnectionManager.cs b/src/Discord.Net.WebSocket/ConnectionManager.cs index e444f359f..f304d4ea8 100644 --- a/src/Discord.Net.WebSocket/ConnectionManager.cs +++ b/src/Discord.Net.WebSocket/ConnectionManager.cs @@ -57,6 +57,9 @@ namespace Discord public virtual async Task StartAsync() { + if (State != ConnectionState.Disconnected) + throw new InvalidOperationException("Cannot start an already running client."); + await AcquireConnectionLock().ConfigureAwait(false); var reconnectCancelToken = new CancellationTokenSource(); _reconnectCancelToken?.Dispose(); diff --git a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj index 01aece130..4121e7d00 100644 --- a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj +++ b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj @@ -1,4 +1,4 @@ - + @@ -13,4 +13,4 @@ - + \ No newline at end of file diff --git a/src/Discord.Net.WebSocket/DiscordShardedClient.Events.cs b/src/Discord.Net.WebSocket/DiscordShardedClient.Events.cs index c9e679669..50230572c 100644 --- a/src/Discord.Net.WebSocket/DiscordShardedClient.Events.cs +++ b/src/Discord.Net.WebSocket/DiscordShardedClient.Events.cs @@ -1,11 +1,11 @@ -using System; +using System; using System.Threading.Tasks; namespace Discord.WebSocket { public partial class DiscordShardedClient { - //General + #region General /// Fired when a shard is connected to the Discord gateway. public event Func ShardConnected { @@ -34,5 +34,6 @@ namespace Discord.WebSocket remove { _shardLatencyUpdatedEvent.Remove(value); } } private readonly AsyncEvent> _shardLatencyUpdatedEvent = new AsyncEvent>(); + #endregion } -} \ No newline at end of file +} diff --git a/src/Discord.Net.WebSocket/DiscordShardedClient.cs b/src/Discord.Net.WebSocket/DiscordShardedClient.cs index e6d05b525..307f9a009 100644 --- a/src/Discord.Net.WebSocket/DiscordShardedClient.cs +++ b/src/Discord.Net.WebSocket/DiscordShardedClient.cs @@ -6,16 +6,19 @@ using System.IO; using System.Linq; using System.Threading.Tasks; using System.Threading; +using System.Collections.Immutable; namespace Discord.WebSocket { public partial class DiscordShardedClient : BaseSocketClient, IDiscordClient { + #region DiscordShardedClient private readonly DiscordSocketConfig _baseConfig; private readonly Dictionary _shardIdsToIndex; private readonly bool _automaticShards; private int[] _shardIds; private DiscordSocketClient[] _shards; + private ImmutableArray> _defaultStickers; private int _totalShards; private SemaphoreSlim[] _identifySemaphores; private object _semaphoreResetLock; @@ -30,7 +33,20 @@ namespace Discord.WebSocket /// public override IActivity Activity { get => _shards[0].Activity; protected set { } } - internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; + internal new DiscordSocketApiClient ApiClient + { + get + { + if (base.ApiClient.CurrentUserId == null) + base.ApiClient.CurrentUserId = CurrentUser?.Id; + + return base.ApiClient; + } + } + /// + public override IReadOnlyCollection> DefaultStickerPacks + => _defaultStickers.ToReadOnlyCollection(); + /// public override IReadOnlyCollection Guilds => GetGuilds().ToReadOnlyCollection(GetGuildCount); /// @@ -68,6 +84,7 @@ namespace Discord.WebSocket _shardIdsToIndex = new Dictionary(); config.DisplayInitialLog = false; _baseConfig = config; + _defaultStickers = ImmutableArray.Create>(); if (config.TotalShards == null) _automaticShards = true; @@ -146,6 +163,10 @@ namespace Discord.WebSocket //Assume thread safe: already in a connection lock for (int i = 0; i < _shards.Length; i++) await _shards[i].LoginAsync(tokenType, token); + + if(_defaultStickers.Length == 0 && _baseConfig.AlwaysDownloadDefaultStickers) + await DownloadDefaultStickersAsync().ConfigureAwait(false); + } internal override async Task OnLogoutAsync() { @@ -238,6 +259,67 @@ namespace Discord.WebSocket result += _shards[i].Guilds.Count; return result; } + /// + public override async Task GetStickerAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null) + { + var sticker = _defaultStickers.FirstOrDefault(x => x.Stickers.Any(y => y.Id == id))?.Stickers.FirstOrDefault(x => x.Id == id); + + if (sticker != null) + return sticker; + + foreach (var guild in Guilds) + { + sticker = await guild.GetStickerAsync(id, CacheMode.CacheOnly).ConfigureAwait(false); + + if (sticker != null) + return sticker; + } + + if (mode == CacheMode.CacheOnly) + return null; + + var model = await ApiClient.GetStickerAsync(id, options).ConfigureAwait(false); + + if (model == null) + return null; + + + if (model.GuildId.IsSpecified) + { + var guild = GetGuild(model.GuildId.Value); + sticker = guild.AddOrUpdateSticker(model); + return sticker; + } + else + { + return SocketSticker.Create(_shards[0], model); + } + } + private async Task DownloadDefaultStickersAsync() + { + var models = await ApiClient.ListNitroStickerPacksAsync().ConfigureAwait(false); + + var builder = ImmutableArray.CreateBuilder>(); + + foreach (var model in models.StickerPacks) + { + var stickers = model.Stickers.Select(x => SocketSticker.Create(_shards[0], x)); + + var pack = new StickerPack( + model.Name, + model.Id, + model.SkuId, + model.CoverStickerId.ToNullable(), + model.Description, + model.BannerAssetId, + stickers + ); + + builder.Add(pack); + } + + _defaultStickers = builder.ToImmutable(); + } /// public override SocketUser GetUser(ulong id) @@ -377,9 +459,45 @@ namespace Discord.WebSocket client.InviteCreated += (invite) => _inviteCreatedEvent.InvokeAsync(invite); client.InviteDeleted += (channel, invite) => _inviteDeletedEvent.InvokeAsync(channel, invite); + + client.InteractionCreated += (interaction) => _interactionCreatedEvent.InvokeAsync(interaction); + client.ButtonExecuted += (arg) => _buttonExecuted.InvokeAsync(arg); + client.SelectMenuExecuted += (arg) => _selectMenuExecuted.InvokeAsync(arg); + client.SlashCommandExecuted += (arg) => _slashCommandExecuted.InvokeAsync(arg); + client.UserCommandExecuted += (arg) => _userCommandExecuted.InvokeAsync(arg); + client.MessageCommandExecuted += (arg) => _messageCommandExecuted.InvokeAsync(arg); + client.AutocompleteExecuted += (arg) => _autocompleteExecuted.InvokeAsync(arg); + + client.ThreadUpdated += (thread1, thread2) => _threadUpdated.InvokeAsync(thread1, thread2); + client.ThreadCreated += (thread) => _threadCreated.InvokeAsync(thread); + client.ThreadDeleted += (thread) => _threadDeleted.InvokeAsync(thread); + + client.ThreadMemberJoined += (user) => _threadMemberJoined.InvokeAsync(user); + client.ThreadMemberLeft += (user) => _threadMemberLeft.InvokeAsync(user); + client.StageEnded += (stage) => _stageEnded.InvokeAsync(stage); + client.StageStarted += (stage) => _stageStarted.InvokeAsync(stage); + client.StageUpdated += (stage1, stage2) => _stageUpdated.InvokeAsync(stage1, stage2); + + client.RequestToSpeak += (stage, user) => _requestToSpeak.InvokeAsync(stage, user); + client.SpeakerAdded += (stage, user) => _speakerAdded.InvokeAsync(stage, user); + client.SpeakerRemoved += (stage, user) => _speakerRemoved.InvokeAsync(stage, user); + + client.GuildStickerCreated += (sticker) => _guildStickerCreated.InvokeAsync(sticker); + client.GuildStickerDeleted += (sticker) => _guildStickerDeleted.InvokeAsync(sticker); + client.GuildStickerUpdated += (before, after) => _guildStickerUpdated.InvokeAsync(before, after); + client.GuildJoinRequestDeleted += (userId, guildId) => _guildJoinRequestDeletedEvent.InvokeAsync(userId, guildId); + + client.GuildScheduledEventCancelled += (arg) => _guildScheduledEventCancelled.InvokeAsync(arg); + client.GuildScheduledEventCompleted += (arg) => _guildScheduledEventCompleted.InvokeAsync(arg); + client.GuildScheduledEventCreated += (arg) => _guildScheduledEventCreated.InvokeAsync(arg); + client.GuildScheduledEventUpdated += (arg1, arg2) => _guildScheduledEventUpdated.InvokeAsync(arg1, arg2); + client.GuildScheduledEventStarted += (arg) => _guildScheduledEventStarted.InvokeAsync(arg); + client.GuildScheduledEventUserAdd += (arg1, arg2) => _guildScheduledEventUserAdd.InvokeAsync(arg1, arg2); + client.GuildScheduledEventUserRemove += (arg1, arg2) => _guildScheduledEventUserRemove.InvokeAsync(arg1, arg2); } +#endregion - //IDiscordClient + #region IDiscordClient /// async Task IDiscordClient.GetApplicationInfoAsync(RequestOptions options) => await GetApplicationInfoAsync().ConfigureAwait(false); @@ -426,7 +544,9 @@ namespace Discord.WebSocket { return await GetVoiceRegionAsync(id).ConfigureAwait(false); } + #endregion + #region Dispose internal override void Dispose(bool disposing) { if (!_isDisposed) @@ -445,5 +565,6 @@ namespace Discord.WebSocket base.Dispose(disposing); } + #endregion } } diff --git a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs index 65fd23d3f..3c0f3d4a8 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Discord.API.Gateway; using Discord.Net.Queue; using Discord.Net.Rest; @@ -75,8 +74,15 @@ namespace Discord.API using (var jsonReader = new JsonTextReader(reader)) { var msg = _serializer.Deserialize(jsonReader); + if (msg != null) + { +#if DEBUG_PACKETS + Console.WriteLine($"<- {(GatewayOpCode)msg.Operation} [{msg.Type ?? "none"}] : {(msg.Payload as Newtonsoft.Json.Linq.JToken)?.ToString().Length}"); +#endif + await _receivedGatewayEvent.InvokeAsync((GatewayOpCode)msg.Operation, msg.Sequence, msg.Type, msg.Payload).ConfigureAwait(false); + } } } }; @@ -87,11 +93,21 @@ namespace Discord.API { var msg = _serializer.Deserialize(jsonReader); if (msg != null) + { +#if DEBUG_PACKETS + Console.WriteLine($"<- {(GatewayOpCode)msg.Operation} [{msg.Type ?? "none"}] : {(msg.Payload as Newtonsoft.Json.Linq.JToken)?.ToString().Length}"); +#endif + await _receivedGatewayEvent.InvokeAsync((GatewayOpCode)msg.Operation, msg.Sequence, msg.Type, msg.Payload).ConfigureAwait(false); + } } }; WebSocketClient.Closed += async ex => { +#if DEBUG_PACKETS + Console.WriteLine(ex); +#endif + await DisconnectAsync().ConfigureAwait(false); await _disconnectedEvent.InvokeAsync(ex).ConfigureAwait(false); }; @@ -153,6 +169,11 @@ namespace Discord.API var gatewayResponse = await GetGatewayAsync().ConfigureAwait(false); _gatewayUrl = $"{gatewayResponse.Url}?v={DiscordConfig.APIVersion}&encoding={DiscordSocketConfig.GatewayEncoding}&compress=zlib-stream"; } + +#if DEBUG_PACKETS + Console.WriteLine("Connecting to gateway: " + _gatewayUrl); +#endif + await WebSocketClient.ConnectAsync(_gatewayUrl).ConfigureAwait(false); ConnectionState = ConnectionState.Connected; @@ -195,7 +216,7 @@ namespace Discord.API ConnectionState = ConnectionState.Disconnected; } - //Core + #region Core public Task SendGatewayAsync(GatewayOpCode opCode, object payload, RequestOptions options = null) => SendGatewayInternalAsync(opCode, payload, options); private async Task SendGatewayInternalAsync(GatewayOpCode opCode, object payload, RequestOptions options) @@ -213,6 +234,10 @@ namespace Discord.API options.BucketId = GatewayBucket.Get(GatewayBucketType.Unbucketed).Id; await RequestQueue.SendAsync(new WebSocketRequest(WebSocketClient, bytes, true, opCode == GatewayOpCode.Heartbeat, options)).ConfigureAwait(false); await _sentGatewayMessageEvent.InvokeAsync(opCode).ConfigureAwait(false); + +#if DEBUG_PACKETS + Console.WriteLine($"-> {opCode}:\n{SerializeJson(payload)}"); +#endif } public async Task SendIdentifyAsync(int largeThreshold = 100, int shardID = 0, int totalShards = 1, GatewayIntents gatewayIntents = GatewayIntents.AllUnprivileged, (UserStatus, bool, long?, GameModel)? presence = null, RequestOptions options = null) @@ -220,7 +245,9 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); var props = new Dictionary { - ["$device"] = "Discord.Net" + ["$device"] = "Discord.Net", + ["$os"] = Environment.OSVersion.Platform.ToString(), + [$"browser"] = "Discord.Net" }; var msg = new IdentifyParams() { @@ -237,12 +264,12 @@ namespace Discord.API if (presence.HasValue) { - msg.Presence = new StatusUpdateParams + msg.Presence = new PresenceUpdateParams { Status = presence.Value.Item1, IsAFK = presence.Value.Item2, IdleSince = presence.Value.Item3, - Game = presence.Value.Item4, + Activities = new object[] { presence.Value.Item4 } }; } @@ -264,18 +291,18 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); await SendGatewayAsync(GatewayOpCode.Heartbeat, lastSeq, options: options).ConfigureAwait(false); } - public async Task SendStatusUpdateAsync(UserStatus status, bool isAFK, long? since, GameModel game, RequestOptions options = null) + public async Task SendPresenceUpdateAsync(UserStatus status, bool isAFK, long? since, GameModel game, RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); - var args = new StatusUpdateParams + var args = new PresenceUpdateParams { Status = status, IdleSince = since, IsAFK = isAFK, - Game = game + Activities = new object[] { game } }; options.BucketId = GatewayBucket.Get(GatewayBucketType.PresenceUpdate).Id; - await SendGatewayAsync(GatewayOpCode.StatusUpdate, args, options: options).ConfigureAwait(false); + await SendGatewayAsync(GatewayOpCode.PresenceUpdate, args, options: options).ConfigureAwait(false); } public async Task SendRequestMembersAsync(IEnumerable guildIds, RequestOptions options = null) { @@ -299,5 +326,6 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); await SendGatewayAsync(GatewayOpCode.GuildSync, guildIds, options: options).ConfigureAwait(false); } + #endregion } } diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.Events.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.Events.cs index 0418727bf..ab13d93db 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.Events.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.Events.cs @@ -6,7 +6,7 @@ namespace Discord.WebSocket { public partial class DiscordSocketClient { - //General + #region General /// Fired when connected to the Discord gateway. public event Func Connected { @@ -45,5 +45,6 @@ namespace Discord.WebSocket internal DiscordSocketClient(DiscordSocketConfig config, DiscordRestApiClient client) : base(config, client) { } + #endregion } } diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 2cdff662c..03c85ffc7 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -24,8 +24,9 @@ namespace Discord.WebSocket /// public partial class DiscordSocketClient : BaseSocketClient, IDiscordClient { + #region DiscordSocketClient private readonly ConcurrentQueue _largeGuilds; - private readonly JsonSerializer _serializer; + internal readonly JsonSerializer _serializer; private readonly DiscordShardedClient _shardedClient; private readonly DiscordSocketClient _parentClient; private readonly ConcurrentQueue _heartbeatTimes; @@ -44,12 +45,13 @@ namespace Discord.WebSocket private RestApplication _applicationInfo; private bool _isDisposed; private GatewayIntents _gatewayIntents; + private ImmutableArray> _defaultStickers; /// /// Provides access to a REST-only client with a shared state from this client. /// public override DiscordSocketRestClient Rest { get; } - /// Gets the shard of of this client. + /// Gets the shard of this client. public int ShardId { get; } /// Gets the current connection state of this client. public ConnectionState ConnectionState => _connection.State; @@ -61,8 +63,9 @@ namespace Discord.WebSocket /// public override IActivity Activity { get => _activity.GetValueOrDefault(); protected set => _activity = Optional.Create(value); } private Optional _activity; + #endregion - //From DiscordSocketConfig + // From DiscordSocketConfig internal int TotalShards { get; private set; } internal int MessageCacheSize { get; private set; } internal int LargeThreshold { get; private set; } @@ -71,10 +74,22 @@ namespace Discord.WebSocket internal WebSocketProvider WebSocketProvider { get; private set; } internal bool AlwaysDownloadUsers { get; private set; } internal int? HandlerTimeout { get; private set; } - - internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; + internal bool AlwaysDownloadDefaultStickers { get; private set; } + internal bool AlwaysResolveStickers { get; private set; } + internal new DiscordSocketApiClient ApiClient => base.ApiClient; /// public override IReadOnlyCollection Guilds => State.Guilds; + /// + public override IReadOnlyCollection> DefaultStickerPacks + { + get + { + if (_shardedClient != null) + return _shardedClient.DefaultStickerPacks; + else + return _defaultStickers.ToReadOnlyCollection(); + } + } /// public override IReadOnlyCollection PrivateChannels => State.PrivateChannels; /// @@ -130,11 +145,14 @@ namespace Discord.WebSocket UdpSocketProvider = config.UdpSocketProvider; WebSocketProvider = config.WebSocketProvider; AlwaysDownloadUsers = config.AlwaysDownloadUsers; + AlwaysDownloadDefaultStickers = config.AlwaysDownloadDefaultStickers; + AlwaysResolveStickers = config.AlwaysResolveStickers; HandlerTimeout = config.HandlerTimeout; State = new ClientState(0, 0); Rest = new DiscordSocketRestClient(config, ApiClient); _heartbeatTimes = new ConcurrentQueue(); _gatewayIntents = config.GatewayIntents; + _defaultStickers = ImmutableArray.Create>(); _stateLock = new SemaphoreSlim(1, 1); _gatewayLogger = LogManager.CreateLogger(ShardId == 0 && TotalShards == 1 ? "Gateway" : $"Shard #{ShardId}"); @@ -193,6 +211,35 @@ namespace Discord.WebSocket base.Dispose(disposing); } + internal override async Task OnLoginAsync(TokenType tokenType, string token) + { + if (_shardedClient == null && _defaultStickers.Length == 0 && AlwaysDownloadDefaultStickers) + { + var models = await ApiClient.ListNitroStickerPacksAsync().ConfigureAwait(false); + + var builder = ImmutableArray.CreateBuilder>(); + + foreach (var model in models.StickerPacks) + { + var stickers = model.Stickers.Select(x => SocketSticker.Create(this, x)); + + var pack = new StickerPack( + model.Name, + model.Id, + model.SkuId, + model.CoverStickerId.ToNullable(), + model.Description, + model.BannerAssetId, + stickers + ); + + builder.Add(pack); + } + + _defaultStickers = builder.ToImmutable(); + } + } + /// internal override async Task OnLogoutAsync() { @@ -279,7 +326,7 @@ namespace Discord.WebSocket /// public override async Task GetApplicationInfoAsync(RequestOptions options = null) - => _applicationInfo ?? (_applicationInfo = await ClientHelper.GetApplicationInfoAsync(this, options ?? RequestOptions.Default).ConfigureAwait(false)); + => _applicationInfo ??= await ClientHelper.GetApplicationInfoAsync(this, options ?? RequestOptions.Default).ConfigureAwait(false); /// public override SocketGuild GetGuild(ulong id) @@ -341,6 +388,83 @@ namespace Discord.WebSocket /// public override SocketUser GetUser(string username, string discriminator) => State.Users.FirstOrDefault(x => x.Discriminator == discriminator && x.Username == username); + + /// + /// Gets a global application command. + /// + /// The id of the command. + /// The options to be used when sending the request. + /// + /// A ValueTask that represents the asynchronous get operation. The task result contains the application command if found, otherwise + /// . + /// + public async ValueTask GetGlobalApplicationCommandAsync(ulong id, RequestOptions options = null) + { + var command = State.GetCommand(id); + + if (command != null) + return command; + + var model = await ApiClient.GetGlobalApplicationCommandAsync(id, options); + + if (model == null) + return null; + + command = SocketApplicationCommand.Create(this, model); + + State.AddCommand(command); + + return command; + } + /// + /// Gets a collection of all global commands. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection of global + /// application commands. + /// + public async Task> GetGlobalApplicationCommandsAsync(RequestOptions options = null) + { + var commands = (await ApiClient.GetGlobalApplicationCommandsAsync(options)).Select(x => SocketApplicationCommand.Create(this, x)); + + foreach(var command in commands) + { + State.AddCommand(command); + } + + return commands.ToImmutableArray(); + } + + public async Task CreateGlobalApplicationCommandAsync(ApplicationCommandProperties properties, RequestOptions options = null) + { + var model = await InteractionHelper.CreateGlobalCommandAsync(this, properties, options).ConfigureAwait(false); + + var entity = State.GetOrAddCommand(model.Id, (id) => SocketApplicationCommand.Create(this, model)); + + //Update it in case it was cached + entity.Update(model); + + return entity; + } + public async Task> BulkOverwriteGlobalApplicationCommandsAsync( + ApplicationCommandProperties[] properties, RequestOptions options = null) + { + var models = await InteractionHelper.BulkOverwriteGlobalCommandsAsync(this, properties, options); + + var entities = models.Select(x => SocketApplicationCommand.Create(this, x)); + + //Purge our previous commands + State.PurgeCommands(x => x.IsGlobalCommand); + + foreach(var entity in entities) + { + State.AddCommand(entity); + } + + return entities.ToImmutableArray(); + } + /// /// Clears cached users from the client. /// @@ -366,6 +490,56 @@ namespace Discord.WebSocket internal void RemoveUser(ulong id) => State.RemoveUser(id); + /// + public override async Task GetStickerAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null) + { + var sticker = _defaultStickers.FirstOrDefault(x => x.Stickers.Any(y => y.Id == id))?.Stickers.FirstOrDefault(x => x.Id == id); + + if (sticker != null) + return sticker; + + foreach(var guild in Guilds) + { + sticker = await guild.GetStickerAsync(id, CacheMode.CacheOnly).ConfigureAwait(false); + + if (sticker != null) + return sticker; + } + + if (mode == CacheMode.CacheOnly) + return null; + + var model = await ApiClient.GetStickerAsync(id, options).ConfigureAwait(false); + + if(model == null) + return null; + + + if (model.GuildId.IsSpecified) + { + var guild = State.GetGuild(model.GuildId.Value); + + //Since the sticker can be from another guild, check if we are in the guild or its in the cache + if (guild != null) + sticker = guild.AddOrUpdateSticker(model); + else + sticker = SocketSticker.Create(this, model); + return sticker; + } + else + { + return SocketSticker.Create(this, model); + } + } + + /// + /// Gets a sticker. + /// + /// The unique identifier of the sticker. + /// A sticker if found, otherwise . + public SocketSticker GetSticker(ulong id) + => GetStickerAsync(id, CacheMode.CacheOnly).GetAwaiter().GetResult(); + /// public override async ValueTask> GetVoiceRegionsAsync(RequestOptions options = null) { @@ -402,6 +576,8 @@ namespace Discord.WebSocket { if (ConnectionState == ConnectionState.Connected) { + EnsureGatewayIntent(GatewayIntents.GuildMembers); + //Race condition leads to guilds being requested twice, probably okay await ProcessUserDownloadsAsync(guilds.Select(x => GetGuild(x.Id)).Where(x => x != null)).ConfigureAwait(false); } @@ -493,7 +669,7 @@ namespace Discord.WebSocket var presence = BuildCurrentStatus() ?? (UserStatus.Online, false, null, null); - await ApiClient.SendStatusUpdateAsync( + await ApiClient.SendPresenceUpdateAsync( status: presence.Item1, isAFK: presence.Item2, since: presence.Item3, @@ -510,7 +686,7 @@ namespace Discord.WebSocket return null; GameModel game = null; - // Discord only accepts rich presence over RPC, don't even bother building a payload + //Discord only accepts rich presence over RPC, don't even bother building a payload if (activity.GetValueOrDefault() != null) { @@ -532,6 +708,7 @@ namespace Discord.WebSocket game); } + #region ProcessMessageAsync private async Task ProcessMessageAsync(GatewayOpCode opCode, int? seq, string type, object payload) { if (seq != null) @@ -604,7 +781,7 @@ namespace Discord.WebSocket case GatewayOpCode.Dispatch: switch (type) { - //Connection + #region Connection case "READY": { try @@ -615,6 +792,7 @@ namespace Discord.WebSocket var state = new ClientState(data.Guilds.Length, data.PrivateChannels.Length); var currentUser = SocketSelfUser.Create(this, state, data.User); + Rest.CreateRestSelfUser(data.User); var activities = _activity.IsSpecified ? ImmutableList.Create(_activity.Value) : null; currentUser.Presence = new SocketPresence(Status, null, activities); ApiClient.CurrentUserId = currentUser.Id; @@ -680,8 +858,9 @@ namespace Discord.WebSocket await _gatewayLogger.InfoAsync("Resumed previous session").ConfigureAwait(false); } break; + #endregion - //Guilds + #region Guilds case "GUILD_CREATE": { var data = (payload as JToken).ToObject(_serializer); @@ -831,8 +1010,62 @@ namespace Discord.WebSocket } } break; + case "GUILD_STICKERS_UPDATE": + { + await _gatewayLogger.DebugAsync($"Received Dispatch (GUILD_STICKERS_UPDATE)").ConfigureAwait(false); + + var data = (payload as JToken).ToObject(_serializer); + + var guild = State.GetGuild(data.GuildId); - //Channels + if (guild == null) + { + await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); + return; + } + + var newStickers = data.Stickers.Where(x => !guild.Stickers.Any(y => y.Id == x.Id)); + var deletedStickers = guild.Stickers.Where(x => !data.Stickers.Any(y => y.Id == x.Id)); + var updatedStickers = data.Stickers.Select(x => + { + var s = guild.Stickers.FirstOrDefault(y => y.Id == x.Id); + if (s == null) + return null; + + var e = s.Equals(x); + if (!e) + { + return (s, x) as (SocketCustomSticker Entity, API.Sticker Model)?; + } + else + { + return null; + } + }).Where(x => x.HasValue).Select(x => x.Value).ToArray(); + + foreach (var model in newStickers) + { + var entity = guild.AddSticker(model); + await TimedInvokeAsync(_guildStickerCreated, nameof(GuildStickerCreated), entity); + } + foreach (var sticker in deletedStickers) + { + var entity = guild.RemoveSticker(sticker.Id); + await TimedInvokeAsync(_guildStickerDeleted, nameof(GuildStickerDeleted), entity); + } + foreach (var entityModelPair in updatedStickers) + { + var before = entityModelPair.Entity.Clone(); + + entityModelPair.Entity.Update(entityModelPair.Model); + + await TimedInvokeAsync(_guildStickerUpdated, nameof(GuildStickerUpdated), before, entityModelPair.Entity); + } + } + break; + #endregion + + #region Channels case "CHANNEL_CREATE": { await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_CREATE)").ConfigureAwait(false); @@ -934,8 +1167,9 @@ namespace Discord.WebSocket } } break; + #endregion - //Members + #region Members case "GUILD_MEMBER_ADD": { await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBER_ADD)").ConfigureAwait(false); @@ -1066,6 +1300,32 @@ namespace Discord.WebSocket } } break; + case "GUILD_JOIN_REQUEST_DELETE": + { + await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_JOIN_REQUEST_DELETE)").ConfigureAwait(false); + + var data = (payload as JToken).ToObject(_serializer); + + var guild = State.GetGuild(data.GuildId); + + if (guild == null) + { + await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); + return; + } + + var user = guild.RemoveUser(data.UserId); + guild.MemberCount--; + + var cacheableUser = new Cacheable(user, data.UserId, user != null, () => Task.FromResult((SocketGuildUser)null)); + + await TimedInvokeAsync(_guildJoinRequestDeletedEvent, nameof(GuildJoinRequestDeleted), cacheableUser, guild).ConfigureAwait(false); + } + break; + #endregion + + #region DM Channels + case "CHANNEL_RECIPIENT_ADD": { await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_RECIPIENT_ADD)").ConfigureAwait(false); @@ -1107,7 +1367,9 @@ namespace Discord.WebSocket } break; - //Roles + #endregion + + #region Roles case "GUILD_ROLE_CREATE": { await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_ROLE_CREATE)").ConfigureAwait(false); @@ -1199,8 +1461,9 @@ namespace Discord.WebSocket } } break; + #endregion - //Bans + #region Bans case "GUILD_BAN_ADD": { await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_BAN_ADD)").ConfigureAwait(false); @@ -1253,8 +1516,9 @@ namespace Discord.WebSocket } } break; + #endregion - //Messages + #region Messages case "MESSAGE_CREATE": { await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_CREATE)").ConfigureAwait(false); @@ -1344,7 +1608,7 @@ namespace Discord.WebSocket } else { - //Edited message isnt in cache, create a detached one + //Edited message isn't in cache, create a detached one SocketUser author; if (data.Author.IsSpecified) { @@ -1585,8 +1849,9 @@ namespace Discord.WebSocket await TimedInvokeAsync(_messagesBulkDeletedEvent, nameof(MessagesBulkDeleted), cacheableList, cacheableChannel).ConfigureAwait(false); } break; + #endregion - //Statuses + #region Statuses case "PRESENCE_UPDATE": { await _gatewayLogger.DebugAsync("Received Dispatch (PRESENCE_UPDATE)").ConfigureAwait(false); @@ -1674,8 +1939,9 @@ namespace Discord.WebSocket await TimedInvokeAsync(_userIsTypingEvent, nameof(UserIsTyping), cacheableUser, cacheableChannel).ConfigureAwait(false); } break; + #endregion - //Users + #region Users case "USER_UPDATE": { await _gatewayLogger.DebugAsync("Received Dispatch (USER_UPDATE)").ConfigureAwait(false); @@ -1694,8 +1960,9 @@ namespace Discord.WebSocket } } break; + #endregion - //Voice + #region Voice case "VOICE_STATE_UPDATE": { await _gatewayLogger.DebugAsync("Received Dispatch (VOICE_STATE_UPDATE)").ConfigureAwait(false); @@ -1732,7 +1999,7 @@ namespace Discord.WebSocket after = SocketVoiceState.Create(null, data); } - // per g250k, this should always be sent, but apparently not always + //Per g250k, this should always be sent, but apparently not always user = guild.GetUser(data.UserId) ?? (data.Member.IsSpecified ? guild.AddOrUpdateUser(data.Member.Value) : null); if (user == null) @@ -1767,6 +2034,29 @@ namespace Discord.WebSocket } } + if (user is SocketGuildUser guildUser && data.ChannelId.HasValue) + { + SocketStageChannel stage = guildUser.Guild.GetStageChannel(data.ChannelId.Value); + + if (stage != null && before.VoiceChannel != null && after.VoiceChannel != null) + { + if (!before.RequestToSpeakTimestamp.HasValue && after.RequestToSpeakTimestamp.HasValue) + { + await TimedInvokeAsync(_requestToSpeak, nameof(RequestToSpeak), stage, guildUser); + return; + } + if(before.IsSuppressed && !after.IsSuppressed) + { + await TimedInvokeAsync(_speakerAdded, nameof(SpeakerAdded), stage, guildUser); + return; + } + if(!before.IsSuppressed && after.IsSuppressed) + { + await TimedInvokeAsync(_speakerRemoved, nameof(SpeakerRemoved), stage, guildUser); + } + } + } + await TimedInvokeAsync(_userVoiceStateUpdatedEvent, nameof(UserVoiceStateUpdated), user, before, after).ConfigureAwait(false); } break; @@ -1801,8 +2091,9 @@ namespace Discord.WebSocket } break; + #endregion - //Invites + #region Invites case "INVITE_CREATE": { await _gatewayLogger.DebugAsync("Received Dispatch (INVITE_CREATE)").ConfigureAwait(false); @@ -1859,8 +2150,529 @@ namespace Discord.WebSocket } } break; + #endregion + + #region Interactions + case "INTERACTION_CREATE": + { + await _gatewayLogger.DebugAsync("Received Dispatch (INTERACTION_CREATE)").ConfigureAwait(false); + + var data = (payload as JToken).ToObject(_serializer); + + SocketChannel channel = null; + if(data.ChannelId.IsSpecified) + { + channel = State.GetChannel(data.ChannelId.Value); + } + else if (data.User.IsSpecified) + { + channel = State.GetDMChannel(data.User.Value.Id); + } + + if (channel == null) + { + var channelModel = await Rest.ApiClient.GetChannelAsync(data.ChannelId.Value); + + if (data.GuildId.IsSpecified) + channel = SocketTextChannel.Create(State.GetGuild(data.GuildId.Value), State, channelModel); + else + channel = (SocketChannel)SocketChannel.CreatePrivate(this, State, channelModel); + + State.AddChannel(channel); + } + + if (channel is ISocketMessageChannel textChannel) + { + var guild = (channel as SocketGuildChannel)?.Guild; + if (guild != null && !guild.IsSynced) + { + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + return; + } + + var interaction = SocketInteraction.Create(this, data, channel as ISocketMessageChannel); + + await TimedInvokeAsync(_interactionCreatedEvent, nameof(InteractionCreated), interaction).ConfigureAwait(false); + + switch (interaction) + { + case SocketSlashCommand slashCommand: + await TimedInvokeAsync(_slashCommandExecuted, nameof(SlashCommandExecuted), slashCommand).ConfigureAwait(false); + break; + case SocketMessageComponent messageComponent: + if(messageComponent.Data.Type == ComponentType.SelectMenu) + await TimedInvokeAsync(_selectMenuExecuted, nameof(SelectMenuExecuted), messageComponent).ConfigureAwait(false); + if(messageComponent.Data.Type == ComponentType.Button) + await TimedInvokeAsync(_buttonExecuted, nameof(ButtonExecuted), messageComponent).ConfigureAwait(false); + break; + case SocketUserCommand userCommand: + await TimedInvokeAsync(_userCommandExecuted, nameof(UserCommandExecuted), userCommand).ConfigureAwait(false); + break; + case SocketMessageCommand messageCommand: + await TimedInvokeAsync(_messageCommandExecuted, nameof(MessageCommandExecuted), messageCommand).ConfigureAwait(false); + break; + case SocketAutocompleteInteraction autocomplete: + await TimedInvokeAsync(_autocompleteExecuted, nameof(AutocompleteExecuted), autocomplete).ConfigureAwait(false); + break; + } + } + else + { + await UnknownChannelAsync(type, data.ChannelId.Value).ConfigureAwait(false); + return; + } + } + break; + case "APPLICATION_COMMAND_CREATE": + { + await _gatewayLogger.DebugAsync("Received Dispatch (APPLICATION_COMMAND_CREATE)").ConfigureAwait(false); + + var data = (payload as JToken).ToObject(_serializer); + + if (data.GuildId.IsSpecified) + { + var guild = State.GetGuild(data.GuildId.Value); + if (guild == null) + { + await UnknownGuildAsync(type, data.GuildId.Value).ConfigureAwait(false); + return; + } + } + + var applicationCommand = SocketApplicationCommand.Create(this, data); + + State.AddCommand(applicationCommand); + + await TimedInvokeAsync(_applicationCommandCreated, nameof(ApplicationCommandCreated), applicationCommand).ConfigureAwait(false); + } + break; + case "APPLICATION_COMMAND_UPDATE": + { + await _gatewayLogger.DebugAsync("Received Dispatch (APPLICATION_COMMAND_UPDATE)").ConfigureAwait(false); + + var data = (payload as JToken).ToObject(_serializer); + + if (data.GuildId.IsSpecified) + { + var guild = State.GetGuild(data.GuildId.Value); + if (guild == null) + { + await UnknownGuildAsync(type, data.GuildId.Value).ConfigureAwait(false); + return; + } + } + + var applicationCommand = SocketApplicationCommand.Create(this, data); + + State.AddCommand(applicationCommand); + + await TimedInvokeAsync(_applicationCommandUpdated, nameof(ApplicationCommandUpdated), applicationCommand).ConfigureAwait(false); + } + break; + case "APPLICATION_COMMAND_DELETE": + { + await _gatewayLogger.DebugAsync("Received Dispatch (APPLICATION_COMMAND_DELETE)").ConfigureAwait(false); + + var data = (payload as JToken).ToObject(_serializer); + + if (data.GuildId.IsSpecified) + { + var guild = State.GetGuild(data.GuildId.Value); + if (guild == null) + { + await UnknownGuildAsync(type, data.GuildId.Value).ConfigureAwait(false); + return; + } + } + + var applicationCommand = SocketApplicationCommand.Create(this, data); + + State.RemoveCommand(applicationCommand.Id); + + await TimedInvokeAsync(_applicationCommandDeleted, nameof(ApplicationCommandDeleted), applicationCommand).ConfigureAwait(false); + } + break; + #endregion + + #region Threads + case "THREAD_CREATE": + { + await _gatewayLogger.DebugAsync("Received Dispatch (THREAD_CREATE)").ConfigureAwait(false); + + var data = (payload as JToken).ToObject(_serializer); + + var guild = State.GetGuild(data.GuildId.Value); + + if (guild == null) + { + await UnknownGuildAsync(type, data.GuildId.Value); + return; + } + + SocketThreadChannel threadChannel = null; - //Ignored (User only) + if ((threadChannel = guild.ThreadChannels.FirstOrDefault(x => x.Id == data.Id)) != null) + { + threadChannel.Update(State, data); + + if(data.ThreadMember.IsSpecified) + threadChannel.AddOrUpdateThreadMember(data.ThreadMember.Value, guild.CurrentUser); + } + else + { + threadChannel = (SocketThreadChannel)guild.AddChannel(State, data); + if (data.ThreadMember.IsSpecified) + threadChannel.AddOrUpdateThreadMember(data.ThreadMember.Value, guild.CurrentUser); + } + + await TimedInvokeAsync(_threadCreated, nameof(ThreadCreated), threadChannel).ConfigureAwait(false); + } + + break; + case "THREAD_UPDATE": + { + await _gatewayLogger.DebugAsync("Received Dispatch (THREAD_UPDATE)").ConfigureAwait(false); + + var data = (payload as JToken).ToObject(_serializer); + var guild = State.GetGuild(data.GuildId.Value); + if (guild == null) + { + await UnknownGuildAsync(type, data.GuildId.Value); + return; + } + + var threadChannel = guild.ThreadChannels.FirstOrDefault(x => x.Id == data.Id); + var before = threadChannel != null + ? new Cacheable(threadChannel.Clone(), data.Id, true, () => Task.FromResult((SocketThreadChannel)null)) + : new Cacheable(null, data.Id, false, () => Task.FromResult((SocketThreadChannel)null)); + + if (threadChannel != null) + { + threadChannel.Update(State, data); + + if (data.ThreadMember.IsSpecified) + threadChannel.AddOrUpdateThreadMember(data.ThreadMember.Value, guild.CurrentUser); + } + else + { + //Thread is updated but was not cached, likely meaning the thread was unarchived. + threadChannel = (SocketThreadChannel)guild.AddChannel(State, data); + if (data.ThreadMember.IsSpecified) + threadChannel.AddOrUpdateThreadMember(data.ThreadMember.Value, guild.CurrentUser); + } + + if (!(guild?.IsSynced ?? true)) + { + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + return; + } + + await TimedInvokeAsync(_threadUpdated, nameof(ThreadUpdated), before, threadChannel).ConfigureAwait(false); + } + break; + case "THREAD_DELETE": + { + await _gatewayLogger.DebugAsync("Received Dispatch (THREAD_DELETE)").ConfigureAwait(false); + + var data = (payload as JToken).ToObject(_serializer); + + var guild = State.GetGuild(data.GuildId.Value); + + if(guild == null) + { + await UnknownGuildAsync(type, data.GuildId.Value).ConfigureAwait(false); + return; + } + + var thread = (SocketThreadChannel)guild.GetChannel(data.Id); + + var cacheable = new Cacheable(thread, data.Id, thread != null, null); + + await TimedInvokeAsync(_threadDeleted, nameof(ThreadDeleted), cacheable).ConfigureAwait(false); + } + break; + case "THREAD_LIST_SYNC": + { + await _gatewayLogger.DebugAsync("Received Dispatch (THREAD_LIST_SYNC)").ConfigureAwait(false); + + var data = (payload as JToken).ToObject(_serializer); + + var guild = State.GetGuild(data.GuildId); + + if(guild == null) + { + await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); + return; + } + + foreach(var thread in data.Threads) + { + var entity = guild.ThreadChannels.FirstOrDefault(x => x.Id == thread.Id); + + if(entity == null) + { + entity = (SocketThreadChannel)guild.AddChannel(State, thread); + } + else + { + entity.Update(State, thread); + } + + foreach(var member in data.Members.Where(x => x.Id.Value == entity.Id)) + { + var guildMember = guild.GetUser(member.Id.Value); + + entity.AddOrUpdateThreadMember(member, guildMember); + } + } + } + break; + case "THREAD_MEMBER_UPDATE": + { + await _gatewayLogger.DebugAsync("Received Dispatch (THREAD_MEMBER_UPDATE)").ConfigureAwait(false); + + var data = (payload as JToken).ToObject(_serializer); + + var thread = (SocketThreadChannel)State.GetChannel(data.Id.Value); + + if (thread == null) + { + await UnknownChannelAsync(type, data.Id.Value); + return; + } + + thread.AddOrUpdateThreadMember(data, thread.Guild.CurrentUser); + } + + break; + case "THREAD_MEMBERS_UPDATE": + { + await _gatewayLogger.DebugAsync("Received Dispatch (THREAD_MEMBERS_UPDATE)").ConfigureAwait(false); + + var data = (payload as JToken).ToObject(_serializer); + + var guild = State.GetGuild(data.GuildId); + + if (guild == null) + { + await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); + return; + } + + var thread = (SocketThreadChannel)guild.GetChannel(data.Id); + + if(thread == null) + { + await UnknownChannelAsync(type, data.Id); + return; + } + + IReadOnlyCollection leftUsers = null; + IReadOnlyCollection joinUsers = null; + + + if (data.RemovedMemberIds.IsSpecified) + { + leftUsers = thread.RemoveUsers(data.RemovedMemberIds.Value); + } + + if (data.AddedMembers.IsSpecified) + { + List newThreadMembers = new List(); + foreach(var threadMember in data.AddedMembers.Value) + { + SocketGuildUser guildMember; + + if (threadMember.Member.IsSpecified) + { + guildMember = guild.AddOrUpdateUser(threadMember.Member.Value); + } + else + { + guildMember = guild.GetUser(threadMember.UserId.Value); + } + + newThreadMembers.Add(thread.AddOrUpdateThreadMember(threadMember, guildMember)); + } + + if (newThreadMembers.Any()) + joinUsers = newThreadMembers.ToImmutableArray(); + } + + if (leftUsers != null) + { + foreach(var threadUser in leftUsers) + { + await TimedInvokeAsync(_threadMemberLeft, nameof(ThreadMemberLeft), threadUser).ConfigureAwait(false); + } + } + + if(joinUsers != null) + { + foreach(var threadUser in joinUsers) + { + await TimedInvokeAsync(_threadMemberJoined, nameof(ThreadMemberJoined), threadUser).ConfigureAwait(false); + } + } + } + + break; + #endregion + + #region Stage Channels + case "STAGE_INSTANCE_CREATE" or "STAGE_INSTANCE_UPDATE" or "STAGE_INSTANCE_DELETE": + { + await _gatewayLogger.DebugAsync($"Received Dispatch ({type})").ConfigureAwait(false); + + var data = (payload as JToken).ToObject(_serializer); + + var guild = State.GetGuild(data.GuildId); + + if(guild == null) + { + await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); + return; + } + + var stageChannel = guild.GetStageChannel(data.ChannelId); + + if(stageChannel == null) + { + await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); + return; + } + + SocketStageChannel before = type == "STAGE_INSTANCE_UPDATE" ? stageChannel.Clone() : null; + + stageChannel.Update(data, type == "STAGE_INSTANCE_CREATE"); + + switch (type) + { + case "STAGE_INSTANCE_CREATE": + await TimedInvokeAsync(_stageStarted, nameof(StageStarted), stageChannel).ConfigureAwait(false); + return; + case "STAGE_INSTANCE_DELETE": + await TimedInvokeAsync(_stageEnded, nameof(StageEnded), stageChannel).ConfigureAwait(false); + return; + case "STAGE_INSTANCE_UPDATE": + await TimedInvokeAsync(_stageUpdated, nameof(StageUpdated), before, stageChannel).ConfigureAwait(false); + return; + } + } + break; + #endregion + + #region Guild Scheduled Events + case "GUILD_SCHEDULED_EVENT_CREATE": + { + await _gatewayLogger.DebugAsync($"Received Dispatch ({type})").ConfigureAwait(false); + + var data = (payload as JToken).ToObject(_serializer); + + var guild = State.GetGuild(data.GuildId); + + if (guild == null) + { + await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); + return; + } + + var newEvent = guild.AddOrUpdateEvent(data); + + await TimedInvokeAsync(_guildScheduledEventCancelled, nameof(GuildScheduledEventCreated), newEvent).ConfigureAwait(false); + } + break; + case "GUILD_SCHEDULED_EVENT_UPDATE": + { + await _gatewayLogger.DebugAsync($"Received Dispatch ({type})").ConfigureAwait(false); + + var data = (payload as JToken).ToObject(_serializer); + + var guild = State.GetGuild(data.GuildId); + + if (guild == null) + { + await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); + return; + } + + var before = guild.GetEvent(data.Id); + + var beforeCacheable = new Cacheable(before, data.Id, before != null, () => Task.FromResult((SocketGuildEvent)null)); + + var after = guild.AddOrUpdateEvent(data); + + if((before != null ? before.Status != GuildScheduledEventStatus.Completed : true) && data.Status == GuildScheduledEventStatus.Completed) + { + await TimedInvokeAsync(_guildScheduledEventCompleted, nameof(GuildScheduledEventCompleted), after).ConfigureAwait(false); + } + else if((before != null ? before.Status != GuildScheduledEventStatus.Active : false) && data.Status == GuildScheduledEventStatus.Active) + { + await TimedInvokeAsync(_guildScheduledEventStarted, nameof(GuildScheduledEventStarted), after).ConfigureAwait(false); + } + else await TimedInvokeAsync(_guildScheduledEventUpdated, nameof(GuildScheduledEventUpdated), beforeCacheable, after).ConfigureAwait(false); + } + break; + case "GUILD_SCHEDULED_EVENT_DELETE": + { + await _gatewayLogger.DebugAsync($"Received Dispatch ({type})").ConfigureAwait(false); + + var data = (payload as JToken).ToObject(_serializer); + + var guild = State.GetGuild(data.GuildId); + + if (guild == null) + { + await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); + return; + } + + var guildEvent = guild.RemoveEvent(data.Id) ?? SocketGuildEvent.Create(this, guild, data); + + await TimedInvokeAsync(_guildScheduledEventCancelled, nameof(GuildScheduledEventCancelled), guildEvent).ConfigureAwait(false); + } + break; + case "GUILD_SCHEDULED_EVENT_USER_ADD" or "GUILD_SCHEDULED_EVENT_USER_REMOVE": + { + await _gatewayLogger.DebugAsync($"Received Dispatch ({type})").ConfigureAwait(false); + + var data = (payload as JToken).ToObject(_serializer); + + var guild = State.GetGuild(data.GuildId); + + if(guild == null) + { + await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); + return; + } + + var guildEvent = guild.GetEvent(data.EventId); + + if (guildEvent == null) + { + await UnknownGuildEventAsync(type, data.EventId, data.GuildId).ConfigureAwait(false); + return; + } + + var user = (SocketUser)guild.GetUser(data.UserId) ?? State.GetUser(data.UserId); + + var cacheableUser = new Cacheable(user, data.UserId, user != null, () => Rest.GetUserAsync(data.UserId)); + + switch (type) + { + case "GUILD_SCHEDULED_EVENT_USER_ADD": + await TimedInvokeAsync(_guildScheduledEventUserAdd, nameof(GuildScheduledEventUserAdd), cacheableUser, guildEvent).ConfigureAwait(false); + break; + case "GUILD_SCHEDULED_EVENT_USER_REMOVE": + await TimedInvokeAsync(_guildScheduledEventUserRemove, nameof(GuildScheduledEventUserRemove), cacheableUser, guildEvent).ConfigureAwait(false); + break; + } + } + break; + + #endregion + + #region Ignored (User only) case "CHANNEL_PINS_ACK": await _gatewayLogger.DebugAsync("Ignored Dispatch (CHANNEL_PINS_ACK)").ConfigureAwait(false); break; @@ -1882,11 +2694,13 @@ namespace Discord.WebSocket case "WEBHOOKS_UPDATE": await _gatewayLogger.DebugAsync("Ignored Dispatch (WEBHOOKS_UPDATE)").ConfigureAwait(false); break; + #endregion - //Others + #region Others default: await _gatewayLogger.WarningAsync($"Unknown Dispatch ({type})").ConfigureAwait(false); break; + #endregion } break; default: @@ -1899,6 +2713,7 @@ namespace Discord.WebSocket await _gatewayLogger.ErrorAsync($"Error handling {opCode}{(type != null ? $" ({type})" : "")}", ex).ConfigureAwait(false); } } + #endregion private async Task RunHeartbeatAsync(int intervalMillis, CancellationToken cancelToken) { @@ -2018,6 +2833,18 @@ namespace Discord.WebSocket channel.Recipient.GlobalUser.RemoveRef(this); } + internal void EnsureGatewayIntent(GatewayIntents intents) + { + if (!_gatewayIntents.HasFlag(intents)) + { + var vals = Enum.GetValues(typeof(GatewayIntents)).Cast(); + + var missingValues = vals.Where(x => intents.HasFlag(x) && !_gatewayIntents.HasFlag(x)); + + throw new InvalidOperationException($"Missing required gateway intent{(missingValues.Count() > 1 ? "s" : "")} {string.Join(", ", missingValues.Select(x => x.ToString()))} in order to execute this operation."); + } + } + private async Task GuildAvailableAsync(SocketGuild guild) { if (!guild.IsConnected) @@ -2158,6 +2985,12 @@ namespace Discord.WebSocket string details = $"{evnt} Guild={guildId}"; await _gatewayLogger.WarningAsync($"Unknown Guild ({details}).").ConfigureAwait(false); } + + private async Task UnknownGuildEventAsync(string evnt, ulong eventId, ulong guildId) + { + string details = $"{evnt} Event={eventId} Guild={guildId}"; + await _gatewayLogger.WarningAsync($"Unknown Guild Event ({details}).").ConfigureAwait(false); + } private async Task UnsyncedGuildAsync(string evnt, ulong guildId) { string details = $"{evnt} Guild={guildId}"; @@ -2166,7 +2999,7 @@ namespace Discord.WebSocket internal int GetAudioId() => _nextAudioId++; - //IDiscordClient + #region IDiscordClient /// async Task IDiscordClient.GetApplicationInfoAsync(RequestOptions options) => await GetApplicationInfoAsync().ConfigureAwait(false); @@ -2217,10 +3050,18 @@ namespace Discord.WebSocket => await GetVoiceRegionAsync(id, options).ConfigureAwait(false); /// + async Task IDiscordClient.GetGlobalApplicationCommandAsync(ulong id, RequestOptions options) + => await GetGlobalApplicationCommandAsync(id, options); + /// + async Task> IDiscordClient.GetGlobalApplicationCommandsAsync(RequestOptions options) + => await GetGlobalApplicationCommandsAsync(options); + + /// async Task IDiscordClient.StartAsync() => await StartAsync().ConfigureAwait(false); /// async Task IDiscordClient.StopAsync() => await StopAsync().ConfigureAwait(false); + #endregion } } diff --git a/src/Discord.Net.WebSocket/DiscordSocketConfig.cs b/src/Discord.Net.WebSocket/DiscordSocketConfig.cs index 22a201c67..8615eac71 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketConfig.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketConfig.cs @@ -49,9 +49,32 @@ namespace Discord.WebSocket /// /// Gets or sets the total number of shards for this application. /// + /// + /// If this is left in a sharded client the bot will get the recommended shard + /// count from discord and use that. + /// public int? TotalShards { get; set; } = null; /// + /// Gets or sets whether or not the client should download the default stickers on startup. + /// + /// + /// When this is set to default stickers arn't present and cannot be resolved by the client. + /// This will make all default stickers have the type of . + /// + public bool AlwaysDownloadDefaultStickers { get; set; } = false; + + /// + /// Gets or sets whether or not the client should automatically resolve the stickers sent on a message. + /// + /// + /// Note if a sticker isn't cached the client will preform a rest request to resolve it. This + /// may be very rest heavy depending on your bots size, it isn't recommended to use this with large scale bots as you + /// can get ratelimited easily. + /// + public bool AlwaysResolveStickers { get; set; } = false; + + /// /// Gets or sets the number of messages per channel that should be kept in cache. Setting this to zero /// disables the message cache entirely. /// @@ -79,7 +102,7 @@ namespace Discord.WebSocket /// /// /// By default, the Discord gateway will only send offline members if a guild has less than a certain number - /// of members (determined by in this library). This behaviour is why + /// of members (determined by in this library). This behavior is why /// sometimes a user may be missing from the WebSocket cache for collections such as /// . /// @@ -137,13 +160,13 @@ namespace Discord.WebSocket { get { - return this.maxWaitForGuildAvailable; + return maxWaitForGuildAvailable; } set { - Preconditions.AtLeast(value, 0, nameof(this.MaxWaitBetweenGuildAvailablesBeforeReady)); - this.maxWaitForGuildAvailable = value; + Preconditions.AtLeast(value, 0, nameof(MaxWaitBetweenGuildAvailablesBeforeReady)); + maxWaitForGuildAvailable = value; } } diff --git a/src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs b/src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs index f78145dbe..62d95402a 100644 --- a/src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs +++ b/src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS1591 using Discord.API; using Discord.API.Voice; using Discord.Net.Converters; @@ -18,6 +17,7 @@ namespace Discord.Audio { internal class DiscordVoiceAPIClient : IDisposable { + #region DiscordVoiceAPIClient public const int MaxBitrate = 128 * 1024; public const string Mode = "xsalsa20_poly1305"; @@ -126,8 +126,9 @@ namespace Discord.Audio await _udp.SendAsync(data, offset, bytes).ConfigureAwait(false); await _sentDataEvent.InvokeAsync(bytes).ConfigureAwait(false); } + #endregion - //WebSocket + #region WebSocket public async Task SendHeartbeatAsync(RequestOptions options = null) { await SendAsync(VoiceOpCode.Heartbeat, DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), options: options).ConfigureAwait(false); @@ -208,10 +209,12 @@ namespace Discord.Audio } private async Task DisconnectInternalAsync() { - if (ConnectionState == ConnectionState.Disconnected) return; + if (ConnectionState == ConnectionState.Disconnected) + return; ConnectionState = ConnectionState.Disconnecting; - try { _connectCancelToken?.Cancel(false); } + try + { _connectCancelToken?.Cancel(false); } catch { } //Wait for tasks to complete @@ -220,8 +223,9 @@ namespace Discord.Audio ConnectionState = ConnectionState.Disconnected; } + #endregion - //Udp + #region Udp public async Task SendDiscoveryAsync(uint ssrc) { var packet = new byte[70]; @@ -252,8 +256,9 @@ namespace Discord.Audio { _udp.SetDestination(ip, port); } + #endregion - //Helpers + #region Helpers private static double ToMilliseconds(Stopwatch stopwatch) => Math.Round((double)stopwatch.ElapsedTicks / (double)Stopwatch.Frequency * 1000.0, 2); private string SerializeJson(object value) { @@ -269,5 +274,6 @@ namespace Discord.Audio using (JsonReader reader = new JsonTextReader(text)) return _serializer.Deserialize(reader); } + #endregion } } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs index b4625f799..1103f8feb 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs @@ -34,16 +34,19 @@ namespace Discord.WebSocket /// If null, all mentioned roles and users will be notified. /// /// The message references to be included. Used to reply to specific messages. + /// The message components to be included with this message. Used for interactions. + /// A collection of stickers to send with the message. + /// A array of s to send with this response. Max 10. /// /// A task that represents an asynchronous send operation for delivering the message. The task result /// contains the sent message. /// - new Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null); + new Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null); /// /// Sends a file to this message channel with an optional caption. /// /// - /// This method follows the same behavior as described in . + /// This method follows the same behavior as described in . /// Please visit its documentation for more details on this method. /// /// The file path of the file. @@ -57,16 +60,19 @@ namespace Discord.WebSocket /// If null, all mentioned roles and users will be notified. /// /// The message references to be included. Used to reply to specific messages. + /// The message components to be included with this message. Used for interactions. + /// A collection of stickers to send with the file. + /// A array of s to send with this response. Max 10. /// /// A task that represents an asynchronous send operation for delivering the message. The task result /// contains the sent message. /// - new Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null); + new Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null); /// /// Sends a file to this message channel with an optional caption. /// /// - /// This method follows the same behavior as described in . + /// This method follows the same behavior as described in . /// Please visit its documentation for more details on this method. /// /// The of the file to be sent. @@ -81,11 +87,14 @@ namespace Discord.WebSocket /// If null, all mentioned roles and users will be notified. /// /// The message references to be included. Used to reply to specific messages. + /// The message components to be included with this message. Used for interactions. + /// A collection of stickers to send with the file. + /// A array of s to send with this response. Max 10. /// /// A task that represents an asynchronous send operation for delivering the message. The task result /// contains the sent message. /// - new Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null); + new Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null); /// /// Gets a cached message from this channel. diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketCategoryChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketCategoryChannel.cs index b90c1976a..9c7dd4fbd 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketCategoryChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketCategoryChannel.cs @@ -14,6 +14,7 @@ namespace Discord.WebSocket [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class SocketCategoryChannel : SocketGuildChannel, ICategoryChannel { + #region SocketCategoryChannel /// public override IReadOnlyCollection Users => Guild.Users.Where(x => Permissions.GetValue( @@ -41,8 +42,9 @@ namespace Discord.WebSocket entity.Update(state, model); return entity; } + #endregion - //Users + #region Users /// public override SocketGuildUser GetUser(ulong id) { @@ -59,21 +61,24 @@ namespace Discord.WebSocket private string DebuggerDisplay => $"{Name} ({Id}, Category)"; internal new SocketCategoryChannel Clone() => MemberwiseClone() as SocketCategoryChannel; + #endregion - // IGuildChannel + #region IGuildChannel /// IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => ImmutableArray.Create>(Users).ToAsyncEnumerable(); /// Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetUser(id)); + #endregion - //IChannel + #region IChannel /// IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => ImmutableArray.Create>(Users).ToAsyncEnumerable(); /// Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetUser(id)); + #endregion } } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs index 13c0c9b4f..758ee9271 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs @@ -13,6 +13,7 @@ namespace Discord.WebSocket [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public abstract class SocketChannel : SocketEntity, IChannel { + #region SocketChannel /// /// Gets when the channel is created. /// @@ -30,19 +31,17 @@ namespace Discord.WebSocket /// Unexpected channel type is created. internal static ISocketPrivateChannel CreatePrivate(DiscordSocketClient discord, ClientState state, Model model) { - switch (model.Type) + return model.Type switch { - case ChannelType.DM: - return SocketDMChannel.Create(discord, state, model); - case ChannelType.Group: - return SocketGroupChannel.Create(discord, state, model); - default: - throw new InvalidOperationException($"Unexpected channel type: {model.Type}"); - } + ChannelType.DM => SocketDMChannel.Create(discord, state, model), + ChannelType.Group => SocketGroupChannel.Create(discord, state, model), + _ => throw new InvalidOperationException($"Unexpected channel type: {model.Type}"), + }; } internal abstract void Update(ClientState state, Model model); + #endregion - //User + #region User /// /// Gets a generic user from this channel. /// @@ -56,8 +55,9 @@ namespace Discord.WebSocket private string DebuggerDisplay => $"Unknown ({Id}, Channel)"; internal SocketChannel Clone() => MemberwiseClone() as SocketChannel; + #endregion - //IChannel + #region IChannel /// string IChannel.Name => null; @@ -67,5 +67,6 @@ namespace Discord.WebSocket /// IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => AsyncEnumerable.Empty>(); //Overridden + #endregion } } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs index 5cfbcc1a8..ccbf9b2b6 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs @@ -70,6 +70,7 @@ namespace Discord.WebSocket { case SocketDMChannel dmChannel: dmChannel.AddMessage(msg); break; case SocketGroupChannel groupChannel: groupChannel.AddMessage(msg); break; + case SocketThreadChannel threadChannel: threadChannel.AddMessage(msg); break; case SocketTextChannel textChannel: textChannel.AddMessage(msg); break; default: throw new NotSupportedException($"Unexpected {nameof(ISocketMessageChannel)} type."); } @@ -78,13 +79,13 @@ namespace Discord.WebSocket public static SocketMessage RemoveMessage(ISocketMessageChannel channel, DiscordSocketClient discord, ulong id) { - switch (channel) + return channel switch { - case SocketDMChannel dmChannel: return dmChannel.RemoveMessage(id); - case SocketGroupChannel groupChannel: return groupChannel.RemoveMessage(id); - case SocketTextChannel textChannel: return textChannel.RemoveMessage(id); - default: throw new NotSupportedException($"Unexpected {nameof(ISocketMessageChannel)} type."); - } + SocketDMChannel dmChannel => dmChannel.RemoveMessage(id), + SocketGroupChannel groupChannel => groupChannel.RemoveMessage(id), + SocketTextChannel textChannel => textChannel.RemoveMessage(id), + _ => throw new NotSupportedException($"Unexpected {nameof(ISocketMessageChannel)} type."), + }; } } } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs index 459707dc7..ea00c9e03 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs @@ -16,6 +16,7 @@ namespace Discord.WebSocket [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class SocketDMChannel : SocketChannel, IDMChannel, ISocketPrivateChannel, ISocketMessageChannel { + #region SocketDMChannel /// /// Gets the recipient of the channel. /// @@ -58,8 +59,9 @@ namespace Discord.WebSocket /// public Task CloseAsync(RequestOptions options = null) => ChannelHelper.DeleteAsync(this, Discord, options); + #endregion - //Messages + #region Messages /// public SocketMessage GetCachedMessage(ulong id) => null; @@ -137,16 +139,25 @@ namespace Discord.WebSocket /// /// Message content is too long, length must be less or equal to . - public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null) - => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, allowedMentions, messageReference, options); + public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null) + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, allowedMentions, messageReference, component, stickers, options, embeds); /// - public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null) - => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, allowedMentions, messageReference, options, isSpoiler); + public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null) + => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, allowedMentions, messageReference, component, stickers, options, isSpoiler, embeds); /// /// Message content is too long, length must be less or equal to . - public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null) - => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, allowedMentions, messageReference, options, isSpoiler); + public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null) + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, allowedMentions, messageReference, component, stickers, options, isSpoiler, embeds); + /// + /// Message content is too long, length must be less or equal to . + public Task SendFileAsync(FileAttachment attachment, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null) + => ChannelHelper.SendFileAsync(this, Discord, attachment, text, isTTS, embed, allowedMentions, messageReference, component, stickers, options, embeds); + /// + /// Message content is too long, length must be less or equal to . + public Task SendFilesAsync(IEnumerable attachments, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null) + => ChannelHelper.SendFilesAsync(this, Discord, attachments, text, isTTS, embed, allowedMentions, messageReference, component, stickers, options, embeds); + /// public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null) => ChannelHelper.DeleteMessageAsync(this, messageId, Discord, options); @@ -172,8 +183,9 @@ namespace Discord.WebSocket { return null; } + #endregion - //Users + #region Users /// /// Gets a user in this channel from the provided . /// @@ -197,26 +209,31 @@ namespace Discord.WebSocket public override string ToString() => $"@{Recipient}"; private string DebuggerDisplay => $"@{Recipient} ({Id}, DM)"; internal new SocketDMChannel Clone() => MemberwiseClone() as SocketDMChannel; + #endregion - //SocketChannel + #region SocketChannel /// internal override IReadOnlyCollection GetUsersInternal() => Users; /// internal override SocketUser GetUserInternal(ulong id) => GetUser(id); + #endregion - //IDMChannel + #region IDMChannel /// IUser IDMChannel.Recipient => Recipient; + #endregion - //ISocketPrivateChannel + #region ISocketPrivateChannel /// IReadOnlyCollection ISocketPrivateChannel.Recipients => ImmutableArray.Create(Recipient); + #endregion - //IPrivateChannel + #region IPrivateChannel /// IReadOnlyCollection IPrivateChannel.Recipients => ImmutableArray.Create(Recipient); + #endregion - //IMessageChannel + #region IMessageChannel /// async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options) { @@ -238,16 +255,23 @@ namespace Discord.WebSocket async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) => await GetPinnedMessagesAsync(options).ConfigureAwait(false); /// - async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageReference messageReference) - => await SendFileAsync(filePath, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference).ConfigureAwait(false); + async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent component, ISticker[] stickers, Embed[] embeds) + => await SendFileAsync(filePath, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference, component, stickers, embeds).ConfigureAwait(false); + /// + async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent component, ISticker[] stickers, Embed[] embeds) + => await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference, component, stickers, embeds).ConfigureAwait(false); + /// + async Task IMessageChannel.SendFileAsync(FileAttachment attachment, string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent component, ISticker[] stickers, Embed[] embeds) + => await SendFileAsync(attachment, text, isTTS, embed, options, allowedMentions, messageReference, component, stickers, embeds).ConfigureAwait(false); /// - async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageReference messageReference) - => await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference).ConfigureAwait(false); + async Task IMessageChannel.SendFilesAsync(IEnumerable attachments, string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent component, ISticker[] stickers, Embed[] embeds) + => await SendFilesAsync(attachments, text, isTTS, embed, options, allowedMentions, messageReference, component, stickers, embeds).ConfigureAwait(false); /// - async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference) - => await SendMessageAsync(text, isTTS, embed, options, allowedMentions, messageReference).ConfigureAwait(false); + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent component, ISticker[] stickers, Embed[] embeds) + => await SendMessageAsync(text, isTTS, embed, options, allowedMentions, messageReference, component, stickers, embeds).ConfigureAwait(false); + #endregion - //IChannel + #region IChannel /// string IChannel.Name => $"@{Recipient}"; @@ -257,5 +281,6 @@ namespace Discord.WebSocket /// IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => ImmutableArray.Create>(Users).ToAsyncEnumerable(); + #endregion } } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs index ab8c76aeb..1bbfa6e97 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs @@ -20,6 +20,7 @@ namespace Discord.WebSocket [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class SocketGroupChannel : SocketChannel, IGroupChannel, ISocketPrivateChannel, ISocketMessageChannel, ISocketAudioChannel { + #region SocketGroupChannel private readonly MessageCache _messages; private readonly ConcurrentDictionary _voiceStates; @@ -31,7 +32,15 @@ namespace Discord.WebSocket /// public IReadOnlyCollection CachedMessages => _messages?.Messages ?? ImmutableArray.Create(); + + /// + /// Returns a collection representing all of the users in the group. + /// public new IReadOnlyCollection Users => _users.ToReadOnlyCollection(); + + /// + /// Returns a collection representing all users in the group, not including the client. + /// public IReadOnlyCollection Recipients => _users.Select(x => x.Value).Where(x => x.Id != Discord.CurrentUser.Id).ToReadOnlyCollection(() => _users.Count - 1); @@ -76,8 +85,9 @@ namespace Discord.WebSocket { throw new NotSupportedException("Voice is not yet supported for group channels."); } +#endregion - //Messages + #region Messages /// public SocketMessage GetCachedMessage(ulong id) => _messages?.Get(id); @@ -163,15 +173,24 @@ namespace Discord.WebSocket /// /// Message content is too long, length must be less or equal to . - public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null) - => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, allowedMentions, messageReference, options); + public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null) + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, allowedMentions, messageReference, component, stickers, options, embeds); /// - public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null) - => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, allowedMentions, messageReference, options, isSpoiler); + public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null) + => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, allowedMentions, messageReference, component, stickers, options, isSpoiler, embeds); + /// + /// Message content is too long, length must be less or equal to . + public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null) + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, allowedMentions, messageReference, component, stickers, options, isSpoiler, embeds); + /// + /// Message content is too long, length must be less or equal to . + public Task SendFileAsync(FileAttachment attachment, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null) + => ChannelHelper.SendFileAsync(this, Discord, attachment, text, isTTS, embed, allowedMentions, messageReference, component, stickers, options, embeds); /// - public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null) - => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, allowedMentions, messageReference, options, isSpoiler); + /// Message content is too long, length must be less or equal to . + public Task SendFilesAsync(IEnumerable attachments, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null) + => ChannelHelper.SendFilesAsync(this, Discord, attachments, text, isTTS, embed, allowedMentions, messageReference, component, stickers, options, embeds); /// public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null) @@ -195,8 +214,9 @@ namespace Discord.WebSocket => _messages?.Add(msg); internal SocketMessage RemoveMessage(ulong id) => _messages?.Remove(id); + #endregion - //Users + #region Users /// /// Gets a user from this group. /// @@ -231,8 +251,9 @@ namespace Discord.WebSocket } return null; } + #endregion - //Voice States + #region Voice States internal SocketVoiceState AddOrUpdateVoiceState(ClientState state, VoiceStateModel model) { var voiceChannel = state.GetChannel(model.ChannelId.Value) as SocketVoiceChannel; @@ -259,22 +280,26 @@ namespace Discord.WebSocket public override string ToString() => Name; private string DebuggerDisplay => $"{Name} ({Id}, Group)"; internal new SocketGroupChannel Clone() => MemberwiseClone() as SocketGroupChannel; + #endregion - //SocketChannel + #region SocketChannel /// internal override IReadOnlyCollection GetUsersInternal() => Users; /// internal override SocketUser GetUserInternal(ulong id) => GetUser(id); + #endregion - //ISocketPrivateChannel + #region ISocketPrivateChannel /// IReadOnlyCollection ISocketPrivateChannel.Recipients => Recipients; + #endregion - //IPrivateChannel + #region IPrivateChannel /// IReadOnlyCollection IPrivateChannel.Recipients => Recipients; + #endregion - //IMessageChannel + #region IMessageChannel /// async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options) { @@ -297,27 +322,37 @@ namespace Discord.WebSocket => await GetPinnedMessagesAsync(options).ConfigureAwait(false); /// - async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageReference messageReference) - => await SendFileAsync(filePath, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference).ConfigureAwait(false); + async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent component, ISticker[] stickers, Embed[] embeds) + => await SendFileAsync(filePath, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference, component, stickers, embeds).ConfigureAwait(false); /// - async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageReference messageReference) - => await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference).ConfigureAwait(false); + async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent component, ISticker[] stickers, Embed[] embeds) + => await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference, component, stickers, embeds).ConfigureAwait(false); + /// + async Task IMessageChannel.SendFileAsync(FileAttachment attachment, string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent component, ISticker[] stickers, Embed[] embeds) + => await SendFileAsync(attachment, text, isTTS, embed, options, allowedMentions, messageReference, component, stickers, embeds).ConfigureAwait(false); + /// + async Task IMessageChannel.SendFilesAsync(IEnumerable attachments, string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent component, ISticker[] stickers, Embed[] embeds) + => await SendFilesAsync(attachments, text, isTTS, embed, options, allowedMentions, messageReference, component, stickers, embeds).ConfigureAwait(false); + /// - async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference) - => await SendMessageAsync(text, isTTS, embed, options, allowedMentions, messageReference).ConfigureAwait(false); + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent component, ISticker[] stickers, Embed[] embeds) + => await SendMessageAsync(text, isTTS, embed, options, allowedMentions, messageReference, component, stickers, embeds).ConfigureAwait(false); + #endregion - //IAudioChannel + #region IAudioChannel /// /// Connecting to a group channel is not supported. Task IAudioChannel.ConnectAsync(bool selfDeaf, bool selfMute, bool external) { throw new NotSupportedException(); } Task IAudioChannel.DisconnectAsync() { throw new NotSupportedException(); } + #endregion - //IChannel + #region IChannel /// Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetUser(id)); /// IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => ImmutableArray.Create>(Users).ToAsyncEnumerable(); + #endregion } } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs index 3cc8496d9..d38a8975b 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs @@ -15,6 +15,7 @@ namespace Discord.WebSocket [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class SocketGuildChannel : SocketChannel, IGuildChannel { + #region SocketGuildChannel private ImmutableArray _overwrites; /// @@ -27,7 +28,7 @@ namespace Discord.WebSocket /// public string Name { get; private set; } /// - public int Position { get; private set; } + public int Position { get; private set; } /// public virtual IReadOnlyCollection PermissionOverwrites => _overwrites; @@ -46,27 +47,24 @@ namespace Discord.WebSocket } internal static SocketGuildChannel Create(SocketGuild guild, ClientState state, Model model) { - switch (model.Type) + return model.Type switch { - case ChannelType.News: - return SocketNewsChannel.Create(guild, state, model); - case ChannelType.Text: - return SocketTextChannel.Create(guild, state, model); - case ChannelType.Voice: - return SocketVoiceChannel.Create(guild, state, model); - case ChannelType.Category: - return SocketCategoryChannel.Create(guild, state, model); - default: - return new SocketGuildChannel(guild.Discord, model.Id, guild); - } + ChannelType.News => SocketNewsChannel.Create(guild, state, model), + ChannelType.Text => SocketTextChannel.Create(guild, state, model), + ChannelType.Voice => SocketVoiceChannel.Create(guild, state, model), + ChannelType.Category => SocketCategoryChannel.Create(guild, state, model), + ChannelType.PrivateThread or ChannelType.PublicThread or ChannelType.NewsThread => SocketThreadChannel.Create(guild, state, model), + ChannelType.Stage => SocketStageChannel.Create(guild, state, model), + _ => new SocketGuildChannel(guild.Discord, model.Id, guild), + }; } /// internal override void Update(ClientState state, Model model) { Name = model.Name.Value; - Position = model.Position.Value; - - var overwrites = model.PermissionOverwrites.Value; + Position = model.Position.GetValueOrDefault(0); + + var overwrites = model.PermissionOverwrites.GetValueOrDefault(new API.Overwrite[0]); var newOverwrites = ImmutableArray.CreateBuilder(overwrites.Length); for (int i = 0; i < overwrites.Length; i++) newOverwrites.Add(overwrites[i].ToEntity()); @@ -176,14 +174,16 @@ namespace Discord.WebSocket public override string ToString() => Name; private string DebuggerDisplay => $"{Name} ({Id}, Guild)"; internal new SocketGuildChannel Clone() => MemberwiseClone() as SocketGuildChannel; +#endregion - //SocketChannel + #region SocketChannel /// internal override IReadOnlyCollection GetUsersInternal() => Users; /// internal override SocketUser GetUserInternal(ulong id) => GetUser(id); + #endregion - //IGuildChannel + #region IGuildChannel /// IGuild IGuildChannel.Guild => Guild; /// @@ -214,13 +214,15 @@ namespace Discord.WebSocket /// Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetUser(id)); + #endregion - //IChannel + #region IChannel /// IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => ImmutableArray.Create>(Users).ToAsyncEnumerable(); //Overridden in Text/Voice /// Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetUser(id)); //Overridden in Text/Voice + #endregion } } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketStageChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketStageChannel.cs new file mode 100644 index 000000000..91bca5054 --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketStageChannel.cs @@ -0,0 +1,158 @@ +using Discord.Rest; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; +using Model = Discord.API.Channel; +using StageInstance = Discord.API.StageInstance; + +namespace Discord.WebSocket +{ + /// + /// Represents a stage channel received over the gateway. + /// + public class SocketStageChannel : SocketVoiceChannel, IStageChannel + { + /// + public string Topic { get; private set; } + + /// + public StagePrivacyLevel? PrivacyLevel { get; private set; } + + /// + public bool? IsDiscoverableDisabled { get; private set; } + + /// + public bool IsLive { get; private set; } + + /// + /// Returns if the current user is a speaker within the stage, otherwise . + /// + public bool IsSpeaker + => !Guild.CurrentUser.IsSuppressed; + + /// + /// Gets a collection of users who are speakers within the stage. + /// + public IReadOnlyCollection Speakers + => Users.Where(x => !x.IsSuppressed).ToImmutableArray(); + + internal new SocketStageChannel Clone() => MemberwiseClone() as SocketStageChannel; + + internal SocketStageChannel(DiscordSocketClient discord, ulong id, SocketGuild guild) + : base(discord, id, guild) { } + + internal new static SocketStageChannel Create(SocketGuild guild, ClientState state, Model model) + { + var entity = new SocketStageChannel(guild.Discord, model.Id, guild); + entity.Update(state, model); + return entity; + } + + internal void Update(StageInstance model, bool isLive = false) + { + IsLive = isLive; + if (isLive) + { + Topic = model.Topic; + PrivacyLevel = model.PrivacyLevel; + IsDiscoverableDisabled = model.DiscoverableDisabled; + } + else + { + Topic = null; + PrivacyLevel = null; + IsDiscoverableDisabled = null; + } + } + + /// + public async Task StartStageAsync(string topic, StagePrivacyLevel privacyLevel = StagePrivacyLevel.GuildOnly, RequestOptions options = null) + { + var args = new API.Rest.CreateStageInstanceParams + { + ChannelId = Id, + Topic = topic, + PrivacyLevel = privacyLevel + }; + + var model = await Discord.ApiClient.CreateStageInstanceAsync(args, options).ConfigureAwait(false); + + Update(model, true); + } + + /// + public async Task ModifyInstanceAsync(Action func, RequestOptions options = null) + { + var model = await ChannelHelper.ModifyAsync(this, Discord, func, options); + + Update(model, true); + } + + /// + public async Task StopStageAsync(RequestOptions options = null) + { + await Discord.ApiClient.DeleteStageInstanceAsync(Id, options); + + Update(null); + } + + /// + public Task RequestToSpeakAsync(RequestOptions options = null) + { + var args = new API.Rest.ModifyVoiceStateParams + { + ChannelId = Id, + RequestToSpeakTimestamp = DateTimeOffset.UtcNow + }; + return Discord.ApiClient.ModifyMyVoiceState(Guild.Id, args, options); + } + + /// + public Task BecomeSpeakerAsync(RequestOptions options = null) + { + var args = new API.Rest.ModifyVoiceStateParams + { + ChannelId = Id, + Suppressed = false + }; + return Discord.ApiClient.ModifyMyVoiceState(Guild.Id, args, options); + } + + /// + public Task StopSpeakingAsync(RequestOptions options = null) + { + var args = new API.Rest.ModifyVoiceStateParams + { + ChannelId = Id, + Suppressed = true + }; + return Discord.ApiClient.ModifyMyVoiceState(Guild.Id, args, options); + } + + /// + public Task MoveToSpeakerAsync(IGuildUser user, RequestOptions options = null) + { + var args = new API.Rest.ModifyVoiceStateParams + { + ChannelId = Id, + Suppressed = false + }; + + return Discord.ApiClient.ModifyUserVoiceState(Guild.Id, user.Id, args); + } + + /// + public Task RemoveFromSpeakerAsync(IGuildUser user, RequestOptions options = null) + { + var args = new API.Rest.ModifyVoiceStateParams + { + ChannelId = Id, + Suppressed = true + }; + + return Discord.ApiClient.ModifyUserVoiceState(Guild.Id, user.Id, args); + } + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs index 71a20c198..8722b569d 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs @@ -16,6 +16,7 @@ namespace Discord.WebSocket [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class SocketTextChannel : SocketGuildChannel, ITextChannel, ISocketMessageChannel { + #region SocketTextChannel private readonly MessageCache _messages; /// @@ -50,6 +51,12 @@ namespace Discord.WebSocket Permissions.ResolveChannel(Guild, x, this, Permissions.ResolveGuild(Guild, x)), ChannelPermission.ViewChannel)).ToImmutableArray(); + /// + /// Gets a collection of threads within this text channel. + /// + public IReadOnlyCollection Threads + => Guild.ThreadChannels.Where(x => x.ParentChannel.Id == Id).ToImmutableArray(); + internal SocketTextChannel(DiscordSocketClient discord, ulong id, SocketGuild guild) : base(discord, id, guild) { @@ -66,16 +73,59 @@ namespace Discord.WebSocket { base.Update(state, model); CategoryId = model.CategoryId; - Topic = model.Topic.Value; + Topic = model.Topic.GetValueOrDefault(); SlowModeInterval = model.SlowMode.GetValueOrDefault(); // some guilds haven't been patched to include this yet? _nsfw = model.Nsfw.GetValueOrDefault(); } /// - public Task ModifyAsync(Action func, RequestOptions options = null) + public virtual Task ModifyAsync(Action func, RequestOptions options = null) => ChannelHelper.ModifyAsync(this, Discord, func, options); - //Messages + /// + /// Creates a thread within this . + /// + /// + /// When is the thread type will be based off of the + /// channel its created in. When called on a , it creates a . + /// When called on a , it creates a . The id of the created + /// thread will be the same as the id of the message, and as such a message can only have a + /// single thread created from it. + /// + /// The name of the thread. + /// + /// The type of the thread. + /// + /// Note: This parameter is not used if the parameter is not specified. + /// + /// + /// + /// The duration on which this thread archives after. + /// + /// Note: Options and + /// are only available for guilds that are boosted. You can check in the to see if the + /// guild has the THREE_DAY_THREAD_ARCHIVE and SEVEN_DAY_THREAD_ARCHIVE. + /// + /// + /// The message which to start the thread from. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous create operation. The task result contains a + /// + public async Task CreateThreadAsync(string name, ThreadType type = ThreadType.PublicThread, + ThreadArchiveDuration autoArchiveDuration = ThreadArchiveDuration.OneDay, IMessage message = null, bool? invitable = null, int? slowmode = null, RequestOptions options = null) + { + var model = await ThreadHelper.CreateThreadAsync(Discord, this, name, type, autoArchiveDuration, message, invitable, slowmode, options); + + var thread = (SocketThreadChannel)Guild.AddOrUpdateChannel(Discord.State, model); + + await thread.DownloadUsersAsync(); + + return thread; + } +#endregion + + #region Messages /// public SocketMessage GetCachedMessage(ulong id) => _messages?.Get(id); @@ -161,17 +211,27 @@ namespace Discord.WebSocket /// /// Message content is too long, length must be less or equal to . - public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null) - => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, allowedMentions, messageReference, options); + public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null) + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, allowedMentions, messageReference, component, stickers, options, embeds); /// - public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null) - => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, allowedMentions, messageReference, options, isSpoiler); + public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null) + => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, allowedMentions, messageReference, component, stickers, options, isSpoiler, embeds); /// /// Message content is too long, length must be less or equal to . - public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null) - => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, allowedMentions, messageReference, options, isSpoiler); + public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null) + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, allowedMentions, messageReference, component, stickers, options, isSpoiler, embeds); + + /// + /// Message content is too long, length must be less or equal to . + public Task SendFileAsync(FileAttachment attachment, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null) + => ChannelHelper.SendFileAsync(this, Discord, attachment, text, isTTS, embed, allowedMentions, messageReference, component, stickers, options, embeds); + + /// + /// Message content is too long, length must be less or equal to . + public Task SendFilesAsync(IEnumerable attachments, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null) + => ChannelHelper.SendFilesAsync(this, Discord, attachments, text, isTTS, embed, allowedMentions, messageReference, component, stickers, options, embeds); /// public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) @@ -202,8 +262,9 @@ namespace Discord.WebSocket => _messages?.Add(msg); internal SocketMessage RemoveMessage(ulong id) => _messages?.Remove(id); + #endregion - //Users + #region Users /// public override SocketGuildUser GetUser(ulong id) { @@ -217,8 +278,9 @@ namespace Discord.WebSocket } return null; } + #endregion - //Webhooks + #region Webhooks /// /// Creates a webhook in this text channel. /// @@ -229,7 +291,7 @@ namespace Discord.WebSocket /// A task that represents the asynchronous creation operation. The task result contains the newly created /// webhook. /// - public Task CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null) + public virtual Task CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null) => ChannelHelper.CreateWebhookAsync(this, Discord, name, avatar, options); /// /// Gets a webhook available in this text channel. @@ -240,7 +302,7 @@ namespace Discord.WebSocket /// A task that represents the asynchronous get operation. The task result contains a webhook associated /// with the identifier; null if the webhook is not found. /// - public Task GetWebhookAsync(ulong id, RequestOptions options = null) + public virtual Task GetWebhookAsync(ulong id, RequestOptions options = null) => ChannelHelper.GetWebhookAsync(this, Discord, id, options); /// /// Gets the webhooks available in this text channel. @@ -250,21 +312,29 @@ namespace Discord.WebSocket /// A task that represents the asynchronous get operation. The task result contains a read-only collection /// of webhooks that is available in this channel. /// - public Task> GetWebhooksAsync(RequestOptions options = null) + public virtual Task> GetWebhooksAsync(RequestOptions options = null) => ChannelHelper.GetWebhooksAsync(this, Discord, options); + #endregion - //Invites + #region Invites /// - public async Task CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) + public virtual async Task CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) => await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, options).ConfigureAwait(false); /// - public async Task> GetInvitesAsync(RequestOptions options = null) + public virtual async Task CreateInviteToApplicationAsync(ulong applicationId, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null) + => await ChannelHelper.CreateInviteToApplicationAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, applicationId, options).ConfigureAwait(false); + /// + public virtual async Task CreateInviteToStreamAsync(IUser user, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null) + => await ChannelHelper.CreateInviteToStreamAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, user, options).ConfigureAwait(false); + /// + public virtual async Task> GetInvitesAsync(RequestOptions options = null) => await ChannelHelper.GetInvitesAsync(this, Discord, options).ConfigureAwait(false); private string DebuggerDisplay => $"{Name} ({Id}, Text)"; internal new SocketTextChannel Clone() => MemberwiseClone() as SocketTextChannel; + #endregion - //ITextChannel + #region ITextChannel /// async Task ITextChannel.CreateWebhookAsync(string name, Stream avatar, RequestOptions options) => await CreateWebhookAsync(name, avatar, options).ConfigureAwait(false); @@ -274,16 +344,21 @@ namespace Discord.WebSocket /// async Task> ITextChannel.GetWebhooksAsync(RequestOptions options) => await GetWebhooksAsync(options).ConfigureAwait(false); + /// + async Task ITextChannel.CreateThreadAsync(string name, ThreadType type, ThreadArchiveDuration autoArchiveDuration, IMessage message, bool? invitable, int? slowmode, RequestOptions options) + => await CreateThreadAsync(name, type, autoArchiveDuration, message, invitable, slowmode, options); + #endregion - //IGuildChannel + #region IGuildChannel /// Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetUser(id)); /// IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => ImmutableArray.Create>(Users).ToAsyncEnumerable(); + #endregion - //IMessageChannel + #region IMessageChannel /// async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options) { @@ -306,18 +381,26 @@ namespace Discord.WebSocket => await GetPinnedMessagesAsync(options).ConfigureAwait(false); /// - async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageReference messageReference) - => await SendFileAsync(filePath, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference).ConfigureAwait(false); + async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent component, ISticker[] stickers, Embed[] embeds) + => await SendFileAsync(filePath, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference, component, stickers, embeds).ConfigureAwait(false); + /// + async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent component, ISticker[] stickers, Embed[] embeds) + => await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference, component, stickers, embeds).ConfigureAwait(false); + /// + async Task IMessageChannel.SendFileAsync(FileAttachment attachment, string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent component, ISticker[] stickers, Embed[] embeds) + => await SendFileAsync(attachment, text, isTTS, embed, options, allowedMentions, messageReference, component, stickers, embeds).ConfigureAwait(false); /// - async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageReference messageReference) - => await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference).ConfigureAwait(false); + async Task IMessageChannel.SendFilesAsync(IEnumerable attachments, string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent component, ISticker[] stickers, Embed[] embeds) + => await SendFilesAsync(attachments, text, isTTS, embed, options, allowedMentions, messageReference, component, stickers, embeds).ConfigureAwait(false); /// - async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference) - => await SendMessageAsync(text, isTTS, embed, options, allowedMentions, messageReference).ConfigureAwait(false); + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent component, ISticker[] stickers, Embed[] embeds) + => await SendMessageAsync(text, isTTS, embed, options, allowedMentions, messageReference, component, stickers, embeds).ConfigureAwait(false); + #endregion - // INestedChannel + #region INestedChannel /// Task INestedChannel.GetCategoryAsync(CacheMode mode, RequestOptions options) => Task.FromResult(Category); + #endregion } } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketThreadChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketThreadChannel.cs new file mode 100644 index 000000000..7fcafc14a --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketThreadChannel.cs @@ -0,0 +1,339 @@ +using Discord.Rest; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Model = Discord.API.Channel; +using ThreadMember = Discord.API.ThreadMember; +using System.Collections.Concurrent; + +namespace Discord.WebSocket +{ + /// + /// Represents a thread channel inside of a guild. + /// + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public class SocketThreadChannel : SocketTextChannel, IThreadChannel + { + /// + public ThreadType Type { get; private set; } + + /// + /// Gets the owner of the current thread. + /// + public SocketThreadUser Owner { get; private set; } + + /// + /// Gets the current users within this thread. + /// + public SocketThreadUser CurrentUser + => Users.FirstOrDefault(x => x.Id == Discord.CurrentUser.Id); + + /// + public bool HasJoined { get; private set; } + + /// + /// if this thread is private, otherwise + /// + public bool IsPrivateThread + => Type == ThreadType.PrivateThread; + + /// + /// Gets the parent channel this thread resides in. + /// + public SocketTextChannel ParentChannel { get; private set; } + + /// + public int MessageCount { get; private set; } + + /// + public int MemberCount { get; private set; } + + /// + public bool IsArchived { get; private set; } + + /// + public DateTimeOffset ArchiveTimestamp { get; private set; } + + /// + public ThreadArchiveDuration AutoArchiveDuration { get; private set; } + + /// + public bool IsLocked { get; private set; } + + /// + /// Gets a collection of cached users within this thread. + /// + public new IReadOnlyCollection Users => + _members.Values.ToImmutableArray(); + + private readonly ConcurrentDictionary _members; + + private string DebuggerDisplay => $"{Name} ({Id}, Thread)"; + + private bool _usersDownloaded; + + private readonly object _downloadLock = new object(); + + internal SocketThreadChannel(DiscordSocketClient discord, SocketGuild guild, ulong id, SocketTextChannel parent) + : base(discord, id, guild) + { + ParentChannel = parent; + _members = new ConcurrentDictionary(); + } + + internal new static SocketThreadChannel Create(SocketGuild guild, ClientState state, Model model) + { + var parent = (SocketTextChannel)guild.GetChannel(model.CategoryId.Value); + var entity = new SocketThreadChannel(guild.Discord, guild, model.Id, parent); + entity.Update(state, model); + return entity; + } + + internal override void Update(ClientState state, Model model) + { + base.Update(state, model); + + Type = (ThreadType)model.Type; + MessageCount = model.MessageCount.GetValueOrDefault(-1); + MemberCount = model.MemberCount.GetValueOrDefault(-1); + + if (model.ThreadMetadata.IsSpecified) + { + IsArchived = model.ThreadMetadata.Value.Archived; + ArchiveTimestamp = model.ThreadMetadata.Value.ArchiveTimestamp; + AutoArchiveDuration = model.ThreadMetadata.Value.AutoArchiveDuration; + IsLocked = model.ThreadMetadata.Value.Locked.GetValueOrDefault(false); + } + + if (model.OwnerId.IsSpecified) + { + Owner = GetUser(model.OwnerId.Value); + } + + HasJoined = model.ThreadMember.IsSpecified; + } + + internal IReadOnlyCollection RemoveUsers(ulong[] users) + { + List threadUsers = new(); + + foreach (var userId in users) + { + if (_members.TryRemove(userId, out var user)) + threadUsers.Add(user); + } + + return threadUsers.ToImmutableArray(); + } + + internal SocketThreadUser AddOrUpdateThreadMember(ThreadMember model, SocketGuildUser guildMember) + { + if (_members.TryGetValue(model.UserId.Value, out SocketThreadUser member)) + member.Update(model); + else + { + member = SocketThreadUser.Create(Guild, this, model, guildMember); + member.GlobalUser.AddRef(); + _members[member.Id] = member; + } + return member; + } + + /// + public new SocketThreadUser GetUser(ulong id) + { + var user = Users.FirstOrDefault(x => x.Id == id); + return user; + } + + /// + /// Gets all users inside this thread. + /// + /// + /// If all users are not downloaded then this method will call and return the result. + /// + /// The options to be used when sending the request. + /// A task representing the download operation. + public async Task> GetUsersAsync(RequestOptions options = null) + { + // download all users if we havent + if (!_usersDownloaded) + { + await DownloadUsersAsync(options); + _usersDownloaded = true; + } + + return Users; + } + + /// + /// Downloads all users that have access to this thread. + /// + /// The options to be used when sending the request. + /// A task representing the asynchronous download operation. + public async Task DownloadUsersAsync(RequestOptions options = null) + { + var users = await Discord.ApiClient.ListThreadMembersAsync(Id, options); + + lock (_downloadLock) + { + foreach (var threadMember in users) + { + var guildUser = Guild.GetUser(threadMember.UserId.Value); + + AddOrUpdateThreadMember(threadMember, guildUser); + } + } + } + + internal new SocketThreadChannel Clone() => MemberwiseClone() as SocketThreadChannel; + + /// + public Task JoinAsync(RequestOptions options = null) + => Discord.ApiClient.JoinThreadAsync(Id, options); + + /// + public Task LeaveAsync(RequestOptions options = null) + => Discord.ApiClient.LeaveThreadAsync(Id, options); + + /// + /// Adds a user to this thread. + /// + /// The to add. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous operation of adding a member to a thread. + /// + public Task AddUserAsync(IGuildUser user, RequestOptions options = null) + => Discord.ApiClient.AddThreadMemberAsync(Id, user.Id, options); + + /// + /// Removes a user from this thread. + /// + /// The to remove from this thread. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous operation of removing a user from this thread. + /// + public Task RemoveUserAsync(IGuildUser user, RequestOptions options = null) + => Discord.ApiClient.RemoveThreadMemberAsync(Id, user.Id, options); + + /// + /// + /// This method is not supported in threads. + /// + public override Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, RequestOptions options = null) + => throw new NotSupportedException("This method is not supported in threads."); + + /// + /// + /// This method is not supported in threads. + /// + public override Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions, RequestOptions options = null) + => throw new NotSupportedException("This method is not supported in threads."); + + /// + /// + /// This method is not supported in threads. + /// + public override Task CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) + => throw new NotSupportedException("This method is not supported in threads."); + + /// + /// + /// This method is not supported in threads. + /// + public override Task CreateInviteToApplicationAsync(ulong applicationId, int? maxAge, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) + => throw new NotSupportedException("This method is not supported in threads."); + + /// + /// + /// This method is not supported in threads. + /// + public override Task CreateInviteToStreamAsync(IUser user, int? maxAge, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) + => throw new NotSupportedException("This method is not supported in threads."); + + /// + /// + /// This method is not supported in threads. + /// + public override Task CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null) + => throw new NotSupportedException("This method is not supported in threads."); + + /// + /// + /// This method is not supported in threads. + /// + public override Task> GetInvitesAsync(RequestOptions options = null) + => throw new NotSupportedException("This method is not supported in threads."); + + /// + /// + /// This method is not supported in threads. + /// + public override OverwritePermissions? GetPermissionOverwrite(IRole role) + => ParentChannel.GetPermissionOverwrite(role); + + /// + /// + /// This method is not supported in threads. + /// + public override OverwritePermissions? GetPermissionOverwrite(IUser user) + => ParentChannel.GetPermissionOverwrite(user); + + /// + /// + /// This method is not supported in threads. + /// + public override Task GetWebhookAsync(ulong id, RequestOptions options = null) + => throw new NotSupportedException("This method is not supported in threads."); + + /// + /// + /// This method is not supported in threads. + /// + public override Task> GetWebhooksAsync(RequestOptions options = null) + => throw new NotSupportedException("This method is not supported in threads."); + + /// + /// + /// This method is not supported in threads. + /// + public override Task ModifyAsync(Action func, RequestOptions options = null) + => ThreadHelper.ModifyAsync(this, Discord, func, options); + + /// + /// + /// This method is not supported in threads. + /// + public override Task RemovePermissionOverwriteAsync(IRole role, RequestOptions options = null) + => throw new NotSupportedException("This method is not supported in threads."); + + /// + /// + /// This method is not supported in threads. + /// + public override Task RemovePermissionOverwriteAsync(IUser user, RequestOptions options = null) + => throw new NotSupportedException("This method is not supported in threads."); + + /// + /// + /// This method is not supported in threads. + /// + public override IReadOnlyCollection PermissionOverwrites + => throw new NotSupportedException("This method is not supported in threads."); + + /// + /// + /// This method is not supported in threads. + /// + public override Task SyncPermissionsAsync(RequestOptions options = null) + => throw new NotSupportedException("This method is not supported in threads."); + + string IChannel.Name => Name; + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs index ecaccedd3..e57051e80 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs @@ -16,6 +16,7 @@ namespace Discord.WebSocket [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class SocketVoiceChannel : SocketGuildChannel, IVoiceChannel, ISocketAudioChannel { + #region SocketVoiceChannel /// public int Bitrate { get; private set; } /// @@ -89,29 +90,39 @@ namespace Discord.WebSocket return user; return null; } +#endregion - //Invites + #region Invites /// public async Task CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) => await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, options).ConfigureAwait(false); /// + public async Task CreateInviteToApplicationAsync(ulong applicationId, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null) + => await ChannelHelper.CreateInviteToApplicationAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, applicationId, options).ConfigureAwait(false); + /// + public async Task CreateInviteToStreamAsync(IUser user, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null) + => await ChannelHelper.CreateInviteToStreamAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, user, options).ConfigureAwait(false); + /// public async Task> GetInvitesAsync(RequestOptions options = null) => await ChannelHelper.GetInvitesAsync(this, Discord, options).ConfigureAwait(false); private string DebuggerDisplay => $"{Name} ({Id}, Voice)"; internal new SocketVoiceChannel Clone() => MemberwiseClone() as SocketVoiceChannel; + #endregion - //IGuildChannel + #region IGuildChannel /// Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetUser(id)); /// IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => ImmutableArray.Create>(Users).ToAsyncEnumerable(); + #endregion - // INestedChannel + #region INestedChannel /// Task INestedChannel.GetCategoryAsync(CacheMode mode, RequestOptions options) => Task.FromResult(Category); + #endregion } } diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index 9af4ad57e..beaab1cfe 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -19,6 +19,9 @@ using PresenceModel = Discord.API.Presence; using RoleModel = Discord.API.Role; using UserModel = Discord.API.User; using VoiceStateModel = Discord.API.VoiceState; +using StickerModel = Discord.API.Sticker; +using EventModel = Discord.API.GuildScheduledEvent; +using System.IO; namespace Discord.WebSocket { @@ -28,16 +31,19 @@ namespace Discord.WebSocket [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class SocketGuild : SocketEntity, IGuild, IDisposable { + #region SocketGuild #pragma warning disable IDISP002, IDISP006 private readonly SemaphoreSlim _audioLock; private TaskCompletionSource _syncPromise, _downloaderPromise; private TaskCompletionSource _audioConnectPromise; - private ConcurrentHashSet _channels; + private ConcurrentDictionary _channels; private ConcurrentDictionary _members; private ConcurrentDictionary _roles; private ConcurrentDictionary _voiceStates; + private ConcurrentDictionary _stickers; + private ConcurrentDictionary _events; private ImmutableArray _emotes; - private ImmutableArray _features; + private AudioClient _audioClient; #pragma warning restore IDISP002, IDISP006 @@ -118,9 +124,14 @@ namespace Discord.WebSocket public int? MaxMembers { get; private set; } /// public int? MaxVideoChannelUsers { get; private set; } - + /// + public NsfwLevel NsfwLevel { get; private set; } /// public CultureInfo PreferredCulture { get; private set; } + /// + public bool IsBoostProgressBarEnabled { get; private set; } + /// + public GuildFeatures Features { get; private set; } /// public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); @@ -131,7 +142,7 @@ namespace Discord.WebSocket /// public string DiscoverySplashUrl => CDN.GetGuildDiscoverySplashUrl(Id, DiscoverySplashId); /// - public string BannerUrl => CDN.GetGuildBannerUrl(Id, BannerId); + public string BannerUrl => CDN.GetGuildBannerUrl(Id, BannerId, ImageFormat.Auto); /// Indicates whether the client has all the members downloaded to the local guild cache. public bool HasAllMembers => MemberCount <= DownloadedMemberCount;// _downloaderPromise.Task.IsCompleted; /// Indicates whether the guild cache is synced to this guild. @@ -269,6 +280,14 @@ namespace Discord.WebSocket public IReadOnlyCollection VoiceChannels => Channels.OfType().ToImmutableArray(); /// + /// Gets a collection of all stage channels in this guild. + /// + /// + /// A read-only collection of stage channels found within this guild. + /// + public IReadOnlyCollection StageChannels + => Channels.OfType().ToImmutableArray(); + /// /// Gets a collection of all category channels in this guild. /// /// @@ -277,6 +296,14 @@ namespace Discord.WebSocket public IReadOnlyCollection CategoryChannels => Channels.OfType().ToImmutableArray(); /// + /// Gets a collection of all thread channels in this guild. + /// + /// + /// A read-only collection of thread channels found within this guild. + /// + public IReadOnlyCollection ThreadChannels + => Channels.OfType().ToImmutableArray(); + /// /// Gets the current logged-in user. /// public SocketGuildUser CurrentUser => _members.TryGetValue(Discord.CurrentUser.Id, out SocketGuildUser member) ? member : null; @@ -299,13 +326,16 @@ namespace Discord.WebSocket { var channels = _channels; var state = Discord.State; - return channels.Select(x => state.GetChannel(x) as SocketGuildChannel).Where(x => x != null).ToReadOnlyCollection(channels); + return channels.Select(x => x.Value).Where(x => x != null).ToReadOnlyCollection(channels); } } /// public IReadOnlyCollection Emotes => _emotes; - /// - public IReadOnlyCollection Features => _features; + /// + /// Gets a collection of all custom stickers for this guild. + /// + public IReadOnlyCollection Stickers + => _stickers.Select(x => x.Value).ToImmutableArray(); /// /// Gets a collection of users in this guild. /// @@ -336,12 +366,22 @@ namespace Discord.WebSocket /// public IReadOnlyCollection Roles => _roles.ToReadOnlyCollection(); + /// + /// Gets a collection of all events within this guild. + /// + /// + /// This field is based off of caching alone, since there is no events returned on the guild model. + /// + /// + /// A read-only collection of guild events found within this guild. + /// + public IReadOnlyCollection Events => _events.ToReadOnlyCollection(); + internal SocketGuild(DiscordSocketClient client, ulong id) : base(client, id) { _audioLock = new SemaphoreSlim(1, 1); _emotes = ImmutableArray.Create(); - _features = ImmutableArray.Create(); } internal static SocketGuild Create(DiscordSocketClient discord, ClientState state, ExtendedModel model) { @@ -354,8 +394,10 @@ namespace Discord.WebSocket IsAvailable = !(model.Unavailable ?? false); if (!IsAvailable) { + if(_events == null) + _events = new ConcurrentDictionary(); if (_channels == null) - _channels = new ConcurrentHashSet(); + _channels = new ConcurrentDictionary(); if (_members == null) _members = new ConcurrentDictionary(); if (_roles == null) @@ -371,15 +413,23 @@ namespace Discord.WebSocket Update(state, model as Model); - var channels = new ConcurrentHashSet(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.Channels.Length * 1.05)); + var channels = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.Channels.Length * 1.05)); { for (int i = 0; i < model.Channels.Length; i++) { var channel = SocketGuildChannel.Create(this, state, model.Channels[i]); state.AddChannel(channel); - channels.TryAdd(channel.Id); + channels.TryAdd(channel.Id, channel); + } + + for(int i = 0; i < model.Threads.Length; i++) + { + var threadChannel = SocketThreadChannel.Create(this, state, model.Threads[i]); + state.AddChannel(threadChannel); + channels.TryAdd(threadChannel.Id, threadChannel); } } + _channels = channels; var members = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.Members.Length * 1.05)); @@ -414,6 +464,17 @@ namespace Discord.WebSocket } _voiceStates = voiceStates; + var events = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.GuildScheduledEvents.Length * 1.05)); + { + for (int i = 0; i < model.GuildScheduledEvents.Length; i++) + { + var guildEvent = SocketGuildEvent.Create(Discord, this, model.GuildScheduledEvents[i]); + events.TryAdd(guildEvent.Id, guildEvent); + } + } + _events = events; + + _syncPromise = new TaskCompletionSource(); _downloaderPromise = new TaskCompletionSource(); var _ = _syncPromise.TrySetResultAsync(true); @@ -448,6 +509,7 @@ namespace Discord.WebSocket SystemChannelFlags = model.SystemChannelFlags; Description = model.Description; PremiumSubscriptionCount = model.PremiumSubscriptionCount.GetValueOrDefault(); + NsfwLevel = model.NsfwLevel; if (model.MaxPresences.IsSpecified) MaxPresences = model.MaxPresences.Value ?? 25000; if (model.MaxMembers.IsSpecified) @@ -456,7 +518,8 @@ namespace Discord.WebSocket MaxVideoChannelUsers = model.MaxVideoChannelUsers.Value; PreferredLocale = model.PreferredLocale; PreferredCulture = PreferredLocale == null ? null : new CultureInfo(PreferredLocale); - + if (model.IsBoostProgressBarEnabled.IsSpecified) + IsBoostProgressBarEnabled = model.IsBoostProgressBarEnabled.Value; if (model.Emojis != null) { var emojis = ImmutableArray.CreateBuilder(model.Emojis.Length); @@ -467,10 +530,7 @@ namespace Discord.WebSocket else _emotes = ImmutableArray.Create(); - if (model.Features != null) - _features = model.Features.ToImmutableArray(); - else - _features = ImmutableArray.Create(); + Features = model.Features; var roles = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.Roles.Length * 1.05)); if (model.Roles != null) @@ -482,6 +542,25 @@ namespace Discord.WebSocket } } _roles = roles; + + if (model.Stickers != null) + { + var stickers = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.Stickers.Length * 1.05)); + for (int i = 0; i < model.Stickers.Length; i++) + { + var sticker = model.Stickers[i]; + if (sticker.User.IsSpecified) + AddOrUpdateUser(sticker.User.Value); + + var entity = SocketCustomSticker.Create(Discord, sticker, this, sticker.User.IsSpecified ? sticker.User.Value.Id : null); + + stickers.TryAdd(sticker.Id, entity); + } + + _stickers = stickers; + } + else + _stickers = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, 7); } /*internal void Update(ClientState state, GuildSyncModel model) //TODO remove? userbot related { @@ -514,8 +593,9 @@ namespace Discord.WebSocket emotes.Add(model.Emojis[i].ToEntity()); _emotes = emotes.ToImmutable(); } + #endregion - //General + #region General /// public Task DeleteAsync(RequestOptions options = null) => GuildHelper.DeleteAsync(this, Discord, options); @@ -539,8 +619,9 @@ namespace Discord.WebSocket /// public Task LeaveAsync(RequestOptions options = null) => GuildHelper.LeaveAsync(this, Discord, options); + #endregion - //Bans + #region Bans /// /// Gets a collection of all users banned in this guild. /// @@ -588,8 +669,9 @@ namespace Discord.WebSocket /// public Task RemoveBanAsync(ulong userId, RequestOptions options = null) => GuildHelper.RemoveBanAsync(this, Discord, userId, options); + #endregion - //Channels + #region Channels /// /// Gets a channel in this guild. /// @@ -614,6 +696,16 @@ namespace Discord.WebSocket public SocketTextChannel GetTextChannel(ulong id) => GetChannel(id) as SocketTextChannel; /// + /// Gets a thread in this guild. + /// + /// The snowflake identifier for the thread. + /// + /// A thread channel associated with the specified ; if none is found. + /// + public SocketThreadChannel GetThreadChannel(ulong id) + => GetChannel(id) as SocketThreadChannel; + + /// /// Gets a voice channel in this guild. /// /// The snowflake identifier for the voice channel. @@ -623,6 +715,15 @@ namespace Discord.WebSocket public SocketVoiceChannel GetVoiceChannel(ulong id) => GetChannel(id) as SocketVoiceChannel; /// + /// Gets a stage channel in this guild. + /// + /// The snowflake identifier for the stage channel. + /// + /// A stage channel associated with the specified ; if none is found. + /// + public SocketStageChannel GetStageChannel(ulong id) + => GetChannel(id) as SocketStageChannel; + /// /// Gets a category channel in this guild. /// /// The snowflake identifier for the category channel. @@ -670,6 +771,19 @@ namespace Discord.WebSocket /// public Task CreateVoiceChannelAsync(string name, Action func = null, RequestOptions options = null) => GuildHelper.CreateVoiceChannelAsync(this, Discord, name, options, func); + + /// + /// Creates a new stage channel in this guild. + /// + /// The new name for the stage channel. + /// The delegate containing the properties to be applied to the channel upon its creation. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous creation operation. The task result contains the newly created + /// stage channel. + /// + public Task CreateStageChannelAsync(string name, Action func = null, RequestOptions options = null) + => GuildHelper.CreateStageChannelAsync(this, Discord, name, options, func); /// /// Creates a new channel category in this guild. /// @@ -687,25 +801,40 @@ namespace Discord.WebSocket internal SocketGuildChannel AddChannel(ClientState state, ChannelModel model) { var channel = SocketGuildChannel.Create(this, state, model); - _channels.TryAdd(model.Id); + _channels.TryAdd(model.Id, channel); state.AddChannel(channel); return channel; } + + internal SocketGuildChannel AddOrUpdateChannel(ClientState state, ChannelModel model) + { + if (_channels.TryGetValue(model.Id, out SocketGuildChannel channel)) + channel.Update(Discord.State, model); + else + { + channel = SocketGuildChannel.Create(this, Discord.State, model); + _channels[channel.Id] = channel; + state.AddChannel(channel); + } + return channel; + } + internal SocketGuildChannel RemoveChannel(ClientState state, ulong id) { - if (_channels.TryRemove(id)) + if (_channels.TryRemove(id, out var _)) return state.RemoveChannel(id) as SocketGuildChannel; return null; } internal void PurgeChannelCache(ClientState state) { foreach (var channelId in _channels) - state.RemoveChannel(channelId); + state.RemoveChannel(channelId.Key); _channels.Clear(); } + #endregion - //Voice Regions + #region Voice Regions /// /// Gets a collection of all the voice regions this guild can access. /// @@ -716,14 +845,124 @@ namespace Discord.WebSocket /// public Task> GetVoiceRegionsAsync(RequestOptions options = null) => GuildHelper.GetVoiceRegionsAsync(this, Discord, options); + #endregion - //Integrations + #region Integrations public Task> GetIntegrationsAsync(RequestOptions options = null) => GuildHelper.GetIntegrationsAsync(this, Discord, options); public Task CreateIntegrationAsync(ulong id, string type, RequestOptions options = null) => GuildHelper.CreateIntegrationAsync(this, Discord, id, type, options); + #endregion - //Invites + #region Interactions + /// + /// Deletes all application commands in the current guild. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous delete operation. + /// + public Task DeleteApplicationCommandsAsync(RequestOptions options = null) + => InteractionHelper.DeleteAllGuildCommandsAsync(Discord, Id, options); + + /// + /// Gets a collection of slash commands created by the current user in this guild. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection of + /// slash commands created by the current user. + /// + public async Task> GetApplicationCommandsAsync(RequestOptions options = null) + { + var commands = (await Discord.ApiClient.GetGuildApplicationCommandsAsync(Id, options)).Select(x => SocketApplicationCommand.Create(Discord, x, Id)); + + foreach (var command in commands) + { + Discord.State.AddCommand(command); + } + + return commands.ToImmutableArray(); + } + + /// + /// Gets an application command within this guild with the specified id. + /// + /// The id of the application command to get. + /// The that determines whether the object should be fetched from cache. + /// The options to be used when sending the request. + /// + /// A ValueTask that represents the asynchronous get operation. The task result contains a + /// if found, otherwise . + /// + public async ValueTask GetApplicationCommandAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null) + { + var command = Discord.State.GetCommand(id); + + if (command != null) + return command; + + if (mode == CacheMode.CacheOnly) + return null; + + var model = await Discord.ApiClient.GetGlobalApplicationCommandAsync(id, options); + + if (model == null) + return null; + + command = SocketApplicationCommand.Create(Discord, model, Id); + + Discord.State.AddCommand(command); + + return command; + } + + /// + /// Creates an application command within this guild. + /// + /// The properties to use when creating the command. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous creation operation. The task result contains the command that was created. + /// + public async Task CreateApplicationCommandAsync(ApplicationCommandProperties properties, RequestOptions options = null) + { + var model = await InteractionHelper.CreateGuildCommandAsync(Discord, Id, properties, options); + + var entity = Discord.State.GetOrAddCommand(model.Id, (id) => SocketApplicationCommand.Create(Discord, model)); + + entity.Update(model); + + return entity; + } + + /// + /// Overwrites the application commands within this guild. + /// + /// A collection of properties to use when creating the commands. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous creation operation. The task result contains a collection of commands that was created. + /// + public async Task> BulkOverwriteApplicationCommandAsync(ApplicationCommandProperties[] properties, + RequestOptions options = null) + { + var models = await InteractionHelper.BulkOverwriteGuildCommandsAsync(Discord, Id, properties, options); + + var entities = models.Select(x => SocketApplicationCommand.Create(Discord, x)); + + Discord.State.PurgeCommands(x => !x.IsGlobalCommand && x.Guild.Id == Id); + + foreach(var entity in entities) + { + Discord.State.AddCommand(entity); + } + + return entities.ToImmutableArray(); + } + #endregion + + #region Invites /// /// Gets a collection of all invites in this guild. /// @@ -744,8 +983,9 @@ namespace Discord.WebSocket /// public Task GetVanityInviteAsync(RequestOptions options = null) => GuildHelper.GetVanityInviteAsync(this, Discord, options); + #endregion - //Roles + #region Roles /// /// Gets a role in this guild. /// @@ -794,7 +1034,45 @@ namespace Discord.WebSocket return null; } - //Users + internal SocketRole AddOrUpdateRole(RoleModel model) + { + if (_roles.TryGetValue(model.Id, out SocketRole role)) + _roles[model.Id].Update(Discord.State, model); + else + role = AddRole(model); + + return role; + } + + internal SocketCustomSticker AddSticker(StickerModel model) + { + if (model.User.IsSpecified) + AddOrUpdateUser(model.User.Value); + + var sticker = SocketCustomSticker.Create(Discord, model, this, model.User.IsSpecified ? model.User.Value.Id : null); + _stickers[model.Id] = sticker; + return sticker; + } + + internal SocketCustomSticker AddOrUpdateSticker(StickerModel model) + { + if (_stickers.TryGetValue(model.Id, out SocketCustomSticker sticker)) + _stickers[model.Id].Update(model); + else + sticker = AddSticker(model); + + return sticker; + } + + internal SocketCustomSticker RemoveSticker(ulong id) + { + if (_stickers.TryRemove(id, out SocketCustomSticker sticker)) + return sticker; + return null; + } + #endregion + + #region Users /// public Task AddGuildUserAsync(ulong id, string accessToken, Action func = null, RequestOptions options = null) => GuildHelper.AddGuildUserAsync(this, Discord, id, accessToken, func, options); @@ -935,8 +1213,118 @@ namespace Discord.WebSocket /// public Task> SearchUsersAsync(string query, int limit = DiscordConfig.MaxUsersPerBatch, RequestOptions options = null) => GuildHelper.SearchUsersAsync(this, Discord, query, limit, options); + #endregion + + #region Guild Events + + /// + /// Gets an event in this guild. + /// + /// The snowflake identifier for the event. + /// + /// An event that is associated with the specified ; if none is found. + /// + public SocketGuildEvent GetEvent(ulong id) + { + if (_events.TryGetValue(id, out SocketGuildEvent value)) + return value; + return null; + } + + internal SocketGuildEvent RemoveEvent(ulong id) + { + if (_events.TryRemove(id, out SocketGuildEvent value)) + return value; + return null; + } + + internal SocketGuildEvent AddOrUpdateEvent(EventModel model) + { + if (_events.TryGetValue(model.Id, out SocketGuildEvent value)) + value.Update(model); + else + { + value = SocketGuildEvent.Create(Discord, this, model); + _events[model.Id] = value; + } + return value; + } + + /// + /// Gets an event within this guild. + /// + /// The snowflake identifier for the event. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. + /// + public Task GetEventAsync(ulong id, RequestOptions options = null) + => GuildHelper.GetGuildEventAsync(Discord, id, this, options); - //Audit logs + /// + /// Gets all active events within this guild. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. + /// + public Task> GetEventsAsync(RequestOptions options = null) + => GuildHelper.GetGuildEventsAsync(Discord, this, options); + + /// + /// Creates an event within this guild. + /// + /// The name of the event. + /// The privacy level of the event. + /// The start time of the event. + /// The type of the event. + /// The description of the event. + /// The end time of the event. + /// + /// The channel id of the event. + /// + /// The event must have a type of or + /// in order to use this property. + /// + /// + /// A collection of speakers for the event. + /// The location of the event; links are supported + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous create operation. + /// + public Task CreateEventAsync( + string name, + DateTimeOffset startTime, + GuildScheduledEventType type, + GuildScheduledEventPrivacyLevel privacyLevel = GuildScheduledEventPrivacyLevel.Private, + string description = null, + DateTimeOffset? endTime = null, + ulong? channelId = null, + string location = null, + RequestOptions options = null) + { + // requirements taken from https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-permissions-requirements + switch (type) + { + case GuildScheduledEventType.Stage: + CurrentUser.GuildPermissions.Ensure(GuildPermission.ManageEvents | GuildPermission.ManageChannels | GuildPermission.MuteMembers | GuildPermission.MoveMembers); + break; + case GuildScheduledEventType.Voice: + CurrentUser.GuildPermissions.Ensure(GuildPermission.ManageEvents | GuildPermission.ViewChannel | GuildPermission.Connect); + break; + case GuildScheduledEventType.External: + CurrentUser.GuildPermissions.Ensure(GuildPermission.ManageEvents); + break; + } + + return GuildHelper.CreateGuildEventAsync(Discord, this, name, privacyLevel, startTime, type, description, endTime, channelId, location, options); + } + + + #endregion + + #region Audit logs /// /// Gets the specified number of audit log entries for this guild. /// @@ -951,8 +1339,9 @@ namespace Discord.WebSocket /// public IAsyncEnumerable> GetAuditLogsAsync(int limit, RequestOptions options = null, ulong? beforeId = null, ulong? userId = null, ActionType? actionType = null) => GuildHelper.GetAuditLogsAsync(this, Discord, beforeId, limit, options, userId: userId, actionType: actionType); + #endregion - //Webhooks + #region Webhooks /// /// Gets a webhook found within this guild. /// @@ -974,8 +1363,9 @@ namespace Discord.WebSocket /// public Task> GetWebhooksAsync(RequestOptions options = null) => GuildHelper.GetWebhooksAsync(this, Discord, options); + #endregion - //Emotes + #region Emotes /// public Task> GetEmotesAsync(RequestOptions options = null) => GuildHelper.GetEmotesAsync(this, Discord, options); @@ -993,7 +1383,154 @@ namespace Discord.WebSocket public Task DeleteEmoteAsync(GuildEmote emote, RequestOptions options = null) => GuildHelper.DeleteEmoteAsync(this, Discord, emote.Id, options); - //Voice States + /// + /// Moves the user to the voice channel. + /// + /// The user to move. + /// the channel where the user gets moved to. + /// A task that represents the asynchronous operation for moving a user. + public Task MoveAsync(IGuildUser user, IVoiceChannel targetChannel) + => user.ModifyAsync(x => x.Channel = new Optional(targetChannel)); + + /// + /// Disconnects the user from its current voice channel + /// + /// The user to disconnect. + /// A task that represents the asynchronous operation for disconnecting a user. + async Task IGuild.DisconnectAsync(IGuildUser user) => await user.ModifyAsync(x => x.Channel = new Optional()); + #endregion + + #region Stickers + /// + /// Gets a specific sticker within this guild. + /// + /// The id of the sticker to get. + /// The that determines whether the object should be fetched from cache. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains the sticker found with the + /// specified ; if none is found. + /// + public async ValueTask GetStickerAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null) + { + var sticker = _stickers.FirstOrDefault(x => x.Key == id); + + if (sticker.Value != null) + return sticker.Value; + + if (mode == CacheMode.CacheOnly) + return null; + + var model = await Discord.ApiClient.GetGuildStickerAsync(Id, id, options).ConfigureAwait(false); + + if (model == null) + return null; + + return AddOrUpdateSticker(model); + } + /// + /// Gets a specific sticker within this guild. + /// + /// The id of the sticker to get. + /// A sticker, if none is found then . + public SocketCustomSticker GetSticker(ulong id) + => GetStickerAsync(id, CacheMode.CacheOnly).GetAwaiter().GetResult(); + /// + /// Gets a collection of all stickers within this guild. + /// + /// The that determines whether the object should be fetched from cache. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection + /// of stickers found within the guild. + /// + public async ValueTask> GetStickersAsync(CacheMode mode = CacheMode.AllowDownload, + RequestOptions options = null) + { + if (Stickers.Count > 0) + return Stickers; + + if (mode == CacheMode.CacheOnly) + return ImmutableArray.Create(); + + var models = await Discord.ApiClient.ListGuildStickersAsync(Id, options).ConfigureAwait(false); + + List stickers = new(); + + foreach (var model in models) + { + stickers.Add(AddOrUpdateSticker(model)); + } + + return stickers; + } + /// + /// Creates a new sticker in this guild. + /// + /// The name of the sticker. + /// The description of the sticker. + /// The tags of the sticker. + /// The image of the new emote. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous creation operation. The task result contains the created sticker. + /// + public async Task CreateStickerAsync(string name, string description, IEnumerable tags, Image image, + RequestOptions options = null) + { + var model = await GuildHelper.CreateStickerAsync(Discord, this, name, description, tags, image, options).ConfigureAwait(false); + + return AddOrUpdateSticker(model); + } + /// + /// Creates a new sticker in this guild + /// + /// The name of the sticker. + /// The description of the sticker. + /// The tags of the sticker. + /// The path of the file to upload. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous creation operation. The task result contains the created sticker. + /// + public Task CreateStickerAsync(string name, string description, IEnumerable tags, string path, + RequestOptions options = null) + { + var fs = File.OpenRead(path); + return CreateStickerAsync(name, description, tags, fs, Path.GetFileName(fs.Name), options); + } + /// + /// Creates a new sticker in this guild + /// + /// The name of the sticker. + /// The description of the sticker. + /// The tags of the sticker. + /// The stream containing the file data. + /// The name of the file with the extension, ex: image.png. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous creation operation. The task result contains the created sticker. + /// + public async Task CreateStickerAsync(string name, string description, IEnumerable tags, Stream stream, + string filename, RequestOptions options = null) + { + var model = await GuildHelper.CreateStickerAsync(Discord, this, name, description, tags, stream, filename, options).ConfigureAwait(false); + + return AddOrUpdateSticker(model); + } + /// + /// Deletes a sticker within this guild. + /// + /// The sticker to delete. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous removal operation. + /// + public Task DeleteStickerAsync(SocketCustomSticker sticker, RequestOptions options = null) + => sticker.DeleteAsync(options); + #endregion + + #region Voice States internal async Task AddOrUpdateVoiceStateAsync(ClientState state, VoiceStateModel model) { var voiceChannel = state.GetChannel(model.ChannelId.Value) as SocketVoiceChannel; @@ -1037,8 +1574,9 @@ namespace Discord.WebSocket } return null; } + #endregion - //Audio + #region Audio internal AudioInStream GetAudioStream(ulong userId) { return _audioClient?.GetInputStream(userId); @@ -1143,7 +1681,7 @@ namespace Discord.WebSocket } internal async Task FinishConnectAudio(string url, string token) { - //TODO: Mem Leak: Disconnected/Connected handlers arent cleaned up + //TODO: Mem Leak: Disconnected/Connected handlers aren't cleaned up var voiceState = GetVoiceState(Discord.CurrentUser.Id).Value; await _audioLock.WaitAsync().ConfigureAwait(false); @@ -1192,8 +1730,9 @@ namespace Discord.WebSocket public override string ToString() => Name; private string DebuggerDisplay => $"{Name} ({Id})"; internal SocketGuild Clone() => MemberwiseClone() as SocketGuild; + #endregion - //IGuild + #region IGuild /// ulong? IGuild.AFKChannelId => AFKChannelId; /// @@ -1216,7 +1755,17 @@ namespace Discord.WebSocket int? IGuild.ApproximateMemberCount => null; /// int? IGuild.ApproximatePresenceCount => null; - + /// + IReadOnlyCollection IGuild.Stickers => Stickers; + /// + async Task IGuild.CreateEventAsync(string name, DateTimeOffset startTime, GuildScheduledEventType type, GuildScheduledEventPrivacyLevel privacyLevel, string description, DateTimeOffset? endTime, ulong? channelId, string location, RequestOptions options) + => await CreateEventAsync(name, startTime, type, privacyLevel, description, endTime, channelId, location, options).ConfigureAwait(false); + /// + async Task IGuild.GetEventAsync(ulong id, RequestOptions options) + => await GetEventAsync(id, options).ConfigureAwait(false); + /// + async Task> IGuild.GetEventsAsync(RequestOptions options) + => await GetEventsAsync(options).ConfigureAwait(false); /// async Task> IGuild.GetBansAsync(RequestOptions options) => await GetBansAsync(options).ConfigureAwait(false); @@ -1240,15 +1789,27 @@ namespace Discord.WebSocket Task IGuild.GetTextChannelAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetTextChannel(id)); /// + Task IGuild.GetThreadChannelAsync(ulong id, CacheMode mode, RequestOptions options) + => Task.FromResult(GetThreadChannel(id)); + /// + Task> IGuild.GetThreadChannelsAsync(CacheMode mode, RequestOptions options) + => Task.FromResult>(ThreadChannels); + /// Task> IGuild.GetVoiceChannelsAsync(CacheMode mode, RequestOptions options) => Task.FromResult>(VoiceChannels); /// - Task> IGuild.GetCategoriesAsync(CacheMode mode , RequestOptions options) + Task> IGuild.GetCategoriesAsync(CacheMode mode, RequestOptions options) => Task.FromResult>(CategoryChannels); /// Task IGuild.GetVoiceChannelAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetVoiceChannel(id)); /// + Task IGuild.GetStageChannelAsync(ulong id, CacheMode mode, RequestOptions options) + => Task.FromResult(GetStageChannel(id)); + /// + Task> IGuild.GetStageChannelsAsync(CacheMode mode, RequestOptions options) + => Task.FromResult>(StageChannels); + /// Task IGuild.GetAFKChannelAsync(CacheMode mode, RequestOptions options) => Task.FromResult(AFKChannel); /// @@ -1273,6 +1834,9 @@ namespace Discord.WebSocket async Task IGuild.CreateVoiceChannelAsync(string name, Action func, RequestOptions options) => await CreateVoiceChannelAsync(name, func, options).ConfigureAwait(false); /// + async Task IGuild.CreateStageChannelAsync(string name, Action func, RequestOptions options) + => await CreateStageChannelAsync(name, func, options).ConfigureAwait(false); + /// async Task IGuild.CreateCategoryAsync(string name, Action func, RequestOptions options) => await CreateCategoryChannelAsync(name, func, options).ConfigureAwait(false); @@ -1350,6 +1914,37 @@ namespace Discord.WebSocket /// async Task> IGuild.GetWebhooksAsync(RequestOptions options) => await GetWebhooksAsync(options).ConfigureAwait(false); + /// + async Task> IGuild.GetApplicationCommandsAsync (RequestOptions options) + => await GetApplicationCommandsAsync(options).ConfigureAwait(false); + /// + async Task IGuild.CreateStickerAsync(string name, string description, IEnumerable tags, Image image, RequestOptions options) + => await CreateStickerAsync(name, description, tags, image, options); + /// + async Task IGuild.CreateStickerAsync(string name, string description, IEnumerable tags, Stream stream, string filename, RequestOptions options) + => await CreateStickerAsync(name, description, tags, stream, filename, options); + /// + async Task IGuild.CreateStickerAsync(string name, string description, IEnumerable tags, string path, RequestOptions options) + => await CreateStickerAsync(name, description, tags, path, options); + /// + async Task IGuild.GetStickerAsync(ulong id, CacheMode mode, RequestOptions options) + => await GetStickerAsync(id, mode, options); + /// + async Task> IGuild.GetStickersAsync(CacheMode mode, RequestOptions options) + => await GetStickersAsync(mode, options); + /// + Task IGuild.DeleteStickerAsync(ICustomSticker sticker, RequestOptions options) + => DeleteStickerAsync(_stickers[sticker.Id], options); + /// + async Task IGuild.GetApplicationCommandAsync(ulong id, CacheMode mode, RequestOptions options) + => await GetApplicationCommandAsync(id, mode, options); + /// + async Task IGuild.CreateApplicationCommandAsync(ApplicationCommandProperties properties, RequestOptions options) + => await CreateApplicationCommandAsync(properties, options); + /// + async Task> IGuild.BulkOverwriteApplicationCommandsAsync(ApplicationCommandProperties[] properties, + RequestOptions options) + => await BulkOverwriteApplicationCommandAsync(properties, options); void IDisposable.Dispose() { @@ -1357,5 +1952,6 @@ namespace Discord.WebSocket _audioLock?.Dispose(); _audioClient?.Dispose(); } + #endregion } } diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuildEvent.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuildEvent.cs new file mode 100644 index 000000000..6974c0498 --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuildEvent.cs @@ -0,0 +1,216 @@ +using Discord.Rest; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Model = Discord.API.GuildScheduledEvent; + +namespace Discord.WebSocket +{ + /// + /// Represents a WebSocket-based guild event. + /// + public class SocketGuildEvent : SocketEntity, IGuildScheduledEvent + { + /// + /// Gets the guild of the event. + /// + public SocketGuild Guild { get; private set; } + + /// + /// Gets the channel of the event. + /// + public SocketGuildChannel Channel { get; private set; } + + /// + /// Gets the user who created the event. + /// + public SocketGuildUser Creator { get; private set; } + + /// + public string Name { get; private set; } + + /// + public string Description { get; private set; } + + /// + public DateTimeOffset StartTime { get; private set; } + + /// + public DateTimeOffset? EndTime { get; private set; } + + /// + public GuildScheduledEventPrivacyLevel PrivacyLevel { get; private set; } + + /// + public GuildScheduledEventStatus Status { get; private set; } + + /// + public GuildScheduledEventType Type { get; private set; } + + /// + public ulong? EntityId { get; private set; } + + /// + public string Location { get; private set; } + + /// + public int? UserCount { get; private set; } + + internal SocketGuildEvent(DiscordSocketClient client, SocketGuild guild, ulong id) + : base(client, id) + { + Guild = guild; + } + + internal static SocketGuildEvent Create(DiscordSocketClient client, SocketGuild guild, Model model) + { + var entity = new SocketGuildEvent(client, guild, model.Id); + entity.Update(model); + return entity; + } + + internal void Update(Model model) + { + if (model.ChannelId.IsSpecified && model.ChannelId.Value != null) + { + Channel = Guild.GetChannel(model.ChannelId.Value.Value); + } + + if (model.CreatorId.IsSpecified) + { + var guildUser = Guild.GetUser(model.CreatorId.Value); + + if(guildUser != null) + { + if(model.Creator.IsSpecified) + guildUser.Update(Discord.State, model.Creator.Value); + + Creator = guildUser; + } + else if (guildUser == null && model.Creator.IsSpecified) + { + guildUser = SocketGuildUser.Create(Guild, Discord.State, model.Creator.Value); + Creator = guildUser; + } + } + + Name = model.Name; + Description = model.Description.GetValueOrDefault(); + + EntityId = model.EntityId; + Location = model.EntityMetadata?.Location.GetValueOrDefault(); + Type = model.EntityType; + + PrivacyLevel = model.PrivacyLevel; + EndTime = model.ScheduledEndTime; + StartTime = model.ScheduledStartTime; + Status = model.Status; + UserCount = model.UserCount.ToNullable(); + } + + /// + public Task DeleteAsync(RequestOptions options = null) + => GuildHelper.DeleteEventAsync(Discord, this, options); + + /// + public Task StartAsync(RequestOptions options = null) + => ModifyAsync(x => x.Status = GuildScheduledEventStatus.Active); + + /// + public Task EndAsync(RequestOptions options = null) + => ModifyAsync(x => x.Status = Status == GuildScheduledEventStatus.Scheduled + ? GuildScheduledEventStatus.Cancelled + : GuildScheduledEventStatus.Completed); + + /// + public async Task ModifyAsync(Action func, RequestOptions options = null) + { + var model = await GuildHelper.ModifyGuildEventAsync(Discord, func, this, options).ConfigureAwait(false); + Update(model); + } + + /// + /// Gets a collection of users that are interested in this event. + /// + /// The amount of users to fetch. + /// The options to be used when sending the request. + /// + /// A read-only collection of users. + /// + public Task> GetUsersAsync(int limit = 100, RequestOptions options = null) + => GuildHelper.GetEventUsersAsync(Discord, this, limit, options); + + /// + /// Gets a collection of N users interested in the event. + /// + /// + /// + /// The returned collection is an asynchronous enumerable object; one must call + /// to access the individual messages as a + /// collection. + /// + /// This method will attempt to fetch all users that are interested in the event. + /// The library will attempt to split up the requests according to and . + /// In other words, if there are 300 users, and the constant + /// is 100, the request will be split into 3 individual requests; thus returning 3 individual asynchronous + /// responses, hence the need of flattening. + /// + /// The options to be used when sending the request. + /// + /// Paged collection of users. + /// + public IAsyncEnumerable> GetUsersAsync(RequestOptions options = null) + => GuildHelper.GetEventUsersAsync(Discord, this, null, null, options); + + /// + /// Gets a collection of N users interested in the event. + /// + /// + /// + /// The returned collection is an asynchronous enumerable object; one must call + /// to access the individual users as a + /// collection. + /// + /// + /// Do not fetch too many users at once! This may cause unwanted preemptive rate limit or even actual + /// rate limit, causing your bot to freeze! + /// + /// This method will attempt to fetch the number of users specified under around + /// the user depending on the . The library will + /// attempt to split up the requests according to your and + /// . In other words, should the user request 500 users, + /// and the constant is 100, the request will + /// be split into 5 individual requests; thus returning 5 individual asynchronous responses, hence the need + /// of flattening. + /// + /// The ID of the starting user to get the users from. + /// The direction of the users to be gotten from. + /// The numbers of users to be gotten from. + /// The options to be used when sending the request. + /// + /// Paged collection of users. + /// + public IAsyncEnumerable> GetUsersAsync(ulong fromUserId, Direction dir, int limit = DiscordConfig.MaxGuildEventUsersPerBatch, RequestOptions options = null) + => GuildHelper.GetEventUsersAsync(Discord, this, fromUserId, dir, limit, options); + + #region IGuildScheduledEvent + + /// + IAsyncEnumerable> IGuildScheduledEvent.GetUsersAsync(RequestOptions options) + => GetUsersAsync(options); + /// + IAsyncEnumerable> IGuildScheduledEvent.GetUsersAsync(ulong fromUserId, Direction dir, int limit, RequestOptions options) + => GetUsersAsync(fromUserId, dir, limit, options); + /// + IGuild IGuildScheduledEvent.Guild => Guild; + /// + IUser IGuildScheduledEvent.Creator => Creator; + /// + ulong? IGuildScheduledEvent.ChannelId => Channel?.Id; + + #endregion + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/ContextMenuCommands/MessageCommands/SocketMessageCommand.cs b/src/Discord.Net.WebSocket/Entities/Interaction/ContextMenuCommands/MessageCommands/SocketMessageCommand.cs new file mode 100644 index 000000000..0aa061439 --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Interaction/ContextMenuCommands/MessageCommands/SocketMessageCommand.cs @@ -0,0 +1,45 @@ +using DataModel = Discord.API.ApplicationCommandInteractionData; +using Model = Discord.API.Interaction; + +namespace Discord.WebSocket +{ + /// + /// Represents a Websocket-based slash command received over the gateway. + /// + public class SocketMessageCommand : SocketCommandBase, IMessageCommandInteraction, IDiscordInteraction + { + /// + /// The data associated with this interaction. + /// + public new SocketMessageCommandData Data { get; } + + internal SocketMessageCommand(DiscordSocketClient client, Model model, ISocketMessageChannel channel) + : base(client, model, channel) + { + var dataModel = model.Data.IsSpecified + ? (DataModel)model.Data.Value + : null; + + ulong? guildId = null; + if (Channel is SocketGuildChannel guildChannel) + guildId = guildChannel.Guild.Id; + + Data = SocketMessageCommandData.Create(client, dataModel, model.Id, guildId); + } + + internal new static SocketInteraction Create(DiscordSocketClient client, Model model, ISocketMessageChannel channel) + { + var entity = new SocketMessageCommand(client, model, channel); + entity.Update(model); + return entity; + } + + //IMessageCommandInteraction + /// + IMessageCommandInteractionData IMessageCommandInteraction.Data => Data; + + //IDiscordInteraction + /// + IDiscordInteractionData IDiscordInteraction.Data => Data; + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/ContextMenuCommands/MessageCommands/SocketMessageCommandData.cs b/src/Discord.Net.WebSocket/Entities/Interaction/ContextMenuCommands/MessageCommands/SocketMessageCommandData.cs new file mode 100644 index 000000000..71a30b44a --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Interaction/ContextMenuCommands/MessageCommands/SocketMessageCommandData.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; +using System.Linq; +using Model = Discord.API.ApplicationCommandInteractionData; + +namespace Discord.WebSocket +{ + /// + /// Represents the data tied with the interaction. + /// + public class SocketMessageCommandData : SocketCommandBaseData, IMessageCommandInteractionData, IDiscordInteractionData + { + /// + /// Gets the message associated with this message command. + /// + public SocketMessage Message + => ResolvableData?.Messages.FirstOrDefault().Value; + + /// + /// + /// Note Not implemented for + /// + public override IReadOnlyCollection Options + => throw new System.NotImplementedException(); + + internal SocketMessageCommandData(DiscordSocketClient client, Model model, ulong? guildId) + : base(client, model, guildId) { } + + internal new static SocketMessageCommandData Create(DiscordSocketClient client, Model model, ulong id, ulong? guildId) + { + var entity = new SocketMessageCommandData(client, model, guildId); + entity.Update(model); + return entity; + } + + //IMessageCommandInteractionData + /// + IMessage IMessageCommandInteractionData.Message => Message; + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/ContextMenuCommands/UserCommands/SocketUserCommand.cs b/src/Discord.Net.WebSocket/Entities/Interaction/ContextMenuCommands/UserCommands/SocketUserCommand.cs new file mode 100644 index 000000000..40ee5b537 --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Interaction/ContextMenuCommands/UserCommands/SocketUserCommand.cs @@ -0,0 +1,45 @@ +using DataModel = Discord.API.ApplicationCommandInteractionData; +using Model = Discord.API.Interaction; + +namespace Discord.WebSocket +{ + /// + /// Represents a Websocket-based slash command received over the gateway. + /// + public class SocketUserCommand : SocketCommandBase, IUserCommandInteraction, IDiscordInteraction + { + /// + /// The data associated with this interaction. + /// + public new SocketUserCommandData Data { get; } + + internal SocketUserCommand(DiscordSocketClient client, Model model, ISocketMessageChannel channel) + : base(client, model, channel) + { + var dataModel = model.Data.IsSpecified + ? (DataModel)model.Data.Value + : null; + + ulong? guildId = null; + if (Channel is SocketGuildChannel guildChannel) + guildId = guildChannel.Guild.Id; + + Data = SocketUserCommandData.Create(client, dataModel, model.Id, guildId); + } + + internal new static SocketInteraction Create(DiscordSocketClient client, Model model, ISocketMessageChannel channel) + { + var entity = new SocketUserCommand(client, model, channel); + entity.Update(model); + return entity; + } + + //IUserCommandInteraction + /// + IUserCommandInteractionData IUserCommandInteraction.Data => Data; + + //IDiscordInteraction + /// + IDiscordInteractionData IDiscordInteraction.Data => Data; + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/ContextMenuCommands/UserCommands/SocketUserCommandData.cs b/src/Discord.Net.WebSocket/Entities/Interaction/ContextMenuCommands/UserCommands/SocketUserCommandData.cs new file mode 100644 index 000000000..eaebbcb06 --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Interaction/ContextMenuCommands/UserCommands/SocketUserCommandData.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; +using System.Linq; +using Model = Discord.API.ApplicationCommandInteractionData; + +namespace Discord.WebSocket +{ + /// + /// Represents the data tied with the interaction. + /// + public class SocketUserCommandData : SocketCommandBaseData, IUserCommandInteractionData, IDiscordInteractionData + { + /// + /// Gets the user who this command targets. + /// + public SocketUser Member + => (SocketUser)ResolvableData.GuildMembers.Values.FirstOrDefault() ?? ResolvableData.Users.Values.FirstOrDefault(); + + /// + /// + /// Note Not implemented for + /// + public override IReadOnlyCollection Options + => throw new System.NotImplementedException(); + + internal SocketUserCommandData(DiscordSocketClient client, Model model, ulong? guildId) + : base(client, model, guildId) { } + + internal new static SocketUserCommandData Create(DiscordSocketClient client, Model model, ulong id, ulong? guildId) + { + var entity = new SocketUserCommandData(client, model, guildId); + entity.Update(model); + return entity; + } + + //IUserCommandInteractionData + /// + IUser IUserCommandInteractionData.User => Member; + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponent.cs b/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponent.cs new file mode 100644 index 000000000..928a4302a --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponent.cs @@ -0,0 +1,436 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Model = Discord.API.Interaction; +using DataModel = Discord.API.MessageComponentInteractionData; +using Discord.Rest; +using System.Collections.Generic; +using Discord.Net.Rest; +using System.IO; + +namespace Discord.WebSocket +{ + /// + /// Represents a Websocket-based interaction type for Message Components. + /// + public class SocketMessageComponent : SocketInteraction, IComponentInteraction, IDiscordInteraction + { + /// + /// Gets the data received with this interaction, contains the button that was clicked. + /// + public new SocketMessageComponentData Data { get; } + + /// + /// Gets the message that contained the trigger for this interaction. + /// + public SocketUserMessage Message { get; private set; } + + private object _lock = new object(); + public override bool HasResponded { get; internal set; } = false; + + internal SocketMessageComponent(DiscordSocketClient client, Model model, ISocketMessageChannel channel) + : base(client, model.Id, channel) + { + var dataModel = model.Data.IsSpecified + ? (DataModel)model.Data.Value + : null; + + Data = new SocketMessageComponentData(dataModel); + } + + internal new static SocketMessageComponent Create(DiscordSocketClient client, Model model, ISocketMessageChannel channel) + { + var entity = new SocketMessageComponent(client, model, channel); + entity.Update(model); + return entity; + } + internal override void Update(Model model) + { + base.Update(model); + + if (model.Message.IsSpecified) + { + if (Message == null) + { + SocketUser author = null; + if (Channel is SocketGuildChannel channel) + { + if (model.Message.Value.WebhookId.IsSpecified) + author = SocketWebhookUser.Create(channel.Guild, Discord.State, model.Message.Value.Author.Value, model.Message.Value.WebhookId.Value); + else if (model.Message.Value.Author.IsSpecified) + author = channel.Guild.GetUser(model.Message.Value.Author.Value.Id); + } + else if (model.Message.Value.Author.IsSpecified) + author = (Channel as SocketChannel).GetUser(model.Message.Value.Author.Value.Id); + + Message = SocketUserMessage.Create(Discord, Discord.State, author, Channel, model.Message.Value); + } + else + { + Message.Update(Discord.State, model.Message.Value); + } + } + } + /// + public override async Task RespondAsync( + string text = null, + Embed[] embeds = null, + bool isTTS = false, + bool ephemeral = false, + AllowedMentions allowedMentions = null, + RequestOptions options = null, + MessageComponent component = null, + Embed embed = null) + { + if (!IsValidToken) + throw new InvalidOperationException("Interaction token is no longer valid"); + + if (!InteractionHelper.CanSendResponse(this)) + throw new TimeoutException($"Cannot respond to an interaction after {InteractionHelper.ResponseTimeLimit} seconds!"); + + embeds ??= Array.Empty(); + if (embed != null) + embeds = new[] { embed }.Concat(embeds).ToArray(); + + Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); + Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); + Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed."); + + // check that user flag and user Id list are exclusive, same with role flag and role Id list + if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue) + { + if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users) && + allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0) + { + throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(allowedMentions)); + } + + if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles) && + allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0) + { + throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(allowedMentions)); + } + } + + var response = new API.InteractionResponse + { + Type = InteractionResponseType.ChannelMessageWithSource, + Data = new API.InteractionCallbackData + { + Content = text ?? Optional.Unspecified, + AllowedMentions = allowedMentions?.ToModel(), + Embeds = embeds.Select(x => x.ToModel()).ToArray(), + TTS = isTTS, + Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified + } + }; + + if (ephemeral) + response.Data.Value.Flags = MessageFlags.Ephemeral; + + lock (_lock) + { + if (HasResponded) + { + throw new InvalidOperationException("Cannot respond, update, or defer twice to the same interaction"); + } + } + + await InteractionHelper.SendInteractionResponseAsync(Discord, response, Id, Token, options).ConfigureAwait(false); + + lock (_lock) + { + HasResponded = true; + } + } + + /// + /// Updates the message which this component resides in with the type + /// + /// A delegate containing the properties to modify the message with. + /// The request options for this request. + /// A task that represents the asynchronous operation of updating the message. + public async Task UpdateAsync(Action func, RequestOptions options = null) + { + var args = new MessageProperties(); + func(args); + + if (!IsValidToken) + throw new InvalidOperationException("Interaction token is no longer valid"); + + if (!InteractionHelper.CanSendResponse(this)) + throw new TimeoutException($"Cannot respond to an interaction after {InteractionHelper.ResponseTimeLimit} seconds!"); + + if (args.AllowedMentions.IsSpecified) + { + var allowedMentions = args.AllowedMentions.Value; + Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions), "A max of 100 role Ids are allowed."); + Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions), "A max of 100 user Ids are allowed."); + } + + var embed = args.Embed; + var embeds = args.Embeds; + + bool hasText = args.Content.IsSpecified ? !string.IsNullOrEmpty(args.Content.Value) : !string.IsNullOrEmpty(Message.Content); + bool hasEmbeds = embed.IsSpecified && embed.Value != null || embeds.IsSpecified && embeds.Value?.Length > 0 || Message.Embeds.Any(); + + if (!hasText && !hasEmbeds) + Preconditions.NotNullOrEmpty(args.Content.IsSpecified ? args.Content.Value : string.Empty, nameof(args.Content)); + + var apiEmbeds = embed.IsSpecified || embeds.IsSpecified ? new List() : null; + + if (embed.IsSpecified && embed.Value != null) + { + apiEmbeds.Add(embed.Value.ToModel()); + } + + if (embeds.IsSpecified && embeds.Value != null) + { + apiEmbeds.AddRange(embeds.Value.Select(x => x.ToModel())); + } + + Preconditions.AtMost(apiEmbeds?.Count ?? 0, 10, nameof(args.Embeds), "A max of 10 embeds are allowed."); + + // check that user flag and user Id list are exclusive, same with role flag and role Id list + if (args.AllowedMentions.IsSpecified && args.AllowedMentions.Value != null && args.AllowedMentions.Value.AllowedTypes.HasValue) + { + var allowedMentions = args.AllowedMentions.Value; + if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users) + && allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0) + { + throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(args.AllowedMentions)); + } + + if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles) + && allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0) + { + throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(args.AllowedMentions)); + } + } + + var response = new API.InteractionResponse + { + Type = InteractionResponseType.UpdateMessage, + Data = new API.InteractionCallbackData + { + Content = args.Content, + AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional.Unspecified, + Embeds = apiEmbeds?.ToArray() ?? Optional.Unspecified, + Components = args.Components.IsSpecified + ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Array.Empty() + : Optional.Unspecified, + Flags = args.Flags.IsSpecified ? args.Flags.Value ?? Optional.Unspecified : Optional.Unspecified + } + }; + + lock (_lock) + { + if (HasResponded) + { + throw new InvalidOperationException("Cannot respond, update, or defer twice to the same interaction"); + } + } + + await InteractionHelper.SendInteractionResponseAsync(Discord, response, Id, Token, options).ConfigureAwait(false); + + lock (_lock) + { + HasResponded = true; + } + } + + /// + public override async Task FollowupAsync( + string text = null, + Embed[] embeds = null, + bool isTTS = false, + bool ephemeral = false, + AllowedMentions allowedMentions = null, + RequestOptions options = null, + MessageComponent component = null, + Embed embed = null) + { + if (!IsValidToken) + throw new InvalidOperationException("Interaction token is no longer valid"); + + embeds ??= Array.Empty(); + if (embed != null) + embeds = new[] { embed }.Concat(embeds).ToArray(); + + Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); + Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); + Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed."); + + var args = new API.Rest.CreateWebhookMessageParams + { + Content = text, + AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, + IsTTS = isTTS, + Embeds = embeds.Select(x => x.ToModel()).ToArray(), + Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified + }; + + if (ephemeral) + args.Flags = MessageFlags.Ephemeral; + + return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options).ConfigureAwait(false); + } + + /// + public override async Task FollowupWithFileAsync( + Stream fileStream, + string fileName, + string text = null, + Embed[] embeds = null, + bool isTTS = false, + bool ephemeral = false, + AllowedMentions allowedMentions = null, + RequestOptions options = null, + MessageComponent component = null, + Embed embed = null) + { + if (!IsValidToken) + throw new InvalidOperationException("Interaction token is no longer valid"); + + embeds ??= Array.Empty(); + if (embed != null) + embeds = new[] { embed }.Concat(embeds).ToArray(); + + Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); + Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); + Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed."); + Preconditions.NotNull(fileStream, nameof(fileStream), "File Stream must have data"); + Preconditions.NotNullOrWhitespace(fileName, nameof(fileName), "File Name must not be empty or null"); + + var args = new API.Rest.CreateWebhookMessageParams + { + Content = text, + AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, + IsTTS = isTTS, + Embeds = embeds.Select(x => x.ToModel()).ToArray(), + Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified, + File = fileStream is not null ? new MultipartFile(fileStream, fileName) : Optional.Unspecified + }; + + if (ephemeral) + args.Flags = MessageFlags.Ephemeral; + + return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options).ConfigureAwait(false); + } + + /// + public override async Task FollowupWithFileAsync( + string filePath, + string text = null, + string fileName = null, + Embed[] embeds = null, + bool isTTS = false, + bool ephemeral = false, + AllowedMentions allowedMentions = null, + RequestOptions options = null, + MessageComponent component = null, + Embed embed = null) + { + if (!IsValidToken) + throw new InvalidOperationException("Interaction token is no longer valid"); + + embeds ??= Array.Empty(); + if (embed != null) + embeds = new[] { embed }.Concat(embeds).ToArray(); + + Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); + Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); + Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed."); + Preconditions.NotNullOrWhitespace(filePath, nameof(filePath), "Path must exist"); + + var args = new API.Rest.CreateWebhookMessageParams + { + Content = text, + AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, + IsTTS = isTTS, + Embeds = embeds.Select(x => x.ToModel()).ToArray(), + Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified, + File = !string.IsNullOrEmpty(filePath) ? new MultipartFile(new MemoryStream(File.ReadAllBytes(filePath), false), fileName) : Optional.Unspecified + }; + + if (ephemeral) + args.Flags = MessageFlags.Ephemeral; + + return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options).ConfigureAwait(false); + } + + /// + /// Defers an interaction and responds with type 5 () + /// + /// to send this message ephemerally, otherwise . + /// The request options for this request. + /// + /// A task that represents the asynchronous operation of acknowledging the interaction. + /// + public async Task DeferLoadingAsync(bool ephemeral = false, RequestOptions options = null) + { + if (!InteractionHelper.CanSendResponse(this)) + throw new TimeoutException($"Cannot defer an interaction after {InteractionHelper.ResponseTimeLimit} seconds of no response/acknowledgement"); + + var response = new API.InteractionResponse + { + Type = InteractionResponseType.DeferredChannelMessageWithSource, + Data = ephemeral ? new API.InteractionCallbackData { Flags = MessageFlags.Ephemeral } : Optional.Unspecified + }; + + lock (_lock) + { + if (HasResponded) + { + throw new InvalidOperationException("Cannot respond or defer twice to the same interaction"); + } + } + + await Discord.Rest.ApiClient.CreateInteractionResponseAsync(response, Id, Token, options).ConfigureAwait(false); + + lock (_lock) + { + HasResponded = true; + } + } + + /// + public override async Task DeferAsync(bool ephemeral = false, RequestOptions options = null) + { + if (!InteractionHelper.CanSendResponse(this)) + throw new TimeoutException($"Cannot defer an interaction after {InteractionHelper.ResponseTimeLimit} seconds of no response/acknowledgement"); + + var response = new API.InteractionResponse + { + Type = InteractionResponseType.DeferredUpdateMessage, + Data = ephemeral ? new API.InteractionCallbackData { Flags = MessageFlags.Ephemeral } : Optional.Unspecified + }; + + lock (_lock) + { + if (HasResponded) + { + throw new InvalidOperationException("Cannot respond or defer twice to the same interaction"); + } + } + + await Discord.Rest.ApiClient.CreateInteractionResponseAsync(response, Id, Token, options).ConfigureAwait(false); + + lock (_lock) + { + HasResponded = true; + } + } + + //IComponentInteraction + /// + IComponentInteractionData IComponentInteraction.Data => Data; + + /// + IUserMessage IComponentInteraction.Message => Message; + + //IDiscordInteraction + /// + IDiscordInteractionData IDiscordInteraction.Data => Data; + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponentData.cs b/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponentData.cs new file mode 100644 index 000000000..71e1d0395 --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponentData.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using Model = Discord.API.MessageComponentInteractionData; + +namespace Discord.WebSocket +{ + /// + /// Represents the data sent with a . + /// + public class SocketMessageComponentData : IComponentInteractionData + { + /// + /// Gets the components Custom Id that was clicked. + /// + public string CustomId { get; } + + /// + /// Gets the type of the component clicked. + /// + public ComponentType Type { get; } + + /// + /// Gets the value(s) of a interaction response. + /// + public IReadOnlyCollection Values { get; } + + internal SocketMessageComponentData(Model model) + { + CustomId = model.CustomId; + Type = model.ComponentType; + Values = model.Values.GetValueOrDefault(); + } + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SlashCommands/SocketAutocompleteInteraction.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SlashCommands/SocketAutocompleteInteraction.cs new file mode 100644 index 000000000..5637cb6f0 --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SlashCommands/SocketAutocompleteInteraction.cs @@ -0,0 +1,126 @@ +using Discord.Rest; +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using Model = Discord.API.Interaction; +using DataModel = Discord.API.AutocompleteInteractionData; + +namespace Discord.WebSocket +{ + /// + /// Represents a received over the gateway. + /// + public class SocketAutocompleteInteraction : SocketInteraction, IAutocompleteInteraction, IDiscordInteraction + { + /// + /// The autocomplete data of this interaction. + /// + public new SocketAutocompleteInteractionData Data { get; } + + public override bool HasResponded { get; internal set; } + private object _lock = new object(); + + internal SocketAutocompleteInteraction(DiscordSocketClient client, Model model, ISocketMessageChannel channel) + : base(client, model.Id, channel) + { + var dataModel = model.Data.IsSpecified + ? (DataModel)model.Data.Value + : null; + + if (dataModel != null) + Data = new SocketAutocompleteInteractionData(dataModel); + } + + internal new static SocketAutocompleteInteraction Create(DiscordSocketClient client, Model model, ISocketMessageChannel channel) + { + var entity = new SocketAutocompleteInteraction(client, model, channel); + entity.Update(model); + return entity; + } + + /// + /// Responds to this interaction with a set of choices. + /// + /// + /// The set of choices for the user to pick from. + /// + /// A max of 20 choices are allowed. Passing for this argument will show the executing user that + /// there is no choices for their autocompleted input. + /// + /// + /// The request options for this response. + /// + /// A task that represents the asynchronous operation of responding to this interaction. + /// + public async Task RespondAsync(IEnumerable result, RequestOptions options = null) + { + if (!InteractionHelper.CanSendResponse(this)) + throw new TimeoutException($"Cannot respond to an interaction after {InteractionHelper.ResponseTimeLimit} seconds!"); + + lock (_lock) + { + if (HasResponded) + { + throw new InvalidOperationException("Cannot respond twice to the same interaction"); + } + } + + await InteractionHelper.SendAutocompleteResultAsync(Discord, result, Id, Token, options).ConfigureAwait(false); + lock (_lock) + { + HasResponded = true; + } + } + + /// + /// Responds to this interaction with a set of choices. + /// + /// The request options for this response. + /// + /// The set of choices for the user to pick from. + /// + /// A max of 20 choices are allowed. Passing for this argument will show the executing user that + /// there is no choices for their autocompleted input. + /// + /// + /// + /// A task that represents the asynchronous operation of responding to this interaction. + /// + public Task RespondAsync(RequestOptions options = null, params AutocompleteResult[] result) + => RespondAsync(result, options); + + /// + [Obsolete("Autocomplete interactions cannot be deferred!", true)] + public override Task DeferAsync(bool ephemeral = false, RequestOptions options = null) + => throw new NotSupportedException("Autocomplete interactions cannot be deferred!"); + + /// + [Obsolete("Autocomplete interactions cannot have followups!", true)] + public override Task FollowupAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null) + => throw new NotSupportedException("Autocomplete interactions cannot be deferred!"); + + /// + [Obsolete("Autocomplete interactions cannot have followups!", true)] + public override Task FollowupWithFileAsync(Stream fileStream, string fileName, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null) + => throw new NotSupportedException("Autocomplete interactions cannot be deferred!"); + + /// + [Obsolete("Autocomplete interactions cannot have followups!", true)] + public override Task FollowupWithFileAsync(string filePath, string text = null, string fileName = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null) + => throw new NotSupportedException("Autocomplete interactions cannot be deferred!"); + + /// + [Obsolete("Autocomplete interactions cannot have normal responses!", true)] + public override Task RespondAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null) + => throw new NotSupportedException("Autocomplete interactions cannot be deferred!"); + + //IAutocompleteInteraction + /// + IAutocompleteInteractionData IAutocompleteInteraction.Data => Data; + + //IDiscordInteraction + /// + IDiscordInteractionData IDiscordInteraction.Data => Data; + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SlashCommands/SocketAutocompleteInteractionData.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SlashCommands/SocketAutocompleteInteractionData.cs new file mode 100644 index 000000000..1d9803c02 --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SlashCommands/SocketAutocompleteInteractionData.cs @@ -0,0 +1,73 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using DataModel = Discord.API.AutocompleteInteractionData; + +namespace Discord.WebSocket +{ + /// + /// Represents data for a slash commands autocomplete interaction. + /// + public class SocketAutocompleteInteractionData : IAutocompleteInteractionData, IDiscordInteractionData + { + /// + /// Gets the name of the invoked command. + /// + public string CommandName { get; } + + /// + /// Gets the id of the invoked command. + /// + public ulong CommandId { get; } + + /// + /// Gets the type of the invoked command. + /// + public ApplicationCommandType Type { get; } + + /// + /// Gets the version of the invoked command. + /// + public ulong Version { get; } + + /// + /// Gets the current autocomplete option that is actively being filled out. + /// + public AutocompleteOption Current { get; } + + /// + /// Gets a collection of all the other options the executing users has filled out. + /// + public IReadOnlyCollection Options { get; } + + internal SocketAutocompleteInteractionData(DataModel model) + { + var options = model.Options.SelectMany(GetOptions); + + Current = options.FirstOrDefault(x => x.Focused); + Options = options.ToImmutableArray(); + + if (Options.Count == 1 && Current == null) + Current = Options.FirstOrDefault(); + + CommandName = model.Name; + CommandId = model.Id; + Type = model.Type; + Version = model.Version; + } + + private List GetOptions(API.AutocompleteInteractionDataOption model) + { + var options = new List(); + + options.Add(new AutocompleteOption(model.Type, model.Name, model.Value.GetValueOrDefault(null), model.Focused.GetValueOrDefault(false))); + + if (model.Options.IsSpecified) + { + options.AddRange(model.Options.Value.SelectMany(GetOptions)); + } + + return options; + } + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SlashCommands/SocketSlashCommand.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SlashCommands/SocketSlashCommand.cs new file mode 100644 index 000000000..5343bb225 --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SlashCommands/SocketSlashCommand.cs @@ -0,0 +1,45 @@ +using DataModel = Discord.API.ApplicationCommandInteractionData; +using Model = Discord.API.Interaction; + +namespace Discord.WebSocket +{ + /// + /// Represents a Websocket-based slash command received over the gateway. + /// + public class SocketSlashCommand : SocketCommandBase, ISlashCommandInteraction, IDiscordInteraction + { + /// + /// The data associated with this interaction. + /// + public new SocketSlashCommandData Data { get; } + + internal SocketSlashCommand(DiscordSocketClient client, Model model, ISocketMessageChannel channel) + : base(client, model, channel) + { + var dataModel = model.Data.IsSpecified + ? (DataModel)model.Data.Value + : null; + + ulong? guildId = null; + if (Channel is SocketGuildChannel guildChannel) + guildId = guildChannel.Guild.Id; + + Data = SocketSlashCommandData.Create(client, dataModel, guildId); + } + + internal new static SocketInteraction Create(DiscordSocketClient client, Model model, ISocketMessageChannel channel) + { + var entity = new SocketSlashCommand(client, model, channel); + entity.Update(model); + return entity; + } + + //ISlashCommandInteraction + /// + IApplicationCommandInteractionData ISlashCommandInteraction.Data => Data; + + //IDiscordInteraction + /// + IDiscordInteractionData IDiscordInteraction.Data => Data; + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SlashCommands/SocketSlashCommandData.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SlashCommands/SocketSlashCommandData.cs new file mode 100644 index 000000000..c385ce825 --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SlashCommands/SocketSlashCommandData.cs @@ -0,0 +1,30 @@ +using System.Collections.Immutable; +using System.Linq; +using Model = Discord.API.ApplicationCommandInteractionData; + +namespace Discord.WebSocket +{ + /// + /// Represents the data tied with the interaction. + /// + public class SocketSlashCommandData : SocketCommandBaseData, IDiscordInteractionData + { + internal SocketSlashCommandData(DiscordSocketClient client, Model model, ulong? guildId) + : base(client, model, guildId) { } + + internal static SocketSlashCommandData Create(DiscordSocketClient client, Model model, ulong? guildId) + { + var entity = new SocketSlashCommandData(client, model, guildId); + entity.Update(model); + return entity; + } + internal override void Update(Model model) + { + base.Update(model); + + Options = model.Options.IsSpecified + ? model.Options.Value.Select(x => new SocketSlashCommandDataOption(this, x)).ToImmutableArray() + : ImmutableArray.Create(); + } + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SlashCommands/SocketSlashCommandDataOption.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SlashCommands/SocketSlashCommandDataOption.cs new file mode 100644 index 000000000..265eda75b --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SlashCommands/SocketSlashCommandDataOption.cs @@ -0,0 +1,135 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Model = Discord.API.ApplicationCommandInteractionDataOption; + +namespace Discord.WebSocket +{ + /// + /// Represents a Websocket-based received by the gateway. + /// + public class SocketSlashCommandDataOption : IApplicationCommandInteractionDataOption + { + #region SocketSlashCommandDataOption + /// + public string Name { get; private set; } + + /// + public object Value { get; private set; } + + /// + public ApplicationCommandOptionType Type { get; private set; } + + /// + /// The sub command options received for this sub command group. + /// + public IReadOnlyCollection Options { get; private set; } + + internal SocketSlashCommandDataOption() { } + internal SocketSlashCommandDataOption(SocketSlashCommandData data, Model model) + { + Name = model.Name; + Type = model.Type; + + if (model.Value.IsSpecified) + { + switch (Type) + { + case ApplicationCommandOptionType.User: + case ApplicationCommandOptionType.Role: + case ApplicationCommandOptionType.Channel: + case ApplicationCommandOptionType.Mentionable: + if (ulong.TryParse($"{model.Value.Value}", out var valueId)) + { + switch (Type) + { + case ApplicationCommandOptionType.User: + { + var guildUser = data.ResolvableData.GuildMembers.FirstOrDefault(x => x.Key == valueId).Value; + + if (guildUser != null) + Value = guildUser; + else + Value = data.ResolvableData.Users.FirstOrDefault(x => x.Key == valueId).Value; + } + break; + case ApplicationCommandOptionType.Channel: + Value = data.ResolvableData.Channels.FirstOrDefault(x => x.Key == valueId).Value; + break; + case ApplicationCommandOptionType.Role: + Value = data.ResolvableData.Roles.FirstOrDefault(x => x.Key == valueId).Value; + break; + case ApplicationCommandOptionType.Mentionable: + { + if (data.ResolvableData.GuildMembers.Any(x => x.Key == valueId) || data.ResolvableData.Users.Any(x => x.Key == valueId)) + { + var guildUser = data.ResolvableData.GuildMembers.FirstOrDefault(x => x.Key == valueId).Value; + + if (guildUser != null) + Value = guildUser; + else + Value = data.ResolvableData.Users.FirstOrDefault(x => x.Key == valueId).Value; + } + else if (data.ResolvableData.Roles.Any(x => x.Key == valueId)) + { + Value = data.ResolvableData.Roles.FirstOrDefault(x => x.Key == valueId).Value; + } + } + break; + default: + Value = model.Value.Value; + break; + } + } + break; + case ApplicationCommandOptionType.String: + Value = model.Value.ToString(); + break; + case ApplicationCommandOptionType.Integer: + { + if (model.Value.Value is long val) + Value = val; + else if (long.TryParse(model.Value.Value.ToString(), out long res)) + Value = res; + } + break; + case ApplicationCommandOptionType.Boolean: + { + if (model.Value.Value is bool val) + Value = val; + else if (bool.TryParse(model.Value.Value.ToString(), out bool res)) + Value = res; + } + break; + case ApplicationCommandOptionType.Number: + { + if (model.Value.Value is int val) + Value = val; + else if (double.TryParse(model.Value.Value.ToString(), out double res)) + Value = res; + } + break; + } + } + + Options = model.Options.IsSpecified + ? model.Options.Value.Select(x => new SocketSlashCommandDataOption(data, x)).ToImmutableArray() + : ImmutableArray.Create(); + } + #endregion + + #region Converters + public static explicit operator bool(SocketSlashCommandDataOption option) + => (bool)option.Value; + public static explicit operator int(SocketSlashCommandDataOption option) + => (int)option.Value; + public static explicit operator string(SocketSlashCommandDataOption option) + => option.Value.ToString(); + #endregion + + #region IApplicationCommandInteractionDataOption + IReadOnlyCollection IApplicationCommandInteractionDataOption.Options + => Options; + #endregion + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommand.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommand.cs new file mode 100644 index 000000000..d986a93f3 --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommand.cs @@ -0,0 +1,116 @@ +using Discord.Rest; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; +using GatewayModel = Discord.API.Gateway.ApplicationCommandCreatedUpdatedEvent; +using Model = Discord.API.ApplicationCommand; + +namespace Discord.WebSocket +{ + /// + /// Represents a Websocket-based . + /// + public class SocketApplicationCommand : SocketEntity, IApplicationCommand + { + #region SocketApplicationCommand + /// + /// if this command is a global command, otherwise . + /// + public bool IsGlobalCommand + => Guild == null; + + /// + public ulong ApplicationId { get; private set; } + + /// + public string Name { get; private set; } + + /// + public ApplicationCommandType Type { get; private set; } + + /// + public string Description { get; private set; } + + /// + public bool IsDefaultPermission { get; private set; } + + /// + /// A collection of 's for this command. + /// + /// + /// If the is not a slash command, this field will be an empty collection. + /// + public IReadOnlyCollection Options { get; private set; } + + /// + public DateTimeOffset CreatedAt + => SnowflakeUtils.FromSnowflake(Id); + + /// + /// Returns the guild this command resides in, if this command is a global command then it will return + /// + public SocketGuild Guild + => GuildId.HasValue ? Discord.GetGuild(GuildId.Value) : null; + + private ulong? GuildId { get; set; } + + internal SocketApplicationCommand(DiscordSocketClient client, ulong id, ulong? guildId) + : base(client, id) + { + GuildId = guildId; + } + internal static SocketApplicationCommand Create(DiscordSocketClient client, GatewayModel model) + { + var entity = new SocketApplicationCommand(client, model.Id, model.GuildId.ToNullable()); + entity.Update(model); + return entity; + } + + internal static SocketApplicationCommand Create(DiscordSocketClient client, Model model, ulong? guildId = null) + { + var entity = new SocketApplicationCommand(client, model.Id, guildId); + entity.Update(model); + return entity; + } + + internal void Update(Model model) + { + ApplicationId = model.ApplicationId; + Description = model.Description; + Name = model.Name; + IsDefaultPermission = model.DefaultPermissions.GetValueOrDefault(true); + Type = model.Type; + + Options = model.Options.IsSpecified + ? model.Options.Value.Select(SocketApplicationCommandOption.Create).ToImmutableArray() + : ImmutableArray.Create(); + } + + /// + public Task DeleteAsync(RequestOptions options = null) + => InteractionHelper.DeleteUnknownApplicationCommandAsync(Discord, GuildId, this, options); + + /// + public Task ModifyAsync(Action func, RequestOptions options = null) + { + return ModifyAsync(func, options); + } + + /// + public async Task ModifyAsync(Action func, RequestOptions options = null) where TArg : ApplicationCommandProperties + { + var command = IsGlobalCommand + ? await InteractionHelper.ModifyGlobalCommandAsync(Discord, this, func, options).ConfigureAwait(false) + : await InteractionHelper.ModifyGuildCommandAsync(Discord, this, GuildId.Value, func, options); + + Update(command); + } + #endregion + + #region IApplicationCommand + IReadOnlyCollection IApplicationCommand.Options => Options; + #endregion + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommandChoice.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommandChoice.cs new file mode 100644 index 000000000..e70efa27b --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommandChoice.cs @@ -0,0 +1,29 @@ +using Model = Discord.API.ApplicationCommandOptionChoice; + +namespace Discord.WebSocket +{ + /// + /// Represents a choice for a . + /// + public class SocketApplicationCommandChoice : IApplicationCommandOptionChoice + { + /// + public string Name { get; private set; } + + /// + public object Value { get; private set; } + + internal SocketApplicationCommandChoice() { } + internal static SocketApplicationCommandChoice Create(Model model) + { + var entity = new SocketApplicationCommandChoice(); + entity.Update(model); + return entity; + } + internal void Update(Model model) + { + Name = model.Name; + Value = model.Value; + } + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommandOption.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommandOption.cs new file mode 100644 index 000000000..a19068d48 --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommandOption.cs @@ -0,0 +1,87 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Model = Discord.API.ApplicationCommandOption; + +namespace Discord.WebSocket +{ + /// + /// Represents an option for a . + /// + public class SocketApplicationCommandOption : IApplicationCommandOption + { + /// + public string Name { get; private set; } + + /// + public ApplicationCommandOptionType Type { get; private set; } + + /// + public string Description { get; private set; } + + /// + public bool? IsDefault { get; private set; } + + /// + public bool? IsRequired { get; private set; } + + /// + public double? MinValue { get; private set; } + + /// + public double? MaxValue { get; private set; } + + /// + /// Choices for string and int types for the user to pick from. + /// + public IReadOnlyCollection Choices { get; private set; } + + /// + /// If the option is a subcommand or subcommand group type, this nested options will be the parameters. + /// + public IReadOnlyCollection Options { get; private set; } + + /// + /// The allowed channel types for this option. + /// + public IReadOnlyCollection ChannelTypes { get; private set; } + + internal SocketApplicationCommandOption() { } + internal static SocketApplicationCommandOption Create(Model model) + { + var entity = new SocketApplicationCommandOption(); + entity.Update(model); + return entity; + } + + internal void Update(Model model) + { + Name = model.Name; + Type = model.Type; + Description = model.Description; + + IsDefault = model.Default.ToNullable(); + + IsRequired = model.Required.ToNullable(); + + MinValue = model.MinValue.ToNullable(); + + MaxValue = model.MaxValue.ToNullable(); + + Choices = model.Choices.IsSpecified + ? model.Choices.Value.Select(SocketApplicationCommandChoice.Create).ToImmutableArray() + : ImmutableArray.Create(); + + Options = model.Options.IsSpecified + ? model.Options.Value.Select(Create).ToImmutableArray() + : ImmutableArray.Create(); + + ChannelTypes = model.ChannelTypes.IsSpecified + ? model.ChannelTypes.Value.ToImmutableArray() + : ImmutableArray.Create(); + } + + IReadOnlyCollection IApplicationCommandOption.Choices => Choices; + IReadOnlyCollection IApplicationCommandOption.Options => Options; + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs new file mode 100644 index 000000000..92303d488 --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs @@ -0,0 +1,300 @@ +using Discord.Net.Rest; +using Discord.Rest; +using System; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using DataModel = Discord.API.ApplicationCommandInteractionData; +using Model = Discord.API.Interaction; + +namespace Discord.WebSocket +{ + /// + /// Base class for User, Message, and Slash command interactions. + /// + public class SocketCommandBase : SocketInteraction + { + /// + /// Gets the name of the invoked command. + /// + public string CommandName + => Data.Name; + + /// + /// Gets the id of the invoked command. + /// + public ulong CommandId + => Data.Id; + + /// + /// The data associated with this interaction. + /// + internal new SocketCommandBaseData Data { get; } + + public override bool HasResponded { get; internal set; } + + private object _lock = new object(); + + internal SocketCommandBase(DiscordSocketClient client, Model model, ISocketMessageChannel channel) + : base(client, model.Id, channel) + { + var dataModel = model.Data.IsSpecified + ? (DataModel)model.Data.Value + : null; + + ulong? guildId = null; + if (Channel is SocketGuildChannel guildChannel) + guildId = guildChannel.Guild.Id; + + Data = SocketCommandBaseData.Create(client, dataModel, model.Id, guildId); + } + + internal new static SocketInteraction Create(DiscordSocketClient client, Model model, ISocketMessageChannel channel) + { + var entity = new SocketCommandBase(client, model, channel); + entity.Update(model); + return entity; + } + + internal override void Update(Model model) + { + var data = model.Data.IsSpecified + ? (DataModel)model.Data.Value + : null; + + Data.Update(data); + + base.Update(model); + } + + /// + public override async Task RespondAsync( + string text = null, + Embed[] embeds = null, + bool isTTS = false, + bool ephemeral = false, + AllowedMentions allowedMentions = null, + RequestOptions options = null, + MessageComponent component = null, + Embed embed = null) + { + if (!IsValidToken) + throw new InvalidOperationException("Interaction token is no longer valid"); + + if (!InteractionHelper.CanSendResponse(this)) + throw new TimeoutException($"Cannot respond to an interaction after {InteractionHelper.ResponseTimeLimit} seconds!"); + + embeds ??= Array.Empty(); + if (embed != null) + embeds = new[] { embed }.Concat(embeds).ToArray(); + + Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); + Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); + Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed."); + + // check that user flag and user Id list are exclusive, same with role flag and role Id list + if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue) + { + if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users) && + allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0) + { + throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(allowedMentions)); + } + + if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles) && + allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0) + { + throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(allowedMentions)); + } + } + + var response = new API.InteractionResponse + { + Type = InteractionResponseType.ChannelMessageWithSource, + Data = new API.InteractionCallbackData + { + Content = text, + AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, + Embeds = embeds.Select(x => x.ToModel()).ToArray(), + TTS = isTTS, + Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified, + Flags = ephemeral ? MessageFlags.Ephemeral : Optional.Unspecified + } + }; + + lock (_lock) + { + if (HasResponded) + { + throw new InvalidOperationException("Cannot respond twice to the same interaction"); + } + } + + await InteractionHelper.SendInteractionResponseAsync(Discord, response, Id, Token, options).ConfigureAwait(false); + + lock (_lock) + { + HasResponded = true; + } + } + + /// + public override async Task FollowupAsync( + string text = null, + Embed[] embeds = null, + bool isTTS = false, + bool ephemeral = false, + AllowedMentions allowedMentions = null, + RequestOptions options = null, + MessageComponent component = null, + Embed embed = null) + { + if (!IsValidToken) + throw new InvalidOperationException("Interaction token is no longer valid"); + + embeds ??= Array.Empty(); + if (embed != null) + embeds = new[] { embed }.Concat(embeds).ToArray(); + + Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); + Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); + Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed."); + + var args = new API.Rest.CreateWebhookMessageParams + { + Content = text, + AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, + IsTTS = isTTS, + Embeds = embeds.Select(x => x.ToModel()).ToArray(), + Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified + }; + + if (ephemeral) + args.Flags = MessageFlags.Ephemeral; + + return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options); + } + + /// + public override async Task FollowupWithFileAsync( + Stream fileStream, + string fileName, + string text = null, + Embed[] embeds = null, + bool isTTS = false, + bool ephemeral = false, + AllowedMentions allowedMentions = null, + RequestOptions options = null, + MessageComponent component = null, + Embed embed = null) + { + if (!IsValidToken) + throw new InvalidOperationException("Interaction token is no longer valid"); + + embeds ??= Array.Empty(); + if (embed != null) + embeds = new[] { embed }.Concat(embeds).ToArray(); + + Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); + Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); + Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed."); + Preconditions.NotNull(fileStream, nameof(fileStream), "File Stream must have data"); + Preconditions.NotNullOrEmpty(fileName, nameof(fileName), "File Name must not be empty or null"); + + var args = new API.Rest.CreateWebhookMessageParams + { + Content = text, + AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, + IsTTS = isTTS, + Embeds = embeds.Select(x => x.ToModel()).ToArray(), + Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified, + File = fileStream is not null ? new MultipartFile(fileStream, fileName) : Optional.Unspecified + }; + + if (ephemeral) + args.Flags = MessageFlags.Ephemeral; + + return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options); + } + + /// + public override async Task FollowupWithFileAsync( + string filePath, + string text = null, + string fileName = null, + Embed[] embeds = null, + bool isTTS = false, + bool ephemeral = false, + AllowedMentions allowedMentions = null, + RequestOptions options = null, + MessageComponent component = null, + Embed embed = null) + { + if (!IsValidToken) + throw new InvalidOperationException("Interaction token is no longer valid"); + + embeds ??= Array.Empty(); + if (embed != null) + embeds = new[] { embed }.Concat(embeds).ToArray(); + + Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); + Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); + Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed."); + Preconditions.NotNullOrEmpty(filePath, nameof(filePath), "Path must exist"); + + fileName ??= Path.GetFileName(filePath); + Preconditions.NotNullOrEmpty(fileName, nameof(fileName), "File Name must not be empty or null"); + + var args = new API.Rest.CreateWebhookMessageParams + { + Content = text, + AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, + IsTTS = isTTS, + Embeds = embeds.Select(x => x.ToModel()).ToArray(), + Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified, + File = !string.IsNullOrEmpty(filePath) ? new MultipartFile(new MemoryStream(File.ReadAllBytes(filePath), false), fileName) : Optional.Unspecified + }; + + if (ephemeral) + args.Flags = MessageFlags.Ephemeral; + + return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options); + } + + /// + /// Acknowledges this interaction with the . + /// + /// + /// A task that represents the asynchronous operation of acknowledging the interaction. + /// + public override async Task DeferAsync(bool ephemeral = false, RequestOptions options = null) + { + if (!InteractionHelper.CanSendResponse(this)) + throw new TimeoutException($"Cannot defer an interaction after {InteractionHelper.ResponseTimeLimit} seconds!"); + + var response = new API.InteractionResponse + { + Type = InteractionResponseType.DeferredChannelMessageWithSource, + Data = new API.InteractionCallbackData + { + Flags = ephemeral ? MessageFlags.Ephemeral : Optional.Unspecified + } + }; + + lock (_lock) + { + if (HasResponded) + { + throw new InvalidOperationException("Cannot respond or defer twice to the same interaction"); + } + } + + await Discord.Rest.ApiClient.CreateInteractionResponseAsync(response, Id, Token, options).ConfigureAwait(false); + + lock (_lock) + { + HasResponded = true; + } + } + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBaseData.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBaseData.cs new file mode 100644 index 000000000..cb2f01f5f --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBaseData.cs @@ -0,0 +1,58 @@ +using System.Collections.Generic; +using Model = Discord.API.ApplicationCommandInteractionData; + +namespace Discord.WebSocket +{ + /// + /// Represents the base data tied with the interaction. + /// + public class SocketCommandBaseData : SocketEntity, IApplicationCommandInteractionData where TOption : IApplicationCommandInteractionDataOption + { + /// + public string Name { get; private set; } + + /// + /// The received with this interaction. + /// + public virtual IReadOnlyCollection Options { get; internal set; } + + internal readonly SocketResolvableData ResolvableData; + + private ApplicationCommandType Type { get; set; } + + internal SocketCommandBaseData(DiscordSocketClient client, Model model, ulong? guildId) + : base(client, model.Id) + { + Type = model.Type; + + if (model.Resolved.IsSpecified) + { + ResolvableData = new SocketResolvableData(client, guildId, model); + } + } + + internal static SocketCommandBaseData Create(DiscordSocketClient client, Model model, ulong id, ulong? guildId) + { + var entity = new SocketCommandBaseData(client, model, guildId); + entity.Update(model); + return entity; + } + + internal virtual void Update(Model model) + { + Name = model.Name; + } + + IReadOnlyCollection IApplicationCommandInteractionData.Options + => (IReadOnlyCollection)Options; + } + + /// + /// Represents the base data tied with the interaction. + /// + public class SocketCommandBaseData : SocketCommandBaseData + { + internal SocketCommandBaseData(DiscordSocketClient client, Model model, ulong? guildId) + : base(client, model, guildId) { } + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketResolvableData.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketResolvableData.cs new file mode 100644 index 000000000..c065637ca --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketResolvableData.cs @@ -0,0 +1,109 @@ +using System.Collections.Generic; + +namespace Discord.WebSocket +{ + internal class SocketResolvableData where T : API.IResolvable + { + internal readonly Dictionary GuildMembers + = new Dictionary(); + internal readonly Dictionary Users + = new Dictionary(); + internal readonly Dictionary Channels + = new Dictionary(); + internal readonly Dictionary Roles + = new Dictionary(); + + internal readonly Dictionary Messages + = new Dictionary(); + + internal SocketResolvableData(DiscordSocketClient discord, ulong? guildId, T model) + { + var guild = guildId.HasValue ? discord.GetGuild(guildId.Value) : null; + + var resolved = model.Resolved.Value; + + if (resolved.Users.IsSpecified) + { + foreach (var user in resolved.Users.Value) + { + var socketUser = discord.GetOrCreateUser(discord.State, user.Value); + + Users.Add(ulong.Parse(user.Key), socketUser); + } + } + + if (resolved.Channels.IsSpecified) + { + foreach (var channel in resolved.Channels.Value) + { + SocketChannel socketChannel = guild != null + ? guild.GetChannel(channel.Value.Id) + : discord.GetChannel(channel.Value.Id); + + if (socketChannel == null) + { + var channelModel = guild != null + ? discord.Rest.ApiClient.GetChannelAsync(guild.Id, channel.Value.Id).ConfigureAwait(false).GetAwaiter().GetResult() + : discord.Rest.ApiClient.GetChannelAsync(channel.Value.Id).ConfigureAwait(false).GetAwaiter().GetResult(); + + socketChannel = guild != null + ? SocketGuildChannel.Create(guild, discord.State, channelModel) + : (SocketChannel)SocketChannel.CreatePrivate(discord, discord.State, channelModel); + } + + discord.State.AddChannel(socketChannel); + Channels.Add(ulong.Parse(channel.Key), socketChannel); + } + } + + if (resolved.Members.IsSpecified) + { + foreach (var member in resolved.Members.Value) + { + member.Value.User = resolved.Users.Value[member.Key]; + var user = guild.AddOrUpdateUser(member.Value); + GuildMembers.Add(ulong.Parse(member.Key), user); + } + } + + if (resolved.Roles.IsSpecified) + { + foreach (var role in resolved.Roles.Value) + { + var socketRole = guild.AddOrUpdateRole(role.Value); + Roles.Add(ulong.Parse(role.Key), socketRole); + } + } + + if (resolved.Messages.IsSpecified) + { + foreach (var msg in resolved.Messages.Value) + { + var channel = discord.GetChannel(msg.Value.ChannelId) as ISocketMessageChannel; + + SocketUser author; + if (guild != null) + { + if (msg.Value.WebhookId.IsSpecified) + author = SocketWebhookUser.Create(guild, discord.State, msg.Value.Author.Value, msg.Value.WebhookId.Value); + else + author = guild.GetUser(msg.Value.Author.Value.Id); + } + else + author = (channel as SocketChannel).GetUser(msg.Value.Author.Value.Id); + + if (channel == null) + { + if (!msg.Value.GuildId.IsSpecified) // assume it is a DM + { + channel = discord.CreateDMChannel(msg.Value.ChannelId, msg.Value.Author.Value, discord.State); + } + } + + var message = SocketMessage.Create(discord, discord.State, author, channel, msg.Value); + Messages.Add(message.Id, message); + } + } + } + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs new file mode 100644 index 000000000..f0465d336 --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs @@ -0,0 +1,243 @@ +using Discord.Rest; +using System; +using System.Threading.Tasks; +using Model = Discord.API.Interaction; +using DataModel = Discord.API.ApplicationCommandInteractionData; +using System.IO; + +namespace Discord.WebSocket +{ + /// + /// Represents an Interaction received over the gateway. + /// + public abstract class SocketInteraction : SocketEntity, IDiscordInteraction + { + #region SocketInteraction + /// + /// The this interaction was used in. + /// + public ISocketMessageChannel Channel { get; private set; } + + /// + /// The who triggered this interaction. + /// + public SocketUser User { get; private set; } + + /// + /// The type of this interaction. + /// + public InteractionType Type { get; private set; } + + /// + /// The token used to respond to this interaction. + /// + public string Token { get; private set; } + + /// + /// The data sent with this interaction. + /// + public IDiscordInteractionData Data { get; private set; } + + /// + /// The version of this interaction. + /// + public int Version { get; private set; } + + /// + public DateTimeOffset CreatedAt { get; private set; } + + /// + /// Gets whether or not this interaction has been responded to. + /// + /// + /// This property is locally set -- if you're running multiple bots + /// off the same token then this property won't be in sync with them. + /// + public abstract bool HasResponded { get; internal set; } + + /// + /// if the token is valid for replying to, otherwise . + /// + public bool IsValidToken + => InteractionHelper.CanRespondOrFollowup(this); + + internal SocketInteraction(DiscordSocketClient client, ulong id, ISocketMessageChannel channel) + : base(client, id) + { + Channel = channel; + + CreatedAt = client.UseInteractionSnowflakeDate + ? SnowflakeUtils.FromSnowflake(Id) + : DateTime.UtcNow; + } + + internal static SocketInteraction Create(DiscordSocketClient client, Model model, ISocketMessageChannel channel) + { + if (model.Type == InteractionType.ApplicationCommand) + { + var dataModel = model.Data.IsSpecified + ? (DataModel)model.Data.Value + : null; + + if (dataModel == null) + return null; + + return dataModel.Type switch + { + ApplicationCommandType.Slash => SocketSlashCommand.Create(client, model, channel), + ApplicationCommandType.Message => SocketMessageCommand.Create(client, model, channel), + ApplicationCommandType.User => SocketUserCommand.Create(client, model, channel), + _ => null + }; + } + + if (model.Type == InteractionType.MessageComponent) + return SocketMessageComponent.Create(client, model, channel); + + if (model.Type == InteractionType.ApplicationCommandAutocomplete) + return SocketAutocompleteInteraction.Create(client, model, channel); + + return null; + } + + internal virtual void Update(Model model) + { + Data = model.Data.IsSpecified + ? model.Data.Value + : null; + Token = model.Token; + Version = model.Version; + Type = model.Type; + + if (User == null) + { + if (model.Member.IsSpecified && model.GuildId.IsSpecified) + { + User = SocketGuildUser.Create(Discord.State.GetGuild(model.GuildId.Value), Discord.State, model.Member.Value); + } + else + { + User = SocketGlobalUser.Create(Discord, Discord.State, model.User.Value); + } + } + } + + /// + /// Responds to an Interaction with type . + /// + /// The text of the message to be sent. + /// A array of embeds to send with this response. Max 10. + /// if the message should be read out by a text-to-speech reader, otherwise . + /// if the response should be hidden to everyone besides the invoker of the command, otherwise . + /// The allowed mentions for this response. + /// The request options for this response. + /// A to be sent with this response. + /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. + /// Message content is too long, length must be less or equal to . + /// The parameters provided were invalid or the token was invalid. + public abstract Task RespondAsync(string text = null, Embed[] embeds = null, bool isTTS = false, + bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null); + + /// + /// Sends a followup message for this interaction. + /// + /// The text of the message to be sent. + /// A array of embeds to send with this response. Max 10. + /// if the message should be read out by a text-to-speech reader, otherwise . + /// if the response should be hidden to everyone besides the invoker of the command, otherwise . + /// The allowed mentions for this response. + /// The request options for this response. + /// A to be sent with this response. + /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. + /// + /// The sent message. + /// + public abstract Task FollowupAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, + AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null); + + /// + /// Sends a followup message for this interaction. + /// + /// The text of the message to be sent. + /// The file to upload. + /// The file name of the attachment. + /// A array of embeds to send with this response. Max 10. + /// if the message should be read out by a text-to-speech reader, otherwise . + /// if the response should be hidden to everyone besides the invoker of the command, otherwise . + /// The allowed mentions for this response. + /// The request options for this response. + /// A to be sent with this response. + /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. + /// + /// The sent message. + /// + public abstract Task FollowupWithFileAsync(Stream fileStream, string fileName, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, + AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null); + + /// + /// Sends a followup message for this interaction. + /// + /// The text of the message to be sent. + /// The file to upload. + /// The file name of the attachment. + /// A array of embeds to send with this response. Max 10. + /// if the message should be read out by a text-to-speech reader, otherwise . + /// if the response should be hidden to everyone besides the invoker of the command, otherwise . + /// The allowed mentions for this response. + /// The request options for this response. + /// A to be sent with this response. + /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. + /// + /// The sent message. + /// + public abstract Task FollowupWithFileAsync(string filePath, string text = null, string fileName = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, + AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null); + + /// + /// Gets the original response for this interaction. + /// + /// The request options for this request. + /// A that represents the initial response. + public Task GetOriginalResponseAsync(RequestOptions options = null) + => InteractionHelper.GetOriginalResponseAsync(Discord, Channel, this, options); + + /// + /// Edits original response for this interaction. + /// + /// A delegate containing the properties to modify the message with. + /// The request options for this request. + /// A that represents the initial response. + public async Task ModifyOriginalResponseAsync(Action func, RequestOptions options = null) + { + var model = await InteractionHelper.ModifyInteractionResponseAsync(Discord, Token, func, options); + return RestInteractionMessage.Create(Discord, model, Token, Channel); + } + + /// + /// Acknowledges this interaction. + /// + /// to send this message ephemerally, otherwise . + /// The request options for this request. + /// + /// A task that represents the asynchronous operation of acknowledging the interaction. + /// + public abstract Task DeferAsync(bool ephemeral = false, RequestOptions options = null); + + #endregion + + #region IDiscordInteraction + /// + async Task IDiscordInteraction.FollowupAsync(string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, + RequestOptions options, MessageComponent component, Embed embed) + => await FollowupAsync(text, embeds, isTTS, ephemeral, allowedMentions, options, component, embed).ConfigureAwait(false); + + /// + async Task IDiscordInteraction.GetOriginalResponseAsync(RequestOptions options) + => await GetOriginalResponseAsync(options).ConfigureAwait(false); + + /// + async Task IDiscordInteraction.ModifyOriginalResponseAsync(Action func, RequestOptions options) + => await ModifyOriginalResponseAsync(func, options).ConfigureAwait(false); + #endregion + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Invites/SocketInvite.cs b/src/Discord.Net.WebSocket/Entities/Invites/SocketInvite.cs index 845b48b8b..2b64e170e 100644 --- a/src/Discord.Net.WebSocket/Entities/Invites/SocketInvite.cs +++ b/src/Discord.Net.WebSocket/Entities/Invites/SocketInvite.cs @@ -6,6 +6,9 @@ using Model = Discord.API.Gateway.InviteCreateEvent; namespace Discord.WebSocket { + /// + /// Represents a WebSocket-based invite to a guild. + /// [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class SocketInvite : SocketEntity, IInviteMetadata { @@ -28,16 +31,16 @@ namespace Discord.WebSocket { get { - switch (Channel) + return Channel switch { - case IVoiceChannel voiceChannel: return ChannelType.Voice; - case ICategoryChannel categoryChannel: return ChannelType.Category; - case IDMChannel dmChannel: return ChannelType.DM; - case IGroupChannel groupChannel: return ChannelType.Group; - case INewsChannel newsChannel: return ChannelType.News; - case ITextChannel textChannel: return ChannelType.Text; - default: throw new InvalidOperationException("Invalid channel type."); - } + IVoiceChannel voiceChannel => ChannelType.Voice, + ICategoryChannel categoryChannel => ChannelType.Category, + IDMChannel dmChannel => ChannelType.DM, + IGroupChannel groupChannel => ChannelType.Group, + INewsChannel newsChannel => ChannelType.News, + ITextChannel textChannel => ChannelType.Text, + _ => throw new InvalidOperationException("Invalid channel type."), + }; } } /// diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs index 353c26fb8..4be9f4c5a 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs @@ -1,4 +1,5 @@ using Discord.Rest; +using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -13,8 +14,10 @@ namespace Discord.WebSocket /// public abstract class SocketMessage : SocketEntity, IMessage { + #region SocketMessage private long _timestampTicks; private readonly List _reactions = new List(); + private ImmutableArray _userMentions = ImmutableArray.Create(); /// /// Gets the author of this message. @@ -37,6 +40,9 @@ namespace Discord.WebSocket public string Content { get; private set; } /// + public string CleanContent => MessageHelper.SanitizeMessage(this); + + /// public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); /// public virtual bool IsTTS => false; @@ -58,6 +64,14 @@ namespace Discord.WebSocket /// public MessageReference Reference { get; private set; } + /// + public IReadOnlyCollection Components { get; private set; } + + /// + /// Gets the interaction this message is a response to. + /// + public MessageInteraction Interaction { get; private set; } + /// public MessageFlags? Flags { get; private set; } @@ -92,20 +106,19 @@ namespace Discord.WebSocket /// Collection of WebSocket-based roles. /// public virtual IReadOnlyCollection MentionedRoles => ImmutableArray.Create(); + /// + public virtual IReadOnlyCollection Tags => ImmutableArray.Create(); + /// + public virtual IReadOnlyCollection Stickers => ImmutableArray.Create(); + /// + public IReadOnlyDictionary Reactions => _reactions.GroupBy(r => r.Emote).ToDictionary(x => x.Key, x => new ReactionMetadata { ReactionCount = x.Count(), IsMe = x.Any(y => y.UserId == Discord.CurrentUser.Id) }); /// /// Returns the users mentioned in this message. /// /// /// Collection of WebSocket-based users. /// - public virtual IReadOnlyCollection MentionedUsers => ImmutableArray.Create(); - /// - public virtual IReadOnlyCollection Tags => ImmutableArray.Create(); - /// - public virtual IReadOnlyCollection Stickers => ImmutableArray.Create(); - /// - public IReadOnlyDictionary Reactions => _reactions.GroupBy(r => r.Emote).ToDictionary(x => x.Key, x => new ReactionMetadata { ReactionCount = x.Count(), IsMe = x.Any(y => y.UserId == Discord.CurrentUser.Id) }); - + public IReadOnlyCollection MentionedUsers => _userMentions; /// public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks); @@ -118,7 +131,10 @@ namespace Discord.WebSocket } internal static SocketMessage Create(DiscordSocketClient discord, ClientState state, SocketUser author, ISocketMessageChannel channel, Model model) { - if (model.Type == MessageType.Default || model.Type == MessageType.Reply) + if (model.Type == MessageType.Default || + model.Type == MessageType.Reply || + model.Type == MessageType.ApplicationCommand || + model.Type == MessageType.ThreadStarterMessage) return SocketUserMessage.Create(discord, state, author, channel, model); else return SocketSystemMessage.Create(discord, state, author, channel, model); @@ -131,7 +147,9 @@ namespace Discord.WebSocket _timestampTicks = model.Timestamp.Value.UtcTicks; if (model.Content.IsSpecified) + { Content = model.Content.Value; + } if (model.Application.IsSpecified) { @@ -167,6 +185,86 @@ namespace Discord.WebSocket }; } + if (model.Components.IsSpecified) + { + Components = model.Components.Value.Select(x => new ActionRowComponent(x.Components.Select(y => + { + switch (y.Type) + { + case ComponentType.Button: + { + var parsed = (API.ButtonComponent)y; + return new Discord.ButtonComponent( + parsed.Style, + parsed.Label.GetValueOrDefault(), + parsed.Emote.IsSpecified + ? parsed.Emote.Value.Id.HasValue + ? new Emote(parsed.Emote.Value.Id.Value, parsed.Emote.Value.Name, parsed.Emote.Value.Animated.GetValueOrDefault()) + : new Emoji(parsed.Emote.Value.Name) + : null, + parsed.CustomId.GetValueOrDefault(), + parsed.Url.GetValueOrDefault(), + parsed.Disabled.GetValueOrDefault()); + } + case ComponentType.SelectMenu: + { + var parsed = (API.SelectMenuComponent)y; + return new SelectMenuComponent( + parsed.CustomId, + parsed.Options.Select(z => new SelectMenuOption( + z.Label, + z.Value, + z.Description.GetValueOrDefault(), + z.Emoji.IsSpecified + ? z.Emoji.Value.Id.HasValue + ? new Emote(z.Emoji.Value.Id.Value, z.Emoji.Value.Name, z.Emoji.Value.Animated.GetValueOrDefault()) + : new Emoji(z.Emoji.Value.Name) + : null, + z.Default.ToNullable())).ToList(), + parsed.Placeholder.GetValueOrDefault(), + parsed.MinValues, + parsed.MaxValues, + parsed.Disabled + ); + } + default: + return null; + } + }).ToList())).ToImmutableArray(); + } + else + Components = new List(); + + if (model.UserMentions.IsSpecified) + { + var value = model.UserMentions.Value; + if (value.Length > 0) + { + var newMentions = ImmutableArray.CreateBuilder(value.Length); + for (int i = 0; i < value.Length; i++) + { + var val = value[i]; + if (val != null) + { + var user = Channel.GetUserAsync(val.Id, CacheMode.CacheOnly).GetAwaiter().GetResult() as SocketUser; + if (user != null) + newMentions.Add(user); + else + newMentions.Add(SocketUnknownUser.Create(Discord, state, val)); + } + } + _userMentions = newMentions.ToImmutable(); + } + } + + if (model.Interaction.IsSpecified) + { + Interaction = new MessageInteraction(model.Interaction.Value.Id, + model.Interaction.Value.Type, + model.Interaction.Value.Name, + SocketGlobalUser.Create(Discord, state, model.Interaction.Value.User)); + } + if (model.Flags.IsSpecified) Flags = model.Flags.Value; } @@ -183,8 +281,9 @@ namespace Discord.WebSocket /// public override string ToString() => Content; internal SocketMessage Clone() => MemberwiseClone() as SocketMessage; +#endregion - //IMessage + #region IMessage /// IUser IMessage.Author => Author; /// @@ -199,8 +298,16 @@ namespace Discord.WebSocket IReadOnlyCollection IMessage.MentionedRoleIds => MentionedRoles.Select(x => x.Id).ToImmutableArray(); /// IReadOnlyCollection IMessage.MentionedUserIds => MentionedUsers.Select(x => x.Id).ToImmutableArray(); + + /// + IReadOnlyCollection IMessage.Components => Components; + + /// + IMessageInteraction IMessage.Interaction => Interaction; + /// - IReadOnlyCollection IMessage.Stickers => Stickers; + IReadOnlyCollection IMessage.Stickers => Stickers; + internal void AddReaction(SocketReaction reaction) { @@ -238,5 +345,6 @@ namespace Discord.WebSocket /// public IAsyncEnumerable> GetReactionUsersAsync(IEmote emote, int limit, RequestOptions options = null) => MessageHelper.GetReactionUsersAsync(this, emote, limit, Discord, options); + #endregion } } diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs index 597544f4d..e5776a089 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs @@ -22,8 +22,7 @@ namespace Discord.WebSocket private ImmutableArray _embeds = ImmutableArray.Create(); private ImmutableArray _tags = ImmutableArray.Create(); private ImmutableArray _roleMentions = ImmutableArray.Create(); - private ImmutableArray _userMentions = ImmutableArray.Create(); - private ImmutableArray _stickers = ImmutableArray.Create(); + private ImmutableArray _stickers = ImmutableArray.Create(); /// public override bool IsTTS => _isTTS; @@ -46,9 +45,7 @@ namespace Discord.WebSocket /// public override IReadOnlyCollection MentionedRoles => _roleMentions; /// - public override IReadOnlyCollection MentionedUsers => _userMentions; - /// - public override IReadOnlyCollection Stickers => _stickers; + public override IReadOnlyCollection Stickers => _stickers; /// public IUserMessage ReferencedMessage => _referencedMessage; @@ -108,32 +105,10 @@ namespace Discord.WebSocket _embeds = ImmutableArray.Create(); } - if (model.UserMentions.IsSpecified) - { - var value = model.UserMentions.Value; - if (value.Length > 0) - { - var newMentions = ImmutableArray.CreateBuilder(value.Length); - for (int i = 0; i < value.Length; i++) - { - var val = value[i]; - if (val.Object != null) - { - var user = Channel.GetUserAsync(val.Object.Id, CacheMode.CacheOnly).GetAwaiter().GetResult() as SocketUser; - if (user != null) - newMentions.Add(user); - else - newMentions.Add(SocketUnknownUser.Create(Discord, state, val.Object)); - } - } - _userMentions = newMentions.ToImmutable(); - } - } - if (model.Content.IsSpecified) { var text = model.Content.Value; - _tags = MessageHelper.ParseTags(text, Channel, guild, _userMentions); + _tags = MessageHelper.ParseTags(text, Channel, guild, MentionedUsers); model.Content = text; } @@ -162,18 +137,40 @@ namespace Discord.WebSocket _referencedMessage = SocketUserMessage.Create(Discord, state, refMsgAuthor, Channel, refMsg); } - if (model.Stickers.IsSpecified) + if (model.StickerItems.IsSpecified) { - var value = model.Stickers.Value; + var value = model.StickerItems.Value; if (value.Length > 0) { - var stickers = ImmutableArray.CreateBuilder(value.Length); + var stickers = ImmutableArray.CreateBuilder(value.Length); for (int i = 0; i < value.Length; i++) - stickers.Add(Sticker.Create(value[i])); + { + var stickerItem = value[i]; + SocketSticker sticker = null; + + if (guild != null) + sticker = guild.GetSticker(stickerItem.Id); + + if (sticker == null) + sticker = Discord.GetSticker(stickerItem.Id); + + // if they want to auto resolve + if (Discord.AlwaysResolveStickers) + { + sticker = Task.Run(async () => await Discord.GetStickerAsync(stickerItem.Id).ConfigureAwait(false)).GetAwaiter().GetResult(); + } + + // if its still null, create an unknown + if (sticker == null) + sticker = SocketUnknownSticker.Create(Discord, stickerItem); + + stickers.Add(sticker); + } + _stickers = stickers.ToImmutable(); } else - _stickers = ImmutableArray.Create(); + _stickers = ImmutableArray.Create(); } } diff --git a/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs b/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs index e6aac2c04..1e90b8f5c 100644 --- a/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs +++ b/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs @@ -1,6 +1,6 @@ using Discord.Rest; using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; @@ -14,6 +14,7 @@ namespace Discord.WebSocket [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class SocketRole : SocketEntity, IRole { + #region SocketRole /// /// Gets the guild that owns this role. /// @@ -32,6 +33,10 @@ namespace Discord.WebSocket public bool IsMentionable { get; private set; } /// public string Name { get; private set; } + /// + public Emoji Emoji { get; private set; } + /// + public string Icon { get; private set; } /// public GuildPermissions Permissions { get; private set; } /// @@ -50,7 +55,11 @@ namespace Discord.WebSocket public bool IsEveryone => Id == Guild.Id; /// public string Mention => IsEveryone ? "@everyone" : MentionUtils.MentionRole(Id); - public IEnumerable Members + + /// + /// Returns an IEnumerable containing all that have this role. + /// + public IEnumerable Members => Guild.Users.Where(x => x.Roles.Any(r => r.Id == Id)); internal SocketRole(SocketGuild guild, ulong id) @@ -75,6 +84,16 @@ namespace Discord.WebSocket Permissions = new GuildPermissions(model.Permissions); if (model.Tags.IsSpecified) Tags = model.Tags.Value.ToEntity(); + + if (model.Icon.IsSpecified) + { + Icon = model.Icon.Value; + } + + if (model.Emoji.IsSpecified) + { + Emoji = new Emoji(model.Emoji.Value); + } } /// @@ -84,6 +103,10 @@ namespace Discord.WebSocket public Task DeleteAsync(RequestOptions options = null) => RoleHelper.DeleteAsync(this, Discord, options); + /// + public string GetIconUrl() + => CDN.GetGuildRoleIconUrl(Id, Icon); + /// /// Gets the name of the role. /// @@ -96,9 +119,11 @@ namespace Discord.WebSocket /// public int CompareTo(IRole role) => RoleUtils.Compare(this, role); + #endregion - //IRole + #region IRole /// IGuild IRole.Guild => Guild; + #endregion } } diff --git a/src/Discord.Net.WebSocket/Entities/Stickers/SocketCustomSticker.cs b/src/Discord.Net.WebSocket/Entities/Stickers/SocketCustomSticker.cs new file mode 100644 index 000000000..6a5104012 --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Stickers/SocketCustomSticker.cs @@ -0,0 +1,81 @@ +using Discord.Rest; +using System; +using System.Diagnostics; +using System.Threading.Tasks; +using Model = Discord.API.Sticker; + +namespace Discord.WebSocket +{ + /// + /// Represents a custom sticker within a guild received over the gateway. + /// + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public class SocketCustomSticker : SocketSticker, ICustomSticker + { + #region SocketCustomSticker + /// + /// Gets the user that uploaded the guild sticker. + /// + /// + /// + /// This may return in the WebSocket implementation due to incomplete user collection in + /// large guilds, or the bot doesn't have the MANAGE_EMOJIS_AND_STICKERS permission. + /// + /// + public SocketGuildUser Author + => AuthorId.HasValue ? Guild.GetUser(AuthorId.Value) : null; + + /// + /// Gets the guild the sticker was created in. + /// + public SocketGuild Guild { get; } + + /// + public ulong? AuthorId { get; set; } + + internal SocketCustomSticker(DiscordSocketClient client, ulong id, SocketGuild guild, ulong? authorId = null) + : base(client, id) + { + Guild = guild; + AuthorId = authorId; + } + + internal static SocketCustomSticker Create(DiscordSocketClient client, Model model, SocketGuild guild, ulong? authorId = null) + { + var entity = new SocketCustomSticker(client, model.Id, guild, authorId); + entity.Update(model); + return entity; + } + + /// + public async Task ModifyAsync(Action func, RequestOptions options = null) + { + if (!Guild.CurrentUser.GuildPermissions.Has(GuildPermission.ManageEmojisAndStickers)) + throw new InvalidOperationException($"Missing permission {nameof(GuildPermission.ManageEmojisAndStickers)}"); + + var model = await GuildHelper.ModifyStickerAsync(Discord, Guild.Id, this, func, options); + + Update(model); + } + + /// + public async Task DeleteAsync(RequestOptions options = null) + { + await GuildHelper.DeleteStickerAsync(Discord, Guild.Id, this, options); + Guild.RemoveSticker(Id); + } + + internal SocketCustomSticker Clone() => MemberwiseClone() as SocketCustomSticker; + + private new string DebuggerDisplay => Guild == null ? base.DebuggerDisplay : $"{Name} in {Guild.Name} ({Id})"; + #endregion + + #region ICustomSticker + ulong? ICustomSticker.AuthorId + => AuthorId; + + IGuild ICustomSticker.Guild + => Guild; + #endregion + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Stickers/SocketSticker.cs b/src/Discord.Net.WebSocket/Entities/Stickers/SocketSticker.cs new file mode 100644 index 000000000..ee45720b5 --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Stickers/SocketSticker.cs @@ -0,0 +1,92 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using Model = Discord.API.Sticker; + +namespace Discord.WebSocket +{ + /// + /// Represents a general sticker received over the gateway. + /// + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public class SocketSticker : SocketEntity, ISticker + { + /// + public virtual ulong PackId { get; private set; } + + /// + public string Name { get; protected set; } + + /// + public virtual string Description { get; private set; } + + /// + public virtual IReadOnlyCollection Tags { get; private set; } + + /// + public virtual StickerType Type { get; private set; } + + /// + public StickerFormatType Format { get; protected set; } + + /// + public virtual bool? IsAvailable { get; protected set; } + + /// + public virtual int? SortOrder { get; private set; } + + /// + public string GetStickerUrl() + => CDN.GetStickerUrl(Id, Format); + + internal SocketSticker(DiscordSocketClient client, ulong id) + : base(client, id) { } + + internal static SocketSticker Create(DiscordSocketClient client, Model model) + { + var entity = model.GuildId.IsSpecified + ? new SocketCustomSticker(client, model.Id, client.GetGuild(model.GuildId.Value), model.User.IsSpecified ? model.User.Value.Id : null) + : new SocketSticker(client, model.Id); + + entity.Update(model); + return entity; + } + + internal virtual void Update(Model model) + { + Name = model.Name; + Description = model.Description; + PackId = model.PackId; + IsAvailable = model.Available; + Format = model.FormatType; + Type = model.Type; + SortOrder = model.SortValue; + + Tags = model.Tags.IsSpecified + ? model.Tags.Value.Split(',').Select(x => x.Trim()).ToImmutableArray() + : ImmutableArray.Create(); + } + + internal string DebuggerDisplay => $"{Name} ({Id})"; + + /// + public override bool Equals(object obj) + { + if (obj is Model stickerModel) + { + return stickerModel.Name == Name && + stickerModel.Description == Description && + stickerModel.FormatType == Format && + stickerModel.Id == Id && + stickerModel.PackId == PackId && + stickerModel.Type == Type && + stickerModel.SortValue == SortOrder && + stickerModel.Available == IsAvailable && + (!stickerModel.Tags.IsSpecified || stickerModel.Tags.Value == string.Join(", ", Tags)); + } + + return base.Equals(obj); + } + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Stickers/SocketUnknownSticker.cs b/src/Discord.Net.WebSocket/Entities/Stickers/SocketUnknownSticker.cs new file mode 100644 index 000000000..ca7d2d0f1 --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Stickers/SocketUnknownSticker.cs @@ -0,0 +1,64 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading.Tasks; +using Model = Discord.API.StickerItem; + +namespace Discord.WebSocket +{ + /// + /// Represents an unknown sticker received over the gateway. + /// + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public class SocketUnknownSticker : SocketSticker + { + /// + public override IReadOnlyCollection Tags + => null; + + /// + public override string Description + => null; + + /// + public override ulong PackId + => 0; + /// + public override bool? IsAvailable + => null; + + /// + public override int? SortOrder + => null; + + /// + public new StickerType? Type + => null; + + internal SocketUnknownSticker(DiscordSocketClient client, ulong id) + : base(client, id) { } + + internal static SocketUnknownSticker Create(DiscordSocketClient client, Model model) + { + var entity = new SocketUnknownSticker(client, model.Id); + entity.Update(model); + return entity; + } + + internal void Update(Model model) + { + Name = model.Name; + Format = model.FormatType; + } + + /// + /// Attempts to try to find the sticker. + /// + /// + /// The sticker representing this unknown stickers Id, if none is found then . + /// + public Task ResolveAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null) + => Discord.GetStickerAsync(Id, mode, options); + + private new string DebuggerDisplay => $"{Name} ({Id})"; + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs index 15c5182fc..3a1ad23b6 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs @@ -47,7 +47,7 @@ namespace Discord.WebSocket discord.RemoveUser(Id); } } - + internal void Update(ClientState state, PresenceModel model) { Presence = SocketPresence.Create(model); diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs index 676c0a86c..fe19a41ec 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs @@ -1,11 +1,16 @@ +using System; using System.Diagnostics; using Model = Discord.API.User; namespace Discord.WebSocket { + /// + /// Represents a WebSocket-based group user. + /// [DebuggerDisplay("{DebuggerDisplay,nq}")] public class SocketGroupUser : SocketUser, IGroupUser { + #region SocketGroupUser /// /// Gets the group channel of the user. /// @@ -45,8 +50,9 @@ namespace Discord.WebSocket private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")}, Group)"; internal new SocketGroupUser Clone() => MemberwiseClone() as SocketGroupUser; + #endregion - //IVoiceState + #region IVoiceState /// bool IVoiceState.IsDeafened => false; /// @@ -63,5 +69,8 @@ namespace Discord.WebSocket string IVoiceState.VoiceSessionId => null; /// bool IVoiceState.IsStreaming => false; + /// + DateTimeOffset? IVoiceState.RequestToSpeakTimestamp => null; + #endregion } } diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs index 444c76ffa..147456cb0 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs @@ -18,6 +18,7 @@ namespace Discord.WebSocket [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class SocketGuildUser : SocketUser, IGuildUser { + #region SocketGuildUser private long? _premiumSinceTicks; private long? _joinedAtTicks; private ImmutableArray _roleIds; @@ -29,7 +30,8 @@ namespace Discord.WebSocket public SocketGuild Guild { get; } /// public string Nickname { get; private set; } - + /// + public string GuildAvatarId { get; private set; } /// public override bool IsBot { get { return GlobalUser.IsBot; } internal set { GlobalUser.IsBot = value; } } /// @@ -38,6 +40,7 @@ namespace Discord.WebSocket public override ushort DiscriminatorValue { get { return GlobalUser.DiscriminatorValue; } internal set { GlobalUser.DiscriminatorValue = value; } } /// public override string AvatarId { get { return GlobalUser.AvatarId; } internal set { GlobalUser.AvatarId = value; } } + /// public GuildPermissions GuildPermissions => new GuildPermissions(Permissions.ResolveGuild(Guild, this)); internal override SocketPresence Presence { get; set; } @@ -57,7 +60,11 @@ namespace Discord.WebSocket /// public bool IsStreaming => VoiceState?.IsStreaming ?? false; /// + public DateTimeOffset? RequestToSpeakTimestamp => VoiceState?.RequestToSpeakTimestamp ?? null; + /// public bool? IsPending { get; private set; } + + /// public DateTimeOffset? JoinedAt => DateTimeUtils.FromTicks(_joinedAtTicks); /// @@ -87,7 +94,7 @@ namespace Discord.WebSocket /// Returns the position of the user within the role hierarchy. /// /// - /// The returned value equal to the position of the highest role the user has, or + /// The returned value equal to the position of the highest role the user has, or /// if user is the server owner. /// public int Hierarchy @@ -144,6 +151,8 @@ namespace Discord.WebSocket _joinedAtTicks = model.JoinedAt.Value.UtcTicks; if (model.Nick.IsSpecified) Nickname = model.Nick.Value; + if (model.Avatar.IsSpecified) + GuildAvatarId = model.Avatar.Value; if (model.Roles.IsSpecified) UpdateRoles(model.Roles.Value); if (model.PremiumSince.IsSpecified) @@ -208,11 +217,14 @@ namespace Discord.WebSocket /// public ChannelPermissions GetPermissions(IGuildChannel channel) => new ChannelPermissions(Permissions.ResolveChannel(Guild, this, channel, GuildPermissions.RawValue)); + public string GetGuildAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) + => CDN.GetGuildUserAvatarUrl(Id, Guild.Id, GuildAvatarId, size, format); private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")}, Guild)"; internal new SocketGuildUser Clone() => MemberwiseClone() as SocketGuildUser; + #endregion - //IGuildUser + #region IGuildUser /// IGuild IGuildUser.Guild => Guild; /// @@ -223,5 +235,6 @@ namespace Discord.WebSocket //IVoiceState /// IVoiceChannel IVoiceState.VoiceChannel => VoiceChannel; + #endregion } } diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketThreadUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketThreadUser.cs new file mode 100644 index 000000000..b2311dd7d --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketThreadUser.cs @@ -0,0 +1,209 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Model = Discord.API.ThreadMember; +using System.Collections.Immutable; + +namespace Discord.WebSocket +{ + /// + /// Represents a thread user received over the gateway. + /// + public class SocketThreadUser : SocketUser, IGuildUser + { + /// + /// Gets the this user is in. + /// + public SocketThreadChannel Thread { get; private set; } + + /// + /// Gets the timestamp for when this user joined this thread. + /// + public DateTimeOffset ThreadJoinedAt { get; private set; } + + /// + /// Gets the guild this user is in. + /// + public SocketGuild Guild { get; private set; } + + /// + public DateTimeOffset? JoinedAt + => GuildUser.JoinedAt; + + /// + public string Nickname + => GuildUser.Nickname; + + /// + public DateTimeOffset? PremiumSince + => GuildUser.PremiumSince; + + /// + public bool? IsPending + => GuildUser.IsPending; + /// + public int Hierarchy + => GuildUser.Hierarchy; + + /// + public override string AvatarId + { + get => GuildUser.AvatarId; + internal set => GuildUser.AvatarId = value; + } + /// + public string GuildAvatarId + => GuildUser.GuildAvatarId; + + /// + public override ushort DiscriminatorValue + { + get => GuildUser.DiscriminatorValue; + internal set => GuildUser.DiscriminatorValue = value; + } + + /// + public override bool IsBot + { + get => GuildUser.IsBot; + internal set => GuildUser.IsBot = value; + } + + /// + public override bool IsWebhook + => GuildUser.IsWebhook; + + /// + public override string Username + { + get => GuildUser.Username; + internal set => GuildUser.Username = value; + } + + /// + public bool IsDeafened + => GuildUser.IsDeafened; + + /// + public bool IsMuted + => GuildUser.IsMuted; + + /// + public bool IsSelfDeafened + => GuildUser.IsSelfDeafened; + + /// + public bool IsSelfMuted + => GuildUser.IsSelfMuted; + + /// + public bool IsSuppressed + => GuildUser.IsSuppressed; + + /// + public IVoiceChannel VoiceChannel + => GuildUser.VoiceChannel; + + /// + public string VoiceSessionId + => GuildUser.VoiceSessionId; + + /// + public bool IsStreaming + => GuildUser.IsStreaming; + + /// + public DateTimeOffset? RequestToSpeakTimestamp + => GuildUser.RequestToSpeakTimestamp; + + private SocketGuildUser GuildUser { get; set; } + + internal SocketThreadUser(SocketGuild guild, SocketThreadChannel thread, SocketGuildUser member) + : base(guild.Discord, member.Id) + { + Thread = thread; + Guild = guild; + GuildUser = member; + } + + internal static SocketThreadUser Create(SocketGuild guild, SocketThreadChannel thread, Model model, SocketGuildUser member) + { + var entity = new SocketThreadUser(guild, thread, member); + entity.Update(model); + return entity; + } + + internal void Update(Model model) + { + ThreadJoinedAt = model.JoinTimestamp; + + if (model.Presence.IsSpecified) + { + GuildUser.Update(Discord.State, model.Presence.Value, true); + } + + if (model.Member.IsSpecified) + { + GuildUser.Update(Discord.State, model.Member.Value); + } + } + + /// + public ChannelPermissions GetPermissions(IGuildChannel channel) => GuildUser.GetPermissions(channel); + + /// + public Task KickAsync(string reason = null, RequestOptions options = null) => GuildUser.KickAsync(reason, options); + + /// + public Task ModifyAsync(Action func, RequestOptions options = null) => GuildUser.ModifyAsync(func, options); + + /// + public Task AddRoleAsync(ulong roleId, RequestOptions options = null) => GuildUser.AddRoleAsync(roleId, options); + + /// + public Task AddRoleAsync(IRole role, RequestOptions options = null) => GuildUser.AddRoleAsync(role, options); + + /// + public Task AddRolesAsync(IEnumerable roleIds, RequestOptions options = null) => GuildUser.AddRolesAsync(roleIds, options); + + /// + public Task AddRolesAsync(IEnumerable roles, RequestOptions options = null) => GuildUser.AddRolesAsync(roles, options); + + /// + public Task RemoveRoleAsync(ulong roleId, RequestOptions options = null) => GuildUser.RemoveRoleAsync(roleId, options); + + /// + public Task RemoveRoleAsync(IRole role, RequestOptions options = null) => GuildUser.RemoveRoleAsync(role, options); + + /// + public Task RemoveRolesAsync(IEnumerable roleIds, RequestOptions options = null) => GuildUser.RemoveRolesAsync(roleIds, options); + + /// + public Task RemoveRolesAsync(IEnumerable roles, RequestOptions options = null) => GuildUser.RemoveRolesAsync(roles, options); + + /// + GuildPermissions IGuildUser.GuildPermissions => GuildUser.GuildPermissions; + + /// + IGuild IGuildUser.Guild => Guild; + + /// + ulong IGuildUser.GuildId => Guild.Id; + + /// + IReadOnlyCollection IGuildUser.RoleIds => GuildUser.Roles.Select(x => x.Id).ToImmutableArray(); + + string IGuildUser.GetGuildAvatarUrl(ImageFormat format, ushort size) => GuildUser.GetGuildAvatarUrl(format, size); + + internal override SocketGlobalUser GlobalUser => GuildUser.GlobalUser; + + internal override SocketPresence Presence { get => GuildUser.Presence; set => GuildUser.Presence = value; } + + /// + /// Gets the guild user of this thread user. + /// + /// + public static explicit operator SocketGuildUser(SocketThreadUser user) => user.GuildUser; + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketUnknownUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketUnknownUser.cs index 840a1c30b..a15f7e747 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketUnknownUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketUnknownUser.cs @@ -19,9 +19,10 @@ namespace Discord.WebSocket public override ushort DiscriminatorValue { get; internal set; } /// public override string AvatarId { get; internal set; } + /// public override bool IsBot { get; internal set; } - + /// public override bool IsWebhook => false; /// diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketVoiceState.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketVoiceState.cs index 5bf36e796..816a839fc 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketVoiceState.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketVoiceState.cs @@ -13,7 +13,7 @@ namespace Discord.WebSocket /// /// Initializes a default with everything set to null or false. /// - public static readonly SocketVoiceState Default = new SocketVoiceState(null, null, false, false, false, false, false, false); + public static readonly SocketVoiceState Default = new SocketVoiceState(null, null, null, false, false, false, false, false, false); [Flags] private enum Flags : byte @@ -35,6 +35,8 @@ namespace Discord.WebSocket public SocketVoiceChannel VoiceChannel { get; } /// public string VoiceSessionId { get; } + /// + public DateTimeOffset? RequestToSpeakTimestamp { get; private set; } /// public bool IsMuted => (_voiceStates & Flags.Muted) != 0; @@ -48,11 +50,13 @@ namespace Discord.WebSocket public bool IsSelfDeafened => (_voiceStates & Flags.SelfDeafened) != 0; /// public bool IsStreaming => (_voiceStates & Flags.SelfStream) != 0; + - internal SocketVoiceState(SocketVoiceChannel voiceChannel, string sessionId, bool isSelfMuted, bool isSelfDeafened, bool isMuted, bool isDeafened, bool isSuppressed, bool isStream) + internal SocketVoiceState(SocketVoiceChannel voiceChannel, DateTimeOffset? requestToSpeak, string sessionId, bool isSelfMuted, bool isSelfDeafened, bool isMuted, bool isDeafened, bool isSuppressed, bool isStream) { VoiceChannel = voiceChannel; VoiceSessionId = sessionId; + RequestToSpeakTimestamp = requestToSpeak; Flags voiceStates = Flags.Normal; if (isSelfMuted) @@ -71,7 +75,7 @@ namespace Discord.WebSocket } internal static SocketVoiceState Create(SocketVoiceChannel voiceChannel, Model model) { - return new SocketVoiceState(voiceChannel, model.SessionId, model.SelfMute, model.SelfDeaf, model.Mute, model.Deaf, model.Suppress, model.SelfStream); + return new SocketVoiceState(voiceChannel, model.RequestToSpeakTimestamp.IsSpecified ? model.RequestToSpeakTimestamp.Value : null, model.SessionId, model.SelfMute, model.SelfDeaf, model.Mute, model.Deaf, model.Suppress, model.SelfStream); } /// diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs index 404ab116d..bccfe1a29 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs @@ -13,6 +13,7 @@ namespace Discord.WebSocket [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class SocketWebhookUser : SocketUser, IWebhookUser { + #region SocketWebhookUser /// Gets the guild of this webhook. public SocketGuild Guild { get; } /// @@ -24,6 +25,8 @@ namespace Discord.WebSocket public override ushort DiscriminatorValue { get; internal set; } /// public override string AvatarId { get; internal set; } + + /// public override bool IsBot { get; internal set; } @@ -49,9 +52,9 @@ namespace Discord.WebSocket private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")}, Webhook)"; internal new SocketWebhookUser Clone() => MemberwiseClone() as SocketWebhookUser; +#endregion - - //IGuildUser + #region IGuildUser /// IGuild IGuildUser.Guild => Guild; /// @@ -63,10 +66,16 @@ namespace Discord.WebSocket /// string IGuildUser.Nickname => null; /// + string IGuildUser.GuildAvatarId => null; + /// + string IGuildUser.GetGuildAvatarUrl(ImageFormat format, ushort size) => null; + /// DateTimeOffset? IGuildUser.PremiumSince => null; /// bool? IGuildUser.IsPending => null; /// + int IGuildUser.Hierarchy => 0; + /// GuildPermissions IGuildUser.GuildPermissions => GuildPermissions.Webhook; /// @@ -120,8 +129,9 @@ namespace Discord.WebSocket /// Roles are not supported on webhook users. Task IGuildUser.RemoveRolesAsync(IEnumerable roles, RequestOptions options) => throw new NotSupportedException("Roles are not supported on webhook users."); + #endregion - //IVoiceState + #region IVoiceState /// bool IVoiceState.IsDeafened => false; /// @@ -138,5 +148,8 @@ namespace Discord.WebSocket string IVoiceState.VoiceSessionId => null; /// bool IVoiceState.IsStreaming => false; + /// + DateTimeOffset? IVoiceState.RequestToSpeakTimestamp => null; + #endregion } } diff --git a/src/Discord.Net.WebSocket/Extensions/EntityExtensions.cs b/src/Discord.Net.WebSocket/Extensions/EntityExtensions.cs index cbe575075..46f5c1a26 100644 --- a/src/Discord.Net.WebSocket/Extensions/EntityExtensions.cs +++ b/src/Discord.Net.WebSocket/Extensions/EntityExtensions.cs @@ -9,7 +9,7 @@ namespace Discord.WebSocket { public static IActivity ToEntity(this API.Game model) { - // Custom Status Game + #region Custom Status Game if (model.Id.IsSpecified && model.Id.Value == "custom") { return new CustomStatusGame() @@ -21,13 +21,14 @@ namespace Discord.WebSocket CreatedAt = DateTimeOffset.FromUnixTimeMilliseconds(model.CreatedAt.Value), }; } + #endregion - // Spotify Game + #region Spotify Game if (model.SyncId.IsSpecified) { var assets = model.Assets.GetValueOrDefault()?.ToEntity(); string albumText = assets?[1]?.Text; - string albumArtId = assets?[1]?.ImageId?.Replace("spotify:",""); + string albumArtId = assets?[1]?.ImageId?.Replace("spotify:", ""); var timestamps = model.Timestamps.IsSpecified ? model.Timestamps.Value.ToEntity() : null; return new SpotifyGame { @@ -37,7 +38,7 @@ namespace Discord.WebSocket TrackUrl = CDN.GetSpotifyDirectUrl(model.SyncId.Value), AlbumTitle = albumText, TrackTitle = model.Details.GetValueOrDefault(), - Artists = model.State.GetValueOrDefault()?.Split(';').Select(x=>x?.Trim()).ToImmutableArray(), + Artists = model.State.GetValueOrDefault()?.Split(';').Select(x => x?.Trim()).ToImmutableArray(), StartedAt = timestamps?.Start, EndsAt = timestamps?.End, Duration = timestamps?.End - timestamps?.Start, @@ -46,8 +47,9 @@ namespace Discord.WebSocket Flags = model.Flags.GetValueOrDefault(), }; } + #endregion - // Rich Game + #region Rich Game if (model.ApplicationId.IsSpecified) { ulong appId = model.ApplicationId.Value; @@ -66,7 +68,9 @@ namespace Discord.WebSocket Flags = model.Flags.GetValueOrDefault() }; } - // Stream Game + #endregion + + #region Stream Game if (model.StreamUrl.IsSpecified) { return new StreamingGame( @@ -77,10 +81,13 @@ namespace Discord.WebSocket Details = model.Details.GetValueOrDefault() }; } - // Normal Game + #endregion + + #region Normal Game return new Game(model.Name, model.Type.GetValueOrDefault() ?? ActivityType.Playing, model.Flags.IsSpecified ? model.Flags.Value : ActivityProperties.None, model.Details.GetValueOrDefault()); + #endregion } // (Small, Large) diff --git a/src/Discord.Net.Webhook/DiscordWebhookClient.cs b/src/Discord.Net.Webhook/DiscordWebhookClient.cs index 91d077411..a4fdf9179 100644 --- a/src/Discord.Net.Webhook/DiscordWebhookClient.cs +++ b/src/Discord.Net.Webhook/DiscordWebhookClient.cs @@ -60,8 +60,7 @@ namespace Discord.Webhook /// Thrown if the is null or whitespace. public DiscordWebhookClient(string webhookUrl, DiscordRestConfig config) : this(config) { - string token; - ParseWebhookUrl(webhookUrl, out _webhookId, out token); + ParseWebhookUrl(webhookUrl, out _webhookId, out string token); ApiClient.LoginAsync(TokenType.Webhook, token).GetAwaiter().GetResult(); Webhook = WebhookClientHelper.GetWebhookAsync(this, _webhookId).GetAwaiter().GetResult(); } @@ -88,8 +87,8 @@ namespace Discord.Webhook /// Sends a message to the channel for this webhook. /// Returns the ID of the created message. public Task SendMessageAsync(string text = null, bool isTTS = false, IEnumerable embeds = null, - string username = null, string avatarUrl = null, RequestOptions options = null, AllowedMentions allowedMentions = null) - => WebhookClientHelper.SendMessageAsync(this, text, isTTS, embeds, username, avatarUrl, allowedMentions, options); + string username = null, string avatarUrl = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageComponent component = null) + => WebhookClientHelper.SendMessageAsync(this, text, isTTS, embeds, username, avatarUrl, allowedMentions, options, component); /// /// Modifies a message posted using this webhook. diff --git a/src/Discord.Net.Webhook/Entities/Messages/WebhookMessageProperties.cs b/src/Discord.Net.Webhook/Entities/Messages/WebhookMessageProperties.cs index dec7b6e3b..ca2ff10a0 100644 --- a/src/Discord.Net.Webhook/Entities/Messages/WebhookMessageProperties.cs +++ b/src/Discord.Net.Webhook/Entities/Messages/WebhookMessageProperties.cs @@ -22,5 +22,9 @@ namespace Discord.Webhook /// Gets or sets the allowed mentions of the message. /// public Optional AllowedMentions { get; set; } + /// + /// Gets or sets the components that the message should display. + /// + public Optional Components { get; set; } } } diff --git a/src/Discord.Net.Webhook/Entities/Webhooks/RestInternalWebhook.cs b/src/Discord.Net.Webhook/Entities/Webhooks/RestInternalWebhook.cs index bbb160fcd..2a5c4786e 100644 --- a/src/Discord.Net.Webhook/Entities/Webhooks/RestInternalWebhook.cs +++ b/src/Discord.Net.Webhook/Entities/Webhooks/RestInternalWebhook.cs @@ -17,6 +17,7 @@ namespace Discord.Webhook public string Name { get; private set; } public string AvatarId { get; private set; } public ulong? GuildId { get; private set; } + public ulong? ApplicationId { get; private set; } public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); @@ -44,6 +45,8 @@ namespace Discord.Webhook GuildId = model.GuildId.Value; if (model.Name.IsSpecified) Name = model.Name.Value; + + ApplicationId = model.ApplicationId; } public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) diff --git a/src/Discord.Net.Webhook/WebhookClientHelper.cs b/src/Discord.Net.Webhook/WebhookClientHelper.cs index 886ff234d..6e3651323 100644 --- a/src/Discord.Net.Webhook/WebhookClientHelper.cs +++ b/src/Discord.Net.Webhook/WebhookClientHelper.cs @@ -20,10 +20,15 @@ namespace Discord.Webhook throw new InvalidOperationException("Could not find a webhook with the supplied credentials."); return RestInternalWebhook.Create(client, model); } - public static async Task SendMessageAsync(DiscordWebhookClient client, - string text, bool isTTS, IEnumerable embeds, string username, string avatarUrl, AllowedMentions allowedMentions, RequestOptions options) + public static async Task SendMessageAsync(DiscordWebhookClient client, + string text, bool isTTS, IEnumerable embeds, string username, string avatarUrl, AllowedMentions allowedMentions, RequestOptions options, MessageComponent component) { - var args = new CreateWebhookMessageParams(text) { IsTTS = isTTS }; + var args = new CreateWebhookMessageParams + { + Content = text, + IsTTS = isTTS + }; + if (embeds != null) args.Embeds = embeds.Select(x => x.ToModel()).ToArray(); if (username != null) @@ -32,6 +37,8 @@ namespace Discord.Webhook args.AvatarUrl = avatarUrl; if (allowedMentions != null) args.AllowedMentions = allowedMentions.ToModel(); + if (component != null) + args.Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray(); var model = await client.ApiClient.CreateWebhookMessageAsync(client.Webhook.Id, args, options: options).ConfigureAwait(false); return model.Id; @@ -78,7 +85,8 @@ namespace Discord.Webhook : Optional.Create(), AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value.ToModel() - : Optional.Create() + : Optional.Create(), + Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional.Unspecified, }; await client.ApiClient.ModifyWebhookMessageAsync(client.Webhook.Id, messageId, apiArgs, options) diff --git a/src/Discord.Net/Discord.Net.nuspec b/src/Discord.Net/Discord.Net.nuspec index e3f0150f9..cb773a379 100644 --- a/src/Discord.Net/Discord.Net.nuspec +++ b/src/Discord.Net/Discord.Net.nuspec @@ -36,4 +36,4 @@ - + \ No newline at end of file diff --git a/test/Discord.Net.Analyzers.Tests/Discord.Net.Analyzers.Tests.csproj b/test/Discord.Net.Analyzers.Tests/Discord.Net.Analyzers.Tests.csproj index 8f69672f9..1257041e4 100644 --- a/test/Discord.Net.Analyzers.Tests/Discord.Net.Analyzers.Tests.csproj +++ b/test/Discord.Net.Analyzers.Tests/Discord.Net.Analyzers.Tests.csproj @@ -15,10 +15,10 @@ - - + + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Discord.Net.Analyzers.Tests/Helpers/CodeFixVerifier.Helper.cs b/test/Discord.Net.Analyzers.Tests/Helpers/CodeFixVerifier.Helper.cs index 0f73d0643..42f7b08c1 100644 --- a/test/Discord.Net.Analyzers.Tests/Helpers/CodeFixVerifier.Helper.cs +++ b/test/Discord.Net.Analyzers.Tests/Helpers/CodeFixVerifier.Helper.cs @@ -18,7 +18,7 @@ namespace TestHelper /// Apply the inputted CodeAction to the inputted document. /// Meant to be used to apply codefixes. /// - /// The Document to apply the fix on + /// The Document to apply the fix on. /// A CodeAction that will be applied to the Document. /// A Document with the changes from the CodeAction private static Document ApplyFix(Document document, CodeAction codeAction) @@ -33,8 +33,8 @@ namespace TestHelper /// Note: Considers Diagnostics to be the same if they have the same Ids. In the case of multiple diagnostics with the same Id in a row, /// this method may not necessarily return the new one. /// - /// The Diagnostics that existed in the code before the CodeFix was applied - /// The Diagnostics that exist in the code after the CodeFix was applied + /// The Diagnostics that existed in the code before the CodeFix was applied. + /// The Diagnostics that exist in the code after the CodeFix was applied. /// A list of Diagnostics that only surfaced in the code after the CodeFix was applied private static IEnumerable GetNewDiagnostics(IEnumerable diagnostics, IEnumerable newDiagnostics) { @@ -61,7 +61,7 @@ namespace TestHelper /// /// Get the existing compiler diagnostics on the inputted document. /// - /// The Document to run the compiler diagnostic analyzers on + /// The Document to run the compiler diagnostic analyzers on. /// The compiler diagnostics that were found in the code private static IEnumerable GetCompilerDiagnostics(Document document) { @@ -71,7 +71,7 @@ namespace TestHelper /// /// Given a document, turn it into a string based on the syntax root /// - /// The Document to be converted to a string + /// The Document to be converted to a string. /// A string containing the syntax of the Document after formatting private static string GetStringFromDocument(Document document) { diff --git a/test/Discord.Net.Analyzers.Tests/Helpers/DiagnosticResult.cs b/test/Discord.Net.Analyzers.Tests/Helpers/DiagnosticResult.cs index 5ae6f528e..87d915494 100644 --- a/test/Discord.Net.Analyzers.Tests/Helpers/DiagnosticResult.cs +++ b/test/Discord.Net.Analyzers.Tests/Helpers/DiagnosticResult.cs @@ -20,9 +20,9 @@ namespace TestHelper throw new ArgumentOutOfRangeException(nameof(column), "column must be >= -1"); } - this.Path = path; - this.Line = line; - this.Column = column; + Path = path; + Line = line; + Column = column; } public string Path { get; } @@ -41,16 +41,16 @@ namespace TestHelper { get { - if (this.locations == null) + if (locations == null) { - this.locations = new DiagnosticResultLocation[] { }; + locations = new DiagnosticResultLocation[] { }; } - return this.locations; + return locations; } set { - this.locations = value; + locations = value; } } @@ -64,7 +64,7 @@ namespace TestHelper { get { - return this.Locations.Length > 0 ? this.Locations[0].Path : ""; + return Locations.Length > 0 ? Locations[0].Path : ""; } } @@ -72,7 +72,7 @@ namespace TestHelper { get { - return this.Locations.Length > 0 ? this.Locations[0].Line : -1; + return Locations.Length > 0 ? Locations[0].Line : -1; } } @@ -80,7 +80,7 @@ namespace TestHelper { get { - return this.Locations.Length > 0 ? this.Locations[0].Column : -1; + return Locations.Length > 0 ? Locations[0].Column : -1; } } } diff --git a/test/Discord.Net.Analyzers.Tests/Helpers/DiagnosticVerifier.Helper.cs b/test/Discord.Net.Analyzers.Tests/Helpers/DiagnosticVerifier.Helper.cs index 99654f12c..23bb319a6 100644 --- a/test/Discord.Net.Analyzers.Tests/Helpers/DiagnosticVerifier.Helper.cs +++ b/test/Discord.Net.Analyzers.Tests/Helpers/DiagnosticVerifier.Helper.cs @@ -35,9 +35,9 @@ namespace TestHelper /// /// Given classes in the form of strings, their language, and an IDiagnosticAnlayzer to apply to it, return the diagnostics found in the string after converting it to a document. /// - /// Classes in the form of strings - /// The language the source classes are in - /// The analyzer to be run on the sources + /// Classes in the form of strings. + /// The language the source classes are in. + /// The analyzer to be run on the sources. /// An IEnumerable of Diagnostics that surfaced in the source code, sorted by Location private static Diagnostic[] GetSortedDiagnostics(string[] sources, string language, DiagnosticAnalyzer analyzer) { @@ -48,8 +48,8 @@ namespace TestHelper /// Given an analyzer and a document to apply it to, run the analyzer and gather an array of diagnostics found in it. /// The returned diagnostics are then ordered by location in the source document. /// - /// The analyzer to run on the documents - /// The Documents that the analyzer will be run on + /// The analyzer to run on the documents. + /// The Documents that the analyzer will be run on. /// An IEnumerable of Diagnostics that surfaced in the source code, sorted by Location protected static Diagnostic[] GetSortedDiagnosticsFromDocuments(DiagnosticAnalyzer analyzer, Document[] documents) { @@ -93,7 +93,7 @@ namespace TestHelper /// /// Sort diagnostics by location in source document /// - /// The list of Diagnostics to be sorted + /// The list of Diagnostics to be sorted. /// An IEnumerable containing the Diagnostics in order of Location private static Diagnostic[] SortDiagnostics(IEnumerable diagnostics) { @@ -106,8 +106,8 @@ namespace TestHelper /// /// Given an array of strings as sources and a language, turn them into a project and return the documents and spans of it. /// - /// Classes in the form of strings - /// The language the source code is in + /// Classes in the form of strings. + /// The language the source code is in. /// A Tuple containing the Documents produced from the sources and their TextSpans if relevant private static Document[] GetDocuments(string[] sources, string language) { @@ -130,8 +130,8 @@ namespace TestHelper /// /// Create a Document from a string through creating a project that contains it. /// - /// Classes in the form of a string - /// The language the source code is in + /// Classes in the form of a string. + /// The language the source code is in. /// A Document created from the source string protected static Document CreateDocument(string source, string language = LanguageNames.CSharp) { @@ -141,8 +141,8 @@ namespace TestHelper /// /// Create a project using the inputted strings as sources. /// - /// Classes in the form of strings - /// The language the source code is in + /// Classes in the form of strings. + /// The language the source code is in. /// A Project created out of the Documents created from the source strings private static Project CreateProject(string[] sources, string language = LanguageNames.CSharp) { @@ -187,7 +187,7 @@ namespace TestHelper private static HashSet RecursiveReferencedAssemblies(Assembly a, HashSet assemblies = null) { - assemblies = assemblies ?? new HashSet(); + assemblies ??= new HashSet(); if (assemblies.Add(a)) { foreach (var referencedAssemblyName in a.GetReferencedAssemblies()) diff --git a/test/Discord.Net.Analyzers.Tests/Verifiers/CodeFixVerifier.cs b/test/Discord.Net.Analyzers.Tests/Verifiers/CodeFixVerifier.cs index 5d057b610..d1cb6cd1b 100644 --- a/test/Discord.Net.Analyzers.Tests/Verifiers/CodeFixVerifier.cs +++ b/test/Discord.Net.Analyzers.Tests/Verifiers/CodeFixVerifier.cs @@ -1,4 +1,4 @@ -using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.Diagnostics; @@ -38,10 +38,10 @@ namespace TestHelper /// /// Called to test a C# codefix when applied on the inputted string as a source /// - /// A class in the form of a string before the CodeFix was applied to it - /// A class in the form of a string after the CodeFix was applied to it - /// Index determining which codefix to apply if there are multiple - /// A bool controlling whether or not the test will fail if the CodeFix introduces other warnings after being applied + /// A class in the form of a string before the CodeFix was applied to it. + /// A class in the form of a string after the CodeFix was applied to it. + /// Index determining which codefix to apply if there are multiple. + /// A bool controlling whether or not the test will fail if the CodeFix introduces other warnings after being applied. protected void VerifyCSharpFix(string oldSource, string newSource, int? codeFixIndex = null, bool allowNewCompilerDiagnostics = false) { VerifyFix(LanguageNames.CSharp, GetCSharpDiagnosticAnalyzer(), GetCSharpCodeFixProvider(), oldSource, newSource, codeFixIndex, allowNewCompilerDiagnostics); @@ -50,10 +50,10 @@ namespace TestHelper /// /// Called to test a VB codefix when applied on the inputted string as a source /// - /// A class in the form of a string before the CodeFix was applied to it - /// A class in the form of a string after the CodeFix was applied to it - /// Index determining which codefix to apply if there are multiple - /// A bool controlling whether or not the test will fail if the CodeFix introduces other warnings after being applied + /// A class in the form of a string before the CodeFix was applied to it. + /// A class in the form of a string after the CodeFix was applied to it. + /// Index determining which codefix to apply if there are multiple. + /// A bool controlling whether or not the test will fail if the CodeFix introduces other warnings after being applied. protected void VerifyBasicFix(string oldSource, string newSource, int? codeFixIndex = null, bool allowNewCompilerDiagnostics = false) { VerifyFix(LanguageNames.VisualBasic, GetBasicDiagnosticAnalyzer(), GetBasicCodeFixProvider(), oldSource, newSource, codeFixIndex, allowNewCompilerDiagnostics); @@ -65,13 +65,13 @@ namespace TestHelper /// Then gets the string after the codefix is applied and compares it with the expected result. /// Note: If any codefix causes new diagnostics to show up, the test fails unless allowNewCompilerDiagnostics is set to true. /// - /// The language the source code is in - /// The analyzer to be applied to the source code - /// The codefix to be applied to the code wherever the relevant Diagnostic is found - /// A class in the form of a string before the CodeFix was applied to it - /// A class in the form of a string after the CodeFix was applied to it - /// Index determining which codefix to apply if there are multiple - /// A bool controlling whether or not the test will fail if the CodeFix introduces other warnings after being applied + /// The language the source code is in. + /// The analyzer to be applied to the source code. + /// The codefix to be applied to the code wherever the relevant Diagnostic is found. + /// A class in the form of a string before the CodeFix was applied to it. + /// A class in the form of a string after the CodeFix was applied to it. + /// Index determining which codefix to apply if there are multiple. + /// A bool controlling whether or not the test will fail if the CodeFix introduces other warnings after being applied. private void VerifyFix(string language, DiagnosticAnalyzer analyzer, CodeFixProvider codeFixProvider, string oldSource, string newSource, int? codeFixIndex, bool allowNewCompilerDiagnostics) { var document = CreateDocument(oldSource, language); @@ -126,4 +126,4 @@ namespace TestHelper Assert.Equal(newSource, actual); } } -} \ No newline at end of file +} diff --git a/test/Discord.Net.Analyzers.Tests/Verifiers/DiagnosticVerifier.cs b/test/Discord.Net.Analyzers.Tests/Verifiers/DiagnosticVerifier.cs index 3564093f8..9b0219a63 100644 --- a/test/Discord.Net.Analyzers.Tests/Verifiers/DiagnosticVerifier.cs +++ b/test/Discord.Net.Analyzers.Tests/Verifiers/DiagnosticVerifier.cs @@ -37,8 +37,8 @@ namespace TestHelper /// Called to test a C# DiagnosticAnalyzer when applied on the single inputted string as a source /// Note: input a DiagnosticResult for each Diagnostic expected /// - /// A class in the form of a string to run the analyzer on - /// DiagnosticResults that should appear after the analyzer is run on the source + /// A class in the form of a string to run the analyzer on. + /// DiagnosticResults that should appear after the analyzer is run on the source. protected void VerifyCSharpDiagnostic(string source, params DiagnosticResult[] expected) { VerifyDiagnostics(new[] { source }, LanguageNames.CSharp, GetCSharpDiagnosticAnalyzer(), expected); @@ -48,8 +48,8 @@ namespace TestHelper /// Called to test a VB DiagnosticAnalyzer when applied on the single inputted string as a source /// Note: input a DiagnosticResult for each Diagnostic expected /// - /// A class in the form of a string to run the analyzer on - /// DiagnosticResults that should appear after the analyzer is run on the source + /// A class in the form of a string to run the analyzer on. + /// DiagnosticResults that should appear after the analyzer is run on the source. protected void VerifyBasicDiagnostic(string source, params DiagnosticResult[] expected) { VerifyDiagnostics(new[] { source }, LanguageNames.VisualBasic, GetBasicDiagnosticAnalyzer(), expected); @@ -59,8 +59,8 @@ namespace TestHelper /// Called to test a C# DiagnosticAnalyzer when applied on the inputted strings as a source /// Note: input a DiagnosticResult for each Diagnostic expected /// - /// An array of strings to create source documents from to run the analyzers on - /// DiagnosticResults that should appear after the analyzer is run on the sources + /// An array of strings to create source documents from to run the analyzers on. + /// DiagnosticResults that should appear after the analyzer is run on the sources. protected void VerifyCSharpDiagnostic(string[] sources, params DiagnosticResult[] expected) { VerifyDiagnostics(sources, LanguageNames.CSharp, GetCSharpDiagnosticAnalyzer(), expected); @@ -70,8 +70,8 @@ namespace TestHelper /// Called to test a VB DiagnosticAnalyzer when applied on the inputted strings as a source /// Note: input a DiagnosticResult for each Diagnostic expected /// - /// An array of strings to create source documents from to run the analyzers on - /// DiagnosticResults that should appear after the analyzer is run on the sources + /// An array of strings to create source documents from to run the analyzers on. + /// DiagnosticResults that should appear after the analyzer is run on the sources. protected void VerifyBasicDiagnostic(string[] sources, params DiagnosticResult[] expected) { VerifyDiagnostics(sources, LanguageNames.VisualBasic, GetBasicDiagnosticAnalyzer(), expected); @@ -81,10 +81,10 @@ namespace TestHelper /// General method that gets a collection of actual diagnostics found in the source after the analyzer is run, /// then verifies each of them. /// - /// An array of strings to create source documents from to run the analyzers on - /// The language of the classes represented by the source strings - /// The analyzer to be run on the source code - /// DiagnosticResults that should appear after the analyzer is run on the sources + /// An array of strings to create source documents from to run the analyzers on. + /// The language of the classes represented by the source strings. + /// The analyzer to be run on the source code. + /// DiagnosticResults that should appear after the analyzer is run on the sources. private void VerifyDiagnostics(string[] sources, string language, DiagnosticAnalyzer analyzer, params DiagnosticResult[] expected) { var diagnostics = GetSortedDiagnostics(sources, language, analyzer); @@ -98,9 +98,9 @@ namespace TestHelper /// Checks each of the actual Diagnostics found and compares them with the corresponding DiagnosticResult in the array of expected results. /// Diagnostics are considered equal only if the DiagnosticResultLocation, Id, Severity, and Message of the DiagnosticResult match the actual diagnostic. /// - /// The Diagnostics found by the compiler after running the analyzer on the source code - /// The analyzer that was being run on the sources - /// Diagnostic Results that should have appeared in the code + /// The Diagnostics found by the compiler after running the analyzer on the source code. + /// The analyzer that was being run on the sources. + /// Diagnostic Results that should have appeared in the code. private static void VerifyDiagnosticResults(IEnumerable actualResults, DiagnosticAnalyzer analyzer, params DiagnosticResult[] expectedResults) { int expectedCount = expectedResults.Length; @@ -173,10 +173,10 @@ namespace TestHelper /// /// Helper method to VerifyDiagnosticResult that checks the location of a diagnostic and compares it with the location in the expected DiagnosticResult. /// - /// The analyzer that was being run on the sources - /// The diagnostic that was found in the code - /// The Location of the Diagnostic found in the code - /// The DiagnosticResultLocation that should have been found + /// The analyzer that was being run on the sources. + /// The diagnostic that was found in the code. + /// The Location of the Diagnostic found in the code. + /// The DiagnosticResultLocation that should have been found. private static void VerifyDiagnosticLocation(DiagnosticAnalyzer analyzer, Diagnostic diagnostic, Location actual, DiagnosticResultLocation expected) { var actualSpan = actual.GetLineSpan(); @@ -215,8 +215,8 @@ namespace TestHelper /// /// Helper method to format a Diagnostic into an easily readable string /// - /// The analyzer that this verifier tests - /// The Diagnostics to be formatted + /// The analyzer that this verifier tests. + /// The Diagnostics to be formatted. /// The Diagnostics formatted as a string private static string FormatDiagnostics(DiagnosticAnalyzer analyzer, params Diagnostic[] diagnostics) { diff --git a/test/Discord.Net.Tests.Integration/ChannelsTests.cs b/test/Discord.Net.Tests.Integration/ChannelsTests.cs index 3bf60772f..9bb30c4ef 100644 --- a/test/Discord.Net.Tests.Integration/ChannelsTests.cs +++ b/test/Discord.Net.Tests.Integration/ChannelsTests.cs @@ -19,7 +19,7 @@ namespace Discord public ChannelsTests(RestGuildFixture guildFixture, ITestOutputHelper output) { guild = guildFixture.Guild; - this.output = output; + output = output; output.WriteLine($"RestGuildFixture using guild: {guild.Id}"); // capture all console output guildFixture.Client.Log += LogAsync; diff --git a/test/Discord.Net.Tests.Integration/Discord.Net.Tests.Integration.csproj b/test/Discord.Net.Tests.Integration/Discord.Net.Tests.Integration.csproj index c571059ef..8b16b2971 100644 --- a/test/Discord.Net.Tests.Integration/Discord.Net.Tests.Integration.csproj +++ b/test/Discord.Net.Tests.Integration/Discord.Net.Tests.Integration.csproj @@ -15,9 +15,9 @@ - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Discord.Net.Tests.Integration/GuildTests.cs b/test/Discord.Net.Tests.Integration/GuildTests.cs index 40394a3a0..c309b0ed1 100644 --- a/test/Discord.Net.Tests.Integration/GuildTests.cs +++ b/test/Discord.Net.Tests.Integration/GuildTests.cs @@ -18,7 +18,7 @@ namespace Discord { client = guildFixture.Client; guild = guildFixture.Guild; - this.output = output; + output = output; output.WriteLine($"RestGuildFixture using guild: {guild.Id}"); guildFixture.Client.Log += LogAsync; } diff --git a/test/Discord.Net.Tests.Unit/ChannelPermissionsTests.cs b/test/Discord.Net.Tests.Unit/ChannelPermissionsTests.cs index a3566590a..2cab8fa21 100644 --- a/test/Discord.Net.Tests.Unit/ChannelPermissionsTests.cs +++ b/test/Discord.Net.Tests.Unit/ChannelPermissionsTests.cs @@ -85,6 +85,10 @@ namespace Discord AssertFlag(() => new ChannelPermissions(stream: true), ChannelPermission.Stream); AssertFlag(() => new ChannelPermissions(manageRoles: true), ChannelPermission.ManageRoles); AssertFlag(() => new ChannelPermissions(manageWebhooks: true), ChannelPermission.ManageWebhooks); + AssertFlag(() => new ChannelPermissions(useApplicationCommands: true), ChannelPermission.UseApplicationCommands); + AssertFlag(() => new ChannelPermissions(createPrivateThreads: true), ChannelPermission.CreatePrivateThreads); + AssertFlag(() => new ChannelPermissions(createPublicThreads: true), ChannelPermission.CreatePublicThreads); + AssertFlag(() => new ChannelPermissions(sendMessagesInThreads: true), ChannelPermission.SendMessagesInThreads); } /// diff --git a/test/Discord.Net.Tests.Unit/ColorTests.cs b/test/Discord.Net.Tests.Unit/ColorTests.cs index 87c76e4e2..46d8feabb 100644 --- a/test/Discord.Net.Tests.Unit/ColorTests.cs +++ b/test/Discord.Net.Tests.Unit/ColorTests.cs @@ -10,12 +10,11 @@ namespace Discord /// public class ColorTests { - [Fact] public void Color_New() { Assert.Equal(0u, new Color().RawValue); Assert.Equal(uint.MinValue, new Color(uint.MinValue).RawValue); - Assert.Equal(uint.MaxValue, new Color(uint.MaxValue).RawValue); + Assert.Throws(() => new Color(uint.MaxValue)); } [Fact] public void Color_Default() diff --git a/test/Discord.Net.Tests.Unit/Discord.Net.Tests.Unit.csproj b/test/Discord.Net.Tests.Unit/Discord.Net.Tests.Unit.csproj index 866041696..716c3ebc4 100644 --- a/test/Discord.Net.Tests.Unit/Discord.Net.Tests.Unit.csproj +++ b/test/Discord.Net.Tests.Unit/Discord.Net.Tests.Unit.csproj @@ -13,9 +13,9 @@ - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Discord.Net.Tests.Unit/EmbedBuilderTests.cs b/test/Discord.Net.Tests.Unit/EmbedBuilderTests.cs index da21afee1..83c6ede19 100644 --- a/test/Discord.Net.Tests.Unit/EmbedBuilderTests.cs +++ b/test/Discord.Net.Tests.Unit/EmbedBuilderTests.cs @@ -9,9 +9,9 @@ namespace Discord /// public class EmbedBuilderTests { - private const string name = "chrisj"; - private const string icon = "https://meowpuffygottem.fun/blob.png"; - private const string url = "https://meowpuffygottem.fun/"; + private const string Name = "chrisj"; + private const string Icon = "https://meowpuffygottem.fun/blob.png"; + private const string Url = "https://meowpuffygottem.fun/"; /// /// Tests the behavior of . @@ -24,12 +24,12 @@ namespace Discord Assert.Null(builder.Author); builder = new EmbedBuilder() - .WithAuthor(name, icon, url); + .WithAuthor(Name, Icon, Url); Assert.NotNull(builder.Author); - Assert.Equal(name, builder.Author.Name); - Assert.Equal(icon, builder.Author.IconUrl); - Assert.Equal(url, builder.Author.Url); + Assert.Equal(Name, builder.Author.Name); + Assert.Equal(Icon, builder.Author.IconUrl); + Assert.Equal(Url, builder.Author.Url); } /// @@ -39,15 +39,15 @@ namespace Discord public void WithAuthor_AuthorBuilder() { var author = new EmbedAuthorBuilder() - .WithIconUrl(icon) - .WithName(name) - .WithUrl(url); + .WithIconUrl(Icon) + .WithName(Name) + .WithUrl(Url); var builder = new EmbedBuilder() .WithAuthor(author); Assert.NotNull(builder.Author); - Assert.Equal(name, builder.Author.Name); - Assert.Equal(icon, builder.Author.IconUrl); - Assert.Equal(url, builder.Author.Url); + Assert.Equal(Name, builder.Author.Name); + Assert.Equal(Icon, builder.Author.IconUrl); + Assert.Equal(Url, builder.Author.Url); } /// @@ -58,13 +58,13 @@ namespace Discord { var builder = new EmbedBuilder() .WithAuthor((author) => - author.WithIconUrl(icon) - .WithName(name) - .WithUrl(url)); + author.WithIconUrl(Icon) + .WithName(Name) + .WithUrl(Url)); Assert.NotNull(builder.Author); - Assert.Equal(name, builder.Author.Name); - Assert.Equal(icon, builder.Author.IconUrl); - Assert.Equal(url, builder.Author.Url); + Assert.Equal(Name, builder.Author.Name); + Assert.Equal(Icon, builder.Author.IconUrl); + Assert.Equal(Url, builder.Author.Url); } /// @@ -74,12 +74,12 @@ namespace Discord public void EmbedAuthorBuilder() { var builder = new EmbedAuthorBuilder() - .WithIconUrl(icon) - .WithName(name) - .WithUrl(url); - Assert.Equal(icon, builder.IconUrl); - Assert.Equal(name, builder.Name); - Assert.Equal(url, builder.Url); + .WithIconUrl(Icon) + .WithName(Name) + .WithUrl(Url); + Assert.Equal(Icon, builder.IconUrl); + Assert.Equal(Name, builder.Name); + Assert.Equal(Url, builder.Url); } /// @@ -95,8 +95,10 @@ namespace Discord { Assert.Throws(() => { - var builder = new EmbedBuilder(); - builder.Title = title; + var builder = new EmbedBuilder + { + Title = title + }; }); Assert.Throws(() => { @@ -113,8 +115,10 @@ namespace Discord [InlineData("jVyLChmA7aBZozXQuZ3VDEcwW6zOq0nteOVYBZi31ny73rpXfSSBXR4Jw6FiplDKQseKskwRMuBZkUewrewqAbkBZpslHirvC5nEzRySoDIdTRnkVvTXZUXg75l3bQCjuuHxDd6DfrY8ihd6yZX1Y0XFeg239YBcYV4TpL9uQ8H3HFYxrWhLlG2PRVjUmiglP5iXkawszNwMVm1SZ5LZT4jkMZHxFegVi7170d16iaPWOovu50aDDHy087XBtLKV")] public void Tile_Valid(string title) { - var builder = new EmbedBuilder(); - builder.Title = title; + var builder = new EmbedBuilder + { + Title = title + }; new EmbedBuilder().WithTitle(title); } @@ -133,8 +137,10 @@ namespace Discord Assert.Throws(() => new EmbedBuilder().WithDescription(description)); Assert.Throws(() => { - var b = new EmbedBuilder(); - b.Description = description; + var b = new EmbedBuilder + { + Description = description + }; }); } } @@ -156,14 +162,16 @@ namespace Discord var b = new EmbedBuilder().WithDescription(description); Assert.Equal(description, b.Description); - b = new EmbedBuilder(); - b.Description = description; + b = new EmbedBuilder + { + Description = description + }; Assert.Equal(description, b.Description); } } /// - /// Tests that valid urls do not throw any exceptions. + /// Tests that valid url's do not throw any exceptions. /// /// The url to set. [Theory] @@ -181,10 +189,12 @@ namespace Discord Assert.Equal(result.ImageUrl, url); Assert.Equal(result.ThumbnailUrl, url); - result = new EmbedBuilder(); - result.Url = url; - result.ImageUrl = url; - result.ThumbnailUrl = url; + result = new EmbedBuilder + { + Url = url, + ImageUrl = url, + ThumbnailUrl = url + }; Assert.Equal(result.Url, url); Assert.Equal(result.ImageUrl, url); Assert.Equal(result.ThumbnailUrl, url); @@ -207,15 +217,15 @@ namespace Discord public void Length() { var e = new EmbedBuilder() - .WithAuthor(name, icon, url) + .WithAuthor(Name, Icon, Url) .WithColor(Color.Blue) .WithDescription("This is the test description.") - .WithFooter("This is the footer", url) - .WithImageUrl(url) - .WithThumbnailUrl(url) + .WithFooter("This is the footer", Url) + .WithImageUrl(Url) + .WithThumbnailUrl(Url) .WithTimestamp(DateTimeOffset.MinValue) .WithTitle("This is the title") - .WithUrl(url) + .WithUrl(Url) .AddField("Field 1", "Inline", true) .AddField("Field 2", "Not Inline", false); Assert.Equal(100, e.Length); @@ -253,11 +263,11 @@ namespace Discord var e = new EmbedBuilder() .WithFooter(x => { - x.IconUrl = url; - x.Text = name; + x.IconUrl = Url; + x.Text = Name; }); - Assert.Equal(url, e.Footer.IconUrl); - Assert.Equal(name, e.Footer.Text); + Assert.Equal(Url, e.Footer.IconUrl); + Assert.Equal(Name, e.Footer.Text); } /// @@ -268,18 +278,20 @@ namespace Discord { var footer = new EmbedFooterBuilder() { - IconUrl = url, - Text = name + IconUrl = Url, + Text = Name }; var e = new EmbedBuilder() .WithFooter(footer); - Assert.Equal(url, e.Footer.IconUrl); - Assert.Equal(name, e.Footer.Text); + Assert.Equal(Url, e.Footer.IconUrl); + Assert.Equal(Name, e.Footer.Text); // use the property - e = new EmbedBuilder(); - e.Footer = footer; - Assert.Equal(url, e.Footer.IconUrl); - Assert.Equal(name, e.Footer.Text); + e = new EmbedBuilder + { + Footer = footer + }; + Assert.Equal(Url, e.Footer.IconUrl); + Assert.Equal(Name, e.Footer.Text); } /// @@ -289,9 +301,9 @@ namespace Discord public void WithFooter_Strings() { var e = new EmbedBuilder() - .WithFooter(name, url); - Assert.Equal(url, e.Footer.IconUrl); - Assert.Equal(name, e.Footer.Text); + .WithFooter(Name, Url); + Assert.Equal(Url, e.Footer.IconUrl); + Assert.Equal(Name, e.Footer.Text); } /// @@ -301,10 +313,10 @@ namespace Discord public void EmbedFooterBuilder() { var footer = new EmbedFooterBuilder() - .WithIconUrl(url) - .WithText(name); - Assert.Equal(url, footer.IconUrl); - Assert.Equal(name, footer.Text); + .WithIconUrl(Url) + .WithText(Name); + Assert.Equal(Url, footer.IconUrl); + Assert.Equal(Name, footer.Text); } /// /// Tests that invalid text throws an . @@ -375,10 +387,12 @@ namespace Discord Assert.Equal("value", e.Value); Assert.True(e.IsInline); // use the properties - e = new EmbedFieldBuilder(); - e.IsInline = true; - e.Name = "name"; - e.Value = "value"; + e = new EmbedFieldBuilder + { + IsInline = true, + Name = "name", + Value = "value" + }; Assert.Equal("name", e.Name); Assert.Equal("value", e.Value); Assert.True(e.IsInline); diff --git a/test/Discord.Net.Tests.Unit/FormatTests.cs b/test/Discord.Net.Tests.Unit/FormatTests.cs index 2a5adbaae..c015c7e15 100644 --- a/test/Discord.Net.Tests.Unit/FormatTests.cs +++ b/test/Discord.Net.Tests.Unit/FormatTests.cs @@ -59,5 +59,20 @@ namespace Discord { Assert.Equal(expected, Format.BlockQuote(input)); } + + [Theory] + [InlineData("", "")] + [InlineData("\n", "\n")] + [InlineData("**hi**", "hi")] + [InlineData("__uwu__", "uwu")] + [InlineData(">>__uwu__", "uwu")] + [InlineData("```uwu```", "uwu")] + [InlineData("~uwu~", "uwu")] + [InlineData("berries __and__ *Cream**, I'm a little lad who loves berries and cream", "berries and Cream, I'm a little lad who loves berries and cream")] + public void StripMarkdown(string input, string expected) + { + var test = Format.StripMarkDown(input); + Assert.Equal(expected, test); + } } } diff --git a/test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs b/test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs index cd29b2606..f0b0b2db7 100644 --- a/test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs +++ b/test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs @@ -91,7 +91,14 @@ namespace Discord AssertFlag(() => new GuildPermissions(manageNicknames: true), GuildPermission.ManageNicknames); AssertFlag(() => new GuildPermissions(manageRoles: true), GuildPermission.ManageRoles); AssertFlag(() => new GuildPermissions(manageWebhooks: true), GuildPermission.ManageWebhooks); - AssertFlag(() => new GuildPermissions(manageEmojis: true), GuildPermission.ManageEmojis); + AssertFlag(() => new GuildPermissions(manageEmojisAndStickers: true), GuildPermission.ManageEmojisAndStickers); + AssertFlag(() => new GuildPermissions(useApplicationCommands: true), GuildPermission.UseApplicationCommands); + AssertFlag(() => new GuildPermissions(requestToSpeak: true), GuildPermission.RequestToSpeak); + AssertFlag(() => new GuildPermissions(manageEvents: true), GuildPermission.ManageEvents); + AssertFlag(() => new GuildPermissions(manageThreads: true), GuildPermission.ManageThreads); + AssertFlag(() => new GuildPermissions(createPublicThreads: true), GuildPermission.CreatePublicThreads); + AssertFlag(() => new GuildPermissions(createPrivateThreads: true), GuildPermission.CreatePrivateThreads); + AssertFlag(() => new GuildPermissions(useExternalStickers: true), GuildPermission.UseExternalStickers); } /// @@ -161,7 +168,14 @@ namespace Discord AssertUtil(GuildPermission.ManageNicknames, x => x.ManageNicknames, (p, enable) => p.Modify(manageNicknames: enable)); AssertUtil(GuildPermission.ManageRoles, x => x.ManageRoles, (p, enable) => p.Modify(manageRoles: enable)); AssertUtil(GuildPermission.ManageWebhooks, x => x.ManageWebhooks, (p, enable) => p.Modify(manageWebhooks: enable)); - AssertUtil(GuildPermission.ManageEmojis, x => x.ManageEmojis, (p, enable) => p.Modify(manageEmojis: enable)); + AssertUtil(GuildPermission.ManageEmojisAndStickers, x => x.ManageEmojisAndStickers, (p, enable) => p.Modify(manageEmojisAndStickers: enable)); + AssertUtil(GuildPermission.UseApplicationCommands, x => x.UseApplicationCommands, (p, enable) => p.Modify(useApplicationCommands: enable)); + AssertUtil(GuildPermission.RequestToSpeak, x => x.RequestToSpeak, (p, enable) => p.Modify(requestToSpeak: enable)); + AssertUtil(GuildPermission.ManageEvents, x => x.ManageEvents, (p, enable) => p.Modify(manageEvents: enable)); + AssertUtil(GuildPermission.ManageThreads, x => x.ManageThreads, (p, enable) => p.Modify(manageThreads: enable)); + AssertUtil(GuildPermission.CreatePublicThreads, x => x.CreatePublicThreads, (p, enable) => p.Modify(createPublicThreads: enable)); + AssertUtil(GuildPermission.CreatePrivateThreads, x => x.CreatePrivateThreads, (p, enable) => p.Modify(createPrivateThreads: enable)); + AssertUtil(GuildPermission.UseExternalStickers, x => x.UseExternalStickers, (p, enable) => p.Modify(useExternalStickers: enable)); } } } diff --git a/test/Discord.Net.Tests.Unit/MentionUtilsTests.cs b/test/Discord.Net.Tests.Unit/MentionUtilsTests.cs index 52f35fd9c..abd1191c8 100644 --- a/test/Discord.Net.Tests.Unit/MentionUtilsTests.cs +++ b/test/Discord.Net.Tests.Unit/MentionUtilsTests.cs @@ -47,9 +47,7 @@ namespace Discord var parsed = MentionUtils.ParseUser(user); Assert.Equal(id, parsed); - // also check tryparse - ulong result; - Assert.True(MentionUtils.TryParseUser(user, out result)); + Assert.True(MentionUtils.TryParseUser(user, out ulong result)); Assert.Equal(id, result); } [Theory] @@ -75,9 +73,7 @@ namespace Discord var parsed = MentionUtils.ParseChannel(channel); Assert.Equal(id, parsed); - // also check tryparse - ulong result; - Assert.True(MentionUtils.TryParseChannel(channel, out result)); + Assert.True(MentionUtils.TryParseChannel(channel, out ulong result)); Assert.Equal(id, result); } [Theory] @@ -103,9 +99,7 @@ namespace Discord var parsed = MentionUtils.ParseRole(role); Assert.Equal(id, parsed); - // also check tryparse - ulong result; - Assert.True(MentionUtils.TryParseRole(role, out result)); + Assert.True(MentionUtils.TryParseRole(role, out ulong result)); Assert.Equal(id, result); } [Theory] diff --git a/test/Discord.Net.Tests.Unit/MockedEntities/MockedDMChannel.cs b/test/Discord.Net.Tests.Unit/MockedEntities/MockedDMChannel.cs index 593b9201a..519bab4d9 100644 --- a/test/Discord.Net.Tests.Unit/MockedEntities/MockedDMChannel.cs +++ b/test/Discord.Net.Tests.Unit/MockedEntities/MockedDMChannel.cs @@ -78,24 +78,15 @@ namespace Discord throw new NotImplementedException(); } - public Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null) - { - throw new NotImplementedException(); - } - - public Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null) - { - throw new NotImplementedException(); - } - - public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null) - { - throw new NotImplementedException(); - } - public Task TriggerTypingAsync(RequestOptions options = null) { throw new NotImplementedException(); } + + public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null) => throw new NotImplementedException(); + public Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null) => throw new NotImplementedException(); + public Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null) => throw new NotImplementedException(); + public Task SendFileAsync(FileAttachment attachment, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null) => throw new NotImplementedException(); + public Task SendFilesAsync(IEnumerable attachments, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null) => throw new NotImplementedException(); } } diff --git a/test/Discord.Net.Tests.Unit/MockedEntities/MockedGroupChannel.cs b/test/Discord.Net.Tests.Unit/MockedEntities/MockedGroupChannel.cs index 6daf6a9c8..6b134d92f 100644 --- a/test/Discord.Net.Tests.Unit/MockedEntities/MockedGroupChannel.cs +++ b/test/Discord.Net.Tests.Unit/MockedEntities/MockedGroupChannel.cs @@ -86,17 +86,17 @@ namespace Discord throw new NotImplementedException(); } - public Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null) + public Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null) { throw new NotImplementedException(); } - public Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null) + public Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null) { throw new NotImplementedException(); } - public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null) + public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null) { throw new NotImplementedException(); } @@ -105,5 +105,8 @@ namespace Discord { throw new NotImplementedException(); } + + public Task SendFileAsync(FileAttachment attachment, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null) => throw new NotImplementedException(); + public Task SendFilesAsync(IEnumerable attachments, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null) => throw new NotImplementedException(); } } diff --git a/test/Discord.Net.Tests.Unit/MockedEntities/MockedTextChannel.cs b/test/Discord.Net.Tests.Unit/MockedEntities/MockedTextChannel.cs index 51aece5f2..6d08a4478 100644 --- a/test/Discord.Net.Tests.Unit/MockedEntities/MockedTextChannel.cs +++ b/test/Discord.Net.Tests.Unit/MockedEntities/MockedTextChannel.cs @@ -46,6 +46,10 @@ namespace Discord { throw new NotImplementedException(); } + public Task CreateInviteToApplicationAsync(ulong applicationId, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null) + => throw new NotImplementedException(); + public Task CreateInviteToStreamAsync(IUser user, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null) + => throw new NotImplementedException(); public Task CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null) { @@ -172,17 +176,17 @@ namespace Discord throw new NotImplementedException(); } - public Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null) + public Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null) { throw new NotImplementedException(); } - public Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null) + public Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null) { throw new NotImplementedException(); } - public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null) + public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null) { throw new NotImplementedException(); } @@ -206,5 +210,9 @@ namespace Discord { throw new NotImplementedException(); } + + public Task SendFileAsync(FileAttachment attachment, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null) => throw new NotImplementedException(); + public Task SendFilesAsync(IEnumerable attachments, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null) => throw new NotImplementedException(); + public Task CreateThreadAsync(string name, ThreadType type = ThreadType.PublicThread, ThreadArchiveDuration autoArchiveDuration = ThreadArchiveDuration.OneDay, IMessage message = null, bool? invitable = null, int? slowmode = null, RequestOptions options = null) => throw new NotImplementedException(); } } diff --git a/test/Discord.Net.Tests.Unit/MockedEntities/MockedVoiceChannel.cs b/test/Discord.Net.Tests.Unit/MockedEntities/MockedVoiceChannel.cs index 6696c3613..61a32e391 100644 --- a/test/Discord.Net.Tests.Unit/MockedEntities/MockedVoiceChannel.cs +++ b/test/Discord.Net.Tests.Unit/MockedEntities/MockedVoiceChannel.cs @@ -27,7 +27,6 @@ namespace Discord public string Name => throw new NotImplementedException(); public DateTimeOffset CreatedAt => throw new NotImplementedException(); - public ulong Id => throw new NotImplementedException(); public Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, RequestOptions options = null) @@ -49,6 +48,10 @@ namespace Discord { throw new NotImplementedException(); } + public Task CreateInviteToApplicationAsync(ulong applicationId, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null) + => throw new NotImplementedException(); + public Task CreateInviteToStreamAsync(IUser user, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null) + => throw new NotImplementedException(); public Task DeleteAsync(RequestOptions options = null) {