From 01ae904fe1703454699a408ae6f0a17afb929040 Mon Sep 17 00:00:00 2001
From: Misha133 <61027276+Misha-133@users.noreply.github.com>
Date: Mon, 7 Nov 2022 19:25:49 +0300
Subject: [PATCH] [Feature] Add missing properties in forum & thread channels
(#2469)
* add `AppliedTags` property
* convert collections into immutable arrays
* remove "not supported" remark
* implement `ThreadChannelProperties`
* Add `DefaultSlowModeInterval` and `DefaultSlowModeInterval` properties to forum channels
* add `Moderated` property to `ForumTag``
* `ForumTag` inherits `ISnowflakeEntity`
* Fix `DiscordRestClient.GetChannelAsync` not getting forum channel
* a lot of changes
added:
- channel flags
- `ForumTagBuilder`
- imroved channel modification
* fixed a bug in forum tag emoji parsing
* inherit forum channel from `INesteeChannel`
* implement `INestedChannel` in forum channels
* Add `Flags` property to channels
* add iteraface for forum tags & add equality operators
* Add default reaction emoji property
* add support for modifing default reaction & some renaming
* add createForumChannelAsync to guild
* *fix resharper being a d... and moving code to next line*
* add a `ForumChannels` property
* Some fixes & add support for `default_sort_order`
* fix misleading comment
* fix #2502
* support creating post with applied tags
* fix xmldoc
* set category id on model update
* add limit checks for tag count
---
.../Entities/Channels/ChannelFlags.cs | 22 ++
.../Channels/ForumChannelProperties.cs | 60 ++++++
.../Entities/Channels/ForumSortOrder.cs | 17 ++
.../Channels/GuildChannelProperties.cs | 5 +
.../Entities/Channels/IForumChannel.cs | 68 ++++++-
.../Entities/Channels/IGuildChannel.cs | 11 +
.../Entities/Channels/IThreadChannel.cs | 20 ++
.../Channels/TextChannelProperties.cs | 13 +-
.../Channels/ThreadChannelProperties.cs | 26 +++
src/Discord.Net.Core/Entities/ForumTag.cs | 42 ----
.../Entities/ForumTags/ForumTag.cs | 67 ++++++
.../Entities/ForumTags/ForumTagBuilder.cs | 191 ++++++++++++++++++
.../ForumTags/ForumTagBuilderExtensions.cs | 11 +
.../Entities/ForumTags/ForumTagProperties.cs | 48 +++++
.../Entities/ForumTags/IForumTag.cs | 29 +++
.../Entities/Guilds/IGuild.cs | 12 ++
.../ApplicationCommandOptionType.cs | 2 +-
.../Extensions/ChannelExtensions.cs | 3 +
src/Discord.Net.Rest/API/Common/Channel.cs | 18 +-
.../API/Common/ForumReactionEmoji.cs | 12 ++
src/Discord.Net.Rest/API/Common/ForumTags.cs | 3 +
.../API/Rest/CreateGuildChannelParams.cs | 12 ++
.../API/Rest/CreateMultipartPostAsync.cs | 3 +
.../API/Rest/CreatePostParams.cs | 3 +
.../API/Rest/ModifyForumChannelParams.cs | 23 +++
.../Rest/ModifyForumReactionEmojiParams.cs | 15 ++
.../API/Rest/ModifyForumTagParams.cs | 23 +++
.../API/Rest/ModifyGuildChannelParams.cs | 2 +
.../API/Rest/ModifyThreadParams.cs | 7 +
.../Entities/Channels/ChannelHelper.cs | 1 +
.../Entities/Channels/ForumHelper.cs | 63 ++++++
.../Entities/Channels/RestChannel.cs | 7 +-
.../Entities/Channels/RestForumChannel.cs | 118 ++++++++---
.../Entities/Channels/RestGuildChannel.cs | 5 +
.../Entities/Channels/RestThreadChannel.cs | 12 ++
.../Entities/Channels/ThreadHelper.cs | 35 +++-
.../Entities/Guilds/GuildHelper.cs | 61 ++++++
.../Entities/Guilds/RestGuild.cs | 16 ++
.../Entities/Channels/SocketForumChannel.cs | 114 +++++++++--
.../Entities/Channels/SocketGuildChannel.cs | 5 +
.../Entities/Channels/SocketThreadChannel.cs | 12 +-
.../Entities/Guilds/SocketGuild.cs | 28 +++
.../MockedEntities/MockedCategoryChannel.cs | 2 +
.../MockedEntities/MockedTextChannel.cs | 2 +
.../MockedEntities/MockedVoiceChannel.cs | 2 +
45 files changed, 1132 insertions(+), 119 deletions(-)
create mode 100644 src/Discord.Net.Core/Entities/Channels/ChannelFlags.cs
create mode 100644 src/Discord.Net.Core/Entities/Channels/ForumChannelProperties.cs
create mode 100644 src/Discord.Net.Core/Entities/Channels/ForumSortOrder.cs
create mode 100644 src/Discord.Net.Core/Entities/Channels/ThreadChannelProperties.cs
delete mode 100644 src/Discord.Net.Core/Entities/ForumTag.cs
create mode 100644 src/Discord.Net.Core/Entities/ForumTags/ForumTag.cs
create mode 100644 src/Discord.Net.Core/Entities/ForumTags/ForumTagBuilder.cs
create mode 100644 src/Discord.Net.Core/Entities/ForumTags/ForumTagBuilderExtensions.cs
create mode 100644 src/Discord.Net.Core/Entities/ForumTags/ForumTagProperties.cs
create mode 100644 src/Discord.Net.Core/Entities/ForumTags/IForumTag.cs
create mode 100644 src/Discord.Net.Rest/API/Common/ForumReactionEmoji.cs
create mode 100644 src/Discord.Net.Rest/API/Rest/ModifyForumChannelParams.cs
create mode 100644 src/Discord.Net.Rest/API/Rest/ModifyForumReactionEmojiParams.cs
create mode 100644 src/Discord.Net.Rest/API/Rest/ModifyForumTagParams.cs
create mode 100644 src/Discord.Net.Rest/Entities/Channels/ForumHelper.cs
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..e1a123b37
--- /dev/null
+++ b/src/Discord.Net.Core/Entities/Channels/ForumChannelProperties.cs
@@ -0,0 +1,60 @@
+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; }
+
+ ///
+ /// Gets or sets a new default reaction emoji in this forum channel.
+ ///
+ public Optional DefaultReactionEmoji { get; set; }
+
+ ///
+ /// Gets or sets the rule used to order posts in forum channels.
+ ///
+ public Optional DefaultSortOrder { get; set; }
+}
diff --git a/src/Discord.Net.Core/Entities/Channels/ForumSortOrder.cs b/src/Discord.Net.Core/Entities/Channels/ForumSortOrder.cs
new file mode 100644
index 000000000..2a576d978
--- /dev/null
+++ b/src/Discord.Net.Core/Entities/Channels/ForumSortOrder.cs
@@ -0,0 +1,17 @@
+namespace Discord;
+
+///
+/// Defines the rule used to order posts in forum channels.
+///
+public enum ForumSortOrder
+{
+ ///
+ /// Sort forum posts by activity.
+ ///
+ LatestActivity = 0,
+
+ ///
+ /// Sort forum posts by creation time (from most recent to oldest).
+ ///
+ CreationDate = 1
+}
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 f4c6da2e2..55521bade 100644
--- a/src/Discord.Net.Core/Entities/Channels/IForumChannel.cs
+++ b/src/Discord.Net.Core/Entities/Channels/IForumChannel.cs
@@ -7,7 +7,7 @@ using System.Threading.Tasks;
namespace Discord
{
- public interface IForumChannel : IGuildChannel, IMentionable
+ public interface IForumChannel : IGuildChannel, IMentionable, INestedChannel
{
///
/// Gets a value that indicates whether the channel is NSFW.
@@ -35,6 +35,55 @@ namespace Discord
///
IReadOnlyCollection Tags { get; }
+ ///
+ /// Gets the current rate limit on creating posts in this forum channel.
+ ///
+ ///
+ /// An representing the time in seconds required before the user can send another
+ /// message; 0 if disabled.
+ ///
+ int ThreadCreationInterval { get; }
+
+ ///
+ /// Gets the current default slow-mode delay for threads in this forum channel.
+ ///
+ ///
+ /// An representing the time in seconds required before the user can send another
+ /// message; 0 if disabled.
+ ///
+ int DefaultSlowModeInterval { get; }
+
+ ///
+ /// Gets the emoji to show in the add reaction button on a thread in a forum channel
+ ///
+ ///
+ /// If the emoji is only the will be populated.
+ /// Use to get the emoji.
+ ///
+ IEmote DefaultReactionEmoji { get; }
+
+ ///
+ /// Gets or sets the rule used to order posts in forum channels.
+ ///
+ ///
+ /// Defaults to null, which indicates a preferred sort order hasn't been set
+ ///
+ ForumSortOrder? DefaultSortOrder { 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.
///
@@ -52,12 +101,13 @@ namespace Discord
/// A collection of stickers to send with the message.
/// A array of s to send with this response. Max 10.
/// A message flag to be applied to the sent message, only is permitted.
+ /// An array of to be applied to the post.
///
/// A task that represents the asynchronous creation operation.
///
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);
+ MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None, ForumTag[] tags = null);
///
/// Creates a new post (thread) within the forum.
@@ -78,13 +128,14 @@ namespace Discord
/// A collection of stickers to send with the file.
/// A array of s to send with this response. Max 10.
/// A message flag to be applied to the sent message, only is permitted.
+ /// An array of to be applied to the post.
///
/// A task that represents the asynchronous creation operation.
///
Task CreatePostWithFileAsync(string title, string filePath, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay,
int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, bool isSpoiler = false,
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);
///
/// Creates a new post (thread) within the forum.
@@ -106,13 +157,14 @@ namespace Discord
/// A collection of stickers to send with the file.
/// A array of s to send with this response. Max 10.
/// A message flag to be applied to the sent message, only is permitted.
+ /// An array of to be applied to the post.
///
/// A task that represents the asynchronous creation operation.
///
public Task 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,
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);
///
/// Creates a new post (thread) within the forum.
@@ -132,12 +184,13 @@ namespace Discord
/// A collection of stickers to send with the file.
/// A array of s to send with this response. Max 10.
/// A message flag to be applied to the sent message, only is permitted.
+ /// An array of to be applied to the post.
///
/// A task that represents the asynchronous creation operation.
///
public Task CreatePostWithFileAsync(string title, FileAttachment attachment, 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);
+ MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None, ForumTag[] tags = null);
///
/// Creates a new post (thread) within the forum.
@@ -155,14 +208,15 @@ namespace Discord
///
/// The message components to be included with this message. Used for interactions.
/// A collection of stickers to send with the file.
- /// A array of s to send with this response. Max 10.
+ /// An array of s to send with this response. Max 10.
/// A message flag to be applied to the sent message, only is permitted.
+ /// An array of to be applied to the post.
///
/// A task that represents the asynchronous creation operation.
///
public Task CreatePostWithFilesAsync(string title, IEnumerable attachments, 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);
+ MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None, ForumTag[] tags = null);
///
/// Gets a collection of active threads within this forum channel.
diff --git a/src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs b/src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs
index 992bd71fc..12874f2c2 100644
--- a/src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs
+++ b/src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs
@@ -21,6 +21,17 @@ namespace Discord
///
int Position { get; }
+ ///
+ /// Gets the flags related to this channel.
+ ///
+ ///
+ /// This value is determined by bitwise OR-ing values together.
+ ///
+ ///
+ /// A channel's flags, if any is associated.
+ ///
+ ChannelFlags Flags { get; }
+
///
/// Gets the guild associated with this channel.
///
diff --git a/src/Discord.Net.Core/Entities/Channels/IThreadChannel.cs b/src/Discord.Net.Core/Entities/Channels/IThreadChannel.cs
index f03edbbf9..52df07dcc 100644
--- a/src/Discord.Net.Core/Entities/Channels/IThreadChannel.cs
+++ b/src/Discord.Net.Core/Entities/Channels/IThreadChannel.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Threading.Tasks;
namespace Discord
@@ -56,6 +57,14 @@ namespace Discord
///
bool? IsInvitable { get; }
+ ///
+ /// Gets ids of tags applied to a forum thread
+ ///
+ ///
+ /// This property is only available on forum threads.
+ ///
+ IReadOnlyCollection AppliedTags { get; }
+
///
/// Gets when the thread was created.
///
@@ -102,5 +111,16 @@ namespace Discord
/// A task that represents the asynchronous operation of removing a user from this thread.
///
Task RemoveUserAsync(IGuildUser user, RequestOptions options = null);
+
+ ///
+ /// Modifies this thread channel.
+ ///
+ /// 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);
}
}
diff --git a/src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs b/src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs
index 2dceb025c..acd69f480 100644
--- a/src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs
+++ b/src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
namespace Discord
{
@@ -39,20 +40,10 @@ namespace Discord
/// Thrown if the value does not fall within [0, 21600].
public Optional SlowModeInterval { get; set; }
- ///
- /// Gets or sets whether or not the thread is archived.
- ///
- public Optional Archived { get; set; }
-
- ///
- /// Gets or sets whether or not the thread is locked.
- ///
- public Optional Locked { get; set; }
-
///
/// Gets or sets the auto archive duration.
///
public Optional AutoArchiveDuration { get; set; }
-
+
}
}
diff --git a/src/Discord.Net.Core/Entities/Channels/ThreadChannelProperties.cs b/src/Discord.Net.Core/Entities/Channels/ThreadChannelProperties.cs
new file mode 100644
index 000000000..af5c44129
--- /dev/null
+++ b/src/Discord.Net.Core/Entities/Channels/ThreadChannelProperties.cs
@@ -0,0 +1,26 @@
+using System.Collections.Generic;
+
+namespace Discord;
+
+
+///
+/// Provides properties that are used to modify an with the specified changes.
+///
+///
+public class ThreadChannelProperties : TextChannelProperties
+{
+ ///
+ /// Gets or sets the tags applied to a forum thread
+ ///
+ public Optional> AppliedTags { get; set; }
+
+ ///
+ /// Gets or sets whether or not the thread is locked.
+ ///
+ public Optional Locked { get; set; }
+
+ ///
+ /// Gets or sets whether or not the thread is archived.
+ ///
+ public Optional Archived { get; set; }
+}
diff --git a/src/Discord.Net.Core/Entities/ForumTag.cs b/src/Discord.Net.Core/Entities/ForumTag.cs
deleted file mode 100644
index 26ae4301e..000000000
--- a/src/Discord.Net.Core/Entities/ForumTag.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Discord
-{
- ///
- /// A struct representing a forum channel tag.
- ///
- public struct ForumTag
- {
- ///
- /// Gets the Id of the tag.
- ///
- public ulong Id { get; }
-
- ///
- /// 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; }
-
- 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;
- }
- }
-}
diff --git a/src/Discord.Net.Core/Entities/ForumTags/ForumTag.cs b/src/Discord.Net.Core/Entities/ForumTags/ForumTag.cs
new file mode 100644
index 000000000..afdb99bf7
--- /dev/null
+++ b/src/Discord.Net.Core/Entities/ForumTags/ForumTag.cs
@@ -0,0 +1,67 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+#nullable enable
+
+namespace Discord
+{
+ ///
+ /// A struct representing a forum channel tag.
+ ///
+ public struct ForumTag : ISnowflakeEntity, IForumTag
+ {
+ ///
+ /// Gets the Id of the tag.
+ ///
+ public ulong Id { get; }
+
+ ///
+ public string Name { get; }
+
+ ///
+ public IEmote? Emoji { get; }
+
+ ///
+ public bool IsModerated { get; }
+
+ ///
+ 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);
+
+ ///
+ /// Gets whether supplied tag is equals to the current one.
+ ///
+ 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);
+ }
+}
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..d8e881189
--- /dev/null
+++ b/src/Discord.Net.Core/Entities/ForumTags/ForumTagBuilder.cs
@@ -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;
+
+ ///
+ /// Returns the maximum length of name allowed by Discord.
+ ///
+ public const int MaxNameLength = 20;
+
+ ///
+ /// Gets or sets the snowflake Id of the tag.
+ ///
+ ///
+ /// If set this will update existing tag or will create a new one otherwise.
+ ///
+ public ulong? Id
+ {
+ get { return _id; }
+ set { _id = value; }
+ }
+
+ ///
+ /// 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
+ ///
+ /// If set existing tag will be updated or a new one will be created otherwise.
+ /// Name of the tag.
+ /// Sets whether this tag can only be added to or removed from threads by a member
+ /// with the permission.
+ public ForumTagBuilder(string name, ulong? id = null, bool isModerated = false)
+ {
+ Name = name;
+ IsModerated = isModerated;
+ Id = id;
+ }
+
+ ///
+ /// Initializes a new class with values
+ ///
+ /// Name of the tag.
+ /// If set existing tag will be updated or a new one will be created otherwise.
+ /// Display emoji of the tag.
+ /// Sets whether this tag can only be added to or removed from threads by a member
+ /// with the permission.
+ public ForumTagBuilder(string name, ulong? id = null, bool isModerated = false, IEmote? emoji = null)
+ {
+ Name = name;
+ Emoji = emoji;
+ IsModerated = isModerated;
+ Id = id;
+ }
+
+ ///
+ /// Initializes a new class with values
+ ///
+ /// /// Name of the tag.
+ /// If set existing tag will be updated or a new one will be created otherwise.
+ /// The id of custom Display emoji of the tag.
+ /// Sets whether this tag can only be added to or removed from threads by a member
+ /// with the permission
+ 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;
+ }
+
+ ///
+ /// 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 id of the tag.
+ ///
+ /// If set existing tag will be updated or a new one will be created otherwise.
+ /// Name length must be less than or equal to .
+ public ForumTagBuilder WithId(ulong? id)
+ {
+ Id = id;
+ return this;
+ }
+
+ ///
+ /// Sets the emoji of the tag.
+ ///
+ public ForumTagBuilder WithEmoji(IEmote? emoji)
+ {
+ Emoji = emoji;
+ return this;
+ }
+
+ ///
+ /// Sets whether this tag can only be added to or removed from threads by a member
+ /// with the permission
+ ///
+ 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);
+
+ ///
+ /// Gets whether supplied tag builder is equals to the current one.
+ ///
+ 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);
+}
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..73a953fe6
--- /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.Id, tag.IsModerated, tag.Emoji);
+
+ public static ForumTagBuilder ToForumTagBuilder(this ForumTagProperties tag)
+ => new ForumTagBuilder(tag.Name, tag.Id, tag.IsModerated, tag.Emoji);
+
+}
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..6ded49204
--- /dev/null
+++ b/src/Discord.Net.Core/Entities/ForumTags/ForumTagProperties.cs
@@ -0,0 +1,48 @@
+namespace Discord;
+
+#nullable enable
+
+public class ForumTagProperties : IForumTag
+{
+ ///
+ /// Gets the Id of the tag.
+ ///
+ public ulong Id { get; }
+
+ ///
+ public string Name { get; }
+
+ ///
+ public IEmote? Emoji { get; }
+
+ ///
+ 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);
+
+ ///
+ /// Gets whether supplied tag is equals to the current one.
+ ///
+ 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);
+}
diff --git a/src/Discord.Net.Core/Entities/ForumTags/IForumTag.cs b/src/Discord.Net.Core/Entities/ForumTags/IForumTag.cs
new file mode 100644
index 000000000..8b8b866b2
--- /dev/null
+++ b/src/Discord.Net.Core/Entities/ForumTags/IForumTag.cs
@@ -0,0 +1,29 @@
+namespace Discord;
+
+#nullable enable
+
+///
+/// Represents a Discord forum tag
+///
+public interface IForumTag
+{
+ ///
+ /// Gets the name of the tag.
+ ///
+ string Name { get; }
+
+ ///
+ /// Gets the emoji of the tag or if none is set.
+ ///
+ ///
+ /// If the emoji is only the will be populated.
+ /// Use to get the emoji.
+ ///
+ IEmote? Emoji { get; }
+
+ ///
+ /// Gets whether this tag can only be added to or removed from threads by a member
+ /// with the permission
+ ///
+ bool IsModerated { get; }
+}
diff --git a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs
index 34a08f1e7..d1ff7b99c 100644
--- a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs
+++ b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs
@@ -761,6 +761,18 @@ namespace Discord
///
Task CreateCategoryAsync(string name, Action func = null, RequestOptions options = null);
+ ///
+ /// Creates a new channel forum in this guild.
+ ///
+ /// The new name for the forum.
+ /// The delegate containing the properties to be applied to the channel upon its creation.
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous creation operation. The task result contains the newly created
+ /// forum channel.
+ ///
+ Task CreateForumChannelAsync(string name, Action func = null, RequestOptions options = null);
+
///
/// Gets a collection of all the voice regions this guild can access.
///
diff --git a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionType.cs b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionType.cs
index 4506b66d9..2bad7fcb7 100644
--- a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionType.cs
+++ b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionType.cs
@@ -21,7 +21,7 @@ namespace Discord
String = 3,
///
- /// An .
+ /// An .
///
Integer = 4,
diff --git a/src/Discord.Net.Core/Extensions/ChannelExtensions.cs b/src/Discord.Net.Core/Extensions/ChannelExtensions.cs
index b5ddae1cf..a24588792 100644
--- a/src/Discord.Net.Core/Extensions/ChannelExtensions.cs
+++ b/src/Discord.Net.Core/Extensions/ChannelExtensions.cs
@@ -46,6 +46,9 @@ namespace Discord
case ITextChannel:
return ChannelType.Text;
+
+ case IForumChannel:
+ return ChannelType.Forum;
}
return null;
diff --git a/src/Discord.Net.Rest/API/Common/Channel.cs b/src/Discord.Net.Rest/API/Common/Channel.cs
index d9d7d469c..f9184cd1f 100644
--- a/src/Discord.Net.Rest/API/Common/Channel.cs
+++ b/src/Discord.Net.Rest/API/Common/Channel.cs
@@ -70,8 +70,24 @@ namespace Discord.API
//ForumChannel
[JsonProperty("available_tags")]
public Optional ForumTags { get; set; }
-
+
+ [JsonProperty("applied_tags")]
+ public Optional AppliedTags { get; set; }
+
[JsonProperty("default_auto_archive_duration")]
public Optional AutoArchiveDuration { get; set; }
+
+ [JsonProperty("default_thread_rate_limit_per_user")]
+ public Optional ThreadRateLimitPerUser { get; set; }
+
+ [JsonProperty("flags")]
+ public Optional Flags { get; set; }
+
+ [JsonProperty("default_sort_order")]
+ public Optional DefaultSortOrder { get; set; }
+
+ [JsonProperty("default_reaction_emoji")]
+ public Optional DefaultReactionEmoji { get; set; }
+
}
}
diff --git a/src/Discord.Net.Rest/API/Common/ForumReactionEmoji.cs b/src/Discord.Net.Rest/API/Common/ForumReactionEmoji.cs
new file mode 100644
index 000000000..ae2d2b546
--- /dev/null
+++ b/src/Discord.Net.Rest/API/Common/ForumReactionEmoji.cs
@@ -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 EmojiName { get; set; }
+}
diff --git a/src/Discord.Net.Rest/API/Common/ForumTags.cs b/src/Discord.Net.Rest/API/Common/ForumTags.cs
index 18354e7b2..c4a1fa2a2 100644
--- a/src/Discord.Net.Rest/API/Common/ForumTags.cs
+++ b/src/Discord.Net.Rest/API/Common/ForumTags.cs
@@ -17,5 +17,8 @@ namespace Discord.API
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/CreateGuildChannelParams.cs b/src/Discord.Net.Rest/API/Rest/CreateGuildChannelParams.cs
index 57816e448..74590fb35 100644
--- a/src/Discord.Net.Rest/API/Rest/CreateGuildChannelParams.cs
+++ b/src/Discord.Net.Rest/API/Rest/CreateGuildChannelParams.cs
@@ -23,6 +23,8 @@ namespace Discord.API.Rest
public Optional IsNsfw { get; set; }
[JsonProperty("rate_limit_per_user")]
public Optional SlowModeInterval { get; set; }
+ [JsonProperty("default_auto_archive_duration")]
+ public Optional DefaultAutoArchiveDuration { get; set; }
//Voice channels
[JsonProperty("bitrate")]
@@ -30,6 +32,16 @@ namespace Discord.API.Rest
[JsonProperty("user_limit")]
public Optional UserLimit { get; set; }
+ //Forum channels
+ [JsonProperty("default_reaction_emoji")]
+ public Optional DefaultReactionEmoji { get; set; }
+ [JsonProperty("default_thread_rate_limit_per_user")]
+ public Optional ThreadRateLimitPerUser { get; set; }
+ [JsonProperty("available_tags")]
+ public Optional AvailableTags { get; set; }
+ [JsonProperty("default_sort_order")]
+ public Optional DefaultSortOrder { get; set; }
+
public CreateGuildChannelParams(string name, ChannelType type)
{
Name = name;
diff --git a/src/Discord.Net.Rest/API/Rest/CreateMultipartPostAsync.cs b/src/Discord.Net.Rest/API/Rest/CreateMultipartPostAsync.cs
index 0c8bc5494..bb10a4681 100644
--- a/src/Discord.Net.Rest/API/Rest/CreateMultipartPostAsync.cs
+++ b/src/Discord.Net.Rest/API/Rest/CreateMultipartPostAsync.cs
@@ -27,6 +27,7 @@ namespace Discord.API.Rest
public Optional MessageComponent { get; set; }
public Optional Flags { get; set; }
public Optional Stickers { get; set; }
+ public Optional TagIds { get; set; }
public CreateMultipartPostAsync(params FileAttachment[] attachments)
{
@@ -59,6 +60,8 @@ namespace Discord.API.Rest
message["sticker_ids"] = Stickers.Value;
if (Flags.IsSpecified)
message["flags"] = Flags.Value;
+ if (TagIds.IsSpecified)
+ message["applied_tags"] = TagIds.Value;
List