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:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -19,9 +19,10 @@ namespace Discord.WebSocket
|
||||
public override ushort DiscriminatorValue { get; internal set; }
|
||||
/// <inheritdoc />
|
||||
public override string AvatarId { get; internal set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool IsBot { get; internal set; }
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool IsWebhook => false;
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Discord.WebSocket
|
||||
/// <summary>
|
||||
/// Initializes a default <see cref="SocketVoiceState"/> with everything set to <c>null</c> or <c>false</c>.
|
||||
/// </summary>
|
||||
public static readonly SocketVoiceState Default = new SocketVoiceState(null, null, false, false, false, false, false, false);
|
||||
public static readonly SocketVoiceState Default = new SocketVoiceState(null, null, null, false, false, false, false, false, false);
|
||||
|
||||
[Flags]
|
||||
private enum Flags : byte
|
||||
@@ -35,6 +35,8 @@ namespace Discord.WebSocket
|
||||
public SocketVoiceChannel VoiceChannel { get; }
|
||||
/// <inheritdoc />
|
||||
public string VoiceSessionId { get; }
|
||||
/// <inheritdoc/>
|
||||
public DateTimeOffset? RequestToSpeakTimestamp { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsMuted => (_voiceStates & Flags.Muted) != 0;
|
||||
@@ -48,11 +50,13 @@ namespace Discord.WebSocket
|
||||
public bool IsSelfDeafened => (_voiceStates & Flags.SelfDeafened) != 0;
|
||||
/// <inheritdoc />
|
||||
public bool IsStreaming => (_voiceStates & Flags.SelfStream) != 0;
|
||||
|
||||
|
||||
internal SocketVoiceState(SocketVoiceChannel voiceChannel, string sessionId, bool isSelfMuted, bool isSelfDeafened, bool isMuted, bool isDeafened, bool isSuppressed, bool isStream)
|
||||
internal SocketVoiceState(SocketVoiceChannel voiceChannel, DateTimeOffset? requestToSpeak, string sessionId, bool isSelfMuted, bool isSelfDeafened, bool isMuted, bool isDeafened, bool isSuppressed, bool isStream)
|
||||
{
|
||||
VoiceChannel = voiceChannel;
|
||||
VoiceSessionId = sessionId;
|
||||
RequestToSpeakTimestamp = requestToSpeak;
|
||||
|
||||
Flags voiceStates = Flags.Normal;
|
||||
if (isSelfMuted)
|
||||
@@ -71,7 +75,7 @@ namespace Discord.WebSocket
|
||||
}
|
||||
internal static SocketVoiceState Create(SocketVoiceChannel voiceChannel, Model model)
|
||||
{
|
||||
return new SocketVoiceState(voiceChannel, model.SessionId, model.SelfMute, model.SelfDeaf, model.Mute, model.Deaf, model.Suppress, model.SelfStream);
|
||||
return new SocketVoiceState(voiceChannel, model.RequestToSpeakTimestamp.IsSpecified ? model.RequestToSpeakTimestamp.Value : null, model.SessionId, model.SelfMute, model.SelfDeaf, model.Mute, model.Deaf, model.Suppress, model.SelfStream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace Discord.WebSocket
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
public class SocketWebhookUser : SocketUser, IWebhookUser
|
||||
{
|
||||
#region SocketWebhookUser
|
||||
/// <summary> Gets the guild of this webhook. </summary>
|
||||
public SocketGuild Guild { get; }
|
||||
/// <inheritdoc />
|
||||
@@ -24,6 +25,8 @@ namespace Discord.WebSocket
|
||||
public override ushort DiscriminatorValue { get; internal set; }
|
||||
/// <inheritdoc />
|
||||
public override string AvatarId { get; internal set; }
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool IsBot { get; internal set; }
|
||||
|
||||
@@ -49,9 +52,9 @@ namespace Discord.WebSocket
|
||||
|
||||
private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")}, Webhook)";
|
||||
internal new SocketWebhookUser Clone() => MemberwiseClone() as SocketWebhookUser;
|
||||
#endregion
|
||||
|
||||
|
||||
//IGuildUser
|
||||
#region IGuildUser
|
||||
/// <inheritdoc />
|
||||
IGuild IGuildUser.Guild => Guild;
|
||||
/// <inheritdoc />
|
||||
@@ -63,10 +66,16 @@ namespace Discord.WebSocket
|
||||
/// <inheritdoc />
|
||||
string IGuildUser.Nickname => null;
|
||||
/// <inheritdoc />
|
||||
string IGuildUser.GuildAvatarId => null;
|
||||
/// <inheritdoc />
|
||||
string IGuildUser.GetGuildAvatarUrl(ImageFormat format, ushort size) => null;
|
||||
/// <inheritdoc />
|
||||
DateTimeOffset? IGuildUser.PremiumSince => null;
|
||||
/// <inheritdoc />
|
||||
bool? IGuildUser.IsPending => null;
|
||||
/// <inheritdoc />
|
||||
int IGuildUser.Hierarchy => 0;
|
||||
/// <inheritdoc />
|
||||
GuildPermissions IGuildUser.GuildPermissions => GuildPermissions.Webhook;
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -120,8 +129,9 @@ namespace Discord.WebSocket
|
||||
/// <exception cref="NotSupportedException">Roles are not supported on webhook users.</exception>
|
||||
Task IGuildUser.RemoveRolesAsync(IEnumerable<IRole> roles, RequestOptions options) =>
|
||||
throw new NotSupportedException("Roles are not supported on webhook users.");
|
||||
#endregion
|
||||
|
||||
//IVoiceState
|
||||
#region IVoiceState
|
||||
/// <inheritdoc />
|
||||
bool IVoiceState.IsDeafened => false;
|
||||
/// <inheritdoc />
|
||||
@@ -138,5 +148,8 @@ namespace Discord.WebSocket
|
||||
string IVoiceState.VoiceSessionId => null;
|
||||
/// <inheritdoc />
|
||||
bool IVoiceState.IsStreaming => false;
|
||||
/// <inheritdoc />
|
||||
DateTimeOffset? IVoiceState.RequestToSpeakTimestamp => null;
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user