| @@ -22,7 +22,7 @@ public class Program | |||||
| { | { | ||||
| Console.WriteLine("Bot is connected!"); | Console.WriteLine("Bot is connected!"); | ||||
| return Task.CompletedTask; | return Task.CompletedTask; | ||||
| } | |||||
| }; | |||||
| await Task.Delay(-1); | await Task.Delay(-1); | ||||
| } | } | ||||
| @@ -37,6 +37,7 @@ _client = new DiscordSocketClient(config); | |||||
| - AllUnprivileged: This is a group of most common intents, that do NOT require any [developer portal] intents to be enabled. | - AllUnprivileged: This is a group of most common intents, that do NOT require any [developer portal] intents to be enabled. | ||||
| This includes intents that receive messages such as: `GatewayIntents.GuildMessages, GatewayIntents.DirectMessages` | This includes intents that receive messages such as: `GatewayIntents.GuildMessages, GatewayIntents.DirectMessages` | ||||
| - GuildMembers: An intent disabled by default, as you need to enable it in the [developer portal]. | - GuildMembers: An intent disabled by default, as you need to enable it in the [developer portal]. | ||||
| - MessageContent: An intent also disabled by default as you also need to enable it in the [developer portal]. | |||||
| - GuildPresences: Also disabled by default, this intent together with `GuildMembers` are the only intents not included in `AllUnprivileged`. | - GuildPresences: Also disabled by default, this intent together with `GuildMembers` are the only intents not included in `AllUnprivileged`. | ||||
| - All: All intents, it is ill advised to use this without care, as it _can_ cause a memory leak from presence. | - All: All intents, it is ill advised to use this without care, as it _can_ cause a memory leak from presence. | ||||
| The library will give responsive warnings if you specify unnecessary intents. | The library will give responsive warnings if you specify unnecessary intents. | ||||
| @@ -34,9 +34,16 @@ namespace BasicBot | |||||
| public Program() | public Program() | ||||
| { | { | ||||
| // Config used by DiscordSocketClient | |||||
| // Define intents for the client | |||||
| var config = new DiscordSocketConfig | |||||
| { | |||||
| GatewayIntents = GatewayIntents.AllUnprivileged | GatewayIntents.MessageContent | |||||
| }; | |||||
| // It is recommended to Dispose of a client when you are finished | // It is recommended to Dispose of a client when you are finished | ||||
| // using it, at the end of your app's lifetime. | // using it, at the end of your app's lifetime. | ||||
| _client = new DiscordSocketClient(); | |||||
| _client = new DiscordSocketClient(config); | |||||
| // Subscribing to client events, so that we may receive them whenever they're invoked. | // Subscribing to client events, so that we may receive them whenever they're invoked. | ||||
| _client.Log += LogAsync; | _client.Log += LogAsync; | ||||
| @@ -1,4 +1,4 @@ | |||||
| <Project Sdk="Microsoft.NET.Sdk"> | |||||
| <Project Sdk="Microsoft.NET.Sdk"> | |||||
| <PropertyGroup> | <PropertyGroup> | ||||
| <OutputType>Exe</OutputType> | <OutputType>Exe</OutputType> | ||||
| @@ -6,7 +6,7 @@ | |||||
| </PropertyGroup> | </PropertyGroup> | ||||
| <ItemGroup> | <ItemGroup> | ||||
| <PackageReference Include="Discord.Net.WebSocket" Version="3.6.1"/> | |||||
| <PackageReference Include="Discord.Net.WebSocket" Version="3.8.1"/> | |||||
| </ItemGroup> | </ItemGroup> | ||||
| </Project> | </Project> | ||||
| @@ -13,7 +13,7 @@ | |||||
| <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="5.0.0" /> | <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="5.0.0" /> | ||||
| <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="5.0.0" /> | <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="5.0.0" /> | ||||
| <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.2" /> | <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.2" /> | ||||
| <PackageReference Include="Discord.Net.Interactions" Version="3.6.1" /> | |||||
| <PackageReference Include="Discord.Net.Interactions" Version="3.8.1" /> | |||||
| </ItemGroup> | </ItemGroup> | ||||
| </Project> | </Project> | ||||
| @@ -28,7 +28,8 @@ namespace ShardedClient | |||||
| // have 1 shard per 1500-2000 guilds your bot is in. | // have 1 shard per 1500-2000 guilds your bot is in. | ||||
| var config = new DiscordSocketConfig | var config = new DiscordSocketConfig | ||||
| { | { | ||||
| TotalShards = 2 | |||||
| TotalShards = 2, | |||||
| GatewayIntents = GatewayIntents.AllUnprivileged | GatewayIntents.MessageContent | |||||
| }; | }; | ||||
| // You should dispose a service provider created using ASP.NET | // You should dispose a service provider created using ASP.NET | ||||
| @@ -8,7 +8,7 @@ | |||||
| <ItemGroup> | <ItemGroup> | ||||
| <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.2" /> | <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.2" /> | ||||
| <PackageReference Include="Discord.Net" Version="3.6.1" /> | |||||
| <PackageReference Include="Discord.Net" Version="3.8.1" /> | |||||
| </ItemGroup> | </ItemGroup> | ||||
| </Project> | </Project> | ||||
| @@ -60,6 +60,10 @@ namespace TextCommandFramework | |||||
| private ServiceProvider ConfigureServices() | private ServiceProvider ConfigureServices() | ||||
| { | { | ||||
| return new ServiceCollection() | return new ServiceCollection() | ||||
| .AddSingleton(new DiscordSocketConfig | |||||
| { | |||||
| GatewayIntents = GatewayIntents.AllUnprivileged | GatewayIntents.MessageContent | |||||
| }) | |||||
| .AddSingleton<DiscordSocketClient>() | .AddSingleton<DiscordSocketClient>() | ||||
| .AddSingleton<CommandService>() | .AddSingleton<CommandService>() | ||||
| .AddSingleton<CommandHandlingService>() | .AddSingleton<CommandHandlingService>() | ||||
| @@ -8,8 +8,8 @@ | |||||
| <ItemGroup> | <ItemGroup> | ||||
| <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.2" /> | <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.2" /> | ||||
| <PackageReference Include="Discord.Net.Commands" Version="3.6.1" /> | |||||
| <PackageReference Include="Discord.Net.Websocket" Version="3.6.1" /> | |||||
| <PackageReference Include="Discord.Net.Commands" Version="3.8.1" /> | |||||
| <PackageReference Include="Discord.Net.Websocket" Version="3.8.1" /> | |||||
| </ItemGroup> | </ItemGroup> | ||||
| </Project> | </Project> | ||||
| @@ -7,7 +7,7 @@ | |||||
| </PropertyGroup> | </PropertyGroup> | ||||
| <ItemGroup> | <ItemGroup> | ||||
| <PackageReference Include="Discord.Net.Webhook" Version="3.6.1" /> | |||||
| <PackageReference Include="Discord.Net.Webhook" Version="3.8.1" /> | |||||
| </ItemGroup> | </ItemGroup> | ||||
| </Project> | </Project> | ||||
| @@ -94,10 +94,10 @@ namespace Discord | |||||
| MaxNumberOfDailyApplicationCommandCreatesHasBeenReached = 30034, | MaxNumberOfDailyApplicationCommandCreatesHasBeenReached = 30034, | ||||
| MaximumBansForNonGuildMembersReached = 30035, | MaximumBansForNonGuildMembersReached = 30035, | ||||
| MaximumBanFetchesReached = 30037, | MaximumBanFetchesReached = 30037, | ||||
| MaximumUncompleteGuildScheduledEvents = 30038, | |||||
| MaximumUncompletedGuildScheduledEvents = 30038, | |||||
| MaximumStickersReached = 30039, | MaximumStickersReached = 30039, | ||||
| MaximumPruneRequestReached = 30040, | MaximumPruneRequestReached = 30040, | ||||
| MaximumGuildWigitsReached = 30042, | |||||
| MaximumGuildWidgetsReached = 30042, | |||||
| #endregion | #endregion | ||||
| #region General Request Errors (40XXX) | #region General Request Errors (40XXX) | ||||
| @@ -116,24 +116,24 @@ namespace Discord | |||||
| TargetUserNotInVoice = 40032, | TargetUserNotInVoice = 40032, | ||||
| MessageAlreadyCrossposted = 40033, | MessageAlreadyCrossposted = 40033, | ||||
| ApplicationNameAlreadyExists = 40041, | ApplicationNameAlreadyExists = 40041, | ||||
| #endregion | |||||
| #region Action Preconditions/Checks (50XXX) | |||||
| ApplicationInteractionFailedToSend = 40043, | ApplicationInteractionFailedToSend = 40043, | ||||
| CannotSendAMessageInAForumChannel = 40058, | CannotSendAMessageInAForumChannel = 40058, | ||||
| ThereAreNoTagsAvailableThatCanBeSetByNonModerators = 40066, | ThereAreNoTagsAvailableThatCanBeSetByNonModerators = 40066, | ||||
| ATagIsRequiredToCreateAForumPostInThisChannel = 40067, | ATagIsRequiredToCreateAForumPostInThisChannel = 40067, | ||||
| InteractionHasAlreadyBeenAcknowledged = 40060, | InteractionHasAlreadyBeenAcknowledged = 40060, | ||||
| TagNamesMustBeUnique = 40061, | TagNamesMustBeUnique = 40061, | ||||
| #endregion | |||||
| #region Action Preconditions/Checks (50XXX) | |||||
| MissingPermissions = 50001, | MissingPermissions = 50001, | ||||
| InvalidAccountType = 50002, | InvalidAccountType = 50002, | ||||
| CannotExecuteForDM = 50003, | CannotExecuteForDM = 50003, | ||||
| GuildWigitDisabled = 50004, | |||||
| GuildWidgetDisabled = 50004, | |||||
| CannotEditOtherUsersMessage = 50005, | CannotEditOtherUsersMessage = 50005, | ||||
| CannotSendEmptyMessage = 50006, | CannotSendEmptyMessage = 50006, | ||||
| CannotSendMessageToUser = 50007, | CannotSendMessageToUser = 50007, | ||||
| CannotSendMessageToVoiceChannel = 50008, | CannotSendMessageToVoiceChannel = 50008, | ||||
| ChannelVerificationTooHight = 50009, | |||||
| ChannelVerificationTooHigh = 50009, | |||||
| OAuth2ApplicationDoesntHaveBot = 50010, | OAuth2ApplicationDoesntHaveBot = 50010, | ||||
| OAuth2ApplicationLimitReached = 50011, | OAuth2ApplicationLimitReached = 50011, | ||||
| InvalidOAuth2State = 50012, | InvalidOAuth2State = 50012, | ||||
| @@ -154,6 +154,7 @@ namespace Discord | |||||
| BulkDeleteMessageTooOld = 50034, | BulkDeleteMessageTooOld = 50034, | ||||
| InvalidFormBody = 50035, | InvalidFormBody = 50035, | ||||
| InviteAcceptedForGuildThatBotIsntIn = 50036, | InviteAcceptedForGuildThatBotIsntIn = 50036, | ||||
| InvalidActivityAction = 50039, | |||||
| InvalidAPIVersion = 50041, | InvalidAPIVersion = 50041, | ||||
| FileUploadTooBig = 50045, | FileUploadTooBig = 50045, | ||||
| InvalidFileUpload = 50046, | InvalidFileUpload = 50046, | ||||
| @@ -161,6 +162,7 @@ namespace Discord | |||||
| InvalidGuild = 50055, | InvalidGuild = 50055, | ||||
| InvalidMessageType = 50068, | InvalidMessageType = 50068, | ||||
| PaymentSourceRequiredForGift = 50070, | PaymentSourceRequiredForGift = 50070, | ||||
| CannotModifySystemWebhook = 50073, | |||||
| CannotDeleteRequiredCommunityChannel = 50074, | CannotDeleteRequiredCommunityChannel = 50074, | ||||
| CannotEditStickersWithinAMessage = 50080, | CannotEditStickersWithinAMessage = 50080, | ||||
| InvalidSticker = 50081, | InvalidSticker = 50081, | ||||
| @@ -172,9 +174,8 @@ namespace Discord | |||||
| ServerRequiresMonetization = 50097, | ServerRequiresMonetization = 50097, | ||||
| ServerRequiresBoosts = 50101, | ServerRequiresBoosts = 50101, | ||||
| RequestBodyContainsInvalidJSON = 50109, | RequestBodyContainsInvalidJSON = 50109, | ||||
| FailedToResizeAssetBelowTheMaximumSize = 50138, | |||||
| OwnershipCannotBeTransferredToABotUser = 50132, | OwnershipCannotBeTransferredToABotUser = 50132, | ||||
| AssetResizeBelowTheMaximumSize= 50138, | |||||
| FailedToResizeAssetBelowTheMaximumSize = 50138, | |||||
| UploadedFileNotFound = 50146, | UploadedFileNotFound = 50146, | ||||
| MissingPermissionToSendThisSticker = 50600, | MissingPermissionToSendThisSticker = 50600, | ||||
| #endregion | #endregion | ||||
| @@ -213,8 +214,8 @@ namespace Discord | |||||
| LottieCantContainRasters = 170002, | LottieCantContainRasters = 170002, | ||||
| StickerMaximumFramerateExceeded = 170003, | StickerMaximumFramerateExceeded = 170003, | ||||
| StickerMaximumFrameCountExceeded = 170004, | StickerMaximumFrameCountExceeded = 170004, | ||||
| LottieMaximumDimentionsExceeded = 170005, | |||||
| StickerFramerateBoundsExceeed = 170006, | |||||
| LottieMaximumDimensionsExceeded = 170005, | |||||
| StickerFramerateBoundsExceeded = 170006, | |||||
| StickerAnimationDurationTooLong = 170007, | StickerAnimationDurationTooLong = 170007, | ||||
| #endregion | #endregion | ||||
| @@ -222,7 +223,7 @@ namespace Discord | |||||
| CannotUpdateFinishedEvent = 180000, | CannotUpdateFinishedEvent = 180000, | ||||
| FailedStageCreation = 180002, | FailedStageCreation = 180002, | ||||
| #endregion | #endregion | ||||
| #region Forum & Automod | #region Forum & Automod | ||||
| MessageWasBlockedByAutomaticModeration = 200000, | MessageWasBlockedByAutomaticModeration = 200000, | ||||
| TitleWasBlockedByAutomaticModeration = 200001, | TitleWasBlockedByAutomaticModeration = 200001, | ||||
| @@ -0,0 +1,22 @@ | |||||
| namespace Discord; | |||||
| /// <summary> | |||||
| /// Represents public flags for a channel. | |||||
| /// </summary> | |||||
| public enum ChannelFlags | |||||
| { | |||||
| /// <summary> | |||||
| /// Default value for flags, when none are given to a channel. | |||||
| /// </summary> | |||||
| None = 0, | |||||
| /// <summary> | |||||
| /// Flag given to a thread channel pinned on top of parent forum channel. | |||||
| /// </summary> | |||||
| Pinned = 1 << 1, | |||||
| /// <summary> | |||||
| /// Flag given to a forum channel that requires people to select tags when posting. | |||||
| /// </summary> | |||||
| RequireTag = 1 << 4 | |||||
| } | |||||
| @@ -0,0 +1,60 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| namespace Discord; | |||||
| public class ForumChannelProperties : TextChannelProperties | |||||
| { | |||||
| /// <summary> | |||||
| /// Gets or sets the topic of the channel. | |||||
| /// </summary> | |||||
| /// <remarks> | |||||
| /// Not available in forum channels. | |||||
| /// </remarks> | |||||
| public new Optional<int> SlowModeInterval { get; } | |||||
| /// <summary> | |||||
| /// Gets or sets rate limit on creating posts in this forum channel. | |||||
| /// </summary> | |||||
| /// <remarks> | |||||
| /// Setting this value to anything above zero will require each user to wait X seconds before | |||||
| /// creating another thread; setting this value to <c>0</c> will disable rate limits for this channel. | |||||
| /// <note> | |||||
| /// Users with <see cref="Discord.ChannelPermission.ManageMessages"/> or | |||||
| /// <see cref="ChannelPermission.ManageChannels"/> will be exempt from rate limits. | |||||
| /// </note> | |||||
| /// </remarks> | |||||
| /// <exception cref="ArgumentOutOfRangeException">Thrown if the value does not fall within [0, 21600].</exception> | |||||
| public Optional<int> ThreadCreationInterval { get; set; } | |||||
| /// <summary> | |||||
| /// Gets or sets the default slow-mode for threads in this channel. | |||||
| /// </summary> | |||||
| /// <remarks> | |||||
| /// Setting this value to anything above zero will require each user to wait X seconds before | |||||
| /// sending another message; setting this value to <c>0</c> will disable slow-mode for child threads. | |||||
| /// <note> | |||||
| /// Users with <see cref="Discord.ChannelPermission.ManageMessages"/> or | |||||
| /// <see cref="ChannelPermission.ManageChannels"/> will be exempt from slow-mode. | |||||
| /// </note> | |||||
| /// </remarks> | |||||
| /// <exception cref="ArgumentOutOfRangeException">Thrown if the value does not fall within [0, 21600].</exception> | |||||
| public Optional<int> DefaultSlowModeInterval { get; set; } | |||||
| /// <summary> | |||||
| /// Gets or sets a collection of tags inside of this forum channel. | |||||
| /// </summary> | |||||
| public Optional<IEnumerable<ForumTagProperties>> Tags { get; set; } | |||||
| /// <summary> | |||||
| /// Gets or sets a new default reaction emoji in this forum channel. | |||||
| /// </summary> | |||||
| public Optional<IEmote> DefaultReactionEmoji { get; set; } | |||||
| /// <summary> | |||||
| /// Gets or sets the rule used to order posts in forum channels. | |||||
| /// </summary> | |||||
| public Optional<ForumSortOrder> DefaultSortOrder { get; set; } | |||||
| } | |||||
| @@ -0,0 +1,17 @@ | |||||
| namespace Discord; | |||||
| /// <summary> | |||||
| /// Defines the rule used to order posts in forum channels. | |||||
| /// </summary> | |||||
| public enum ForumSortOrder | |||||
| { | |||||
| /// <summary> | |||||
| /// Sort forum posts by activity. | |||||
| /// </summary> | |||||
| LatestActivity = 0, | |||||
| /// <summary> | |||||
| /// Sort forum posts by creation time (from most recent to oldest). | |||||
| /// </summary> | |||||
| CreationDate = 1 | |||||
| } | |||||
| @@ -36,5 +36,10 @@ namespace Discord | |||||
| /// Gets or sets the permission overwrites for this channel. | /// Gets or sets the permission overwrites for this channel. | ||||
| /// </summary> | /// </summary> | ||||
| public Optional<IEnumerable<Overwrite>> PermissionOverwrites { get; set; } | public Optional<IEnumerable<Overwrite>> PermissionOverwrites { get; set; } | ||||
| /// <summary> | |||||
| /// Gets or sets the flags of the channel. | |||||
| /// </summary> | |||||
| public Optional<ChannelFlags> Flags { get; set; } | |||||
| } | } | ||||
| } | } | ||||
| @@ -7,7 +7,7 @@ using System.Threading.Tasks; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| public interface IForumChannel : IGuildChannel, IMentionable | |||||
| public interface IForumChannel : IGuildChannel, IMentionable, INestedChannel | |||||
| { | { | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets a value that indicates whether the channel is NSFW. | /// Gets a value that indicates whether the channel is NSFW. | ||||
| @@ -35,6 +35,55 @@ namespace Discord | |||||
| /// </summary> | /// </summary> | ||||
| IReadOnlyCollection<ForumTag> Tags { get; } | IReadOnlyCollection<ForumTag> Tags { get; } | ||||
| /// <summary> | |||||
| /// Gets the current rate limit on creating posts in this forum channel. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// An <see cref="int"/> representing the time in seconds required before the user can send another | |||||
| /// message; <c>0</c> if disabled. | |||||
| /// </returns> | |||||
| int ThreadCreationInterval { get; } | |||||
| /// <summary> | |||||
| /// Gets the current default slow-mode delay for threads in this forum channel. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// An <see cref="int"/> representing the time in seconds required before the user can send another | |||||
| /// message; <c>0</c> if disabled. | |||||
| /// </returns> | |||||
| int DefaultSlowModeInterval { get; } | |||||
| /// <summary> | |||||
| /// Gets the emoji to show in the add reaction button on a thread in a forum channel | |||||
| /// </summary> | |||||
| /// <remarks> | |||||
| /// If the emoji is <see cref="Emote"/> only the <see cref="Emote.Id"/> will be populated. | |||||
| /// Use <see cref="IGuild.GetEmoteAsync"/> to get the emoji. | |||||
| /// </remarks> | |||||
| IEmote DefaultReactionEmoji { get; } | |||||
| /// <summary> | |||||
| /// Gets or sets the rule used to order posts in forum channels. | |||||
| /// </summary> | |||||
| /// <remarks> | |||||
| /// Defaults to null, which indicates a preferred sort order hasn't been set | |||||
| /// </remarks> | |||||
| ForumSortOrder? DefaultSortOrder { get; } | |||||
| /// <summary> | |||||
| /// Modifies this forum channel. | |||||
| /// </summary> | |||||
| /// <remarks> | |||||
| /// This method modifies the current forum channel with the specified properties. To see an example of this | |||||
| /// method and what properties are available, please refer to <see cref="ForumChannelProperties"/>. | |||||
| /// </remarks> | |||||
| /// <param name="func">The delegate containing the properties to modify the channel with.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| /// <returns> | |||||
| /// A task that represents the asynchronous modification operation. | |||||
| /// </returns> | |||||
| Task ModifyAsync(Action<ForumChannelProperties> func, RequestOptions options = null); | |||||
| /// <summary> | /// <summary> | ||||
| /// Creates a new post (thread) within the forum. | /// Creates a new post (thread) within the forum. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -52,12 +101,13 @@ namespace Discord | |||||
| /// <param name="stickers">A collection of stickers to send with the message.</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> | /// <param name="embeds">A array of <see cref="Embed"/>s to send with this response. Max 10.</param> | ||||
| /// <param name="flags">A message flag to be applied to the sent message, only <see cref="MessageFlags.SuppressEmbeds"/> is permitted.</param> | /// <param name="flags">A message flag to be applied to the sent message, only <see cref="MessageFlags.SuppressEmbeds"/> is permitted.</param> | ||||
| /// <param name="tags">An array of <see cref="ForumTag"/> to be applied to the post.</param> | |||||
| /// <returns> | /// <returns> | ||||
| /// A task that represents the asynchronous creation operation. | /// A task that represents the asynchronous creation operation. | ||||
| /// </returns> | /// </returns> | ||||
| Task<IThreadChannel> CreatePostAsync(string title, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay, int? slowmode = null, | Task<IThreadChannel> CreatePostAsync(string title, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay, int? slowmode = null, | ||||
| string text = null, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, | string text = null, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, | ||||
| MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None); | |||||
| MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None, ForumTag[] tags = null); | |||||
| /// <summary> | /// <summary> | ||||
| /// Creates a new post (thread) within the forum. | /// Creates a new post (thread) within the forum. | ||||
| @@ -78,13 +128,14 @@ namespace Discord | |||||
| /// <param name="stickers">A collection of stickers to send with the file.</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> | /// <param name="embeds">A array of <see cref="Embed"/>s to send with this response. Max 10.</param> | ||||
| /// <param name="flags">A message flag to be applied to the sent message, only <see cref="MessageFlags.SuppressEmbeds"/> is permitted.</param> | /// <param name="flags">A message flag to be applied to the sent message, only <see cref="MessageFlags.SuppressEmbeds"/> is permitted.</param> | ||||
| /// <param name="tags">An array of <see cref="ForumTag"/> to be applied to the post.</param> | |||||
| /// <returns> | /// <returns> | ||||
| /// A task that represents the asynchronous creation operation. | /// A task that represents the asynchronous creation operation. | ||||
| /// </returns> | /// </returns> | ||||
| Task<IThreadChannel> CreatePostWithFileAsync(string title, string filePath, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay, | Task<IThreadChannel> CreatePostWithFileAsync(string title, string filePath, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay, | ||||
| int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, | int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, | ||||
| AllowedMentions allowedMentions = null, MessageComponent components = null, | AllowedMentions allowedMentions = null, MessageComponent components = null, | ||||
| ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None); | |||||
| ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None, ForumTag[] tags = null); | |||||
| /// <summary> | /// <summary> | ||||
| /// Creates a new post (thread) within the forum. | /// Creates a new post (thread) within the forum. | ||||
| @@ -106,13 +157,14 @@ namespace Discord | |||||
| /// <param name="stickers">A collection of stickers to send with the file.</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> | /// <param name="embeds">A array of <see cref="Embed"/>s to send with this response. Max 10.</param> | ||||
| /// <param name="flags">A message flag to be applied to the sent message, only <see cref="MessageFlags.SuppressEmbeds"/> is permitted.</param> | /// <param name="flags">A message flag to be applied to the sent message, only <see cref="MessageFlags.SuppressEmbeds"/> is permitted.</param> | ||||
| /// <param name="tags">An array of <see cref="ForumTag"/> to be applied to the post.</param> | |||||
| /// <returns> | /// <returns> | ||||
| /// A task that represents the asynchronous creation operation. | /// A task that represents the asynchronous creation operation. | ||||
| /// </returns> | /// </returns> | ||||
| public Task<IThreadChannel> CreatePostWithFileAsync(string title, Stream stream, string filename, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay, | public Task<IThreadChannel> CreatePostWithFileAsync(string title, Stream stream, string filename, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay, | ||||
| int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, | int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, | ||||
| AllowedMentions allowedMentions = null, MessageComponent components = null, | AllowedMentions allowedMentions = null, MessageComponent components = null, | ||||
| ISticker[] stickers = null, Embed[] embeds = null,MessageFlags flags = MessageFlags.None); | |||||
| ISticker[] stickers = null, Embed[] embeds = null,MessageFlags flags = MessageFlags.None, ForumTag[] tags = null); | |||||
| /// <summary> | /// <summary> | ||||
| /// Creates a new post (thread) within the forum. | /// Creates a new post (thread) within the forum. | ||||
| @@ -132,12 +184,13 @@ namespace Discord | |||||
| /// <param name="stickers">A collection of stickers to send with the file.</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> | /// <param name="embeds">A array of <see cref="Embed"/>s to send with this response. Max 10.</param> | ||||
| /// <param name="flags">A message flag to be applied to the sent message, only <see cref="MessageFlags.SuppressEmbeds"/> is permitted.</param> | /// <param name="flags">A message flag to be applied to the sent message, only <see cref="MessageFlags.SuppressEmbeds"/> is permitted.</param> | ||||
| /// <param name="tags">An array of <see cref="ForumTag"/> to be applied to the post.</param> | |||||
| /// <returns> | /// <returns> | ||||
| /// A task that represents the asynchronous creation operation. | /// A task that represents the asynchronous creation operation. | ||||
| /// </returns> | /// </returns> | ||||
| public Task<IThreadChannel> CreatePostWithFileAsync(string title, FileAttachment attachment, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay, | public Task<IThreadChannel> CreatePostWithFileAsync(string title, FileAttachment attachment, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay, | ||||
| int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, | int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, | ||||
| MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None); | |||||
| MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None, ForumTag[] tags = null); | |||||
| /// <summary> | /// <summary> | ||||
| /// Creates a new post (thread) within the forum. | /// Creates a new post (thread) within the forum. | ||||
| @@ -155,14 +208,15 @@ namespace Discord | |||||
| /// </param> | /// </param> | ||||
| /// <param name="components">The message components to be included with this message. Used for interactions.</param> | /// <param name="components">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="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> | |||||
| /// <param name="embeds">An array of <see cref="Embed"/>s to send with this response. Max 10.</param> | |||||
| /// <param name="flags">A message flag to be applied to the sent message, only <see cref="MessageFlags.SuppressEmbeds"/> is permitted.</param> | /// <param name="flags">A message flag to be applied to the sent message, only <see cref="MessageFlags.SuppressEmbeds"/> is permitted.</param> | ||||
| /// <param name="tags">An array of <see cref="ForumTag"/> to be applied to the post.</param> | |||||
| /// <returns> | /// <returns> | ||||
| /// A task that represents the asynchronous creation operation. | /// A task that represents the asynchronous creation operation. | ||||
| /// </returns> | /// </returns> | ||||
| public Task<IThreadChannel> CreatePostWithFilesAsync(string title, IEnumerable<FileAttachment> attachments, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay, | public Task<IThreadChannel> CreatePostWithFilesAsync(string title, IEnumerable<FileAttachment> attachments, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay, | ||||
| int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, | int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, | ||||
| MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None); | |||||
| MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None, ForumTag[] tags = null); | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets a collection of active threads within this forum channel. | /// Gets a collection of active threads within this forum channel. | ||||
| @@ -21,6 +21,17 @@ namespace Discord | |||||
| /// </returns> | /// </returns> | ||||
| int Position { get; } | int Position { get; } | ||||
| /// <summary> | |||||
| /// Gets the flags related to this channel. | |||||
| /// </summary> | |||||
| /// <remarks> | |||||
| /// This value is determined by bitwise OR-ing <see cref="ChannelFlags"/> values together. | |||||
| /// </remarks> | |||||
| /// <returns> | |||||
| /// A channel's flags, if any is associated. | |||||
| /// </returns> | |||||
| ChannelFlags Flags { get; } | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets the guild associated with this channel. | /// Gets the guild associated with this channel. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -1,4 +1,5 @@ | |||||
| using System; | using System; | ||||
| using System.Collections.Generic; | |||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| namespace Discord | namespace Discord | ||||
| @@ -56,6 +57,14 @@ namespace Discord | |||||
| /// </remarks> | /// </remarks> | ||||
| bool? IsInvitable { get; } | bool? IsInvitable { get; } | ||||
| /// <summary> | |||||
| /// Gets ids of tags applied to a forum thread | |||||
| /// </summary> | |||||
| /// <remarks> | |||||
| /// This property is only available on forum threads. | |||||
| /// </remarks> | |||||
| IReadOnlyCollection<ulong> AppliedTags { get; } | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets when the thread was created. | /// Gets when the thread was created. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -102,5 +111,16 @@ namespace Discord | |||||
| /// A task that represents the asynchronous operation of removing a user from this thread. | /// A task that represents the asynchronous operation of removing a user from this thread. | ||||
| /// </returns> | /// </returns> | ||||
| Task RemoveUserAsync(IGuildUser user, RequestOptions options = null); | Task RemoveUserAsync(IGuildUser user, RequestOptions options = null); | ||||
| /// <summary> | |||||
| /// Modifies this thread channel. | |||||
| /// </summary> | |||||
| /// <param name="func">The delegate containing the properties to modify the channel with.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| /// <returns> | |||||
| /// A task that represents the asynchronous modification operation. | |||||
| /// </returns> | |||||
| /// <seealso cref="ThreadChannelProperties"/> | |||||
| Task ModifyAsync(Action<ThreadChannelProperties> func, RequestOptions options = null); | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,4 +1,5 @@ | |||||
| using System; | using System; | ||||
| using System.Collections.Generic; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| @@ -39,20 +40,10 @@ namespace Discord | |||||
| /// <exception cref="ArgumentOutOfRangeException">Thrown if the value does not fall within [0, 21600].</exception> | /// <exception cref="ArgumentOutOfRangeException">Thrown if the value does not fall within [0, 21600].</exception> | ||||
| public Optional<int> SlowModeInterval { get; set; } | public Optional<int> SlowModeInterval { get; set; } | ||||
| /// <summary> | |||||
| /// Gets or sets whether or not the thread is archived. | |||||
| /// </summary> | |||||
| public Optional<bool> Archived { get; set; } | |||||
| /// <summary> | |||||
| /// Gets or sets whether or not the thread is locked. | |||||
| /// </summary> | |||||
| public Optional<bool> Locked { get; set; } | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets or sets the auto archive duration. | /// Gets or sets the auto archive duration. | ||||
| /// </summary> | /// </summary> | ||||
| public Optional<ThreadArchiveDuration> AutoArchiveDuration { get; set; } | public Optional<ThreadArchiveDuration> AutoArchiveDuration { get; set; } | ||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,26 @@ | |||||
| using System.Collections.Generic; | |||||
| namespace Discord; | |||||
| /// <summary> | |||||
| /// Provides properties that are used to modify an <see cref="IThreadChannel"/> with the specified changes. | |||||
| /// </summary> | |||||
| /// <seealso cref="IThreadChannel.ModifyAsync(System.Action{ThreadChannelProperties}, RequestOptions)"/> | |||||
| public class ThreadChannelProperties : TextChannelProperties | |||||
| { | |||||
| /// <summary> | |||||
| /// Gets or sets the tags applied to a forum thread | |||||
| /// </summary> | |||||
| public Optional<IEnumerable<ulong>> AppliedTags { get; set; } | |||||
| /// <summary> | |||||
| /// Gets or sets whether or not the thread is locked. | |||||
| /// </summary> | |||||
| public Optional<bool> Locked { get; set; } | |||||
| /// <summary> | |||||
| /// Gets or sets whether or not the thread is archived. | |||||
| /// </summary> | |||||
| public Optional<bool> Archived { get; set; } | |||||
| } | |||||
| @@ -1,42 +0,0 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord | |||||
| { | |||||
| /// <summary> | |||||
| /// A struct representing a forum channel tag. | |||||
| /// </summary> | |||||
| public struct ForumTag | |||||
| { | |||||
| /// <summary> | |||||
| /// Gets the Id of the tag. | |||||
| /// </summary> | |||||
| public ulong Id { get; } | |||||
| /// <summary> | |||||
| /// Gets the name of the tag. | |||||
| /// </summary> | |||||
| public string Name { get; } | |||||
| /// <summary> | |||||
| /// Gets the emoji of the tag or <see langword="null"/> if none is set. | |||||
| /// </summary> | |||||
| public IEmote Emoji { get; } | |||||
| internal ForumTag(ulong id, string name, ulong? emojiId, string emojiName) | |||||
| { | |||||
| if (emojiId.HasValue && emojiId.Value != 0) | |||||
| Emoji = new Emote(emojiId.Value, emojiName, false); | |||||
| else if (emojiName != null) | |||||
| Emoji = new Emoji(name); | |||||
| else | |||||
| Emoji = null; | |||||
| Id = id; | |||||
| Name = name; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,67 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| #nullable enable | |||||
| namespace Discord | |||||
| { | |||||
| /// <summary> | |||||
| /// A struct representing a forum channel tag. | |||||
| /// </summary> | |||||
| public struct ForumTag : ISnowflakeEntity, IForumTag | |||||
| { | |||||
| /// <summary> | |||||
| /// Gets the Id of the tag. | |||||
| /// </summary> | |||||
| public ulong Id { get; } | |||||
| /// <inheritdoc/> | |||||
| public string Name { get; } | |||||
| /// <inheritdoc/> | |||||
| public IEmote? Emoji { get; } | |||||
| /// <inheritdoc/> | |||||
| public bool IsModerated { get; } | |||||
| /// <inheritdoc/> | |||||
| public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); | |||||
| internal ForumTag(ulong id, string name, ulong? emojiId = null, string? emojiName = null, bool moderated = false) | |||||
| { | |||||
| if (emojiId.HasValue && emojiId.Value != 0) | |||||
| Emoji = new Emote(emojiId.Value, null, false); | |||||
| else if (emojiName != null) | |||||
| Emoji = new Emoji(emojiName); | |||||
| else | |||||
| Emoji = null; | |||||
| Id = id; | |||||
| Name = name; | |||||
| IsModerated = moderated; | |||||
| } | |||||
| public override int GetHashCode() => (Id, Name, Emoji, IsModerated).GetHashCode(); | |||||
| public override bool Equals(object? obj) | |||||
| => obj is ForumTag tag && Equals(tag); | |||||
| /// <summary> | |||||
| /// Gets whether supplied tag is equals to the current one. | |||||
| /// </summary> | |||||
| public bool Equals(ForumTag tag) | |||||
| => Id == tag.Id && | |||||
| Name == tag.Name && | |||||
| (Emoji is Emoji emoji && tag.Emoji is Emoji otherEmoji && emoji.Equals(otherEmoji) || | |||||
| Emoji is Emote emote && tag.Emoji is Emote otherEmote && emote.Equals(otherEmote)) && | |||||
| IsModerated == tag.IsModerated; | |||||
| public static bool operator ==(ForumTag? left, ForumTag? right) | |||||
| => left?.Equals(right) ?? right is null; | |||||
| public static bool operator !=(ForumTag? left, ForumTag? right) => !(left == right); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,191 @@ | |||||
| #nullable enable | |||||
| using System; | |||||
| namespace Discord; | |||||
| public class ForumTagBuilder | |||||
| { | |||||
| private string? _name; | |||||
| private IEmote? _emoji; | |||||
| private bool _moderated; | |||||
| private ulong? _id; | |||||
| /// <summary> | |||||
| /// Returns the maximum length of name allowed by Discord. | |||||
| /// </summary> | |||||
| public const int MaxNameLength = 20; | |||||
| /// <summary> | |||||
| /// Gets or sets the snowflake Id of the tag. | |||||
| /// </summary> | |||||
| /// <remarks> | |||||
| /// If set this will update existing tag or will create a new one otherwise. | |||||
| /// </remarks> | |||||
| public ulong? Id | |||||
| { | |||||
| get { return _id; } | |||||
| set { _id = value; } | |||||
| } | |||||
| /// <summary> | |||||
| /// Gets or sets the name of the tag. | |||||
| /// </summary> | |||||
| /// <exception cref="ArgumentException">Name length must be less than or equal to <see cref="MaxNameLength"/>.</exception> | |||||
| public string? Name | |||||
| { | |||||
| get { return _name; } | |||||
| set | |||||
| { | |||||
| if (value?.Length > MaxNameLength) | |||||
| throw new ArgumentException(message: $"Name length must be less than or equal to {MaxNameLength}.", paramName: nameof(Name)); | |||||
| _name = value; | |||||
| } | |||||
| } | |||||
| /// <summary> | |||||
| /// Gets or sets the emoji of the tag. | |||||
| /// </summary> | |||||
| public IEmote? Emoji | |||||
| { | |||||
| get { return _emoji; } | |||||
| set { _emoji = value; } | |||||
| } | |||||
| /// <summary> | |||||
| /// Gets or sets whether this tag can only be added to or removed from threads by a member | |||||
| /// with the <see cref="GuildPermissions.ManageThreads"/> permission | |||||
| /// </summary> | |||||
| public bool IsModerated | |||||
| { | |||||
| get { return _moderated; } | |||||
| set { _moderated = value; } | |||||
| } | |||||
| /// <summary> | |||||
| /// Initializes a new <see cref="ForumTagBuilder"/> class. | |||||
| /// </summary> | |||||
| public ForumTagBuilder() | |||||
| { | |||||
| } | |||||
| /// <summary> | |||||
| /// Initializes a new <see cref="ForumTagBuilder"/> class with values | |||||
| /// </summary> | |||||
| /// <param name="id"> If set existing tag will be updated or a new one will be created otherwise.</param> | |||||
| /// <param name="name"> Name of the tag.</param> | |||||
| /// <param name="isModerated"> Sets whether this tag can only be added to or removed from threads by a member | |||||
| /// with the <see cref="GuildPermissions.ManageThreads"/> permission. </param> | |||||
| public ForumTagBuilder(string name, ulong? id = null, bool isModerated = false) | |||||
| { | |||||
| Name = name; | |||||
| IsModerated = isModerated; | |||||
| Id = id; | |||||
| } | |||||
| /// <summary> | |||||
| /// Initializes a new <see cref="ForumTagBuilder"/> class with values | |||||
| /// </summary> | |||||
| /// <param name="name"> Name of the tag.</param> | |||||
| /// <param name="id"> If set existing tag will be updated or a new one will be created otherwise.</param> | |||||
| /// <param name="emoji"> Display emoji of the tag.</param> | |||||
| /// <param name="isModerated"> Sets whether this tag can only be added to or removed from threads by a member | |||||
| /// with the <see cref="GuildPermissions.ManageThreads"/> permission. </param> | |||||
| public ForumTagBuilder(string name, ulong? id = null, bool isModerated = false, IEmote? emoji = null) | |||||
| { | |||||
| Name = name; | |||||
| Emoji = emoji; | |||||
| IsModerated = isModerated; | |||||
| Id = id; | |||||
| } | |||||
| /// <summary> | |||||
| /// Initializes a new <see cref="ForumTagBuilder"/> class with values | |||||
| /// </summary> | |||||
| /// /// <param name="name"> Name of the tag.</param> | |||||
| /// <param name="id"> If set existing tag will be updated or a new one will be created otherwise.</param> | |||||
| /// <param name="emoteId"> The id of custom Display emoji of the tag.</param> | |||||
| /// <param name="isModerated"> Sets whether this tag can only be added to or removed from threads by a member | |||||
| /// with the <see cref="GuildPermissions.ManageThreads"/> permission </param> | |||||
| public ForumTagBuilder(string name, ulong? id = null, bool isModerated = false, ulong? emoteId = null) | |||||
| { | |||||
| Name = name; | |||||
| if(emoteId is not null) | |||||
| Emoji = new Emote(emoteId.Value, null, false); | |||||
| IsModerated = isModerated; | |||||
| Id = id; | |||||
| } | |||||
| /// <summary> | |||||
| /// Builds the Tag. | |||||
| /// </summary> | |||||
| /// <returns>An instance of <see cref="ForumTagProperties"/></returns> | |||||
| /// <exception cref="ArgumentNullException">"Name must be set to build the tag"</exception> | |||||
| public ForumTagProperties Build() | |||||
| { | |||||
| if (_name is null) | |||||
| throw new ArgumentNullException(nameof(Name), "Name must be set to build the tag"); | |||||
| return new ForumTagProperties(_name!, _emoji, _moderated); | |||||
| } | |||||
| /// <summary> | |||||
| /// Sets the name of the tag. | |||||
| /// </summary> | |||||
| /// <exception cref="ArgumentException">Name length must be less than or equal to <see cref="MaxNameLength"/>.</exception> | |||||
| public ForumTagBuilder WithName(string name) | |||||
| { | |||||
| Name = name; | |||||
| return this; | |||||
| } | |||||
| /// <summary> | |||||
| /// Sets the id of the tag. | |||||
| /// </summary> | |||||
| /// <param name="id"> If set existing tag will be updated or a new one will be created otherwise.</param> | |||||
| /// <exception cref="ArgumentException">Name length must be less than or equal to <see cref="MaxNameLength"/>.</exception> | |||||
| public ForumTagBuilder WithId(ulong? id) | |||||
| { | |||||
| Id = id; | |||||
| return this; | |||||
| } | |||||
| /// <summary> | |||||
| /// Sets the emoji of the tag. | |||||
| /// </summary> | |||||
| public ForumTagBuilder WithEmoji(IEmote? emoji) | |||||
| { | |||||
| Emoji = emoji; | |||||
| return this; | |||||
| } | |||||
| /// <summary> | |||||
| /// Sets whether this tag can only be added to or removed from threads by a member | |||||
| /// with the <see cref="GuildPermissions.ManageThreads"/> permission | |||||
| /// </summary> | |||||
| public ForumTagBuilder WithModerated(bool moderated) | |||||
| { | |||||
| IsModerated = moderated; | |||||
| return this; | |||||
| } | |||||
| public override int GetHashCode() => base.GetHashCode(); | |||||
| public override bool Equals(object? obj) | |||||
| => obj is ForumTagBuilder builder && Equals(builder); | |||||
| /// <summary> | |||||
| /// Gets whether supplied tag builder is equals to the current one. | |||||
| /// </summary> | |||||
| public bool Equals(ForumTagBuilder? builder) | |||||
| => builder is not null && | |||||
| Id == builder.Id && | |||||
| Name == builder.Name && | |||||
| (Emoji is Emoji emoji && builder.Emoji is Emoji otherEmoji && emoji.Equals(otherEmoji) || | |||||
| Emoji is Emote emote && builder.Emoji is Emote otherEmote && emote.Equals(otherEmote)) && | |||||
| IsModerated == builder.IsModerated; | |||||
| public static bool operator ==(ForumTagBuilder? left, ForumTagBuilder? right) | |||||
| => left?.Equals(right) ?? right is null ; | |||||
| public static bool operator !=(ForumTagBuilder? left, ForumTagBuilder? right) => !(left == right); | |||||
| } | |||||
| @@ -0,0 +1,11 @@ | |||||
| namespace Discord; | |||||
| public static class ForumTagBuilderExtensions | |||||
| { | |||||
| public static ForumTagBuilder ToForumTagBuilder(this ForumTag tag) | |||||
| => new ForumTagBuilder(tag.Name, tag.Id, tag.IsModerated, tag.Emoji); | |||||
| public static ForumTagBuilder ToForumTagBuilder(this ForumTagProperties tag) | |||||
| => new ForumTagBuilder(tag.Name, tag.Id, tag.IsModerated, tag.Emoji); | |||||
| } | |||||
| @@ -0,0 +1,48 @@ | |||||
| namespace Discord; | |||||
| #nullable enable | |||||
| public class ForumTagProperties : IForumTag | |||||
| { | |||||
| /// <summary> | |||||
| /// Gets the Id of the tag. | |||||
| /// </summary> | |||||
| public ulong Id { get; } | |||||
| /// <inheritdoc/> | |||||
| public string Name { get; } | |||||
| /// <inheritdoc/> | |||||
| public IEmote? Emoji { get; } | |||||
| /// <inheritdoc/> | |||||
| public bool IsModerated { get; } | |||||
| internal ForumTagProperties(string name, IEmote? emoji = null, bool isMmoderated = false) | |||||
| { | |||||
| Name = name; | |||||
| Emoji = emoji; | |||||
| IsModerated = isMmoderated; | |||||
| } | |||||
| public override int GetHashCode() => (Id, Name, Emoji, IsModerated).GetHashCode(); | |||||
| public override bool Equals(object? obj) | |||||
| => obj is ForumTagProperties tag && Equals(tag); | |||||
| /// <summary> | |||||
| /// Gets whether supplied tag is equals to the current one. | |||||
| /// </summary> | |||||
| public bool Equals(ForumTagProperties? tag) | |||||
| => tag is not null && | |||||
| Id == tag.Id && | |||||
| Name == tag.Name && | |||||
| (Emoji is Emoji emoji && tag.Emoji is Emoji otherEmoji && emoji.Equals(otherEmoji) || | |||||
| Emoji is Emote emote && tag.Emoji is Emote otherEmote && emote.Equals(otherEmote)) && | |||||
| IsModerated == tag.IsModerated; | |||||
| public static bool operator ==(ForumTagProperties? left, ForumTagProperties? right) | |||||
| => left?.Equals(right) ?? right is null; | |||||
| public static bool operator !=(ForumTagProperties? left, ForumTagProperties? right) => !(left == right); | |||||
| } | |||||
| @@ -0,0 +1,29 @@ | |||||
| namespace Discord; | |||||
| #nullable enable | |||||
| /// <summary> | |||||
| /// Represents a Discord forum tag | |||||
| /// </summary> | |||||
| public interface IForumTag | |||||
| { | |||||
| /// <summary> | |||||
| /// Gets the name of the tag. | |||||
| /// </summary> | |||||
| string Name { get; } | |||||
| /// <summary> | |||||
| /// Gets the emoji of the tag or <see langword="null"/> if none is set. | |||||
| /// </summary> | |||||
| /// <remarks> | |||||
| /// If the emoji is <see cref="Emote"/> only the <see cref="Emote.Id"/> will be populated. | |||||
| /// Use <see cref="IGuild.GetEmoteAsync"/> to get the emoji. | |||||
| /// </remarks> | |||||
| IEmote? Emoji { get; } | |||||
| /// <summary> | |||||
| /// Gets whether this tag can only be added to or removed from threads by a member | |||||
| /// with the <see cref="GuildPermissions.ManageThreads"/> permission | |||||
| /// </summary> | |||||
| bool IsModerated { get; } | |||||
| } | |||||
| @@ -181,5 +181,9 @@ namespace Discord | |||||
| /// The guild has enabled the welcome screen. | /// The guild has enabled the welcome screen. | ||||
| /// </summary> | /// </summary> | ||||
| WelcomeScreenEnabled = 1L << 41, | WelcomeScreenEnabled = 1L << 41, | ||||
| /// <summary> | |||||
| /// The guild has been set as a support server on the App Directory. | |||||
| /// </summary> | |||||
| DeveloperSupportServer = 1L << 42, | |||||
| } | } | ||||
| } | } | ||||
| @@ -761,6 +761,18 @@ namespace Discord | |||||
| /// </returns> | /// </returns> | ||||
| Task<ICategoryChannel> CreateCategoryAsync(string name, Action<GuildChannelProperties> func = null, RequestOptions options = null); | Task<ICategoryChannel> CreateCategoryAsync(string name, Action<GuildChannelProperties> func = null, RequestOptions options = null); | ||||
| /// <summary> | |||||
| /// Creates a new channel forum in this guild. | |||||
| /// </summary> | |||||
| /// <param name="name">The new name for the forum.</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 | |||||
| /// forum channel. | |||||
| /// </returns> | |||||
| Task<IForumChannel> CreateForumChannelAsync(string name, Action<ForumChannelProperties> func = null, RequestOptions options = null); | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets a collection of all the voice regions this guild can access. | /// Gets a collection of all the voice regions this guild can access. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -21,7 +21,7 @@ namespace Discord | |||||
| String = 3, | String = 3, | ||||
| /// <summary> | /// <summary> | ||||
| /// An <see langword="int"/>. | |||||
| /// An <see langword="long"/>. | |||||
| /// </summary> | /// </summary> | ||||
| Integer = 4, | Integer = 4, | ||||
| @@ -5,17 +5,28 @@ namespace Discord | |||||
| /// <summary> | /// <summary> | ||||
| /// Represents a class used to make timestamps in messages. see <see href="https://discord.com/developers/docs/reference#message-formatting-timestamp-styles"/>. | /// Represents a class used to make timestamps in messages. see <see href="https://discord.com/developers/docs/reference#message-formatting-timestamp-styles"/>. | ||||
| /// </summary> | /// </summary> | ||||
| public class TimestampTag | |||||
| public readonly struct TimestampTag | |||||
| { | { | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets or sets the style of the timestamp tag. | |||||
| /// Gets the time for this timestamp tag. | |||||
| /// </summary> | /// </summary> | ||||
| public TimestampTagStyles Style { get; set; } = TimestampTagStyles.ShortDateTime; | |||||
| public DateTimeOffset Time { get; } | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets or sets the time for this timestamp tag. | |||||
| /// Gets the style of this tag. <see langword="null"/> if none was provided. | |||||
| /// </summary> | /// </summary> | ||||
| public DateTimeOffset Time { get; set; } | |||||
| public TimestampTagStyles? Style { get; } | |||||
| /// <summary> | |||||
| /// Creates a new <see cref="TimestampTag"/> from the provided time. | |||||
| /// </summary> | |||||
| /// <param name="time">The time for this timestamp tag.</param> | |||||
| /// <param name="style">The style for this timestamp tag.</param> | |||||
| public TimestampTag(DateTimeOffset time, TimestampTagStyles? style = null) | |||||
| { | |||||
| Time = time; | |||||
| Style = style; | |||||
| } | |||||
| /// <summary> | /// <summary> | ||||
| /// Converts the current timestamp tag to the string representation supported by discord. | /// Converts the current timestamp tag to the string representation supported by discord. | ||||
| @@ -23,11 +34,23 @@ namespace Discord | |||||
| /// If the <see cref="Time"/> is null then the default 0 will be used. | /// If the <see cref="Time"/> is null then the default 0 will be used. | ||||
| /// </para> | /// </para> | ||||
| /// </summary> | /// </summary> | ||||
| /// <remarks> | |||||
| /// Will use the provided <see cref="Style"/> if provided. If this value is null, it will default to <see cref="TimestampTagStyles.ShortDateTime"/>. | |||||
| /// </remarks> | |||||
| /// <returns>A string that is compatible in a discord message, ex: <code><t:1625944201:f></code></returns> | /// <returns>A string that is compatible in a discord message, ex: <code><t:1625944201:f></code></returns> | ||||
| public override string ToString() | public override string ToString() | ||||
| { | |||||
| return $"<t:{Time.ToUnixTimeSeconds()}:{(char)Style}>"; | |||||
| } | |||||
| => ToString(Style ?? TimestampTagStyles.ShortDateTime); | |||||
| /// <summary> | |||||
| /// Converts the current timestamp tag to the string representation supported by discord. | |||||
| /// <para> | |||||
| /// If the <see cref="Time"/> is null then the default 0 will be used. | |||||
| /// </para> | |||||
| /// </summary> | |||||
| /// <param name="style">The formatting style for this tag.</param> | |||||
| /// <returns>A string that is compatible in a discord message, ex: <code><t:1625944201:f></code></returns> | |||||
| public string ToString(TimestampTagStyles style) | |||||
| => $"<t:{Time.ToUnixTimeSeconds()}:{(char)style}>"; | |||||
| /// <summary> | /// <summary> | ||||
| /// Creates a new timestamp tag with the specified <see cref="DateTime"/> object. | /// Creates a new timestamp tag with the specified <see cref="DateTime"/> object. | ||||
| @@ -35,14 +58,8 @@ namespace Discord | |||||
| /// <param name="time">The time of this timestamp tag.</param> | /// <param name="time">The time of this timestamp tag.</param> | ||||
| /// <param name="style">The style for this timestamp tag.</param> | /// <param name="style">The style for this timestamp tag.</param> | ||||
| /// <returns>The newly create timestamp tag.</returns> | /// <returns>The newly create timestamp tag.</returns> | ||||
| public static TimestampTag FromDateTime(DateTime time, TimestampTagStyles style = TimestampTagStyles.ShortDateTime) | |||||
| { | |||||
| return new TimestampTag | |||||
| { | |||||
| Style = style, | |||||
| Time = time | |||||
| }; | |||||
| } | |||||
| public static TimestampTag FromDateTime(DateTime time, TimestampTagStyles? style = null) | |||||
| => new(time, style); | |||||
| /// <summary> | /// <summary> | ||||
| /// Creates a new timestamp tag with the specified <see cref="DateTimeOffset"/> object. | /// Creates a new timestamp tag with the specified <see cref="DateTimeOffset"/> object. | ||||
| @@ -50,13 +67,25 @@ namespace Discord | |||||
| /// <param name="time">The time of this timestamp tag.</param> | /// <param name="time">The time of this timestamp tag.</param> | ||||
| /// <param name="style">The style for this timestamp tag.</param> | /// <param name="style">The style for this timestamp tag.</param> | ||||
| /// <returns>The newly create timestamp tag.</returns> | /// <returns>The newly create timestamp tag.</returns> | ||||
| public static TimestampTag FromDateTimeOffset(DateTimeOffset time, TimestampTagStyles style = TimestampTagStyles.ShortDateTime) | |||||
| { | |||||
| return new TimestampTag | |||||
| { | |||||
| Style = style, | |||||
| Time = time | |||||
| }; | |||||
| } | |||||
| public static TimestampTag FromDateTimeOffset(DateTimeOffset time, TimestampTagStyles? style = null) | |||||
| => new(time, style); | |||||
| /// <summary> | |||||
| /// Immediately formats the provided time and style into a timestamp string. | |||||
| /// </summary> | |||||
| /// <param name="time">The time of this timestamp tag.</param> | |||||
| /// <param name="style">The style for this timestamp tag.</param> | |||||
| /// <returns>The newly create timestamp string.</returns> | |||||
| public static string FormatFromDateTime(DateTime time, TimestampTagStyles style) | |||||
| => FormatFromDateTimeOffset(time, style); | |||||
| /// <summary> | |||||
| /// Immediately formats the provided time and style into a timestamp string. | |||||
| /// </summary> | |||||
| /// <param name="time">The time of this timestamp tag.</param> | |||||
| /// <param name="style">The style for this timestamp tag.</param> | |||||
| /// <returns>The newly create timestamp string.</returns> | |||||
| public static string FormatFromDateTimeOffset(DateTimeOffset time, TimestampTagStyles style) | |||||
| => $"<t:{time.ToUnixTimeSeconds()}:{(char)style}>"; | |||||
| } | } | ||||
| } | |||||
| } | |||||
| @@ -16,6 +16,11 @@ namespace Discord | |||||
| /// <summary> | /// <summary> | ||||
| /// Nitro subscription. Includes app perks as well as the games subscription service. | /// Nitro subscription. Includes app perks as well as the games subscription service. | ||||
| /// </summary> | /// </summary> | ||||
| Nitro = 2 | |||||
| Nitro = 2, | |||||
| /// <summary> | |||||
| /// Nitro Basic subscription. Includes app perks like video backgrounds, sending bigger files. | |||||
| /// </summary> | |||||
| NitroBasic = 3 | |||||
| } | } | ||||
| } | } | ||||
| @@ -46,6 +46,9 @@ namespace Discord | |||||
| case ITextChannel: | case ITextChannel: | ||||
| return ChannelType.Text; | return ChannelType.Text; | ||||
| case IForumChannel: | |||||
| return ChannelType.Forum; | |||||
| } | } | ||||
| return null; | return null; | ||||
| @@ -23,7 +23,7 @@ namespace Discord.Interactions | |||||
| public string CommandName { get; } | public string CommandName { get; } | ||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||
| public override IReadOnlyCollection<CommandParameterInfo> Parameters { get; } | |||||
| public override IReadOnlyList<CommandParameterInfo> Parameters { get; } | |||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||
| public override bool SupportsWildCards => false; | public override bool SupportsWildCards => false; | ||||
| @@ -41,9 +41,12 @@ namespace Discord.Interactions | |||||
| if (context.Interaction is not IAutocompleteInteraction) | if (context.Interaction is not IAutocompleteInteraction) | ||||
| return ExecuteResult.FromError(InteractionCommandError.ParseFailed, $"Provided {nameof(IInteractionContext)} doesn't belong to a Autocomplete Interaction"); | return ExecuteResult.FromError(InteractionCommandError.ParseFailed, $"Provided {nameof(IInteractionContext)} doesn't belong to a Autocomplete Interaction"); | ||||
| return await RunAsync(context, Array.Empty<object>(), services).ConfigureAwait(false); | |||||
| return await base.ExecuteAsync(context, services).ConfigureAwait(false); | |||||
| } | } | ||||
| protected override Task<IResult> ParseArgumentsAsync(IInteractionContext context, IServiceProvider services) | |||||
| => Task.FromResult(ParseResult.FromSuccess(Array.Empty<object>()) as IResult); | |||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||
| protected override Task InvokeModuleEvent(IInteractionContext context, IResult result) => | protected override Task InvokeModuleEvent(IInteractionContext context, IResult result) => | ||||
| CommandService._autocompleteCommandExecutedEvent.InvokeAsync(this, context, result); | CommandService._autocompleteCommandExecutedEvent.InvokeAsync(this, context, result); | ||||
| @@ -64,7 +64,7 @@ namespace Discord.Interactions | |||||
| public IReadOnlyCollection<PreconditionAttribute> Preconditions { get; } | public IReadOnlyCollection<PreconditionAttribute> Preconditions { get; } | ||||
| /// <inheritdoc cref="ICommandInfo.Parameters"/> | /// <inheritdoc cref="ICommandInfo.Parameters"/> | ||||
| public abstract IReadOnlyCollection<TParameter> Parameters { get; } | |||||
| public abstract IReadOnlyList<TParameter> Parameters { get; } | |||||
| internal CommandInfo(Builders.ICommandBuilder builder, ModuleInfo module, InteractionService commandService) | internal CommandInfo(Builders.ICommandBuilder builder, ModuleInfo module, InteractionService commandService) | ||||
| { | { | ||||
| @@ -85,71 +85,16 @@ namespace Discord.Interactions | |||||
| } | } | ||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||
| public abstract Task<IResult> ExecuteAsync(IInteractionContext context, IServiceProvider services); | |||||
| protected abstract Task InvokeModuleEvent(IInteractionContext context, IResult result); | |||||
| protected abstract string GetLogString(IInteractionContext context); | |||||
| /// <inheritdoc/> | |||||
| public async Task<PreconditionResult> CheckPreconditionsAsync(IInteractionContext context, IServiceProvider services) | |||||
| { | |||||
| async Task<PreconditionResult> CheckGroups(ILookup<string, PreconditionAttribute> preconditions, string type) | |||||
| { | |||||
| foreach (IGrouping<string, PreconditionAttribute> preconditionGroup in preconditions) | |||||
| { | |||||
| if (preconditionGroup.Key == null) | |||||
| { | |||||
| foreach (PreconditionAttribute precondition in preconditionGroup) | |||||
| { | |||||
| var result = await precondition.CheckRequirementsAsync(context, this, services).ConfigureAwait(false); | |||||
| if (!result.IsSuccess) | |||||
| return result; | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| var results = new List<PreconditionResult>(); | |||||
| foreach (PreconditionAttribute precondition in preconditionGroup) | |||||
| results.Add(await precondition.CheckRequirementsAsync(context, this, services).ConfigureAwait(false)); | |||||
| if (!results.Any(p => p.IsSuccess)) | |||||
| return PreconditionGroupResult.FromError($"{type} precondition group {preconditionGroup.Key} failed.", results); | |||||
| } | |||||
| } | |||||
| return PreconditionGroupResult.FromSuccess(); | |||||
| } | |||||
| var moduleResult = await CheckGroups(Module.GroupedPreconditions, "Module").ConfigureAwait(false); | |||||
| if (!moduleResult.IsSuccess) | |||||
| return moduleResult; | |||||
| var commandResult = await CheckGroups(_groupedPreconditions, "Command").ConfigureAwait(false); | |||||
| return !commandResult.IsSuccess ? commandResult : PreconditionResult.FromSuccess(); | |||||
| } | |||||
| protected async Task<IResult> RunAsync(IInteractionContext context, object[] args, IServiceProvider services) | |||||
| public virtual async Task<IResult> ExecuteAsync(IInteractionContext context, IServiceProvider services) | |||||
| { | { | ||||
| switch (RunMode) | switch (RunMode) | ||||
| { | { | ||||
| case RunMode.Sync: | case RunMode.Sync: | ||||
| { | |||||
| if (CommandService._autoServiceScopes) | |||||
| { | |||||
| using var scope = services?.CreateScope(); | |||||
| return await ExecuteInternalAsync(context, args, scope?.ServiceProvider ?? EmptyServiceProvider.Instance).ConfigureAwait(false); | |||||
| } | |||||
| return await ExecuteInternalAsync(context, args, services).ConfigureAwait(false); | |||||
| } | |||||
| return await ExecuteInternalAsync(context, services).ConfigureAwait(false); | |||||
| case RunMode.Async: | case RunMode.Async: | ||||
| _ = Task.Run(async () => | _ = Task.Run(async () => | ||||
| { | { | ||||
| if (CommandService._autoServiceScopes) | |||||
| { | |||||
| using var scope = services?.CreateScope(); | |||||
| await ExecuteInternalAsync(context, args, scope?.ServiceProvider ?? EmptyServiceProvider.Instance).ConfigureAwait(false); | |||||
| } | |||||
| else | |||||
| await ExecuteInternalAsync(context, args, services).ConfigureAwait(false); | |||||
| await ExecuteInternalAsync(context, services).ConfigureAwait(false); | |||||
| }); | }); | ||||
| break; | break; | ||||
| default: | default: | ||||
| @@ -159,16 +104,33 @@ namespace Discord.Interactions | |||||
| return ExecuteResult.FromSuccess(); | return ExecuteResult.FromSuccess(); | ||||
| } | } | ||||
| private async Task<IResult> ExecuteInternalAsync(IInteractionContext context, object[] args, IServiceProvider services) | |||||
| protected abstract Task<IResult> ParseArgumentsAsync(IInteractionContext context, IServiceProvider services); | |||||
| private async Task<IResult> ExecuteInternalAsync(IInteractionContext context, IServiceProvider services) | |||||
| { | { | ||||
| await CommandService._cmdLogger.DebugAsync($"Executing {GetLogString(context)}").ConfigureAwait(false); | await CommandService._cmdLogger.DebugAsync($"Executing {GetLogString(context)}").ConfigureAwait(false); | ||||
| using var scope = services?.CreateScope(); | |||||
| if (CommandService._autoServiceScopes) | |||||
| services = scope?.ServiceProvider ?? EmptyServiceProvider.Instance; | |||||
| try | try | ||||
| { | { | ||||
| var preconditionResult = await CheckPreconditionsAsync(context, services).ConfigureAwait(false); | var preconditionResult = await CheckPreconditionsAsync(context, services).ConfigureAwait(false); | ||||
| if (!preconditionResult.IsSuccess) | if (!preconditionResult.IsSuccess) | ||||
| return await InvokeEventAndReturn(context, preconditionResult).ConfigureAwait(false); | return await InvokeEventAndReturn(context, preconditionResult).ConfigureAwait(false); | ||||
| var argsResult = await ParseArgumentsAsync(context, services).ConfigureAwait(false); | |||||
| if (!argsResult.IsSuccess) | |||||
| return await InvokeEventAndReturn(context, argsResult).ConfigureAwait(false); | |||||
| if(argsResult is not ParseResult parseResult) | |||||
| return ExecuteResult.FromError(InteractionCommandError.BadArgs, "Complex command parsing failed for an unknown reason."); | |||||
| var args = parseResult.Args; | |||||
| var index = 0; | var index = 0; | ||||
| foreach (var parameter in Parameters) | foreach (var parameter in Parameters) | ||||
| { | { | ||||
| @@ -221,7 +183,47 @@ namespace Discord.Interactions | |||||
| } | } | ||||
| } | } | ||||
| protected async ValueTask<IResult> InvokeEventAndReturn(IInteractionContext context, IResult result) | |||||
| protected abstract Task InvokeModuleEvent(IInteractionContext context, IResult result); | |||||
| protected abstract string GetLogString(IInteractionContext context); | |||||
| /// <inheritdoc/> | |||||
| public async Task<PreconditionResult> CheckPreconditionsAsync(IInteractionContext context, IServiceProvider services) | |||||
| { | |||||
| async Task<PreconditionResult> CheckGroups(ILookup<string, PreconditionAttribute> preconditions, string type) | |||||
| { | |||||
| foreach (IGrouping<string, PreconditionAttribute> preconditionGroup in preconditions) | |||||
| { | |||||
| if (preconditionGroup.Key == null) | |||||
| { | |||||
| foreach (PreconditionAttribute precondition in preconditionGroup) | |||||
| { | |||||
| var result = await precondition.CheckRequirementsAsync(context, this, services).ConfigureAwait(false); | |||||
| if (!result.IsSuccess) | |||||
| return result; | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| var results = new List<PreconditionResult>(); | |||||
| foreach (PreconditionAttribute precondition in preconditionGroup) | |||||
| results.Add(await precondition.CheckRequirementsAsync(context, this, services).ConfigureAwait(false)); | |||||
| if (!results.Any(p => p.IsSuccess)) | |||||
| return PreconditionGroupResult.FromError($"{type} precondition group {preconditionGroup.Key} failed.", results); | |||||
| } | |||||
| } | |||||
| return PreconditionGroupResult.FromSuccess(); | |||||
| } | |||||
| var moduleResult = await CheckGroups(Module.GroupedPreconditions, "Module").ConfigureAwait(false); | |||||
| if (!moduleResult.IsSuccess) | |||||
| return moduleResult; | |||||
| var commandResult = await CheckGroups(_groupedPreconditions, "Command").ConfigureAwait(false); | |||||
| return !commandResult.IsSuccess ? commandResult : PreconditionResult.FromSuccess(); | |||||
| } | |||||
| protected async Task<T> InvokeEventAndReturn<T>(IInteractionContext context, T result) where T : IResult | |||||
| { | { | ||||
| await InvokeModuleEvent(context, result).ConfigureAwait(false); | await InvokeModuleEvent(context, result).ConfigureAwait(false); | ||||
| return result; | return result; | ||||
| @@ -13,7 +13,7 @@ namespace Discord.Interactions | |||||
| public class ComponentCommandInfo : CommandInfo<ComponentCommandParameterInfo> | public class ComponentCommandInfo : CommandInfo<ComponentCommandParameterInfo> | ||||
| { | { | ||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||
| public override IReadOnlyCollection<ComponentCommandParameterInfo> Parameters { get; } | |||||
| public override IReadOnlyList<ComponentCommandParameterInfo> Parameters { get; } | |||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||
| public override bool SupportsWildCards => true; | public override bool SupportsWildCards => true; | ||||
| @@ -25,48 +25,32 @@ namespace Discord.Interactions | |||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||
| public override async Task<IResult> ExecuteAsync(IInteractionContext context, IServiceProvider services) | public override async Task<IResult> ExecuteAsync(IInteractionContext context, IServiceProvider services) | ||||
| => await ExecuteAsync(context, services, null).ConfigureAwait(false); | |||||
| /// <summary> | |||||
| /// Execute this command using dependency injection. | |||||
| /// </summary> | |||||
| /// <param name="context">Context that will be injected to the <see cref="InteractionModuleBase{T}"/>.</param> | |||||
| /// <param name="services">Services that will be used while initializing the <see cref="InteractionModuleBase{T}"/>.</param> | |||||
| /// <param name="additionalArgs">Provide additional string parameters to the method along with the auto generated parameters.</param> | |||||
| /// <returns> | |||||
| /// A task representing the asynchronous command execution process. | |||||
| /// </returns> | |||||
| public async Task<IResult> ExecuteAsync(IInteractionContext context, IServiceProvider services, params string[] additionalArgs) | |||||
| { | { | ||||
| if (context.Interaction is not IComponentInteraction componentInteraction) | |||||
| if (context.Interaction is not IComponentInteraction) | |||||
| return ExecuteResult.FromError(InteractionCommandError.ParseFailed, $"Provided {nameof(IInteractionContext)} doesn't belong to a Message Component Interaction"); | return ExecuteResult.FromError(InteractionCommandError.ParseFailed, $"Provided {nameof(IInteractionContext)} doesn't belong to a Message Component Interaction"); | ||||
| return await ExecuteAsync(context, Parameters, additionalArgs, componentInteraction.Data, services); | |||||
| return await base.ExecuteAsync(context, services).ConfigureAwait(false); | |||||
| } | } | ||||
| /// <inheritdoc/> | |||||
| public async Task<IResult> ExecuteAsync(IInteractionContext context, IEnumerable<CommandParameterInfo> paramList, IEnumerable<string> wildcardCaptures, IComponentInteractionData data, | |||||
| IServiceProvider services) | |||||
| protected override async Task<IResult> ParseArgumentsAsync(IInteractionContext context, IServiceProvider services) | |||||
| { | { | ||||
| var paramCount = paramList.Count(); | |||||
| var captureCount = wildcardCaptures?.Count() ?? 0; | |||||
| if (context.Interaction is not IComponentInteraction messageComponent) | |||||
| return ExecuteResult.FromError(InteractionCommandError.ParseFailed, $"Provided {nameof(IInteractionContext)} doesn't belong to a Component Command Interaction"); | |||||
| var captures = (context as IRouteMatchContainer)?.SegmentMatches?.ToList(); | |||||
| var captureCount = captures?.Count() ?? 0; | |||||
| try | try | ||||
| { | { | ||||
| var args = new object[paramCount]; | |||||
| var data = (context.Interaction as IComponentInteraction).Data; | |||||
| var args = new object[Parameters.Count]; | |||||
| for (var i = 0; i < paramCount; i++) | |||||
| for(var i = 0; i < Parameters.Count; i++) | |||||
| { | { | ||||
| var parameter = Parameters.ElementAt(i); | |||||
| var parameter = Parameters[i]; | |||||
| var isCapture = i < captureCount; | var isCapture = i < captureCount; | ||||
| if (isCapture ^ parameter.IsRouteSegmentParameter) | if (isCapture ^ parameter.IsRouteSegmentParameter) | ||||
| return await InvokeEventAndReturn(context, ExecuteResult.FromError(InteractionCommandError.BadArgs, "Argument type and parameter type didn't match (Wild Card capture/Component value)")).ConfigureAwait(false); | return await InvokeEventAndReturn(context, ExecuteResult.FromError(InteractionCommandError.BadArgs, "Argument type and parameter type didn't match (Wild Card capture/Component value)")).ConfigureAwait(false); | ||||
| var readResult = isCapture ? await parameter.TypeReader.ReadAsync(context, wildcardCaptures.ElementAt(i), services).ConfigureAwait(false) : | |||||
| var readResult = isCapture ? await parameter.TypeReader.ReadAsync(context, captures[i].Value, services).ConfigureAwait(false) : | |||||
| await parameter.TypeConverter.ReadAsync(context, data, services).ConfigureAwait(false); | await parameter.TypeConverter.ReadAsync(context, data, services).ConfigureAwait(false); | ||||
| if (!readResult.IsSuccess) | if (!readResult.IsSuccess) | ||||
| @@ -75,7 +59,7 @@ namespace Discord.Interactions | |||||
| args[i] = readResult.Value; | args[i] = readResult.Value; | ||||
| } | } | ||||
| return await RunAsync(context, args, services).ConfigureAwait(false); | |||||
| return ParseResult.FromSuccess(args); | |||||
| } | } | ||||
| catch (Exception ex) | catch (Exception ex) | ||||
| { | { | ||||
| @@ -24,7 +24,7 @@ namespace Discord.Interactions | |||||
| public GuildPermission? DefaultMemberPermissions { get; } | public GuildPermission? DefaultMemberPermissions { get; } | ||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||
| public override IReadOnlyCollection<CommandParameterInfo> Parameters { get; } | |||||
| public override IReadOnlyList<CommandParameterInfo> Parameters { get; } | |||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||
| public override bool SupportsWildCards => false; | public override bool SupportsWildCards => false; | ||||
| @@ -14,18 +14,23 @@ namespace Discord.Interactions | |||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||
| public override async Task<IResult> ExecuteAsync(IInteractionContext context, IServiceProvider services) | public override async Task<IResult> ExecuteAsync(IInteractionContext context, IServiceProvider services) | ||||
| { | { | ||||
| if (context.Interaction is not IMessageCommandInteraction messageCommand) | |||||
| if (context.Interaction is not IMessageCommandInteraction) | |||||
| return ExecuteResult.FromError(InteractionCommandError.ParseFailed, $"Provided {nameof(IInteractionContext)} doesn't belong to a Message Command Interation"); | return ExecuteResult.FromError(InteractionCommandError.ParseFailed, $"Provided {nameof(IInteractionContext)} doesn't belong to a Message Command Interation"); | ||||
| return await base.ExecuteAsync(context, services).ConfigureAwait(false); | |||||
| } | |||||
| protected override Task<IResult> ParseArgumentsAsync(IInteractionContext context, IServiceProvider services) | |||||
| { | |||||
| try | try | ||||
| { | { | ||||
| object[] args = new object[1] { messageCommand.Data.Message }; | |||||
| object[] args = new object[1] { (context.Interaction as IMessageCommandInteraction).Data.Message }; | |||||
| return await RunAsync(context, args, services).ConfigureAwait(false); | |||||
| return Task.FromResult(ParseResult.FromSuccess(args) as IResult); | |||||
| } | } | ||||
| catch (Exception ex) | catch (Exception ex) | ||||
| { | { | ||||
| return ExecuteResult.FromError(ex); | |||||
| return Task.FromResult(ParseResult.FromError(ex) as IResult); | |||||
| } | } | ||||
| } | } | ||||
| @@ -17,15 +17,20 @@ namespace Discord.Interactions | |||||
| if (context.Interaction is not IUserCommandInteraction userCommand) | if (context.Interaction is not IUserCommandInteraction userCommand) | ||||
| return ExecuteResult.FromError(InteractionCommandError.ParseFailed, $"Provided {nameof(IInteractionContext)} doesn't belong to a Message Command Interation"); | return ExecuteResult.FromError(InteractionCommandError.ParseFailed, $"Provided {nameof(IInteractionContext)} doesn't belong to a Message Command Interation"); | ||||
| return await base.ExecuteAsync(context, services).ConfigureAwait(false); | |||||
| } | |||||
| protected override Task<IResult> ParseArgumentsAsync(IInteractionContext context, IServiceProvider services) | |||||
| { | |||||
| try | try | ||||
| { | { | ||||
| object[] args = new object[1] { userCommand.Data.User }; | |||||
| object[] args = new object[1] { (context.Interaction as IUserCommandInteraction).Data.User }; | |||||
| return await RunAsync(context, args, services).ConfigureAwait(false); | |||||
| return Task.FromResult(ParseResult.FromSuccess(args) as IResult); | |||||
| } | } | ||||
| catch (Exception ex) | catch (Exception ex) | ||||
| { | { | ||||
| return ExecuteResult.FromError(ex); | |||||
| return Task.FromResult(ParseResult.FromError(ex) as IResult); | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,7 +1,6 @@ | |||||
| using System; | using System; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Collections.Immutable; | using System.Collections.Immutable; | ||||
| using System.Diagnostics.Tracing; | |||||
| using System.Linq; | using System.Linq; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| namespace Discord.Interactions | namespace Discord.Interactions | ||||
| @@ -20,7 +19,7 @@ namespace Discord.Interactions | |||||
| public override bool SupportsWildCards => true; | public override bool SupportsWildCards => true; | ||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||
| public override IReadOnlyCollection<ModalCommandParameterInfo> Parameters { get; } | |||||
| public override IReadOnlyList<ModalCommandParameterInfo> Parameters { get; } | |||||
| internal ModalCommandInfo(Builders.ModalCommandBuilder builder, ModuleInfo module, InteractionService commandService) : base(builder, module, commandService) | internal ModalCommandInfo(Builders.ModalCommandBuilder builder, ModuleInfo module, InteractionService commandService) : base(builder, module, commandService) | ||||
| { | { | ||||
| @@ -30,34 +29,29 @@ namespace Discord.Interactions | |||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||
| public override async Task<IResult> ExecuteAsync(IInteractionContext context, IServiceProvider services) | public override async Task<IResult> ExecuteAsync(IInteractionContext context, IServiceProvider services) | ||||
| => await ExecuteAsync(context, services, null).ConfigureAwait(false); | |||||
| /// <summary> | |||||
| /// Execute this command using dependency injection. | |||||
| /// </summary> | |||||
| /// <param name="context">Context that will be injected to the <see cref="InteractionModuleBase{T}"/>.</param> | |||||
| /// <param name="services">Services that will be used while initializing the <see cref="InteractionModuleBase{T}"/>.</param> | |||||
| /// <param name="additionalArgs">Provide additional string parameters to the method along with the auto generated parameters.</param> | |||||
| /// <returns> | |||||
| /// A task representing the asynchronous command execution process. | |||||
| /// </returns> | |||||
| public async Task<IResult> ExecuteAsync(IInteractionContext context, IServiceProvider services, params string[] additionalArgs) | |||||
| { | { | ||||
| if (context.Interaction is not IModalInteraction modalInteraction) | if (context.Interaction is not IModalInteraction modalInteraction) | ||||
| return ExecuteResult.FromError(InteractionCommandError.ParseFailed, $"Provided {nameof(IInteractionContext)} doesn't belong to a Modal Interaction."); | return ExecuteResult.FromError(InteractionCommandError.ParseFailed, $"Provided {nameof(IInteractionContext)} doesn't belong to a Modal Interaction."); | ||||
| return await base.ExecuteAsync(context, services).ConfigureAwait(false); | |||||
| } | |||||
| protected override async Task<IResult> ParseArgumentsAsync(IInteractionContext context, IServiceProvider services) | |||||
| { | |||||
| var captures = (context as IRouteMatchContainer)?.SegmentMatches?.ToList(); | |||||
| var captureCount = captures?.Count() ?? 0; | |||||
| try | try | ||||
| { | { | ||||
| var args = new object[Parameters.Count]; | var args = new object[Parameters.Count]; | ||||
| var captureCount = additionalArgs?.Length ?? 0; | |||||
| for(var i = 0; i < Parameters.Count; i++) | |||||
| for (var i = 0; i < Parameters.Count; i++) | |||||
| { | { | ||||
| var parameter = Parameters.ElementAt(i); | var parameter = Parameters.ElementAt(i); | ||||
| if(i < captureCount) | |||||
| if (i < captureCount) | |||||
| { | { | ||||
| var readResult = await parameter.TypeReader.ReadAsync(context, additionalArgs[i], services).ConfigureAwait(false); | |||||
| var readResult = await parameter.TypeReader.ReadAsync(context, captures[i].Value, services).ConfigureAwait(false); | |||||
| if (!readResult.IsSuccess) | if (!readResult.IsSuccess) | ||||
| return await InvokeEventAndReturn(context, readResult).ConfigureAwait(false); | return await InvokeEventAndReturn(context, readResult).ConfigureAwait(false); | ||||
| @@ -69,13 +63,14 @@ namespace Discord.Interactions | |||||
| if (!modalResult.IsSuccess) | if (!modalResult.IsSuccess) | ||||
| return await InvokeEventAndReturn(context, modalResult).ConfigureAwait(false); | return await InvokeEventAndReturn(context, modalResult).ConfigureAwait(false); | ||||
| if (modalResult is not ParseResult parseResult) | |||||
| if (modalResult is not TypeConverterResult converterResult) | |||||
| return await InvokeEventAndReturn(context, ExecuteResult.FromError(InteractionCommandError.BadArgs, "Command parameter parsing failed for an unknown reason.")); | return await InvokeEventAndReturn(context, ExecuteResult.FromError(InteractionCommandError.BadArgs, "Command parameter parsing failed for an unknown reason.")); | ||||
| args[i] = parseResult.Value; | |||||
| args[i] = converterResult.Value; | |||||
| } | } | ||||
| } | } | ||||
| return await RunAsync(context, args, services); | |||||
| return ParseResult.FromSuccess(args); | |||||
| } | } | ||||
| catch (Exception ex) | catch (Exception ex) | ||||
| { | { | ||||
| @@ -33,7 +33,7 @@ namespace Discord.Interactions | |||||
| public GuildPermission? DefaultMemberPermissions { get; } | public GuildPermission? DefaultMemberPermissions { get; } | ||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||
| public override IReadOnlyCollection<SlashCommandParameterInfo> Parameters { get; } | |||||
| public override IReadOnlyList<SlashCommandParameterInfo> Parameters { get; } | |||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||
| public override bool SupportsWildCards => false; | public override bool SupportsWildCards => false; | ||||
| @@ -41,9 +41,9 @@ namespace Discord.Interactions | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets the flattened collection of command parameters and complex parameter fields. | /// Gets the flattened collection of command parameters and complex parameter fields. | ||||
| /// </summary> | /// </summary> | ||||
| public IReadOnlyCollection<SlashCommandParameterInfo> FlattenedParameters { get; } | |||||
| public IReadOnlyList<SlashCommandParameterInfo> FlattenedParameters { get; } | |||||
| internal SlashCommandInfo (Builders.SlashCommandBuilder builder, ModuleInfo module, InteractionService commandService) : base(builder, module, commandService) | |||||
| internal SlashCommandInfo(Builders.SlashCommandBuilder builder, ModuleInfo module, InteractionService commandService) : base(builder, module, commandService) | |||||
| { | { | ||||
| Description = builder.Description; | Description = builder.Description; | ||||
| DefaultPermission = builder.DefaultPermission; | DefaultPermission = builder.DefaultPermission; | ||||
| @@ -60,49 +60,45 @@ namespace Discord.Interactions | |||||
| } | } | ||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||
| public override async Task<IResult> ExecuteAsync (IInteractionContext context, IServiceProvider services) | |||||
| public override async Task<IResult> ExecuteAsync(IInteractionContext context, IServiceProvider services) | |||||
| { | { | ||||
| if(context.Interaction is not ISlashCommandInteraction slashCommand) | |||||
| if (context.Interaction is not ISlashCommandInteraction) | |||||
| return ExecuteResult.FromError(InteractionCommandError.ParseFailed, $"Provided {nameof(IInteractionContext)} doesn't belong to a Slash Command Interaction"); | return ExecuteResult.FromError(InteractionCommandError.ParseFailed, $"Provided {nameof(IInteractionContext)} doesn't belong to a Slash Command Interaction"); | ||||
| var options = slashCommand.Data.Options; | |||||
| while (options != null && options.Any(x => x.Type == ApplicationCommandOptionType.SubCommand || x.Type == ApplicationCommandOptionType.SubCommandGroup)) | |||||
| options = options.ElementAt(0)?.Options; | |||||
| return await ExecuteAsync(context, Parameters, options?.ToList(), services); | |||||
| return await base.ExecuteAsync(context, services); | |||||
| } | } | ||||
| private async Task<IResult> ExecuteAsync (IInteractionContext context, IEnumerable<SlashCommandParameterInfo> paramList, | |||||
| List<IApplicationCommandInteractionDataOption> argList, IServiceProvider services) | |||||
| protected override async Task<IResult> ParseArgumentsAsync(IInteractionContext context, IServiceProvider services) | |||||
| { | { | ||||
| try | |||||
| List<IApplicationCommandInteractionDataOption> GetOptions() | |||||
| { | { | ||||
| var slashCommandParameterInfos = paramList.ToList(); | |||||
| var args = new object[slashCommandParameterInfos.Count]; | |||||
| for (var i = 0; i < slashCommandParameterInfos.Count; i++) | |||||
| { | |||||
| var parameter = slashCommandParameterInfos[i]; | |||||
| var result = await ParseArgument(parameter, context, argList, services).ConfigureAwait(false); | |||||
| if (!result.IsSuccess) | |||||
| return await InvokeEventAndReturn(context, result).ConfigureAwait(false); | |||||
| var options = (context.Interaction as ISlashCommandInteraction).Data.Options; | |||||
| if (result is not ParseResult parseResult) | |||||
| return ExecuteResult.FromError(InteractionCommandError.BadArgs, "Command parameter parsing failed for an unknown reason."); | |||||
| while (options != null && options.Any(x => x.Type == ApplicationCommandOptionType.SubCommand || x.Type == ApplicationCommandOptionType.SubCommandGroup)) | |||||
| options = options.ElementAt(0)?.Options; | |||||
| args[i] = parseResult.Value; | |||||
| } | |||||
| return await RunAsync(context, args, services).ConfigureAwait(false); | |||||
| return options.ToList(); | |||||
| } | } | ||||
| catch(Exception ex) | |||||
| var options = GetOptions(); | |||||
| var args = new object[Parameters.Count]; | |||||
| for(var i = 0; i < Parameters.Count; i++) | |||||
| { | { | ||||
| return await InvokeEventAndReturn(context, ExecuteResult.FromError(ex)).ConfigureAwait(false); | |||||
| var parameter = Parameters[i]; | |||||
| var result = await ParseArgumentAsync(parameter, context, options, services).ConfigureAwait(false); | |||||
| if (!result.IsSuccess) | |||||
| return await InvokeEventAndReturn(context, ParseResult.FromError(result)).ConfigureAwait(false); | |||||
| if (result is not TypeConverterResult converterResult) | |||||
| return ExecuteResult.FromError(InteractionCommandError.BadArgs, "Complex command parsing failed for an unknown reason."); | |||||
| args[i] = converterResult.Value; | |||||
| } | } | ||||
| return ParseResult.FromSuccess(args); | |||||
| } | } | ||||
| private async Task<IResult> ParseArgument(SlashCommandParameterInfo parameterInfo, IInteractionContext context, List<IApplicationCommandInteractionDataOption> argList, | |||||
| private async ValueTask<IResult> ParseArgumentAsync(SlashCommandParameterInfo parameterInfo, IInteractionContext context, List<IApplicationCommandInteractionDataOption> argList, | |||||
| IServiceProvider services) | IServiceProvider services) | ||||
| { | { | ||||
| if (parameterInfo.IsComplexParameter) | if (parameterInfo.IsComplexParameter) | ||||
| @@ -111,32 +107,29 @@ namespace Discord.Interactions | |||||
| for (var i = 0; i < ctorArgs.Length; i++) | for (var i = 0; i < ctorArgs.Length; i++) | ||||
| { | { | ||||
| var result = await ParseArgument(parameterInfo.ComplexParameterFields.ElementAt(i), context, argList, services).ConfigureAwait(false); | |||||
| var result = await ParseArgumentAsync(parameterInfo.ComplexParameterFields.ElementAt(i), context, argList, services).ConfigureAwait(false); | |||||
| if (!result.IsSuccess) | if (!result.IsSuccess) | ||||
| return result; | return result; | ||||
| if (result is not ParseResult parseResult) | |||||
| if (result is not TypeConverterResult converterResult) | |||||
| return ExecuteResult.FromError(InteractionCommandError.BadArgs, "Complex command parsing failed for an unknown reason."); | return ExecuteResult.FromError(InteractionCommandError.BadArgs, "Complex command parsing failed for an unknown reason."); | ||||
| ctorArgs[i] = parseResult.Value; | |||||
| ctorArgs[i] = converterResult.Value; | |||||
| } | } | ||||
| return ParseResult.FromSuccess(parameterInfo._complexParameterInitializer(ctorArgs)); | |||||
| return TypeConverterResult.FromSuccess(parameterInfo._complexParameterInitializer(ctorArgs)); | |||||
| } | } | ||||
| var arg = argList?.Find(x => string.Equals(x.Name, parameterInfo.Name, StringComparison.OrdinalIgnoreCase)); | var arg = argList?.Find(x => string.Equals(x.Name, parameterInfo.Name, StringComparison.OrdinalIgnoreCase)); | ||||
| if (arg == default) | if (arg == default) | ||||
| return parameterInfo.IsRequired ? ExecuteResult.FromError(InteractionCommandError.BadArgs, "Command was invoked with too few parameters") : | return parameterInfo.IsRequired ? ExecuteResult.FromError(InteractionCommandError.BadArgs, "Command was invoked with too few parameters") : | ||||
| ParseResult.FromSuccess(parameterInfo.DefaultValue); | |||||
| TypeConverterResult.FromSuccess(parameterInfo.DefaultValue); | |||||
| var typeConverter = parameterInfo.TypeConverter; | var typeConverter = parameterInfo.TypeConverter; | ||||
| var readResult = await typeConverter.ReadAsync(context, arg, services).ConfigureAwait(false); | var readResult = await typeConverter.ReadAsync(context, arg, services).ConfigureAwait(false); | ||||
| if (!readResult.IsSuccess) | |||||
| return readResult; | |||||
| return ParseResult.FromSuccess(readResult.Value); | |||||
| return readResult; | |||||
| } | } | ||||
| protected override Task InvokeModuleEvent (IInteractionContext context, IResult result) | protected override Task InvokeModuleEvent (IInteractionContext context, IResult result) | ||||
| @@ -103,7 +103,7 @@ namespace Discord.Interactions | |||||
| public async Task<IResult> CreateModalAsync(IInteractionContext context, IServiceProvider services = null, bool throwOnMissingField = false) | public async Task<IResult> CreateModalAsync(IInteractionContext context, IServiceProvider services = null, bool throwOnMissingField = false) | ||||
| { | { | ||||
| if (context.Interaction is not IModalInteraction modalInteraction) | if (context.Interaction is not IModalInteraction modalInteraction) | ||||
| return ParseResult.FromError(InteractionCommandError.Unsuccessful, "Provided context doesn't belong to a Modal Interaction."); | |||||
| return TypeConverterResult.FromError(InteractionCommandError.Unsuccessful, "Provided context doesn't belong to a Modal Interaction."); | |||||
| services ??= EmptyServiceProvider.Instance; | services ??= EmptyServiceProvider.Instance; | ||||
| @@ -120,7 +120,7 @@ namespace Discord.Interactions | |||||
| if (!throwOnMissingField) | if (!throwOnMissingField) | ||||
| args[i] = input.DefaultValue; | args[i] = input.DefaultValue; | ||||
| else | else | ||||
| return ParseResult.FromError(InteractionCommandError.BadArgs, $"Modal interaction is missing the required field: {input.CustomId}"); | |||||
| return TypeConverterResult.FromError(InteractionCommandError.BadArgs, $"Modal interaction is missing the required field: {input.CustomId}"); | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| @@ -133,7 +133,7 @@ namespace Discord.Interactions | |||||
| } | } | ||||
| } | } | ||||
| return ParseResult.FromSuccess(_initializer(args)); | |||||
| return TypeConverterResult.FromSuccess(_initializer(args)); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -822,7 +822,7 @@ namespace Discord.Interactions | |||||
| SetMatchesIfApplicable(context, result); | SetMatchesIfApplicable(context, result); | ||||
| return await result.Command.ExecuteAsync(context, services, result.RegexCaptureGroups).ConfigureAwait(false); | |||||
| return await result.Command.ExecuteAsync(context, services).ConfigureAwait(false); | |||||
| } | } | ||||
| private async Task<IResult> ExecuteAutocompleteAsync (IInteractionContext context, IAutocompleteInteraction interaction, IServiceProvider services ) | private async Task<IResult> ExecuteAutocompleteAsync (IInteractionContext context, IAutocompleteInteraction interaction, IServiceProvider services ) | ||||
| @@ -869,7 +869,7 @@ namespace Discord.Interactions | |||||
| SetMatchesIfApplicable(context, result); | SetMatchesIfApplicable(context, result); | ||||
| return await result.Command.ExecuteAsync(context, services, result.RegexCaptureGroups).ConfigureAwait(false); | |||||
| return await result.Command.ExecuteAsync(context, services).ConfigureAwait(false); | |||||
| } | } | ||||
| private static void SetMatchesIfApplicable<T>(IInteractionContext context, SearchResult<T> searchResult) | private static void SetMatchesIfApplicable<T>(IInteractionContext context, SearchResult<T> searchResult) | ||||
| @@ -2,9 +2,9 @@ using System; | |||||
| namespace Discord.Interactions | namespace Discord.Interactions | ||||
| { | { | ||||
| internal struct ParseResult : IResult | |||||
| public struct ParseResult : IResult | |||||
| { | { | ||||
| public object Value { get; } | |||||
| public object[] Args { get; } | |||||
| public InteractionCommandError? Error { get; } | public InteractionCommandError? Error { get; } | ||||
| @@ -12,15 +12,15 @@ namespace Discord.Interactions | |||||
| public bool IsSuccess => !Error.HasValue; | public bool IsSuccess => !Error.HasValue; | ||||
| private ParseResult(object value, InteractionCommandError? error, string reason) | |||||
| private ParseResult(object[] args, InteractionCommandError? error, string reason) | |||||
| { | { | ||||
| Value = value; | |||||
| Args = args; | |||||
| Error = error; | Error = error; | ||||
| ErrorReason = reason; | ErrorReason = reason; | ||||
| } | } | ||||
| public static ParseResult FromSuccess(object value) => | |||||
| new ParseResult(value, null, null); | |||||
| public static ParseResult FromSuccess(object[] args) => | |||||
| new ParseResult(args, null, null); | |||||
| public static ParseResult FromError(Exception exception) => | public static ParseResult FromError(Exception exception) => | ||||
| new ParseResult(null, InteractionCommandError.Exception, exception.Message); | new ParseResult(null, InteractionCommandError.Exception, exception.Message); | ||||
| @@ -70,8 +70,24 @@ namespace Discord.API | |||||
| //ForumChannel | //ForumChannel | ||||
| [JsonProperty("available_tags")] | [JsonProperty("available_tags")] | ||||
| public Optional<ForumTags[]> ForumTags { get; set; } | public Optional<ForumTags[]> ForumTags { get; set; } | ||||
| [JsonProperty("applied_tags")] | |||||
| public Optional<ulong[]> AppliedTags { get; set; } | |||||
| [JsonProperty("default_auto_archive_duration")] | [JsonProperty("default_auto_archive_duration")] | ||||
| public Optional<ThreadArchiveDuration> AutoArchiveDuration { get; set; } | public Optional<ThreadArchiveDuration> AutoArchiveDuration { get; set; } | ||||
| [JsonProperty("default_thread_rate_limit_per_user")] | |||||
| public Optional<int> ThreadRateLimitPerUser { get; set; } | |||||
| [JsonProperty("flags")] | |||||
| public Optional<ChannelFlags> Flags { get; set; } | |||||
| [JsonProperty("default_sort_order")] | |||||
| public Optional<ForumSortOrder?> DefaultSortOrder { get; set; } | |||||
| [JsonProperty("default_reaction_emoji")] | |||||
| public Optional<ForumReactionEmoji> DefaultReactionEmoji { get; set; } | |||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,12 @@ | |||||
| using Newtonsoft.Json; | |||||
| namespace Discord.API; | |||||
| public class ForumReactionEmoji | |||||
| { | |||||
| [JsonProperty("emoji_id")] | |||||
| public ulong? EmojiId { get; set; } | |||||
| [JsonProperty("emoji_name")] | |||||
| public Optional<string> EmojiName { get; set; } | |||||
| } | |||||
| @@ -17,5 +17,8 @@ namespace Discord.API | |||||
| public Optional<ulong?> EmojiId { get; set; } | public Optional<ulong?> EmojiId { get; set; } | ||||
| [JsonProperty("emoji_name")] | [JsonProperty("emoji_name")] | ||||
| public Optional<string> EmojiName { get; set; } | public Optional<string> EmojiName { get; set; } | ||||
| [JsonProperty("moderated")] | |||||
| public bool Moderated { get; set; } | |||||
| } | } | ||||
| } | } | ||||
| @@ -23,6 +23,8 @@ namespace Discord.API.Rest | |||||
| public Optional<bool> IsNsfw { get; set; } | public Optional<bool> IsNsfw { get; set; } | ||||
| [JsonProperty("rate_limit_per_user")] | [JsonProperty("rate_limit_per_user")] | ||||
| public Optional<int> SlowModeInterval { get; set; } | public Optional<int> SlowModeInterval { get; set; } | ||||
| [JsonProperty("default_auto_archive_duration")] | |||||
| public Optional<ThreadArchiveDuration> DefaultAutoArchiveDuration { get; set; } | |||||
| //Voice channels | //Voice channels | ||||
| [JsonProperty("bitrate")] | [JsonProperty("bitrate")] | ||||
| @@ -30,6 +32,16 @@ namespace Discord.API.Rest | |||||
| [JsonProperty("user_limit")] | [JsonProperty("user_limit")] | ||||
| public Optional<int?> UserLimit { get; set; } | public Optional<int?> UserLimit { get; set; } | ||||
| //Forum channels | |||||
| [JsonProperty("default_reaction_emoji")] | |||||
| public Optional<ModifyForumReactionEmojiParams> DefaultReactionEmoji { get; set; } | |||||
| [JsonProperty("default_thread_rate_limit_per_user")] | |||||
| public Optional<int> ThreadRateLimitPerUser { get; set; } | |||||
| [JsonProperty("available_tags")] | |||||
| public Optional<ModifyForumTagParams[]> AvailableTags { get; set; } | |||||
| [JsonProperty("default_sort_order")] | |||||
| public Optional<ForumSortOrder?> DefaultSortOrder { get; set; } | |||||
| public CreateGuildChannelParams(string name, ChannelType type) | public CreateGuildChannelParams(string name, ChannelType type) | ||||
| { | { | ||||
| Name = name; | Name = name; | ||||
| @@ -27,6 +27,7 @@ namespace Discord.API.Rest | |||||
| public Optional<ActionRowComponent[]> MessageComponent { get; set; } | public Optional<ActionRowComponent[]> MessageComponent { get; set; } | ||||
| public Optional<MessageFlags?> Flags { get; set; } | public Optional<MessageFlags?> Flags { get; set; } | ||||
| public Optional<ulong[]> Stickers { get; set; } | public Optional<ulong[]> Stickers { get; set; } | ||||
| public Optional<ulong[]> TagIds { get; set; } | |||||
| public CreateMultipartPostAsync(params FileAttachment[] attachments) | public CreateMultipartPostAsync(params FileAttachment[] attachments) | ||||
| { | { | ||||
| @@ -59,6 +60,8 @@ namespace Discord.API.Rest | |||||
| message["sticker_ids"] = Stickers.Value; | message["sticker_ids"] = Stickers.Value; | ||||
| if (Flags.IsSpecified) | if (Flags.IsSpecified) | ||||
| message["flags"] = Flags.Value; | message["flags"] = Flags.Value; | ||||
| if (TagIds.IsSpecified) | |||||
| message["applied_tags"] = TagIds.Value; | |||||
| List<object> attachments = new(); | List<object> attachments = new(); | ||||
| @@ -21,5 +21,8 @@ namespace Discord.API.Rest | |||||
| [JsonProperty("message")] | [JsonProperty("message")] | ||||
| public ForumThreadMessage Message { get; set; } | public ForumThreadMessage Message { get; set; } | ||||
| [JsonProperty("applied_tags")] | |||||
| public Optional<ulong[]> Tags { get; set; } | |||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,23 @@ | |||||
| using Newtonsoft.Json; | |||||
| namespace Discord.API.Rest; | |||||
| [JsonObject(MemberSerialization = MemberSerialization.OptIn)] | |||||
| internal class ModifyForumChannelParams : ModifyTextChannelParams | |||||
| { | |||||
| [JsonProperty("available_tags")] | |||||
| public Optional<ModifyForumTagParams[]> Tags { get; set; } | |||||
| [JsonProperty("default_thread_rate_limit_per_user")] | |||||
| public Optional<int> DefaultSlowModeInterval { get; set; } | |||||
| [JsonProperty("rate_limit_per_user")] | |||||
| public Optional<int> ThreadCreationInterval { get; set; } | |||||
| [JsonProperty("default_reaction_emoji")] | |||||
| public Optional<ModifyForumReactionEmojiParams> DefaultReactionEmoji { get; set; } | |||||
| [JsonProperty("default_sort_order")] | |||||
| public Optional<ForumSortOrder> DefaultSortOrder { get; set; } | |||||
| } | |||||
| @@ -0,0 +1,15 @@ | |||||
| using Newtonsoft.Json; | |||||
| namespace Discord.API; | |||||
| [JsonObject(MemberSerialization = MemberSerialization.OptIn)] | |||||
| public class ModifyForumReactionEmojiParams | |||||
| { | |||||
| [JsonProperty("emoji_id")] | |||||
| public Optional<ulong?> EmojiId { get; set; } | |||||
| [JsonProperty("emoji_name")] | |||||
| public Optional<string> EmojiName { get; set; } | |||||
| } | |||||
| @@ -0,0 +1,23 @@ | |||||
| using Newtonsoft.Json; | |||||
| namespace Discord.API | |||||
| { | |||||
| [JsonObject(MemberSerialization = MemberSerialization.OptIn)] | |||||
| internal class ModifyForumTagParams | |||||
| { | |||||
| [JsonProperty("id")] | |||||
| public Optional<ulong> Id { get; set; } | |||||
| [JsonProperty("name")] | |||||
| public string Name { get; set; } | |||||
| [JsonProperty("emoji_id")] | |||||
| public Optional<ulong?> EmojiId { get; set; } | |||||
| [JsonProperty("emoji_name")] | |||||
| public Optional<string> EmojiName { get; set; } | |||||
| [JsonProperty("moderated")] | |||||
| public bool Moderated { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -13,5 +13,7 @@ namespace Discord.API.Rest | |||||
| public Optional<ulong?> CategoryId { get; set; } | public Optional<ulong?> CategoryId { get; set; } | ||||
| [JsonProperty("permission_overwrites")] | [JsonProperty("permission_overwrites")] | ||||
| public Optional<Overwrite[]> Overwrites { get; set; } | public Optional<Overwrite[]> Overwrites { get; set; } | ||||
| [JsonProperty("flags")] | |||||
| public Optional<ChannelFlags?> Flags { get; set; } | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,4 +1,5 @@ | |||||
| using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
| using System.Collections.Generic; | |||||
| namespace Discord.API.Rest | namespace Discord.API.Rest | ||||
| { | { | ||||
| @@ -18,5 +19,11 @@ namespace Discord.API.Rest | |||||
| [JsonProperty("rate_limit_per_user")] | [JsonProperty("rate_limit_per_user")] | ||||
| public Optional<int> Slowmode { get; set; } | public Optional<int> Slowmode { get; set; } | ||||
| [JsonProperty("applied_tags")] | |||||
| public Optional<IEnumerable<ulong>> AppliedTags { get; set; } | |||||
| [JsonProperty("flags")] | |||||
| public Optional<ChannelFlags> Flags { get; set; } | |||||
| } | } | ||||
| } | } | ||||
| @@ -38,6 +38,7 @@ namespace Discord.Rest | |||||
| Deny = overwrite.Permissions.DenyValue.ToString() | Deny = overwrite.Permissions.DenyValue.ToString() | ||||
| }).ToArray() | }).ToArray() | ||||
| : Optional.Create<API.Overwrite[]>(), | : Optional.Create<API.Overwrite[]>(), | ||||
| Flags = args.Flags.GetValueOrDefault(), | |||||
| }; | }; | ||||
| return await client.ApiClient.ModifyGuildChannelAsync(channel.Id, apiArgs, options).ConfigureAwait(false); | return await client.ApiClient.ModifyGuildChannelAsync(channel.Id, apiArgs, options).ConfigureAwait(false); | ||||
| } | } | ||||
| @@ -0,0 +1,63 @@ | |||||
| using Discord.API; | |||||
| using System; | |||||
| using System.Linq; | |||||
| using System.Threading.Tasks; | |||||
| using Model = Discord.API.Channel; | |||||
| namespace Discord.Rest; | |||||
| internal static class ForumHelper | |||||
| { | |||||
| public static async Task<Model> ModifyAsync(IForumChannel channel, BaseDiscordClient client, | |||||
| Action<ForumChannelProperties> func, | |||||
| RequestOptions options) | |||||
| { | |||||
| var args = new ForumChannelProperties(); | |||||
| func(args); | |||||
| Preconditions.AtMost(args.Tags.IsSpecified ? args.Tags.Value.Count() : 0, 5, nameof(args.Tags), "Forum channel can have max 20 tags."); | |||||
| var apiArgs = new API.Rest.ModifyForumChannelParams() | |||||
| { | |||||
| Name = args.Name, | |||||
| Position = args.Position, | |||||
| CategoryId = args.CategoryId, | |||||
| Overwrites = args.PermissionOverwrites.IsSpecified | |||||
| ? args.PermissionOverwrites.Value.Select(overwrite => new API.Overwrite | |||||
| { | |||||
| TargetId = overwrite.TargetId, | |||||
| TargetType = overwrite.TargetType, | |||||
| Allow = overwrite.Permissions.AllowValue.ToString(), | |||||
| Deny = overwrite.Permissions.DenyValue.ToString() | |||||
| }).ToArray() | |||||
| : Optional.Create<API.Overwrite[]>(), | |||||
| DefaultSlowModeInterval = args.DefaultSlowModeInterval, | |||||
| ThreadCreationInterval = args.ThreadCreationInterval, | |||||
| Tags = args.Tags.IsSpecified | |||||
| ? args.Tags.Value.Select(tag => new API.ModifyForumTagParams | |||||
| { | |||||
| Name = tag.Name, | |||||
| EmojiId = tag.Emoji is Emote emote | |||||
| ? emote.Id | |||||
| : Optional<ulong?>.Unspecified, | |||||
| EmojiName = tag.Emoji is Emoji emoji | |||||
| ? emoji.Name | |||||
| : Optional<string>.Unspecified | |||||
| }).ToArray() | |||||
| : Optional.Create<API.ModifyForumTagParams[]>(), | |||||
| Flags = args.Flags.GetValueOrDefault(), | |||||
| Topic = args.Topic, | |||||
| DefaultReactionEmoji = args.DefaultReactionEmoji.IsSpecified | |||||
| ? new API.ModifyForumReactionEmojiParams | |||||
| { | |||||
| EmojiId = args.DefaultReactionEmoji.Value is Emote emote ? | |||||
| emote.Id : Optional<ulong?>.Unspecified, | |||||
| EmojiName = args.DefaultReactionEmoji.Value is Emoji emoji ? | |||||
| emoji.Name : Optional<string>.Unspecified | |||||
| } | |||||
| : Optional<ModifyForumReactionEmojiParams>.Unspecified, | |||||
| DefaultSortOrder = args.DefaultSortOrder | |||||
| }; | |||||
| return await client.ApiClient.ModifyGuildChannelAsync(channel.Id, apiArgs, options).ConfigureAwait(false); | |||||
| } | |||||
| } | |||||
| @@ -30,13 +30,15 @@ namespace Discord.Rest | |||||
| ChannelType.Stage or | ChannelType.Stage or | ||||
| ChannelType.NewsThread or | ChannelType.NewsThread or | ||||
| ChannelType.PrivateThread or | ChannelType.PrivateThread or | ||||
| ChannelType.PublicThread | |||||
| ChannelType.PublicThread or | |||||
| ChannelType.Forum | |||||
| => RestGuildChannel.Create(discord, new RestGuild(discord, model.GuildId.Value), model), | => RestGuildChannel.Create(discord, new RestGuild(discord, model.GuildId.Value), model), | ||||
| ChannelType.DM or ChannelType.Group => CreatePrivate(discord, model) as RestChannel, | ChannelType.DM or ChannelType.Group => CreatePrivate(discord, model) as RestChannel, | ||||
| ChannelType.Category => RestCategoryChannel.Create(discord, new RestGuild(discord, model.GuildId.Value), model), | ChannelType.Category => RestCategoryChannel.Create(discord, new RestGuild(discord, model.GuildId.Value), model), | ||||
| _ => new RestChannel(discord, model.Id), | _ => new RestChannel(discord, model.Id), | ||||
| }; | }; | ||||
| } | } | ||||
| internal static RestChannel Create(BaseDiscordClient discord, Model model, IGuild guild) | internal static RestChannel Create(BaseDiscordClient discord, Model model, IGuild guild) | ||||
| { | { | ||||
| return model.Type switch | return model.Type switch | ||||
| @@ -47,7 +49,8 @@ namespace Discord.Rest | |||||
| ChannelType.Stage or | ChannelType.Stage or | ||||
| ChannelType.NewsThread or | ChannelType.NewsThread or | ||||
| ChannelType.PrivateThread or | ChannelType.PrivateThread or | ||||
| ChannelType.PublicThread | |||||
| ChannelType.PublicThread or | |||||
| ChannelType.Forum | |||||
| => RestGuildChannel.Create(discord, guild, model), | => RestGuildChannel.Create(discord, guild, model), | ||||
| ChannelType.DM or ChannelType.Group => CreatePrivate(discord, model) as RestChannel, | ChannelType.DM or ChannelType.Group => CreatePrivate(discord, model) as RestChannel, | ||||
| ChannelType.Category => RestCategoryChannel.Create(discord, guild, model), | ChannelType.Category => RestCategoryChannel.Create(discord, guild, model), | ||||
| @@ -26,6 +26,21 @@ namespace Discord.Rest | |||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||
| public IReadOnlyCollection<ForumTag> Tags { get; private set; } | public IReadOnlyCollection<ForumTag> Tags { get; private set; } | ||||
| /// <inheritdoc/> | |||||
| public int ThreadCreationInterval { get; private set; } | |||||
| /// <inheritdoc/> | |||||
| public int DefaultSlowModeInterval { get; private set; } | |||||
| /// <inheritdoc/> | |||||
| public ulong? CategoryId { get; private set; } | |||||
| /// <inheritdoc/> | |||||
| public IEmote DefaultReactionEmoji { get; private set; } | |||||
| /// <inheritdoc/> | |||||
| public ForumSortOrder? DefaultSortOrder { get; private set; } | |||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||
| public string Mention => MentionUtils.MentionChannel(Id); | public string Mention => MentionUtils.MentionChannel(Id); | ||||
| @@ -35,9 +50,9 @@ namespace Discord.Rest | |||||
| } | } | ||||
| internal new static RestStageChannel Create(BaseDiscordClient discord, IGuild guild, Model model) | |||||
| internal new static RestForumChannel Create(BaseDiscordClient discord, IGuild guild, Model model) | |||||
| { | { | ||||
| var entity = new RestStageChannel(discord, guild, model.Id); | |||||
| var entity = new RestForumChannel(discord, guild, model.Id); | |||||
| entity.Update(model); | entity.Update(model); | ||||
| return entity; | return entity; | ||||
| } | } | ||||
| @@ -49,46 +64,75 @@ namespace Discord.Rest | |||||
| Topic = model.Topic.GetValueOrDefault(); | Topic = model.Topic.GetValueOrDefault(); | ||||
| DefaultAutoArchiveDuration = model.AutoArchiveDuration.GetValueOrDefault(ThreadArchiveDuration.OneDay); | DefaultAutoArchiveDuration = model.AutoArchiveDuration.GetValueOrDefault(ThreadArchiveDuration.OneDay); | ||||
| if (model.ThreadRateLimitPerUser.IsSpecified) | |||||
| DefaultSlowModeInterval = model.ThreadRateLimitPerUser.Value; | |||||
| if(model.SlowMode.IsSpecified) | |||||
| ThreadCreationInterval = model.SlowMode.Value; | |||||
| DefaultSortOrder = model.DefaultSortOrder.GetValueOrDefault(); | |||||
| Tags = model.ForumTags.GetValueOrDefault(Array.Empty<API.ForumTags>()).Select( | Tags = model.ForumTags.GetValueOrDefault(Array.Empty<API.ForumTags>()).Select( | ||||
| x => new ForumTag(x.Id, x.Name, x.EmojiId.GetValueOrDefault(null), x.EmojiName.GetValueOrDefault()) | |||||
| x => new ForumTag(x.Id, x.Name, x.EmojiId.GetValueOrDefault(null), x.EmojiName.GetValueOrDefault(), x.Moderated) | |||||
| ).ToImmutableArray(); | ).ToImmutableArray(); | ||||
| if (model.DefaultReactionEmoji.IsSpecified && model.DefaultReactionEmoji.Value is not null) | |||||
| { | |||||
| if (model.DefaultReactionEmoji.Value.EmojiId.HasValue && model.DefaultReactionEmoji.Value.EmojiId.Value != 0) | |||||
| DefaultReactionEmoji = new Emote(model.DefaultReactionEmoji.Value.EmojiId.GetValueOrDefault(), null, false); | |||||
| else if (model.DefaultReactionEmoji.Value.EmojiName.IsSpecified) | |||||
| DefaultReactionEmoji = new Emoji(model.DefaultReactionEmoji.Value.EmojiName.Value); | |||||
| else | |||||
| DefaultReactionEmoji = null; | |||||
| } | |||||
| CategoryId = model.CategoryId.GetValueOrDefault(); | |||||
| } | |||||
| /// <inheritdoc/> | |||||
| public async Task ModifyAsync(Action<ForumChannelProperties> func, RequestOptions options = null) | |||||
| { | |||||
| var model = await ForumHelper.ModifyAsync(this, Discord, func, options); | |||||
| Update(model); | |||||
| } | } | ||||
| /// <inheritdoc cref="IForumChannel.CreatePostAsync(string, ThreadArchiveDuration, int?, string, Embed, RequestOptions, AllowedMentions, MessageComponent, ISticker[], Embed[], MessageFlags)"/> | |||||
| public Task<RestThreadChannel> CreatePostAsync(string title, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay, int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) | |||||
| => ThreadHelper.CreatePostAsync(this, Discord, title, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags); | |||||
| /// <inheritdoc cref="IForumChannel.CreatePostAsync(string, ThreadArchiveDuration, int?, string, Embed, RequestOptions, AllowedMentions, MessageComponent, ISticker[], Embed[], MessageFlags, ForumTag[])"/> | |||||
| public Task<RestThreadChannel> CreatePostAsync(string title, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay, int? slowmode = null, | |||||
| string text = null, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageComponent components = null, ISticker[] stickers = null, | |||||
| Embed[] embeds = null, MessageFlags flags = MessageFlags.None, ForumTag[] tags = null) | |||||
| => ThreadHelper.CreatePostAsync(this, Discord, title, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags, tags?.Select(tag => tag.Id).ToArray()); | |||||
| /// <inheritdoc cref="IForumChannel.CreatePostWithFileAsync(string, string, ThreadArchiveDuration, int?, string, Embed, RequestOptions, bool, AllowedMentions, MessageComponent, ISticker[], Embed[], MessageFlags)"/> | |||||
| /// <inheritdoc cref="IForumChannel.CreatePostWithFileAsync(string, string, ThreadArchiveDuration, int?, string, Embed, RequestOptions, bool, AllowedMentions, MessageComponent, ISticker[], Embed[], MessageFlags, ForumTag[])"/> | |||||
| public async Task<RestThreadChannel> CreatePostWithFileAsync(string title, string filePath, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay, | public async Task<RestThreadChannel> CreatePostWithFileAsync(string title, string filePath, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay, | ||||
| int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, | int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, | ||||
| AllowedMentions allowedMentions = null, MessageComponent components = null, | AllowedMentions allowedMentions = null, MessageComponent components = null, | ||||
| ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) | |||||
| ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None, ForumTag[] tags = null) | |||||
| { | { | ||||
| using var file = new FileAttachment(filePath, isSpoiler: isSpoiler); | using var file = new FileAttachment(filePath, isSpoiler: isSpoiler); | ||||
| return await ThreadHelper.CreatePostAsync(this, Discord, title, new FileAttachment[] { file }, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false); | |||||
| return await ThreadHelper.CreatePostAsync(this, Discord, title, new FileAttachment[] { file }, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags, tags?.Select(tag => tag.Id).ToArray()).ConfigureAwait(false); | |||||
| } | } | ||||
| /// <inheritdoc cref="IForumChannel.CreatePostWithFileAsync(string, Stream, string, ThreadArchiveDuration, int?, string, Embed, RequestOptions, bool, AllowedMentions, MessageComponent, ISticker[], Embed[], MessageFlags)"/> | |||||
| /// <inheritdoc cref="IForumChannel.CreatePostWithFileAsync(string, Stream, string, ThreadArchiveDuration, int?, string, Embed, RequestOptions, bool, AllowedMentions, MessageComponent, ISticker[], Embed[], MessageFlags, ForumTag[])"/> | |||||
| public async Task<RestThreadChannel> CreatePostWithFileAsync(string title, Stream stream, string filename, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay, | public async Task<RestThreadChannel> CreatePostWithFileAsync(string title, Stream stream, string filename, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay, | ||||
| int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, | int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, | ||||
| AllowedMentions allowedMentions = null, MessageComponent components = null, | AllowedMentions allowedMentions = null, MessageComponent components = null, | ||||
| ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) | |||||
| ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None, ForumTag[] tags = null) | |||||
| { | { | ||||
| using var file = new FileAttachment(stream, filename, isSpoiler: isSpoiler); | using var file = new FileAttachment(stream, filename, isSpoiler: isSpoiler); | ||||
| return await ThreadHelper.CreatePostAsync(this, Discord, title, new FileAttachment[] { file }, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false); | |||||
| return await ThreadHelper.CreatePostAsync(this, Discord, title, new FileAttachment[] { file }, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags, tags?.Select(tag => tag.Id).ToArray()).ConfigureAwait(false); | |||||
| } | } | ||||
| /// <inheritdoc cref="IForumChannel.CreatePostWithFileAsync(string, FileAttachment, ThreadArchiveDuration, int?, string, Embed, RequestOptions, AllowedMentions, MessageComponent, ISticker[], Embed[], MessageFlags)"/> | |||||
| /// <inheritdoc cref="IForumChannel.CreatePostWithFileAsync(string, FileAttachment, ThreadArchiveDuration, int?, string, Embed, RequestOptions, AllowedMentions, MessageComponent, ISticker[], Embed[], MessageFlags, ForumTag[])"/> | |||||
| public Task<RestThreadChannel> CreatePostWithFileAsync(string title, FileAttachment attachment, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay, | public Task<RestThreadChannel> CreatePostWithFileAsync(string title, FileAttachment attachment, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay, | ||||
| int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, | int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, | ||||
| MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) | |||||
| => ThreadHelper.CreatePostAsync(this, Discord, title, new FileAttachment[] { attachment }, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags); | |||||
| MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None, ForumTag[] tags = null) | |||||
| => ThreadHelper.CreatePostAsync(this, Discord, title, new FileAttachment[] { attachment }, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags, tags?.Select(tag => tag.Id).ToArray()); | |||||
| /// <inheritdoc cref="IForumChannel.CreatePostWithFilesAsync(string, IEnumerable{FileAttachment}, ThreadArchiveDuration, int?, string, Embed, RequestOptions, AllowedMentions, MessageComponent, ISticker[], Embed[], MessageFlags)"/> | |||||
| /// <inheritdoc cref="IForumChannel.CreatePostWithFilesAsync(string, IEnumerable{FileAttachment}, ThreadArchiveDuration, int?, string, Embed, RequestOptions, AllowedMentions, MessageComponent, ISticker[], Embed[], MessageFlags, ForumTag[])"/> | |||||
| public Task<RestThreadChannel> CreatePostWithFilesAsync(string title, IEnumerable<FileAttachment> attachments, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay, | public Task<RestThreadChannel> CreatePostWithFilesAsync(string title, IEnumerable<FileAttachment> attachments, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay, | ||||
| int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, | int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, | ||||
| MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) | |||||
| => ThreadHelper.CreatePostAsync(this, Discord, title, attachments, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags); | |||||
| MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None, ForumTag[] tags = null) | |||||
| => ThreadHelper.CreatePostAsync(this, Discord, title, attachments, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags, tags?.Select(tag => tag.Id).ToArray()); | |||||
| /// <inheritdoc cref="IForumChannel.GetActiveThreadsAsync(RequestOptions)"/> | /// <inheritdoc cref="IForumChannel.GetActiveThreadsAsync(RequestOptions)"/> | ||||
| public Task<IReadOnlyCollection<RestThreadChannel>> GetActiveThreadsAsync(RequestOptions options = null) | public Task<IReadOnlyCollection<RestThreadChannel>> GetActiveThreadsAsync(RequestOptions options = null) | ||||
| @@ -115,17 +159,45 @@ namespace Discord.Rest | |||||
| => await GetPrivateArchivedThreadsAsync(limit, before, options).ConfigureAwait(false); | => await GetPrivateArchivedThreadsAsync(limit, before, options).ConfigureAwait(false); | ||||
| async Task<IReadOnlyCollection<IThreadChannel>> IForumChannel.GetJoinedPrivateArchivedThreadsAsync(int? limit, DateTimeOffset? before, RequestOptions options) | async Task<IReadOnlyCollection<IThreadChannel>> IForumChannel.GetJoinedPrivateArchivedThreadsAsync(int? limit, DateTimeOffset? before, RequestOptions options) | ||||
| => await GetJoinedPrivateArchivedThreadsAsync(limit, before, options).ConfigureAwait(false); | => await GetJoinedPrivateArchivedThreadsAsync(limit, before, options).ConfigureAwait(false); | ||||
| async Task<IThreadChannel> IForumChannel.CreatePostAsync(string title, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags) | |||||
| async Task<IThreadChannel> IForumChannel.CreatePostAsync(string title, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags, ForumTag[] tags) | |||||
| => await CreatePostAsync(title, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false); | => await CreatePostAsync(title, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false); | ||||
| async Task<IThreadChannel> IForumChannel.CreatePostWithFileAsync(string title, string filePath, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags) | |||||
| async Task<IThreadChannel> IForumChannel.CreatePostWithFileAsync(string title, string filePath, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags, ForumTag[] tags) | |||||
| => await CreatePostWithFileAsync(title, filePath, archiveDuration, slowmode, text, embed, options, isSpoiler, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false); | => await CreatePostWithFileAsync(title, filePath, archiveDuration, slowmode, text, embed, options, isSpoiler, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false); | ||||
| async Task<IThreadChannel> IForumChannel.CreatePostWithFileAsync(string title, Stream stream, string filename, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags) | |||||
| async Task<IThreadChannel> IForumChannel.CreatePostWithFileAsync(string title, Stream stream, string filename, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags, ForumTag[] tags) | |||||
| => await CreatePostWithFileAsync(title, stream, filename, archiveDuration, slowmode, text, embed, options, isSpoiler, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false); | => await CreatePostWithFileAsync(title, stream, filename, archiveDuration, slowmode, text, embed, options, isSpoiler, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false); | ||||
| async Task<IThreadChannel> IForumChannel.CreatePostWithFileAsync(string title, FileAttachment attachment, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags) | |||||
| async Task<IThreadChannel> IForumChannel.CreatePostWithFileAsync(string title, FileAttachment attachment, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags, ForumTag[] tags) | |||||
| => await CreatePostWithFileAsync(title, attachment, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false); | => await CreatePostWithFileAsync(title, attachment, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false); | ||||
| async Task<IThreadChannel> IForumChannel.CreatePostWithFilesAsync(string title, IEnumerable<FileAttachment> attachments, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags) | |||||
| async Task<IThreadChannel> IForumChannel.CreatePostWithFilesAsync(string title, IEnumerable<FileAttachment> attachments, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags, ForumTag[] tags) | |||||
| => await CreatePostWithFilesAsync(title, attachments, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags); | => await CreatePostWithFilesAsync(title, attachments, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags); | ||||
| #endregion | #endregion | ||||
| #region INestedChannel | |||||
| /// <inheritdoc /> | |||||
| 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); | |||||
| public virtual async Task<IInviteMetadata> CreateInviteToApplicationAsync(ulong applicationId, int? maxAge = 86400, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null) | |||||
| => await ChannelHelper.CreateInviteToApplicationAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, applicationId, options); | |||||
| /// <inheritdoc /> | |||||
| public virtual async Task<IInviteMetadata> CreateInviteToApplicationAsync(DefaultApplications application, int? maxAge = 86400, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null) | |||||
| => await ChannelHelper.CreateInviteToApplicationAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, (ulong)application, options); | |||||
| public virtual Task<IInviteMetadata> CreateInviteToStreamAsync(IUser user, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null) | |||||
| => throw new NotImplementedException(); | |||||
| /// <inheritdoc /> | |||||
| public virtual async Task<IReadOnlyCollection<IInviteMetadata>> GetInvitesAsync(RequestOptions options = null) | |||||
| => await ChannelHelper.GetInvitesAsync(this, Discord, options).ConfigureAwait(false); | |||||
| /// <inheritdoc /> | |||||
| async Task<ICategoryChannel> INestedChannel.GetCategoryAsync(CacheMode mode, RequestOptions options) | |||||
| { | |||||
| if (CategoryId.HasValue && mode == CacheMode.AllowDownload) | |||||
| return (await Guild.GetChannelAsync(CategoryId.Value, mode, options).ConfigureAwait(false)) as ICategoryChannel; | |||||
| return null; | |||||
| } | |||||
| /// <inheritdoc /> | |||||
| public Task SyncPermissionsAsync(RequestOptions options = null) | |||||
| => ChannelHelper.SyncPermissionsAsync(this, Discord, options); | |||||
| #endregion | |||||
| } | } | ||||
| } | } | ||||
| @@ -26,6 +26,9 @@ namespace Discord.Rest | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public ulong GuildId => Guild.Id; | public ulong GuildId => Guild.Id; | ||||
| /// <inheritdoc /> | |||||
| public ChannelFlags Flags { get; private set; } | |||||
| internal RestGuildChannel(BaseDiscordClient discord, IGuild guild, ulong id) | internal RestGuildChannel(BaseDiscordClient discord, IGuild guild, ulong id) | ||||
| : base(discord, id) | : base(discord, id) | ||||
| { | { | ||||
| @@ -62,6 +65,8 @@ namespace Discord.Rest | |||||
| newOverwrites.Add(overwrites[i].ToEntity()); | newOverwrites.Add(overwrites[i].ToEntity()); | ||||
| _overwrites = newOverwrites.ToImmutable(); | _overwrites = newOverwrites.ToImmutable(); | ||||
| } | } | ||||
| Flags = model.Flags.GetValueOrDefault(ChannelFlags.None); | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| @@ -37,6 +37,9 @@ namespace Discord.Rest | |||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||
| public bool? IsInvitable { get; private set; } | public bool? IsInvitable { get; private set; } | ||||
| /// <inheritdoc/> | |||||
| public IReadOnlyCollection<ulong> AppliedTags { get; private set; } | |||||
| /// <inheritdoc cref="IThreadChannel.CreatedAt"/> | /// <inheritdoc cref="IThreadChannel.CreatedAt"/> | ||||
| public override DateTimeOffset CreatedAt { get; } | public override DateTimeOffset CreatedAt { get; } | ||||
| @@ -77,6 +80,8 @@ namespace Discord.Rest | |||||
| MessageCount = model.MessageCount.GetValueOrDefault(0); | MessageCount = model.MessageCount.GetValueOrDefault(0); | ||||
| Type = (ThreadType)model.Type; | Type = (ThreadType)model.Type; | ||||
| ParentChannelId = model.CategoryId.Value; | ParentChannelId = model.CategoryId.Value; | ||||
| AppliedTags = model.AppliedTags.GetValueOrDefault(Array.Empty<ulong>()).ToImmutableArray(); | |||||
| } | } | ||||
| /// <summary> | /// <summary> | ||||
| @@ -109,6 +114,13 @@ namespace Discord.Rest | |||||
| Update(model); | Update(model); | ||||
| } | } | ||||
| /// <inheritdoc/> | |||||
| public async Task ModifyAsync(Action<ThreadChannelProperties> func, RequestOptions options = null) | |||||
| { | |||||
| var model = await ThreadHelper.ModifyAsync(this, Discord, func, options); | |||||
| Update(model); | |||||
| } | |||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||
| /// <remarks> | /// <remarks> | ||||
| /// <b>This method is not supported in threads.</b> | /// <b>This method is not supported in threads.</b> | ||||
| @@ -19,8 +19,7 @@ namespace Discord.Rest | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets whether or not the guild has Text-In-Voice enabled and the voice channel is a TiV channel. | /// Gets whether or not the guild has Text-In-Voice enabled and the voice channel is a TiV channel. | ||||
| /// </summary> | /// </summary> | ||||
| public virtual bool IsTextInVoice | |||||
| => Guild.Features.HasTextInVoice; | |||||
| public virtual bool IsTextInVoice => true; | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public int Bitrate { get; private set; } | public int Bitrate { get; private set; } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| @@ -1,3 +1,4 @@ | |||||
| using Discord.API; | |||||
| using Discord.API.Rest; | using Discord.API.Rest; | ||||
| using System; | using System; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| @@ -46,18 +47,23 @@ namespace Discord.Rest | |||||
| } | } | ||||
| public static async Task<Model> ModifyAsync(IThreadChannel channel, BaseDiscordClient client, | public static async Task<Model> ModifyAsync(IThreadChannel channel, BaseDiscordClient client, | ||||
| Action<TextChannelProperties> func, | |||||
| Action<ThreadChannelProperties> func, | |||||
| RequestOptions options) | RequestOptions options) | ||||
| { | { | ||||
| var args = new TextChannelProperties(); | |||||
| var args = new ThreadChannelProperties(); | |||||
| func(args); | func(args); | ||||
| Preconditions.AtMost(args.AppliedTags.IsSpecified ? args.AppliedTags.Value.Count() : 0, 5, nameof(args.AppliedTags), "Forum post can have max 5 applied tags."); | |||||
| var apiArgs = new ModifyThreadParams | var apiArgs = new ModifyThreadParams | ||||
| { | { | ||||
| Name = args.Name, | Name = args.Name, | ||||
| Archived = args.Archived, | Archived = args.Archived, | ||||
| AutoArchiveDuration = args.AutoArchiveDuration, | AutoArchiveDuration = args.AutoArchiveDuration, | ||||
| Locked = args.Locked, | Locked = args.Locked, | ||||
| Slowmode = args.SlowModeInterval | |||||
| Slowmode = args.SlowModeInterval, | |||||
| AppliedTags = args.AppliedTags, | |||||
| Flags = args.Flags, | |||||
| }; | }; | ||||
| return await client.ApiClient.ModifyThreadAsync(channel.Id, apiArgs, options).ConfigureAwait(false); | return await client.ApiClient.ModifyThreadAsync(channel.Id, apiArgs, options).ConfigureAwait(false); | ||||
| } | } | ||||
| @@ -103,7 +109,10 @@ namespace Discord.Rest | |||||
| return RestThreadUser.Create(client, channel.Guild, model, channel); | return RestThreadUser.Create(client, channel.Guild, model, channel); | ||||
| } | } | ||||
| public static async Task<RestThreadChannel> CreatePostAsync(IForumChannel channel, BaseDiscordClient client, string title, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay, int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) | |||||
| public static async Task<RestThreadChannel> CreatePostAsync(IForumChannel channel, BaseDiscordClient client, string title, | |||||
| ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay, int? slowmode = null, string text = null, Embed embed = null, | |||||
| RequestOptions options = null, AllowedMentions allowedMentions = null, MessageComponent components = null, ISticker[] stickers = null, | |||||
| Embed[] embeds = null, MessageFlags flags = MessageFlags.None, ulong[] tagIds = null) | |||||
| { | { | ||||
| embeds ??= Array.Empty<Embed>(); | embeds ??= Array.Empty<Embed>(); | ||||
| if (embed != null) | if (embed != null) | ||||
| @@ -112,6 +121,7 @@ namespace Discord.Rest | |||||
| Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); | 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(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.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed."); | ||||
| Preconditions.AtMost(tagIds?.Length ?? 0, 5, nameof(tagIds), "Forum post can have max 5 applied tags."); | |||||
| // check that user flag and user Id list are exclusive, same with role flag and role Id list | // 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 != null && allowedMentions.AllowedTypes.HasValue) | ||||
| @@ -134,10 +144,12 @@ namespace Discord.Rest | |||||
| Preconditions.AtMost(stickers.Length, 3, nameof(stickers), "A max of 3 stickers are allowed."); | Preconditions.AtMost(stickers.Length, 3, nameof(stickers), "A max of 3 stickers are allowed."); | ||||
| } | } | ||||
| if (flags is not MessageFlags.None and not MessageFlags.SuppressEmbeds) | if (flags is not MessageFlags.None and not MessageFlags.SuppressEmbeds) | ||||
| throw new ArgumentException("The only valid MessageFlags are SuppressEmbeds and none.", nameof(flags)); | throw new ArgumentException("The only valid MessageFlags are SuppressEmbeds and none.", nameof(flags)); | ||||
| if (channel.Flags.HasFlag(ChannelFlags.RequireTag)) | |||||
| throw new ArgumentException($"The channel {channel.Name} requires posts to have at least one tag."); | |||||
| var args = new CreatePostParams() | var args = new CreatePostParams() | ||||
| { | { | ||||
| Title = title, | Title = title, | ||||
| @@ -151,7 +163,8 @@ namespace Discord.Rest | |||||
| Flags = flags, | Flags = flags, | ||||
| Components = components?.Components?.Any() ?? false ? components.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional<API.ActionRowComponent[]>.Unspecified, | Components = components?.Components?.Any() ?? false ? components.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional<API.ActionRowComponent[]>.Unspecified, | ||||
| Stickers = stickers?.Any() ?? false ? stickers.Select(x => x.Id).ToArray() : Optional<ulong[]>.Unspecified, | Stickers = stickers?.Any() ?? false ? stickers.Select(x => x.Id).ToArray() : Optional<ulong[]>.Unspecified, | ||||
| } | |||||
| }, | |||||
| Tags = tagIds | |||||
| }; | }; | ||||
| var model = await client.ApiClient.CreatePostAsync(channel.Id, args, options).ConfigureAwait(false); | var model = await client.ApiClient.CreatePostAsync(channel.Id, args, options).ConfigureAwait(false); | ||||
| @@ -159,7 +172,9 @@ namespace Discord.Rest | |||||
| return RestThreadChannel.Create(client, channel.Guild, model); | return RestThreadChannel.Create(client, channel.Guild, model); | ||||
| } | } | ||||
| public static async Task<RestThreadChannel> CreatePostAsync(IForumChannel channel, BaseDiscordClient client, string title, IEnumerable<FileAttachment> attachments, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags) | |||||
| public static async Task<RestThreadChannel> CreatePostAsync(IForumChannel channel, BaseDiscordClient client, string title, IEnumerable<FileAttachment> attachments, | |||||
| ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageComponent components, | |||||
| ISticker[] stickers, Embed[] embeds, MessageFlags flags, ulong[] tagIds = null) | |||||
| { | { | ||||
| embeds ??= Array.Empty<Embed>(); | embeds ??= Array.Empty<Embed>(); | ||||
| if (embed != null) | if (embed != null) | ||||
| @@ -168,6 +183,8 @@ namespace Discord.Rest | |||||
| Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); | 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(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.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed."); | ||||
| Preconditions.AtMost(tagIds?.Length ?? 0, 5, nameof(tagIds), "Forum post can have max 5 applied tags."); | |||||
| // check that user flag and user Id list are exclusive, same with role flag and role Id list | // 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 != null && allowedMentions.AllowedTypes.HasValue) | ||||
| @@ -190,9 +207,11 @@ namespace Discord.Rest | |||||
| Preconditions.AtMost(stickers.Length, 3, nameof(stickers), "A max of 3 stickers are allowed."); | Preconditions.AtMost(stickers.Length, 3, nameof(stickers), "A max of 3 stickers are allowed."); | ||||
| } | } | ||||
| if (flags is not MessageFlags.None and not MessageFlags.SuppressEmbeds) | if (flags is not MessageFlags.None and not MessageFlags.SuppressEmbeds) | ||||
| throw new ArgumentException("The only valid MessageFlags are SuppressEmbeds and none.", nameof(flags)); | throw new ArgumentException("The only valid MessageFlags are SuppressEmbeds and none.", nameof(flags)); | ||||
| if (channel.Flags.HasFlag(ChannelFlags.RequireTag)) | |||||
| throw new ArgumentException($"The channel {channel.Name} requires posts to have at least one tag."); | |||||
| var args = new CreateMultipartPostAsync(attachments.ToArray()) | var args = new CreateMultipartPostAsync(attachments.ToArray()) | ||||
| { | { | ||||
| @@ -1,3 +1,4 @@ | |||||
| using Discord.API; | |||||
| using Discord.API.Rest; | using Discord.API.Rest; | ||||
| using System; | using System; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| @@ -252,6 +253,7 @@ namespace Discord.Rest | |||||
| Deny = overwrite.Permissions.DenyValue.ToString() | Deny = overwrite.Permissions.DenyValue.ToString() | ||||
| }).ToArray() | }).ToArray() | ||||
| : Optional.Create<API.Overwrite[]>(), | : Optional.Create<API.Overwrite[]>(), | ||||
| DefaultAutoArchiveDuration = props.AutoArchiveDuration | |||||
| }; | }; | ||||
| var model = await client.ApiClient.CreateGuildChannelAsync(guild.Id, args, options).ConfigureAwait(false); | var model = await client.ApiClient.CreateGuildChannelAsync(guild.Id, args, options).ConfigureAwait(false); | ||||
| return RestTextChannel.Create(client, guild, model); | return RestTextChannel.Create(client, guild, model); | ||||
| @@ -338,6 +340,65 @@ namespace Discord.Rest | |||||
| var model = await client.ApiClient.CreateGuildChannelAsync(guild.Id, args, options).ConfigureAwait(false); | var model = await client.ApiClient.CreateGuildChannelAsync(guild.Id, args, options).ConfigureAwait(false); | ||||
| return RestCategoryChannel.Create(client, guild, model); | return RestCategoryChannel.Create(client, guild, model); | ||||
| } | } | ||||
| /// <exception cref="ArgumentNullException"><paramref name="name"/> is <c>null</c>.</exception> | |||||
| public static async Task<RestForumChannel> CreateForumChannelAsync(IGuild guild, BaseDiscordClient client, | |||||
| string name, RequestOptions options, Action<ForumChannelProperties> func = null) | |||||
| { | |||||
| if (name == null) | |||||
| throw new ArgumentNullException(paramName: nameof(name)); | |||||
| var props = new ForumChannelProperties(); | |||||
| func?.Invoke(props); | |||||
| Preconditions.AtMost(props.Tags.IsSpecified ? props.Tags.Value.Count() : 0, 5, nameof(props.Tags), "Forum channel can have max 20 tags."); | |||||
| var args = new CreateGuildChannelParams(name, ChannelType.Forum) | |||||
| { | |||||
| Position = props.Position, | |||||
| Overwrites = props.PermissionOverwrites.IsSpecified | |||||
| ? props.PermissionOverwrites.Value.Select(overwrite => new API.Overwrite | |||||
| { | |||||
| TargetId = overwrite.TargetId, | |||||
| TargetType = overwrite.TargetType, | |||||
| Allow = overwrite.Permissions.AllowValue.ToString(), | |||||
| Deny = overwrite.Permissions.DenyValue.ToString() | |||||
| }).ToArray() | |||||
| : Optional.Create<API.Overwrite[]>(), | |||||
| SlowModeInterval = props.ThreadCreationInterval, | |||||
| AvailableTags = props.Tags.GetValueOrDefault(Array.Empty<ForumTagProperties>()).Select( | |||||
| x => new ModifyForumTagParams | |||||
| { | |||||
| Id = x.Id, | |||||
| Name = x.Name, | |||||
| EmojiId = x.Emoji is Emote emote | |||||
| ? emote.Id | |||||
| : Optional<ulong?>.Unspecified, | |||||
| EmojiName = x.Emoji is Emoji emoji | |||||
| ? emoji.Name | |||||
| : Optional<string>.Unspecified, | |||||
| Moderated = x.IsModerated | |||||
| }).ToArray(), | |||||
| DefaultReactionEmoji = props.DefaultReactionEmoji.IsSpecified | |||||
| ? new API.ModifyForumReactionEmojiParams | |||||
| { | |||||
| EmojiId = props.DefaultReactionEmoji.Value is Emote emote ? | |||||
| emote.Id : Optional<ulong?>.Unspecified, | |||||
| EmojiName = props.DefaultReactionEmoji.Value is Emoji emoji ? | |||||
| emoji.Name : Optional<string>.Unspecified | |||||
| } | |||||
| : Optional<ModifyForumReactionEmojiParams>.Unspecified, | |||||
| ThreadRateLimitPerUser = props.DefaultSlowModeInterval, | |||||
| CategoryId = props.CategoryId, | |||||
| IsNsfw = props.IsNsfw, | |||||
| Topic = props.Topic, | |||||
| DefaultAutoArchiveDuration = props.AutoArchiveDuration, | |||||
| DefaultSortOrder = props.DefaultSortOrder.GetValueOrDefault(ForumSortOrder.LatestActivity) | |||||
| }; | |||||
| var model = await client.ApiClient.CreateGuildChannelAsync(guild.Id, args, options).ConfigureAwait(false); | |||||
| return RestForumChannel.Create(client, guild, model); | |||||
| } | |||||
| #endregion | #endregion | ||||
| #region Voice Regions | #region Voice Regions | ||||
| @@ -710,6 +710,19 @@ namespace Discord.Rest | |||||
| public Task<RestCategoryChannel> CreateCategoryChannelAsync(string name, Action<GuildChannelProperties> func = null, RequestOptions options = null) | public Task<RestCategoryChannel> CreateCategoryChannelAsync(string name, Action<GuildChannelProperties> func = null, RequestOptions options = null) | ||||
| => GuildHelper.CreateCategoryChannelAsync(this, Discord, name, options, func); | => GuildHelper.CreateCategoryChannelAsync(this, Discord, name, options, func); | ||||
| /// <summary> | |||||
| /// Creates a category channel with the provided name. | |||||
| /// </summary> | |||||
| /// <param name="name">The name of the new 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> | |||||
| /// <exception cref="ArgumentNullException"><paramref name="name" /> is <see langword="null"/>.</exception> | |||||
| /// <returns> | |||||
| /// The created category channel. | |||||
| /// </returns> | |||||
| public Task<RestForumChannel> CreateForumChannelAsync(string name, Action<ForumChannelProperties> func = null, RequestOptions options = null) | |||||
| => GuildHelper.CreateForumChannelAsync(this, Discord, name, options, func); | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets a collection of all the voice regions this guild can access. | /// Gets a collection of all the voice regions this guild can access. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -1370,6 +1383,9 @@ namespace Discord.Rest | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| async Task<ICategoryChannel> IGuild.CreateCategoryAsync(string name, Action<GuildChannelProperties> func, RequestOptions options) | async Task<ICategoryChannel> IGuild.CreateCategoryAsync(string name, Action<GuildChannelProperties> func, RequestOptions options) | ||||
| => await CreateCategoryChannelAsync(name, func, options).ConfigureAwait(false); | => await CreateCategoryChannelAsync(name, func, options).ConfigureAwait(false); | ||||
| /// <inheritdoc /> | |||||
| async Task<IForumChannel> IGuild.CreateForumChannelAsync(string name, Action<ForumChannelProperties> func, RequestOptions options) | |||||
| => await CreateForumChannelAsync(name, func, options).ConfigureAwait(false); | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| async Task<IReadOnlyCollection<IVoiceRegion>> IGuild.GetVoiceRegionsAsync(RequestOptions options) | async Task<IReadOnlyCollection<IVoiceRegion>> IGuild.GetVoiceRegionsAsync(RequestOptions options) | ||||
| @@ -352,10 +352,6 @@ namespace Discord.WebSocket | |||||
| if (guild.IsAvailable) | if (guild.IsAvailable) | ||||
| await GuildUnavailableAsync(guild).ConfigureAwait(false); | await GuildUnavailableAsync(guild).ConfigureAwait(false); | ||||
| } | } | ||||
| _sessionId = null; | |||||
| _lastSeq = 0; | |||||
| ApiClient.ResumeGatewayUrl = null; | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| @@ -906,7 +902,7 @@ namespace Discord.WebSocket | |||||
| var activities = _activity.IsSpecified ? ImmutableList.Create(_activity.Value) : null; | var activities = _activity.IsSpecified ? ImmutableList.Create(_activity.Value) : null; | ||||
| currentUser.Presence = new SocketPresence(Status, null, activities); | currentUser.Presence = new SocketPresence(Status, null, activities); | ||||
| ApiClient.CurrentUserId = currentUser.Id; | ApiClient.CurrentUserId = currentUser.Id; | ||||
| ApiClient.CurrentApplicationId = data.Application.Id; | |||||
| ApiClient.CurrentApplicationId = data.Application?.Id; | |||||
| Rest.CurrentUser = RestSelfUser.Create(this, data.User); | Rest.CurrentUser = RestSelfUser.Create(this, data.User); | ||||
| int unavailableGuilds = 0; | int unavailableGuilds = 0; | ||||
| for (int i = 0; i < data.Guilds.Length; i++) | for (int i = 0; i < data.Guilds.Length; i++) | ||||
| @@ -27,9 +27,33 @@ namespace Discord.WebSocket | |||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||
| public IReadOnlyCollection<ForumTag> Tags { get; private set; } | public IReadOnlyCollection<ForumTag> Tags { get; private set; } | ||||
| /// <inheritdoc/> | |||||
| public int ThreadCreationInterval { get; private set; } | |||||
| /// <inheritdoc/> | |||||
| public int DefaultSlowModeInterval { get; private set; } | |||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||
| public string Mention => MentionUtils.MentionChannel(Id); | public string Mention => MentionUtils.MentionChannel(Id); | ||||
| /// <inheritdoc/> | |||||
| public ulong? CategoryId { get; private set; } | |||||
| /// <inheritdoc/> | |||||
| public IEmote DefaultReactionEmoji { get; private set; } | |||||
| /// <inheritdoc/> | |||||
| public ForumSortOrder? DefaultSortOrder { get; private set; } | |||||
| /// <summary> | |||||
| /// Gets the parent (category) of this channel in the guild's channel list. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// An <see cref="ICategoryChannel"/> representing the parent of this channel; <c>null</c> if none is set. | |||||
| /// </returns> | |||||
| public ICategoryChannel Category | |||||
| => CategoryId.HasValue ? Guild.GetChannel(CategoryId.Value) as ICategoryChannel : null; | |||||
| internal SocketForumChannel(DiscordSocketClient discord, ulong id, SocketGuild guild) : base(discord, id, guild) { } | internal SocketForumChannel(DiscordSocketClient discord, ulong id, SocketGuild guild) : base(discord, id, guild) { } | ||||
| internal new static SocketForumChannel Create(SocketGuild guild, ClientState state, Model model) | internal new static SocketForumChannel Create(SocketGuild guild, ClientState state, Model model) | ||||
| @@ -46,46 +70,70 @@ namespace Discord.WebSocket | |||||
| Topic = model.Topic.GetValueOrDefault(); | Topic = model.Topic.GetValueOrDefault(); | ||||
| DefaultAutoArchiveDuration = model.AutoArchiveDuration.GetValueOrDefault(ThreadArchiveDuration.OneDay); | DefaultAutoArchiveDuration = model.AutoArchiveDuration.GetValueOrDefault(ThreadArchiveDuration.OneDay); | ||||
| if (model.ThreadRateLimitPerUser.IsSpecified) | |||||
| DefaultSlowModeInterval = model.ThreadRateLimitPerUser.Value; | |||||
| if (model.SlowMode.IsSpecified) | |||||
| ThreadCreationInterval = model.SlowMode.Value; | |||||
| DefaultSortOrder = model.DefaultSortOrder.GetValueOrDefault(); | |||||
| Tags = model.ForumTags.GetValueOrDefault(Array.Empty<API.ForumTags>()).Select( | Tags = model.ForumTags.GetValueOrDefault(Array.Empty<API.ForumTags>()).Select( | ||||
| x => new ForumTag(x.Id, x.Name, x.EmojiId.GetValueOrDefault(null), x.EmojiName.GetValueOrDefault()) | |||||
| x => new ForumTag(x.Id, x.Name, x.EmojiId.GetValueOrDefault(null), x.EmojiName.GetValueOrDefault(), x.Moderated) | |||||
| ).ToImmutableArray(); | ).ToImmutableArray(); | ||||
| if (model.DefaultReactionEmoji.IsSpecified && model.DefaultReactionEmoji.Value is not null) | |||||
| { | |||||
| if (model.DefaultReactionEmoji.Value.EmojiId.HasValue && model.DefaultReactionEmoji.Value.EmojiId.Value != 0) | |||||
| DefaultReactionEmoji = new Emote(model.DefaultReactionEmoji.Value.EmojiId.GetValueOrDefault(), null, false); | |||||
| else if (model.DefaultReactionEmoji.Value.EmojiName.IsSpecified) | |||||
| DefaultReactionEmoji = new Emoji(model.DefaultReactionEmoji.Value.EmojiName.Value); | |||||
| else | |||||
| DefaultReactionEmoji = null; | |||||
| } | |||||
| CategoryId = model.CategoryId.GetValueOrDefault(); | |||||
| } | } | ||||
| /// <inheritdoc cref="IForumChannel.CreatePostAsync(string, ThreadArchiveDuration, int?, string, Embed, RequestOptions, AllowedMentions, MessageComponent, ISticker[], Embed[], MessageFlags)"/> | |||||
| public Task<RestThreadChannel> CreatePostAsync(string title, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay, int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) | |||||
| => ThreadHelper.CreatePostAsync(this, Discord, title, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags); | |||||
| /// <inheritdoc /> | |||||
| public virtual Task ModifyAsync(Action<ForumChannelProperties> func, RequestOptions options = null) | |||||
| => ForumHelper.ModifyAsync(this, Discord, func, options); | |||||
| /// <inheritdoc cref="IForumChannel.CreatePostAsync(string, ThreadArchiveDuration, int?, string, Embed, RequestOptions, AllowedMentions, MessageComponent, ISticker[], Embed[], MessageFlags, ForumTag[])"/> | |||||
| public Task<RestThreadChannel> CreatePostAsync(string title, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay, int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None, ForumTag[] tags = null) | |||||
| => ThreadHelper.CreatePostAsync(this, Discord, title, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags, tags?.Select(tag => tag.Id).ToArray()); | |||||
| /// <inheritdoc cref="IForumChannel.CreatePostWithFileAsync(string, string, ThreadArchiveDuration, int?, string, Embed, RequestOptions, bool, AllowedMentions, MessageComponent, ISticker[], Embed[], MessageFlags)"/> | |||||
| /// <inheritdoc cref="IForumChannel.CreatePostWithFileAsync(string, string, ThreadArchiveDuration, int?, string, Embed, RequestOptions, bool, AllowedMentions, MessageComponent, ISticker[], Embed[], MessageFlags, ForumTag[])"/> | |||||
| public async Task<RestThreadChannel> CreatePostWithFileAsync(string title, string filePath, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay, | public async Task<RestThreadChannel> CreatePostWithFileAsync(string title, string filePath, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay, | ||||
| int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, | int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, | ||||
| AllowedMentions allowedMentions = null, MessageComponent components = null, | AllowedMentions allowedMentions = null, MessageComponent components = null, | ||||
| ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) | |||||
| ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None, ForumTag[] tags = null) | |||||
| { | { | ||||
| using var file = new FileAttachment(filePath, isSpoiler: isSpoiler); | using var file = new FileAttachment(filePath, isSpoiler: isSpoiler); | ||||
| return await ThreadHelper.CreatePostAsync(this, Discord, title, new FileAttachment[] { file }, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false); | |||||
| return await ThreadHelper.CreatePostAsync(this, Discord, title, new FileAttachment[] { file }, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags, tags?.Select(tag => tag.Id).ToArray()).ConfigureAwait(false); | |||||
| } | } | ||||
| /// <inheritdoc cref="IForumChannel.CreatePostWithFileAsync(string, Stream, string, ThreadArchiveDuration, int?, string, Embed, RequestOptions, bool, AllowedMentions, MessageComponent, ISticker[], Embed[], MessageFlags)"/> | |||||
| /// <inheritdoc cref="IForumChannel.CreatePostWithFileAsync(string, Stream, string, ThreadArchiveDuration, int?, string, Embed, RequestOptions, bool, AllowedMentions, MessageComponent, ISticker[], Embed[], MessageFlags, ForumTag[])"/> | |||||
| public async Task<RestThreadChannel> CreatePostWithFileAsync(string title, Stream stream, string filename, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay, | public async Task<RestThreadChannel> CreatePostWithFileAsync(string title, Stream stream, string filename, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay, | ||||
| int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, | int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, | ||||
| AllowedMentions allowedMentions = null, MessageComponent components = null, | AllowedMentions allowedMentions = null, MessageComponent components = null, | ||||
| ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) | |||||
| ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None, ForumTag[] tags = null) | |||||
| { | { | ||||
| using var file = new FileAttachment(stream, filename, isSpoiler: isSpoiler); | using var file = new FileAttachment(stream, filename, isSpoiler: isSpoiler); | ||||
| return await ThreadHelper.CreatePostAsync(this, Discord, title, new FileAttachment[] { file }, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false); | |||||
| return await ThreadHelper.CreatePostAsync(this, Discord, title, new FileAttachment[] { file }, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags, tags?.Select(tag => tag.Id).ToArray()).ConfigureAwait(false); | |||||
| } | } | ||||
| /// <inheritdoc cref="IForumChannel.CreatePostWithFileAsync(string, FileAttachment, ThreadArchiveDuration, int?, string, Embed, RequestOptions, AllowedMentions, MessageComponent, ISticker[], Embed[], MessageFlags)"/> | |||||
| /// <inheritdoc cref="IForumChannel.CreatePostWithFileAsync(string, FileAttachment, ThreadArchiveDuration, int?, string, Embed, RequestOptions, AllowedMentions, MessageComponent, ISticker[], Embed[], MessageFlags, ForumTag[])"/> | |||||
| public Task<RestThreadChannel> CreatePostWithFileAsync(string title, FileAttachment attachment, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay, | public Task<RestThreadChannel> CreatePostWithFileAsync(string title, FileAttachment attachment, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay, | ||||
| int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, | int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, | ||||
| MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) | |||||
| => ThreadHelper.CreatePostAsync(this, Discord, title, new FileAttachment[] { attachment }, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags); | |||||
| MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None, ForumTag[] tags = null) | |||||
| => ThreadHelper.CreatePostAsync(this, Discord, title, new FileAttachment[] { attachment }, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags, tags?.Select(tag => tag.Id).ToArray()); | |||||
| /// <inheritdoc cref="IForumChannel.CreatePostWithFilesAsync(string, IEnumerable{FileAttachment}, ThreadArchiveDuration, int?, string, Embed, RequestOptions, AllowedMentions, MessageComponent, ISticker[], Embed[], MessageFlags)"/> | |||||
| /// <inheritdoc cref="IForumChannel.CreatePostWithFilesAsync(string, IEnumerable{FileAttachment}, ThreadArchiveDuration, int?, string, Embed, RequestOptions, AllowedMentions, MessageComponent, ISticker[], Embed[], MessageFlags, ForumTag[])"/> | |||||
| public Task<RestThreadChannel> CreatePostWithFilesAsync(string title, IEnumerable<FileAttachment> attachments, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay, | public Task<RestThreadChannel> CreatePostWithFilesAsync(string title, IEnumerable<FileAttachment> attachments, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay, | ||||
| int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, | int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, | ||||
| MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) | |||||
| => ThreadHelper.CreatePostAsync(this, Discord, title, attachments, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags); | |||||
| MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None, ForumTag[] tags = null) | |||||
| => ThreadHelper.CreatePostAsync(this, Discord, title, attachments, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags, tags?.Select(tag => tag.Id).ToArray()); | |||||
| /// <inheritdoc cref="IForumChannel.GetActiveThreadsAsync(RequestOptions)"/> | /// <inheritdoc cref="IForumChannel.GetActiveThreadsAsync(RequestOptions)"/> | ||||
| public Task<IReadOnlyCollection<RestThreadChannel>> GetActiveThreadsAsync(RequestOptions options = null) | public Task<IReadOnlyCollection<RestThreadChannel>> GetActiveThreadsAsync(RequestOptions options = null) | ||||
| @@ -112,17 +160,41 @@ namespace Discord.WebSocket | |||||
| => await GetPrivateArchivedThreadsAsync(limit, before, options).ConfigureAwait(false); | => await GetPrivateArchivedThreadsAsync(limit, before, options).ConfigureAwait(false); | ||||
| async Task<IReadOnlyCollection<IThreadChannel>> IForumChannel.GetJoinedPrivateArchivedThreadsAsync(int? limit, DateTimeOffset? before, RequestOptions options) | async Task<IReadOnlyCollection<IThreadChannel>> IForumChannel.GetJoinedPrivateArchivedThreadsAsync(int? limit, DateTimeOffset? before, RequestOptions options) | ||||
| => await GetJoinedPrivateArchivedThreadsAsync(limit, before, options).ConfigureAwait(false); | => await GetJoinedPrivateArchivedThreadsAsync(limit, before, options).ConfigureAwait(false); | ||||
| async Task<IThreadChannel> IForumChannel.CreatePostAsync(string title, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags) | |||||
| async Task<IThreadChannel> IForumChannel.CreatePostAsync(string title, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags, ForumTag[] tags) | |||||
| => await CreatePostAsync(title, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false); | => await CreatePostAsync(title, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false); | ||||
| async Task<IThreadChannel> IForumChannel.CreatePostWithFileAsync(string title, string filePath, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags) | |||||
| async Task<IThreadChannel> IForumChannel.CreatePostWithFileAsync(string title, string filePath, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags, ForumTag[] tags) | |||||
| => await CreatePostWithFileAsync(title, filePath, archiveDuration, slowmode, text, embed, options, isSpoiler, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false); | => await CreatePostWithFileAsync(title, filePath, archiveDuration, slowmode, text, embed, options, isSpoiler, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false); | ||||
| async Task<IThreadChannel> IForumChannel.CreatePostWithFileAsync(string title, Stream stream, string filename, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags) | |||||
| async Task<IThreadChannel> IForumChannel.CreatePostWithFileAsync(string title, Stream stream, string filename, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags, ForumTag[] tags) | |||||
| => await CreatePostWithFileAsync(title, stream, filename, archiveDuration, slowmode, text, embed, options, isSpoiler, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false); | => await CreatePostWithFileAsync(title, stream, filename, archiveDuration, slowmode, text, embed, options, isSpoiler, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false); | ||||
| async Task<IThreadChannel> IForumChannel.CreatePostWithFileAsync(string title, FileAttachment attachment, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags) | |||||
| async Task<IThreadChannel> IForumChannel.CreatePostWithFileAsync(string title, FileAttachment attachment, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags, ForumTag[] tags) | |||||
| => await CreatePostWithFileAsync(title, attachment, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false); | => await CreatePostWithFileAsync(title, attachment, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags).ConfigureAwait(false); | ||||
| async Task<IThreadChannel> IForumChannel.CreatePostWithFilesAsync(string title, IEnumerable<FileAttachment> attachments, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags) | |||||
| async Task<IThreadChannel> IForumChannel.CreatePostWithFilesAsync(string title, IEnumerable<FileAttachment> attachments, ThreadArchiveDuration archiveDuration, int? slowmode, string text, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageComponent components, ISticker[] stickers, Embed[] embeds, MessageFlags flags, ForumTag[] tags) | |||||
| => await CreatePostWithFilesAsync(title, attachments, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags); | => await CreatePostWithFilesAsync(title, attachments, archiveDuration, slowmode, text, embed, options, allowedMentions, components, stickers, embeds, flags); | ||||
| #endregion | #endregion | ||||
| #region INestedChannel | |||||
| /// <inheritdoc /> | |||||
| 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); | |||||
| public virtual async Task<IInviteMetadata> CreateInviteToApplicationAsync(ulong applicationId, int? maxAge = 86400, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null) | |||||
| => await ChannelHelper.CreateInviteToApplicationAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, applicationId, options); | |||||
| /// <inheritdoc /> | |||||
| public virtual async Task<IInviteMetadata> CreateInviteToApplicationAsync(DefaultApplications application, int? maxAge = 86400, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null) | |||||
| => await ChannelHelper.CreateInviteToApplicationAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, (ulong)application, options); | |||||
| public virtual Task<IInviteMetadata> CreateInviteToStreamAsync(IUser user, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null) | |||||
| => throw new NotImplementedException(); | |||||
| /// <inheritdoc /> | |||||
| public virtual async Task<IReadOnlyCollection<IInviteMetadata>> GetInvitesAsync(RequestOptions options = null) | |||||
| => await ChannelHelper.GetInvitesAsync(this, Discord, options).ConfigureAwait(false); | |||||
| /// <inheritdoc /> | |||||
| Task<ICategoryChannel> INestedChannel.GetCategoryAsync(CacheMode mode, RequestOptions options) | |||||
| => Task.FromResult(Category); | |||||
| /// <inheritdoc /> | |||||
| public virtual Task SyncPermissionsAsync(RequestOptions options = null) | |||||
| => ChannelHelper.SyncPermissionsAsync(this, Discord, options); | |||||
| #endregion | |||||
| } | } | ||||
| } | } | ||||
| @@ -30,6 +30,9 @@ namespace Discord.WebSocket | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public int Position { get; private set; } | public int Position { get; private set; } | ||||
| /// <inheritdoc /> | |||||
| public ChannelFlags Flags { get; private set; } | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public virtual IReadOnlyCollection<Overwrite> PermissionOverwrites => _overwrites; | public virtual IReadOnlyCollection<Overwrite> PermissionOverwrites => _overwrites; | ||||
| /// <summary> | /// <summary> | ||||
| @@ -74,6 +77,8 @@ namespace Discord.WebSocket | |||||
| for (int i = 0; i < overwrites.Length; i++) | for (int i = 0; i < overwrites.Length; i++) | ||||
| newOverwrites.Add(overwrites[i].ToEntity()); | newOverwrites.Add(overwrites[i].ToEntity()); | ||||
| _overwrites = newOverwrites.ToImmutable(); | _overwrites = newOverwrites.ToImmutable(); | ||||
| Flags = model.Flags.GetValueOrDefault(ChannelFlags.None); | |||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| @@ -89,6 +89,9 @@ namespace Discord.WebSocket | |||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||
| public bool? IsInvitable { get; private set; } | public bool? IsInvitable { get; private set; } | ||||
| /// <inheritdoc/> | |||||
| public IReadOnlyCollection<ulong> AppliedTags { get; private set; } | |||||
| /// <inheritdoc cref="IThreadChannel.CreatedAt"/> | /// <inheritdoc cref="IThreadChannel.CreatedAt"/> | ||||
| public override DateTimeOffset CreatedAt { get; } | public override DateTimeOffset CreatedAt { get; } | ||||
| @@ -149,6 +152,8 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| HasJoined = model.ThreadMember.IsSpecified; | HasJoined = model.ThreadMember.IsSpecified; | ||||
| AppliedTags = model.AppliedTags.GetValueOrDefault(Array.Empty<ulong>()).ToImmutableArray(); | |||||
| } | } | ||||
| internal IReadOnlyCollection<SocketThreadUser> RemoveUsers(ulong[] users) | internal IReadOnlyCollection<SocketThreadUser> RemoveUsers(ulong[] users) | ||||
| @@ -334,12 +339,13 @@ namespace Discord.WebSocket | |||||
| => throw new NotSupportedException("This method is not supported in threads."); | => throw new NotSupportedException("This method is not supported in threads."); | ||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||
| /// <remarks> | |||||
| /// <b>This method is not supported in threads.</b> | |||||
| /// </remarks> | |||||
| public override Task ModifyAsync(Action<TextChannelProperties> func, RequestOptions options = null) | public override Task ModifyAsync(Action<TextChannelProperties> func, RequestOptions options = null) | ||||
| => ThreadHelper.ModifyAsync(this, Discord, func, options); | => ThreadHelper.ModifyAsync(this, Discord, func, options); | ||||
| /// <inheritdoc/> | |||||
| public Task ModifyAsync(Action<ThreadChannelProperties> func, RequestOptions options = null) | |||||
| => ThreadHelper.ModifyAsync(this, Discord, func, options); | |||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||
| /// <remarks> | /// <remarks> | ||||
| /// <b>This method is not supported in threads.</b> | /// <b>This method is not supported in threads.</b> | ||||
| @@ -302,6 +302,16 @@ namespace Discord.WebSocket | |||||
| /// </returns> | /// </returns> | ||||
| public IReadOnlyCollection<SocketThreadChannel> ThreadChannels | public IReadOnlyCollection<SocketThreadChannel> ThreadChannels | ||||
| => Channels.OfType<SocketThreadChannel>().ToImmutableArray(); | => Channels.OfType<SocketThreadChannel>().ToImmutableArray(); | ||||
| /// <summary> | |||||
| /// Gets a collection of all forum channels in this guild. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// A read-only collection of forum channels found within this guild. | |||||
| /// </returns> | |||||
| public IReadOnlyCollection<SocketForumChannel> ForumChannels | |||||
| => Channels.OfType<SocketForumChannel>().ToImmutableArray(); | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets the current logged-in user. | /// Gets the current logged-in user. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -790,6 +800,7 @@ namespace Discord.WebSocket | |||||
| /// </returns> | /// </returns> | ||||
| public Task<RestStageChannel> CreateStageChannelAsync(string name, Action<VoiceChannelProperties> func = null, RequestOptions options = null) | public Task<RestStageChannel> CreateStageChannelAsync(string name, Action<VoiceChannelProperties> func = null, RequestOptions options = null) | ||||
| => GuildHelper.CreateStageChannelAsync(this, Discord, name, options, func); | => GuildHelper.CreateStageChannelAsync(this, Discord, name, options, func); | ||||
| /// <summary> | /// <summary> | ||||
| /// Creates a new channel category in this guild. | /// Creates a new channel category in this guild. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -804,6 +815,20 @@ namespace Discord.WebSocket | |||||
| public Task<RestCategoryChannel> CreateCategoryChannelAsync(string name, Action<GuildChannelProperties> func = null, RequestOptions options = null) | public Task<RestCategoryChannel> CreateCategoryChannelAsync(string name, Action<GuildChannelProperties> func = null, RequestOptions options = null) | ||||
| => GuildHelper.CreateCategoryChannelAsync(this, Discord, name, options, func); | => GuildHelper.CreateCategoryChannelAsync(this, Discord, name, options, func); | ||||
| /// <summary> | |||||
| /// Creates a new channel forum in this guild. | |||||
| /// </summary> | |||||
| /// <param name="name">The new name for the forum.</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> | |||||
| /// <exception cref="ArgumentNullException"><paramref name="name"/> is <see langword="null"/>.</exception> | |||||
| /// <returns> | |||||
| /// A task that represents the asynchronous creation operation. The task result contains the newly created | |||||
| /// forum channel. | |||||
| /// </returns> | |||||
| public Task<RestForumChannel> CreateForumChannelAsync(string name, Action<ForumChannelProperties> func = null, RequestOptions options = null) | |||||
| => GuildHelper.CreateForumChannelAsync(this, Discord, name, options, func); | |||||
| internal SocketGuildChannel AddChannel(ClientState state, ChannelModel model) | internal SocketGuildChannel AddChannel(ClientState state, ChannelModel model) | ||||
| { | { | ||||
| var channel = SocketGuildChannel.Create(this, state, model); | var channel = SocketGuildChannel.Create(this, state, model); | ||||
| @@ -1897,6 +1922,9 @@ namespace Discord.WebSocket | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| async Task<ICategoryChannel> IGuild.CreateCategoryAsync(string name, Action<GuildChannelProperties> func, RequestOptions options) | async Task<ICategoryChannel> IGuild.CreateCategoryAsync(string name, Action<GuildChannelProperties> func, RequestOptions options) | ||||
| => await CreateCategoryChannelAsync(name, func, options).ConfigureAwait(false); | => await CreateCategoryChannelAsync(name, func, options).ConfigureAwait(false); | ||||
| /// <inheritdoc /> | |||||
| async Task<IForumChannel> IGuild.CreateForumChannelAsync(string name, Action<ForumChannelProperties> func, RequestOptions options) | |||||
| => await CreateForumChannelAsync(name, func, options).ConfigureAwait(false); | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| async Task<IReadOnlyCollection<IVoiceRegion>> IGuild.GetVoiceRegionsAsync(RequestOptions options) | async Task<IReadOnlyCollection<IVoiceRegion>> IGuild.GetVoiceRegionsAsync(RequestOptions options) | ||||
| @@ -9,9 +9,9 @@ | |||||
| <description>An asynchronous API wrapper for Discord. This metapackage includes all of the optional Discord.Net components.</description> | <description>An asynchronous API wrapper for Discord. This metapackage includes all of the optional Discord.Net components.</description> | ||||
| <tags>discord;discordapp</tags> | <tags>discord;discordapp</tags> | ||||
| <projectUrl>https://github.com/discord-net/Discord.Net</projectUrl> | <projectUrl>https://github.com/discord-net/Discord.Net</projectUrl> | ||||
| <licenseUrl>http://opensource.org/licenses/MIT</licenseUrl> | |||||
| <license type="expression">MIT</license> | |||||
| <requireLicenseAcceptance>false</requireLicenseAcceptance> | <requireLicenseAcceptance>false</requireLicenseAcceptance> | ||||
| <iconUrl>https://github.com/discord-net/Discord.Net/raw/dev/docs/marketing/logo/PackageLogo.png</iconUrl> | |||||
| <icon>PackageLogo.png</icon> | |||||
| <dependencies> | <dependencies> | ||||
| <group targetFramework="net6.0"> | <group targetFramework="net6.0"> | ||||
| <dependency id="Discord.Net.Core" version="3.9.0$suffix$" /> | <dependency id="Discord.Net.Core" version="3.9.0$suffix$" /> | ||||
| @@ -55,4 +55,7 @@ | |||||
| </group> | </group> | ||||
| </dependencies> | </dependencies> | ||||
| </metadata> | </metadata> | ||||
| <files> | |||||
| <file src="../../docs/marketing/logo/PackageLogo.png" /> | |||||
| </files> | |||||
| </package> | </package> | ||||
| @@ -20,6 +20,8 @@ namespace Discord | |||||
| public DateTimeOffset CreatedAt => throw new NotImplementedException(); | public DateTimeOffset CreatedAt => throw new NotImplementedException(); | ||||
| public ulong Id => throw new NotImplementedException(); | public ulong Id => throw new NotImplementedException(); | ||||
| public ChannelFlags Flags => throw new NotImplementedException(); | |||||
| public Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, RequestOptions options = null) | public Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, RequestOptions options = null) | ||||
| { | { | ||||
| @@ -34,6 +34,8 @@ namespace Discord | |||||
| public ulong Id => throw new NotImplementedException(); | public ulong Id => throw new NotImplementedException(); | ||||
| public ChannelFlags Flags => throw new NotImplementedException(); | |||||
| public Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, RequestOptions options = null) | public Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, RequestOptions options = null) | ||||
| { | { | ||||
| throw new NotImplementedException(); | throw new NotImplementedException(); | ||||
| @@ -36,6 +36,8 @@ namespace Discord | |||||
| public string Mention => throw new NotImplementedException(); | public string Mention => throw new NotImplementedException(); | ||||
| public ChannelFlags Flags => throw new NotImplementedException(); | |||||
| public Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, RequestOptions options = null) => throw new NotImplementedException(); | public Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, RequestOptions options = null) => throw new NotImplementedException(); | ||||
| public Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions, RequestOptions options = null) => throw new NotImplementedException(); | public Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions, RequestOptions options = null) => throw new NotImplementedException(); | ||||
| public Task<IAudioClient> ConnectAsync(bool selfDeaf = false, bool selfMute = false, bool external = false) => throw new NotImplementedException(); | public Task<IAudioClient> ConnectAsync(bool selfDeaf = false, bool selfMute = false, bool external = false) => throw new NotImplementedException(); | ||||