diff --git a/src/Discord.Net.Core/Discord.Net.Core.xml b/src/Discord.Net.Core/Discord.Net.Core.xml
index dc1641bcd..5213edd3c 100644
--- a/src/Discord.Net.Core/Discord.Net.Core.xml
+++ b/src/Discord.Net.Core/Discord.Net.Core.xml
@@ -1982,11 +1982,48 @@
of webhooks that is available in this channel.
+
+
+ Creates a thread within this .
+
+
+ When is the thread type will be based off of the
+ channel its created in. When called on a , it creates a .
+ When called on a , it creates a . The id of the created
+ thread will be the same as the id of the message, and as such a message can only have a
+ single thread created from it.
+
+ The name of the thread.
+
+ The type of the thread.
+
+ Note: This parameter is not used if the parameter is not specified.
+
+
+
+ The duration on which this thread archives after.
+
+ Note: Options and
+ are only available for guilds that are boosted. You can check in the to see if the
+ guild has the THREE_DAY_THREAD_ARCHIVE and SEVEN_DAY_THREAD_ARCHIVE.
+
+
+ The message which to start the thread from.
+ The options to be used when sending the request.
+
+ A task that represents the asynchronous create operation. The task result contains a
+
+
Represents a thread channel inside of a guild.
+
+
+ Gets the type of the current thread channel.
+
+
if the current user has joined this thread, otherwise .
@@ -2022,17 +2059,43 @@
An approximate count of messages in a thread, stops counting at 50.
-
+
Joins the current thread.
-
+ The options to be used when sending the request.
+
+ A task that represents the asynchronous join operation.
+
-
+
Leaves the current thread.
-
+ The options to be used when sending the request.
+
+ A task that represents the asynchronous leave operation.
+
+
+
+
+ Adds a user to this thread.
+
+ The to add.
+ The options to be used when sending the request.
+
+ A task that represents the asynchronous operation of adding a member to a thread.
+
+
+
+
+ Removes a user from this thread.
+
+ The to remove from this thread.
+ The options to be used when sending the request.
+
+ A task that represents the asynchronous operation of removing a user from this thread.
+
@@ -2133,6 +2196,21 @@
Thrown if the value does not fall within [0, 21600].
+
+
+ Gets or sets whether or not the thread is archived.
+
+
+
+
+ Gets or sets whether or not the thread is locked.
+
+
+
+
+ Gets or sets the auto archive duration.
+
+
Represents the thread auto archive duration.
@@ -2164,40 +2242,26 @@
-
+
- Gets or sets whether or not the thread is archived.
+ Represents types of threads.
-
+
- Gets or sets whether or not the thread is locked.
+ Represents a temporary sub-channel within a GUILD_NEWS channel.
-
+
- Gets or sets the name of the thread.
+ Represents a temporary sub-channel within a GUILD_TEXT channel.
-
+
- Gets or sets the auto archive duration.
+ Represents a temporary sub-channel within a GUILD_TEXT channel that is only viewable by those invited and those with the MANAGE_THREADS permission
-
-
- Gets or sets the slow-mode ratelimit in seconds for 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 this channel.
-
- Users with or
- will be exempt from slow-mode.
-
-
- Thrown if the value does not fall within [0, 21600].
-
Provides properties that are used to modify an with the specified changes.
@@ -4747,7 +4811,7 @@
A button must contain either a or a , but not both.
A button must have an or a .
A link button must contain a URL.
- A link must include a protocol (http or https).
+ A URL must include a protocol (http or https).
A non-link button must contain a custom id
@@ -6041,7 +6105,7 @@
The built embed object.
Total embed length exceeds .
- Any Url must include protocols (i.e http:// or https://).
+ Any Url must be well formatted include its protocols (i.e http:// or https://).
@@ -8024,6 +8088,16 @@
Allows for viewing of audit logs.
+
+
+ Allows guild members to view a channel, which includes reading messages in text channels.
+
+
+
+
+ Allows for sending messages in a channel
+
+
Allows for sending of text-to-speech messages.
@@ -8094,6 +8168,11 @@
Allows for using voice-activity-detection in a voice channel.
+
+
+ Allows for using priority speaker in a voice channel.
+
+
Allows video streaming in a voice channel.
@@ -8127,15 +8206,49 @@
authentication when used on a guild that has server-wide 2FA enabled.
-
+
- Allows management and editing of emojis.
+ Allows management and editing of emojis and stickers.
+
+
+ This permission requires the owner account to use two-factor
+ authentication when used on a guild that has server-wide 2FA enabled.
+
+
+
+
+ Allows members to use slash commands in text channels.
+
+
+
+
+ Allows for requesting to speak in stage channels. (This permission is under active development and may be changed or removed.).
+
+
+
+
+ Allows for deleting and archiving threads, and viewing all private threads.
This permission requires the owner account to use two-factor
authentication when used on a guild that has server-wide 2FA enabled.
+
+
+ Allows for creating and participating in threads.
+
+
+
+
+ Allows for creating and participating in private threads.
+
+
+
+
+ Allows the usage of custom stickers from other servers.
+
+
Gets a blank that grants no permissions.
@@ -8238,7 +8351,7 @@
If true, a user may edit the webhooks for this guild.
-
+
If true, a user may edit the emojis for this guild.
diff --git a/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs b/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs
index a2baf6990..70ba872fc 100644
--- a/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs
+++ b/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs
@@ -114,5 +114,38 @@ namespace Discord
/// of webhooks that is available in this channel.
///
Task> GetWebhooksAsync(RequestOptions options = null);
+
+ ///
+ /// Creates a thread within this .
+ ///
+ ///
+ /// When is the thread type will be based off of the
+ /// channel its created in. When called on a , it creates a .
+ /// When called on a , it creates a . The id of the created
+ /// thread will be the same as the id of the message, and as such a message can only have a
+ /// single thread created from it.
+ ///
+ /// The name of the thread.
+ ///
+ /// The type of the thread.
+ ///
+ /// Note: This parameter is not used if the parameter is not specified.
+ ///
+ ///
+ ///
+ /// The duration on which this thread archives after.
+ ///
+ /// Note: Options and
+ /// are only available for guilds that are boosted. You can check in the to see if the
+ /// guild has the THREE_DAY_THREAD_ARCHIVE and SEVEN_DAY_THREAD_ARCHIVE.
+ ///
+ ///
+ /// The message which to start the thread from.
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous create operation. The task result contains a
+ ///
+ Task CreateThreadAsync(string name, ThreadType type = ThreadType.PublicThread, ThreadArchiveDuration autoArchiveDuration = ThreadArchiveDuration.OneDay,
+ IMessage message = null, RequestOptions options = null);
}
}
diff --git a/src/Discord.Net.Core/Entities/Channels/IThreadChannel.cs b/src/Discord.Net.Core/Entities/Channels/IThreadChannel.cs
index f801d3fd8..b5d6eea20 100644
--- a/src/Discord.Net.Core/Entities/Channels/IThreadChannel.cs
+++ b/src/Discord.Net.Core/Entities/Channels/IThreadChannel.cs
@@ -11,6 +11,11 @@ namespace Discord
///
public interface IThreadChannel : ITextChannel, IGuildChannel
{
+ ///
+ /// Gets the type of the current thread channel.
+ ///
+ ThreadType Type { get; }
+
///
/// if the current user has joined this thread, otherwise .
///
@@ -49,13 +54,39 @@ namespace Discord
///
/// Joins the current thread.
///
- ///
- Task JoinAsync();
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous join operation.
+ ///
+ Task JoinAsync(RequestOptions options = null);
///
/// Leaves the current thread.
///
- ///
- Task LeaveAsync();
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous leave operation.
+ ///
+ Task LeaveAsync(RequestOptions options = null);
+
+ ///
+ /// Adds a user to this thread.
+ ///
+ /// The to add.
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous operation of adding a member to a thread.
+ ///
+ Task AddUserAsync(IGuildUser user, RequestOptions options = null);
+
+ ///
+ /// Removes a user from this thread.
+ ///
+ /// The to remove from this thread.
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous operation of removing a user from this thread.
+ ///
+ Task RemoveUserAsync(IGuildUser user, RequestOptions options = null);
}
}
diff --git a/src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs b/src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs
index 821f358f5..2dceb025c 100644
--- a/src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs
+++ b/src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs
@@ -38,5 +38,21 @@ 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
deleted file mode 100644
index 10a0d7654..000000000
--- a/src/Discord.Net.Core/Entities/Channels/ThreadChannelProperties.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Discord.Entities
-{
- public class ThreadChannelProperties
- {
- ///
- /// 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 name of the thread.
- ///
- public Optional Name { get; set; }
-
- ///
- /// Gets or sets the auto archive duration.
- ///
- public Optional AutoArchiveDuration { get; set; }
-
- ///
- /// Gets or sets the slow-mode ratelimit in seconds for 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 this channel.
- ///
- /// Users with or
- /// will be exempt from slow-mode.
- ///
- ///
- /// Thrown if the value does not fall within [0, 21600].
- public Optional SlowModeInterval { get; set; }
- }
-}
diff --git a/src/Discord.Net.Core/Entities/Channels/ThreadType.cs b/src/Discord.Net.Core/Entities/Channels/ThreadType.cs
new file mode 100644
index 000000000..2db09bcb9
--- /dev/null
+++ b/src/Discord.Net.Core/Entities/Channels/ThreadType.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Discord
+{
+ ///
+ /// Represents types of threads.
+ ///
+ public enum ThreadType
+ {
+ ///
+ /// Represents a temporary sub-channel within a GUILD_NEWS channel.
+ ///
+ NewsThread = 10,
+
+ ///
+ /// Represents a temporary sub-channel within a GUILD_TEXT channel.
+ ///
+ PublicThread = 11,
+
+ ///
+ /// Represents a temporary sub-channel within a GUILD_TEXT channel that is only viewable by those invited and those with the MANAGE_THREADS permission
+ ///
+ PrivateThread = 12,
+ }
+}
diff --git a/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs b/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs
index 31bd6164a..89ee20b9f 100644
--- a/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs
+++ b/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs
@@ -10,7 +10,7 @@ namespace Discord
///
/// Allows creation of instant invites.
///
- CreateInstantInvite = 0x00_00_00_01,
+ CreateInstantInvite = 0x00_00_00_01,
///
/// Allows kicking members.
///
@@ -18,7 +18,7 @@ namespace Discord
/// This permission requires the owner account to use two-factor
/// authentication when used on a guild that has server-wide 2FA enabled.
///
- KickMembers = 0x00_00_00_02,
+ KickMembers = 0x00_00_00_02,
///
/// Allows banning members.
///
@@ -26,7 +26,7 @@ namespace Discord
/// This permission requires the owner account to use two-factor
/// authentication when used on a guild that has server-wide 2FA enabled.
///
- BanMembers = 0x00_00_00_04,
+ BanMembers = 0x00_00_00_04,
///
/// Allows all permissions and bypasses channel permission overwrites.
///
@@ -34,7 +34,7 @@ namespace Discord
/// This permission requires the owner account to use two-factor
/// authentication when used on a guild that has server-wide 2FA enabled.
///
- Administrator = 0x00_00_00_08,
+ Administrator = 0x00_00_00_08,
///
/// Allows management and editing of channels.
///
@@ -42,7 +42,7 @@ namespace Discord
/// This permission requires the owner account to use two-factor
/// authentication when used on a guild that has server-wide 2FA enabled.
///
- ManageChannels = 0x00_00_00_10,
+ ManageChannels = 0x00_00_00_10,
///
/// Allows management and editing of the guild.
///
@@ -50,27 +50,33 @@ namespace Discord
/// This permission requires the owner account to use two-factor
/// authentication when used on a guild that has server-wide 2FA enabled.
///
- ManageGuild = 0x00_00_00_20,
+ ManageGuild = 0x00_00_00_20,
///
/// Allows for viewing of guild insights
///
- ViewGuildInsights = 0x00_08_00_00,
+ ViewGuildInsights = 0x00_08_00_00,
// Text
///
/// Allows for the addition of reactions to messages.
///
- AddReactions = 0x00_00_00_40,
+ AddReactions = 0x00_00_00_40,
///
/// Allows for viewing of audit logs.
///
- ViewAuditLog = 0x00_00_00_80,
- ViewChannel = 0x00_00_04_00,
- SendMessages = 0x00_00_08_00,
+ ViewAuditLog = 0x00_00_00_80,
+ ///
+ /// Allows guild members to view a channel, which includes reading messages in text channels.
+ ///
+ ViewChannel = 0x00_00_04_00,
+ ///
+ /// Allows for sending messages in a channel
+ ///
+ SendMessages = 0x00_00_08_00,
///
/// Allows for sending of text-to-speech messages.
///
- SendTTSMessages = 0x00_00_10_00,
+ SendTTSMessages = 0x00_00_10_00,
///
/// Allows for deletion of other users messages.
///
@@ -78,70 +84,73 @@ namespace Discord
/// This permission requires the owner account to use two-factor
/// authentication when used on a guild that has server-wide 2FA enabled.
///
- ManageMessages = 0x00_00_20_00,
+ ManageMessages = 0x00_00_20_00,
///
/// Allows links sent by users with this permission will be auto-embedded.
///
- EmbedLinks = 0x00_00_40_00,
+ EmbedLinks = 0x00_00_40_00,
///
/// Allows for uploading images and files.
///
- AttachFiles = 0x00_00_80_00,
+ AttachFiles = 0x00_00_80_00,
///
/// Allows for reading of message history.
///
- ReadMessageHistory = 0x00_01_00_00,
+ ReadMessageHistory = 0x00_01_00_00,
///
/// Allows for using the @everyone tag to notify all users in a channel, and the @here tag to notify all
/// online users in a channel.
///
- MentionEveryone = 0x00_02_00_00,
+ MentionEveryone = 0x00_02_00_00,
///
/// Allows the usage of custom emojis from other servers.
///
- UseExternalEmojis = 0x00_04_00_00,
+ UseExternalEmojis = 0x00_04_00_00,
// Voice
///
/// Allows for joining of a voice channel.
///
- Connect = 0x00_10_00_00,
+ Connect = 0x00_10_00_00,
///
/// Allows for speaking in a voice channel.
///
- Speak = 0x00_20_00_00,
+ Speak = 0x00_20_00_00,
///
/// Allows for muting members in a voice channel.
///
- MuteMembers = 0x00_40_00_00,
+ MuteMembers = 0x00_40_00_00,
///
/// Allows for deafening of members in a voice channel.
///
- DeafenMembers = 0x00_80_00_00,
+ DeafenMembers = 0x00_80_00_00,
///
/// Allows for moving of members between voice channels.
///
- MoveMembers = 0x01_00_00_00,
+ MoveMembers = 0x01_00_00_00,
///
/// Allows for using voice-activity-detection in a voice channel.
///
- UseVAD = 0x02_00_00_00,
- PrioritySpeaker = 0x00_00_01_00,
+ UseVAD = 0x02_00_00_00,
+ ///
+ /// Allows for using priority speaker in a voice channel.
+ ///
+ PrioritySpeaker = 0x00_00_01_00,
///
/// Allows video streaming in a voice channel.
///
- Stream = 0x00_00_02_00,
+ Stream = 0x00_00_02_00,
// General 2
///
/// Allows for modification of own nickname.
///
- ChangeNickname = 0x04_00_00_00,
+ ChangeNickname = 0x04_00_00_00,
///
/// Allows for modification of other users nicknames.
///
- ManageNicknames = 0x08_00_00_00,
+ ManageNicknames = 0x08_00_00_00,
///
/// Allows management and editing of roles.
///
@@ -149,7 +158,7 @@ namespace Discord
/// This permission requires the owner account to use two-factor
/// authentication when used on a guild that has server-wide 2FA enabled.
///
- ManageRoles = 0x10_00_00_00,
+ ManageRoles = 0x10_00_00_00,
///
/// Allows management and editing of webhooks.
///
@@ -157,14 +166,43 @@ namespace Discord
/// This permission requires the owner account to use two-factor
/// authentication when used on a guild that has server-wide 2FA enabled.
///
- ManageWebhooks = 0x20_00_00_00,
+ ManageWebhooks = 0x20_00_00_00,
///
- /// Allows management and editing of emojis.
+ /// Allows management and editing of emojis and stickers.
///
///
/// This permission requires the owner account to use two-factor
/// authentication when used on a guild that has server-wide 2FA enabled.
///
- ManageEmojis = 0x40_00_00_00
+ ManageEmojisAndStickers = 0x40_00_00_00,
+ ///
+ /// Allows members to use slash commands in text channels.
+ ///
+ UseSlashCommands = 0x80_00_00_00,
+ ///
+ /// Allows for requesting to speak in stage channels. (This permission is under active development and may be changed or removed.).
+ ///
+ RequestToSpeak = 0x01_00_00_00_00,
+ ///
+ /// Allows for deleting and archiving threads, and viewing all private threads.
+ ///
+ ///
+ /// This permission requires the owner account to use two-factor
+ /// authentication when used on a guild that has server-wide 2FA enabled.
+ ///
+ ManageThreads = 0x04_00_00_00_00,
+ ///
+ /// Allows for creating and participating in threads.
+ ///
+ UsePublicThreads = 0x08_00_00_00_00,
+ ///
+ /// Allows for creating and participating in private threads.
+ ///
+ UsePrivateThreads = 0x10_00_00_00_00,
+ ///
+ /// Allows the usage of custom stickers from other servers.
+ ///
+ UseExternalStickers = 0x20_00_00_00_00
+
}
}
diff --git a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs
index b03c0e1a8..1914a6f86 100644
--- a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs
+++ b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs
@@ -82,7 +82,7 @@ namespace Discord
/// If true, a user may edit the webhooks for this guild.
public bool ManageWebhooks => Permissions.GetValue(RawValue, GuildPermission.ManageWebhooks);
/// If true, a user may edit the emojis for this guild.
- public bool ManageEmojis => Permissions.GetValue(RawValue, GuildPermission.ManageEmojis);
+ public bool ManageEmojisAndStickers => Permissions.GetValue(RawValue, GuildPermission.ManageEmojisAndStickers);
/// Creates a new with the provided packed value.
public GuildPermissions(ulong rawValue) { RawValue = rawValue; }
@@ -121,7 +121,7 @@ namespace Discord
bool? manageNicknames = null,
bool? manageRoles = null,
bool? manageWebhooks = null,
- bool? manageEmojis = null)
+ bool? manageEmojisAndStickers = null)
{
ulong value = initialValue;
@@ -155,7 +155,7 @@ namespace Discord
Permissions.SetValue(ref value, manageNicknames, GuildPermission.ManageNicknames);
Permissions.SetValue(ref value, manageRoles, GuildPermission.ManageRoles);
Permissions.SetValue(ref value, manageWebhooks, GuildPermission.ManageWebhooks);
- Permissions.SetValue(ref value, manageEmojis, GuildPermission.ManageEmojis);
+ Permissions.SetValue(ref value, manageEmojisAndStickers, GuildPermission.ManageEmojisAndStickers);
RawValue = value;
}
@@ -224,7 +224,7 @@ namespace Discord
changeNickname: changeNickname,
manageNicknames: manageNicknames,
manageWebhooks: manageWebhooks,
- manageEmojis: manageEmojis)
+ manageEmojisAndStickers: manageEmojis)
{ }
/// Creates a new from this one, changing the provided non-null permissions.
diff --git a/src/Discord.Net.Rest/API/Common/ThreadMember.cs b/src/Discord.Net.Rest/API/Common/ThreadMember.cs
index b4c542454..d8ce15e92 100644
--- a/src/Discord.Net.Rest/API/Common/ThreadMember.cs
+++ b/src/Discord.Net.Rest/API/Common/ThreadMember.cs
@@ -18,6 +18,12 @@ namespace Discord.API
[JsonProperty("join_timestamp")]
public DateTimeOffset JoinTimestamp { get; set; }
+ [JsonProperty("presense")]
+ public Optional Presence { get; set; }
+
+ [JsonProperty("member")]
+ public Optional Member { get; set; }
+
[JsonProperty("flags")]
public int Flags { get; set; } // No enum type (yet?)
}
diff --git a/src/Discord.Net.Rest/API/Common/ThreadMetadata.cs b/src/Discord.Net.Rest/API/Common/ThreadMetadata.cs
index f6da249f7..0bc068c1c 100644
--- a/src/Discord.Net.Rest/API/Common/ThreadMetadata.cs
+++ b/src/Discord.Net.Rest/API/Common/ThreadMetadata.cs
@@ -13,7 +13,7 @@ namespace Discord.API
public bool Archived { get; set; }
[JsonProperty("auto_archive_duration")]
- public int AutoArchiveDuration { get; set; }
+ public ThreadArchiveDuration AutoArchiveDuration { get; set; }
[JsonProperty("archive_timestamp")]
public DateTimeOffset ArchiveTimestamp { get; set; }
diff --git a/src/Discord.Net.Rest/API/Rest/ModifyThreadParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyThreadParams.cs
new file mode 100644
index 000000000..c62b3bfbb
--- /dev/null
+++ b/src/Discord.Net.Rest/API/Rest/ModifyThreadParams.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Discord;
+using Newtonsoft.Json;
+
+namespace Discord.API.Rest
+{
+ internal class ModifyThreadParams
+ {
+ [JsonProperty("name")]
+ public Optional Name { get; set; }
+
+ [JsonProperty("archived")]
+ public Optional Archived { get; set; }
+
+ [JsonProperty("auto_archive_duration")]
+ public Optional AutoArchiveDuration { get; set; }
+
+ [JsonProperty("locked")]
+ public Optional Locked { get; set; }
+
+ [JsonProperty("rate_limit_per_user")]
+ public Optional Slowmode { get; set; }
+ }
+}
diff --git a/src/Discord.Net.Rest/API/Rest/StartThreadParams.cs b/src/Discord.Net.Rest/API/Rest/StartThreadParams.cs
index 167d94099..c41398d93 100644
--- a/src/Discord.Net.Rest/API/Rest/StartThreadParams.cs
+++ b/src/Discord.Net.Rest/API/Rest/StartThreadParams.cs
@@ -16,6 +16,6 @@ namespace Discord.API.Rest
public ThreadArchiveDuration Duration { get; set; }
[JsonProperty("type")]
- public Optional Type { get; set; }
+ public Optional Type { get; set; }
}
}
diff --git a/src/Discord.Net.Rest/Discord.Net.Rest.xml b/src/Discord.Net.Rest/Discord.Net.Rest.xml
index 465d5085f..eda180f01 100644
--- a/src/Discord.Net.Rest/Discord.Net.Rest.xml
+++ b/src/Discord.Net.Rest/Discord.Net.Rest.xml
@@ -2458,6 +2458,38 @@
+
+
+ Creates a thread within this .
+
+
+ When is the thread type will be based off of the
+ channel its created in. When called on a , it creates a .
+ When called on a , it creates a . The id of the created
+ thread will be the same as the id of the message, and as such a message can only have a
+ single thread created from it.
+
+ The name of the thread.
+
+ The type of the thread.
+
+ Note: This parameter is not used if the parameter is not specified.
+
+
+
+ The duration on which this thread archives after.
+
+ Note: Options and
+ are only available for guilds that are boosted. You can check in the to see if the
+ guild has the THREE_DAY_THREAD_ARCHIVE and SEVEN_DAY_THREAD_ARCHIVE.
+
+
+ The message which to start the thread from.
+ The options to be used when sending the request.
+
+ A task that represents the asynchronous create operation. The task result contains a
+
+
@@ -2506,6 +2538,163 @@
+
+
+ Represents a thread channel recieved over REST.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Gets the parent text channel id.
+
+
+
+
+ Gets a user within this thread.
+
+ The id of the user to fetch.
+ The options to be used when sending the request.
+
+ A task representing the asyncronous get operation. The task returns a
+ if found, otherwise .
+
+
+
+
+ Gets a collection of users within this thread.
+
+ The options to be used when sending the request.
+
+ A task representing the asyncronous get operation. The task returns a
+ of 's.
+
+
+
+
+
+
+
+
+ This method is not supported in threads.
+
+
+
+
+
+ This method is not supported in threads.
+
+
+
+
+
+ This method is not supported in threads.
+
+
+
+
+
+ This method is not supported in threads.
+
+
+
+
+
+ This method is not supported in threads.
+
+
+
+
+
+ This method is not supported in threads.
+
+
+
+
+
+ This method is not supported in threads.
+
+
+
+
+
+ This method is not supported in threads.
+
+
+
+
+
+ This method is not supported in threads.
+
+
+
+
+
+ This method is not supported in threads.
+
+
+
+
+
+ This method is not supported in threads.
+
+
+
+
+
+ This method is not supported in threads.
+
+
+
+
+
+ This method is not supported in threads.
+
+
+
+
+
+ This method is not supported in threads.
+
+
+
+
+
+ This method is not supported in threads.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Represents a REST-based voice channel in a guild.
@@ -4271,6 +4460,35 @@
Unable to modify this object using a different token.
+
+
+ Represents a thread user recieved over the REST api.
+
+
+
+
+ Gets the this user is in.
+
+
+
+
+ Gets the timestamp for when this user joined this thread.
+
+
+
+
+ Gets the guild this user is in.
+
+
+
+
+ Gets the guild user for this thread user.
+
+
+ A task representing the asyncronous get operation. The task returns a
+ that represents the current thread user.
+
+
Represents a REST-based user.
diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs
index b312fd99f..51e98c1a3 100644
--- a/src/Discord.Net.Rest/DiscordRestApiClient.cs
+++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs
@@ -415,6 +415,15 @@ namespace Discord.API
}
// Threads
+ public async Task ModifyThreadAsync(ulong channelId, ModifyThreadParams args, RequestOptions options = null)
+ {
+ Preconditions.NotEqual(channelId, 0, nameof(channelId));
+
+ var bucket = new BucketIds(channelId: channelId);
+
+ return await SendJsonAsync("PATCH", () => $"channels/{channelId}", args, bucket, options: options);
+ }
+
public async Task StartThreadAsync(ulong channelId, ulong messageId, StartThreadParams args, RequestOptions options = null)
{
Preconditions.NotEqual(channelId, 0, nameof(channelId));
@@ -426,7 +435,7 @@ namespace Discord.API
return await SendJsonAsync("POST", () => $"channels/{channelId}/messages/{messageId}/threads", args, bucket, options: options).ConfigureAwait(false);
}
-
+
public async Task StartThreadAsync(ulong channelId, StartThreadParams args, RequestOptions options = null)
{
Preconditions.NotEqual(channelId, 0, nameof(channelId));
@@ -484,7 +493,7 @@ namespace Discord.API
var bucket = new BucketIds(channelId: channelId);
- return await SendAsync("GET", () => $"/channels/{channelId}", bucket, options: options);
+ return await SendAsync("GET", () => $"channels/{channelId}/thread-members", bucket, options: options);
}
public async Task GetActiveThreadsAsync(ulong channelId, RequestOptions options = null)
diff --git a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs
index f12977d62..6343aed03 100644
--- a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs
+++ b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs
@@ -41,14 +41,14 @@ namespace Discord.Rest
{
base.Update(model);
CategoryId = model.CategoryId;
- Topic = model.Topic.Value;
+ Topic = model.Topic.GetValueOrDefault();
if (model.SlowMode.IsSpecified)
SlowModeInterval = model.SlowMode.Value;
IsNsfw = model.Nsfw.GetValueOrDefault();
}
///
- public async Task ModifyAsync(Action func, RequestOptions options = null)
+ public virtual async Task ModifyAsync(Action func, RequestOptions options = null)
{
var model = await ChannelHelper.ModifyAsync(this, Discord, func, options).ConfigureAwait(false);
Update(model);
@@ -173,7 +173,7 @@ namespace Discord.Rest
/// A task that represents the asynchronous creation operation. The task result contains the newly created
/// webhook.
///
- public Task CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null)
+ public virtual Task CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null)
=> ChannelHelper.CreateWebhookAsync(this, Discord, name, avatar, options);
///
/// Gets a webhook available in this text channel.
@@ -184,7 +184,7 @@ namespace Discord.Rest
/// A task that represents the asynchronous get operation. The task result contains a webhook associated
/// with the identifier; null if the webhook is not found.
///
- public Task GetWebhookAsync(ulong id, RequestOptions options = null)
+ public virtual Task GetWebhookAsync(ulong id, RequestOptions options = null)
=> ChannelHelper.GetWebhookAsync(this, Discord, id, options);
///
/// Gets the webhooks available in this text channel.
@@ -194,7 +194,7 @@ namespace Discord.Rest
/// A task that represents the asynchronous get operation. The task result contains a read-only collection
/// of webhooks that is available in this channel.
///
- public Task> GetWebhooksAsync(RequestOptions options = null)
+ public virtual Task> GetWebhooksAsync(RequestOptions options = null)
=> ChannelHelper.GetWebhooksAsync(this, Discord, options);
///
@@ -205,7 +205,7 @@ namespace Discord.Rest
/// A task that represents the asynchronous get operation. The task result contains the category channel
/// representing the parent of this channel; null if none is set.
///
- public Task GetCategoryAsync(RequestOptions options = null)
+ public virtual Task GetCategoryAsync(RequestOptions options = null)
=> ChannelHelper.GetCategoryAsync(this, Discord, options);
///
public Task SyncPermissionsAsync(RequestOptions options = null)
@@ -213,18 +213,55 @@ namespace Discord.Rest
//Invites
///
- public async Task CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
+ public virtual async Task 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 Task CreateInviteToApplicationAsync(ulong applicationId, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
+ public virtual Task CreateInviteToApplicationAsync(ulong applicationId, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
=> throw new NotImplementedException();
- public Task CreateInviteToStreamAsync(IUser user, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
+ public virtual Task CreateInviteToStreamAsync(IUser user, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
=> throw new NotImplementedException();
///
- public async Task> GetInvitesAsync(RequestOptions options = null)
+ public virtual async Task> GetInvitesAsync(RequestOptions options = null)
=> await ChannelHelper.GetInvitesAsync(this, Discord, options).ConfigureAwait(false);
private string DebuggerDisplay => $"{Name} ({Id}, Text)";
+ ///
+ /// Creates a thread within this .
+ ///
+ ///
+ /// When is the thread type will be based off of the
+ /// channel its created in. When called on a , it creates a .
+ /// When called on a , it creates a . The id of the created
+ /// thread will be the same as the id of the message, and as such a message can only have a
+ /// single thread created from it.
+ ///
+ /// The name of the thread.
+ ///
+ /// The type of the thread.
+ ///
+ /// Note: This parameter is not used if the parameter is not specified.
+ ///
+ ///
+ ///
+ /// The duration on which this thread archives after.
+ ///
+ /// Note: Options and
+ /// are only available for guilds that are boosted. You can check in the to see if the
+ /// guild has the THREE_DAY_THREAD_ARCHIVE and SEVEN_DAY_THREAD_ARCHIVE.
+ ///
+ ///
+ /// The message which to start the thread from.
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous create operation. The task result contains a
+ ///
+ public async Task CreateThreadAsync(string name, ThreadType type = ThreadType.PublicThread,
+ ThreadArchiveDuration autoArchiveDuration = ThreadArchiveDuration.OneDay, IMessage message = null, RequestOptions options = null)
+ {
+ var model = await ThreadHelper.CreateThreadAsync(Discord, this, name, type, autoArchiveDuration, message, options);
+ return RestThreadChannel.Create(Discord, this.Guild, model);
+ }
+
//ITextChannel
///
async Task ITextChannel.CreateWebhookAsync(string name, Stream avatar, RequestOptions options)
@@ -236,6 +273,9 @@ namespace Discord.Rest
async Task> ITextChannel.GetWebhooksAsync(RequestOptions options)
=> await GetWebhooksAsync(options).ConfigureAwait(false);
+ async Task ITextChannel.CreateThreadAsync(string name, ThreadType type, ThreadArchiveDuration autoArchiveDuration, IMessage message, RequestOptions options)
+ => await CreateThreadAsync(name, type, autoArchiveDuration, message, options);
+
//IMessageChannel
///
async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options)
diff --git a/src/Discord.Net.Rest/Entities/Channels/RestThreadChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestThreadChannel.cs
new file mode 100644
index 000000000..640b7443b
--- /dev/null
+++ b/src/Discord.Net.Rest/Entities/Channels/RestThreadChannel.cs
@@ -0,0 +1,230 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Model = Discord.API.Channel;
+
+namespace Discord.Rest
+{
+ ///
+ /// Represents a thread channel recieved over REST.
+ ///
+ public class RestThreadChannel : RestTextChannel, IThreadChannel
+ {
+ public ThreadType Type { get; private set; }
+ ///
+ public bool Joined { get; private set; }
+
+ ///
+ public bool Archived { get; private set; }
+
+ ///
+ public ThreadArchiveDuration AutoArchiveDuration { get; private set; }
+
+ ///
+ public DateTimeOffset ArchiveTimestamp { get; private set; }
+
+ ///
+ public bool Locked { get; private set; }
+
+ ///
+ public int MemberCount { get; private set; }
+
+ ///
+ public int MessageCount { get; private set; }
+
+ ///
+ /// Gets the parent text channel id.
+ ///
+ public ulong ParentChannelId { get; private set; }
+
+ internal RestThreadChannel(BaseDiscordClient discord, IGuild guild, ulong id)
+ : base(discord, guild, id)
+ {
+
+ }
+
+ internal new static RestThreadChannel Create(BaseDiscordClient discord, IGuild guild, Model model)
+ {
+ var entity = new RestThreadChannel(discord, guild, model.Id);
+ entity.Update(model);
+ return entity;
+ }
+
+ internal override void Update(Model model)
+ {
+ base.Update(model);
+
+ this.Joined = model.ThreadMember.IsSpecified;
+
+ if (model.ThreadMetadata.IsSpecified)
+ {
+ this.Archived = model.ThreadMetadata.Value.Archived;
+ this.AutoArchiveDuration = model.ThreadMetadata.Value.AutoArchiveDuration;
+ this.ArchiveTimestamp = model.ThreadMetadata.Value.ArchiveTimestamp;
+ this.Locked = model.ThreadMetadata.Value.Locked.GetValueOrDefault(false);
+
+ }
+
+ this.MemberCount = model.MemberCount.GetValueOrDefault(0);
+ this.MessageCount = model.MessageCount.GetValueOrDefault(0);
+ this.Type = (ThreadType)model.Type;
+ this.ParentChannelId = model.CategoryId.Value;
+ }
+
+ ///
+ /// Gets a user within this thread.
+ ///
+ /// The id of the user to fetch.
+ /// The options to be used when sending the request.
+ ///
+ /// A task representing the asyncronous get operation. The task returns a
+ /// if found, otherwise .
+ ///
+ public new Task GetUserAsync(ulong userId, RequestOptions options = null)
+ => ThreadHelper.GetUserAsync(userId, this, Discord, options);
+
+ ///
+ /// Gets a collection of users within this thread.
+ ///
+ /// The options to be used when sending the request.
+ ///
+ /// A task representing the asyncronous get operation. The task returns a
+ /// of 's.
+ ///
+ public new async Task> GetUsersAsync(RequestOptions options = null)
+ => (await ThreadHelper.GetUsersAsync(this, Discord, options).ConfigureAwait(false)).ToImmutableArray();
+
+ ///
+ public override async Task ModifyAsync(Action func, RequestOptions options = null)
+ {
+ var model = await ThreadHelper.ModifyAsync(this, Discord, func, options);
+ Update(model);
+ }
+
+ ///
+ ///
+ /// This method is not supported in threads.
+ ///
+ public override Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, RequestOptions options = null)
+ => throw new NotImplementedException();
+
+ ///
+ ///
+ /// This method is not supported in threads.
+ ///
+ public override Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions, RequestOptions options = null)
+ => throw new NotImplementedException();
+
+ ///
+ ///
+ /// This method is not supported in threads.
+ ///
+ public override Task CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
+ => throw new NotImplementedException();
+
+ ///
+ ///
+ /// This method is not supported in threads.
+ ///
+ public override Task CreateInviteToApplicationAsync(ulong applicationId, int? maxAge, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
+ => throw new NotImplementedException();
+
+ ///
+ ///
+ /// This method is not supported in threads.
+ ///
+ public override Task CreateInviteToStreamAsync(IUser user, int? maxAge, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
+ => throw new NotImplementedException();
+
+ ///
+ ///
+ /// This method is not supported in threads.
+ ///
+ public override Task CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null)
+ => throw new NotImplementedException();
+
+ ///
+ ///
+ /// This method is not supported in threads.
+ ///
+ public override Task GetCategoryAsync(RequestOptions options = null)
+ => throw new NotImplementedException();
+
+ ///
+ ///
+ /// This method is not supported in threads.
+ ///
+ public override Task> GetInvitesAsync(RequestOptions options = null)
+ => throw new NotImplementedException();
+
+ ///
+ ///
+ /// This method is not supported in threads.
+ ///
+ public override OverwritePermissions? GetPermissionOverwrite(IRole role)
+ => throw new NotImplementedException();
+
+ ///
+ ///
+ /// This method is not supported in threads.
+ ///
+ public override OverwritePermissions? GetPermissionOverwrite(IUser user)
+ => throw new NotImplementedException();
+
+ ///
+ ///
+ /// This method is not supported in threads.
+ ///
+ public override Task GetWebhookAsync(ulong id, RequestOptions options = null)
+ => throw new NotImplementedException();
+
+ ///
+ ///
+ /// This method is not supported in threads.
+ ///
+ public override Task> GetWebhooksAsync(RequestOptions options = null)
+ => throw new NotImplementedException();
+
+ ///
+ ///
+ /// This method is not supported in threads.
+ ///
+ public override Task RemovePermissionOverwriteAsync(IRole role, RequestOptions options = null)
+ => throw new NotImplementedException();
+
+ ///
+ ///
+ /// This method is not supported in threads.
+ ///
+ public override Task RemovePermissionOverwriteAsync(IUser user, RequestOptions options = null)
+ => throw new NotImplementedException();
+
+ ///
+ ///
+ /// This method is not supported in threads.
+ ///
+ public override IReadOnlyCollection PermissionOverwrites
+ => throw new NotImplementedException();
+
+
+ ///
+ public Task JoinAsync(RequestOptions options = null)
+ => Discord.ApiClient.JoinThreadAsync(this.Id, options);
+
+ ///
+ public Task LeaveAsync(RequestOptions options = null)
+ => Discord.ApiClient.LeaveThreadAsync(this.Id, options);
+
+ ///
+ public Task AddUserAsync(IGuildUser user, RequestOptions options = null)
+ => Discord.ApiClient.AddThreadMemberAsync(this.Id, user.Id, options);
+
+ ///
+ public Task RemoveUserAsync(IGuildUser user, RequestOptions options = null)
+ => Discord.ApiClient.RemoveThreadMemberAsync(this.Id, user.Id, options);
+ }
+}
diff --git a/src/Discord.Net.Rest/Entities/Channels/ThreadHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ThreadHelper.cs
new file mode 100644
index 000000000..958210d85
--- /dev/null
+++ b/src/Discord.Net.Rest/Entities/Channels/ThreadHelper.cs
@@ -0,0 +1,66 @@
+using Discord.API.Rest;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Model = Discord.API.Channel;
+
+namespace Discord.Rest
+{
+ internal static class ThreadHelper
+ {
+ public static async Task CreateThreadAsync(BaseDiscordClient client, ITextChannel channel, string name, ThreadType type = ThreadType.PublicThread,
+ ThreadArchiveDuration autoArchiveDuration = ThreadArchiveDuration.OneDay, IMessage message = null, RequestOptions options = null)
+ {
+ if (autoArchiveDuration == ThreadArchiveDuration.OneWeek && !channel.Guild.Features.Contains("SEVEN_DAY_THREAD_ARCHIVE"))
+ throw new ArgumentException($"The guild {channel.Guild.Name} does not have the SEVEN_DAY_THREAD_ARCHIVE feature!");
+
+ if (autoArchiveDuration == ThreadArchiveDuration.ThreeDays && !channel.Guild.Features.Contains("THREE_DAY_THREAD_ARCHIVE"))
+ throw new ArgumentException($"The guild {channel.Guild.Name} does not have the THREE_DAY_THREAD_ARCHIVE feature!");
+
+ var args = new StartThreadParams()
+ {
+ Name = name,
+ Duration = autoArchiveDuration,
+ Type = type
+ };
+
+ Model model = null;
+
+ if (message != null)
+ model = await client.ApiClient.StartThreadAsync(channel.Id, message.Id, args, options).ConfigureAwait(false);
+ else
+ model = await client.ApiClient.StartThreadAsync(channel.Id, args, options).ConfigureAwait(false);
+
+ return model;
+ }
+
+ public static async Task ModifyAsync(IThreadChannel channel, BaseDiscordClient client,
+ Action func,
+ RequestOptions options)
+ {
+ var args = new TextChannelProperties();
+ func(args);
+ var apiArgs = new API.Rest.ModifyThreadParams
+ {
+ Name = args.Name,
+ Archived = args.Archived,
+ AutoArchiveDuration = args.AutoArchiveDuration,
+ Locked = args.Locked,
+ Slowmode = args.SlowModeInterval
+ };
+ return await client.ApiClient.ModifyThreadAsync(channel.Id, apiArgs, options).ConfigureAwait(false);
+ }
+
+ public static async Task GetUsersAsync(IThreadChannel channel, BaseDiscordClient client, RequestOptions options = null)
+ {
+ var users = await client.ApiClient.ListThreadMembersAsync(channel.Id, options);
+
+ return users.Select(x => RestThreadUser.Create(client, channel.Guild, x, channel)).ToArray();
+ }
+
+ public static async Task GetUserAsync(ulong userdId, IThreadChannel channel, BaseDiscordClient client, RequestOptions options = null)
+ => (await GetUsersAsync(channel, client, options).ConfigureAwait(false)).FirstOrDefault(x => x.Id == userdId);
+ }
+}
diff --git a/src/Discord.Net.Rest/Entities/Users/RestThreadUser.cs b/src/Discord.Net.Rest/Entities/Users/RestThreadUser.cs
new file mode 100644
index 000000000..d74591e75
--- /dev/null
+++ b/src/Discord.Net.Rest/Entities/Users/RestThreadUser.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Model = Discord.API.ThreadMember;
+
+namespace Discord.Rest
+{
+ ///
+ /// Represents a thread user recieved over the REST api.
+ ///
+ public class RestThreadUser : RestEntity
+ {
+ ///
+ /// Gets the this user is in.
+ ///
+ public IThreadChannel Thread { get; }
+
+ ///
+ /// Gets the timestamp for when this user joined this thread.
+ ///
+ public DateTimeOffset JoinedAt { get; private set; }
+
+ ///
+ /// Gets the guild this user is in.
+ ///
+ public IGuild Guild { get; }
+
+ internal RestThreadUser(BaseDiscordClient discord, IGuild guild, IThreadChannel channel, ulong id)
+ : base(discord, id)
+ {
+ this.Guild = guild;
+ this.Thread = channel;
+ }
+
+ internal static RestThreadUser Create(BaseDiscordClient client, IGuild guild, Model model, IThreadChannel channel)
+ {
+ var entity = new RestThreadUser(client, guild, channel, model.UserId.Value);
+ entity.Update(model);
+ return entity;
+ }
+
+ internal void Update(Model model)
+ {
+ this.JoinedAt = model.JoinTimestamp;
+ }
+
+ ///
+ /// Gets the guild user for this thread user.
+ ///
+ ///
+ /// A task representing the asyncronous get operation. The task returns a
+ /// that represents the current thread user.
+ ///
+ public Task GetGuildUser()
+ => Guild.GetUserAsync(this.Id);
+ }
+}
diff --git a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs
index f29f13cf6..d49afff25 100644
--- a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs
+++ b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs
@@ -572,5 +572,25 @@ namespace Discord.WebSocket
}
internal readonly AsyncEvent, Task>> _threadDeleted = new AsyncEvent, Task>>();
+ ///
+ /// Fired when a user joins a thread
+ ///
+ public event Func ThreadMemberJoined
+ {
+ add { _threadMemberJoined.Add(value); }
+ remove { _threadMemberJoined.Remove(value); }
+ }
+ internal readonly AsyncEvent> _threadMemberJoined = new AsyncEvent>();
+
+ ///
+ /// Fired when a user leaves a thread
+ ///
+ public event Func ThreadMemberLeft
+ {
+ add { _threadMemberLeft.Add(value); }
+ remove { _threadMemberLeft.Remove(value); }
+ }
+ internal readonly AsyncEvent> _threadMemberLeft = new AsyncEvent>();
+
}
}
diff --git a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml
index 8db8a1340..8c6f031fa 100644
--- a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml
+++ b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml
@@ -771,6 +771,16 @@
Fired when a thread is deleted.
+
+
+ Fired when a user joins a thread
+
+
+
+
+ Fired when a user leaves a thread
+
+
@@ -2380,6 +2390,16 @@
Represents a thread channel inside of a guild.
+
+
+ Gets the owner of the current thread.
+
+
+
+
+ Gets the current users within this thread.
+
+
@@ -2411,182 +2431,152 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+ Gets a collection of cached users within this thread.
+
-
+
-
+
- Gets a message from this message channel.
+ Gets all users inside this thread.
- This method follows the same behavior as described in .
- Please visit its documentation for more details on this method.
+ If all users are not downloaded then this method will call and return the result.
- The snowflake identifier of the message.
The options to be used when sending the request.
-
- A task that represents an asynchronous get operation for retrieving the message. The task result contains
- the retrieved message; null if no message is found with the specified identifier.
-
+ A task representing the download operation.
-
+
- Gets the last N messages from this message channel.
+ Downloads all users that have access to this thread.
-
- This method follows the same behavior as described in .
- Please visit its documentation for more details on this method.
-
- The numbers of message to be gotten from.
The options to be used when sending the request.
-
- Paged collection of messages.
-
+ A task representing the asyncronous download operation.
+
+
+
-
+
+
+
+
- Gets a collection of messages in this channel.
+ Adds a user to this thread.
-
- This method follows the same behavior as described in .
- Please visit its documentation for more details on this method.
-
- The ID of the starting message to get the messages from.
- The direction of the messages to be gotten from.
- The numbers of message to be gotten from.
+ The to add.
The options to be used when sending the request.
- Paged collection of messages.
+ A task that represents the asynchronous operation of adding a member to a thread.
-
+
- Gets a collection of messages in this channel.
+ Removes a user from this thread.
-
- This method follows the same behavior as described in .
- Please visit its documentation for more details on this method.
-
- The starting message to get the messages from.
- The direction of the messages to be gotten from.
- The numbers of message to be gotten from.
+ The to remove from this thread.
The options to be used when sending the request.
- Paged collection of messages.
+ A task that represents the asynchronous operation of removing a user from this thread.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Message content is too long, length must be less or equal to .
-
-
-
-
-
-
- Message content is too long, length must be less or equal to .
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ This method is not supported in threads.
+
-
-
+
+
+
+ This method is not supported in threads.
+
-
-
+
+
+
+ This method is not supported in threads.
+
-
-
+
+
+
+ This method is not supported in threads.
+
-
-
+
+
+
+ This method is not supported in threads.
+
-
-
+
+
+
+ This method is not supported in threads.
+
-
-
+
+
+
+ This method is not supported in threads.
+
-
-
+
+
+
+ This method is not supported in threads.
+
-
-
+
+
+
+ This method is not supported in threads.
+
-
-
+
+
+
+ This method is not supported in threads.
+
-
-
+
+
+
+ This method is not supported in threads.
+
-
-
+
+
+
+ This method is not supported in threads.
+
-
-
+
+
+
+ This method is not supported in threads.
+
-
-
+
+
+
+ This method is not supported in threads.
+
-
-
+
+
+
+ This method is not supported in threads.
+
-
-
+
+
+
+ This method is not supported in threads.
+
@@ -4535,6 +4525,11 @@
+
+
+ Represents a thread user received over the gateway.
+
+
Gets the this user is in.
@@ -4565,9 +4560,6 @@
-
-
-
@@ -4580,27 +4572,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -4658,15 +4629,6 @@
-
-
-
-
-
-
-
-
-
diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs
index c52675d66..0c55eeab6 100644
--- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs
+++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs
@@ -1975,63 +1975,58 @@ namespace Discord.WebSocket
// Threads
case "THREAD_CREATE":
{
- try
- {
- await _gatewayLogger.DebugAsync("Received Dispatch (THREAD_CREATE)").ConfigureAwait(false);
+ await _gatewayLogger.DebugAsync("Received Dispatch (THREAD_CREATE)").ConfigureAwait(false);
- var data = (payload as JToken).ToObject(_serializer);
+ var data = (payload as JToken).ToObject(_serializer);
- var guild = State.GetGuild(data.GuildId.Value);
+ var guild = State.GetGuild(data.GuildId.Value);
- if (guild == null)
- {
- await UnknownGuildAsync(type, data.GuildId.Value);
- return;
- }
+ if (guild == null)
+ {
+ await UnknownGuildAsync(type, data.GuildId.Value);
+ return;
+ }
- var threadChannel = (SocketThreadChannel)guild.AddChannel(this.State, data);
+ SocketThreadChannel threadChannel = null;
- await TimedInvokeAsync(_threadCreated, nameof(ThreadCreated), threadChannel).ConfigureAwait(false);
+ if ((threadChannel = guild.ThreadChannels.FirstOrDefault(x => x.Id == data.Id)) != null)
+ {
+ threadChannel.Update(this.State, data);
+ threadChannel.AddOrUpdateThreadMember(data.ThreadMember.Value, guild.CurrentUser);
}
- catch(Exception x)
+ else
{
-
+ threadChannel = (SocketThreadChannel)guild.AddChannel(this.State, data);
+ threadChannel.AddOrUpdateThreadMember(data.ThreadMember.Value, guild.CurrentUser);
+ await TimedInvokeAsync(_threadCreated, nameof(ThreadCreated), threadChannel).ConfigureAwait(false);
}
}
break;
case "THREAD_UPDATE":
{
- try
- {
- await _gatewayLogger.DebugAsync("Received Dispatch (THREAD_UPDATE)").ConfigureAwait(false);
-
- var data = (payload as JToken).ToObject(_serializer);
- var guild = State.GetGuild(data.GuildId.Value);
- if (guild == null)
- {
- await UnknownGuildAsync(type, data.GuildId.Value);
- return;
- }
+ await _gatewayLogger.DebugAsync("Received Dispatch (THREAD_UPDATE)").ConfigureAwait(false);
- var channel = (SocketThreadChannel)guild.GetChannel(data.Id);
+ var data = (payload as JToken).ToObject(_serializer);
+ var guild = State.GetGuild(data.GuildId.Value);
+ if (guild == null)
+ {
+ await UnknownGuildAsync(type, data.GuildId.Value);
+ return;
+ }
- var before = channel.Clone();
- channel.Update(State, data);
+ var channel = (SocketThreadChannel)guild.GetChannel(data.Id);
- if (!(guild?.IsSynced ?? true))
- {
- await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false);
- return;
- }
+ var before = channel.Clone();
+ channel.Update(State, data);
- await TimedInvokeAsync(_threadUpdated, nameof(ThreadUpdated), before, channel).ConfigureAwait(false);
- }
- catch(Exception x)
+ if (!(guild?.IsSynced ?? true))
{
-
+ await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false);
+ return;
}
+ await TimedInvokeAsync(_threadUpdated, nameof(ThreadUpdated), before, channel).ConfigureAwait(false);
}
break;
case "THREAD_DELETE":
@@ -2075,19 +2070,30 @@ namespace Discord.WebSocket
if(entity == null)
{
- guild.AddChannel(this.State, thread);
+ entity = (SocketThreadChannel)guild.AddChannel(this.State, thread);
}
else
{
entity.Update(this.State, thread);
}
+
+ foreach(var member in data.Members.Where(x => x.Id.Value == entity.Id))
+ {
+ var guildMember = guild.GetUser(member.Id.Value);
+
+ entity.AddOrUpdateThreadMember(member, guildMember);
+ }
}
}
break;
case "THREAD_MEMBER_UPDATE":
{
await _gatewayLogger.DebugAsync("Received Dispatch (THREAD_MEMBER_UPDATE)").ConfigureAwait(false);
- var p = payload;
+
+ var data = (payload as JToken).ToObject(_serializer);
+
+ //var guild = State.GetGuild(data.)
+
}
break;
@@ -2097,6 +2103,69 @@ namespace Discord.WebSocket
var data = (payload as JToken).ToObject(_serializer);
+ var guild = State.GetGuild(data.GuildId);
+
+ if (guild == null)
+ {
+ await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false);
+ return;
+ }
+
+ var thread = (SocketThreadChannel)guild.GetChannel(data.Id);
+
+ if(thread == null)
+ {
+ await UnknownChannelAsync(type, data.Id);
+ return;
+ }
+
+ IReadOnlyCollection leftUsers = null;
+ IReadOnlyCollection joinUsers = null;
+
+
+ if (data.RemovedMemberIds.IsSpecified)
+ {
+ leftUsers = thread.RemoveUsers(data.RemovedMemberIds.Value);
+ }
+
+ if (data.AddedMembers.IsSpecified)
+ {
+ List newThreadMembers = new List();
+ foreach(var threadMember in data.AddedMembers.Value)
+ {
+ SocketGuildUser guildMember;
+
+ if (threadMember.Member.IsSpecified)
+ {
+ guildMember = guild.AddOrUpdateUser(threadMember.Member.Value);
+ }
+ else
+ {
+ guildMember = guild.GetUser(threadMember.UserId.Value);
+ }
+
+ newThreadMembers.Add(thread.AddOrUpdateThreadMember(threadMember, guildMember));
+ }
+
+ if (newThreadMembers.Any())
+ joinUsers = newThreadMembers.ToImmutableArray();
+ }
+
+ if (leftUsers != null)
+ {
+ foreach(var threadUser in leftUsers)
+ {
+ await TimedInvokeAsync(_threadMemberLeft, nameof(ThreadMemberLeft), threadUser).ConfigureAwait(false);
+ }
+ }
+
+ if(joinUsers != null)
+ {
+ foreach(var threadUser in joinUsers)
+ {
+ await TimedInvokeAsync(_threadMemberJoined, nameof(ThreadMemberJoined), threadUser).ConfigureAwait(false);
+ }
+ }
}
break;
@@ -2138,6 +2207,7 @@ namespace Discord.WebSocket
catch (Exception ex)
{
await _gatewayLogger.ErrorAsync($"Error handling {opCode}{(type != null ? $" ({type})" : "")}", ex).ConfigureAwait(false);
+ Console.WriteLine(ex);
}
}
diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs
index 5cfbcc1a8..4a1dc45c7 100644
--- a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs
+++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs
@@ -70,6 +70,7 @@ namespace Discord.WebSocket
{
case SocketDMChannel dmChannel: dmChannel.AddMessage(msg); break;
case SocketGroupChannel groupChannel: groupChannel.AddMessage(msg); break;
+ case SocketThreadChannel threadChannel: threadChannel.AddMessage(msg); break;
case SocketTextChannel textChannel: textChannel.AddMessage(msg); break;
default: throw new NotSupportedException($"Unexpected {nameof(ISocketMessageChannel)} type.");
}
diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs
index 192d74328..ca9164a46 100644
--- a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs
+++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs
@@ -78,9 +78,46 @@ namespace Discord.WebSocket
}
///
- public Task ModifyAsync(Action func, RequestOptions options = null)
+ public virtual Task ModifyAsync(Action func, RequestOptions options = null)
=> ChannelHelper.ModifyAsync(this, Discord, func, options);
+ ///
+ /// Creates a thread within this .
+ ///
+ ///
+ /// When is the thread type will be based off of the
+ /// channel its created in. When called on a , it creates a .
+ /// When called on a , it creates a . The id of the created
+ /// thread will be the same as the id of the message, and as such a message can only have a
+ /// single thread created from it.
+ ///
+ /// The name of the thread.
+ ///
+ /// The type of the thread.
+ ///
+ /// Note: This parameter is not used if the parameter is not specified.
+ ///
+ ///
+ ///
+ /// The duration on which this thread archives after.
+ ///
+ /// Note: Options and
+ /// are only available for guilds that are boosted. You can check in the to see if the
+ /// guild has the THREE_DAY_THREAD_ARCHIVE and SEVEN_DAY_THREAD_ARCHIVE.
+ ///
+ ///
+ /// The message which to start the thread from.
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous create operation. The task result contains a
+ ///
+ public async Task CreateThreadAsync(string name, ThreadType type = ThreadType.PublicThread,
+ ThreadArchiveDuration autoArchiveDuration = ThreadArchiveDuration.OneDay, IMessage message = null, RequestOptions options = null)
+ {
+ var model = await ThreadHelper.CreateThreadAsync(Discord, this, name, type, autoArchiveDuration, message, options);
+ return SocketThreadChannel.Create(this.Guild, Discord.State, model);
+ }
+
//Messages
///
public SocketMessage GetCachedMessage(ulong id)
@@ -235,7 +272,7 @@ namespace Discord.WebSocket
/// A task that represents the asynchronous creation operation. The task result contains the newly created
/// webhook.
///
- public Task CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null)
+ public virtual Task CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null)
=> ChannelHelper.CreateWebhookAsync(this, Discord, name, avatar, options);
///
/// Gets a webhook available in this text channel.
@@ -246,7 +283,7 @@ namespace Discord.WebSocket
/// A task that represents the asynchronous get operation. The task result contains a webhook associated
/// with the identifier; null if the webhook is not found.
///
- public Task GetWebhookAsync(ulong id, RequestOptions options = null)
+ public virtual Task GetWebhookAsync(ulong id, RequestOptions options = null)
=> ChannelHelper.GetWebhookAsync(this, Discord, id, options);
///
/// Gets the webhooks available in this text channel.
@@ -256,21 +293,21 @@ namespace Discord.WebSocket
/// A task that represents the asynchronous get operation. The task result contains a read-only collection
/// of webhooks that is available in this channel.
///
- public Task> GetWebhooksAsync(RequestOptions options = null)
+ public virtual Task> GetWebhooksAsync(RequestOptions options = null)
=> ChannelHelper.GetWebhooksAsync(this, Discord, options);
//Invites
///
- public async Task CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
+ public virtual async Task 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 async Task CreateInviteToApplicationAsync(ulong applicationId, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
+ public virtual async Task CreateInviteToApplicationAsync(ulong applicationId, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
=> await ChannelHelper.CreateInviteToApplicationAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, applicationId, options).ConfigureAwait(false);
///
- public async Task CreateInviteToStreamAsync(IUser user, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
+ public virtual async Task CreateInviteToStreamAsync(IUser user, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
=> await ChannelHelper.CreateInviteToStreamAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, user, options).ConfigureAwait(false);
///
- public async Task> GetInvitesAsync(RequestOptions options = null)
+ public virtual async Task> GetInvitesAsync(RequestOptions options = null)
=> await ChannelHelper.GetInvitesAsync(this, Discord, options).ConfigureAwait(false);
private string DebuggerDisplay => $"{Name} ({Id}, Text)";
@@ -286,6 +323,9 @@ namespace Discord.WebSocket
///
async Task> ITextChannel.GetWebhooksAsync(RequestOptions options)
=> await GetWebhooksAsync(options).ConfigureAwait(false);
+ ///
+ async Task ITextChannel.CreateThreadAsync(string name, ThreadType type, ThreadArchiveDuration autoArchiveDuration, IMessage message, RequestOptions options)
+ => await CreateThreadAsync(name, type, autoArchiveDuration, message, options);
//IGuildChannel
///
diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketThreadChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketThreadChannel.cs
index 4072276cb..141d7535c 100644
--- a/src/Discord.Net.WebSocket/Entities/Channels/SocketThreadChannel.cs
+++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketThreadChannel.cs
@@ -9,6 +9,8 @@ using System.Text;
using System.Threading.Tasks;
using Model = Discord.API.Channel;
using ThreadMember = Discord.API.ThreadMember;
+using MemberUpdates = Discord.API.Gateway.ThreadMembersUpdated;
+using System.Collections.Concurrent;
namespace Discord.WebSocket
{
@@ -16,8 +18,21 @@ namespace Discord.WebSocket
/// Represents a thread channel inside of a guild.
///
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
- public class SocketThreadChannel : SocketGuildChannel, IThreadChannel, ISocketMessageChannel
+ public class SocketThreadChannel : SocketTextChannel, IThreadChannel
{
+ ///
+ public ThreadType Type { get; private set; }
+
+ ///
+ /// Gets the owner of the current thread.
+ ///
+ public SocketThreadUser Owner { get; private set; }
+
+ ///
+ /// Gets the current users within this thread.
+ ///
+ public SocketThreadUser CurrentUser
+ => Users.FirstOrDefault(x => x.Id == Discord.CurrentUser.Id);
///
public bool Joined { get; private set; }
@@ -25,7 +40,8 @@ namespace Discord.WebSocket
///
/// if this thread is private, otherwise
///
- public bool IsPrivateThread { get; private set; }
+ public bool IsPrivateThread
+ => this.Type == ThreadType.PrivateThread;
///
/// Gets the parent channel this thread resides in.
@@ -50,37 +66,29 @@ namespace Discord.WebSocket
///
public bool Locked { get; private set; }
- ///
- public bool IsNsfw { get; private set; }
-
- ///
- public string Topic { get; private set; }
-
- ///
- public int SlowModeInterval { get; private set; }
-
- ///
- public string Mention { get; private set; }
+ ///
+ /// Gets a collection of cached users within this thread.
+ ///
+ public new IReadOnlyCollection Users =>
+ _members.Values.ToImmutableArray();
- ///
- public ulong? CategoryId { get; private set; }
- ///
- public IReadOnlyCollection CachedMessages => _messages?.Messages ?? ImmutableArray.Create();
+ private ConcurrentDictionary _members;
- public new IReadOnlyCollection Users = ImmutableArray.Create();
+ private string DebuggerDisplay => $"{Name} ({Id}, Thread)";
- private readonly MessageCache _messages;
+ private bool _usersDownloaded = false;
- private string DebuggerDisplay => $"{Name} ({Id}, Thread)";
+ private object _downloadLock = new object();
internal SocketThreadChannel(DiscordSocketClient discord, SocketGuild guild, ulong id, SocketTextChannel parent)
: base(discord, id, guild)
{
this.ParentChannel = parent;
+ this._members = new ConcurrentDictionary();
}
- internal static SocketThreadChannel Create(SocketGuild guild, ClientState state, Model model)
+ internal new static SocketThreadChannel Create(SocketGuild guild, ClientState state, Model model)
{
var parent = (SocketTextChannel)guild.GetChannel(model.CategoryId.Value);
var entity = new SocketThreadChannel(guild.Discord, guild, model.Id, parent);
@@ -92,10 +100,9 @@ namespace Discord.WebSocket
{
base.Update(state, model);
+ this.Type = (ThreadType)model.Type;
this.MessageCount = model.MessageCount.GetValueOrDefault(-1);
this.MemberCount = model.MemberCount.GetValueOrDefault(-1);
-
- this.IsPrivateThread = model.Type == ChannelType.PrivateThread;
if (model.ThreadMetadata.IsSpecified)
{
@@ -104,259 +111,233 @@ namespace Discord.WebSocket
this.AutoArchiveDuration = (ThreadArchiveDuration)model.ThreadMetadata.Value.AutoArchiveDuration;
this.Locked = model.ThreadMetadata.Value.Locked.GetValueOrDefault(false);
}
+
+ if (model.OwnerId.IsSpecified)
+ {
+ this.Owner = GetUser(model.OwnerId.Value);
+ }
+
+ this.Joined = model.ThreadMember.IsSpecified;
}
- internal void Update(ClientState state, ThreadMember self)
+ internal IReadOnlyCollection RemoveUsers(ulong[] users)
{
+ List threadUsers = new();
- }
+ foreach (var userId in users)
+ {
+ if (_members.TryRemove(userId, out var user))
+ threadUsers.Add(user);
+ }
- ///
- public virtual Task SyncPermissionsAsync(RequestOptions options = null)
- => ChannelHelper.SyncPermissionsAsync(this, Discord, options);
+ return threadUsers.ToImmutableArray();
+ }
- ///
- public Task ModifyAsync(Action func, RequestOptions options = null)
- => ChannelHelper.ModifyAsync(this, Discord, func, options);
+ internal SocketThreadUser AddOrUpdateThreadMember(ThreadMember model, SocketGuildUser guildMember)
+ {
+ if (_members.TryGetValue(model.UserId.Value, out SocketThreadUser member))
+ member.Update(model);
+ else
+ {
+ member = SocketThreadUser.Create(this.Guild, this, model, guildMember);
+ member.GlobalUser.AddRef();
+ _members[member.Id] = member;
+ }
+ return member;
+ }
- //Messages
+ //Users
///
- public SocketMessage GetCachedMessage(ulong id)
- => _messages?.Get(id);
+ public new SocketThreadUser GetUser(ulong id)
+ {
+ var user = Users.FirstOrDefault(x => x.Id == id);
+ return user;
+ }
///
- /// Gets a message from this message channel.
+ /// Gets all users inside this thread.
///
///
- /// This method follows the same behavior as described in .
- /// Please visit its documentation for more details on this method.
+ /// If all users are not downloaded then this method will call and return the result.
///
- /// The snowflake identifier of the message.
/// The options to be used when sending the request.
- ///
- /// A task that represents an asynchronous get operation for retrieving the message. The task result contains
- /// the retrieved message; null if no message is found with the specified identifier.
- ///
- public async Task GetMessageAsync(ulong id, RequestOptions options = null)
+ /// A task representing the download operation.
+ public async Task> GetUsersAsync(RequestOptions options = null)
{
- IMessage msg = _messages?.Get(id);
- if (msg == null)
- msg = await ChannelHelper.GetMessageAsync(this, Discord, id, options).ConfigureAwait(false);
- return msg;
+ // download all users if we havent
+ if (!_usersDownloaded)
+ {
+ await DownloadUsersAsync(options);
+ this._usersDownloaded = true;
+ }
+
+ return this.Users;
}
+
///
- /// Gets the last N messages from this message channel.
+ /// Downloads all users that have access to this thread.
///
- ///
- /// This method follows the same behavior as described in .
- /// Please visit its documentation for more details on this method.
- ///
- /// The numbers of message to be gotten from.
/// The options to be used when sending the request.
- ///
- /// Paged collection of messages.
- ///
- public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
- => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, CacheMode.AllowDownload, options);
+ /// A task representing the asyncronous download operation.
+ public async Task DownloadUsersAsync(RequestOptions options = null)
+ {
+ var users = await Discord.ApiClient.ListThreadMembersAsync(this.Id, options);
+
+ lock (_downloadLock)
+ {
+ foreach (var threadMember in users)
+ {
+ var guildUser = this.Guild.GetUser(threadMember.UserId.Value);
+
+ this.AddOrUpdateThreadMember(threadMember, guildUser);
+ }
+ }
+ }
+
+ internal new SocketThreadChannel Clone() => MemberwiseClone() as SocketThreadChannel;
+
+ ///
+ public Task JoinAsync(RequestOptions options = null)
+ => Discord.ApiClient.JoinThreadAsync(this.Id, options);
+
+ ///
+ public Task LeaveAsync(RequestOptions options = null)
+ => Discord.ApiClient.LeaveThreadAsync(this.Id, options);
///
- /// Gets a collection of messages in this channel.
+ /// Adds a user to this thread.
///
- ///
- /// This method follows the same behavior as described in .
- /// Please visit its documentation for more details on this method.
- ///
- /// The ID of the starting message to get the messages from.
- /// The direction of the messages to be gotten from.
- /// The numbers of message to be gotten from.
+ /// The to add.
/// The options to be used when sending the request.
///
- /// Paged collection of messages.
+ /// A task that represents the asynchronous operation of adding a member to a thread.
///
- public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
- => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, CacheMode.AllowDownload, options);
+ public Task AddUserAsync(IGuildUser user, RequestOptions options = null)
+ => Discord.ApiClient.AddThreadMemberAsync(this.Id, user.Id, options);
///
- /// Gets a collection of messages in this channel.
+ /// Removes a user from this thread.
///
- ///
- /// This method follows the same behavior as described in .
- /// Please visit its documentation for more details on this method.
- ///
- /// The starting message to get the messages from.
- /// The direction of the messages to be gotten from.
- /// The numbers of message to be gotten from.
+ /// The to remove from this thread.
/// The options to be used when sending the request.
///
- /// Paged collection of messages.
+ /// A task that represents the asynchronous operation of removing a user from this thread.
///
- public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
- => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, CacheMode.AllowDownload, options);
-
- ///
- public IReadOnlyCollection GetCachedMessages(int limit = DiscordConfig.MaxMessagesPerBatch)
- => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, null, Direction.Before, limit);
-
- ///
- public IReadOnlyCollection GetCachedMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch)
- => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessageId, dir, limit);
-
- ///
- public IReadOnlyCollection GetCachedMessages(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch)
- => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessage.Id, dir, limit);
-
- ///
- public Task> GetPinnedMessagesAsync(RequestOptions options = null)
- => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options);
-
- ///
- /// Message content is too long, length must be less or equal to .
- public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null)
- => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, allowedMentions, messageReference, component, options);
+ public Task RemoveUserAsync(IGuildUser user, RequestOptions options = null)
+ => Discord.ApiClient.RemoveThreadMemberAsync(this.Id, user.Id, options);
- ///
- public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null)
- => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, allowedMentions, messageReference, component, options, isSpoiler);
-
- ///
- /// Message content is too long, length must be less or equal to .
- public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null)
- => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, allowedMentions, messageReference, component, options, isSpoiler);
-
- ///
- public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null)
- => ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options);
- ///
- public Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null)
- => ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options);
- ///
- public async Task ModifyMessageAsync(ulong messageId, Action func, RequestOptions options = null)
- => await ChannelHelper.ModifyMessageAsync(this, messageId, func, Discord, options).ConfigureAwait(false);
-
- ///
- public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null)
- => ChannelHelper.DeleteMessageAsync(this, messageId, Discord, options);
-
- ///
- public Task DeleteMessageAsync(IMessage message, RequestOptions options = null)
- => ChannelHelper.DeleteMessageAsync(this, message.Id, Discord, options);
-
- ///
- public Task TriggerTypingAsync(RequestOptions options = null)
- => ChannelHelper.TriggerTypingAsync(this, Discord, options);
-
- ///
- public IDisposable EnterTypingState(RequestOptions options = null)
- => ChannelHelper.EnterTypingState(this, Discord, options);
+ ///
+ ///
+ /// This method is not supported in threads.
+ ///
+ public override Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, RequestOptions options = null)
+ => throw new NotImplementedException();
- internal void AddMessage(SocketMessage msg)
- => _messages?.Add(msg);
+ ///
+ ///
+ /// This method is not supported in threads.
+ ///
+ public override Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions, RequestOptions options = null)
+ => throw new NotImplementedException();
- internal SocketMessage RemoveMessage(ulong id)
- => _messages?.Remove(id);
+ ///
+ ///
+ /// This method is not supported in threads.
+ ///
+ public override Task CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
+ => throw new NotImplementedException();
- //Users
- ///
- public new SocketThreadUser GetUser(ulong id)
- {
- var user = Users.FirstOrDefault(x => x.Id == id);
- return user;
- }
+ ///
+ ///
+ /// This method is not supported in threads.
+ ///
+ public override Task CreateInviteToApplicationAsync(ulong applicationId, int? maxAge, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
+ => throw new NotImplementedException();
- public Task GetUsersAsync()
- {
- return Task.CompletedTask;
- }
+ ///
+ ///
+ /// This method is not supported in threads.
+ ///
+ public override Task CreateInviteToStreamAsync(IUser user, int? maxAge, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
+ => throw new NotImplementedException();
-
- internal new SocketThreadChannel Clone() => MemberwiseClone() as SocketThreadChannel;
+ ///
+ ///
+ /// This method is not supported in threads.
+ ///
+ public override Task CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null)
+ => throw new NotImplementedException();
- //ITextChannel
- ///
- Task ITextChannel.CreateWebhookAsync(string name, Stream avatar, RequestOptions options)
- => throw new NotSupportedException("Thread channels don't support webhooks");
- ///
- Task ITextChannel.GetWebhookAsync(ulong id, RequestOptions options)
- => throw new NotSupportedException("Thread channels don't support webhooks");
- ///
- Task> ITextChannel.GetWebhooksAsync(RequestOptions options)
- => throw new NotSupportedException("Thread channels don't support webhooks");
+ ///
+ ///
+ /// This method is not supported in threads.
+ ///
+ public override Task> GetInvitesAsync(RequestOptions options = null)
+ => throw new NotImplementedException();
- //IGuildChannel
- ///
- Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options)
- => Task.FromResult(GetUser(id));
- ///
- IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options)
- => ImmutableArray.Create>(Users).ToAsyncEnumerable();
+ ///
+ ///
+ /// This method is not supported in threads.
+ ///
+ public override OverwritePermissions? GetPermissionOverwrite(IRole role)
+ => throw new NotImplementedException();
- //IMessageChannel
- ///
- async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options)
- {
- if (mode == CacheMode.AllowDownload)
- return await GetMessageAsync(id, options).ConfigureAwait(false);
- else
- return GetCachedMessage(id);
- }
- ///
- IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options)
- => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, mode, options);
- ///
- IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options)
- => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, mode, options);
- ///
- IAsyncEnumerable> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode, RequestOptions options)
- => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, mode, options);
- ///
- async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options)
- => await GetPinnedMessagesAsync(options).ConfigureAwait(false);
+ ///
+ ///
+ /// This method is not supported in threads.
+ ///
+ public override OverwritePermissions? GetPermissionOverwrite(IUser user)
+ => throw new NotImplementedException();
- ///
- async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent component)
- => await SendFileAsync(filePath, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference, component).ConfigureAwait(false);
- ///
- async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent component)
- => await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference, component).ConfigureAwait(false);
- ///
- async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent component)
- => await SendMessageAsync(text, isTTS, embed, options, allowedMentions, messageReference, component).ConfigureAwait(false);
+ ///
+ ///
+ /// This method is not supported in threads.
+ ///
+ public override Task GetWebhookAsync(ulong id, RequestOptions options = null)
+ => throw new NotImplementedException();
- // INestedChannel
- ///
- Task INestedChannel.GetCategoryAsync(CacheMode mode, RequestOptions options)
- => Task.FromResult(this.ParentChannel.Category);
- Task INestedChannel.CreateInviteAsync(int? maxAge, int? maxUses, bool isTemporary, bool isUnique, RequestOptions options)
- => throw new NotSupportedException("Thread channels don't support invites");
- Task INestedChannel.CreateInviteToApplicationAsync(ulong applicationId, int? maxAge, int? maxUses, bool isTemporary, bool isUnique, RequestOptions options)
- => throw new NotSupportedException("Thread channels don't support invites");
- Task INestedChannel.CreateInviteToStreamAsync(IUser user, int? maxAge, int? maxUses, bool isTemporary, bool isUnique, RequestOptions options)
- => throw new NotSupportedException("Thread channels don't support invites");
- Task> INestedChannel.GetInvitesAsync(RequestOptions options)
- => throw new NotSupportedException("Thread channels don't support invites");
-
- public Task JoinAsync()
- {
- return Task.CompletedTask;
- }
+ ///
+ ///
+ /// This method is not supported in threads.
+ ///
+ public override Task> GetWebhooksAsync(RequestOptions options = null)
+ => throw new NotImplementedException();
- public Task LeaveAsync()
- {
- return Task.CompletedTask;
- }
+ ///
+ ///
+ /// This method is not supported in threads.
+ ///
+ public override Task ModifyAsync(Action func, RequestOptions options = null)
+ => throw new NotImplementedException();
- public Task AddThreadMember(IGuildUser user)
- {
- return Task.CompletedTask;
- }
+ ///
+ ///
+ /// This method is not supported in threads.
+ ///
+ public override Task RemovePermissionOverwriteAsync(IRole role, RequestOptions options = null)
+ => throw new NotImplementedException();
- public Task RemoveThreadMember(IGuildUser user)
- {
- return Task.CompletedTask;
- }
+ ///
+ ///
+ /// This method is not supported in threads.
+ ///
+ public override Task RemovePermissionOverwriteAsync(IUser user, RequestOptions options = null)
+ => throw new NotImplementedException();
+ ///
+ ///
+ /// This method is not supported in threads.
+ ///
+ public override IReadOnlyCollection PermissionOverwrites
+ => throw new NotImplementedException();
+ ///
+ ///
+ /// This method is not supported in threads.
+ ///
+ public override Task SyncPermissionsAsync(RequestOptions options = null)
+ => throw new NotImplementedException();
}
}
diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
index 547dc6d7f..58e2c152f 100644
--- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
+++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
@@ -387,7 +387,15 @@ namespace Discord.WebSocket
state.AddChannel(channel);
channels.TryAdd(channel.Id);
}
+
+ for(int i = 0; i < model.Threads.Length; i++)
+ {
+ var threadChannel = SocketThreadChannel.Create(this, state, model.Threads[i]);
+ state.AddChannel(threadChannel);
+ channels.TryAdd(threadChannel.Id);
+ }
}
+
_channels = channels;
var members = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.Members.Length * 1.05));
diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketThreadUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketThreadUser.cs
index 66a5579c4..df9194d5b 100644
--- a/src/Discord.Net.WebSocket/Entities/Users/SocketThreadUser.cs
+++ b/src/Discord.Net.WebSocket/Entities/Users/SocketThreadUser.cs
@@ -10,7 +10,10 @@ using System.Collections.Immutable;
namespace Discord.WebSocket
{
- public class SocketThreadUser : IGuildUser
+ ///
+ /// Represents a thread user received over the gateway.
+ ///
+ public class SocketThreadUser : SocketUser, IGuildUser
{
///
/// Gets the this user is in.
@@ -44,56 +47,36 @@ namespace Discord.WebSocket
=> GuildUser.IsPending;
///
- public string AvatarId
- => GuildUser.AvatarId;
-
- ///
- public string Discriminator
- => GuildUser.Discriminator;
+ public override string AvatarId
+ {
+ get => GuildUser.AvatarId;
+ internal set => GuildUser.AvatarId = value;
+ }
///
- public ushort DiscriminatorValue
- => GuildUser.DiscriminatorValue;
+ public override ushort DiscriminatorValue
+ {
+ get => GuildUser.DiscriminatorValue;
+ internal set => GuildUser.DiscriminatorValue = value;
+ }
///
- public bool IsBot
- => GuildUser.IsBot;
+ public override bool IsBot
+ {
+ get => GuildUser.IsBot;
+ internal set => GuildUser.IsBot = value;
+ }
///
- public bool IsWebhook
+ public override bool IsWebhook
=> GuildUser.IsWebhook;
///
- public string Username
- => GuildUser.Username;
-
- ///
- public UserProperties? PublicFlags
- => GuildUser.PublicFlags;
-
- ///
- public DateTimeOffset CreatedAt
- => GuildUser.CreatedAt;
-
- ///
- public ulong Id
- => GuildUser.Id;
-
- ///
- public string Mention
- => GuildUser.Mention;
-
- ///
- public UserStatus Status
- => GuildUser.Status;
-
- ///
- public IImmutableSet ActiveClients
- => GuildUser.ActiveClients;
-
- ///
- public IImmutableList Activities
- => GuildUser.Activities;
+ public override string Username
+ {
+ get => GuildUser.Username;
+ internal set => GuildUser.Username = value;
+ }
///
public bool IsDeafened
@@ -130,13 +113,14 @@ namespace Discord.WebSocket
private SocketGuildUser GuildUser { get; set; }
internal SocketThreadUser(SocketGuild guild, SocketThreadChannel thread, SocketGuildUser member)
+ : base(guild.Discord, member.Id)
{
this.Thread = thread;
this.Guild = guild;
this.GuildUser = member;
}
- internal SocketThreadUser Create(SocketGuild guild, SocketThreadChannel thread, Model model, SocketGuildUser member)
+ internal static SocketThreadUser Create(SocketGuild guild, SocketThreadChannel thread, Model model, SocketGuildUser member)
{
var entity = new SocketThreadUser(guild, thread, member);
entity.Update(model);
@@ -146,6 +130,16 @@ namespace Discord.WebSocket
internal void Update(Model model)
{
this.ThreadJoinedAt = model.JoinTimestamp;
+
+ if (model.Presence.IsSpecified)
+ {
+ this.GuildUser.Update(Discord.State, model.Presence.Value, true);
+ }
+
+ if (model.Member.IsSpecified)
+ {
+ this.GuildUser.Update(Discord.State, model.Member.Value);
+ }
}
@@ -182,15 +176,6 @@ namespace Discord.WebSocket
///
public Task RemoveRolesAsync(IEnumerable roles, RequestOptions options = null) => GuildUser.RemoveRolesAsync(roles, options);
- ///
- public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) => GuildUser.GetAvatarUrl(format, size);
-
- ///
- public string GetDefaultAvatarUrl() => GuildUser.GetDefaultAvatarUrl();
-
- ///
- public Task CreateDMChannelAsync(RequestOptions options = null) => GuildUser.CreateDMChannelAsync(options);
-
///
GuildPermissions IGuildUser.GuildPermissions => this.GuildUser.GuildPermissions;
@@ -203,6 +188,10 @@ namespace Discord.WebSocket
///
IReadOnlyCollection IGuildUser.RoleIds => this.GuildUser.Roles.Select(x => x.Id).ToImmutableArray();
+ internal override SocketGlobalUser GlobalUser => GuildUser.GlobalUser;
+
+ internal override SocketPresence Presence { get => GuildUser.Presence; set => GuildUser.Presence = value; }
+
///
/// Gets the guild user of this thread user.
///
diff --git a/test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs b/test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs
index cd29b2606..137dc5575 100644
--- a/test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs
+++ b/test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs
@@ -161,7 +161,7 @@ namespace Discord
AssertUtil(GuildPermission.ManageNicknames, x => x.ManageNicknames, (p, enable) => p.Modify(manageNicknames: enable));
AssertUtil(GuildPermission.ManageRoles, x => x.ManageRoles, (p, enable) => p.Modify(manageRoles: enable));
AssertUtil(GuildPermission.ManageWebhooks, x => x.ManageWebhooks, (p, enable) => p.Modify(manageWebhooks: enable));
- AssertUtil(GuildPermission.ManageEmojis, x => x.ManageEmojis, (p, enable) => p.Modify(manageEmojis: enable));
+ AssertUtil(GuildPermission.ManageEmojis, x => x.ManageEmojisAndStickers, (p, enable) => p.Modify(manageEmojis: enable));
}
}
}
diff --git a/test/Discord.Net.Tests.Unit/MockedEntities/MockedTextChannel.cs b/test/Discord.Net.Tests.Unit/MockedEntities/MockedTextChannel.cs
index 0bde9d1e0..18ff3b3ed 100644
--- a/test/Discord.Net.Tests.Unit/MockedEntities/MockedTextChannel.cs
+++ b/test/Discord.Net.Tests.Unit/MockedEntities/MockedTextChannel.cs
@@ -210,5 +210,7 @@ namespace Discord
{
throw new NotImplementedException();
}
+
+ public Task CreateThreadAsync(string name, ThreadType type = ThreadType.PublicThread, ThreadArchiveDuration autoArchiveDuration = ThreadArchiveDuration.OneDay, IMessage message = null, RequestOptions options = null) => throw new NotImplementedException();
}
}