Merge Labs 3.X into dev (#1923)
* 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 <lynchquin@gmail.com> * 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 <lynchquin@gmail.com> 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> 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 <lynchquin@gmail.com> * 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 <lynchquin@gmail.com> * 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 <lynchquin@gmail.com> * 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 <sh2@live.dk> Co-authored-by: drobbins329 <drobbins329@gmail.com> Co-authored-by: MrCakeSlayer <13650699+MrCakeSlayer@users.noreply.github.com> Co-authored-by: d4n3436 <dan3436@hotmail.com> Co-authored-by: Will <WilliamWelsh@users.noreply.github.com> Co-authored-by: Eugene Garbuzov <kkxo.mail@gmail.com> 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 <marens101@gmail.com> 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 <billchirico@gmail.com> Co-authored-by: Liege72 <65319395+Liege72@users.noreply.github.com> Co-authored-by: Floowey <floowey@gmx.at> Co-authored-by: Cenk Ergen <57065323+Cenngo@users.noreply.github.com> Co-authored-by: exsersewo <exsersewo@systemexit.co.uk> Co-authored-by: Dennis Fischer <fischer_dennis@live.de>
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Gateway
|
||||
{
|
||||
internal class ApplicationCommandCreatedUpdatedEvent : ApplicationCommand
|
||||
{
|
||||
[JsonProperty("guild_id")]
|
||||
public Optional<ulong> GuildId { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#pragma warning disable CS1591
|
||||
namespace Discord.API.Gateway
|
||||
{
|
||||
internal enum GatewayOpCode : byte
|
||||
@@ -10,7 +9,7 @@ namespace Discord.API.Gateway
|
||||
/// <summary> C→S - Used to associate a connection with a token and specify configuration. </summary>
|
||||
Identify = 2,
|
||||
/// <summary> C→S - Used to update client's status and current game id. </summary>
|
||||
StatusUpdate = 3,
|
||||
PresenceUpdate = 3,
|
||||
/// <summary> C→S - Used to join a particular voice channel. </summary>
|
||||
VoiceStateUpdate = 4,
|
||||
/// <summary> C→S - Used to ensure the guild's voice server is alive. </summary>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Gateway
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Gateway
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Gateway
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Gateway
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Gateway
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Gateway
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Gateway
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Gateway
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Gateway
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Gateway
|
||||
|
||||
@@ -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<int[]> ShardingParams { get; set; }
|
||||
[JsonProperty("presence")]
|
||||
public Optional<StatusUpdateParams> Presence { get; set; }
|
||||
public Optional<PresenceUpdateParams> Presence { get; set; }
|
||||
[JsonProperty("intents")]
|
||||
public Optional<int> Intents { get; set; }
|
||||
}
|
||||
|
||||
32
src/Discord.Net.WebSocket/API/Gateway/InviteCreatedEvent.cs
Normal file
32
src/Discord.Net.WebSocket/API/Gateway/InviteCreatedEvent.cs
Normal file
@@ -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<DateTimeOffset> RawTimestamp { get; set; }
|
||||
[JsonProperty("guild_id")]
|
||||
public ulong? GuildID { get; set; }
|
||||
[JsonProperty("inviter")]
|
||||
public Optional<User> 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; }
|
||||
}
|
||||
}
|
||||
19
src/Discord.Net.WebSocket/API/Gateway/InviteDeletedEvent.cs
Normal file
19
src/Discord.Net.WebSocket/API/Gateway/InviteDeletedEvent.cs
Normal file
@@ -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<ulong> GuildID { get; set; }
|
||||
[JsonProperty("code")]
|
||||
public string Code { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
using System.Collections.Generic;
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Gateway
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Gateway
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
using System.Collections.Generic;
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Gateway
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Gateway
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
19
src/Discord.Net.WebSocket/API/Gateway/ThreadListSyncEvent.cs
Normal file
19
src/Discord.Net.WebSocket/API/Gateway/ThreadListSyncEvent.cs
Normal file
@@ -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<ulong[]> ChannelIds { get; set; }
|
||||
|
||||
[JsonProperty("threads")]
|
||||
public Channel[] Threads { get; set; }
|
||||
|
||||
[JsonProperty("members")]
|
||||
public ThreadMember[] Members { get; set; }
|
||||
}
|
||||
}
|
||||
22
src/Discord.Net.WebSocket/API/Gateway/ThreadMembersUpdate.cs
Normal file
22
src/Discord.Net.WebSocket/API/Gateway/ThreadMembersUpdate.cs
Normal file
@@ -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<ThreadMember[]> AddedMembers { get; set; }
|
||||
|
||||
[JsonProperty("removed_member_ids")]
|
||||
public Optional<ulong[]> RemovedMemberIds { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Gateway
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Gateway
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Gateway
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Gateway
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Voice
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Voice
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Voice
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Voice
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Voice
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#pragma warning disable CS1591
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Voice
|
||||
|
||||
63
src/Discord.Net.WebSocket/API/Voice/VoiceCloseCode.cs
Normal file
63
src/Discord.Net.WebSocket/API/Voice/VoiceCloseCode.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Discord.API.Voice
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents generic op codes for voice disconnect.
|
||||
/// </summary>
|
||||
public enum VoiceCloseCode
|
||||
{
|
||||
/// <summary>
|
||||
/// You sent an invalid opcode.
|
||||
/// </summary>
|
||||
UnknownOpcode = 4001,
|
||||
/// <summary>
|
||||
/// You sent an invalid payload in your identifying to the Gateway.
|
||||
/// </summary>
|
||||
DecodeFailure = 4002,
|
||||
/// <summary>
|
||||
/// You sent a payload before identifying with the Gateway.
|
||||
/// </summary>
|
||||
NotAuthenticated = 4003,
|
||||
/// <summary>
|
||||
/// The token you sent in your identify payload is incorrect.
|
||||
/// </summary>
|
||||
AuthenticationFailed = 4004,
|
||||
/// <summary>
|
||||
/// You sent more than one identify payload. Stahp.
|
||||
/// </summary>
|
||||
AlreadyAuthenticated = 4005,
|
||||
/// <summary>
|
||||
/// Your session is no longer valid.
|
||||
/// </summary>
|
||||
SessionNolongerValid = 4006,
|
||||
/// <summary>
|
||||
/// Your session has timed out.
|
||||
/// </summary>
|
||||
SessionTimeout = 4009,
|
||||
/// <summary>
|
||||
/// We can't find the server you're trying to connect to.
|
||||
/// </summary>
|
||||
ServerNotFound = 4011,
|
||||
/// <summary>
|
||||
/// We didn't recognize the protocol you sent.
|
||||
/// </summary>
|
||||
UnknownProtocol = 4012,
|
||||
/// <summary>
|
||||
/// Channel was deleted, you were kicked, voice server changed, or the main gateway session was dropped. Should not reconnect.
|
||||
/// </summary>
|
||||
Disconnected = 4014,
|
||||
/// <summary>
|
||||
/// The server crashed. Our bad! Try resuming.
|
||||
/// </summary>
|
||||
VoiceServerCrashed = 4015,
|
||||
/// <summary>
|
||||
/// We didn't recognize your encryption.
|
||||
/// </summary>
|
||||
UnknownEncryptionMode = 4016,
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
#pragma warning disable CS1591
|
||||
namespace Discord.API.Voice
|
||||
{
|
||||
internal enum VoiceOpCode : byte
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
/// <summary> Fired when a channel is created. </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
@@ -23,7 +24,7 @@ namespace Discord.WebSocket
|
||||
/// <code language="cs" region="ChannelCreated"
|
||||
/// source="..\Discord.Net.Examples\WebSocket\BaseSocketClient.Events.Examples.cs"/>
|
||||
/// </example>
|
||||
public event Func<SocketChannel, Task> ChannelCreated
|
||||
public event Func<SocketChannel, Task> ChannelCreated
|
||||
{
|
||||
add { _channelCreatedEvent.Add(value); }
|
||||
remove { _channelCreatedEvent.Remove(value); }
|
||||
@@ -45,7 +46,8 @@ namespace Discord.WebSocket
|
||||
/// <code language="cs" region="ChannelDestroyed"
|
||||
/// source="..\Discord.Net.Examples\WebSocket\BaseSocketClient.Events.Examples.cs"/>
|
||||
/// </example>
|
||||
public event Func<SocketChannel, Task> ChannelDestroyed {
|
||||
public event Func<SocketChannel, Task> ChannelDestroyed
|
||||
{
|
||||
add { _channelDestroyedEvent.Add(value); }
|
||||
remove { _channelDestroyedEvent.Remove(value); }
|
||||
}
|
||||
@@ -67,13 +69,15 @@ namespace Discord.WebSocket
|
||||
/// <code language="cs" region="ChannelUpdated"
|
||||
/// source="..\Discord.Net.Examples\WebSocket\BaseSocketClient.Events.Examples.cs"/>
|
||||
/// </example>
|
||||
public event Func<SocketChannel, SocketChannel, Task> ChannelUpdated {
|
||||
public event Func<SocketChannel, SocketChannel, Task> ChannelUpdated
|
||||
{
|
||||
add { _channelUpdatedEvent.Add(value); }
|
||||
remove { _channelUpdatedEvent.Remove(value); }
|
||||
}
|
||||
}
|
||||
internal readonly AsyncEvent<Func<SocketChannel, SocketChannel, Task>> _channelUpdatedEvent = new AsyncEvent<Func<SocketChannel, SocketChannel, Task>>();
|
||||
#endregion
|
||||
|
||||
//Messages
|
||||
#region Messages
|
||||
/// <summary> Fired when a message is received. </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
@@ -92,7 +96,8 @@ namespace Discord.WebSocket
|
||||
/// <code language="cs" region="MessageReceived"
|
||||
/// source="..\Discord.Net.Examples\WebSocket\BaseSocketClient.Events.Examples.cs"/>
|
||||
/// </example>
|
||||
public event Func<SocketMessage, Task> MessageReceived {
|
||||
public event Func<SocketMessage, Task> MessageReceived
|
||||
{
|
||||
add { _messageReceivedEvent.Add(value); }
|
||||
remove { _messageReceivedEvent.Remove(value); }
|
||||
}
|
||||
@@ -124,7 +129,9 @@ namespace Discord.WebSocket
|
||||
/// <code language="cs" region="MessageDeleted"
|
||||
/// source="..\Discord.Net.Examples\WebSocket\BaseSocketClient.Events.Examples.cs" />
|
||||
/// </example>
|
||||
public event Func<Cacheable<IMessage, ulong>, Cacheable<IMessageChannel, ulong>, Task> MessageDeleted {
|
||||
|
||||
public event Func<Cacheable<IMessage, ulong>, Cacheable<IMessageChannel, ulong>, Task> MessageDeleted
|
||||
{
|
||||
add { _messageDeletedEvent.Add(value); }
|
||||
remove { _messageDeletedEvent.Remove(value); }
|
||||
}
|
||||
@@ -182,7 +189,8 @@ namespace Discord.WebSocket
|
||||
/// <see cref="ISocketMessageChannel"/> parameter.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public event Func<Cacheable<IMessage, ulong>, SocketMessage, ISocketMessageChannel, Task> MessageUpdated {
|
||||
public event Func<Cacheable<IMessage, ulong>, SocketMessage, ISocketMessageChannel, Task> MessageUpdated
|
||||
{
|
||||
add { _messageUpdatedEvent.Add(value); }
|
||||
remove { _messageUpdatedEvent.Remove(value); }
|
||||
}
|
||||
@@ -217,19 +225,22 @@ namespace Discord.WebSocket
|
||||
/// <code language="cs" region="ReactionAdded"
|
||||
/// source="..\Discord.Net.Examples\WebSocket\BaseSocketClient.Events.Examples.cs"/>
|
||||
/// </example>
|
||||
public event Func<Cacheable<IUserMessage, ulong>, Cacheable<IMessageChannel, ulong>, SocketReaction, Task> ReactionAdded {
|
||||
public event Func<Cacheable<IUserMessage, ulong>, Cacheable<IMessageChannel, ulong>, SocketReaction, Task> ReactionAdded
|
||||
{
|
||||
add { _reactionAddedEvent.Add(value); }
|
||||
remove { _reactionAddedEvent.Remove(value); }
|
||||
}
|
||||
internal readonly AsyncEvent<Func<Cacheable<IUserMessage, ulong>, Cacheable<IMessageChannel, ulong>, SocketReaction, Task>> _reactionAddedEvent = new AsyncEvent<Func<Cacheable<IUserMessage, ulong>, Cacheable<IMessageChannel, ulong>, SocketReaction, Task>>();
|
||||
/// <summary> Fired when a reaction is removed from a message. </summary>
|
||||
public event Func<Cacheable<IUserMessage, ulong>, Cacheable<IMessageChannel, ulong>, SocketReaction, Task> ReactionRemoved {
|
||||
public event Func<Cacheable<IUserMessage, ulong>, Cacheable<IMessageChannel, ulong>, SocketReaction, Task> ReactionRemoved
|
||||
{
|
||||
add { _reactionRemovedEvent.Add(value); }
|
||||
remove { _reactionRemovedEvent.Remove(value); }
|
||||
}
|
||||
internal readonly AsyncEvent<Func<Cacheable<IUserMessage, ulong>, Cacheable<IMessageChannel, ulong>, SocketReaction, Task>> _reactionRemovedEvent = new AsyncEvent<Func<Cacheable<IUserMessage, ulong>, Cacheable<IMessageChannel, ulong>, SocketReaction, Task>>();
|
||||
/// <summary> Fired when all reactions to a message are cleared. </summary>
|
||||
public event Func<Cacheable<IUserMessage, ulong>, Cacheable<IMessageChannel, ulong>, Task> ReactionsCleared {
|
||||
public event Func<Cacheable<IUserMessage, ulong>, Cacheable<IMessageChannel, ulong>, Task> ReactionsCleared
|
||||
{
|
||||
add { _reactionsClearedEvent.Add(value); }
|
||||
remove { _reactionsClearedEvent.Remove(value); }
|
||||
}
|
||||
@@ -256,104 +267,200 @@ namespace Discord.WebSocket
|
||||
remove { _reactionsRemovedForEmoteEvent.Remove(value); }
|
||||
}
|
||||
internal readonly AsyncEvent<Func<Cacheable<IUserMessage, ulong>, Cacheable<IMessageChannel, ulong>, IEmote, Task>> _reactionsRemovedForEmoteEvent = new AsyncEvent<Func<Cacheable<IUserMessage, ulong>, Cacheable<IMessageChannel, ulong>, IEmote, Task>>();
|
||||
#endregion
|
||||
|
||||
//Roles
|
||||
#region Roles
|
||||
/// <summary> Fired when a role is created. </summary>
|
||||
public event Func<SocketRole, Task> RoleCreated {
|
||||
public event Func<SocketRole, Task> RoleCreated
|
||||
{
|
||||
add { _roleCreatedEvent.Add(value); }
|
||||
remove { _roleCreatedEvent.Remove(value); }
|
||||
}
|
||||
internal readonly AsyncEvent<Func<SocketRole, Task>> _roleCreatedEvent = new AsyncEvent<Func<SocketRole, Task>>();
|
||||
/// <summary> Fired when a role is deleted. </summary>
|
||||
public event Func<SocketRole, Task> RoleDeleted {
|
||||
public event Func<SocketRole, Task> RoleDeleted
|
||||
{
|
||||
add { _roleDeletedEvent.Add(value); }
|
||||
remove { _roleDeletedEvent.Remove(value); }
|
||||
}
|
||||
internal readonly AsyncEvent<Func<SocketRole, Task>> _roleDeletedEvent = new AsyncEvent<Func<SocketRole, Task>>();
|
||||
/// <summary> Fired when a role is updated. </summary>
|
||||
public event Func<SocketRole, SocketRole, Task> RoleUpdated {
|
||||
public event Func<SocketRole, SocketRole, Task> RoleUpdated
|
||||
{
|
||||
add { _roleUpdatedEvent.Add(value); }
|
||||
remove { _roleUpdatedEvent.Remove(value); }
|
||||
}
|
||||
internal readonly AsyncEvent<Func<SocketRole, SocketRole, Task>> _roleUpdatedEvent = new AsyncEvent<Func<SocketRole, SocketRole, Task>>();
|
||||
#endregion
|
||||
|
||||
//Guilds
|
||||
#region Guilds
|
||||
/// <summary> Fired when the connected account joins a guild. </summary>
|
||||
public event Func<SocketGuild, Task> JoinedGuild {
|
||||
public event Func<SocketGuild, Task> JoinedGuild
|
||||
{
|
||||
add { _joinedGuildEvent.Add(value); }
|
||||
remove { _joinedGuildEvent.Remove(value); }
|
||||
}
|
||||
internal readonly AsyncEvent<Func<SocketGuild, Task>> _joinedGuildEvent = new AsyncEvent<Func<SocketGuild, Task>>();
|
||||
/// <summary> Fired when the connected account leaves a guild. </summary>
|
||||
public event Func<SocketGuild, Task> LeftGuild {
|
||||
public event Func<SocketGuild, Task> LeftGuild
|
||||
{
|
||||
add { _leftGuildEvent.Add(value); }
|
||||
remove { _leftGuildEvent.Remove(value); }
|
||||
}
|
||||
internal readonly AsyncEvent<Func<SocketGuild, Task>> _leftGuildEvent = new AsyncEvent<Func<SocketGuild, Task>>();
|
||||
/// <summary> Fired when a guild becomes available. </summary>
|
||||
public event Func<SocketGuild, Task> GuildAvailable {
|
||||
public event Func<SocketGuild, Task> GuildAvailable
|
||||
{
|
||||
add { _guildAvailableEvent.Add(value); }
|
||||
remove { _guildAvailableEvent.Remove(value); }
|
||||
}
|
||||
internal readonly AsyncEvent<Func<SocketGuild, Task>> _guildAvailableEvent = new AsyncEvent<Func<SocketGuild, Task>>();
|
||||
/// <summary> Fired when a guild becomes unavailable. </summary>
|
||||
public event Func<SocketGuild, Task> GuildUnavailable {
|
||||
public event Func<SocketGuild, Task> GuildUnavailable
|
||||
{
|
||||
add { _guildUnavailableEvent.Add(value); }
|
||||
remove { _guildUnavailableEvent.Remove(value); }
|
||||
}
|
||||
internal readonly AsyncEvent<Func<SocketGuild, Task>> _guildUnavailableEvent = new AsyncEvent<Func<SocketGuild, Task>>();
|
||||
/// <summary> Fired when offline guild members are downloaded. </summary>
|
||||
public event Func<SocketGuild, Task> GuildMembersDownloaded {
|
||||
public event Func<SocketGuild, Task> GuildMembersDownloaded
|
||||
{
|
||||
add { _guildMembersDownloadedEvent.Add(value); }
|
||||
remove { _guildMembersDownloadedEvent.Remove(value); }
|
||||
}
|
||||
internal readonly AsyncEvent<Func<SocketGuild, Task>> _guildMembersDownloadedEvent = new AsyncEvent<Func<SocketGuild, Task>>();
|
||||
/// <summary> Fired when a guild is updated. </summary>
|
||||
public event Func<SocketGuild, SocketGuild, Task> GuildUpdated {
|
||||
public event Func<SocketGuild, SocketGuild, Task> GuildUpdated
|
||||
{
|
||||
add { _guildUpdatedEvent.Add(value); }
|
||||
remove { _guildUpdatedEvent.Remove(value); }
|
||||
}
|
||||
internal readonly AsyncEvent<Func<SocketGuild, SocketGuild, Task>> _guildUpdatedEvent = new AsyncEvent<Func<SocketGuild, SocketGuild, Task>>();
|
||||
/// <summary>Fired when a user leaves without agreeing to the member screening </summary>
|
||||
public event Func<Cacheable<SocketGuildUser, ulong>, SocketGuild, Task> GuildJoinRequestDeleted
|
||||
{
|
||||
add { _guildJoinRequestDeletedEvent.Add(value); }
|
||||
remove { _guildJoinRequestDeletedEvent.Remove(value); }
|
||||
}
|
||||
internal readonly AsyncEvent<Func<Cacheable<SocketGuildUser, ulong>, SocketGuild, Task>> _guildJoinRequestDeletedEvent = new AsyncEvent<Func<Cacheable<SocketGuildUser, ulong>, SocketGuild, Task>>();
|
||||
#endregion
|
||||
|
||||
//Users
|
||||
#region Guild Events
|
||||
|
||||
/// <summary>
|
||||
/// Fired when a guild event is created.
|
||||
/// </summary>
|
||||
public event Func<SocketGuildEvent, Task> GuildScheduledEventCreated
|
||||
{
|
||||
add { _guildScheduledEventCreated.Add(value); }
|
||||
remove { _guildScheduledEventCreated.Remove(value); }
|
||||
}
|
||||
internal readonly AsyncEvent<Func<SocketGuildEvent, Task>> _guildScheduledEventCreated = new AsyncEvent<Func<SocketGuildEvent, Task>>();
|
||||
|
||||
/// <summary>
|
||||
/// Fired when a guild event is updated.
|
||||
/// </summary>
|
||||
public event Func<Cacheable<SocketGuildEvent, ulong>, SocketGuildEvent, Task> GuildScheduledEventUpdated
|
||||
{
|
||||
add { _guildScheduledEventUpdated.Add(value); }
|
||||
remove { _guildScheduledEventUpdated.Remove(value); }
|
||||
}
|
||||
internal readonly AsyncEvent<Func<Cacheable<SocketGuildEvent, ulong>, SocketGuildEvent, Task>> _guildScheduledEventUpdated = new AsyncEvent<Func<Cacheable<SocketGuildEvent, ulong>, SocketGuildEvent, Task>>();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Fired when a guild event is cancelled.
|
||||
/// </summary>
|
||||
public event Func<SocketGuildEvent, Task> GuildScheduledEventCancelled
|
||||
{
|
||||
add { _guildScheduledEventCancelled.Add(value); }
|
||||
remove { _guildScheduledEventCancelled.Remove(value); }
|
||||
}
|
||||
internal readonly AsyncEvent<Func<SocketGuildEvent, Task>> _guildScheduledEventCancelled = new AsyncEvent<Func<SocketGuildEvent, Task>>();
|
||||
|
||||
/// <summary>
|
||||
/// Fired when a guild event is completed.
|
||||
/// </summary>
|
||||
public event Func<SocketGuildEvent, Task> GuildScheduledEventCompleted
|
||||
{
|
||||
add { _guildScheduledEventCompleted.Add(value); }
|
||||
remove { _guildScheduledEventCompleted.Remove(value); }
|
||||
}
|
||||
internal readonly AsyncEvent<Func<SocketGuildEvent, Task>> _guildScheduledEventCompleted = new AsyncEvent<Func<SocketGuildEvent, Task>>();
|
||||
|
||||
/// <summary>
|
||||
/// Fired when a guild event is started.
|
||||
/// </summary>
|
||||
public event Func<SocketGuildEvent, Task> GuildScheduledEventStarted
|
||||
{
|
||||
add { _guildScheduledEventStarted.Add(value); }
|
||||
remove { _guildScheduledEventStarted.Remove(value); }
|
||||
}
|
||||
internal readonly AsyncEvent<Func<SocketGuildEvent, Task>> _guildScheduledEventStarted = new AsyncEvent<Func<SocketGuildEvent, Task>>();
|
||||
|
||||
public event Func<Cacheable<SocketUser, RestUser, IUser, ulong>, SocketGuildEvent, Task> GuildScheduledEventUserAdd
|
||||
{
|
||||
add { _guildScheduledEventUserAdd.Add(value); }
|
||||
remove { _guildScheduledEventUserAdd.Remove(value); }
|
||||
}
|
||||
internal readonly AsyncEvent<Func<Cacheable<SocketUser, RestUser, IUser, ulong>, SocketGuildEvent, Task>> _guildScheduledEventUserAdd = new AsyncEvent<Func<Cacheable<SocketUser, RestUser, IUser, ulong>, SocketGuildEvent, Task>>();
|
||||
|
||||
public event Func<Cacheable<SocketUser, RestUser, IUser, ulong>, SocketGuildEvent, Task> GuildScheduledEventUserRemove
|
||||
{
|
||||
add { _guildScheduledEventUserRemove.Add(value); }
|
||||
remove { _guildScheduledEventUserRemove.Remove(value); }
|
||||
}
|
||||
internal readonly AsyncEvent<Func<Cacheable<SocketUser, RestUser, IUser, ulong>, SocketGuildEvent, Task>> _guildScheduledEventUserRemove = new AsyncEvent<Func<Cacheable<SocketUser, RestUser, IUser, ulong>, SocketGuildEvent, Task>>();
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Users
|
||||
/// <summary> Fired when a user joins a guild. </summary>
|
||||
public event Func<SocketGuildUser, Task> UserJoined {
|
||||
public event Func<SocketGuildUser, Task> UserJoined
|
||||
{
|
||||
add { _userJoinedEvent.Add(value); }
|
||||
remove { _userJoinedEvent.Remove(value); }
|
||||
}
|
||||
internal readonly AsyncEvent<Func<SocketGuildUser, Task>> _userJoinedEvent = new AsyncEvent<Func<SocketGuildUser, Task>>();
|
||||
/// <summary> Fired when a user leaves a guild. </summary>
|
||||
public event Func<SocketGuildUser, Task> UserLeft {
|
||||
public event Func<SocketGuildUser, Task> UserLeft
|
||||
{
|
||||
add { _userLeftEvent.Add(value); }
|
||||
remove { _userLeftEvent.Remove(value); }
|
||||
}
|
||||
internal readonly AsyncEvent<Func<SocketGuildUser, Task>> _userLeftEvent = new AsyncEvent<Func<SocketGuildUser, Task>>();
|
||||
/// <summary> Fired when a user is banned from a guild. </summary>
|
||||
public event Func<SocketUser, SocketGuild, Task> UserBanned {
|
||||
public event Func<SocketUser, SocketGuild, Task> UserBanned
|
||||
{
|
||||
add { _userBannedEvent.Add(value); }
|
||||
remove { _userBannedEvent.Remove(value); }
|
||||
}
|
||||
internal readonly AsyncEvent<Func<SocketUser, SocketGuild, Task>> _userBannedEvent = new AsyncEvent<Func<SocketUser, SocketGuild, Task>>();
|
||||
/// <summary> Fired when a user is unbanned from a guild. </summary>
|
||||
public event Func<SocketUser, SocketGuild, Task> UserUnbanned {
|
||||
public event Func<SocketUser, SocketGuild, Task> UserUnbanned
|
||||
{
|
||||
add { _userUnbannedEvent.Add(value); }
|
||||
remove { _userUnbannedEvent.Remove(value); }
|
||||
}
|
||||
internal readonly AsyncEvent<Func<SocketUser, SocketGuild, Task>> _userUnbannedEvent = new AsyncEvent<Func<SocketUser, SocketGuild, Task>>();
|
||||
/// <summary> Fired when a user is updated. </summary>
|
||||
public event Func<SocketUser, SocketUser, Task> UserUpdated {
|
||||
public event Func<SocketUser, SocketUser, Task> UserUpdated
|
||||
{
|
||||
add { _userUpdatedEvent.Add(value); }
|
||||
remove { _userUpdatedEvent.Remove(value); }
|
||||
}
|
||||
internal readonly AsyncEvent<Func<SocketUser, SocketUser, Task>> _userUpdatedEvent = new AsyncEvent<Func<SocketUser, SocketUser, Task>>();
|
||||
/// <summary> Fired when a guild member is updated, or a member presence is updated. </summary>
|
||||
public event Func<Cacheable<SocketGuildUser, ulong>, SocketGuildUser, Task> GuildMemberUpdated {
|
||||
public event Func<Cacheable<SocketGuildUser, ulong>, SocketGuildUser, Task> GuildMemberUpdated
|
||||
{
|
||||
add { _guildMemberUpdatedEvent.Add(value); }
|
||||
remove { _guildMemberUpdatedEvent.Remove(value); }
|
||||
}
|
||||
internal readonly AsyncEvent<Func<Cacheable<SocketGuildUser, ulong>, SocketGuildUser, Task>> _guildMemberUpdatedEvent = new AsyncEvent<Func<Cacheable<SocketGuildUser, ulong>, SocketGuildUser, Task>>();
|
||||
internal readonly AsyncEvent<Func<Cacheable<SocketGuildUser, ulong>, SocketGuildUser, Task>> _guildMemberUpdatedEvent = new AsyncEvent<Func<Cacheable<SocketGuildUser, ulong>, SocketGuildUser, Task>>();
|
||||
/// <summary> Fired when a user joins, leaves, or moves voice channels. </summary>
|
||||
public event Func<SocketUser, SocketVoiceState, SocketVoiceState, Task> UserVoiceStateUpdated {
|
||||
public event Func<SocketUser, SocketVoiceState, SocketVoiceState, Task> UserVoiceStateUpdated
|
||||
{
|
||||
add { _userVoiceStateUpdatedEvent.Add(value); }
|
||||
remove { _userVoiceStateUpdatedEvent.Remove(value); }
|
||||
}
|
||||
@@ -361,36 +468,41 @@ namespace Discord.WebSocket
|
||||
/// <summary> Fired when the bot connects to a Discord voice server. </summary>
|
||||
public event Func<SocketVoiceServer, Task> VoiceServerUpdated
|
||||
{
|
||||
add { _voiceServerUpdatedEvent.Add(value); }
|
||||
add { _voiceServerUpdatedEvent.Add(value); }
|
||||
remove { _voiceServerUpdatedEvent.Remove(value); }
|
||||
}
|
||||
internal readonly AsyncEvent<Func<SocketVoiceServer, Task>> _voiceServerUpdatedEvent = new AsyncEvent<Func<SocketVoiceServer, Task>>();
|
||||
/// <summary> Fired when the connected account is updated. </summary>
|
||||
public event Func<SocketSelfUser, SocketSelfUser, Task> CurrentUserUpdated {
|
||||
public event Func<SocketSelfUser, SocketSelfUser, Task> CurrentUserUpdated
|
||||
{
|
||||
add { _selfUpdatedEvent.Add(value); }
|
||||
remove { _selfUpdatedEvent.Remove(value); }
|
||||
}
|
||||
internal readonly AsyncEvent<Func<SocketSelfUser, SocketSelfUser, Task>> _selfUpdatedEvent = new AsyncEvent<Func<SocketSelfUser, SocketSelfUser, Task>>();
|
||||
/// <summary> Fired when a user starts typing. </summary>
|
||||
public event Func<Cacheable<IUser, ulong>, Cacheable<IMessageChannel, ulong>, Task> UserIsTyping {
|
||||
public event Func<Cacheable<IUser, ulong>, Cacheable<IMessageChannel, ulong>, Task> UserIsTyping
|
||||
{
|
||||
add { _userIsTypingEvent.Add(value); }
|
||||
remove { _userIsTypingEvent.Remove(value); }
|
||||
}
|
||||
internal readonly AsyncEvent<Func<Cacheable<IUser, ulong>, Cacheable<IMessageChannel, ulong>, Task>> _userIsTypingEvent = new AsyncEvent<Func<Cacheable<IUser, ulong>, Cacheable<IMessageChannel, ulong>, Task>>();
|
||||
/// <summary> Fired when a user joins a group channel. </summary>
|
||||
public event Func<SocketGroupUser, Task> RecipientAdded {
|
||||
public event Func<SocketGroupUser, Task> RecipientAdded
|
||||
{
|
||||
add { _recipientAddedEvent.Add(value); }
|
||||
remove { _recipientAddedEvent.Remove(value); }
|
||||
}
|
||||
internal readonly AsyncEvent<Func<SocketGroupUser, Task>> _recipientAddedEvent = new AsyncEvent<Func<SocketGroupUser, Task>>();
|
||||
/// <summary> Fired when a user is removed from a group channel. </summary>
|
||||
public event Func<SocketGroupUser, Task> RecipientRemoved {
|
||||
public event Func<SocketGroupUser, Task> RecipientRemoved
|
||||
{
|
||||
add { _recipientRemovedEvent.Add(value); }
|
||||
remove { _recipientRemovedEvent.Remove(value); }
|
||||
}
|
||||
internal readonly AsyncEvent<Func<SocketGroupUser, Task>> _recipientRemovedEvent = new AsyncEvent<Func<SocketGroupUser, Task>>();
|
||||
#endregion
|
||||
|
||||
//Invites
|
||||
#region Invites
|
||||
/// <summary>
|
||||
/// Fired when an invite is created.
|
||||
/// </summary>
|
||||
@@ -431,5 +543,292 @@ namespace Discord.WebSocket
|
||||
remove { _inviteDeletedEvent.Remove(value); }
|
||||
}
|
||||
internal readonly AsyncEvent<Func<SocketGuildChannel, string, Task>> _inviteDeletedEvent = new AsyncEvent<Func<SocketGuildChannel, string, Task>>();
|
||||
#endregion
|
||||
|
||||
#region Interactions
|
||||
/// <summary>
|
||||
/// Fired when an Interaction is created. This event covers all types of interactions including but not limited to: buttons, select menus, slash commands, autocompletes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This event is fired when an interaction is created. The event handler must return a
|
||||
/// <see cref="Task"/> and accept a <see cref="SocketInteraction"/> as its parameter.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The interaction created will be passed into the <see cref="SocketInteraction"/> parameter.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public event Func<SocketInteraction, Task> InteractionCreated
|
||||
{
|
||||
add { _interactionCreatedEvent.Add(value); }
|
||||
remove { _interactionCreatedEvent.Remove(value); }
|
||||
}
|
||||
internal readonly AsyncEvent<Func<SocketInteraction, Task>> _interactionCreatedEvent = new AsyncEvent<Func<SocketInteraction, Task>>();
|
||||
|
||||
/// <summary>
|
||||
/// Fired when a button is clicked and its interaction is received.
|
||||
/// </summary>
|
||||
public event Func<SocketMessageComponent, Task> ButtonExecuted
|
||||
{
|
||||
add => _buttonExecuted.Add(value);
|
||||
remove => _buttonExecuted.Remove(value);
|
||||
}
|
||||
internal readonly AsyncEvent<Func<SocketMessageComponent, Task>> _buttonExecuted = new AsyncEvent<Func<SocketMessageComponent, Task>>();
|
||||
|
||||
/// <summary>
|
||||
/// Fired when a select menu is used and its interaction is received.
|
||||
/// </summary>
|
||||
public event Func<SocketMessageComponent, Task> SelectMenuExecuted
|
||||
{
|
||||
add => _selectMenuExecuted.Add(value);
|
||||
remove => _selectMenuExecuted.Remove(value);
|
||||
}
|
||||
internal readonly AsyncEvent<Func<SocketMessageComponent, Task>> _selectMenuExecuted = new AsyncEvent<Func<SocketMessageComponent, Task>>();
|
||||
/// <summary>
|
||||
/// Fired when a slash command is used and its interaction is received.
|
||||
/// </summary>
|
||||
public event Func<SocketSlashCommand, Task> SlashCommandExecuted
|
||||
{
|
||||
add => _slashCommandExecuted.Add(value);
|
||||
remove => _slashCommandExecuted.Remove(value);
|
||||
}
|
||||
internal readonly AsyncEvent<Func<SocketSlashCommand, Task>> _slashCommandExecuted = new AsyncEvent<Func<SocketSlashCommand, Task>>();
|
||||
|
||||
/// <summary>
|
||||
/// Fired when a user command is used and its interaction is received.
|
||||
/// </summary>
|
||||
public event Func<SocketUserCommand, Task> UserCommandExecuted
|
||||
{
|
||||
add => _userCommandExecuted.Add(value);
|
||||
remove => _userCommandExecuted.Remove(value);
|
||||
}
|
||||
internal readonly AsyncEvent<Func<SocketUserCommand, Task>> _userCommandExecuted = new AsyncEvent<Func<SocketUserCommand, Task>>();
|
||||
|
||||
/// <summary>
|
||||
/// Fired when a message command is used and its interaction is received.
|
||||
/// </summary>
|
||||
public event Func<SocketMessageCommand, Task> MessageCommandExecuted
|
||||
{
|
||||
add => _messageCommandExecuted.Add(value);
|
||||
remove => _messageCommandExecuted.Remove(value);
|
||||
}
|
||||
internal readonly AsyncEvent<Func<SocketMessageCommand, Task>> _messageCommandExecuted = new AsyncEvent<Func<SocketMessageCommand, Task>>();
|
||||
/// <summary>
|
||||
/// Fired when an autocomplete is used and its interaction is received.
|
||||
/// </summary>
|
||||
public event Func<SocketAutocompleteInteraction, Task> AutocompleteExecuted
|
||||
{
|
||||
add => _autocompleteExecuted.Add(value);
|
||||
remove => _autocompleteExecuted.Remove(value);
|
||||
}
|
||||
internal readonly AsyncEvent<Func<SocketAutocompleteInteraction, Task>> _autocompleteExecuted = new AsyncEvent<Func<SocketAutocompleteInteraction, Task>>();
|
||||
|
||||
/// <summary>
|
||||
/// Fired when a guild application command is created.
|
||||
///</summary>
|
||||
///<remarks>
|
||||
/// <para>
|
||||
/// This event is fired when an application command is created. The event handler must return a
|
||||
/// <see cref="Task"/> and accept a <see cref="SocketApplicationCommand"/> as its parameter.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The command that was deleted will be passed into the <see cref="SocketApplicationCommand"/> parameter.
|
||||
/// </para>
|
||||
/// <note>
|
||||
/// <b>This event is an undocumented discord event and may break at any time, its not recommended to rely on this event</b>
|
||||
/// </note>
|
||||
/// </remarks>
|
||||
public event Func<SocketApplicationCommand, Task> ApplicationCommandCreated
|
||||
{
|
||||
add { _applicationCommandCreated.Add(value); }
|
||||
remove { _applicationCommandCreated.Remove(value); }
|
||||
}
|
||||
internal readonly AsyncEvent<Func<SocketApplicationCommand, Task>> _applicationCommandCreated = new AsyncEvent<Func<SocketApplicationCommand, Task>>();
|
||||
|
||||
/// <summary>
|
||||
/// Fired when a guild application command is updated.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This event is fired when an application command is updated. The event handler must return a
|
||||
/// <see cref="Task"/> and accept a <see cref="SocketApplicationCommand"/> as its parameter.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The command that was deleted will be passed into the <see cref="SocketApplicationCommand"/> parameter.
|
||||
/// </para>
|
||||
/// <note>
|
||||
/// <b>This event is an undocumented discord event and may break at any time, its not recommended to rely on this event</b>
|
||||
/// </note>
|
||||
/// </remarks>
|
||||
public event Func<SocketApplicationCommand, Task> ApplicationCommandUpdated
|
||||
{
|
||||
add { _applicationCommandUpdated.Add(value); }
|
||||
remove { _applicationCommandUpdated.Remove(value); }
|
||||
}
|
||||
internal readonly AsyncEvent<Func<SocketApplicationCommand, Task>> _applicationCommandUpdated = new AsyncEvent<Func<SocketApplicationCommand, Task>>();
|
||||
|
||||
/// <summary>
|
||||
/// Fired when a guild application command is deleted.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This event is fired when an application command is deleted. The event handler must return a
|
||||
/// <see cref="Task"/> and accept a <see cref="SocketApplicationCommand"/> as its parameter.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The command that was deleted will be passed into the <see cref="SocketApplicationCommand"/> parameter.
|
||||
/// </para>
|
||||
/// <note>
|
||||
/// <b>This event is an undocumented discord event and may break at any time, its not recommended to rely on this event</b>
|
||||
/// </note>
|
||||
/// </remarks>
|
||||
public event Func<SocketApplicationCommand, Task> ApplicationCommandDeleted
|
||||
{
|
||||
add { _applicationCommandDeleted.Add(value); }
|
||||
remove { _applicationCommandDeleted.Remove(value); }
|
||||
}
|
||||
internal readonly AsyncEvent<Func<SocketApplicationCommand, Task>> _applicationCommandDeleted = new AsyncEvent<Func<SocketApplicationCommand, Task>>();
|
||||
|
||||
/// <summary>
|
||||
/// Fired when a thread is created within a guild, or when the current user is added to a thread.
|
||||
/// </summary>
|
||||
public event Func<SocketThreadChannel, Task> ThreadCreated
|
||||
{
|
||||
add { _threadCreated.Add(value); }
|
||||
remove { _threadCreated.Remove(value); }
|
||||
}
|
||||
internal readonly AsyncEvent<Func<SocketThreadChannel, Task>> _threadCreated = new AsyncEvent<Func<SocketThreadChannel, Task>>();
|
||||
|
||||
/// <summary>
|
||||
/// Fired when a thread is updated within a guild.
|
||||
/// </summary>
|
||||
public event Func<Cacheable<SocketThreadChannel, ulong>, SocketThreadChannel, Task> ThreadUpdated
|
||||
{
|
||||
add { _threadUpdated.Add(value); }
|
||||
remove { _threadUpdated.Remove(value); }
|
||||
}
|
||||
|
||||
internal readonly AsyncEvent<Func<Cacheable<SocketThreadChannel, ulong>, SocketThreadChannel, Task>> _threadUpdated = new();
|
||||
|
||||
/// <summary>
|
||||
/// Fired when a thread is deleted.
|
||||
/// </summary>
|
||||
public event Func<Cacheable<SocketThreadChannel, ulong>, Task> ThreadDeleted
|
||||
{
|
||||
add { _threadDeleted.Add(value); }
|
||||
remove { _threadDeleted.Remove(value); }
|
||||
}
|
||||
internal readonly AsyncEvent<Func<Cacheable<SocketThreadChannel, ulong>, Task>> _threadDeleted = new AsyncEvent<Func<Cacheable<SocketThreadChannel, ulong>, Task>>();
|
||||
|
||||
/// <summary>
|
||||
/// Fired when a user joins a thread
|
||||
/// </summary>
|
||||
public event Func<SocketThreadUser, Task> ThreadMemberJoined
|
||||
{
|
||||
add { _threadMemberJoined.Add(value); }
|
||||
remove { _threadMemberJoined.Remove(value); }
|
||||
}
|
||||
internal readonly AsyncEvent<Func<SocketThreadUser, Task>> _threadMemberJoined = new AsyncEvent<Func<SocketThreadUser, Task>>();
|
||||
|
||||
/// <summary>
|
||||
/// Fired when a user leaves a thread
|
||||
/// </summary>
|
||||
public event Func<SocketThreadUser, Task> ThreadMemberLeft
|
||||
{
|
||||
add { _threadMemberLeft.Add(value); }
|
||||
remove { _threadMemberLeft.Remove(value); }
|
||||
}
|
||||
internal readonly AsyncEvent<Func<SocketThreadUser, Task>> _threadMemberLeft = new AsyncEvent<Func<SocketThreadUser, Task>>();
|
||||
|
||||
/// <summary>
|
||||
/// Fired when a stage is started.
|
||||
/// </summary>
|
||||
public event Func<SocketStageChannel, Task> StageStarted
|
||||
{
|
||||
add { _stageStarted.Add(value); }
|
||||
remove { _stageStarted.Remove(value); }
|
||||
}
|
||||
internal readonly AsyncEvent<Func<SocketStageChannel, Task>> _stageStarted = new AsyncEvent<Func<SocketStageChannel, Task>>();
|
||||
|
||||
/// <summary>
|
||||
/// Fired when a stage ends.
|
||||
/// </summary>
|
||||
public event Func<SocketStageChannel, Task> StageEnded
|
||||
{
|
||||
add { _stageEnded.Add(value); }
|
||||
remove { _stageEnded.Remove(value); }
|
||||
}
|
||||
internal readonly AsyncEvent<Func<SocketStageChannel, Task>> _stageEnded = new AsyncEvent<Func<SocketStageChannel, Task>>();
|
||||
|
||||
/// <summary>
|
||||
/// Fired when a stage is updated.
|
||||
/// </summary>
|
||||
public event Func<SocketStageChannel, SocketStageChannel, Task> StageUpdated
|
||||
{
|
||||
add { _stageUpdated.Add(value); }
|
||||
remove { _stageUpdated.Remove(value); }
|
||||
}
|
||||
internal readonly AsyncEvent<Func<SocketStageChannel, SocketStageChannel, Task>> _stageUpdated = new AsyncEvent<Func<SocketStageChannel, SocketStageChannel, Task>>();
|
||||
|
||||
/// <summary>
|
||||
/// Fired when a user requests to speak within a stage channel.
|
||||
/// </summary>
|
||||
public event Func<SocketStageChannel, SocketGuildUser, Task> RequestToSpeak
|
||||
{
|
||||
add { _requestToSpeak.Add(value); }
|
||||
remove { _requestToSpeak.Remove(value); }
|
||||
}
|
||||
internal readonly AsyncEvent<Func<SocketStageChannel, SocketGuildUser, Task>> _requestToSpeak = new AsyncEvent<Func<SocketStageChannel, SocketGuildUser, Task>>();
|
||||
|
||||
/// <summary>
|
||||
/// Fired when a speaker is added in a stage channel.
|
||||
/// </summary>
|
||||
public event Func<SocketStageChannel, SocketGuildUser, Task> SpeakerAdded
|
||||
{
|
||||
add { _speakerAdded.Add(value); }
|
||||
remove { _speakerAdded.Remove(value); }
|
||||
}
|
||||
internal readonly AsyncEvent<Func<SocketStageChannel, SocketGuildUser, Task>> _speakerAdded = new AsyncEvent<Func<SocketStageChannel, SocketGuildUser, Task>>();
|
||||
|
||||
/// <summary>
|
||||
/// Fired when a speaker is removed from a stage channel.
|
||||
/// </summary>
|
||||
public event Func<SocketStageChannel, SocketGuildUser, Task> SpeakerRemoved
|
||||
{
|
||||
add { _speakerRemoved.Add(value); }
|
||||
remove { _speakerRemoved.Remove(value); }
|
||||
}
|
||||
internal readonly AsyncEvent<Func<SocketStageChannel, SocketGuildUser, Task>> _speakerRemoved = new AsyncEvent<Func<SocketStageChannel, SocketGuildUser, Task>>();
|
||||
|
||||
/// <summary>
|
||||
/// Fired when a sticker in a guild is created.
|
||||
/// </summary>
|
||||
public event Func<SocketCustomSticker, Task> GuildStickerCreated
|
||||
{
|
||||
add { _guildStickerCreated.Add(value); }
|
||||
remove { _guildStickerCreated.Remove(value); }
|
||||
}
|
||||
internal readonly AsyncEvent<Func<SocketCustomSticker, Task>> _guildStickerCreated = new AsyncEvent<Func<SocketCustomSticker, Task>>();
|
||||
|
||||
/// <summary>
|
||||
/// Fired when a sticker in a guild is updated.
|
||||
/// </summary>
|
||||
public event Func<SocketCustomSticker, SocketCustomSticker, Task> GuildStickerUpdated
|
||||
{
|
||||
add { _guildStickerUpdated.Add(value); }
|
||||
remove { _guildStickerUpdated.Remove(value); }
|
||||
}
|
||||
internal readonly AsyncEvent<Func<SocketCustomSticker, SocketCustomSticker, Task>> _guildStickerUpdated = new AsyncEvent<Func<SocketCustomSticker, SocketCustomSticker, Task>>();
|
||||
|
||||
/// <summary>
|
||||
/// Fired when a sticker in a guild is deleted.
|
||||
/// </summary>
|
||||
public event Func<SocketCustomSticker, Task> GuildStickerDeleted
|
||||
{
|
||||
add { _guildStickerDeleted.Add(value); }
|
||||
remove { _guildStickerDeleted.Remove(value); }
|
||||
}
|
||||
internal readonly AsyncEvent<Func<SocketCustomSticker, Task>> _guildStickerDeleted = new AsyncEvent<Func<SocketCustomSticker, Task>>();
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace Discord.WebSocket
|
||||
/// </summary>
|
||||
public abstract partial class BaseSocketClient : BaseDiscordClient, IDiscordClient
|
||||
{
|
||||
#region BaseSocketClient
|
||||
protected readonly DiscordSocketConfig BaseConfig;
|
||||
|
||||
/// <summary>
|
||||
@@ -44,6 +45,10 @@ namespace Discord.WebSocket
|
||||
|
||||
internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection of default stickers.
|
||||
/// </summary>
|
||||
public abstract IReadOnlyCollection<StickerPack<SocketSticker>> DefaultStickerPacks { get; }
|
||||
/// <summary>
|
||||
/// Gets the current logged-in user.
|
||||
/// </summary>
|
||||
@@ -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);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a Discord application information for the logged-in user.
|
||||
@@ -268,8 +273,19 @@ namespace Discord.WebSocket
|
||||
/// </returns>
|
||||
public Task<RestInviteMetadata> GetInviteAsync(string inviteId, RequestOptions options = null)
|
||||
=> ClientHelper.GetInviteAsync(this, inviteId, options ?? RequestOptions.Default);
|
||||
|
||||
// IDiscordClient
|
||||
/// <summary>
|
||||
/// Gets a sticker.
|
||||
/// </summary>
|
||||
/// <param name="mode">Whether or not to allow downloading from the api.</param>
|
||||
/// <param name="id">The id of the sticker to get.</param>
|
||||
/// <param name="options">The options to be used when sending the request.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="SocketSticker"/> if found, otherwise <see langword="null"/>.
|
||||
/// </returns>
|
||||
public abstract Task<SocketSticker> GetStickerAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
|
||||
#endregion
|
||||
|
||||
#region IDiscordClient
|
||||
/// <inheritdoc />
|
||||
async Task<IApplication> IDiscordClient.GetApplicationInfoAsync(RequestOptions options)
|
||||
=> await GetApplicationInfoAsync(options).ConfigureAwait(false);
|
||||
@@ -317,5 +333,6 @@ namespace Discord.WebSocket
|
||||
{
|
||||
return await GetVoiceRegionsAsync().ConfigureAwait(false);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,12 +16,14 @@ namespace Discord.WebSocket
|
||||
private readonly ConcurrentDictionary<ulong, SocketGuild> _guilds;
|
||||
private readonly ConcurrentDictionary<ulong, SocketGlobalUser> _users;
|
||||
private readonly ConcurrentHashSet<ulong> _groupChannels;
|
||||
private readonly ConcurrentDictionary<ulong, SocketApplicationCommand> _commands;
|
||||
|
||||
internal IReadOnlyCollection<SocketChannel> Channels => _channels.ToReadOnlyCollection();
|
||||
internal IReadOnlyCollection<SocketDMChannel> DMChannels => _dmChannels.ToReadOnlyCollection();
|
||||
internal IReadOnlyCollection<SocketGroupChannel> GroupChannels => _groupChannels.Select(x => GetChannel(x) as SocketGroupChannel).ToReadOnlyCollection(_groupChannels);
|
||||
internal IReadOnlyCollection<SocketGuild> Guilds => _guilds.ToReadOnlyCollection();
|
||||
internal IReadOnlyCollection<SocketGlobalUser> Users => _users.ToReadOnlyCollection();
|
||||
internal IReadOnlyCollection<SocketApplicationCommand> Commands => _commands.ToReadOnlyCollection();
|
||||
|
||||
internal IReadOnlyCollection<ISocketPrivateChannel> PrivateChannels =>
|
||||
_dmChannels.Select(x => x.Value as ISocketPrivateChannel).Concat(
|
||||
@@ -37,6 +39,7 @@ namespace Discord.WebSocket
|
||||
_guilds = new ConcurrentDictionary<ulong, SocketGuild>(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(guildCount * CollectionMultiplier));
|
||||
_users = new ConcurrentDictionary<ulong, SocketGlobalUser>(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(estimatedUsersCount * CollectionMultiplier));
|
||||
_groupChannels = new ConcurrentHashSet<ulong>(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(10 * CollectionMultiplier));
|
||||
_commands = new ConcurrentDictionary<ulong, SocketApplicationCommand>();
|
||||
}
|
||||
|
||||
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<ulong, SocketApplicationCommand> 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<SocketApplicationCommand, bool> precondition)
|
||||
{
|
||||
var ids = _commands.Where(x => precondition(x.Value)).Select(x => x.Key);
|
||||
|
||||
foreach (var id in ids)
|
||||
_commands.TryRemove(id, out var _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace Discord.Commands
|
||||
/// <summary> The sharded variant of <see cref="ICommandContext"/>, which may contain the client, user, guild, channel, and message. </summary>
|
||||
public class ShardedCommandContext : SocketCommandContext, ICommandContext
|
||||
{
|
||||
#region ShardedCommandContext
|
||||
/// <summary> Gets the <see cref="DiscordShardedClient"/> that the command is executed with. </summary>
|
||||
public new DiscordShardedClient Client { get; }
|
||||
|
||||
@@ -17,9 +18,11 @@ namespace Discord.Commands
|
||||
/// <summary> Gets the shard ID of the command context. </summary>
|
||||
private static int GetShardId(DiscordShardedClient client, IGuild guild)
|
||||
=> guild == null ? 0 : client.GetShardIdFor(guild);
|
||||
#endregion
|
||||
|
||||
//ICommandContext
|
||||
#region ICommandContext
|
||||
/// <inheritdoc />
|
||||
IDiscordClient ICommandContext.Client => Client;
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace Discord.Commands
|
||||
/// </summary>
|
||||
public class SocketCommandContext : ICommandContext
|
||||
{
|
||||
#region SocketCommandContext
|
||||
/// <summary>
|
||||
/// Gets the <see cref="DiscordSocketClient" /> that the command is executed with.
|
||||
/// </summary>
|
||||
@@ -46,8 +47,9 @@ namespace Discord.Commands
|
||||
User = msg.Author;
|
||||
Message = msg;
|
||||
}
|
||||
#endregion
|
||||
|
||||
//ICommandContext
|
||||
#region ICommandContext
|
||||
/// <inheritdoc/>
|
||||
IDiscordClient ICommandContext.Client => Client;
|
||||
/// <inheritdoc/>
|
||||
@@ -58,5 +60,6 @@ namespace Discord.Commands
|
||||
IUser ICommandContext.User => User;
|
||||
/// <inheritdoc/>
|
||||
IUserMessage ICommandContext.Message => Message;
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="../../Discord.Net.targets" />
|
||||
<Import Project="../../StyleAnalyzer.targets"/>
|
||||
<PropertyGroup>
|
||||
@@ -13,4 +13,4 @@
|
||||
<ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.csproj" />
|
||||
<ProjectReference Include="..\Discord.Net.Rest\Discord.Net.Rest.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -1,11 +1,11 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Discord.WebSocket
|
||||
{
|
||||
public partial class DiscordShardedClient
|
||||
{
|
||||
//General
|
||||
#region General
|
||||
/// <summary> Fired when a shard is connected to the Discord gateway. </summary>
|
||||
public event Func<DiscordSocketClient, Task> ShardConnected
|
||||
{
|
||||
@@ -34,5 +34,6 @@ namespace Discord.WebSocket
|
||||
remove { _shardLatencyUpdatedEvent.Remove(value); }
|
||||
}
|
||||
private readonly AsyncEvent<Func<int, int, DiscordSocketClient, Task>> _shardLatencyUpdatedEvent = new AsyncEvent<Func<int, int, DiscordSocketClient, Task>>();
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<int, int> _shardIdsToIndex;
|
||||
private readonly bool _automaticShards;
|
||||
private int[] _shardIds;
|
||||
private DiscordSocketClient[] _shards;
|
||||
private ImmutableArray<StickerPack<SocketSticker>> _defaultStickers;
|
||||
private int _totalShards;
|
||||
private SemaphoreSlim[] _identifySemaphores;
|
||||
private object _semaphoreResetLock;
|
||||
@@ -30,7 +33,20 @@ namespace Discord.WebSocket
|
||||
/// <inheritdoc />
|
||||
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;
|
||||
}
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public override IReadOnlyCollection<StickerPack<SocketSticker>> DefaultStickerPacks
|
||||
=> _defaultStickers.ToReadOnlyCollection();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IReadOnlyCollection<SocketGuild> Guilds => GetGuilds().ToReadOnlyCollection(GetGuildCount);
|
||||
/// <inheritdoc />
|
||||
@@ -68,6 +84,7 @@ namespace Discord.WebSocket
|
||||
_shardIdsToIndex = new Dictionary<int, int>();
|
||||
config.DisplayInitialLog = false;
|
||||
_baseConfig = config;
|
||||
_defaultStickers = ImmutableArray.Create<StickerPack<SocketSticker>>();
|
||||
|
||||
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;
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public override async Task<SocketSticker> 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<StickerPack<SocketSticker>>();
|
||||
|
||||
foreach (var model in models.StickerPacks)
|
||||
{
|
||||
var stickers = model.Stickers.Select(x => SocketSticker.Create(_shards[0], x));
|
||||
|
||||
var pack = new StickerPack<SocketSticker>(
|
||||
model.Name,
|
||||
model.Id,
|
||||
model.SkuId,
|
||||
model.CoverStickerId.ToNullable(),
|
||||
model.Description,
|
||||
model.BannerAssetId,
|
||||
stickers
|
||||
);
|
||||
|
||||
builder.Add(pack);
|
||||
}
|
||||
|
||||
_defaultStickers = builder.ToImmutable();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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);
|
||||
}
|
||||
|
||||
//IDiscordClient
|
||||
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
|
||||
|
||||
#region IDiscordClient
|
||||
/// <inheritdoc />
|
||||
async Task<IApplication> 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<SocketFrame>(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<SocketFrame>(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<string, string>
|
||||
{
|
||||
["$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<ulong> 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Discord.WebSocket
|
||||
{
|
||||
public partial class DiscordSocketClient
|
||||
{
|
||||
//General
|
||||
#region General
|
||||
/// <summary> Fired when connected to the Discord gateway. </summary>
|
||||
public event Func<Task> Connected
|
||||
{
|
||||
@@ -45,5 +45,6 @@ namespace Discord.WebSocket
|
||||
internal DiscordSocketClient(DiscordSocketConfig config, DiscordRestApiClient client) : base(config, client)
|
||||
{
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -49,8 +49,31 @@ namespace Discord.WebSocket
|
||||
/// <summary>
|
||||
/// Gets or sets the total number of shards for this application.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If this is left <see langword="null"/> in a sharded client the bot will get the recommended shard
|
||||
/// count from discord and use that.
|
||||
/// </remarks>
|
||||
public int? TotalShards { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether or not the client should download the default stickers on startup.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When this is set to <see langword="false"/> default stickers arn't present and cannot be resolved by the client.
|
||||
/// This will make all default stickers have the type of <see cref="SocketUnknownSticker"/>.
|
||||
/// </remarks>
|
||||
public bool AlwaysDownloadDefaultStickers { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether or not the client should automatically resolve the stickers sent on a message.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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.
|
||||
/// </remarks>
|
||||
public bool AlwaysResolveStickers { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// By default, the Discord gateway will only send offline members if a guild has less than a certain number
|
||||
/// of members (determined by <see cref="LargeThreshold"/> in this library). This behaviour is why
|
||||
/// of members (determined by <see cref="LargeThreshold"/> in this library). This behavior is why
|
||||
/// sometimes a user may be missing from the WebSocket cache for collections such as
|
||||
/// <see cref="Discord.WebSocket.SocketGuild.Users"/>.
|
||||
/// </para>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<T>(reader);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,16 +34,19 @@ namespace Discord.WebSocket
|
||||
/// If <c>null</c>, all mentioned roles and users will be notified.
|
||||
/// </param>
|
||||
/// <param name="messageReference">The message references to be included. Used to reply to specific messages.</param>
|
||||
/// <param name="component">The message components to be included with this message. Used for interactions.</param>
|
||||
/// <param name="stickers">A collection of stickers to send with the message.</param>
|
||||
/// <param name="embeds">A array of <see cref="Embed"/>s to send with this response. Max 10.</param>
|
||||
/// <returns>
|
||||
/// A task that represents an asynchronous send operation for delivering the message. The task result
|
||||
/// contains the sent message.
|
||||
/// </returns>
|
||||
new Task<RestUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null);
|
||||
new Task<RestUserMessage> 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);
|
||||
/// <summary>
|
||||
/// Sends a file to this message channel with an optional caption.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method follows the same behavior as described in <see cref="IMessageChannel.SendFileAsync(string, string, bool, Embed, RequestOptions, bool, AllowedMentions, MessageReference)"/>.
|
||||
/// This method follows the same behavior as described in <see cref="IMessageChannel.SendFileAsync(string, string, bool, Embed, RequestOptions, bool, AllowedMentions, MessageReference, MessageComponent, ISticker[], Embed[])"/>.
|
||||
/// Please visit its documentation for more details on this method.
|
||||
/// </remarks>
|
||||
/// <param name="filePath">The file path of the file.</param>
|
||||
@@ -57,16 +60,19 @@ namespace Discord.WebSocket
|
||||
/// If <c>null</c>, all mentioned roles and users will be notified.
|
||||
/// </param>
|
||||
/// <param name="messageReference">The message references to be included. Used to reply to specific messages.</param>
|
||||
/// <param name="component">The message components to be included with this message. Used for interactions.</param>
|
||||
/// <param name="stickers">A collection of stickers to send with the file.</param>
|
||||
/// <param name="embeds">A array of <see cref="Embed"/>s to send with this response. Max 10.</param>
|
||||
/// <returns>
|
||||
/// A task that represents an asynchronous send operation for delivering the message. The task result
|
||||
/// contains the sent message.
|
||||
/// </returns>
|
||||
new Task<RestUserMessage> 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<RestUserMessage> 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);
|
||||
/// <summary>
|
||||
/// Sends a file to this message channel with an optional caption.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method follows the same behavior as described in <see cref="IMessageChannel.SendFileAsync(Stream, string, string, bool, Embed, RequestOptions, bool, AllowedMentions, MessageReference)"/>.
|
||||
/// This method follows the same behavior as described in <see cref="IMessageChannel.SendFileAsync(Stream, string, string, bool, Embed, RequestOptions, bool, AllowedMentions, MessageReference, MessageComponent, ISticker[], Embed[])"/>.
|
||||
/// Please visit its documentation for more details on this method.
|
||||
/// </remarks>
|
||||
/// <param name="stream">The <see cref="Stream" /> of the file to be sent.</param>
|
||||
@@ -81,11 +87,14 @@ namespace Discord.WebSocket
|
||||
/// If <c>null</c>, all mentioned roles and users will be notified.
|
||||
/// </param>
|
||||
/// <param name="messageReference">The message references to be included. Used to reply to specific messages.</param>
|
||||
/// <param name="component">The message components to be included with this message. Used for interactions.</param>
|
||||
/// <param name="stickers">A collection of stickers to send with the file.</param>
|
||||
/// <param name="embeds">A array of <see cref="Embed"/>s to send with this response. Max 10.</param>
|
||||
/// <returns>
|
||||
/// A task that represents an asynchronous send operation for delivering the message. The task result
|
||||
/// contains the sent message.
|
||||
/// </returns>
|
||||
new Task<RestUserMessage> 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<RestUserMessage> 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);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a cached message from this channel.
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Discord.WebSocket
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
public class SocketCategoryChannel : SocketGuildChannel, ICategoryChannel
|
||||
{
|
||||
#region SocketCategoryChannel
|
||||
/// <inheritdoc />
|
||||
public override IReadOnlyCollection<SocketGuildUser> Users
|
||||
=> Guild.Users.Where(x => Permissions.GetValue(
|
||||
@@ -41,8 +42,9 @@ namespace Discord.WebSocket
|
||||
entity.Update(state, model);
|
||||
return entity;
|
||||
}
|
||||
#endregion
|
||||
|
||||
//Users
|
||||
#region Users
|
||||
/// <inheritdoc />
|
||||
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
|
||||
/// <inheritdoc />
|
||||
IAsyncEnumerable<IReadOnlyCollection<IGuildUser>> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options)
|
||||
=> ImmutableArray.Create<IReadOnlyCollection<IGuildUser>>(Users).ToAsyncEnumerable();
|
||||
/// <inheritdoc />
|
||||
Task<IGuildUser> IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options)
|
||||
=> Task.FromResult<IGuildUser>(GetUser(id));
|
||||
#endregion
|
||||
|
||||
//IChannel
|
||||
#region IChannel
|
||||
/// <inheritdoc />
|
||||
IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options)
|
||||
=> ImmutableArray.Create<IReadOnlyCollection<IUser>>(Users).ToAsyncEnumerable();
|
||||
/// <inheritdoc />
|
||||
Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options)
|
||||
=> Task.FromResult<IUser>(GetUser(id));
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace Discord.WebSocket
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
public abstract class SocketChannel : SocketEntity<ulong>, IChannel
|
||||
{
|
||||
#region SocketChannel
|
||||
/// <summary>
|
||||
/// Gets when the channel is created.
|
||||
/// </summary>
|
||||
@@ -30,19 +31,17 @@ namespace Discord.WebSocket
|
||||
/// <exception cref="InvalidOperationException">Unexpected channel type is created.</exception>
|
||||
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
|
||||
/// <summary>
|
||||
/// Gets a generic user from this channel.
|
||||
/// </summary>
|
||||
@@ -56,8 +55,9 @@ namespace Discord.WebSocket
|
||||
|
||||
private string DebuggerDisplay => $"Unknown ({Id}, Channel)";
|
||||
internal SocketChannel Clone() => MemberwiseClone() as SocketChannel;
|
||||
#endregion
|
||||
|
||||
//IChannel
|
||||
#region IChannel
|
||||
/// <inheritdoc />
|
||||
string IChannel.Name => null;
|
||||
|
||||
@@ -67,5 +67,6 @@ namespace Discord.WebSocket
|
||||
/// <inheritdoc />
|
||||
IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options)
|
||||
=> AsyncEnumerable.Empty<IReadOnlyCollection<IUser>>(); //Overridden
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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."),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace Discord.WebSocket
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
public class SocketDMChannel : SocketChannel, IDMChannel, ISocketPrivateChannel, ISocketMessageChannel
|
||||
{
|
||||
#region SocketDMChannel
|
||||
/// <summary>
|
||||
/// Gets the recipient of the channel.
|
||||
/// </summary>
|
||||
@@ -58,8 +59,9 @@ namespace Discord.WebSocket
|
||||
/// <inheritdoc />
|
||||
public Task CloseAsync(RequestOptions options = null)
|
||||
=> ChannelHelper.DeleteAsync(this, Discord, options);
|
||||
#endregion
|
||||
|
||||
//Messages
|
||||
#region Messages
|
||||
/// <inheritdoc />
|
||||
public SocketMessage GetCachedMessage(ulong id)
|
||||
=> null;
|
||||
@@ -137,16 +139,25 @@ namespace Discord.WebSocket
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception>
|
||||
public Task<RestUserMessage> 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<RestUserMessage> 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);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<RestUserMessage> 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<RestUserMessage> 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);
|
||||
/// <inheritdoc />
|
||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception>
|
||||
public Task<RestUserMessage> 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<RestUserMessage> 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);
|
||||
/// <inheritdoc />
|
||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception>
|
||||
public Task<RestUserMessage> 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);
|
||||
/// <inheritdoc />
|
||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception>
|
||||
public Task<RestUserMessage> SendFilesAsync(IEnumerable<FileAttachment> 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);
|
||||
|
||||
/// <inheritdoc />
|
||||
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
|
||||
/// <summary>
|
||||
/// Gets a user in this channel from the provided <paramref name="id"/>.
|
||||
/// </summary>
|
||||
@@ -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
|
||||
/// <inheritdoc />
|
||||
internal override IReadOnlyCollection<SocketUser> GetUsersInternal() => Users;
|
||||
/// <inheritdoc />
|
||||
internal override SocketUser GetUserInternal(ulong id) => GetUser(id);
|
||||
#endregion
|
||||
|
||||
//IDMChannel
|
||||
#region IDMChannel
|
||||
/// <inheritdoc />
|
||||
IUser IDMChannel.Recipient => Recipient;
|
||||
#endregion
|
||||
|
||||
//ISocketPrivateChannel
|
||||
#region ISocketPrivateChannel
|
||||
/// <inheritdoc />
|
||||
IReadOnlyCollection<SocketUser> ISocketPrivateChannel.Recipients => ImmutableArray.Create(Recipient);
|
||||
#endregion
|
||||
|
||||
//IPrivateChannel
|
||||
#region IPrivateChannel
|
||||
/// <inheritdoc />
|
||||
IReadOnlyCollection<IUser> IPrivateChannel.Recipients => ImmutableArray.Create<IUser>(Recipient);
|
||||
#endregion
|
||||
|
||||
//IMessageChannel
|
||||
#region IMessageChannel
|
||||
/// <inheritdoc />
|
||||
async Task<IMessage> IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options)
|
||||
{
|
||||
@@ -238,16 +255,23 @@ namespace Discord.WebSocket
|
||||
async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options)
|
||||
=> await GetPinnedMessagesAsync(options).ConfigureAwait(false);
|
||||
/// <inheritdoc />
|
||||
async Task<IUserMessage> 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<IUserMessage> 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);
|
||||
/// <inheritdoc />
|
||||
async Task<IUserMessage> 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<IUserMessage> 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);
|
||||
/// <inheritdoc />
|
||||
async Task<IUserMessage> 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<IUserMessage> 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);
|
||||
/// <inheritdoc />
|
||||
async Task<IUserMessage> IMessageChannel.SendFilesAsync(IEnumerable<FileAttachment> 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);
|
||||
/// <inheritdoc />
|
||||
async Task<IUserMessage> 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
|
||||
/// <inheritdoc />
|
||||
string IChannel.Name => $"@{Recipient}";
|
||||
|
||||
@@ -257,5 +281,6 @@ namespace Discord.WebSocket
|
||||
/// <inheritdoc />
|
||||
IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options)
|
||||
=> ImmutableArray.Create<IReadOnlyCollection<IUser>>(Users).ToAsyncEnumerable();
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<ulong, SocketVoiceState> _voiceStates;
|
||||
|
||||
@@ -31,7 +32,15 @@ namespace Discord.WebSocket
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyCollection<SocketMessage> CachedMessages => _messages?.Messages ?? ImmutableArray.Create<SocketMessage>();
|
||||
|
||||
/// <summary>
|
||||
/// Returns a collection representing all of the users in the group.
|
||||
/// </summary>
|
||||
public new IReadOnlyCollection<SocketGroupUser> Users => _users.ToReadOnlyCollection();
|
||||
|
||||
/// <summary>
|
||||
/// Returns a collection representing all users in the group, not including the client.
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<SocketGroupUser> 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
|
||||
/// <inheritdoc />
|
||||
public SocketMessage GetCachedMessage(ulong id)
|
||||
=> _messages?.Get(id);
|
||||
@@ -163,15 +173,24 @@ namespace Discord.WebSocket
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception>
|
||||
public Task<RestUserMessage> 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<RestUserMessage> 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);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<RestUserMessage> 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<RestUserMessage> 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);
|
||||
/// <inheritdoc />
|
||||
public Task<RestUserMessage> 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);
|
||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception>
|
||||
public Task<RestUserMessage> 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);
|
||||
/// <inheritdoc />
|
||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception>
|
||||
public Task<RestUserMessage> 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);
|
||||
/// <inheritdoc />
|
||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception>
|
||||
public Task<RestUserMessage> SendFilesAsync(IEnumerable<FileAttachment> 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);
|
||||
|
||||
/// <inheritdoc />
|
||||
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
|
||||
/// <summary>
|
||||
/// Gets a user from this group.
|
||||
/// </summary>
|
||||
@@ -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
|
||||
/// <inheritdoc />
|
||||
internal override IReadOnlyCollection<SocketUser> GetUsersInternal() => Users;
|
||||
/// <inheritdoc />
|
||||
internal override SocketUser GetUserInternal(ulong id) => GetUser(id);
|
||||
#endregion
|
||||
|
||||
//ISocketPrivateChannel
|
||||
#region ISocketPrivateChannel
|
||||
/// <inheritdoc />
|
||||
IReadOnlyCollection<SocketUser> ISocketPrivateChannel.Recipients => Recipients;
|
||||
#endregion
|
||||
|
||||
//IPrivateChannel
|
||||
#region IPrivateChannel
|
||||
/// <inheritdoc />
|
||||
IReadOnlyCollection<IUser> IPrivateChannel.Recipients => Recipients;
|
||||
#endregion
|
||||
|
||||
//IMessageChannel
|
||||
#region IMessageChannel
|
||||
/// <inheritdoc />
|
||||
async Task<IMessage> IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options)
|
||||
{
|
||||
@@ -297,27 +322,37 @@ namespace Discord.WebSocket
|
||||
=> await GetPinnedMessagesAsync(options).ConfigureAwait(false);
|
||||
|
||||
/// <inheritdoc />
|
||||
async Task<IUserMessage> 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<IUserMessage> 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);
|
||||
/// <inheritdoc />
|
||||
async Task<IUserMessage> 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<IUserMessage> 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);
|
||||
/// <inheritdoc />
|
||||
async Task<IUserMessage> 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<IUserMessage> 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);
|
||||
/// <inheritdoc />
|
||||
async Task<IUserMessage> IMessageChannel.SendFilesAsync(IEnumerable<FileAttachment> 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);
|
||||
|
||||
//IAudioChannel
|
||||
/// <inheritdoc />
|
||||
async Task<IUserMessage> 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
|
||||
|
||||
#region IAudioChannel
|
||||
/// <inheritdoc />
|
||||
/// <exception cref="NotSupportedException">Connecting to a group channel is not supported.</exception>
|
||||
Task<IAudioClient> IAudioChannel.ConnectAsync(bool selfDeaf, bool selfMute, bool external) { throw new NotSupportedException(); }
|
||||
Task IAudioChannel.DisconnectAsync() { throw new NotSupportedException(); }
|
||||
#endregion
|
||||
|
||||
//IChannel
|
||||
#region IChannel
|
||||
/// <inheritdoc />
|
||||
Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options)
|
||||
=> Task.FromResult<IUser>(GetUser(id));
|
||||
/// <inheritdoc />
|
||||
IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options)
|
||||
=> ImmutableArray.Create<IReadOnlyCollection<IUser>>(Users).ToAsyncEnumerable();
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace Discord.WebSocket
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
public class SocketGuildChannel : SocketChannel, IGuildChannel
|
||||
{
|
||||
#region SocketGuildChannel
|
||||
private ImmutableArray<Overwrite> _overwrites;
|
||||
|
||||
/// <summary>
|
||||
@@ -27,7 +28,7 @@ namespace Discord.WebSocket
|
||||
/// <inheritdoc />
|
||||
public string Name { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public int Position { get; private set; }
|
||||
public int Position { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual IReadOnlyCollection<Overwrite> 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),
|
||||
};
|
||||
}
|
||||
/// <inheritdoc />
|
||||
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<Overwrite>(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
|
||||
/// <inheritdoc />
|
||||
internal override IReadOnlyCollection<SocketUser> GetUsersInternal() => Users;
|
||||
/// <inheritdoc />
|
||||
internal override SocketUser GetUserInternal(ulong id) => GetUser(id);
|
||||
#endregion
|
||||
|
||||
//IGuildChannel
|
||||
#region IGuildChannel
|
||||
/// <inheritdoc />
|
||||
IGuild IGuildChannel.Guild => Guild;
|
||||
/// <inheritdoc />
|
||||
@@ -214,13 +214,15 @@ namespace Discord.WebSocket
|
||||
/// <inheritdoc />
|
||||
Task<IGuildUser> IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options)
|
||||
=> Task.FromResult<IGuildUser>(GetUser(id));
|
||||
#endregion
|
||||
|
||||
//IChannel
|
||||
#region IChannel
|
||||
/// <inheritdoc />
|
||||
IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options)
|
||||
=> ImmutableArray.Create<IReadOnlyCollection<IUser>>(Users).ToAsyncEnumerable(); //Overridden in Text/Voice
|
||||
/// <inheritdoc />
|
||||
Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options)
|
||||
=> Task.FromResult<IUser>(GetUser(id)); //Overridden in Text/Voice
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a stage channel received over the gateway.
|
||||
/// </summary>
|
||||
public class SocketStageChannel : SocketVoiceChannel, IStageChannel
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public string Topic { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public StagePrivacyLevel? PrivacyLevel { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool? IsDiscoverableDisabled { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsLive { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns <see langword="true"/> if the current user is a speaker within the stage, otherwise <see langword="false"/>.
|
||||
/// </summary>
|
||||
public bool IsSpeaker
|
||||
=> !Guild.CurrentUser.IsSuppressed;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection of users who are speakers within the stage.
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<SocketGuildUser> 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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task ModifyInstanceAsync(Action<StageInstanceProperties> func, RequestOptions options = null)
|
||||
{
|
||||
var model = await ChannelHelper.ModifyAsync(this, Discord, func, options);
|
||||
|
||||
Update(model, true);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task StopStageAsync(RequestOptions options = null)
|
||||
{
|
||||
await Discord.ApiClient.DeleteStageInstanceAsync(Id, options);
|
||||
|
||||
Update(null);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task BecomeSpeakerAsync(RequestOptions options = null)
|
||||
{
|
||||
var args = new API.Rest.ModifyVoiceStateParams
|
||||
{
|
||||
ChannelId = Id,
|
||||
Suppressed = false
|
||||
};
|
||||
return Discord.ApiClient.ModifyMyVoiceState(Guild.Id, args, options);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task StopSpeakingAsync(RequestOptions options = null)
|
||||
{
|
||||
var args = new API.Rest.ModifyVoiceStateParams
|
||||
{
|
||||
ChannelId = Id,
|
||||
Suppressed = true
|
||||
};
|
||||
return Discord.ApiClient.ModifyMyVoiceState(Guild.Id, args, options);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ namespace Discord.WebSocket
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
public class SocketTextChannel : SocketGuildChannel, ITextChannel, ISocketMessageChannel
|
||||
{
|
||||
#region SocketTextChannel
|
||||
private readonly MessageCache _messages;
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -50,6 +51,12 @@ namespace Discord.WebSocket
|
||||
Permissions.ResolveChannel(Guild, x, this, Permissions.ResolveGuild(Guild, x)),
|
||||
ChannelPermission.ViewChannel)).ToImmutableArray();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection of threads within this text channel.
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<SocketThreadChannel> 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();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task ModifyAsync(Action<TextChannelProperties> func, RequestOptions options = null)
|
||||
public virtual Task ModifyAsync(Action<TextChannelProperties> func, RequestOptions options = null)
|
||||
=> ChannelHelper.ModifyAsync(this, Discord, func, options);
|
||||
|
||||
//Messages
|
||||
/// <summary>
|
||||
/// Creates a thread within this <see cref="ITextChannel"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When <paramref name="message"/> is <see langword="null"/> the thread type will be based off of the
|
||||
/// channel its created in. When called on a <see cref="ITextChannel"/>, it creates a <see cref="ThreadType.PublicThread"/>.
|
||||
/// When called on a <see cref="INewsChannel"/>, it creates a <see cref="ThreadType.NewsThread"/>. 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.
|
||||
/// </remarks>
|
||||
/// <param name="name">The name of the thread.</param>
|
||||
/// <param name="type">
|
||||
/// The type of the thread.
|
||||
/// <para>
|
||||
/// <b>Note: </b>This parameter is not used if the <paramref name="message"/> parameter is not specified.
|
||||
/// </para>
|
||||
/// </param>
|
||||
/// <param name="autoArchiveDuration">
|
||||
/// The duration on which this thread archives after.
|
||||
/// <para>
|
||||
/// <b>Note: </b> Options <see cref="ThreadArchiveDuration.OneWeek"/> and <see cref="ThreadArchiveDuration.ThreeDays"/>
|
||||
/// are only available for guilds that are boosted. You can check in the <see cref="IGuild.Features"/> to see if the
|
||||
/// guild has the <b>THREE_DAY_THREAD_ARCHIVE</b> and <b>SEVEN_DAY_THREAD_ARCHIVE</b>.
|
||||
/// </para>
|
||||
/// </param>
|
||||
/// <param name="message">The message which to start the thread from.</param>
|
||||
/// <param name="options">The options to be used when sending the request.</param>
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous create operation. The task result contains a <see cref="IThreadChannel"/>
|
||||
/// </returns>
|
||||
public async Task<SocketThreadChannel> 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
|
||||
/// <inheritdoc />
|
||||
public SocketMessage GetCachedMessage(ulong id)
|
||||
=> _messages?.Get(id);
|
||||
@@ -161,17 +211,27 @@ namespace Discord.WebSocket
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception>
|
||||
public Task<RestUserMessage> 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<RestUserMessage> 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);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<RestUserMessage> 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<RestUserMessage> 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);
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception>
|
||||
public Task<RestUserMessage> 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<RestUserMessage> 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);
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception>
|
||||
public Task<RestUserMessage> 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);
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception>
|
||||
public Task<RestUserMessage> SendFilesAsync(IEnumerable<FileAttachment> 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);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task DeleteMessagesAsync(IEnumerable<IMessage> 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
|
||||
/// <inheritdoc />
|
||||
public override SocketGuildUser GetUser(ulong id)
|
||||
{
|
||||
@@ -217,8 +278,9 @@ namespace Discord.WebSocket
|
||||
}
|
||||
return null;
|
||||
}
|
||||
#endregion
|
||||
|
||||
//Webhooks
|
||||
#region Webhooks
|
||||
/// <summary>
|
||||
/// Creates a webhook in this text channel.
|
||||
/// </summary>
|
||||
@@ -229,7 +291,7 @@ namespace Discord.WebSocket
|
||||
/// A task that represents the asynchronous creation operation. The task result contains the newly created
|
||||
/// webhook.
|
||||
/// </returns>
|
||||
public Task<RestWebhook> CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null)
|
||||
public virtual Task<RestWebhook> CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null)
|
||||
=> ChannelHelper.CreateWebhookAsync(this, Discord, name, avatar, options);
|
||||
/// <summary>
|
||||
/// 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; <c>null</c> if the webhook is not found.
|
||||
/// </returns>
|
||||
public Task<RestWebhook> GetWebhookAsync(ulong id, RequestOptions options = null)
|
||||
public virtual Task<RestWebhook> GetWebhookAsync(ulong id, RequestOptions options = null)
|
||||
=> ChannelHelper.GetWebhookAsync(this, Discord, id, options);
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </returns>
|
||||
public Task<IReadOnlyCollection<RestWebhook>> GetWebhooksAsync(RequestOptions options = null)
|
||||
public virtual Task<IReadOnlyCollection<RestWebhook>> GetWebhooksAsync(RequestOptions options = null)
|
||||
=> ChannelHelper.GetWebhooksAsync(this, Discord, options);
|
||||
#endregion
|
||||
|
||||
//Invites
|
||||
#region Invites
|
||||
/// <inheritdoc />
|
||||
public async Task<IInviteMetadata> CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
|
||||
public virtual async Task<IInviteMetadata> 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);
|
||||
/// <inheritdoc />
|
||||
public async Task<IReadOnlyCollection<IInviteMetadata>> GetInvitesAsync(RequestOptions options = null)
|
||||
public virtual async Task<IInviteMetadata> 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);
|
||||
/// <inheritdoc />
|
||||
public virtual async Task<IInviteMetadata> 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);
|
||||
/// <inheritdoc />
|
||||
public virtual async Task<IReadOnlyCollection<IInviteMetadata>> 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
|
||||
/// <inheritdoc />
|
||||
async Task<IWebhook> ITextChannel.CreateWebhookAsync(string name, Stream avatar, RequestOptions options)
|
||||
=> await CreateWebhookAsync(name, avatar, options).ConfigureAwait(false);
|
||||
@@ -274,16 +344,21 @@ namespace Discord.WebSocket
|
||||
/// <inheritdoc />
|
||||
async Task<IReadOnlyCollection<IWebhook>> ITextChannel.GetWebhooksAsync(RequestOptions options)
|
||||
=> await GetWebhooksAsync(options).ConfigureAwait(false);
|
||||
/// <inheritdoc />
|
||||
async Task<IThreadChannel> 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
|
||||
/// <inheritdoc />
|
||||
Task<IGuildUser> IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options)
|
||||
=> Task.FromResult<IGuildUser>(GetUser(id));
|
||||
/// <inheritdoc />
|
||||
IAsyncEnumerable<IReadOnlyCollection<IGuildUser>> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options)
|
||||
=> ImmutableArray.Create<IReadOnlyCollection<IGuildUser>>(Users).ToAsyncEnumerable();
|
||||
#endregion
|
||||
|
||||
//IMessageChannel
|
||||
#region IMessageChannel
|
||||
/// <inheritdoc />
|
||||
async Task<IMessage> IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options)
|
||||
{
|
||||
@@ -306,18 +381,26 @@ namespace Discord.WebSocket
|
||||
=> await GetPinnedMessagesAsync(options).ConfigureAwait(false);
|
||||
|
||||
/// <inheritdoc />
|
||||
async Task<IUserMessage> 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<IUserMessage> 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);
|
||||
/// <inheritdoc />
|
||||
async Task<IUserMessage> 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<IUserMessage> 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);
|
||||
/// <inheritdoc />
|
||||
async Task<IUserMessage> 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<IUserMessage> 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);
|
||||
/// <inheritdoc />
|
||||
async Task<IUserMessage> IMessageChannel.SendFilesAsync(IEnumerable<FileAttachment> 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);
|
||||
/// <inheritdoc />
|
||||
async Task<IUserMessage> 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
|
||||
/// <inheritdoc />
|
||||
Task<ICategoryChannel> INestedChannel.GetCategoryAsync(CacheMode mode, RequestOptions options)
|
||||
=> Task.FromResult(Category);
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a thread channel inside of a guild.
|
||||
/// </summary>
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
public class SocketThreadChannel : SocketTextChannel, IThreadChannel
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public ThreadType Type { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the owner of the current thread.
|
||||
/// </summary>
|
||||
public SocketThreadUser Owner { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current users within this thread.
|
||||
/// </summary>
|
||||
public SocketThreadUser CurrentUser
|
||||
=> Users.FirstOrDefault(x => x.Id == Discord.CurrentUser.Id);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool HasJoined { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// <see langword="true"/> if this thread is private, otherwise <see langword="false"/>
|
||||
/// </summary>
|
||||
public bool IsPrivateThread
|
||||
=> Type == ThreadType.PrivateThread;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parent channel this thread resides in.
|
||||
/// </summary>
|
||||
public SocketTextChannel ParentChannel { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int MessageCount { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int MemberCount { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsArchived { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DateTimeOffset ArchiveTimestamp { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ThreadArchiveDuration AutoArchiveDuration { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsLocked { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection of cached users within this thread.
|
||||
/// </summary>
|
||||
public new IReadOnlyCollection<SocketThreadUser> Users =>
|
||||
_members.Values.ToImmutableArray();
|
||||
|
||||
private readonly ConcurrentDictionary<ulong, SocketThreadUser> _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<ulong, SocketThreadUser>();
|
||||
}
|
||||
|
||||
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<SocketThreadUser> RemoveUsers(ulong[] users)
|
||||
{
|
||||
List<SocketThreadUser> 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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public new SocketThreadUser GetUser(ulong id)
|
||||
{
|
||||
var user = Users.FirstOrDefault(x => x.Id == id);
|
||||
return user;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all users inside this thread.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If all users are not downloaded then this method will call <see cref="DownloadUsersAsync(RequestOptions)"/> and return the result.
|
||||
/// </remarks>
|
||||
/// <param name="options">The options to be used when sending the request.</param>
|
||||
/// <returns>A task representing the download operation.</returns>
|
||||
public async Task<IReadOnlyCollection<SocketThreadUser>> GetUsersAsync(RequestOptions options = null)
|
||||
{
|
||||
// download all users if we havent
|
||||
if (!_usersDownloaded)
|
||||
{
|
||||
await DownloadUsersAsync(options);
|
||||
_usersDownloaded = true;
|
||||
}
|
||||
|
||||
return Users;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Downloads all users that have access to this thread.
|
||||
/// </summary>
|
||||
/// <param name="options">The options to be used when sending the request.</param>
|
||||
/// <returns>A task representing the asynchronous download operation.</returns>
|
||||
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;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task JoinAsync(RequestOptions options = null)
|
||||
=> Discord.ApiClient.JoinThreadAsync(Id, options);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task LeaveAsync(RequestOptions options = null)
|
||||
=> Discord.ApiClient.LeaveThreadAsync(Id, options);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a user to this thread.
|
||||
/// </summary>
|
||||
/// <param name="user">The <see cref="IGuildUser"/> to add.</param>
|
||||
/// <param name="options">The options to be used when sending the request.</param>
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous operation of adding a member to a thread.
|
||||
/// </returns>
|
||||
public Task AddUserAsync(IGuildUser user, RequestOptions options = null)
|
||||
=> Discord.ApiClient.AddThreadMemberAsync(Id, user.Id, options);
|
||||
|
||||
/// <summary>
|
||||
/// Removes a user from this thread.
|
||||
/// </summary>
|
||||
/// <param name="user">The <see cref="IGuildUser"/> to remove from this thread.</param>
|
||||
/// <param name="options">The options to be used when sending the request.</param>
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous operation of removing a user from this thread.
|
||||
/// </returns>
|
||||
public Task RemoveUserAsync(IGuildUser user, RequestOptions options = null)
|
||||
=> Discord.ApiClient.RemoveThreadMemberAsync(Id, user.Id, options);
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>
|
||||
/// <b>This method is not supported in threads.</b>
|
||||
/// </remarks>
|
||||
public override Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, RequestOptions options = null)
|
||||
=> throw new NotSupportedException("This method is not supported in threads.");
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>
|
||||
/// <b>This method is not supported in threads.</b>
|
||||
/// </remarks>
|
||||
public override Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions, RequestOptions options = null)
|
||||
=> throw new NotSupportedException("This method is not supported in threads.");
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>
|
||||
/// <b>This method is not supported in threads.</b>
|
||||
/// </remarks>
|
||||
public override Task<IInviteMetadata> 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.");
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>
|
||||
/// <b>This method is not supported in threads.</b>
|
||||
/// </remarks>
|
||||
public override Task<IInviteMetadata> 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.");
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>
|
||||
/// <b>This method is not supported in threads.</b>
|
||||
/// </remarks>
|
||||
public override Task<IInviteMetadata> 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.");
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>
|
||||
/// <b>This method is not supported in threads.</b>
|
||||
/// </remarks>
|
||||
public override Task<RestWebhook> CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null)
|
||||
=> throw new NotSupportedException("This method is not supported in threads.");
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>
|
||||
/// <b>This method is not supported in threads.</b>
|
||||
/// </remarks>
|
||||
public override Task<IReadOnlyCollection<IInviteMetadata>> GetInvitesAsync(RequestOptions options = null)
|
||||
=> throw new NotSupportedException("This method is not supported in threads.");
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>
|
||||
/// <b>This method is not supported in threads.</b>
|
||||
/// </remarks>
|
||||
public override OverwritePermissions? GetPermissionOverwrite(IRole role)
|
||||
=> ParentChannel.GetPermissionOverwrite(role);
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>
|
||||
/// <b>This method is not supported in threads.</b>
|
||||
/// </remarks>
|
||||
public override OverwritePermissions? GetPermissionOverwrite(IUser user)
|
||||
=> ParentChannel.GetPermissionOverwrite(user);
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>
|
||||
/// <b>This method is not supported in threads.</b>
|
||||
/// </remarks>
|
||||
public override Task<RestWebhook> GetWebhookAsync(ulong id, RequestOptions options = null)
|
||||
=> throw new NotSupportedException("This method is not supported in threads.");
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>
|
||||
/// <b>This method is not supported in threads.</b>
|
||||
/// </remarks>
|
||||
public override Task<IReadOnlyCollection<RestWebhook>> GetWebhooksAsync(RequestOptions options = null)
|
||||
=> throw new NotSupportedException("This method is not supported in threads.");
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>
|
||||
/// <b>This method is not supported in threads.</b>
|
||||
/// </remarks>
|
||||
public override Task ModifyAsync(Action<TextChannelProperties> func, RequestOptions options = null)
|
||||
=> ThreadHelper.ModifyAsync(this, Discord, func, options);
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>
|
||||
/// <b>This method is not supported in threads.</b>
|
||||
/// </remarks>
|
||||
public override Task RemovePermissionOverwriteAsync(IRole role, RequestOptions options = null)
|
||||
=> throw new NotSupportedException("This method is not supported in threads.");
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>
|
||||
/// <b>This method is not supported in threads.</b>
|
||||
/// </remarks>
|
||||
public override Task RemovePermissionOverwriteAsync(IUser user, RequestOptions options = null)
|
||||
=> throw new NotSupportedException("This method is not supported in threads.");
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>
|
||||
/// <b>This method is not supported in threads.</b>
|
||||
/// </remarks>
|
||||
public override IReadOnlyCollection<Overwrite> PermissionOverwrites
|
||||
=> throw new NotSupportedException("This method is not supported in threads.");
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>
|
||||
/// <b>This method is not supported in threads.</b>
|
||||
/// </remarks>
|
||||
public override Task SyncPermissionsAsync(RequestOptions options = null)
|
||||
=> throw new NotSupportedException("This method is not supported in threads.");
|
||||
|
||||
string IChannel.Name => Name;
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ namespace Discord.WebSocket
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
public class SocketVoiceChannel : SocketGuildChannel, IVoiceChannel, ISocketAudioChannel
|
||||
{
|
||||
#region SocketVoiceChannel
|
||||
/// <inheritdoc />
|
||||
public int Bitrate { get; private set; }
|
||||
/// <inheritdoc />
|
||||
@@ -89,29 +90,39 @@ namespace Discord.WebSocket
|
||||
return user;
|
||||
return null;
|
||||
}
|
||||
#endregion
|
||||
|
||||
//Invites
|
||||
#region Invites
|
||||
/// <inheritdoc />
|
||||
public async Task<IInviteMetadata> 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);
|
||||
/// <inheritdoc />
|
||||
public async Task<IInviteMetadata> 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);
|
||||
/// <inheritdoc />
|
||||
public async Task<IInviteMetadata> 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);
|
||||
/// <inheritdoc />
|
||||
public async Task<IReadOnlyCollection<IInviteMetadata>> 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
|
||||
/// <inheritdoc />
|
||||
Task<IGuildUser> IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options)
|
||||
=> Task.FromResult<IGuildUser>(GetUser(id));
|
||||
/// <inheritdoc />
|
||||
IAsyncEnumerable<IReadOnlyCollection<IGuildUser>> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options)
|
||||
=> ImmutableArray.Create<IReadOnlyCollection<IGuildUser>>(Users).ToAsyncEnumerable();
|
||||
#endregion
|
||||
|
||||
// INestedChannel
|
||||
#region INestedChannel
|
||||
/// <inheritdoc />
|
||||
Task<ICategoryChannel> INestedChannel.GetCategoryAsync(CacheMode mode, RequestOptions options)
|
||||
=> Task.FromResult(Category);
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<ulong>, IGuild, IDisposable
|
||||
{
|
||||
#region SocketGuild
|
||||
#pragma warning disable IDISP002, IDISP006
|
||||
private readonly SemaphoreSlim _audioLock;
|
||||
private TaskCompletionSource<bool> _syncPromise, _downloaderPromise;
|
||||
private TaskCompletionSource<AudioClient> _audioConnectPromise;
|
||||
private ConcurrentHashSet<ulong> _channels;
|
||||
private ConcurrentDictionary<ulong, SocketGuildChannel> _channels;
|
||||
private ConcurrentDictionary<ulong, SocketGuildUser> _members;
|
||||
private ConcurrentDictionary<ulong, SocketRole> _roles;
|
||||
private ConcurrentDictionary<ulong, SocketVoiceState> _voiceStates;
|
||||
private ConcurrentDictionary<ulong, SocketCustomSticker> _stickers;
|
||||
private ConcurrentDictionary<ulong, SocketGuildEvent> _events;
|
||||
private ImmutableArray<GuildEmote> _emotes;
|
||||
private ImmutableArray<string> _features;
|
||||
|
||||
private AudioClient _audioClient;
|
||||
#pragma warning restore IDISP002, IDISP006
|
||||
|
||||
@@ -118,9 +124,14 @@ namespace Discord.WebSocket
|
||||
public int? MaxMembers { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public int? MaxVideoChannelUsers { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NsfwLevel NsfwLevel { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public CultureInfo PreferredCulture { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public bool IsBoostProgressBarEnabled { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public GuildFeatures Features { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id);
|
||||
@@ -131,7 +142,7 @@ namespace Discord.WebSocket
|
||||
/// <inheritdoc />
|
||||
public string DiscoverySplashUrl => CDN.GetGuildDiscoverySplashUrl(Id, DiscoverySplashId);
|
||||
/// <inheritdoc />
|
||||
public string BannerUrl => CDN.GetGuildBannerUrl(Id, BannerId);
|
||||
public string BannerUrl => CDN.GetGuildBannerUrl(Id, BannerId, ImageFormat.Auto);
|
||||
/// <summary> Indicates whether the client has all the members downloaded to the local guild cache. </summary>
|
||||
public bool HasAllMembers => MemberCount <= DownloadedMemberCount;// _downloaderPromise.Task.IsCompleted;
|
||||
/// <summary> Indicates whether the guild cache is synced to this guild. </summary>
|
||||
@@ -269,6 +280,14 @@ namespace Discord.WebSocket
|
||||
public IReadOnlyCollection<SocketVoiceChannel> VoiceChannels
|
||||
=> Channels.OfType<SocketVoiceChannel>().ToImmutableArray();
|
||||
/// <summary>
|
||||
/// Gets a collection of all stage channels in this guild.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A read-only collection of stage channels found within this guild.
|
||||
/// </returns>
|
||||
public IReadOnlyCollection<SocketStageChannel> StageChannels
|
||||
=> Channels.OfType<SocketStageChannel>().ToImmutableArray();
|
||||
/// <summary>
|
||||
/// Gets a collection of all category channels in this guild.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
@@ -277,6 +296,14 @@ namespace Discord.WebSocket
|
||||
public IReadOnlyCollection<SocketCategoryChannel> CategoryChannels
|
||||
=> Channels.OfType<SocketCategoryChannel>().ToImmutableArray();
|
||||
/// <summary>
|
||||
/// Gets a collection of all thread channels in this guild.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A read-only collection of thread channels found within this guild.
|
||||
/// </returns>
|
||||
public IReadOnlyCollection<SocketThreadChannel> ThreadChannels
|
||||
=> Channels.OfType<SocketThreadChannel>().ToImmutableArray();
|
||||
/// <summary>
|
||||
/// Gets the current logged-in user.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyCollection<GuildEmote> Emotes => _emotes;
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyCollection<string> Features => _features;
|
||||
/// <summary>
|
||||
/// Gets a collection of all custom stickers for this guild.
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<SocketCustomSticker> Stickers
|
||||
=> _stickers.Select(x => x.Value).ToImmutableArray();
|
||||
/// <summary>
|
||||
/// Gets a collection of users in this guild.
|
||||
/// </summary>
|
||||
@@ -336,12 +366,22 @@ namespace Discord.WebSocket
|
||||
/// </returns>
|
||||
public IReadOnlyCollection<SocketRole> Roles => _roles.ToReadOnlyCollection();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection of all events within this guild.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This field is based off of caching alone, since there is no events returned on the guild model.
|
||||
/// </remarks>
|
||||
/// <returns>
|
||||
/// A read-only collection of guild events found within this guild.
|
||||
/// </returns>
|
||||
public IReadOnlyCollection<SocketGuildEvent> Events => _events.ToReadOnlyCollection();
|
||||
|
||||
internal SocketGuild(DiscordSocketClient client, ulong id)
|
||||
: base(client, id)
|
||||
{
|
||||
_audioLock = new SemaphoreSlim(1, 1);
|
||||
_emotes = ImmutableArray.Create<GuildEmote>();
|
||||
_features = ImmutableArray.Create<string>();
|
||||
}
|
||||
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<ulong, SocketGuildEvent>();
|
||||
if (_channels == null)
|
||||
_channels = new ConcurrentHashSet<ulong>();
|
||||
_channels = new ConcurrentDictionary<ulong, SocketGuildChannel>();
|
||||
if (_members == null)
|
||||
_members = new ConcurrentDictionary<ulong, SocketGuildUser>();
|
||||
if (_roles == null)
|
||||
@@ -371,15 +413,23 @@ namespace Discord.WebSocket
|
||||
|
||||
Update(state, model as Model);
|
||||
|
||||
var channels = new ConcurrentHashSet<ulong>(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.Channels.Length * 1.05));
|
||||
var channels = new ConcurrentDictionary<ulong, SocketGuildChannel>(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<ulong, SocketGuildUser>(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.Members.Length * 1.05));
|
||||
@@ -414,6 +464,17 @@ namespace Discord.WebSocket
|
||||
}
|
||||
_voiceStates = voiceStates;
|
||||
|
||||
var events = new ConcurrentDictionary<ulong, SocketGuildEvent>(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<bool>();
|
||||
_downloaderPromise = new TaskCompletionSource<bool>();
|
||||
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<GuildEmote>(model.Emojis.Length);
|
||||
@@ -467,10 +530,7 @@ namespace Discord.WebSocket
|
||||
else
|
||||
_emotes = ImmutableArray.Create<GuildEmote>();
|
||||
|
||||
if (model.Features != null)
|
||||
_features = model.Features.ToImmutableArray();
|
||||
else
|
||||
_features = ImmutableArray.Create<string>();
|
||||
Features = model.Features;
|
||||
|
||||
var roles = new ConcurrentDictionary<ulong, SocketRole>(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<ulong, SocketCustomSticker>(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<ulong, SocketCustomSticker>(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
|
||||
/// <inheritdoc />
|
||||
public Task DeleteAsync(RequestOptions options = null)
|
||||
=> GuildHelper.DeleteAsync(this, Discord, options);
|
||||
@@ -539,8 +619,9 @@ namespace Discord.WebSocket
|
||||
/// <inheritdoc />
|
||||
public Task LeaveAsync(RequestOptions options = null)
|
||||
=> GuildHelper.LeaveAsync(this, Discord, options);
|
||||
#endregion
|
||||
|
||||
//Bans
|
||||
#region Bans
|
||||
/// <summary>
|
||||
/// Gets a collection of all users banned in this guild.
|
||||
/// </summary>
|
||||
@@ -588,8 +669,9 @@ namespace Discord.WebSocket
|
||||
/// <inheritdoc />
|
||||
public Task RemoveBanAsync(ulong userId, RequestOptions options = null)
|
||||
=> GuildHelper.RemoveBanAsync(this, Discord, userId, options);
|
||||
#endregion
|
||||
|
||||
//Channels
|
||||
#region Channels
|
||||
/// <summary>
|
||||
/// Gets a channel in this guild.
|
||||
/// </summary>
|
||||
@@ -614,6 +696,16 @@ namespace Discord.WebSocket
|
||||
public SocketTextChannel GetTextChannel(ulong id)
|
||||
=> GetChannel(id) as SocketTextChannel;
|
||||
/// <summary>
|
||||
/// Gets a thread in this guild.
|
||||
/// </summary>
|
||||
/// <param name="id">The snowflake identifier for the thread.</param>
|
||||
/// <returns>
|
||||
/// A thread channel associated with the specified <paramref name="id" />; <see langword="null"/> if none is found.
|
||||
/// </returns>
|
||||
public SocketThreadChannel GetThreadChannel(ulong id)
|
||||
=> GetChannel(id) as SocketThreadChannel;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a voice channel in this guild.
|
||||
/// </summary>
|
||||
/// <param name="id">The snowflake identifier for the voice channel.</param>
|
||||
@@ -623,6 +715,15 @@ namespace Discord.WebSocket
|
||||
public SocketVoiceChannel GetVoiceChannel(ulong id)
|
||||
=> GetChannel(id) as SocketVoiceChannel;
|
||||
/// <summary>
|
||||
/// Gets a stage channel in this guild.
|
||||
/// </summary>
|
||||
/// <param name="id">The snowflake identifier for the stage channel.</param>
|
||||
/// <returns>
|
||||
/// A stage channel associated with the specified <paramref name="id" />; <see langword="null"/> if none is found.
|
||||
/// </returns>
|
||||
public SocketStageChannel GetStageChannel(ulong id)
|
||||
=> GetChannel(id) as SocketStageChannel;
|
||||
/// <summary>
|
||||
/// Gets a category channel in this guild.
|
||||
/// </summary>
|
||||
/// <param name="id">The snowflake identifier for the category channel.</param>
|
||||
@@ -670,6 +771,19 @@ namespace Discord.WebSocket
|
||||
/// </returns>
|
||||
public Task<RestVoiceChannel> CreateVoiceChannelAsync(string name, Action<VoiceChannelProperties> func = null, RequestOptions options = null)
|
||||
=> GuildHelper.CreateVoiceChannelAsync(this, Discord, name, options, func);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new stage channel in this guild.
|
||||
/// </summary>
|
||||
/// <param name="name">The new name for the stage channel.</param>
|
||||
/// <param name="func">The delegate containing the properties to be applied to the channel upon its creation.</param>
|
||||
/// <param name="options">The options to be used when sending the request.</param>
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous creation operation. The task result contains the newly created
|
||||
/// stage channel.
|
||||
/// </returns>
|
||||
public Task<RestStageChannel> CreateStageChannelAsync(string name, Action<VoiceChannelProperties> func = null, RequestOptions options = null)
|
||||
=> GuildHelper.CreateStageChannelAsync(this, Discord, name, options, func);
|
||||
/// <summary>
|
||||
/// Creates a new channel category in this guild.
|
||||
/// </summary>
|
||||
@@ -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
|
||||
/// <summary>
|
||||
/// Gets a collection of all the voice regions this guild can access.
|
||||
/// </summary>
|
||||
@@ -716,14 +845,124 @@ namespace Discord.WebSocket
|
||||
/// </returns>
|
||||
public Task<IReadOnlyCollection<RestVoiceRegion>> GetVoiceRegionsAsync(RequestOptions options = null)
|
||||
=> GuildHelper.GetVoiceRegionsAsync(this, Discord, options);
|
||||
#endregion
|
||||
|
||||
//Integrations
|
||||
#region Integrations
|
||||
public Task<IReadOnlyCollection<RestGuildIntegration>> GetIntegrationsAsync(RequestOptions options = null)
|
||||
=> GuildHelper.GetIntegrationsAsync(this, Discord, options);
|
||||
public Task<RestGuildIntegration> CreateIntegrationAsync(ulong id, string type, RequestOptions options = null)
|
||||
=> GuildHelper.CreateIntegrationAsync(this, Discord, id, type, options);
|
||||
#endregion
|
||||
|
||||
//Invites
|
||||
#region Interactions
|
||||
/// <summary>
|
||||
/// Deletes all application commands in the current guild.
|
||||
/// </summary>
|
||||
/// <param name="options">The options to be used when sending the request.</param>
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous delete operation.
|
||||
/// </returns>
|
||||
public Task DeleteApplicationCommandsAsync(RequestOptions options = null)
|
||||
=> InteractionHelper.DeleteAllGuildCommandsAsync(Discord, Id, options);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection of slash commands created by the current user in this guild.
|
||||
/// </summary>
|
||||
/// <param name="options">The options to be used when sending the request.</param>
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous get operation. The task result contains a read-only collection of
|
||||
/// slash commands created by the current user.
|
||||
/// </returns>
|
||||
public async Task<IReadOnlyCollection<SocketApplicationCommand>> 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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an application command within this guild with the specified id.
|
||||
/// </summary>
|
||||
/// <param name="id">The id of the application command to get.</param>
|
||||
/// <param name="mode">The <see cref="CacheMode" /> that determines whether the object should be fetched from cache.</param>
|
||||
/// <param name="options">The options to be used when sending the request.</param>
|
||||
/// <returns>
|
||||
/// A ValueTask that represents the asynchronous get operation. The task result contains a <see cref="IApplicationCommand"/>
|
||||
/// if found, otherwise <see langword="null"/>.
|
||||
/// </returns>
|
||||
public async ValueTask<SocketApplicationCommand> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an application command within this guild.
|
||||
/// </summary>
|
||||
/// <param name="properties">The properties to use when creating the command.</param>
|
||||
/// <param name="options">The options to be used when sending the request.</param>
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous creation operation. The task result contains the command that was created.
|
||||
/// </returns>
|
||||
public async Task<SocketApplicationCommand> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overwrites the application commands within this guild.
|
||||
/// </summary>
|
||||
/// <param name="properties">A collection of properties to use when creating the commands.</param>
|
||||
/// <param name="options">The options to be used when sending the request.</param>
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous creation operation. The task result contains a collection of commands that was created.
|
||||
/// </returns>
|
||||
public async Task<IReadOnlyCollection<SocketApplicationCommand>> 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
|
||||
/// <summary>
|
||||
/// Gets a collection of all invites in this guild.
|
||||
/// </summary>
|
||||
@@ -744,8 +983,9 @@ namespace Discord.WebSocket
|
||||
/// </returns>
|
||||
public Task<RestInviteMetadata> GetVanityInviteAsync(RequestOptions options = null)
|
||||
=> GuildHelper.GetVanityInviteAsync(this, Discord, options);
|
||||
#endregion
|
||||
|
||||
//Roles
|
||||
#region Roles
|
||||
/// <summary>
|
||||
/// Gets a role in this guild.
|
||||
/// </summary>
|
||||
@@ -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
|
||||
/// <inheritdoc />
|
||||
public Task<RestGuildUser> AddGuildUserAsync(ulong id, string accessToken, Action<AddGuildUserProperties> func = null, RequestOptions options = null)
|
||||
=> GuildHelper.AddGuildUserAsync(this, Discord, id, accessToken, func, options);
|
||||
@@ -935,8 +1213,118 @@ namespace Discord.WebSocket
|
||||
/// </returns>
|
||||
public Task<IReadOnlyCollection<RestGuildUser>> SearchUsersAsync(string query, int limit = DiscordConfig.MaxUsersPerBatch, RequestOptions options = null)
|
||||
=> GuildHelper.SearchUsersAsync(this, Discord, query, limit, options);
|
||||
#endregion
|
||||
|
||||
//Audit logs
|
||||
#region Guild Events
|
||||
|
||||
/// <summary>
|
||||
/// Gets an event in this guild.
|
||||
/// </summary>
|
||||
/// <param name="id">The snowflake identifier for the event.</param>
|
||||
/// <returns>
|
||||
/// An event that is associated with the specified <paramref name="id"/>; <see langword="null"/> if none is found.
|
||||
/// </returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an event within this guild.
|
||||
/// </summary>
|
||||
/// <param name="id">The snowflake identifier for the event.</param>
|
||||
/// <param name="options">The options to be used when sending the request.</param>
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous get operation.
|
||||
/// </returns>
|
||||
public Task<RestGuildEvent> GetEventAsync(ulong id, RequestOptions options = null)
|
||||
=> GuildHelper.GetGuildEventAsync(Discord, id, this, options);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all active events within this guild.
|
||||
/// </summary>
|
||||
/// <param name="options">The options to be used when sending the request.</param>
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous get operation.
|
||||
/// </returns>
|
||||
public Task<IReadOnlyCollection<RestGuildEvent>> GetEventsAsync(RequestOptions options = null)
|
||||
=> GuildHelper.GetGuildEventsAsync(Discord, this, options);
|
||||
|
||||
/// <summary>
|
||||
/// Creates an event within this guild.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the event.</param>
|
||||
/// <param name="privacyLevel">The privacy level of the event.</param>
|
||||
/// <param name="startTime">The start time of the event.</param>
|
||||
/// <param name="type">The type of the event.</param>
|
||||
/// <param name="description">The description of the event.</param>
|
||||
/// <param name="endTime">The end time of the event.</param>
|
||||
/// <param name="channelId">
|
||||
/// The channel id of the event.
|
||||
/// <remarks>
|
||||
/// The event must have a type of <see cref="GuildScheduledEventType.Stage"/> or <see cref="GuildScheduledEventType.Voice"/>
|
||||
/// in order to use this property.
|
||||
/// </remarks>
|
||||
/// </param>
|
||||
/// <param name="speakers">A collection of speakers for the event.</param>
|
||||
/// <param name="location">The location of the event; links are supported</param>
|
||||
/// <param name="options">The options to be used when sending the request.</param>
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous create operation.
|
||||
/// </returns>
|
||||
public Task<RestGuildEvent> 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
|
||||
/// <summary>
|
||||
/// Gets the specified number of audit log entries for this guild.
|
||||
/// </summary>
|
||||
@@ -951,8 +1339,9 @@ namespace Discord.WebSocket
|
||||
/// </returns>
|
||||
public IAsyncEnumerable<IReadOnlyCollection<RestAuditLogEntry>> 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
|
||||
/// <summary>
|
||||
/// Gets a webhook found within this guild.
|
||||
/// </summary>
|
||||
@@ -974,8 +1363,9 @@ namespace Discord.WebSocket
|
||||
/// </returns>
|
||||
public Task<IReadOnlyCollection<RestWebhook>> GetWebhooksAsync(RequestOptions options = null)
|
||||
=> GuildHelper.GetWebhooksAsync(this, Discord, options);
|
||||
#endregion
|
||||
|
||||
//Emotes
|
||||
#region Emotes
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyCollection<GuildEmote>> 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
|
||||
/// <summary>
|
||||
/// Moves the user to the voice channel.
|
||||
/// </summary>
|
||||
/// <param name="user">The user to move.</param>
|
||||
/// <param name="targetChannel">the channel where the user gets moved to.</param>
|
||||
/// <returns>A task that represents the asynchronous operation for moving a user.</returns>
|
||||
public Task MoveAsync(IGuildUser user, IVoiceChannel targetChannel)
|
||||
=> user.ModifyAsync(x => x.Channel = new Optional<IVoiceChannel>(targetChannel));
|
||||
|
||||
/// <summary>
|
||||
/// Disconnects the user from its current voice channel
|
||||
/// </summary>
|
||||
/// <param name="user">The user to disconnect.</param>
|
||||
/// <returns>A task that represents the asynchronous operation for disconnecting a user.</returns>
|
||||
async Task IGuild.DisconnectAsync(IGuildUser user) => await user.ModifyAsync(x => x.Channel = new Optional<IVoiceChannel>());
|
||||
#endregion
|
||||
|
||||
#region Stickers
|
||||
/// <summary>
|
||||
/// Gets a specific sticker within this guild.
|
||||
/// </summary>
|
||||
/// <param name="id">The id of the sticker to get.</param>
|
||||
/// <param name="mode">The <see cref="CacheMode" /> that determines whether the object should be fetched from cache.</param>
|
||||
/// <param name="options">The options to be used when sending the request.</param>
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous get operation. The task result contains the sticker found with the
|
||||
/// specified <paramref name="id"/>; <see langword="null" /> if none is found.
|
||||
/// </returns>
|
||||
public async ValueTask<SocketCustomSticker> 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);
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets a specific sticker within this guild.
|
||||
/// </summary>
|
||||
/// <param name="id">The id of the sticker to get.</param>
|
||||
/// <returns>A sticker, if none is found then <see langword="null"/>.</returns>
|
||||
public SocketCustomSticker GetSticker(ulong id)
|
||||
=> GetStickerAsync(id, CacheMode.CacheOnly).GetAwaiter().GetResult();
|
||||
/// <summary>
|
||||
/// Gets a collection of all stickers within this guild.
|
||||
/// </summary>
|
||||
/// <param name="mode">The <see cref="CacheMode" /> that determines whether the object should be fetched from cache.</param>
|
||||
/// <param name="options">The options to be used when sending the request.</param>
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous get operation. The task result contains a read-only collection
|
||||
/// of stickers found within the guild.
|
||||
/// </returns>
|
||||
public async ValueTask<IReadOnlyCollection<SocketCustomSticker>> GetStickersAsync(CacheMode mode = CacheMode.AllowDownload,
|
||||
RequestOptions options = null)
|
||||
{
|
||||
if (Stickers.Count > 0)
|
||||
return Stickers;
|
||||
|
||||
if (mode == CacheMode.CacheOnly)
|
||||
return ImmutableArray.Create<SocketCustomSticker>();
|
||||
|
||||
var models = await Discord.ApiClient.ListGuildStickersAsync(Id, options).ConfigureAwait(false);
|
||||
|
||||
List<SocketCustomSticker> stickers = new();
|
||||
|
||||
foreach (var model in models)
|
||||
{
|
||||
stickers.Add(AddOrUpdateSticker(model));
|
||||
}
|
||||
|
||||
return stickers;
|
||||
}
|
||||
/// <summary>
|
||||
/// Creates a new sticker in this guild.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the sticker.</param>
|
||||
/// <param name="description">The description of the sticker.</param>
|
||||
/// <param name="tags">The tags of the sticker.</param>
|
||||
/// <param name="image">The image of the new emote.</param>
|
||||
/// <param name="options">The options to be used when sending the request.</param>
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous creation operation. The task result contains the created sticker.
|
||||
/// </returns>
|
||||
public async Task<SocketCustomSticker> CreateStickerAsync(string name, string description, IEnumerable<string> tags, Image image,
|
||||
RequestOptions options = null)
|
||||
{
|
||||
var model = await GuildHelper.CreateStickerAsync(Discord, this, name, description, tags, image, options).ConfigureAwait(false);
|
||||
|
||||
return AddOrUpdateSticker(model);
|
||||
}
|
||||
/// <summary>
|
||||
/// Creates a new sticker in this guild
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the sticker.</param>
|
||||
/// <param name="description">The description of the sticker.</param>
|
||||
/// <param name="tags">The tags of the sticker.</param>
|
||||
/// <param name="path">The path of the file to upload.</param>
|
||||
/// <param name="options">The options to be used when sending the request.</param>
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous creation operation. The task result contains the created sticker.
|
||||
/// </returns>
|
||||
public Task<SocketCustomSticker> CreateStickerAsync(string name, string description, IEnumerable<string> tags, string path,
|
||||
RequestOptions options = null)
|
||||
{
|
||||
var fs = File.OpenRead(path);
|
||||
return CreateStickerAsync(name, description, tags, fs, Path.GetFileName(fs.Name), options);
|
||||
}
|
||||
/// <summary>
|
||||
/// Creates a new sticker in this guild
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the sticker.</param>
|
||||
/// <param name="description">The description of the sticker.</param>
|
||||
/// <param name="tags">The tags of the sticker.</param>
|
||||
/// <param name="stream">The stream containing the file data.</param>
|
||||
/// <param name="filename">The name of the file <b>with</b> the extension, ex: image.png.</param>
|
||||
/// <param name="options">The options to be used when sending the request.</param>
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous creation operation. The task result contains the created sticker.
|
||||
/// </returns>
|
||||
public async Task<SocketCustomSticker> CreateStickerAsync(string name, string description, IEnumerable<string> 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);
|
||||
}
|
||||
/// <summary>
|
||||
/// Deletes a sticker within this guild.
|
||||
/// </summary>
|
||||
/// <param name="sticker">The sticker to delete.</param>
|
||||
/// <param name="options">The options to be used when sending the request.</param>
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous removal operation.
|
||||
/// </returns>
|
||||
public Task DeleteStickerAsync(SocketCustomSticker sticker, RequestOptions options = null)
|
||||
=> sticker.DeleteAsync(options);
|
||||
#endregion
|
||||
|
||||
#region Voice States
|
||||
internal async Task<SocketVoiceState> 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
|
||||
/// <inheritdoc />
|
||||
ulong? IGuild.AFKChannelId => AFKChannelId;
|
||||
/// <inheritdoc />
|
||||
@@ -1216,7 +1755,17 @@ namespace Discord.WebSocket
|
||||
int? IGuild.ApproximateMemberCount => null;
|
||||
/// <inheritdoc />
|
||||
int? IGuild.ApproximatePresenceCount => null;
|
||||
|
||||
/// <inheritdoc />
|
||||
IReadOnlyCollection<ICustomSticker> IGuild.Stickers => Stickers;
|
||||
/// <inheritdoc />
|
||||
async Task<IGuildScheduledEvent> 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);
|
||||
/// <inheritdoc />
|
||||
async Task<IGuildScheduledEvent> IGuild.GetEventAsync(ulong id, RequestOptions options)
|
||||
=> await GetEventAsync(id, options).ConfigureAwait(false);
|
||||
/// <inheritdoc />
|
||||
async Task<IReadOnlyCollection<IGuildScheduledEvent>> IGuild.GetEventsAsync(RequestOptions options)
|
||||
=> await GetEventsAsync(options).ConfigureAwait(false);
|
||||
/// <inheritdoc />
|
||||
async Task<IReadOnlyCollection<IBan>> IGuild.GetBansAsync(RequestOptions options)
|
||||
=> await GetBansAsync(options).ConfigureAwait(false);
|
||||
@@ -1240,15 +1789,27 @@ namespace Discord.WebSocket
|
||||
Task<ITextChannel> IGuild.GetTextChannelAsync(ulong id, CacheMode mode, RequestOptions options)
|
||||
=> Task.FromResult<ITextChannel>(GetTextChannel(id));
|
||||
/// <inheritdoc />
|
||||
Task<IThreadChannel> IGuild.GetThreadChannelAsync(ulong id, CacheMode mode, RequestOptions options)
|
||||
=> Task.FromResult<IThreadChannel>(GetThreadChannel(id));
|
||||
/// <inheritdoc />
|
||||
Task<IReadOnlyCollection<IThreadChannel>> IGuild.GetThreadChannelsAsync(CacheMode mode, RequestOptions options)
|
||||
=> Task.FromResult<IReadOnlyCollection<IThreadChannel>>(ThreadChannels);
|
||||
/// <inheritdoc />
|
||||
Task<IReadOnlyCollection<IVoiceChannel>> IGuild.GetVoiceChannelsAsync(CacheMode mode, RequestOptions options)
|
||||
=> Task.FromResult<IReadOnlyCollection<IVoiceChannel>>(VoiceChannels);
|
||||
/// <inheritdoc />
|
||||
Task<IReadOnlyCollection<ICategoryChannel>> IGuild.GetCategoriesAsync(CacheMode mode , RequestOptions options)
|
||||
Task<IReadOnlyCollection<ICategoryChannel>> IGuild.GetCategoriesAsync(CacheMode mode, RequestOptions options)
|
||||
=> Task.FromResult<IReadOnlyCollection<ICategoryChannel>>(CategoryChannels);
|
||||
/// <inheritdoc />
|
||||
Task<IVoiceChannel> IGuild.GetVoiceChannelAsync(ulong id, CacheMode mode, RequestOptions options)
|
||||
=> Task.FromResult<IVoiceChannel>(GetVoiceChannel(id));
|
||||
/// <inheritdoc />
|
||||
Task<IStageChannel> IGuild.GetStageChannelAsync(ulong id, CacheMode mode, RequestOptions options)
|
||||
=> Task.FromResult<IStageChannel>(GetStageChannel(id));
|
||||
/// <inheritdoc />
|
||||
Task<IReadOnlyCollection<IStageChannel>> IGuild.GetStageChannelsAsync(CacheMode mode, RequestOptions options)
|
||||
=> Task.FromResult<IReadOnlyCollection<IStageChannel>>(StageChannels);
|
||||
/// <inheritdoc />
|
||||
Task<IVoiceChannel> IGuild.GetAFKChannelAsync(CacheMode mode, RequestOptions options)
|
||||
=> Task.FromResult<IVoiceChannel>(AFKChannel);
|
||||
/// <inheritdoc />
|
||||
@@ -1273,6 +1834,9 @@ namespace Discord.WebSocket
|
||||
async Task<IVoiceChannel> IGuild.CreateVoiceChannelAsync(string name, Action<VoiceChannelProperties> func, RequestOptions options)
|
||||
=> await CreateVoiceChannelAsync(name, func, options).ConfigureAwait(false);
|
||||
/// <inheritdoc />
|
||||
async Task<IStageChannel> IGuild.CreateStageChannelAsync(string name, Action<VoiceChannelProperties> func, RequestOptions options)
|
||||
=> await CreateStageChannelAsync(name, func, options).ConfigureAwait(false);
|
||||
/// <inheritdoc />
|
||||
async Task<ICategoryChannel> IGuild.CreateCategoryAsync(string name, Action<GuildChannelProperties> func, RequestOptions options)
|
||||
=> await CreateCategoryChannelAsync(name, func, options).ConfigureAwait(false);
|
||||
|
||||
@@ -1350,6 +1914,37 @@ namespace Discord.WebSocket
|
||||
/// <inheritdoc />
|
||||
async Task<IReadOnlyCollection<IWebhook>> IGuild.GetWebhooksAsync(RequestOptions options)
|
||||
=> await GetWebhooksAsync(options).ConfigureAwait(false);
|
||||
/// <inheritdoc />
|
||||
async Task<IReadOnlyCollection<IApplicationCommand>> IGuild.GetApplicationCommandsAsync (RequestOptions options)
|
||||
=> await GetApplicationCommandsAsync(options).ConfigureAwait(false);
|
||||
/// <inheritdoc />
|
||||
async Task<ICustomSticker> IGuild.CreateStickerAsync(string name, string description, IEnumerable<string> tags, Image image, RequestOptions options)
|
||||
=> await CreateStickerAsync(name, description, tags, image, options);
|
||||
/// <inheritdoc />
|
||||
async Task<ICustomSticker> IGuild.CreateStickerAsync(string name, string description, IEnumerable<string> tags, Stream stream, string filename, RequestOptions options)
|
||||
=> await CreateStickerAsync(name, description, tags, stream, filename, options);
|
||||
/// <inheritdoc />
|
||||
async Task<ICustomSticker> IGuild.CreateStickerAsync(string name, string description, IEnumerable<string> tags, string path, RequestOptions options)
|
||||
=> await CreateStickerAsync(name, description, tags, path, options);
|
||||
/// <inheritdoc />
|
||||
async Task<ICustomSticker> IGuild.GetStickerAsync(ulong id, CacheMode mode, RequestOptions options)
|
||||
=> await GetStickerAsync(id, mode, options);
|
||||
/// <inheritdoc />
|
||||
async Task<IReadOnlyCollection<ICustomSticker>> IGuild.GetStickersAsync(CacheMode mode, RequestOptions options)
|
||||
=> await GetStickersAsync(mode, options);
|
||||
/// <inheritdoc />
|
||||
Task IGuild.DeleteStickerAsync(ICustomSticker sticker, RequestOptions options)
|
||||
=> DeleteStickerAsync(_stickers[sticker.Id], options);
|
||||
/// <inheritdoc />
|
||||
async Task<IApplicationCommand> IGuild.GetApplicationCommandAsync(ulong id, CacheMode mode, RequestOptions options)
|
||||
=> await GetApplicationCommandAsync(id, mode, options);
|
||||
/// <inheritdoc />
|
||||
async Task<IApplicationCommand> IGuild.CreateApplicationCommandAsync(ApplicationCommandProperties properties, RequestOptions options)
|
||||
=> await CreateApplicationCommandAsync(properties, options);
|
||||
/// <inheritdoc />
|
||||
async Task<IReadOnlyCollection<IApplicationCommand>> 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
|
||||
}
|
||||
}
|
||||
|
||||
216
src/Discord.Net.WebSocket/Entities/Guilds/SocketGuildEvent.cs
Normal file
216
src/Discord.Net.WebSocket/Entities/Guilds/SocketGuildEvent.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a WebSocket-based guild event.
|
||||
/// </summary>
|
||||
public class SocketGuildEvent : SocketEntity<ulong>, IGuildScheduledEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the guild of the event.
|
||||
/// </summary>
|
||||
public SocketGuild Guild { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the channel of the event.
|
||||
/// </summary>
|
||||
public SocketGuildChannel Channel { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the user who created the event.
|
||||
/// </summary>
|
||||
public SocketGuildUser Creator { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Name { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Description { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DateTimeOffset StartTime { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DateTimeOffset? EndTime { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public GuildScheduledEventPrivacyLevel PrivacyLevel { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public GuildScheduledEventStatus Status { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public GuildScheduledEventType Type { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ulong? EntityId { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Location { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task DeleteAsync(RequestOptions options = null)
|
||||
=> GuildHelper.DeleteEventAsync(Discord, this, options);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task StartAsync(RequestOptions options = null)
|
||||
=> ModifyAsync(x => x.Status = GuildScheduledEventStatus.Active);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task EndAsync(RequestOptions options = null)
|
||||
=> ModifyAsync(x => x.Status = Status == GuildScheduledEventStatus.Scheduled
|
||||
? GuildScheduledEventStatus.Cancelled
|
||||
: GuildScheduledEventStatus.Completed);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task ModifyAsync(Action<GuildScheduledEventsProperties> func, RequestOptions options = null)
|
||||
{
|
||||
var model = await GuildHelper.ModifyGuildEventAsync(Discord, func, this, options).ConfigureAwait(false);
|
||||
Update(model);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection of users that are interested in this event.
|
||||
/// </summary>
|
||||
/// <param name="limit">The amount of users to fetch.</param>
|
||||
/// <param name="options">The options to be used when sending the request.</param>
|
||||
/// <returns>
|
||||
/// A read-only collection of users.
|
||||
/// </returns>
|
||||
public Task<IReadOnlyCollection<RestUser>> GetUsersAsync(int limit = 100, RequestOptions options = null)
|
||||
=> GuildHelper.GetEventUsersAsync(Discord, this, limit, options);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection of N users interested in the event.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <note type="important">
|
||||
/// The returned collection is an asynchronous enumerable object; one must call
|
||||
/// <see cref="AsyncEnumerableExtensions.FlattenAsync{T}"/> to access the individual messages as a
|
||||
/// collection.
|
||||
/// </note>
|
||||
/// 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 <see cref="DiscordConfig.MaxGuildEventUsersPerBatch"/>.
|
||||
/// In other words, if there are 300 users, and the <see cref="Discord.DiscordConfig.MaxGuildEventUsersPerBatch"/> constant
|
||||
/// is <c>100</c>, the request will be split into 3 individual requests; thus returning 3 individual asynchronous
|
||||
/// responses, hence the need of flattening.
|
||||
/// </remarks>
|
||||
/// <param name="options">The options to be used when sending the request.</param>
|
||||
/// <returns>
|
||||
/// Paged collection of users.
|
||||
/// </returns>
|
||||
public IAsyncEnumerable<IReadOnlyCollection<RestUser>> GetUsersAsync(RequestOptions options = null)
|
||||
=> GuildHelper.GetEventUsersAsync(Discord, this, null, null, options);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection of N users interested in the event.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <note type="important">
|
||||
/// The returned collection is an asynchronous enumerable object; one must call
|
||||
/// <see cref="AsyncEnumerableExtensions.FlattenAsync{T}"/> to access the individual users as a
|
||||
/// collection.
|
||||
/// </note>
|
||||
/// <note type="warning">
|
||||
/// 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!
|
||||
/// </note>
|
||||
/// This method will attempt to fetch the number of users specified under <paramref name="limit"/> around
|
||||
/// the user <paramref name="fromUserId"/> depending on the <paramref name="dir"/>. The library will
|
||||
/// attempt to split up the requests according to your <paramref name="limit"/> and
|
||||
/// <see cref="DiscordConfig.MaxGuildEventUsersPerBatch"/>. In other words, should the user request 500 users,
|
||||
/// and the <see cref="Discord.DiscordConfig.MaxGuildEventUsersPerBatch"/> constant is <c>100</c>, the request will
|
||||
/// be split into 5 individual requests; thus returning 5 individual asynchronous responses, hence the need
|
||||
/// of flattening.
|
||||
/// </remarks>
|
||||
/// <param name="fromUserId">The ID of the starting user to get the users from.</param>
|
||||
/// <param name="dir">The direction of the users to be gotten from.</param>
|
||||
/// <param name="limit">The numbers of users to be gotten from.</param>
|
||||
/// <param name="options">The options to be used when sending the request.</param>
|
||||
/// <returns>
|
||||
/// Paged collection of users.
|
||||
/// </returns>
|
||||
public IAsyncEnumerable<IReadOnlyCollection<RestUser>> GetUsersAsync(ulong fromUserId, Direction dir, int limit = DiscordConfig.MaxGuildEventUsersPerBatch, RequestOptions options = null)
|
||||
=> GuildHelper.GetEventUsersAsync(Discord, this, fromUserId, dir, limit, options);
|
||||
|
||||
#region IGuildScheduledEvent
|
||||
|
||||
/// <inheritdoc/>
|
||||
IAsyncEnumerable<IReadOnlyCollection<IUser>> IGuildScheduledEvent.GetUsersAsync(RequestOptions options)
|
||||
=> GetUsersAsync(options);
|
||||
/// <inheritdoc/>
|
||||
IAsyncEnumerable<IReadOnlyCollection<IUser>> IGuildScheduledEvent.GetUsersAsync(ulong fromUserId, Direction dir, int limit, RequestOptions options)
|
||||
=> GetUsersAsync(fromUserId, dir, limit, options);
|
||||
/// <inheritdoc/>
|
||||
IGuild IGuildScheduledEvent.Guild => Guild;
|
||||
/// <inheritdoc/>
|
||||
IUser IGuildScheduledEvent.Creator => Creator;
|
||||
/// <inheritdoc/>
|
||||
ulong? IGuildScheduledEvent.ChannelId => Channel?.Id;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using DataModel = Discord.API.ApplicationCommandInteractionData;
|
||||
using Model = Discord.API.Interaction;
|
||||
|
||||
namespace Discord.WebSocket
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a Websocket-based slash command received over the gateway.
|
||||
/// </summary>
|
||||
public class SocketMessageCommand : SocketCommandBase, IMessageCommandInteraction, IDiscordInteraction
|
||||
{
|
||||
/// <summary>
|
||||
/// The data associated with this interaction.
|
||||
/// </summary>
|
||||
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
|
||||
/// <inheritdoc/>
|
||||
IMessageCommandInteractionData IMessageCommandInteraction.Data => Data;
|
||||
|
||||
//IDiscordInteraction
|
||||
/// <inheritdoc/>
|
||||
IDiscordInteractionData IDiscordInteraction.Data => Data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Model = Discord.API.ApplicationCommandInteractionData;
|
||||
|
||||
namespace Discord.WebSocket
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the data tied with the <see cref="SocketMessageCommand"/> interaction.
|
||||
/// </summary>
|
||||
public class SocketMessageCommandData : SocketCommandBaseData, IMessageCommandInteractionData, IDiscordInteractionData
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the message associated with this message command.
|
||||
/// </summary>
|
||||
public SocketMessage Message
|
||||
=> ResolvableData?.Messages.FirstOrDefault().Value;
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>
|
||||
/// <b>Note</b> Not implemented for <see cref="SocketMessageCommandData"/>
|
||||
/// </remarks>
|
||||
public override IReadOnlyCollection<IApplicationCommandInteractionDataOption> 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
|
||||
/// <inheritdoc/>
|
||||
IMessage IMessageCommandInteractionData.Message => Message;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using DataModel = Discord.API.ApplicationCommandInteractionData;
|
||||
using Model = Discord.API.Interaction;
|
||||
|
||||
namespace Discord.WebSocket
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a Websocket-based slash command received over the gateway.
|
||||
/// </summary>
|
||||
public class SocketUserCommand : SocketCommandBase, IUserCommandInteraction, IDiscordInteraction
|
||||
{
|
||||
/// <summary>
|
||||
/// The data associated with this interaction.
|
||||
/// </summary>
|
||||
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
|
||||
/// <inheritdoc/>
|
||||
IUserCommandInteractionData IUserCommandInteraction.Data => Data;
|
||||
|
||||
//IDiscordInteraction
|
||||
/// <inheritdoc/>
|
||||
IDiscordInteractionData IDiscordInteraction.Data => Data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Model = Discord.API.ApplicationCommandInteractionData;
|
||||
|
||||
namespace Discord.WebSocket
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the data tied with the <see cref="SocketUserCommand"/> interaction.
|
||||
/// </summary>
|
||||
public class SocketUserCommandData : SocketCommandBaseData, IUserCommandInteractionData, IDiscordInteractionData
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the user who this command targets.
|
||||
/// </summary>
|
||||
public SocketUser Member
|
||||
=> (SocketUser)ResolvableData.GuildMembers.Values.FirstOrDefault() ?? ResolvableData.Users.Values.FirstOrDefault();
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>
|
||||
/// <b>Note</b> Not implemented for <see cref="SocketUserCommandData"/>
|
||||
/// </remarks>
|
||||
public override IReadOnlyCollection<IApplicationCommandInteractionDataOption> 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
|
||||
/// <inheritdoc/>
|
||||
IUser IUserCommandInteractionData.User => Member;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a Websocket-based interaction type for Message Components.
|
||||
/// </summary>
|
||||
public class SocketMessageComponent : SocketInteraction, IComponentInteraction, IDiscordInteraction
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the data received with this interaction, contains the button that was clicked.
|
||||
/// </summary>
|
||||
public new SocketMessageComponentData Data { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the message that contained the trigger for this interaction.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
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<Embed>();
|
||||
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<string>.Unspecified,
|
||||
AllowedMentions = allowedMentions?.ToModel(),
|
||||
Embeds = embeds.Select(x => x.ToModel()).ToArray(),
|
||||
TTS = isTTS,
|
||||
Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the message which this component resides in with the type <see cref="InteractionResponseType.UpdateMessage"/>
|
||||
/// </summary>
|
||||
/// <param name="func">A delegate containing the properties to modify the message with.</param>
|
||||
/// <param name="options">The request options for this <see langword="async"/> request.</param>
|
||||
/// <returns>A task that represents the asynchronous operation of updating the message.</returns>
|
||||
public async Task UpdateAsync(Action<MessageProperties> 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<API.Embed>() : 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<API.AllowedMentions>.Unspecified,
|
||||
Embeds = apiEmbeds?.ToArray() ?? Optional<API.Embed[]>.Unspecified,
|
||||
Components = args.Components.IsSpecified
|
||||
? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Array.Empty<API.ActionRowComponent>()
|
||||
: Optional<API.ActionRowComponent[]>.Unspecified,
|
||||
Flags = args.Flags.IsSpecified ? args.Flags.Value ?? Optional<MessageFlags>.Unspecified : Optional<MessageFlags>.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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<RestFollowupMessage> 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<Embed>();
|
||||
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<API.AllowedMentions>.Unspecified,
|
||||
IsTTS = isTTS,
|
||||
Embeds = embeds.Select(x => x.ToModel()).ToArray(),
|
||||
Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified
|
||||
};
|
||||
|
||||
if (ephemeral)
|
||||
args.Flags = MessageFlags.Ephemeral;
|
||||
|
||||
return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<RestFollowupMessage> 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<Embed>();
|
||||
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<API.AllowedMentions>.Unspecified,
|
||||
IsTTS = isTTS,
|
||||
Embeds = embeds.Select(x => x.ToModel()).ToArray(),
|
||||
Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified,
|
||||
File = fileStream is not null ? new MultipartFile(fileStream, fileName) : Optional<MultipartFile>.Unspecified
|
||||
};
|
||||
|
||||
if (ephemeral)
|
||||
args.Flags = MessageFlags.Ephemeral;
|
||||
|
||||
return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<RestFollowupMessage> 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<Embed>();
|
||||
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<API.AllowedMentions>.Unspecified,
|
||||
IsTTS = isTTS,
|
||||
Embeds = embeds.Select(x => x.ToModel()).ToArray(),
|
||||
Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified,
|
||||
File = !string.IsNullOrEmpty(filePath) ? new MultipartFile(new MemoryStream(File.ReadAllBytes(filePath), false), fileName) : Optional<MultipartFile>.Unspecified
|
||||
};
|
||||
|
||||
if (ephemeral)
|
||||
args.Flags = MessageFlags.Ephemeral;
|
||||
|
||||
return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defers an interaction and responds with type 5 (<see cref="InteractionResponseType.DeferredChannelMessageWithSource"/>)
|
||||
/// </summary>
|
||||
/// <param name="ephemeral"><see langword="true"/> to send this message ephemerally, otherwise <see langword="false"/>.</param>
|
||||
/// <param name="options">The request options for this <see langword="async"/> request.</param>
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous operation of acknowledging the interaction.
|
||||
/// </returns>
|
||||
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<API.InteractionCallbackData>.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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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<API.InteractionCallbackData>.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
|
||||
/// <inheritdoc/>
|
||||
IComponentInteractionData IComponentInteraction.Data => Data;
|
||||
|
||||
/// <inheritdoc/>
|
||||
IUserMessage IComponentInteraction.Message => Message;
|
||||
|
||||
//IDiscordInteraction
|
||||
/// <inheritdoc/>
|
||||
IDiscordInteractionData IDiscordInteraction.Data => Data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using System.Collections.Generic;
|
||||
using Model = Discord.API.MessageComponentInteractionData;
|
||||
|
||||
namespace Discord.WebSocket
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the data sent with a <see cref="InteractionType.MessageComponent"/>.
|
||||
/// </summary>
|
||||
public class SocketMessageComponentData : IComponentInteractionData
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the components Custom Id that was clicked.
|
||||
/// </summary>
|
||||
public string CustomId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the component clicked.
|
||||
/// </summary>
|
||||
public ComponentType Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value(s) of a <see cref="SelectMenuComponent"/> interaction response.
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<string> Values { get; }
|
||||
|
||||
internal SocketMessageComponentData(Model model)
|
||||
{
|
||||
CustomId = model.CustomId;
|
||||
Type = model.ComponentType;
|
||||
Values = model.Values.GetValueOrDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a <see cref="InteractionType.ApplicationCommandAutocomplete"/> received over the gateway.
|
||||
/// </summary>
|
||||
public class SocketAutocompleteInteraction : SocketInteraction, IAutocompleteInteraction, IDiscordInteraction
|
||||
{
|
||||
/// <summary>
|
||||
/// The autocomplete data of this interaction.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Responds to this interaction with a set of choices.
|
||||
/// </summary>
|
||||
/// <param name="result">
|
||||
/// The set of choices for the user to pick from.
|
||||
/// <remarks>
|
||||
/// A max of 20 choices are allowed. Passing <see langword="null"/> for this argument will show the executing user that
|
||||
/// there is no choices for their autocompleted input.
|
||||
/// </remarks>
|
||||
/// </param>
|
||||
/// <param name="options">The request options for this response.</param>
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous operation of responding to this interaction.
|
||||
/// </returns>
|
||||
public async Task RespondAsync(IEnumerable<AutocompleteResult> 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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Responds to this interaction with a set of choices.
|
||||
/// </summary>
|
||||
/// <param name="options">The request options for this response.</param>
|
||||
/// <param name="result">
|
||||
/// The set of choices for the user to pick from.
|
||||
/// <remarks>
|
||||
/// A max of 20 choices are allowed. Passing <see langword="null"/> for this argument will show the executing user that
|
||||
/// there is no choices for their autocompleted input.
|
||||
/// </remarks>
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous operation of responding to this interaction.
|
||||
/// </returns>
|
||||
public Task RespondAsync(RequestOptions options = null, params AutocompleteResult[] result)
|
||||
=> RespondAsync(result, options);
|
||||
|
||||
/// <inheritdoc/>
|
||||
[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!");
|
||||
|
||||
/// <inheritdoc/>
|
||||
[Obsolete("Autocomplete interactions cannot have followups!", true)]
|
||||
public override Task<RestFollowupMessage> 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!");
|
||||
|
||||
/// <inheritdoc/>
|
||||
[Obsolete("Autocomplete interactions cannot have followups!", true)]
|
||||
public override Task<RestFollowupMessage> 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!");
|
||||
|
||||
/// <inheritdoc/>
|
||||
[Obsolete("Autocomplete interactions cannot have followups!", true)]
|
||||
public override Task<RestFollowupMessage> 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!");
|
||||
|
||||
/// <inheritdoc/>
|
||||
[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
|
||||
/// <inheritdoc/>
|
||||
IAutocompleteInteractionData IAutocompleteInteraction.Data => Data;
|
||||
|
||||
//IDiscordInteraction
|
||||
/// <inheritdoc/>
|
||||
IDiscordInteractionData IDiscordInteraction.Data => Data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using DataModel = Discord.API.AutocompleteInteractionData;
|
||||
|
||||
namespace Discord.WebSocket
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents data for a slash commands autocomplete interaction.
|
||||
/// </summary>
|
||||
public class SocketAutocompleteInteractionData : IAutocompleteInteractionData, IDiscordInteractionData
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the name of the invoked command.
|
||||
/// </summary>
|
||||
public string CommandName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the id of the invoked command.
|
||||
/// </summary>
|
||||
public ulong CommandId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the invoked command.
|
||||
/// </summary>
|
||||
public ApplicationCommandType Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the version of the invoked command.
|
||||
/// </summary>
|
||||
public ulong Version { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current autocomplete option that is actively being filled out.
|
||||
/// </summary>
|
||||
public AutocompleteOption Current { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection of all the other options the executing users has filled out.
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<AutocompleteOption> 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<AutocompleteOption> GetOptions(API.AutocompleteInteractionDataOption model)
|
||||
{
|
||||
var options = new List<AutocompleteOption>();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using DataModel = Discord.API.ApplicationCommandInteractionData;
|
||||
using Model = Discord.API.Interaction;
|
||||
|
||||
namespace Discord.WebSocket
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a Websocket-based slash command received over the gateway.
|
||||
/// </summary>
|
||||
public class SocketSlashCommand : SocketCommandBase, ISlashCommandInteraction, IDiscordInteraction
|
||||
{
|
||||
/// <summary>
|
||||
/// The data associated with this interaction.
|
||||
/// </summary>
|
||||
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
|
||||
/// <inheritdoc/>
|
||||
IApplicationCommandInteractionData ISlashCommandInteraction.Data => Data;
|
||||
|
||||
//IDiscordInteraction
|
||||
/// <inheritdoc/>
|
||||
IDiscordInteractionData IDiscordInteraction.Data => Data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using Model = Discord.API.ApplicationCommandInteractionData;
|
||||
|
||||
namespace Discord.WebSocket
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the data tied with the <see cref="SocketSlashCommand"/> interaction.
|
||||
/// </summary>
|
||||
public class SocketSlashCommandData : SocketCommandBaseData<SocketSlashCommandDataOption>, 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<SocketSlashCommandDataOption>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using Model = Discord.API.ApplicationCommandInteractionDataOption;
|
||||
|
||||
namespace Discord.WebSocket
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a Websocket-based <see cref="IApplicationCommandInteractionDataOption"/> received by the gateway.
|
||||
/// </summary>
|
||||
public class SocketSlashCommandDataOption : IApplicationCommandInteractionDataOption
|
||||
{
|
||||
#region SocketSlashCommandDataOption
|
||||
/// <inheritdoc/>
|
||||
public string Name { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public object Value { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ApplicationCommandOptionType Type { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The sub command options received for this sub command group.
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<SocketSlashCommandDataOption> 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<SocketSlashCommandDataOption>();
|
||||
}
|
||||
#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> IApplicationCommandInteractionDataOption.Options
|
||||
=> Options;
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a Websocket-based <see cref="IApplicationCommand"/>.
|
||||
/// </summary>
|
||||
public class SocketApplicationCommand : SocketEntity<ulong>, IApplicationCommand
|
||||
{
|
||||
#region SocketApplicationCommand
|
||||
/// <summary>
|
||||
/// <see langword="true"/> if this command is a global command, otherwise <see langword="false"/>.
|
||||
/// </summary>
|
||||
public bool IsGlobalCommand
|
||||
=> Guild == null;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ulong ApplicationId { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Name { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ApplicationCommandType Type { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Description { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsDefaultPermission { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// A collection of <see cref="SocketApplicationCommandOption"/>'s for this command.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If the <see cref="Type"/> is not a slash command, this field will be an empty collection.
|
||||
/// </remarks>
|
||||
public IReadOnlyCollection<SocketApplicationCommandOption> Options { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DateTimeOffset CreatedAt
|
||||
=> SnowflakeUtils.FromSnowflake(Id);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the guild this command resides in, if this command is a global command then it will return <see langword="null"/>
|
||||
/// </summary>
|
||||
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<SocketApplicationCommandOption>();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task DeleteAsync(RequestOptions options = null)
|
||||
=> InteractionHelper.DeleteUnknownApplicationCommandAsync(Discord, GuildId, this, options);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task ModifyAsync(Action<ApplicationCommandProperties> func, RequestOptions options = null)
|
||||
{
|
||||
return ModifyAsync<ApplicationCommandProperties>(func, options);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task ModifyAsync<TArg>(Action<TArg> 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<IApplicationCommandOption> IApplicationCommand.Options => Options;
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Model = Discord.API.ApplicationCommandOptionChoice;
|
||||
|
||||
namespace Discord.WebSocket
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a choice for a <see cref="SocketApplicationCommandOption"/>.
|
||||
/// </summary>
|
||||
public class SocketApplicationCommandChoice : IApplicationCommandOptionChoice
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public string Name { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using Model = Discord.API.ApplicationCommandOption;
|
||||
|
||||
namespace Discord.WebSocket
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an option for a <see cref="SocketApplicationCommand"/>.
|
||||
/// </summary>
|
||||
public class SocketApplicationCommandOption : IApplicationCommandOption
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public string Name { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ApplicationCommandOptionType Type { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Description { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool? IsDefault { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool? IsRequired { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public double? MinValue { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public double? MaxValue { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Choices for string and int types for the user to pick from.
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<SocketApplicationCommandChoice> Choices { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// If the option is a subcommand or subcommand group type, this nested options will be the parameters.
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<SocketApplicationCommandOption> Options { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The allowed channel types for this option.
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<ChannelType> 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<SocketApplicationCommandChoice>();
|
||||
|
||||
Options = model.Options.IsSpecified
|
||||
? model.Options.Value.Select(Create).ToImmutableArray()
|
||||
: ImmutableArray.Create<SocketApplicationCommandOption>();
|
||||
|
||||
ChannelTypes = model.ChannelTypes.IsSpecified
|
||||
? model.ChannelTypes.Value.ToImmutableArray()
|
||||
: ImmutableArray.Create<ChannelType>();
|
||||
}
|
||||
|
||||
IReadOnlyCollection<IApplicationCommandOptionChoice> IApplicationCommandOption.Choices => Choices;
|
||||
IReadOnlyCollection<IApplicationCommandOption> IApplicationCommandOption.Options => Options;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for User, Message, and Slash command interactions.
|
||||
/// </summary>
|
||||
public class SocketCommandBase : SocketInteraction
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the name of the invoked command.
|
||||
/// </summary>
|
||||
public string CommandName
|
||||
=> Data.Name;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the id of the invoked command.
|
||||
/// </summary>
|
||||
public ulong CommandId
|
||||
=> Data.Id;
|
||||
|
||||
/// <summary>
|
||||
/// The data associated with this interaction.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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<Embed>();
|
||||
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<API.AllowedMentions>.Unspecified,
|
||||
Embeds = embeds.Select(x => x.ToModel()).ToArray(),
|
||||
TTS = isTTS,
|
||||
Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified,
|
||||
Flags = ephemeral ? MessageFlags.Ephemeral : Optional<MessageFlags>.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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<RestFollowupMessage> 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<Embed>();
|
||||
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<API.AllowedMentions>.Unspecified,
|
||||
IsTTS = isTTS,
|
||||
Embeds = embeds.Select(x => x.ToModel()).ToArray(),
|
||||
Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified
|
||||
};
|
||||
|
||||
if (ephemeral)
|
||||
args.Flags = MessageFlags.Ephemeral;
|
||||
|
||||
return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<RestFollowupMessage> 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<Embed>();
|
||||
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<API.AllowedMentions>.Unspecified,
|
||||
IsTTS = isTTS,
|
||||
Embeds = embeds.Select(x => x.ToModel()).ToArray(),
|
||||
Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified,
|
||||
File = fileStream is not null ? new MultipartFile(fileStream, fileName) : Optional<MultipartFile>.Unspecified
|
||||
};
|
||||
|
||||
if (ephemeral)
|
||||
args.Flags = MessageFlags.Ephemeral;
|
||||
|
||||
return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<RestFollowupMessage> 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<Embed>();
|
||||
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<API.AllowedMentions>.Unspecified,
|
||||
IsTTS = isTTS,
|
||||
Embeds = embeds.Select(x => x.ToModel()).ToArray(),
|
||||
Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified,
|
||||
File = !string.IsNullOrEmpty(filePath) ? new MultipartFile(new MemoryStream(File.ReadAllBytes(filePath), false), fileName) : Optional<MultipartFile>.Unspecified
|
||||
};
|
||||
|
||||
if (ephemeral)
|
||||
args.Flags = MessageFlags.Ephemeral;
|
||||
|
||||
return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Acknowledges this interaction with the <see cref="InteractionResponseType.DeferredChannelMessageWithSource"/>.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous operation of acknowledging the interaction.
|
||||
/// </returns>
|
||||
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<MessageFlags>.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using System.Collections.Generic;
|
||||
using Model = Discord.API.ApplicationCommandInteractionData;
|
||||
|
||||
namespace Discord.WebSocket
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the base data tied with the <see cref="SocketCommandBase"/> interaction.
|
||||
/// </summary>
|
||||
public class SocketCommandBaseData<TOption> : SocketEntity<ulong>, IApplicationCommandInteractionData where TOption : IApplicationCommandInteractionDataOption
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public string Name { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <typeparamref name="TOption"/> received with this interaction.
|
||||
/// </summary>
|
||||
public virtual IReadOnlyCollection<TOption> Options { get; internal set; }
|
||||
|
||||
internal readonly SocketResolvableData<Model> 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<Model>(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<IApplicationCommandInteractionDataOption> IApplicationCommandInteractionData.Options
|
||||
=> (IReadOnlyCollection<IApplicationCommandInteractionDataOption>)Options;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the base data tied with the <see cref="SocketCommandBase"/> interaction.
|
||||
/// </summary>
|
||||
public class SocketCommandBaseData : SocketCommandBaseData<IApplicationCommandInteractionDataOption>
|
||||
{
|
||||
internal SocketCommandBaseData(DiscordSocketClient client, Model model, ulong? guildId)
|
||||
: base(client, model, guildId) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Discord.WebSocket
|
||||
{
|
||||
internal class SocketResolvableData<T> where T : API.IResolvable
|
||||
{
|
||||
internal readonly Dictionary<ulong, SocketGuildUser> GuildMembers
|
||||
= new Dictionary<ulong, SocketGuildUser>();
|
||||
internal readonly Dictionary<ulong, SocketGlobalUser> Users
|
||||
= new Dictionary<ulong, SocketGlobalUser>();
|
||||
internal readonly Dictionary<ulong, SocketChannel> Channels
|
||||
= new Dictionary<ulong, SocketChannel>();
|
||||
internal readonly Dictionary<ulong, SocketRole> Roles
|
||||
= new Dictionary<ulong, SocketRole>();
|
||||
|
||||
internal readonly Dictionary<ulong, SocketMessage> Messages
|
||||
= new Dictionary<ulong, SocketMessage>();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an Interaction received over the gateway.
|
||||
/// </summary>
|
||||
public abstract class SocketInteraction : SocketEntity<ulong>, IDiscordInteraction
|
||||
{
|
||||
#region SocketInteraction
|
||||
/// <summary>
|
||||
/// The <see cref="ISocketMessageChannel"/> this interaction was used in.
|
||||
/// </summary>
|
||||
public ISocketMessageChannel Channel { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="SocketUser"/> who triggered this interaction.
|
||||
/// </summary>
|
||||
public SocketUser User { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The type of this interaction.
|
||||
/// </summary>
|
||||
public InteractionType Type { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The token used to respond to this interaction.
|
||||
/// </summary>
|
||||
public string Token { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The data sent with this interaction.
|
||||
/// </summary>
|
||||
public IDiscordInteractionData Data { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The version of this interaction.
|
||||
/// </summary>
|
||||
public int Version { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DateTimeOffset CreatedAt { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether or not this interaction has been responded to.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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.
|
||||
/// </remarks>
|
||||
public abstract bool HasResponded { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// <see langword="true"/> if the token is valid for replying to, otherwise <see langword="false"/>.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Responds to an Interaction with type <see cref="InteractionResponseType.ChannelMessageWithSource"/>.
|
||||
/// </summary>
|
||||
/// <param name="text">The text of the message to be sent.</param>
|
||||
/// <param name="embeds">A array of embeds to send with this response. Max 10.</param>
|
||||
/// <param name="isTTS"><see langword="true"/> if the message should be read out by a text-to-speech reader, otherwise <see langword="false"/>.</param>
|
||||
/// <param name="ephemeral"><see langword="true"/> if the response should be hidden to everyone besides the invoker of the command, otherwise <see langword="false"/>.</param>
|
||||
/// <param name="allowedMentions">The allowed mentions for this response.</param>
|
||||
/// <param name="options">The request options for this response.</param>
|
||||
/// <param name="component">A <see cref="MessageComponent"/> to be sent with this response.</param>
|
||||
/// <param name="embed">A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception>
|
||||
/// <exception cref="InvalidOperationException">The parameters provided were invalid or the token was invalid.</exception>
|
||||
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);
|
||||
|
||||
/// <summary>
|
||||
/// Sends a followup message for this interaction.
|
||||
/// </summary>
|
||||
/// <param name="text">The text of the message to be sent.</param>
|
||||
/// <param name="embeds">A array of embeds to send with this response. Max 10.</param>
|
||||
/// <param name="isTTS"><see langword="true"/> if the message should be read out by a text-to-speech reader, otherwise <see langword="false"/>.</param>
|
||||
/// <param name="ephemeral"><see langword="true"/> if the response should be hidden to everyone besides the invoker of the command, otherwise <see langword="false"/>.</param>
|
||||
/// <param name="allowedMentions">The allowed mentions for this response.</param>
|
||||
/// <param name="options">The request options for this response.</param>
|
||||
/// <param name="component">A <see cref="MessageComponent"/> to be sent with this response.</param>
|
||||
/// <param name="embed">A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored.</param>
|
||||
/// <returns>
|
||||
/// The sent message.
|
||||
/// </returns>
|
||||
public abstract Task<RestFollowupMessage> 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);
|
||||
|
||||
/// <summary>
|
||||
/// Sends a followup message for this interaction.
|
||||
/// </summary>
|
||||
/// <param name="text">The text of the message to be sent.</param>
|
||||
/// <param name="fileStream">The file to upload.</param>
|
||||
/// <param name="fileName">The file name of the attachment.</param>
|
||||
/// <param name="embeds">A array of embeds to send with this response. Max 10.</param>
|
||||
/// <param name="isTTS"><see langword="true"/> if the message should be read out by a text-to-speech reader, otherwise <see langword="false"/>.</param>
|
||||
/// <param name="ephemeral"><see langword="true"/> if the response should be hidden to everyone besides the invoker of the command, otherwise <see langword="false"/>.</param>
|
||||
/// <param name="allowedMentions">The allowed mentions for this response.</param>
|
||||
/// <param name="options">The request options for this response.</param>
|
||||
/// <param name="component">A <see cref="MessageComponent"/> to be sent with this response.</param>
|
||||
/// <param name="embed">A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored.</param>
|
||||
/// <returns>
|
||||
/// The sent message.
|
||||
/// </returns>
|
||||
public abstract Task<RestFollowupMessage> 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);
|
||||
|
||||
/// <summary>
|
||||
/// Sends a followup message for this interaction.
|
||||
/// </summary>
|
||||
/// <param name="text">The text of the message to be sent.</param>
|
||||
/// <param name="filePath">The file to upload.</param>
|
||||
/// <param name="fileName">The file name of the attachment.</param>
|
||||
/// <param name="embeds">A array of embeds to send with this response. Max 10.</param>
|
||||
/// <param name="isTTS"><see langword="true"/> if the message should be read out by a text-to-speech reader, otherwise <see langword="false"/>.</param>
|
||||
/// <param name="ephemeral"><see langword="true"/> if the response should be hidden to everyone besides the invoker of the command, otherwise <see langword="false"/>.</param>
|
||||
/// <param name="allowedMentions">The allowed mentions for this response.</param>
|
||||
/// <param name="options">The request options for this response.</param>
|
||||
/// <param name="component">A <see cref="MessageComponent"/> to be sent with this response.</param>
|
||||
/// <param name="embed">A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored.</param>
|
||||
/// <returns>
|
||||
/// The sent message.
|
||||
/// </returns>
|
||||
public abstract Task<RestFollowupMessage> 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);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the original response for this interaction.
|
||||
/// </summary>
|
||||
/// <param name="options">The request options for this <see langword="async"/> request.</param>
|
||||
/// <returns>A <see cref="RestInteractionMessage"/> that represents the initial response.</returns>
|
||||
public Task<RestInteractionMessage> GetOriginalResponseAsync(RequestOptions options = null)
|
||||
=> InteractionHelper.GetOriginalResponseAsync(Discord, Channel, this, options);
|
||||
|
||||
/// <summary>
|
||||
/// Edits original response for this interaction.
|
||||
/// </summary>
|
||||
/// <param name="func">A delegate containing the properties to modify the message with.</param>
|
||||
/// <param name="options">The request options for this <see langword="async"/> request.</param>
|
||||
/// <returns>A <see cref="RestInteractionMessage"/> that represents the initial response.</returns>
|
||||
public async Task<RestInteractionMessage> ModifyOriginalResponseAsync(Action<MessageProperties> func, RequestOptions options = null)
|
||||
{
|
||||
var model = await InteractionHelper.ModifyInteractionResponseAsync(Discord, Token, func, options);
|
||||
return RestInteractionMessage.Create(Discord, model, Token, Channel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Acknowledges this interaction.
|
||||
/// </summary>
|
||||
/// <param name="ephemeral"><see langword="true"/> to send this message ephemerally, otherwise <see langword="false"/>.</param>
|
||||
/// <param name="options">The request options for this <see langword="async"/> request.</param>
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous operation of acknowledging the interaction.
|
||||
/// </returns>
|
||||
public abstract Task DeferAsync(bool ephemeral = false, RequestOptions options = null);
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDiscordInteraction
|
||||
/// <inheritdoc/>
|
||||
async Task<IUserMessage> 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);
|
||||
|
||||
/// <inheritdoc/>
|
||||
async Task<IUserMessage> IDiscordInteraction.GetOriginalResponseAsync(RequestOptions options)
|
||||
=> await GetOriginalResponseAsync(options).ConfigureAwait(false);
|
||||
|
||||
/// <inheritdoc/>
|
||||
async Task<IUserMessage> IDiscordInteraction.ModifyOriginalResponseAsync(Action<MessageProperties> func, RequestOptions options)
|
||||
=> await ModifyOriginalResponseAsync(func, options).ConfigureAwait(false);
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,9 @@ using Model = Discord.API.Gateway.InviteCreateEvent;
|
||||
|
||||
namespace Discord.WebSocket
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a WebSocket-based invite to a guild.
|
||||
/// </summary>
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
public class SocketInvite : SocketEntity<string>, 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."),
|
||||
};
|
||||
}
|
||||
}
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -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
|
||||
/// </summary>
|
||||
public abstract class SocketMessage : SocketEntity<ulong>, IMessage
|
||||
{
|
||||
#region SocketMessage
|
||||
private long _timestampTicks;
|
||||
private readonly List<SocketReaction> _reactions = new List<SocketReaction>();
|
||||
private ImmutableArray<SocketUser> _userMentions = ImmutableArray.Create<SocketUser>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the author of this message.
|
||||
@@ -36,6 +39,9 @@ namespace Discord.WebSocket
|
||||
/// <inheritdoc />
|
||||
public string Content { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string CleanContent => MessageHelper.SanitizeMessage(this);
|
||||
|
||||
/// <inheritdoc />
|
||||
public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id);
|
||||
/// <inheritdoc />
|
||||
@@ -58,6 +64,14 @@ namespace Discord.WebSocket
|
||||
/// <inheritdoc />
|
||||
public MessageReference Reference { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IReadOnlyCollection<ActionRowComponent> Components { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the interaction this message is a response to.
|
||||
/// </summary>
|
||||
public MessageInteraction<SocketUser> Interaction { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public MessageFlags? Flags { get; private set; }
|
||||
|
||||
@@ -92,20 +106,19 @@ namespace Discord.WebSocket
|
||||
/// Collection of WebSocket-based roles.
|
||||
/// </returns>
|
||||
public virtual IReadOnlyCollection<SocketRole> MentionedRoles => ImmutableArray.Create<SocketRole>();
|
||||
/// <inheritdoc />
|
||||
public virtual IReadOnlyCollection<ITag> Tags => ImmutableArray.Create<ITag>();
|
||||
/// <inheritdoc />
|
||||
public virtual IReadOnlyCollection<SocketSticker> Stickers => ImmutableArray.Create<SocketSticker>();
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyDictionary<IEmote, ReactionMetadata> 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) });
|
||||
/// <summary>
|
||||
/// Returns the users mentioned in this message.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// Collection of WebSocket-based users.
|
||||
/// </returns>
|
||||
public virtual IReadOnlyCollection<SocketUser> MentionedUsers => ImmutableArray.Create<SocketUser>();
|
||||
/// <inheritdoc />
|
||||
public virtual IReadOnlyCollection<ITag> Tags => ImmutableArray.Create<ITag>();
|
||||
/// <inheritdoc />
|
||||
public virtual IReadOnlyCollection<Sticker> Stickers => ImmutableArray.Create<Sticker>();
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyDictionary<IEmote, ReactionMetadata> 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<SocketUser> MentionedUsers => _userMentions;
|
||||
/// <inheritdoc />
|
||||
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<IMessageComponent, IMessageComponent>(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<ActionRowComponent>();
|
||||
|
||||
if (model.UserMentions.IsSpecified)
|
||||
{
|
||||
var value = model.UserMentions.Value;
|
||||
if (value.Length > 0)
|
||||
{
|
||||
var newMentions = ImmutableArray.CreateBuilder<SocketUser>(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<SocketUser>(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
|
||||
/// </returns>
|
||||
public override string ToString() => Content;
|
||||
internal SocketMessage Clone() => MemberwiseClone() as SocketMessage;
|
||||
#endregion
|
||||
|
||||
//IMessage
|
||||
#region IMessage
|
||||
/// <inheritdoc />
|
||||
IUser IMessage.Author => Author;
|
||||
/// <inheritdoc />
|
||||
@@ -199,8 +298,16 @@ namespace Discord.WebSocket
|
||||
IReadOnlyCollection<ulong> IMessage.MentionedRoleIds => MentionedRoles.Select(x => x.Id).ToImmutableArray();
|
||||
/// <inheritdoc />
|
||||
IReadOnlyCollection<ulong> IMessage.MentionedUserIds => MentionedUsers.Select(x => x.Id).ToImmutableArray();
|
||||
|
||||
/// <inheritdoc/>
|
||||
IReadOnlyCollection<IMessageComponent> IMessage.Components => Components;
|
||||
|
||||
/// <inheritdoc/>
|
||||
IMessageInteraction IMessage.Interaction => Interaction;
|
||||
|
||||
/// <inheritdoc />
|
||||
IReadOnlyCollection<ISticker> IMessage.Stickers => Stickers;
|
||||
IReadOnlyCollection<IStickerItem> IMessage.Stickers => Stickers;
|
||||
|
||||
|
||||
internal void AddReaction(SocketReaction reaction)
|
||||
{
|
||||
@@ -238,5 +345,6 @@ namespace Discord.WebSocket
|
||||
/// <inheritdoc />
|
||||
public IAsyncEnumerable<IReadOnlyCollection<IUser>> GetReactionUsersAsync(IEmote emote, int limit, RequestOptions options = null)
|
||||
=> MessageHelper.GetReactionUsersAsync(this, emote, limit, Discord, options);
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,8 +22,7 @@ namespace Discord.WebSocket
|
||||
private ImmutableArray<Embed> _embeds = ImmutableArray.Create<Embed>();
|
||||
private ImmutableArray<ITag> _tags = ImmutableArray.Create<ITag>();
|
||||
private ImmutableArray<SocketRole> _roleMentions = ImmutableArray.Create<SocketRole>();
|
||||
private ImmutableArray<SocketUser> _userMentions = ImmutableArray.Create<SocketUser>();
|
||||
private ImmutableArray<Sticker> _stickers = ImmutableArray.Create<Sticker>();
|
||||
private ImmutableArray<SocketSticker> _stickers = ImmutableArray.Create<SocketSticker>();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool IsTTS => _isTTS;
|
||||
@@ -46,9 +45,7 @@ namespace Discord.WebSocket
|
||||
/// <inheritdoc />
|
||||
public override IReadOnlyCollection<SocketRole> MentionedRoles => _roleMentions;
|
||||
/// <inheritdoc />
|
||||
public override IReadOnlyCollection<SocketUser> MentionedUsers => _userMentions;
|
||||
/// <inheritdoc />
|
||||
public override IReadOnlyCollection<Sticker> Stickers => _stickers;
|
||||
public override IReadOnlyCollection<SocketSticker> Stickers => _stickers;
|
||||
/// <inheritdoc />
|
||||
public IUserMessage ReferencedMessage => _referencedMessage;
|
||||
|
||||
@@ -108,32 +105,10 @@ namespace Discord.WebSocket
|
||||
_embeds = ImmutableArray.Create<Embed>();
|
||||
}
|
||||
|
||||
if (model.UserMentions.IsSpecified)
|
||||
{
|
||||
var value = model.UserMentions.Value;
|
||||
if (value.Length > 0)
|
||||
{
|
||||
var newMentions = ImmutableArray.CreateBuilder<SocketUser>(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<Sticker>(value.Length);
|
||||
var stickers = ImmutableArray.CreateBuilder<SocketSticker>(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<Sticker>();
|
||||
_stickers = ImmutableArray.Create<SocketSticker>();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<ulong>, IRole
|
||||
{
|
||||
#region SocketRole
|
||||
/// <summary>
|
||||
/// Gets the guild that owns this role.
|
||||
/// </summary>
|
||||
@@ -32,6 +33,10 @@ namespace Discord.WebSocket
|
||||
public bool IsMentionable { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public string Name { get; private set; }
|
||||
/// <inheritdoc/>
|
||||
public Emoji Emoji { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public string Icon { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public GuildPermissions Permissions { get; private set; }
|
||||
/// <inheritdoc />
|
||||
@@ -50,7 +55,11 @@ namespace Discord.WebSocket
|
||||
public bool IsEveryone => Id == Guild.Id;
|
||||
/// <inheritdoc />
|
||||
public string Mention => IsEveryone ? "@everyone" : MentionUtils.MentionRole(Id);
|
||||
public IEnumerable<SocketGuildUser> Members
|
||||
|
||||
/// <summary>
|
||||
/// Returns an IEnumerable containing all <see cref="SocketGuildUser"/> that have this role.
|
||||
/// </summary>
|
||||
public IEnumerable<SocketGuildUser> 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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -84,6 +103,10 @@ namespace Discord.WebSocket
|
||||
public Task DeleteAsync(RequestOptions options = null)
|
||||
=> RoleHelper.DeleteAsync(this, Discord, options);
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetIconUrl()
|
||||
=> CDN.GetGuildRoleIconUrl(Id, Icon);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the role.
|
||||
/// </summary>
|
||||
@@ -96,9 +119,11 @@ namespace Discord.WebSocket
|
||||
|
||||
/// <inheritdoc />
|
||||
public int CompareTo(IRole role) => RoleUtils.Compare(this, role);
|
||||
#endregion
|
||||
|
||||
//IRole
|
||||
#region IRole
|
||||
/// <inheritdoc />
|
||||
IGuild IRole.Guild => Guild;
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
using Discord.Rest;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Model = Discord.API.Sticker;
|
||||
|
||||
namespace Discord.WebSocket
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a custom sticker within a guild received over the gateway.
|
||||
/// </summary>
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
public class SocketCustomSticker : SocketSticker, ICustomSticker
|
||||
{
|
||||
#region SocketCustomSticker
|
||||
/// <summary>
|
||||
/// Gets the user that uploaded the guild sticker.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <note>
|
||||
/// This may return <see langword="null"/> in the WebSocket implementation due to incomplete user collection in
|
||||
/// large guilds, or the bot doesn't have the MANAGE_EMOJIS_AND_STICKERS permission.
|
||||
/// </note>
|
||||
/// </remarks>
|
||||
public SocketGuildUser Author
|
||||
=> AuthorId.HasValue ? Guild.GetUser(AuthorId.Value) : null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the guild the sticker was created in.
|
||||
/// </summary>
|
||||
public SocketGuild Guild { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task ModifyAsync(Action<StickerProperties> 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);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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
|
||||
}
|
||||
}
|
||||
92
src/Discord.Net.WebSocket/Entities/Stickers/SocketSticker.cs
Normal file
92
src/Discord.Net.WebSocket/Entities/Stickers/SocketSticker.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a general sticker received over the gateway.
|
||||
/// </summary>
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
public class SocketSticker : SocketEntity<ulong>, ISticker
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public virtual ulong PackId { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Name { get; protected set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual string Description { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual IReadOnlyCollection<string> Tags { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual StickerType Type { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public StickerFormatType Format { get; protected set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual bool? IsAvailable { get; protected set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual int? SortOrder { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
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<string>();
|
||||
}
|
||||
|
||||
internal string DebuggerDisplay => $"{Name} ({Id})";
|
||||
|
||||
/// <inheritdoc/>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Model = Discord.API.StickerItem;
|
||||
|
||||
namespace Discord.WebSocket
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an unknown sticker received over the gateway.
|
||||
/// </summary>
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
public class SocketUnknownSticker : SocketSticker
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override IReadOnlyCollection<string> Tags
|
||||
=> null;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string Description
|
||||
=> null;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ulong PackId
|
||||
=> 0;
|
||||
/// <inheritdoc/>
|
||||
public override bool? IsAvailable
|
||||
=> null;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int? SortOrder
|
||||
=> null;
|
||||
|
||||
/// <inheritdoc/>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to try to find the sticker.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The sticker representing this unknown stickers Id, if none is found then <see langword="null"/>.
|
||||
/// </returns>
|
||||
public Task<SocketSticker> ResolveAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null)
|
||||
=> Discord.GetStickerAsync(Id, mode, options);
|
||||
|
||||
private new string DebuggerDisplay => $"{Name} ({Id})";
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@ namespace Discord.WebSocket
|
||||
discord.RemoveUser(Id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal void Update(ClientState state, PresenceModel model)
|
||||
{
|
||||
Presence = SocketPresence.Create(model);
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using Model = Discord.API.User;
|
||||
|
||||
namespace Discord.WebSocket
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a WebSocket-based group user.
|
||||
/// </summary>
|
||||
[DebuggerDisplay("{DebuggerDisplay,nq}")]
|
||||
public class SocketGroupUser : SocketUser, IGroupUser
|
||||
{
|
||||
#region SocketGroupUser
|
||||
/// <summary>
|
||||
/// Gets the group channel of the user.
|
||||
/// </summary>
|
||||
@@ -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
|
||||
/// <inheritdoc />
|
||||
bool IVoiceState.IsDeafened => false;
|
||||
/// <inheritdoc />
|
||||
@@ -63,5 +69,8 @@ namespace Discord.WebSocket
|
||||
string IVoiceState.VoiceSessionId => null;
|
||||
/// <inheritdoc />
|
||||
bool IVoiceState.IsStreaming => false;
|
||||
/// <inheritdoc />
|
||||
DateTimeOffset? IVoiceState.RequestToSpeakTimestamp => null;
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<ulong> _roleIds;
|
||||
@@ -29,7 +30,8 @@ namespace Discord.WebSocket
|
||||
public SocketGuild Guild { get; }
|
||||
/// <inheritdoc />
|
||||
public string Nickname { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string GuildAvatarId { get; private set; }
|
||||
/// <inheritdoc />
|
||||
public override bool IsBot { get { return GlobalUser.IsBot; } internal set { GlobalUser.IsBot = value; } }
|
||||
/// <inheritdoc />
|
||||
@@ -38,6 +40,7 @@ namespace Discord.WebSocket
|
||||
public override ushort DiscriminatorValue { get { return GlobalUser.DiscriminatorValue; } internal set { GlobalUser.DiscriminatorValue = value; } }
|
||||
/// <inheritdoc />
|
||||
public override string AvatarId { get { return GlobalUser.AvatarId; } internal set { GlobalUser.AvatarId = value; } }
|
||||
|
||||
/// <inheritdoc />
|
||||
public GuildPermissions GuildPermissions => new GuildPermissions(Permissions.ResolveGuild(Guild, this));
|
||||
internal override SocketPresence Presence { get; set; }
|
||||
@@ -57,7 +60,11 @@ namespace Discord.WebSocket
|
||||
/// <inheritdoc />
|
||||
public bool IsStreaming => VoiceState?.IsStreaming ?? false;
|
||||
/// <inheritdoc />
|
||||
public DateTimeOffset? RequestToSpeakTimestamp => VoiceState?.RequestToSpeakTimestamp ?? null;
|
||||
/// <inheritdoc />
|
||||
public bool? IsPending { get; private set; }
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public DateTimeOffset? JoinedAt => DateTimeUtils.FromTicks(_joinedAtTicks);
|
||||
/// <summary>
|
||||
@@ -87,7 +94,7 @@ namespace Discord.WebSocket
|
||||
/// Returns the position of the user within the role hierarchy.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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
|
||||
/// <see cref="int.MaxValue"/> if user is the server owner.
|
||||
/// </remarks>
|
||||
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
|
||||
/// <inheritdoc />
|
||||
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
|
||||
/// <inheritdoc />
|
||||
IGuild IGuildUser.Guild => Guild;
|
||||
/// <inheritdoc />
|
||||
@@ -223,5 +235,6 @@ namespace Discord.WebSocket
|
||||
//IVoiceState
|
||||
/// <inheritdoc />
|
||||
IVoiceChannel IVoiceState.VoiceChannel => VoiceChannel;
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
209
src/Discord.Net.WebSocket/Entities/Users/SocketThreadUser.cs
Normal file
209
src/Discord.Net.WebSocket/Entities/Users/SocketThreadUser.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a thread user received over the gateway.
|
||||
/// </summary>
|
||||
public class SocketThreadUser : SocketUser, IGuildUser
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the <see cref="SocketThreadChannel"/> this user is in.
|
||||
/// </summary>
|
||||
public SocketThreadChannel Thread { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the timestamp for when this user joined this thread.
|
||||
/// </summary>
|
||||
public DateTimeOffset ThreadJoinedAt { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the guild this user is in.
|
||||
/// </summary>
|
||||
public SocketGuild Guild { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DateTimeOffset? JoinedAt
|
||||
=> GuildUser.JoinedAt;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Nickname
|
||||
=> GuildUser.Nickname;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DateTimeOffset? PremiumSince
|
||||
=> GuildUser.PremiumSince;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool? IsPending
|
||||
=> GuildUser.IsPending;
|
||||
/// <inheritdoc />
|
||||
public int Hierarchy
|
||||
=> GuildUser.Hierarchy;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string AvatarId
|
||||
{
|
||||
get => GuildUser.AvatarId;
|
||||
internal set => GuildUser.AvatarId = value;
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public string GuildAvatarId
|
||||
=> GuildUser.GuildAvatarId;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ushort DiscriminatorValue
|
||||
{
|
||||
get => GuildUser.DiscriminatorValue;
|
||||
internal set => GuildUser.DiscriminatorValue = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool IsBot
|
||||
{
|
||||
get => GuildUser.IsBot;
|
||||
internal set => GuildUser.IsBot = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool IsWebhook
|
||||
=> GuildUser.IsWebhook;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string Username
|
||||
{
|
||||
get => GuildUser.Username;
|
||||
internal set => GuildUser.Username = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsDeafened
|
||||
=> GuildUser.IsDeafened;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsMuted
|
||||
=> GuildUser.IsMuted;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsSelfDeafened
|
||||
=> GuildUser.IsSelfDeafened;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsSelfMuted
|
||||
=> GuildUser.IsSelfMuted;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsSuppressed
|
||||
=> GuildUser.IsSuppressed;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IVoiceChannel VoiceChannel
|
||||
=> GuildUser.VoiceChannel;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string VoiceSessionId
|
||||
=> GuildUser.VoiceSessionId;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsStreaming
|
||||
=> GuildUser.IsStreaming;
|
||||
|
||||
/// <inheritdoc/>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelPermissions GetPermissions(IGuildChannel channel) => GuildUser.GetPermissions(channel);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task KickAsync(string reason = null, RequestOptions options = null) => GuildUser.KickAsync(reason, options);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task ModifyAsync(Action<GuildUserProperties> func, RequestOptions options = null) => GuildUser.ModifyAsync(func, options);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task AddRoleAsync(ulong roleId, RequestOptions options = null) => GuildUser.AddRoleAsync(roleId, options);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task AddRoleAsync(IRole role, RequestOptions options = null) => GuildUser.AddRoleAsync(role, options);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task AddRolesAsync(IEnumerable<ulong> roleIds, RequestOptions options = null) => GuildUser.AddRolesAsync(roleIds, options);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task AddRolesAsync(IEnumerable<IRole> roles, RequestOptions options = null) => GuildUser.AddRolesAsync(roles, options);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task RemoveRoleAsync(ulong roleId, RequestOptions options = null) => GuildUser.RemoveRoleAsync(roleId, options);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task RemoveRoleAsync(IRole role, RequestOptions options = null) => GuildUser.RemoveRoleAsync(role, options);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task RemoveRolesAsync(IEnumerable<ulong> roleIds, RequestOptions options = null) => GuildUser.RemoveRolesAsync(roleIds, options);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task RemoveRolesAsync(IEnumerable<IRole> roles, RequestOptions options = null) => GuildUser.RemoveRolesAsync(roles, options);
|
||||
|
||||
/// <inheritdoc/>
|
||||
GuildPermissions IGuildUser.GuildPermissions => GuildUser.GuildPermissions;
|
||||
|
||||
/// <inheritdoc/>
|
||||
IGuild IGuildUser.Guild => Guild;
|
||||
|
||||
/// <inheritdoc/>
|
||||
ulong IGuildUser.GuildId => Guild.Id;
|
||||
|
||||
/// <inheritdoc/>
|
||||
IReadOnlyCollection<ulong> 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; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the guild user of this thread user.
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
public static explicit operator SocketGuildUser(SocketThreadUser user) => user.GuildUser;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user