diff --git a/src/Discord.Net.Core/Entities/Channels/ChannelFlags.cs b/src/Discord.Net.Core/Entities/Channels/ChannelFlags.cs new file mode 100644 index 000000000..37f34a90e --- /dev/null +++ b/src/Discord.Net.Core/Entities/Channels/ChannelFlags.cs @@ -0,0 +1,22 @@ +namespace Discord; + +/// +/// Represents public flags for a channel. +/// +public enum ChannelFlags +{ + /// + /// Default value for flags, when none are given to a channel. + /// + None = 0, + + /// + /// Flag given to a thread channel pinned on top of parent forum channel. + /// + Pinned = 1 << 1, + + /// + /// Flag given to a forum channel that requires people to select tags when posting. + /// + RequireTag = 1 << 4 +} diff --git a/src/Discord.Net.Core/Entities/Channels/ForumChannelProperties.cs b/src/Discord.Net.Core/Entities/Channels/ForumChannelProperties.cs new file mode 100644 index 000000000..243f18cb5 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Channels/ForumChannelProperties.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; + +namespace Discord; + +public class ForumChannelProperties : TextChannelProperties +{ + + /// + /// Gets or sets the topic of the channel. + /// + /// + /// Not available in forum channels. + /// + public new Optional SlowModeInterval { get; } + + /// + /// Gets or sets rate limit on creating posts in this forum channel. + /// + /// + /// Setting this value to anything above zero will require each user to wait X seconds before + /// creating another thread; setting this value to 0 will disable rate limits for this channel. + /// + /// Users with or + /// will be exempt from rate limits. + /// + /// + /// Thrown if the value does not fall within [0, 21600]. + public Optional ThreadCreationInterval { get; set; } + + + /// + /// Gets or sets the default slow-mode for threads in this channel. + /// + /// + /// Setting this value to anything above zero will require each user to wait X seconds before + /// sending another message; setting this value to 0 will disable slow-mode for child threads. + /// + /// Users with or + /// will be exempt from slow-mode. + /// + /// + /// Thrown if the value does not fall within [0, 21600]. + public Optional DefaultSlowModeInterval { get; set; } + + /// + /// Gets or sets a collection of tags inside of this forum channel. + /// + public Optional> Tags { get; set; } +} diff --git a/src/Discord.Net.Core/Entities/Channels/GuildChannelProperties.cs b/src/Discord.Net.Core/Entities/Channels/GuildChannelProperties.cs index 339d6fffd..1e7d69c2d 100644 --- a/src/Discord.Net.Core/Entities/Channels/GuildChannelProperties.cs +++ b/src/Discord.Net.Core/Entities/Channels/GuildChannelProperties.cs @@ -36,5 +36,10 @@ namespace Discord /// Gets or sets the permission overwrites for this channel. /// public Optional> PermissionOverwrites { get; set; } + + /// + /// Gets or sets the flags of the channel. + /// + public Optional Flags { get; set; } } } diff --git a/src/Discord.Net.Core/Entities/Channels/IForumChannel.cs b/src/Discord.Net.Core/Entities/Channels/IForumChannel.cs index fb6211615..6e1da6e96 100644 --- a/src/Discord.Net.Core/Entities/Channels/IForumChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IForumChannel.cs @@ -53,6 +53,20 @@ namespace Discord /// int DefaultSlowModeInterval { get; } + /// + /// Modifies this forum channel. + /// + /// + /// 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 . + /// + /// The delegate containing the properties to modify the channel with. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous modification operation. + /// + Task ModifyAsync(Action func, RequestOptions options = null); + /// /// Creates a new post (thread) within the forum. /// diff --git a/src/Discord.Net.Core/Entities/ForumTag.cs b/src/Discord.Net.Core/Entities/ForumTags/ForumTag.cs similarity index 74% rename from src/Discord.Net.Core/Entities/ForumTag.cs rename to src/Discord.Net.Core/Entities/ForumTags/ForumTag.cs index aef983555..5b962294a 100644 --- a/src/Discord.Net.Core/Entities/ForumTag.cs +++ b/src/Discord.Net.Core/Entities/ForumTags/ForumTag.cs @@ -4,6 +4,8 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +#nullable enable + namespace Discord { /// @@ -24,23 +26,27 @@ namespace Discord /// /// Gets the emoji of the tag or if none is set. /// - public IEmote Emoji { get; } + /// + /// If the emoji is only the will be populated. + /// Use to get the emoji. + /// + public IEmote? Emoji { get; } /// /// Gets whether this tag can only be added to or removed from threads by a member /// with the permission /// - public bool Moderated { get; } + public bool IsModerated { get; } /// /// Gets when the tag was created. /// public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); - internal ForumTag(ulong id, string name, ulong? emojiId, string emojiName, bool moderated) + internal ForumTag(ulong id, string name, ulong? emojiId, string? emojiName, bool moderated) { if (emojiId.HasValue && emojiId.Value != 0) - Emoji = new Emote(emojiId.Value, emojiName, false); + Emoji = new Emote(emojiId.Value, null, false); else if (emojiName != null) Emoji = new Emoji(name); else @@ -48,7 +54,7 @@ namespace Discord Id = id; Name = name; - Moderated = moderated; + IsModerated = moderated; } } } diff --git a/src/Discord.Net.Core/Entities/ForumTags/ForumTagBuilder.cs b/src/Discord.Net.Core/Entities/ForumTags/ForumTagBuilder.cs new file mode 100644 index 000000000..19d0aa639 --- /dev/null +++ b/src/Discord.Net.Core/Entities/ForumTags/ForumTagBuilder.cs @@ -0,0 +1,128 @@ +#nullable enable +using System; + +namespace Discord; + +public class ForumTagBuilder +{ + private string? _name; + private IEmote? _emoji; + private bool _moderated; + + /// + /// Returns the maximum length of name allowed by Discord. + /// + public const int MaxNameLength = 20; + + /// + /// Gets or sets the name of the tag. + /// + /// Name length must be less than or equal to . + 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; + } + } + + /// + /// Gets or sets the emoji of the tag. + /// + public IEmote? Emoji + { + get { return _emoji; } + set { _emoji = value; } + } + + /// + /// Gets or sets whether this tag can only be added to or removed from threads by a member + /// with the permission + /// + public bool IsModerated + { + get { return _moderated; } + set { _moderated = value; } + } + + /// + /// Initializes a new class. + /// + public ForumTagBuilder() + { + + } + + /// + /// Initializes a new class with values + /// + public ForumTagBuilder(string name) + { + Name = name; + IsModerated = false; + } + + /// + /// Initializes a new class with values + /// + public ForumTagBuilder(string name, IEmote? emoji = null, bool moderated = false) + { + Name = name; + Emoji = emoji; + IsModerated = moderated; + } + + /// + /// Initializes a new class with values + /// + public ForumTagBuilder(string name, ulong? emoteId = null, bool moderated = false) + { + Name = name; + if(emoteId is not null) + Emoji = new Emote(emoteId.Value, string.Empty, false); + IsModerated = moderated; + } + + /// + /// Builds the Tag. + /// + /// An instance of + /// "Name must be set to build the tag" + 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); + } + + /// + /// Sets the name of the tag. + /// + /// Name length must be less than or equal to . + public ForumTagBuilder WithName(string name) + { + Name = name; + return this; + } + + /// + /// Sets the emoji of the tag. + /// + public ForumTagBuilder WithEmoji(IEmote? emoji) + { + Emoji = emoji; + return this; + } + + /// + /// Sets the IsModerated of the tag. + /// + public ForumTagBuilder WithModerated(bool moderated) + { + IsModerated = moderated; + return this; + } +} diff --git a/src/Discord.Net.Core/Entities/ForumTags/ForumTagBuilderExtensions.cs b/src/Discord.Net.Core/Entities/ForumTags/ForumTagBuilderExtensions.cs new file mode 100644 index 000000000..5bf7d2b85 --- /dev/null +++ b/src/Discord.Net.Core/Entities/ForumTags/ForumTagBuilderExtensions.cs @@ -0,0 +1,11 @@ +namespace Discord; + +public static class ForumTagBuilderExtensions +{ + public static ForumTagBuilder ToForumTagBuilder(this ForumTag tag) + => new ForumTagBuilder(tag.Name, tag.Emoji, tag.IsModerated); + + public static ForumTagBuilder ToForumTagBuilder(this ForumTagProperties tag) + => new ForumTagBuilder(tag.Name, tag.Emoji, tag.IsModerated); + +} diff --git a/src/Discord.Net.Core/Entities/ForumTags/ForumTagProperties.cs b/src/Discord.Net.Core/Entities/ForumTags/ForumTagProperties.cs new file mode 100644 index 000000000..2770438be --- /dev/null +++ b/src/Discord.Net.Core/Entities/ForumTags/ForumTagProperties.cs @@ -0,0 +1,29 @@ +namespace Discord; + +#nullable enable + +public class ForumTagProperties +{ + /// + /// Gets the name of the tag. + /// + public string Name { get; } + + /// + /// Gets the emoji of the tag or if none is set. + /// + public IEmote? Emoji { get; } + + /// + /// Gets whether this tag can only be added to or removed from threads by a member + /// with the permission + /// + public bool IsModerated { get; } + + internal ForumTagProperties(string name, IEmote? emoji = null, bool isMmoderated = false) + { + Name = name; + Emoji = emoji; + IsModerated = isMmoderated; + } +} diff --git a/src/Discord.Net.Rest/API/Rest/ForumTagParams.cs b/src/Discord.Net.Rest/API/Rest/ForumTagParams.cs new file mode 100644 index 000000000..86f77816f --- /dev/null +++ b/src/Discord.Net.Rest/API/Rest/ForumTagParams.cs @@ -0,0 +1,21 @@ +using Newtonsoft.Json; + +namespace Discord.API +{ + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] + internal class ForumTagParams + { + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("emoji_id")] + public Optional EmojiId { get; set; } + + [JsonProperty("emoji_name")] + public Optional EmojiName { get; set; } + + [JsonProperty("moderated")] + public bool Moderated { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Rest/ModifyForumChannelParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyForumChannelParams.cs new file mode 100644 index 000000000..9d97e80fa --- /dev/null +++ b/src/Discord.Net.Rest/API/Rest/ModifyForumChannelParams.cs @@ -0,0 +1,17 @@ +using Newtonsoft.Json; + +namespace Discord.API.Rest; + + +[JsonObject(MemberSerialization = MemberSerialization.OptIn)] +internal class ModifyForumChannelParams : ModifyTextChannelParams +{ + [JsonProperty("available_tags")] + public Optional Tags { get; set; } + + [JsonProperty("default_thread_rate_limit_per_user")] + public Optional DefaultSlowModeInterval { get; set; } + + [JsonProperty("rate_limit_per_user")] + public Optional ThreadCreationInterval { get; set; } +} diff --git a/src/Discord.Net.Rest/API/Rest/ModifyGuildChannelParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyGuildChannelParams.cs index dfe9cd980..dea0c037f 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyGuildChannelParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyGuildChannelParams.cs @@ -13,5 +13,7 @@ namespace Discord.API.Rest public Optional CategoryId { get; set; } [JsonProperty("permission_overwrites")] public Optional Overwrites { get; set; } + [JsonProperty("flags")] + public Optional Flags { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/ModifyThreadParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyThreadParams.cs index 53c1f4f5f..bd651b22c 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyThreadParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyThreadParams.cs @@ -22,5 +22,8 @@ namespace Discord.API.Rest [JsonProperty("applied_tags")] public Optional> AppliedTags { get; set; } + + [JsonProperty("flags")] + public Optional Flags { get; set; } } } diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index d66fd5e51..4e353c39b 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -38,6 +38,7 @@ namespace Discord.Rest Deny = overwrite.Permissions.DenyValue.ToString() }).ToArray() : Optional.Create(), + Flags = args.Flags.GetValueOrDefault(), }; return await client.ApiClient.ModifyGuildChannelAsync(channel.Id, apiArgs, options).ConfigureAwait(false); } diff --git a/src/Discord.Net.Rest/Entities/Channels/ForumHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ForumHelper.cs new file mode 100644 index 000000000..aeb7c22a4 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Channels/ForumHelper.cs @@ -0,0 +1,49 @@ +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 ModifyAsync(IForumChannel channel, BaseDiscordClient client, + Action func, + RequestOptions options) + { + var args = new ForumChannelProperties(); + func(args); + 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(), + DefaultSlowModeInterval = args.DefaultSlowModeInterval, + ThreadCreationInterval = args.ThreadCreationInterval, + Tags = args.Tags.IsSpecified + ? args.Tags.Value.Select(tag => new API.ForumTagParams + { + Name = tag.Name, + EmojiId = tag.Emoji is Emote emote + ? emote.Id + : Optional.Unspecified, + EmojiName = tag.Emoji is Emoji emoji + ? emoji.Name + : Optional.Unspecified + }).ToArray() + : Optional.Create(), + Flags = args.Flags.GetValueOrDefault(), + Topic = args.Topic, + }; + return await client.ApiClient.ModifyGuildChannelAsync(channel.Id, apiArgs, options).ConfigureAwait(false); + } +} diff --git a/src/Discord.Net.Rest/Entities/Channels/ThreadHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ThreadHelper.cs index 574b12491..5c4ce817e 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ThreadHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ThreadHelper.cs @@ -58,7 +58,8 @@ namespace Discord.Rest AutoArchiveDuration = args.AutoArchiveDuration, Locked = args.Locked, Slowmode = args.SlowModeInterval, - AppliedTags = args.AppliedTags + AppliedTags = args.AppliedTags, + Flags = args.Flags, }; return await client.ApiClient.ModifyThreadAsync(channel.Id, apiArgs, options).ConfigureAwait(false); } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketForumChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketForumChannel.cs index aa4ccb9ae..9347d0327 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketForumChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketForumChannel.cs @@ -63,6 +63,10 @@ namespace Discord.WebSocket ).ToImmutableArray(); } + /// + public virtual Task ModifyAsync(Action func, RequestOptions options = null) + => ForumHelper.ModifyAsync(this, Discord, func, options); + /// public Task 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);