Browse Source

Threads pt3

pull/1923/head
quin lynch 3 years ago
parent
commit
29a3e87835
28 changed files with 1572 additions and 627 deletions
  1. +144
    -31
      src/Discord.Net.Core/Discord.Net.Core.xml
  2. +33
    -0
      src/Discord.Net.Core/Entities/Channels/ITextChannel.cs
  3. +35
    -4
      src/Discord.Net.Core/Entities/Channels/IThreadChannel.cs
  4. +16
    -0
      src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs
  5. +0
    -45
      src/Discord.Net.Core/Entities/Channels/ThreadChannelProperties.cs
  6. +29
    -0
      src/Discord.Net.Core/Entities/Channels/ThreadType.cs
  7. +70
    -32
      src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs
  8. +4
    -4
      src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs
  9. +6
    -0
      src/Discord.Net.Rest/API/Common/ThreadMember.cs
  10. +1
    -1
      src/Discord.Net.Rest/API/Common/ThreadMetadata.cs
  11. +28
    -0
      src/Discord.Net.Rest/API/Rest/ModifyThreadParams.cs
  12. +1
    -1
      src/Discord.Net.Rest/API/Rest/StartThreadParams.cs
  13. +218
    -0
      src/Discord.Net.Rest/Discord.Net.Rest.xml
  14. +11
    -2
      src/Discord.Net.Rest/DiscordRestApiClient.cs
  15. +50
    -10
      src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs
  16. +230
    -0
      src/Discord.Net.Rest/Entities/Channels/RestThreadChannel.cs
  17. +66
    -0
      src/Discord.Net.Rest/Entities/Channels/ThreadHelper.cs
  18. +60
    -0
      src/Discord.Net.Rest/Entities/Users/RestThreadUser.cs
  19. +20
    -0
      src/Discord.Net.WebSocket/BaseSocketClient.Events.cs
  20. +131
    -169
      src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml
  21. +109
    -39
      src/Discord.Net.WebSocket/DiscordSocketClient.cs
  22. +1
    -0
      src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs
  23. +48
    -8
      src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs
  24. +209
    -228
      src/Discord.Net.WebSocket/Entities/Channels/SocketThreadChannel.cs
  25. +8
    -0
      src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
  26. +41
    -52
      src/Discord.Net.WebSocket/Entities/Users/SocketThreadUser.cs
  27. +1
    -1
      test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs
  28. +2
    -0
      test/Discord.Net.Tests.Unit/MockedEntities/MockedTextChannel.cs

+ 144
- 31
src/Discord.Net.Core/Discord.Net.Core.xml View File

@@ -1982,11 +1982,48 @@
of webhooks that is available in this channel.
</returns>
</member>
<member name="M:Discord.ITextChannel.CreateThreadAsync(System.String,Discord.ThreadType,Discord.ThreadArchiveDuration,Discord.IMessage,Discord.RequestOptions)">
<summary>
Creates a thread within this <see cref="T:Discord.ITextChannel"/>.
</summary>
<remarks>
When <paramref name="message"/> is <see langword="null"/> the thread type will be based off of the
channel its created in. When called on a <see cref="T:Discord.ITextChannel"/>, it creates a <see cref="F:Discord.ThreadType.PublicThread"/>.
When called on a <see cref="T:Discord.INewsChannel"/>, it creates a <see cref="F:Discord.ThreadType.NewsThread"/>. The id of the created
thread will be the same as the id of the message, and as such a message can only have a
single thread created from it.
</remarks>
<param name="name">The name of the thread.</param>
<param name="type">
The type of the thread.
<para>
<b>Note: </b>This parameter is not used if the <paramref name="message"/> parameter is not specified.
</para>
</param>
<param name="autoArchiveDuration">
The duration on which this thread archives after.
<para>
<b>Note: </b> Options <see cref="F:Discord.ThreadArchiveDuration.OneWeek"/> and <see cref="F:Discord.ThreadArchiveDuration.ThreeDays"/>
are only available for guilds that are boosted. You can check in the <see cref="P:Discord.IGuild.Features"/> to see if the
guild has the <b>THREE_DAY_THREAD_ARCHIVE</b> and <b>SEVEN_DAY_THREAD_ARCHIVE</b>.
</para>
</param>
<param name="message">The message which to start the thread from.</param>
<param name="options">The options to be used when sending the request.</param>
<returns>
A task that represents the asynchronous create operation. The task result contains a <see cref="T:Discord.IThreadChannel"/>
</returns>
</member>
<member name="T:Discord.IThreadChannel">
<summary>
Represents a thread channel inside of a guild.
</summary>
</member>
<member name="P:Discord.IThreadChannel.Type">
<summary>
Gets the type of the current thread channel.
</summary>
</member>
<member name="P:Discord.IThreadChannel.Joined">
<summary>
<see langword="true"/> if the current user has joined this thread, otherwise <see langword="false"/>.
@@ -2022,17 +2059,43 @@
An approximate count of messages in a thread, stops counting at 50.
</summary>
</member>
<member name="M:Discord.IThreadChannel.JoinAsync">
<member name="M:Discord.IThreadChannel.JoinAsync(Discord.RequestOptions)">
<summary>
Joins the current thread.
</summary>
<returns></returns>
<param name="options">The options to be used when sending the request.</param>
<returns>
A task that represents the asynchronous join operation.
</returns>
</member>
<member name="M:Discord.IThreadChannel.LeaveAsync">
<member name="M:Discord.IThreadChannel.LeaveAsync(Discord.RequestOptions)">
<summary>
Leaves the current thread.
</summary>
<returns></returns>
<param name="options">The options to be used when sending the request.</param>
<returns>
A task that represents the asynchronous leave operation.
</returns>
</member>
<member name="M:Discord.IThreadChannel.AddUserAsync(Discord.IGuildUser,Discord.RequestOptions)">
<summary>
Adds a user to this thread.
</summary>
<param name="user">The <see cref="T:Discord.IGuildUser"/> to add.</param>
<param name="options">The options to be used when sending the request.</param>
<returns>
A task that represents the asynchronous operation of adding a member to a thread.
</returns>
</member>
<member name="M:Discord.IThreadChannel.RemoveUserAsync(Discord.IGuildUser,Discord.RequestOptions)">
<summary>
Removes a user from this thread.
</summary>
<param name="user">The <see cref="T:Discord.IGuildUser"/> to remove from this thread.</param>
<param name="options">The options to be used when sending the request.</param>
<returns>
A task that represents the asynchronous operation of removing a user from this thread.
</returns>
</member>
<member name="T:Discord.IVoiceChannel">
<summary>
@@ -2133,6 +2196,21 @@
</remarks>
<exception cref="T:System.ArgumentOutOfRangeException">Thrown if the value does not fall within [0, 21600].</exception>
</member>
<member name="P:Discord.TextChannelProperties.Archived">
<summary>
Gets or sets whether or not the thread is archived.
</summary>
</member>
<member name="P:Discord.TextChannelProperties.Locked">
<summary>
Gets or sets whether or not the thread is locked.
</summary>
</member>
<member name="P:Discord.TextChannelProperties.AutoArchiveDuration">
<summary>
Gets or sets the auto archive duration.
</summary>
</member>
<member name="T:Discord.ThreadArchiveDuration">
<summary>
Represents the thread auto archive duration.
@@ -2164,40 +2242,26 @@
</remarks>
</summary>
</member>
<member name="P:Discord.Entities.ThreadChannelProperties.Archived">
<member name="T:Discord.ThreadType">
<summary>
Gets or sets whether or not the thread is archived.
Represents types of threads.
</summary>
</member>
<member name="P:Discord.Entities.ThreadChannelProperties.Locked">
<member name="F:Discord.ThreadType.NewsThread">
<summary>
Gets or sets whether or not the thread is locked.
Represents a temporary sub-channel within a GUILD_NEWS channel.
</summary>
</member>
<member name="P:Discord.Entities.ThreadChannelProperties.Name">
<member name="F:Discord.ThreadType.PublicThread">
<summary>
Gets or sets the name of the thread.
Represents a temporary sub-channel within a GUILD_TEXT channel.
</summary>
</member>
<member name="P:Discord.Entities.ThreadChannelProperties.AutoArchiveDuration">
<member name="F:Discord.ThreadType.PrivateThread">
<summary>
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
</summary>
</member>
<member name="P:Discord.Entities.ThreadChannelProperties.SlowModeInterval">
<summary>
Gets or sets the slow-mode ratelimit in seconds for this channel.
</summary>
<remarks>
Setting this value to anything above zero will require each user to wait X seconds before
sending another message; setting this value to <c>0</c> will disable slow-mode for this channel.
<note>
Users with <see cref="F:Discord.ChannelPermission.ManageMessages"/> or
<see cref="F:Discord.ChannelPermission.ManageChannels"/> will be exempt from slow-mode.
</note>
</remarks>
<exception cref="T:System.ArgumentOutOfRangeException">Thrown if the value does not fall within [0, 21600].</exception>
</member>
<member name="T:Discord.VoiceChannelProperties">
<summary>
Provides properties that are used to modify an <see cref="T:Discord.IVoiceChannel" /> with the specified changes.
@@ -4747,7 +4811,7 @@
<exception cref="T:System.InvalidOperationException">A button must contain either a <see cref="P:Discord.ButtonBuilder.Url"/> or a <see cref="P:Discord.ButtonBuilder.CustomId"/>, but not both.</exception>
<exception cref="T:System.InvalidOperationException">A button must have an <see cref="P:Discord.ButtonBuilder.Emote"/> or a <see cref="P:Discord.ButtonBuilder.Label"/>.</exception>
<exception cref="T:System.InvalidOperationException">A link button must contain a URL.</exception>
<exception cref="T:System.InvalidOperationException">A link must include a protocol (http or https).</exception>
<exception cref="T:System.InvalidOperationException">A URL must include a protocol (http or https).</exception>
<exception cref="T:System.InvalidOperationException">A non-link button must contain a custom id</exception>
</member>
<member name="T:Discord.SelectMenuBuilder">
@@ -6041,7 +6105,7 @@
The built embed object.
</returns>
<exception cref="T:System.InvalidOperationException">Total embed length exceeds <see cref="F:Discord.EmbedBuilder.MaxEmbedLength"/>.</exception>
<exception cref="T:System.InvalidOperationException">Any Url must include protocols (i.e http:// or https://).</exception>
<exception cref="T:System.InvalidOperationException">Any Url must be well formatted include its protocols (i.e http:// or https://).</exception>
</member>
<member name="T:Discord.EmbedFieldBuilder">
<summary>
@@ -8024,6 +8088,16 @@
Allows for viewing of audit logs.
</summary>
</member>
<member name="F:Discord.GuildPermission.ViewChannel">
<summary>
Allows guild members to view a channel, which includes reading messages in text channels.
</summary>
</member>
<member name="F:Discord.GuildPermission.SendMessages">
<summary>
Allows for sending messages in a channel
</summary>
</member>
<member name="F:Discord.GuildPermission.SendTTSMessages">
<summary>
Allows for sending of text-to-speech messages.
@@ -8094,6 +8168,11 @@
Allows for using voice-activity-detection in a voice channel.
</summary>
</member>
<member name="F:Discord.GuildPermission.PrioritySpeaker">
<summary>
Allows for using priority speaker in a voice channel.
</summary>
</member>
<member name="F:Discord.GuildPermission.Stream">
<summary>
Allows video streaming in a voice channel.
@@ -8127,15 +8206,49 @@
authentication when used on a guild that has server-wide 2FA enabled.
</remarks>
</member>
<member name="F:Discord.GuildPermission.ManageEmojis">
<member name="F:Discord.GuildPermission.ManageEmojisAndStickers">
<summary>
Allows management and editing of emojis.
Allows management and editing of emojis and stickers.
</summary>
<remarks>
This permission requires the owner account to use two-factor
authentication when used on a guild that has server-wide 2FA enabled.
</remarks>
</member>
<member name="F:Discord.GuildPermission.UseSlashCommands">
<summary>
Allows members to use slash commands in text channels.
</summary>
</member>
<member name="F:Discord.GuildPermission.RequestToSpeak">
<summary>
Allows for requesting to speak in stage channels. <i>(This permission is under active development and may be changed or removed.)</i>.
</summary>
</member>
<member name="F:Discord.GuildPermission.ManageThreads">
<summary>
Allows for deleting and archiving threads, and viewing all private threads.
</summary>
<remarks>
This permission requires the owner account to use two-factor
authentication when used on a guild that has server-wide 2FA enabled.
</remarks>
</member>
<member name="F:Discord.GuildPermission.UsePublicThreads">
<summary>
Allows for creating and participating in threads.
</summary>
</member>
<member name="F:Discord.GuildPermission.UsePrivateThreads">
<summary>
Allows for creating and participating in private threads.
</summary>
</member>
<member name="F:Discord.GuildPermission.UseExternalStickers">
<summary>
Allows the usage of custom stickers from other servers.
</summary>
</member>
<member name="F:Discord.GuildPermissions.None">
<summary> Gets a blank <see cref="T:Discord.GuildPermissions"/> that grants no permissions. </summary>
</member>
@@ -8238,7 +8351,7 @@
<member name="P:Discord.GuildPermissions.ManageWebhooks">
<summary> If <c>true</c>, a user may edit the webhooks for this guild. </summary>
</member>
<member name="P:Discord.GuildPermissions.ManageEmojis">
<member name="P:Discord.GuildPermissions.ManageEmojisAndStickers">
<summary> If <c>true</c>, a user may edit the emojis for this guild. </summary>
</member>
<member name="M:Discord.GuildPermissions.#ctor(System.UInt64)">


+ 33
- 0
src/Discord.Net.Core/Entities/Channels/ITextChannel.cs View File

@@ -114,5 +114,38 @@ namespace Discord
/// of webhooks that is available in this channel.
/// </returns>
Task<IReadOnlyCollection<IWebhook>> GetWebhooksAsync(RequestOptions options = null);

/// <summary>
/// Creates a thread within this <see cref="ITextChannel"/>.
/// </summary>
/// <remarks>
/// When <paramref name="message"/> is <see langword="null"/> the thread type will be based off of the
/// channel its created in. When called on a <see cref="ITextChannel"/>, it creates a <see cref="ThreadType.PublicThread"/>.
/// When called on a <see cref="INewsChannel"/>, it creates a <see cref="ThreadType.NewsThread"/>. The id of the created
/// thread will be the same as the id of the message, and as such a message can only have a
/// single thread created from it.
/// </remarks>
/// <param name="name">The name of the thread.</param>
/// <param name="type">
/// The type of the thread.
/// <para>
/// <b>Note: </b>This parameter is not used if the <paramref name="message"/> parameter is not specified.
/// </para>
/// </param>
/// <param name="autoArchiveDuration">
/// The duration on which this thread archives after.
/// <para>
/// <b>Note: </b> Options <see cref="ThreadArchiveDuration.OneWeek"/> and <see cref="ThreadArchiveDuration.ThreeDays"/>
/// are only available for guilds that are boosted. You can check in the <see cref="IGuild.Features"/> to see if the
/// guild has the <b>THREE_DAY_THREAD_ARCHIVE</b> and <b>SEVEN_DAY_THREAD_ARCHIVE</b>.
/// </para>
/// </param>
/// <param name="message">The message which to start the thread from.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous create operation. The task result contains a <see cref="IThreadChannel"/>
/// </returns>
Task<IThreadChannel> CreateThreadAsync(string name, ThreadType type = ThreadType.PublicThread, ThreadArchiveDuration autoArchiveDuration = ThreadArchiveDuration.OneDay,
IMessage message = null, RequestOptions options = null);
}
}

+ 35
- 4
src/Discord.Net.Core/Entities/Channels/IThreadChannel.cs View File

@@ -11,6 +11,11 @@ namespace Discord
/// </summary>
public interface IThreadChannel : ITextChannel, IGuildChannel
{
/// <summary>
/// Gets the type of the current thread channel.
/// </summary>
ThreadType Type { get; }

/// <summary>
/// <see langword="true"/> if the current user has joined this thread, otherwise <see langword="false"/>.
/// </summary>
@@ -49,13 +54,39 @@ namespace Discord
/// <summary>
/// Joins the current thread.
/// </summary>
/// <returns></returns>
Task JoinAsync();
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous join operation.
/// </returns>
Task JoinAsync(RequestOptions options = null);

/// <summary>
/// Leaves the current thread.
/// </summary>
/// <returns></returns>
Task LeaveAsync();
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous leave operation.
/// </returns>
Task LeaveAsync(RequestOptions options = null);

/// <summary>
/// Adds a user to this thread.
/// </summary>
/// <param name="user">The <see cref="IGuildUser"/> to add.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous operation of adding a member to a thread.
/// </returns>
Task AddUserAsync(IGuildUser user, RequestOptions options = null);

/// <summary>
/// Removes a user from this thread.
/// </summary>
/// <param name="user">The <see cref="IGuildUser"/> to remove from this thread.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous operation of removing a user from this thread.
/// </returns>
Task RemoveUserAsync(IGuildUser user, RequestOptions options = null);
}
}

+ 16
- 0
src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs View File

@@ -38,5 +38,21 @@ namespace Discord
/// </remarks>
/// <exception cref="ArgumentOutOfRangeException">Thrown if the value does not fall within [0, 21600].</exception>
public Optional<int> SlowModeInterval { get; set; }

/// <summary>
/// Gets or sets whether or not the thread is archived.
/// </summary>
public Optional<bool> Archived { get; set; }

/// <summary>
/// Gets or sets whether or not the thread is locked.
/// </summary>
public Optional<bool> Locked { get; set; }

/// <summary>
/// Gets or sets the auto archive duration.
/// </summary>
public Optional<ThreadArchiveDuration> AutoArchiveDuration { get; set; }
}
}

+ 0
- 45
src/Discord.Net.Core/Entities/Channels/ThreadChannelProperties.cs View File

@@ -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
{
/// <summary>
/// Gets or sets whether or not the thread is archived.
/// </summary>
public Optional<bool> Archived { get; set; }

/// <summary>
/// Gets or sets whether or not the thread is locked.
/// </summary>
public Optional<bool> Locked { get; set; }

/// <summary>
/// Gets or sets the name of the thread.
/// </summary>
public Optional<string> Name { get; set; }

/// <summary>
/// Gets or sets the auto archive duration.
/// </summary>
public Optional<ThreadArchiveDuration> AutoArchiveDuration { get; set; }

/// <summary>
/// Gets or sets the slow-mode ratelimit in seconds for this channel.
/// </summary>
/// <remarks>
/// Setting this value to anything above zero will require each user to wait X seconds before
/// sending another message; setting this value to <c>0</c> will disable slow-mode for this channel.
/// <note>
/// Users with <see cref="Discord.ChannelPermission.ManageMessages"/> or
/// <see cref="ChannelPermission.ManageChannels"/> will be exempt from slow-mode.
/// </note>
/// </remarks>
/// <exception cref="ArgumentOutOfRangeException">Thrown if the value does not fall within [0, 21600].</exception>
public Optional<int> SlowModeInterval { get; set; }
}
}

+ 29
- 0
src/Discord.Net.Core/Entities/Channels/ThreadType.cs View File

@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord
{
/// <summary>
/// Represents types of threads.
/// </summary>
public enum ThreadType
{
/// <summary>
/// Represents a temporary sub-channel within a GUILD_NEWS channel.
/// </summary>
NewsThread = 10,

/// <summary>
/// Represents a temporary sub-channel within a GUILD_TEXT channel.
/// </summary>
PublicThread = 11,

/// <summary>
/// Represents a temporary sub-channel within a GUILD_TEXT channel that is only viewable by those invited and those with the MANAGE_THREADS permission
/// </summary>
PrivateThread = 12,
}
}

+ 70
- 32
src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs View File

@@ -10,7 +10,7 @@ namespace Discord
/// <summary>
/// Allows creation of instant invites.
/// </summary>
CreateInstantInvite = 0x00_00_00_01,
CreateInstantInvite = 0x00_00_00_01,
/// <summary>
/// Allows kicking members.
/// </summary>
@@ -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.
/// </remarks>
KickMembers = 0x00_00_00_02,
KickMembers = 0x00_00_00_02,
/// <summary>
/// Allows banning members.
/// </summary>
@@ -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.
/// </remarks>
BanMembers = 0x00_00_00_04,
BanMembers = 0x00_00_00_04,
/// <summary>
/// Allows all permissions and bypasses channel permission overwrites.
/// </summary>
@@ -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.
/// </remarks>
Administrator = 0x00_00_00_08,
Administrator = 0x00_00_00_08,
/// <summary>
/// Allows management and editing of channels.
/// </summary>
@@ -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.
/// </remarks>
ManageChannels = 0x00_00_00_10,
ManageChannels = 0x00_00_00_10,
/// <summary>
/// Allows management and editing of the guild.
/// </summary>
@@ -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.
/// </remarks>
ManageGuild = 0x00_00_00_20,
ManageGuild = 0x00_00_00_20,
/// <summary>
/// Allows for viewing of guild insights
/// </summary>
ViewGuildInsights = 0x00_08_00_00,
ViewGuildInsights = 0x00_08_00_00,

// Text
/// <summary>
/// Allows for the addition of reactions to messages.
/// </summary>
AddReactions = 0x00_00_00_40,
AddReactions = 0x00_00_00_40,
/// <summary>
/// Allows for viewing of audit logs.
/// </summary>
ViewAuditLog = 0x00_00_00_80,
ViewChannel = 0x00_00_04_00,
SendMessages = 0x00_00_08_00,
ViewAuditLog = 0x00_00_00_80,
/// <summary>
/// Allows guild members to view a channel, which includes reading messages in text channels.
/// </summary>
ViewChannel = 0x00_00_04_00,
/// <summary>
/// Allows for sending messages in a channel
/// </summary>
SendMessages = 0x00_00_08_00,
/// <summary>
/// Allows for sending of text-to-speech messages.
/// </summary>
SendTTSMessages = 0x00_00_10_00,
SendTTSMessages = 0x00_00_10_00,
/// <summary>
/// Allows for deletion of other users messages.
/// </summary>
@@ -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.
/// </remarks>
ManageMessages = 0x00_00_20_00,
ManageMessages = 0x00_00_20_00,
/// <summary>
/// Allows links sent by users with this permission will be auto-embedded.
/// </summary>
EmbedLinks = 0x00_00_40_00,
EmbedLinks = 0x00_00_40_00,
/// <summary>
/// Allows for uploading images and files.
/// </summary>
AttachFiles = 0x00_00_80_00,
AttachFiles = 0x00_00_80_00,
/// <summary>
/// Allows for reading of message history.
/// </summary>
ReadMessageHistory = 0x00_01_00_00,
ReadMessageHistory = 0x00_01_00_00,
/// <summary>
/// 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.
/// </summary>
MentionEveryone = 0x00_02_00_00,
MentionEveryone = 0x00_02_00_00,
/// <summary>
/// Allows the usage of custom emojis from other servers.
/// </summary>
UseExternalEmojis = 0x00_04_00_00,
UseExternalEmojis = 0x00_04_00_00,


// Voice
/// <summary>
/// Allows for joining of a voice channel.
/// </summary>
Connect = 0x00_10_00_00,
Connect = 0x00_10_00_00,
/// <summary>
/// Allows for speaking in a voice channel.
/// </summary>
Speak = 0x00_20_00_00,
Speak = 0x00_20_00_00,
/// <summary>
/// Allows for muting members in a voice channel.
/// </summary>
MuteMembers = 0x00_40_00_00,
MuteMembers = 0x00_40_00_00,
/// <summary>
/// Allows for deafening of members in a voice channel.
/// </summary>
DeafenMembers = 0x00_80_00_00,
DeafenMembers = 0x00_80_00_00,
/// <summary>
/// Allows for moving of members between voice channels.
/// </summary>
MoveMembers = 0x01_00_00_00,
MoveMembers = 0x01_00_00_00,
/// <summary>
/// Allows for using voice-activity-detection in a voice channel.
/// </summary>
UseVAD = 0x02_00_00_00,
PrioritySpeaker = 0x00_00_01_00,
UseVAD = 0x02_00_00_00,
/// <summary>
/// Allows for using priority speaker in a voice channel.
/// </summary>
PrioritySpeaker = 0x00_00_01_00,
/// <summary>
/// Allows video streaming in a voice channel.
/// </summary>
Stream = 0x00_00_02_00,
Stream = 0x00_00_02_00,

// General 2
/// <summary>
/// Allows for modification of own nickname.
/// </summary>
ChangeNickname = 0x04_00_00_00,
ChangeNickname = 0x04_00_00_00,
/// <summary>
/// Allows for modification of other users nicknames.
/// </summary>
ManageNicknames = 0x08_00_00_00,
ManageNicknames = 0x08_00_00_00,
/// <summary>
/// Allows management and editing of roles.
/// </summary>
@@ -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.
/// </remarks>
ManageRoles = 0x10_00_00_00,
ManageRoles = 0x10_00_00_00,
/// <summary>
/// Allows management and editing of webhooks.
/// </summary>
@@ -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.
/// </remarks>
ManageWebhooks = 0x20_00_00_00,
ManageWebhooks = 0x20_00_00_00,
/// <summary>
/// Allows management and editing of emojis.
/// Allows management and editing of emojis and stickers.
/// </summary>
/// <remarks>
/// This permission requires the owner account to use two-factor
/// authentication when used on a guild that has server-wide 2FA enabled.
/// </remarks>
ManageEmojis = 0x40_00_00_00
ManageEmojisAndStickers = 0x40_00_00_00,
/// <summary>
/// Allows members to use slash commands in text channels.
/// </summary>
UseSlashCommands = 0x80_00_00_00,
/// <summary>
/// Allows for requesting to speak in stage channels. <i>(This permission is under active development and may be changed or removed.)</i>.
/// </summary>
RequestToSpeak = 0x01_00_00_00_00,
/// <summary>
/// Allows for deleting and archiving threads, and viewing all private threads.
/// </summary>
/// <remarks>
/// This permission requires the owner account to use two-factor
/// authentication when used on a guild that has server-wide 2FA enabled.
/// </remarks>
ManageThreads = 0x04_00_00_00_00,
/// <summary>
/// Allows for creating and participating in threads.
/// </summary>
UsePublicThreads = 0x08_00_00_00_00,
/// <summary>
/// Allows for creating and participating in private threads.
/// </summary>
UsePrivateThreads = 0x10_00_00_00_00,
/// <summary>
/// Allows the usage of custom stickers from other servers.
/// </summary>
UseExternalStickers = 0x20_00_00_00_00

}
}

+ 4
- 4
src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs View File

@@ -82,7 +82,7 @@ namespace Discord
/// <summary> If <c>true</c>, a user may edit the webhooks for this guild. </summary>
public bool ManageWebhooks => Permissions.GetValue(RawValue, GuildPermission.ManageWebhooks);
/// <summary> If <c>true</c>, a user may edit the emojis for this guild. </summary>
public bool ManageEmojis => Permissions.GetValue(RawValue, GuildPermission.ManageEmojis);
public bool ManageEmojisAndStickers => Permissions.GetValue(RawValue, GuildPermission.ManageEmojisAndStickers);

/// <summary> Creates a new <see cref="GuildPermissions"/> with the provided packed value. </summary>
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)
{ }

/// <summary> Creates a new <see cref="GuildPermissions"/> from this one, changing the provided non-null permissions. </summary>


+ 6
- 0
src/Discord.Net.Rest/API/Common/ThreadMember.cs View File

@@ -18,6 +18,12 @@ namespace Discord.API
[JsonProperty("join_timestamp")]
public DateTimeOffset JoinTimestamp { get; set; }

[JsonProperty("presense")]
public Optional<Presence> Presence { get; set; }

[JsonProperty("member")]
public Optional<GuildMember> Member { get; set; }

[JsonProperty("flags")]
public int Flags { get; set; } // No enum type (yet?)
}


+ 1
- 1
src/Discord.Net.Rest/API/Common/ThreadMetadata.cs View File

@@ -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; }


+ 28
- 0
src/Discord.Net.Rest/API/Rest/ModifyThreadParams.cs View File

@@ -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<string> Name { get; set; }

[JsonProperty("archived")]
public Optional<bool> Archived { get; set; }

[JsonProperty("auto_archive_duration")]
public Optional<ThreadArchiveDuration> AutoArchiveDuration { get; set; }

[JsonProperty("locked")]
public Optional<bool> Locked { get; set; }

[JsonProperty("rate_limit_per_user")]
public Optional<int> Slowmode { get; set; }
}
}

+ 1
- 1
src/Discord.Net.Rest/API/Rest/StartThreadParams.cs View File

@@ -16,6 +16,6 @@ namespace Discord.API.Rest
public ThreadArchiveDuration Duration { get; set; }

[JsonProperty("type")]
public Optional<ChannelType> Type { get; set; }
public Optional<ThreadType> Type { get; set; }
}
}

+ 218
- 0
src/Discord.Net.Rest/Discord.Net.Rest.xml View File

@@ -2458,6 +2458,38 @@
<member name="M:Discord.Rest.RestTextChannel.GetInvitesAsync(Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="M:Discord.Rest.RestTextChannel.CreateThreadAsync(System.String,Discord.ThreadType,Discord.ThreadArchiveDuration,Discord.IMessage,Discord.RequestOptions)">
<summary>
Creates a thread within this <see cref="T:Discord.ITextChannel"/>.
</summary>
<remarks>
When <paramref name="message"/> is <see langword="null"/> the thread type will be based off of the
channel its created in. When called on a <see cref="T:Discord.ITextChannel"/>, it creates a <see cref="F:Discord.ThreadType.PublicThread"/>.
When called on a <see cref="T:Discord.INewsChannel"/>, it creates a <see cref="F:Discord.ThreadType.NewsThread"/>. The id of the created
thread will be the same as the id of the message, and as such a message can only have a
single thread created from it.
</remarks>
<param name="name">The name of the thread.</param>
<param name="type">
The type of the thread.
<para>
<b>Note: </b>This parameter is not used if the <paramref name="message"/> parameter is not specified.
</para>
</param>
<param name="autoArchiveDuration">
The duration on which this thread archives after.
<para>
<b>Note: </b> Options <see cref="F:Discord.ThreadArchiveDuration.OneWeek"/> and <see cref="F:Discord.ThreadArchiveDuration.ThreeDays"/>
are only available for guilds that are boosted. You can check in the <see cref="P:Discord.IGuild.Features"/> to see if the
guild has the <b>THREE_DAY_THREAD_ARCHIVE</b> and <b>SEVEN_DAY_THREAD_ARCHIVE</b>.
</para>
</param>
<param name="message">The message which to start the thread from.</param>
<param name="options">The options to be used when sending the request.</param>
<returns>
A task that represents the asynchronous create operation. The task result contains a <see cref="T:Discord.IThreadChannel"/>
</returns>
</member>
<member name="M:Discord.Rest.RestTextChannel.Discord#ITextChannel#CreateWebhookAsync(System.String,System.IO.Stream,Discord.RequestOptions)">
<inheritdoc />
</member>
@@ -2506,6 +2538,163 @@
<member name="M:Discord.Rest.RestTextChannel.Discord#INestedChannel#GetCategoryAsync(Discord.CacheMode,Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="T:Discord.Rest.RestThreadChannel">
<summary>
Represents a thread channel recieved over REST.
</summary>
</member>
<member name="P:Discord.Rest.RestThreadChannel.Joined">
<inheritdoc/>
</member>
<member name="P:Discord.Rest.RestThreadChannel.Archived">
<inheritdoc/>
</member>
<member name="P:Discord.Rest.RestThreadChannel.AutoArchiveDuration">
<inheritdoc/>
</member>
<member name="P:Discord.Rest.RestThreadChannel.ArchiveTimestamp">
<inheritdoc/>
</member>
<member name="P:Discord.Rest.RestThreadChannel.Locked">
<inheritdoc/>
</member>
<member name="P:Discord.Rest.RestThreadChannel.MemberCount">
<inheritdoc/>
</member>
<member name="P:Discord.Rest.RestThreadChannel.MessageCount">
<inheritdoc/>
</member>
<member name="P:Discord.Rest.RestThreadChannel.ParentChannelId">
<summary>
Gets the parent text channel id.
</summary>
</member>
<member name="M:Discord.Rest.RestThreadChannel.GetUserAsync(System.UInt64,Discord.RequestOptions)">
<summary>
Gets a user within this thread.
</summary>
<param name="userId">The id of the user to fetch.</param>
<param name="options">The options to be used when sending the request.</param>
<returns>
A task representing the asyncronous get operation. The task returns a
<see cref="T:Discord.Rest.RestThreadUser"/> if found, otherwise <see langword="null"/>.
</returns>
</member>
<member name="M:Discord.Rest.RestThreadChannel.GetUsersAsync(Discord.RequestOptions)">
<summary>
Gets a collection of users within this thread.
</summary>
<param name="options">The options to be used when sending the request.</param>
<returns>
A task representing the asyncronous get operation. The task returns a
<see cref="T:System.Collections.Generic.IReadOnlyCollection`1"/> of <see cref="T:Discord.Rest.RestThreadUser"/>'s.
</returns>
</member>
<member name="M:Discord.Rest.RestThreadChannel.ModifyAsync(System.Action{Discord.TextChannelProperties},Discord.RequestOptions)">
<inheritdoc/>
</member>
<member name="M:Discord.Rest.RestThreadChannel.AddPermissionOverwriteAsync(Discord.IRole,Discord.OverwritePermissions,Discord.RequestOptions)">
<inheritdoc/>
<remarks>
<b>This method is not supported in threads.</b>
</remarks>
</member>
<member name="M:Discord.Rest.RestThreadChannel.AddPermissionOverwriteAsync(Discord.IUser,Discord.OverwritePermissions,Discord.RequestOptions)">
<inheritdoc/>
<remarks>
<b>This method is not supported in threads.</b>
</remarks>
</member>
<member name="M:Discord.Rest.RestThreadChannel.CreateInviteAsync(System.Nullable{System.Int32},System.Nullable{System.Int32},System.Boolean,System.Boolean,Discord.RequestOptions)">
<inheritdoc/>
<remarks>
<b>This method is not supported in threads.</b>
</remarks>
</member>
<member name="M:Discord.Rest.RestThreadChannel.CreateInviteToApplicationAsync(System.UInt64,System.Nullable{System.Int32},System.Nullable{System.Int32},System.Boolean,System.Boolean,Discord.RequestOptions)">
<inheritdoc/>
<remarks>
<b>This method is not supported in threads.</b>
</remarks>
</member>
<member name="M:Discord.Rest.RestThreadChannel.CreateInviteToStreamAsync(Discord.IUser,System.Nullable{System.Int32},System.Nullable{System.Int32},System.Boolean,System.Boolean,Discord.RequestOptions)">
<inheritdoc/>
<remarks>
<b>This method is not supported in threads.</b>
</remarks>
</member>
<member name="M:Discord.Rest.RestThreadChannel.CreateWebhookAsync(System.String,System.IO.Stream,Discord.RequestOptions)">
<inheritdoc/>
<remarks>
<b>This method is not supported in threads.</b>
</remarks>
</member>
<member name="M:Discord.Rest.RestThreadChannel.GetCategoryAsync(Discord.RequestOptions)">
<inheritdoc/>
<remarks>
<b>This method is not supported in threads.</b>
</remarks>
</member>
<member name="M:Discord.Rest.RestThreadChannel.GetInvitesAsync(Discord.RequestOptions)">
<inheritdoc/>
<remarks>
<b>This method is not supported in threads.</b>
</remarks>
</member>
<member name="M:Discord.Rest.RestThreadChannel.GetPermissionOverwrite(Discord.IRole)">
<inheritdoc/>
<remarks>
<b>This method is not supported in threads.</b>
</remarks>
</member>
<member name="M:Discord.Rest.RestThreadChannel.GetPermissionOverwrite(Discord.IUser)">
<inheritdoc/>
<remarks>
<b>This method is not supported in threads.</b>
</remarks>
</member>
<member name="M:Discord.Rest.RestThreadChannel.GetWebhookAsync(System.UInt64,Discord.RequestOptions)">
<inheritdoc/>
<remarks>
<b>This method is not supported in threads.</b>
</remarks>
</member>
<member name="M:Discord.Rest.RestThreadChannel.GetWebhooksAsync(Discord.RequestOptions)">
<inheritdoc/>
<remarks>
<b>This method is not supported in threads.</b>
</remarks>
</member>
<member name="M:Discord.Rest.RestThreadChannel.RemovePermissionOverwriteAsync(Discord.IRole,Discord.RequestOptions)">
<inheritdoc/>
<remarks>
<b>This method is not supported in threads.</b>
</remarks>
</member>
<member name="M:Discord.Rest.RestThreadChannel.RemovePermissionOverwriteAsync(Discord.IUser,Discord.RequestOptions)">
<inheritdoc/>
<remarks>
<b>This method is not supported in threads.</b>
</remarks>
</member>
<member name="P:Discord.Rest.RestThreadChannel.PermissionOverwrites">
<inheritdoc/>
<remarks>
<b>This method is not supported in threads.</b>
</remarks>
</member>
<member name="M:Discord.Rest.RestThreadChannel.JoinAsync(Discord.RequestOptions)">
<inheritdoc/>
</member>
<member name="M:Discord.Rest.RestThreadChannel.LeaveAsync(Discord.RequestOptions)">
<inheritdoc/>
</member>
<member name="M:Discord.Rest.RestThreadChannel.AddUserAsync(Discord.IGuildUser,Discord.RequestOptions)">
<inheritdoc/>
</member>
<member name="M:Discord.Rest.RestThreadChannel.RemoveUserAsync(Discord.IGuildUser,Discord.RequestOptions)">
<inheritdoc/>
</member>
<member name="T:Discord.Rest.RestVoiceChannel">
<summary>
Represents a REST-based voice channel in a guild.
@@ -4271,6 +4460,35 @@
<inheritdoc />
<exception cref="T:System.InvalidOperationException">Unable to modify this object using a different token.</exception>
</member>
<member name="T:Discord.Rest.RestThreadUser">
<summary>
Represents a thread user recieved over the REST api.
</summary>
</member>
<member name="P:Discord.Rest.RestThreadUser.Thread">
<summary>
Gets the <see cref="T:Discord.Rest.RestThreadChannel"/> this user is in.
</summary>
</member>
<member name="P:Discord.Rest.RestThreadUser.JoinedAt">
<summary>
Gets the timestamp for when this user joined this thread.
</summary>
</member>
<member name="P:Discord.Rest.RestThreadUser.Guild">
<summary>
Gets the guild this user is in.
</summary>
</member>
<member name="M:Discord.Rest.RestThreadUser.GetGuildUser">
<summary>
Gets the guild user for this thread user.
</summary>
<returns>
A task representing the asyncronous get operation. The task returns a
<see cref="T:Discord.IGuildUser"/> that represents the current thread user.
</returns>
</member>
<member name="T:Discord.Rest.RestUser">
<summary>
Represents a REST-based user.


+ 11
- 2
src/Discord.Net.Rest/DiscordRestApiClient.cs View File

@@ -415,6 +415,15 @@ namespace Discord.API
}

// Threads
public async Task<Channel> ModifyThreadAsync(ulong channelId, ModifyThreadParams args, RequestOptions options = null)
{
Preconditions.NotEqual(channelId, 0, nameof(channelId));

var bucket = new BucketIds(channelId: channelId);

return await SendJsonAsync<Channel>("PATCH", () => $"channels/{channelId}", args, bucket, options: options);
}

public async Task<Channel> 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<Channel>("POST", () => $"channels/{channelId}/messages/{messageId}/threads", args, bucket, options: options).ConfigureAwait(false);
}
public async Task<Channel> 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<ThreadMember[]>("GET", () => $"/channels/{channelId}", bucket, options: options);
return await SendAsync<ThreadMember[]>("GET", () => $"channels/{channelId}/thread-members", bucket, options: options);
}

public async Task<ChannelThreads> GetActiveThreadsAsync(ulong channelId, RequestOptions options = null)


+ 50
- 10
src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs View File

@@ -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();
}

/// <inheritdoc />
public async Task ModifyAsync(Action<TextChannelProperties> func, RequestOptions options = null)
public virtual async Task ModifyAsync(Action<TextChannelProperties> 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.
/// </returns>
public Task<RestWebhook> CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null)
public virtual Task<RestWebhook> CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null)
=> ChannelHelper.CreateWebhookAsync(this, Discord, name, avatar, options);
/// <summary>
/// Gets a webhook available in this text channel.
@@ -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; <c>null</c> if the webhook is not found.
/// </returns>
public Task<RestWebhook> GetWebhookAsync(ulong id, RequestOptions options = null)
public virtual Task<RestWebhook> GetWebhookAsync(ulong id, RequestOptions options = null)
=> ChannelHelper.GetWebhookAsync(this, Discord, id, options);
/// <summary>
/// Gets the webhooks available in this text channel.
@@ -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.
/// </returns>
public Task<IReadOnlyCollection<RestWebhook>> GetWebhooksAsync(RequestOptions options = null)
public virtual Task<IReadOnlyCollection<RestWebhook>> GetWebhooksAsync(RequestOptions options = null)
=> ChannelHelper.GetWebhooksAsync(this, Discord, options);

/// <summary>
@@ -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; <c>null</c> if none is set.
/// </returns>
public Task<ICategoryChannel> GetCategoryAsync(RequestOptions options = null)
public virtual Task<ICategoryChannel> GetCategoryAsync(RequestOptions options = null)
=> ChannelHelper.GetCategoryAsync(this, Discord, options);
/// <inheritdoc />
public Task SyncPermissionsAsync(RequestOptions options = null)
@@ -213,18 +213,55 @@ namespace Discord.Rest

//Invites
/// <inheritdoc />
public async Task<IInviteMetadata> CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
public virtual async Task<IInviteMetadata> CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
=> await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, options).ConfigureAwait(false);
public Task<IInviteMetadata> CreateInviteToApplicationAsync(ulong applicationId, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
public virtual Task<IInviteMetadata> CreateInviteToApplicationAsync(ulong applicationId, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
=> throw new NotImplementedException();
public Task<IInviteMetadata> CreateInviteToStreamAsync(IUser user, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
public virtual Task<IInviteMetadata> CreateInviteToStreamAsync(IUser user, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
=> throw new NotImplementedException();
/// <inheritdoc />
public async Task<IReadOnlyCollection<IInviteMetadata>> GetInvitesAsync(RequestOptions options = null)
public virtual async Task<IReadOnlyCollection<IInviteMetadata>> GetInvitesAsync(RequestOptions options = null)
=> await ChannelHelper.GetInvitesAsync(this, Discord, options).ConfigureAwait(false);

private string DebuggerDisplay => $"{Name} ({Id}, Text)";

/// <summary>
/// Creates a thread within this <see cref="ITextChannel"/>.
/// </summary>
/// <remarks>
/// When <paramref name="message"/> is <see langword="null"/> the thread type will be based off of the
/// channel its created in. When called on a <see cref="ITextChannel"/>, it creates a <see cref="ThreadType.PublicThread"/>.
/// When called on a <see cref="INewsChannel"/>, it creates a <see cref="ThreadType.NewsThread"/>. The id of the created
/// thread will be the same as the id of the message, and as such a message can only have a
/// single thread created from it.
/// </remarks>
/// <param name="name">The name of the thread.</param>
/// <param name="type">
/// The type of the thread.
/// <para>
/// <b>Note: </b>This parameter is not used if the <paramref name="message"/> parameter is not specified.
/// </para>
/// </param>
/// <param name="autoArchiveDuration">
/// The duration on which this thread archives after.
/// <para>
/// <b>Note: </b> Options <see cref="ThreadArchiveDuration.OneWeek"/> and <see cref="ThreadArchiveDuration.ThreeDays"/>
/// are only available for guilds that are boosted. You can check in the <see cref="IGuild.Features"/> to see if the
/// guild has the <b>THREE_DAY_THREAD_ARCHIVE</b> and <b>SEVEN_DAY_THREAD_ARCHIVE</b>.
/// </para>
/// </param>
/// <param name="message">The message which to start the thread from.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous create operation. The task result contains a <see cref="IThreadChannel"/>
/// </returns>
public async Task<RestThreadChannel> 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
/// <inheritdoc />
async Task<IWebhook> ITextChannel.CreateWebhookAsync(string name, Stream avatar, RequestOptions options)
@@ -236,6 +273,9 @@ namespace Discord.Rest
async Task<IReadOnlyCollection<IWebhook>> ITextChannel.GetWebhooksAsync(RequestOptions options)
=> await GetWebhooksAsync(options).ConfigureAwait(false);

async Task<IThreadChannel> ITextChannel.CreateThreadAsync(string name, ThreadType type, ThreadArchiveDuration autoArchiveDuration, IMessage message, RequestOptions options)
=> await CreateThreadAsync(name, type, autoArchiveDuration, message, options);

//IMessageChannel
/// <inheritdoc />
async Task<IMessage> IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options)


+ 230
- 0
src/Discord.Net.Rest/Entities/Channels/RestThreadChannel.cs View File

@@ -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
{
/// <summary>
/// Represents a thread channel recieved over REST.
/// </summary>
public class RestThreadChannel : RestTextChannel, IThreadChannel
{
public ThreadType Type { get; private set; }
/// <inheritdoc/>
public bool Joined { get; private set; }

/// <inheritdoc/>
public bool Archived { get; private set; }

/// <inheritdoc/>
public ThreadArchiveDuration AutoArchiveDuration { get; private set; }

/// <inheritdoc/>
public DateTimeOffset ArchiveTimestamp { get; private set; }

/// <inheritdoc/>
public bool Locked { get; private set; }

/// <inheritdoc/>
public int MemberCount { get; private set; }

/// <inheritdoc/>
public int MessageCount { get; private set; }

/// <summary>
/// Gets the parent text channel id.
/// </summary>
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;
}

/// <summary>
/// Gets a user within this thread.
/// </summary>
/// <param name="userId">The id of the user to fetch.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task representing the asyncronous get operation. The task returns a
/// <see cref="RestThreadUser"/> if found, otherwise <see langword="null"/>.
/// </returns>
public new Task<RestThreadUser> GetUserAsync(ulong userId, RequestOptions options = null)
=> ThreadHelper.GetUserAsync(userId, this, Discord, options);

/// <summary>
/// Gets a collection of users within this thread.
/// </summary>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task representing the asyncronous get operation. The task returns a
/// <see cref="IReadOnlyCollection{T}"/> of <see cref="RestThreadUser"/>'s.
/// </returns>
public new async Task<IReadOnlyCollection<RestThreadUser>> GetUsersAsync(RequestOptions options = null)
=> (await ThreadHelper.GetUsersAsync(this, Discord, options).ConfigureAwait(false)).ToImmutableArray();

/// <inheritdoc/>
public override async Task ModifyAsync(Action<TextChannelProperties> func, RequestOptions options = null)
{
var model = await ThreadHelper.ModifyAsync(this, Discord, func, options);
Update(model);
}

/// <inheritdoc/>
/// <remarks>
/// <b>This method is not supported in threads.</b>
/// </remarks>
public override Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, RequestOptions options = null)
=> throw new NotImplementedException();

/// <inheritdoc/>
/// <remarks>
/// <b>This method is not supported in threads.</b>
/// </remarks>
public override Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions, RequestOptions options = null)
=> throw new NotImplementedException();

/// <inheritdoc/>
/// <remarks>
/// <b>This method is not supported in threads.</b>
/// </remarks>
public override Task<IInviteMetadata> CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
=> throw new NotImplementedException();

/// <inheritdoc/>
/// <remarks>
/// <b>This method is not supported in threads.</b>
/// </remarks>
public override Task<IInviteMetadata> CreateInviteToApplicationAsync(ulong applicationId, int? maxAge, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
=> throw new NotImplementedException();

/// <inheritdoc/>
/// <remarks>
/// <b>This method is not supported in threads.</b>
/// </remarks>
public override Task<IInviteMetadata> CreateInviteToStreamAsync(IUser user, int? maxAge, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
=> throw new NotImplementedException();

/// <inheritdoc/>
/// <remarks>
/// <b>This method is not supported in threads.</b>
/// </remarks>
public override Task<RestWebhook> CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null)
=> throw new NotImplementedException();

/// <inheritdoc/>
/// <remarks>
/// <b>This method is not supported in threads.</b>
/// </remarks>
public override Task<ICategoryChannel> GetCategoryAsync(RequestOptions options = null)
=> throw new NotImplementedException();

/// <inheritdoc/>
/// <remarks>
/// <b>This method is not supported in threads.</b>
/// </remarks>
public override Task<IReadOnlyCollection<IInviteMetadata>> GetInvitesAsync(RequestOptions options = null)
=> throw new NotImplementedException();

/// <inheritdoc/>
/// <remarks>
/// <b>This method is not supported in threads.</b>
/// </remarks>
public override OverwritePermissions? GetPermissionOverwrite(IRole role)
=> throw new NotImplementedException();

/// <inheritdoc/>
/// <remarks>
/// <b>This method is not supported in threads.</b>
/// </remarks>
public override OverwritePermissions? GetPermissionOverwrite(IUser user)
=> throw new NotImplementedException();

/// <inheritdoc/>
/// <remarks>
/// <b>This method is not supported in threads.</b>
/// </remarks>
public override Task<RestWebhook> GetWebhookAsync(ulong id, RequestOptions options = null)
=> throw new NotImplementedException();

/// <inheritdoc/>
/// <remarks>
/// <b>This method is not supported in threads.</b>
/// </remarks>
public override Task<IReadOnlyCollection<RestWebhook>> GetWebhooksAsync(RequestOptions options = null)
=> throw new NotImplementedException();

/// <inheritdoc/>
/// <remarks>
/// <b>This method is not supported in threads.</b>
/// </remarks>
public override Task RemovePermissionOverwriteAsync(IRole role, RequestOptions options = null)
=> throw new NotImplementedException();

/// <inheritdoc/>
/// <remarks>
/// <b>This method is not supported in threads.</b>
/// </remarks>
public override Task RemovePermissionOverwriteAsync(IUser user, RequestOptions options = null)
=> throw new NotImplementedException();

/// <inheritdoc/>
/// <remarks>
/// <b>This method is not supported in threads.</b>
/// </remarks>
public override IReadOnlyCollection<Overwrite> PermissionOverwrites
=> throw new NotImplementedException();

/// <inheritdoc/>
public Task JoinAsync(RequestOptions options = null)
=> Discord.ApiClient.JoinThreadAsync(this.Id, options);

/// <inheritdoc/>
public Task LeaveAsync(RequestOptions options = null)
=> Discord.ApiClient.LeaveThreadAsync(this.Id, options);

/// <inheritdoc/>
public Task AddUserAsync(IGuildUser user, RequestOptions options = null)
=> Discord.ApiClient.AddThreadMemberAsync(this.Id, user.Id, options);

/// <inheritdoc/>
public Task RemoveUserAsync(IGuildUser user, RequestOptions options = null)
=> Discord.ApiClient.RemoveThreadMemberAsync(this.Id, user.Id, options);
}
}

+ 66
- 0
src/Discord.Net.Rest/Entities/Channels/ThreadHelper.cs View File

@@ -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<Model> 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<Model> ModifyAsync(IThreadChannel channel, BaseDiscordClient client,
Action<TextChannelProperties> 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<RestThreadUser[]> 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<RestThreadUser> GetUserAsync(ulong userdId, IThreadChannel channel, BaseDiscordClient client, RequestOptions options = null)
=> (await GetUsersAsync(channel, client, options).ConfigureAwait(false)).FirstOrDefault(x => x.Id == userdId);
}
}

+ 60
- 0
src/Discord.Net.Rest/Entities/Users/RestThreadUser.cs View File

@@ -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
{
/// <summary>
/// Represents a thread user recieved over the REST api.
/// </summary>
public class RestThreadUser : RestEntity<ulong>
{
/// <summary>
/// Gets the <see cref="RestThreadChannel"/> this user is in.
/// </summary>
public IThreadChannel Thread { get; }

/// <summary>
/// Gets the timestamp for when this user joined this thread.
/// </summary>
public DateTimeOffset JoinedAt { get; private set; }

/// <summary>
/// Gets the guild this user is in.
/// </summary>
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;
}

/// <summary>
/// Gets the guild user for this thread user.
/// </summary>
/// <returns>
/// A task representing the asyncronous get operation. The task returns a
/// <see cref="IGuildUser"/> that represents the current thread user.
/// </returns>
public Task<IGuildUser> GetGuildUser()
=> Guild.GetUserAsync(this.Id);
}
}

+ 20
- 0
src/Discord.Net.WebSocket/BaseSocketClient.Events.cs View File

@@ -572,5 +572,25 @@ namespace Discord.WebSocket
}
internal readonly AsyncEvent<Func<Cacheable<SocketThreadChannel, ulong>, Task>> _threadDeleted = new AsyncEvent<Func<Cacheable<SocketThreadChannel, ulong>, Task>>();

/// <summary>
/// Fired when a user joins a thread
/// </summary>
public event Func<SocketThreadUser, Task> ThreadMemberJoined
{
add { _threadMemberJoined.Add(value); }
remove { _threadMemberJoined.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketThreadUser, Task>> _threadMemberJoined = new AsyncEvent<Func<SocketThreadUser, Task>>();

/// <summary>
/// Fired when a user leaves a thread
/// </summary>
public event Func<SocketThreadUser, Task> ThreadMemberLeft
{
add { _threadMemberLeft.Add(value); }
remove { _threadMemberLeft.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketThreadUser, Task>> _threadMemberLeft = new AsyncEvent<Func<SocketThreadUser, Task>>();

}
}

+ 131
- 169
src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml View File

@@ -771,6 +771,16 @@
Fired when a thread is deleted.
</summary>
</member>
<member name="E:Discord.WebSocket.BaseSocketClient.ThreadMemberJoined">
<summary>
Fired when a user joins a thread
</summary>
</member>
<member name="E:Discord.WebSocket.BaseSocketClient.ThreadMemberLeft">
<summary>
Fired when a user leaves a thread
</summary>
</member>
<member name="P:Discord.WebSocket.DiscordShardedClient.Latency">
<inheritdoc />
</member>
@@ -2380,6 +2390,16 @@
Represents a thread channel inside of a guild.
</summary>
</member>
<member name="P:Discord.WebSocket.SocketThreadChannel.Owner">
<summary>
Gets the owner of the current thread.
</summary>
</member>
<member name="P:Discord.WebSocket.SocketThreadChannel.CurrentUser">
<summary>
Gets the current users within this thread.
</summary>
</member>
<member name="P:Discord.WebSocket.SocketThreadChannel.Joined">
<inheritdoc/>
</member>
@@ -2411,182 +2431,152 @@
<member name="P:Discord.WebSocket.SocketThreadChannel.Locked">
<inheritdoc/>
</member>
<member name="P:Discord.WebSocket.SocketThreadChannel.IsNsfw">
<inheritdoc/>
</member>
<member name="P:Discord.WebSocket.SocketThreadChannel.Topic">
<inheritdoc/>
</member>
<member name="P:Discord.WebSocket.SocketThreadChannel.SlowModeInterval">
<inheritdoc/>
</member>
<member name="P:Discord.WebSocket.SocketThreadChannel.Mention">
<inheritdoc/>
</member>
<member name="P:Discord.WebSocket.SocketThreadChannel.CategoryId">
<inheritdoc/>
</member>
<member name="P:Discord.WebSocket.SocketThreadChannel.CachedMessages">
<inheritdoc />
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.SyncPermissionsAsync(Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.ModifyAsync(System.Action{Discord.TextChannelProperties},Discord.RequestOptions)">
<inheritdoc />
<member name="P:Discord.WebSocket.SocketThreadChannel.Users">
<summary>
Gets a collection of cached users within this thread.
</summary>
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.GetCachedMessage(System.UInt64)">
<member name="M:Discord.WebSocket.SocketThreadChannel.GetUser(System.UInt64)">
<inheritdoc />
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.GetMessageAsync(System.UInt64,Discord.RequestOptions)">
<member name="M:Discord.WebSocket.SocketThreadChannel.GetUsersAsync(Discord.RequestOptions)">
<summary>
Gets a message from this message channel.
Gets all users inside this thread.
</summary>
<remarks>
This method follows the same behavior as described in <see cref="M:Discord.IMessageChannel.GetMessageAsync(System.UInt64,Discord.CacheMode,Discord.RequestOptions)"/>.
Please visit its documentation for more details on this method.
If all users are not downloaded then this method will call <see cref="M:Discord.WebSocket.SocketThreadChannel.DownloadUsersAsync(Discord.RequestOptions)"/> and return the result.
</remarks>
<param name="id">The snowflake identifier of the message.</param>
<param name="options">The options to be used when sending the request.</param>
<returns>
A task that represents an asynchronous get operation for retrieving the message. The task result contains
the retrieved message; <c>null</c> if no message is found with the specified identifier.
</returns>
<returns>A task representing the download operation.</returns>
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.GetMessagesAsync(System.Int32,Discord.RequestOptions)">
<member name="M:Discord.WebSocket.SocketThreadChannel.DownloadUsersAsync(Discord.RequestOptions)">
<summary>
Gets the last N messages from this message channel.
Downloads all users that have access to this thread.
</summary>
<remarks>
This method follows the same behavior as described in <see cref="M:Discord.IMessageChannel.GetMessagesAsync(System.Int32,Discord.CacheMode,Discord.RequestOptions)"/>.
Please visit its documentation for more details on this method.
</remarks>
<param name="limit">The numbers of message to be gotten from.</param>
<param name="options">The options to be used when sending the request.</param>
<returns>
Paged collection of messages.
</returns>
<returns>A task representing the asyncronous download operation.</returns>
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.JoinAsync(Discord.RequestOptions)">
<inheritdoc/>
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.GetMessagesAsync(System.UInt64,Discord.Direction,System.Int32,Discord.RequestOptions)">
<member name="M:Discord.WebSocket.SocketThreadChannel.LeaveAsync(Discord.RequestOptions)">
<inheritdoc/>
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.AddUserAsync(Discord.IGuildUser,Discord.RequestOptions)">
<summary>
Gets a collection of messages in this channel.
Adds a user to this thread.
</summary>
<remarks>
This method follows the same behavior as described in <see cref="M:Discord.IMessageChannel.GetMessagesAsync(System.UInt64,Discord.Direction,System.Int32,Discord.CacheMode,Discord.RequestOptions)"/>.
Please visit its documentation for more details on this method.
</remarks>
<param name="fromMessageId">The ID of the starting message to get the messages from.</param>
<param name="dir">The direction of the messages to be gotten from.</param>
<param name="limit">The numbers of message to be gotten from.</param>
<param name="user">The <see cref="T:Discord.IGuildUser"/> to add.</param>
<param name="options">The options to be used when sending the request.</param>
<returns>
Paged collection of messages.
A task that represents the asynchronous operation of adding a member to a thread.
</returns>
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.GetMessagesAsync(Discord.IMessage,Discord.Direction,System.Int32,Discord.RequestOptions)">
<member name="M:Discord.WebSocket.SocketThreadChannel.RemoveUserAsync(Discord.IGuildUser,Discord.RequestOptions)">
<summary>
Gets a collection of messages in this channel.
Removes a user from this thread.
</summary>
<remarks>
This method follows the same behavior as described in <see cref="M:Discord.IMessageChannel.GetMessagesAsync(Discord.IMessage,Discord.Direction,System.Int32,Discord.CacheMode,Discord.RequestOptions)"/>.
Please visit its documentation for more details on this method.
</remarks>
<param name="fromMessage">The starting message to get the messages from.</param>
<param name="dir">The direction of the messages to be gotten from.</param>
<param name="limit">The numbers of message to be gotten from.</param>
<param name="user">The <see cref="T:Discord.IGuildUser"/> to remove from this thread.</param>
<param name="options">The options to be used when sending the request.</param>
<returns>
Paged collection of messages.
A task that represents the asynchronous operation of removing a user from this thread.
</returns>
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.GetCachedMessages(System.Int32)">
<inheritdoc />
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.GetCachedMessages(System.UInt64,Discord.Direction,System.Int32)">
<inheritdoc />
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.GetCachedMessages(Discord.IMessage,Discord.Direction,System.Int32)">
<inheritdoc />
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.GetPinnedMessagesAsync(Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.SendMessageAsync(System.String,System.Boolean,Discord.Embed,Discord.RequestOptions,Discord.AllowedMentions,Discord.MessageReference,Discord.MessageComponent)">
<inheritdoc />
<exception cref="T:System.ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="F:Discord.DiscordConfig.MaxMessageSize"/>.</exception>
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.SendFileAsync(System.String,System.String,System.Boolean,Discord.Embed,Discord.RequestOptions,System.Boolean,Discord.AllowedMentions,Discord.MessageReference,Discord.MessageComponent)">
<inheritdoc />
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.SendFileAsync(System.IO.Stream,System.String,System.String,System.Boolean,Discord.Embed,Discord.RequestOptions,System.Boolean,Discord.AllowedMentions,Discord.MessageReference,Discord.MessageComponent)">
<inheritdoc />
<exception cref="T:System.ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="F:Discord.DiscordConfig.MaxMessageSize"/>.</exception>
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.DeleteMessagesAsync(System.Collections.Generic.IEnumerable{Discord.IMessage},Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.DeleteMessagesAsync(System.Collections.Generic.IEnumerable{System.UInt64},Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.ModifyMessageAsync(System.UInt64,System.Action{Discord.MessageProperties},Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.DeleteMessageAsync(System.UInt64,Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.DeleteMessageAsync(Discord.IMessage,Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.TriggerTypingAsync(Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.EnterTypingState(Discord.RequestOptions)">
<inheritdoc />
<member name="M:Discord.WebSocket.SocketThreadChannel.AddPermissionOverwriteAsync(Discord.IRole,Discord.OverwritePermissions,Discord.RequestOptions)">
<inheritdoc/>
<remarks>
<b>This method is not supported in threads.</b>
</remarks>
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.GetUser(System.UInt64)">
<inheritdoc />
<member name="M:Discord.WebSocket.SocketThreadChannel.AddPermissionOverwriteAsync(Discord.IUser,Discord.OverwritePermissions,Discord.RequestOptions)">
<inheritdoc/>
<remarks>
<b>This method is not supported in threads.</b>
</remarks>
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.Discord#ITextChannel#CreateWebhookAsync(System.String,System.IO.Stream,Discord.RequestOptions)">
<inheritdoc />
<member name="M:Discord.WebSocket.SocketThreadChannel.CreateInviteAsync(System.Nullable{System.Int32},System.Nullable{System.Int32},System.Boolean,System.Boolean,Discord.RequestOptions)">
<inheritdoc/>
<remarks>
<b>This method is not supported in threads.</b>
</remarks>
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.Discord#ITextChannel#GetWebhookAsync(System.UInt64,Discord.RequestOptions)">
<inheritdoc />
<member name="M:Discord.WebSocket.SocketThreadChannel.CreateInviteToApplicationAsync(System.UInt64,System.Nullable{System.Int32},System.Nullable{System.Int32},System.Boolean,System.Boolean,Discord.RequestOptions)">
<inheritdoc/>
<remarks>
<b>This method is not supported in threads.</b>
</remarks>
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.Discord#ITextChannel#GetWebhooksAsync(Discord.RequestOptions)">
<inheritdoc />
<member name="M:Discord.WebSocket.SocketThreadChannel.CreateInviteToStreamAsync(Discord.IUser,System.Nullable{System.Int32},System.Nullable{System.Int32},System.Boolean,System.Boolean,Discord.RequestOptions)">
<inheritdoc/>
<remarks>
<b>This method is not supported in threads.</b>
</remarks>
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.Discord#IGuildChannel#GetUserAsync(System.UInt64,Discord.CacheMode,Discord.RequestOptions)">
<inheritdoc />
<member name="M:Discord.WebSocket.SocketThreadChannel.CreateWebhookAsync(System.String,System.IO.Stream,Discord.RequestOptions)">
<inheritdoc/>
<remarks>
<b>This method is not supported in threads.</b>
</remarks>
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.Discord#IGuildChannel#GetUsersAsync(Discord.CacheMode,Discord.RequestOptions)">
<inheritdoc />
<member name="M:Discord.WebSocket.SocketThreadChannel.GetInvitesAsync(Discord.RequestOptions)">
<inheritdoc/>
<remarks>
<b>This method is not supported in threads.</b>
</remarks>
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.Discord#IMessageChannel#GetMessageAsync(System.UInt64,Discord.CacheMode,Discord.RequestOptions)">
<inheritdoc />
<member name="M:Discord.WebSocket.SocketThreadChannel.GetPermissionOverwrite(Discord.IRole)">
<inheritdoc/>
<remarks>
<b>This method is not supported in threads.</b>
</remarks>
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.Discord#IMessageChannel#GetMessagesAsync(System.Int32,Discord.CacheMode,Discord.RequestOptions)">
<inheritdoc />
<member name="M:Discord.WebSocket.SocketThreadChannel.GetPermissionOverwrite(Discord.IUser)">
<inheritdoc/>
<remarks>
<b>This method is not supported in threads.</b>
</remarks>
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.Discord#IMessageChannel#GetMessagesAsync(System.UInt64,Discord.Direction,System.Int32,Discord.CacheMode,Discord.RequestOptions)">
<inheritdoc />
<member name="M:Discord.WebSocket.SocketThreadChannel.GetWebhookAsync(System.UInt64,Discord.RequestOptions)">
<inheritdoc/>
<remarks>
<b>This method is not supported in threads.</b>
</remarks>
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.Discord#IMessageChannel#GetMessagesAsync(Discord.IMessage,Discord.Direction,System.Int32,Discord.CacheMode,Discord.RequestOptions)">
<inheritdoc />
<member name="M:Discord.WebSocket.SocketThreadChannel.GetWebhooksAsync(Discord.RequestOptions)">
<inheritdoc/>
<remarks>
<b>This method is not supported in threads.</b>
</remarks>
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.Discord#IMessageChannel#GetPinnedMessagesAsync(Discord.RequestOptions)">
<inheritdoc />
<member name="M:Discord.WebSocket.SocketThreadChannel.ModifyAsync(System.Action{Discord.TextChannelProperties},Discord.RequestOptions)">
<inheritdoc/>
<remarks>
<b>This method is not supported in threads.</b>
</remarks>
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.Discord#IMessageChannel#SendFileAsync(System.String,System.String,System.Boolean,Discord.Embed,Discord.RequestOptions,System.Boolean,Discord.AllowedMentions,Discord.MessageReference,Discord.MessageComponent)">
<inheritdoc />
<member name="M:Discord.WebSocket.SocketThreadChannel.RemovePermissionOverwriteAsync(Discord.IRole,Discord.RequestOptions)">
<inheritdoc/>
<remarks>
<b>This method is not supported in threads.</b>
</remarks>
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.Discord#IMessageChannel#SendFileAsync(System.IO.Stream,System.String,System.String,System.Boolean,Discord.Embed,Discord.RequestOptions,System.Boolean,Discord.AllowedMentions,Discord.MessageReference,Discord.MessageComponent)">
<inheritdoc />
<member name="M:Discord.WebSocket.SocketThreadChannel.RemovePermissionOverwriteAsync(Discord.IUser,Discord.RequestOptions)">
<inheritdoc/>
<remarks>
<b>This method is not supported in threads.</b>
</remarks>
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.Discord#IMessageChannel#SendMessageAsync(System.String,System.Boolean,Discord.Embed,Discord.RequestOptions,Discord.AllowedMentions,Discord.MessageReference,Discord.MessageComponent)">
<inheritdoc />
<member name="P:Discord.WebSocket.SocketThreadChannel.PermissionOverwrites">
<inheritdoc/>
<remarks>
<b>This method is not supported in threads.</b>
</remarks>
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.Discord#INestedChannel#GetCategoryAsync(Discord.CacheMode,Discord.RequestOptions)">
<inheritdoc />
<member name="M:Discord.WebSocket.SocketThreadChannel.SyncPermissionsAsync(Discord.RequestOptions)">
<inheritdoc/>
<remarks>
<b>This method is not supported in threads.</b>
</remarks>
</member>
<member name="T:Discord.WebSocket.SocketVoiceChannel">
<summary>
@@ -4535,6 +4525,11 @@
<member name="M:Discord.WebSocket.SocketSelfUser.ModifyAsync(System.Action{Discord.SelfUserProperties},Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="T:Discord.WebSocket.SocketThreadUser">
<summary>
Represents a thread user received over the gateway.
</summary>
</member>
<member name="P:Discord.WebSocket.SocketThreadUser.Thread">
<summary>
Gets the <see cref="T:Discord.WebSocket.SocketThreadChannel"/> this user is in.
@@ -4565,9 +4560,6 @@
<member name="P:Discord.WebSocket.SocketThreadUser.AvatarId">
<inheritdoc/>
</member>
<member name="P:Discord.WebSocket.SocketThreadUser.Discriminator">
<inheritdoc/>
</member>
<member name="P:Discord.WebSocket.SocketThreadUser.DiscriminatorValue">
<inheritdoc/>
</member>
@@ -4580,27 +4572,6 @@
<member name="P:Discord.WebSocket.SocketThreadUser.Username">
<inheritdoc/>
</member>
<member name="P:Discord.WebSocket.SocketThreadUser.PublicFlags">
<inheritdoc/>
</member>
<member name="P:Discord.WebSocket.SocketThreadUser.CreatedAt">
<inheritdoc/>
</member>
<member name="P:Discord.WebSocket.SocketThreadUser.Id">
<inheritdoc/>
</member>
<member name="P:Discord.WebSocket.SocketThreadUser.Mention">
<inheritdoc/>
</member>
<member name="P:Discord.WebSocket.SocketThreadUser.Status">
<inheritdoc/>
</member>
<member name="P:Discord.WebSocket.SocketThreadUser.ActiveClients">
<inheritdoc/>
</member>
<member name="P:Discord.WebSocket.SocketThreadUser.Activities">
<inheritdoc/>
</member>
<member name="P:Discord.WebSocket.SocketThreadUser.IsDeafened">
<inheritdoc/>
</member>
@@ -4658,15 +4629,6 @@
<member name="M:Discord.WebSocket.SocketThreadUser.RemoveRolesAsync(System.Collections.Generic.IEnumerable{Discord.IRole},Discord.RequestOptions)">
<inheritdoc/>
</member>
<member name="M:Discord.WebSocket.SocketThreadUser.GetAvatarUrl(Discord.ImageFormat,System.UInt16)">
<inheritdoc/>
</member>
<member name="M:Discord.WebSocket.SocketThreadUser.GetDefaultAvatarUrl">
<inheritdoc/>
</member>
<member name="M:Discord.WebSocket.SocketThreadUser.CreateDMChannelAsync(Discord.RequestOptions)">
<inheritdoc/>
</member>
<member name="P:Discord.WebSocket.SocketThreadUser.Discord#IGuildUser#GuildPermissions">
<inheritdoc/>
</member>


+ 109
- 39
src/Discord.Net.WebSocket/DiscordSocketClient.cs View File

@@ -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<Channel>(_serializer);
var data = (payload as JToken).ToObject<Channel>(_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<API.Channel>(_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<API.Channel>(_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<ThreadMember>(_serializer);

//var guild = State.GetGuild(data.)

}

break;
@@ -2097,6 +2103,69 @@ namespace Discord.WebSocket

var data = (payload as JToken).ToObject<ThreadMembersUpdated>(_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<SocketThreadUser> leftUsers = null;
IReadOnlyCollection<SocketThreadUser> joinUsers = null;


if (data.RemovedMemberIds.IsSpecified)
{
leftUsers = thread.RemoveUsers(data.RemovedMemberIds.Value);
}

if (data.AddedMembers.IsSpecified)
{
List<SocketThreadUser> newThreadMembers = new List<SocketThreadUser>();
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);
}
}



+ 1
- 0
src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs View File

@@ -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.");
}


+ 48
- 8
src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs View File

@@ -78,9 +78,46 @@ namespace Discord.WebSocket
}

/// <inheritdoc />
public Task ModifyAsync(Action<TextChannelProperties> func, RequestOptions options = null)
public virtual Task ModifyAsync(Action<TextChannelProperties> func, RequestOptions options = null)
=> ChannelHelper.ModifyAsync(this, Discord, func, options);

/// <summary>
/// Creates a thread within this <see cref="ITextChannel"/>.
/// </summary>
/// <remarks>
/// When <paramref name="message"/> is <see langword="null"/> the thread type will be based off of the
/// channel its created in. When called on a <see cref="ITextChannel"/>, it creates a <see cref="ThreadType.PublicThread"/>.
/// When called on a <see cref="INewsChannel"/>, it creates a <see cref="ThreadType.NewsThread"/>. The id of the created
/// thread will be the same as the id of the message, and as such a message can only have a
/// single thread created from it.
/// </remarks>
/// <param name="name">The name of the thread.</param>
/// <param name="type">
/// The type of the thread.
/// <para>
/// <b>Note: </b>This parameter is not used if the <paramref name="message"/> parameter is not specified.
/// </para>
/// </param>
/// <param name="autoArchiveDuration">
/// The duration on which this thread archives after.
/// <para>
/// <b>Note: </b> Options <see cref="ThreadArchiveDuration.OneWeek"/> and <see cref="ThreadArchiveDuration.ThreeDays"/>
/// are only available for guilds that are boosted. You can check in the <see cref="IGuild.Features"/> to see if the
/// guild has the <b>THREE_DAY_THREAD_ARCHIVE</b> and <b>SEVEN_DAY_THREAD_ARCHIVE</b>.
/// </para>
/// </param>
/// <param name="message">The message which to start the thread from.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous create operation. The task result contains a <see cref="IThreadChannel"/>
/// </returns>
public async Task<SocketThreadChannel> CreateThreadAsync(string name, ThreadType type = ThreadType.PublicThread,
ThreadArchiveDuration autoArchiveDuration = ThreadArchiveDuration.OneDay, IMessage message = null, RequestOptions options = null)
{
var model = await ThreadHelper.CreateThreadAsync(Discord, this, name, type, autoArchiveDuration, message, options);
return SocketThreadChannel.Create(this.Guild, Discord.State, model);
}

//Messages
/// <inheritdoc />
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.
/// </returns>
public Task<RestWebhook> CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null)
public virtual Task<RestWebhook> CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null)
=> ChannelHelper.CreateWebhookAsync(this, Discord, name, avatar, options);
/// <summary>
/// Gets a webhook available in this text channel.
@@ -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; <c>null</c> if the webhook is not found.
/// </returns>
public Task<RestWebhook> GetWebhookAsync(ulong id, RequestOptions options = null)
public virtual Task<RestWebhook> GetWebhookAsync(ulong id, RequestOptions options = null)
=> ChannelHelper.GetWebhookAsync(this, Discord, id, options);
/// <summary>
/// Gets the webhooks available in this text channel.
@@ -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.
/// </returns>
public Task<IReadOnlyCollection<RestWebhook>> GetWebhooksAsync(RequestOptions options = null)
public virtual Task<IReadOnlyCollection<RestWebhook>> GetWebhooksAsync(RequestOptions options = null)
=> ChannelHelper.GetWebhooksAsync(this, Discord, options);

//Invites
/// <inheritdoc />
public async Task<IInviteMetadata> CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
public virtual async Task<IInviteMetadata> CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
=> await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, options).ConfigureAwait(false);
/// <inheritdoc />
public async Task<IInviteMetadata> CreateInviteToApplicationAsync(ulong applicationId, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
public virtual async Task<IInviteMetadata> CreateInviteToApplicationAsync(ulong applicationId, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
=> await ChannelHelper.CreateInviteToApplicationAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, applicationId, options).ConfigureAwait(false);
/// <inheritdoc />
public async Task<IInviteMetadata> CreateInviteToStreamAsync(IUser user, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
public virtual async Task<IInviteMetadata> CreateInviteToStreamAsync(IUser user, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
=> await ChannelHelper.CreateInviteToStreamAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, user, options).ConfigureAwait(false);
/// <inheritdoc />
public async Task<IReadOnlyCollection<IInviteMetadata>> GetInvitesAsync(RequestOptions options = null)
public virtual async Task<IReadOnlyCollection<IInviteMetadata>> 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
/// <inheritdoc />
async Task<IReadOnlyCollection<IWebhook>> ITextChannel.GetWebhooksAsync(RequestOptions options)
=> await GetWebhooksAsync(options).ConfigureAwait(false);
/// <inheritdoc />
async Task<IThreadChannel> ITextChannel.CreateThreadAsync(string name, ThreadType type, ThreadArchiveDuration autoArchiveDuration, IMessage message, RequestOptions options)
=> await CreateThreadAsync(name, type, autoArchiveDuration, message, options);

//IGuildChannel
/// <inheritdoc />


+ 209
- 228
src/Discord.Net.WebSocket/Entities/Channels/SocketThreadChannel.cs View File

@@ -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.
/// </summary>
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
public class SocketThreadChannel : SocketGuildChannel, IThreadChannel, ISocketMessageChannel
public class SocketThreadChannel : SocketTextChannel, IThreadChannel
{
/// <inheritdoc/>
public ThreadType Type { get; private set; }

/// <summary>
/// Gets the owner of the current thread.
/// </summary>
public SocketThreadUser Owner { get; private set; }

/// <summary>
/// Gets the current users within this thread.
/// </summary>
public SocketThreadUser CurrentUser
=> Users.FirstOrDefault(x => x.Id == Discord.CurrentUser.Id);

/// <inheritdoc/>
public bool Joined { get; private set; }
@@ -25,7 +40,8 @@ namespace Discord.WebSocket
/// <summary>
/// <see langword="true"/> if this thread is private, otherwise <see langword="false"/>
/// </summary>
public bool IsPrivateThread { get; private set; }
public bool IsPrivateThread
=> this.Type == ThreadType.PrivateThread;

/// <summary>
/// Gets the parent channel this thread resides in.
@@ -50,37 +66,29 @@ namespace Discord.WebSocket
/// <inheritdoc/>
public bool Locked { get; private set; }

/// <inheritdoc/>
public bool IsNsfw { get; private set; }

/// <inheritdoc/>
public string Topic { get; private set; }

/// <inheritdoc/>
public int SlowModeInterval { get; private set; }

/// <inheritdoc/>
public string Mention { get; private set; }
/// <summary>
/// Gets a collection of cached users within this thread.
/// </summary>
public new IReadOnlyCollection<SocketThreadUser> Users =>
_members.Values.ToImmutableArray();

/// <inheritdoc/>
public ulong? CategoryId { get; private set; }

/// <inheritdoc />
public IReadOnlyCollection<SocketMessage> CachedMessages => _messages?.Messages ?? ImmutableArray.Create<SocketMessage>();
private ConcurrentDictionary<ulong, SocketThreadUser> _members;

public new IReadOnlyCollection<SocketThreadUser> Users = ImmutableArray.Create<SocketThreadUser>();
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<ulong, SocketThreadUser>();
}

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<SocketThreadUser> RemoveUsers(ulong[] users)
{
List<SocketThreadUser> threadUsers = new();

}
foreach (var userId in users)
{
if (_members.TryRemove(userId, out var user))
threadUsers.Add(user);
}

/// <inheritdoc />
public virtual Task SyncPermissionsAsync(RequestOptions options = null)
=> ChannelHelper.SyncPermissionsAsync(this, Discord, options);
return threadUsers.ToImmutableArray();
}

/// <inheritdoc />
public Task ModifyAsync(Action<TextChannelProperties> 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
/// <inheritdoc />
public SocketMessage GetCachedMessage(ulong id)
=> _messages?.Get(id);
public new SocketThreadUser GetUser(ulong id)
{
var user = Users.FirstOrDefault(x => x.Id == id);
return user;
}

/// <summary>
/// Gets a message from this message channel.
/// Gets all users inside this thread.
/// </summary>
/// <remarks>
/// This method follows the same behavior as described in <see cref="IMessageChannel.GetMessageAsync"/>.
/// Please visit its documentation for more details on this method.
/// If all users are not downloaded then this method will call <see cref="DownloadUsersAsync(RequestOptions)"/> and return the result.
/// </remarks>
/// <param name="id">The snowflake identifier of the message.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents an asynchronous get operation for retrieving the message. The task result contains
/// the retrieved message; <c>null</c> if no message is found with the specified identifier.
/// </returns>
public async Task<IMessage> GetMessageAsync(ulong id, RequestOptions options = null)
/// <returns>A task representing the download operation.</returns>
public async Task<IReadOnlyCollection<SocketThreadUser>> 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;
}


/// <summary>
/// Gets the last N messages from this message channel.
/// Downloads all users that have access to this thread.
/// </summary>
/// <remarks>
/// This method follows the same behavior as described in <see cref="IMessageChannel.GetMessagesAsync(int, CacheMode, RequestOptions)"/>.
/// Please visit its documentation for more details on this method.
/// </remarks>
/// <param name="limit">The numbers of message to be gotten from.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// Paged collection of messages.
/// </returns>
public IAsyncEnumerable<IReadOnlyCollection<IMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
=> SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, CacheMode.AllowDownload, options);
/// <returns>A task representing the asyncronous download operation.</returns>
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;

/// <inheritdoc/>
public Task JoinAsync(RequestOptions options = null)
=> Discord.ApiClient.JoinThreadAsync(this.Id, options);

/// <inheritdoc/>
public Task LeaveAsync(RequestOptions options = null)
=> Discord.ApiClient.LeaveThreadAsync(this.Id, options);

/// <summary>
/// Gets a collection of messages in this channel.
/// Adds a user to this thread.
/// </summary>
/// <remarks>
/// This method follows the same behavior as described in <see cref="IMessageChannel.GetMessagesAsync(ulong, Direction, int, CacheMode, RequestOptions)"/>.
/// Please visit its documentation for more details on this method.
/// </remarks>
/// <param name="fromMessageId">The ID of the starting message to get the messages from.</param>
/// <param name="dir">The direction of the messages to be gotten from.</param>
/// <param name="limit">The numbers of message to be gotten from.</param>
/// <param name="user">The <see cref="IGuildUser"/> to add.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// Paged collection of messages.
/// A task that represents the asynchronous operation of adding a member to a thread.
/// </returns>
public IAsyncEnumerable<IReadOnlyCollection<IMessage>> 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);

/// <summary>
/// Gets a collection of messages in this channel.
/// Removes a user from this thread.
/// </summary>
/// <remarks>
/// This method follows the same behavior as described in <see cref="IMessageChannel.GetMessagesAsync(IMessage, Direction, int, CacheMode, RequestOptions)"/>.
/// Please visit its documentation for more details on this method.
/// </remarks>
/// <param name="fromMessage">The starting message to get the messages from.</param>
/// <param name="dir">The direction of the messages to be gotten from.</param>
/// <param name="limit">The numbers of message to be gotten from.</param>
/// <param name="user">The <see cref="IGuildUser"/> to remove from this thread.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// Paged collection of messages.
/// A task that represents the asynchronous operation of removing a user from this thread.
/// </returns>
public IAsyncEnumerable<IReadOnlyCollection<IMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
=> SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, CacheMode.AllowDownload, options);

/// <inheritdoc />
public IReadOnlyCollection<SocketMessage> GetCachedMessages(int limit = DiscordConfig.MaxMessagesPerBatch)
=> SocketChannelHelper.GetCachedMessages(this, Discord, _messages, null, Direction.Before, limit);

/// <inheritdoc />
public IReadOnlyCollection<SocketMessage> GetCachedMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch)
=> SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessageId, dir, limit);

/// <inheritdoc />
public IReadOnlyCollection<SocketMessage> GetCachedMessages(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch)
=> SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessage.Id, dir, limit);

/// <inheritdoc />
public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null)
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord, options);

/// <inheritdoc />
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception>
public Task<RestUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, 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);

/// <inheritdoc />
public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null)
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, allowedMentions, messageReference, component, options, isSpoiler);

/// <inheritdoc />
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception>
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null)
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, allowedMentions, messageReference, component, options, isSpoiler);

/// <inheritdoc />
public Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null)
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options);
/// <inheritdoc />
public Task DeleteMessagesAsync(IEnumerable<ulong> messageIds, RequestOptions options = null)
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options);

/// <inheritdoc />
public async Task<IUserMessage> ModifyMessageAsync(ulong messageId, Action<MessageProperties> func, RequestOptions options = null)
=> await ChannelHelper.ModifyMessageAsync(this, messageId, func, Discord, options).ConfigureAwait(false);

/// <inheritdoc />
public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null)
=> ChannelHelper.DeleteMessageAsync(this, messageId, Discord, options);

/// <inheritdoc />
public Task DeleteMessageAsync(IMessage message, RequestOptions options = null)
=> ChannelHelper.DeleteMessageAsync(this, message.Id, Discord, options);

/// <inheritdoc />
public Task TriggerTypingAsync(RequestOptions options = null)
=> ChannelHelper.TriggerTypingAsync(this, Discord, options);

/// <inheritdoc />
public IDisposable EnterTypingState(RequestOptions options = null)
=> ChannelHelper.EnterTypingState(this, Discord, options);
/// <inheritdoc/>
/// <remarks>
/// <b>This method is not supported in threads.</b>
/// </remarks>
public override Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, RequestOptions options = null)
=> throw new NotImplementedException();

internal void AddMessage(SocketMessage msg)
=> _messages?.Add(msg);
/// <inheritdoc/>
/// <remarks>
/// <b>This method is not supported in threads.</b>
/// </remarks>
public override Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions, RequestOptions options = null)
=> throw new NotImplementedException();

internal SocketMessage RemoveMessage(ulong id)
=> _messages?.Remove(id);
/// <inheritdoc/>
/// <remarks>
/// <b>This method is not supported in threads.</b>
/// </remarks>
public override Task<IInviteMetadata> CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
=> throw new NotImplementedException();

//Users
/// <inheritdoc />
public new SocketThreadUser GetUser(ulong id)
{
var user = Users.FirstOrDefault(x => x.Id == id);
return user;
}
/// <inheritdoc/>
/// <remarks>
/// <b>This method is not supported in threads.</b>
/// </remarks>
public override Task<IInviteMetadata> CreateInviteToApplicationAsync(ulong applicationId, int? maxAge, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
=> throw new NotImplementedException();

public Task GetUsersAsync()
{
return Task.CompletedTask;
}
/// <inheritdoc/>
/// <remarks>
/// <b>This method is not supported in threads.</b>
/// </remarks>
public override Task<IInviteMetadata> CreateInviteToStreamAsync(IUser user, int? maxAge, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
=> throw new NotImplementedException();

internal new SocketThreadChannel Clone() => MemberwiseClone() as SocketThreadChannel;
/// <inheritdoc/>
/// <remarks>
/// <b>This method is not supported in threads.</b>
/// </remarks>
public override Task<RestWebhook> CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null)
=> throw new NotImplementedException();

//ITextChannel
/// <inheritdoc />
Task<IWebhook> ITextChannel.CreateWebhookAsync(string name, Stream avatar, RequestOptions options)
=> throw new NotSupportedException("Thread channels don't support webhooks");
/// <inheritdoc />
Task<IWebhook> ITextChannel.GetWebhookAsync(ulong id, RequestOptions options)
=> throw new NotSupportedException("Thread channels don't support webhooks");
/// <inheritdoc />
Task<IReadOnlyCollection<IWebhook>> ITextChannel.GetWebhooksAsync(RequestOptions options)
=> throw new NotSupportedException("Thread channels don't support webhooks");
/// <inheritdoc/>
/// <remarks>
/// <b>This method is not supported in threads.</b>
/// </remarks>
public override Task<IReadOnlyCollection<IInviteMetadata>> GetInvitesAsync(RequestOptions options = null)
=> throw new NotImplementedException();

//IGuildChannel
/// <inheritdoc />
Task<IGuildUser> IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options)
=> Task.FromResult<IGuildUser>(GetUser(id));
/// <inheritdoc />
IAsyncEnumerable<IReadOnlyCollection<IGuildUser>> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options)
=> ImmutableArray.Create<IReadOnlyCollection<IGuildUser>>(Users).ToAsyncEnumerable();
/// <inheritdoc/>
/// <remarks>
/// <b>This method is not supported in threads.</b>
/// </remarks>
public override OverwritePermissions? GetPermissionOverwrite(IRole role)
=> throw new NotImplementedException();

//IMessageChannel
/// <inheritdoc />
async Task<IMessage> IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options)
{
if (mode == CacheMode.AllowDownload)
return await GetMessageAsync(id, options).ConfigureAwait(false);
else
return GetCachedMessage(id);
}
/// <inheritdoc />
IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options)
=> SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, mode, options);
/// <inheritdoc />
IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options)
=> SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, mode, options);
/// <inheritdoc />
IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode, RequestOptions options)
=> SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, mode, options);
/// <inheritdoc />
async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options)
=> await GetPinnedMessagesAsync(options).ConfigureAwait(false);
/// <inheritdoc/>
/// <remarks>
/// <b>This method is not supported in threads.</b>
/// </remarks>
public override OverwritePermissions? GetPermissionOverwrite(IUser user)
=> throw new NotImplementedException();

/// <inheritdoc />
async Task<IUserMessage> 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);
/// <inheritdoc />
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent component)
=> await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler, allowedMentions, messageReference, component).ConfigureAwait(false);
/// <inheritdoc />
async Task<IUserMessage> 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);
/// <inheritdoc/>
/// <remarks>
/// <b>This method is not supported in threads.</b>
/// </remarks>
public override Task<RestWebhook> GetWebhookAsync(ulong id, RequestOptions options = null)
=> throw new NotImplementedException();

// INestedChannel
/// <inheritdoc />
Task<ICategoryChannel> INestedChannel.GetCategoryAsync(CacheMode mode, RequestOptions options)
=> Task.FromResult(this.ParentChannel.Category);
Task<IInviteMetadata> INestedChannel.CreateInviteAsync(int? maxAge, int? maxUses, bool isTemporary, bool isUnique, RequestOptions options)
=> throw new NotSupportedException("Thread channels don't support invites");
Task<IInviteMetadata> INestedChannel.CreateInviteToApplicationAsync(ulong applicationId, int? maxAge, int? maxUses, bool isTemporary, bool isUnique, RequestOptions options)
=> throw new NotSupportedException("Thread channels don't support invites");
Task<IInviteMetadata> INestedChannel.CreateInviteToStreamAsync(IUser user, int? maxAge, int? maxUses, bool isTemporary, bool isUnique, RequestOptions options)
=> throw new NotSupportedException("Thread channels don't support invites");
Task<IReadOnlyCollection<IInviteMetadata>> INestedChannel.GetInvitesAsync(RequestOptions options)
=> throw new NotSupportedException("Thread channels don't support invites");

public Task JoinAsync()
{
return Task.CompletedTask;
}
/// <inheritdoc/>
/// <remarks>
/// <b>This method is not supported in threads.</b>
/// </remarks>
public override Task<IReadOnlyCollection<RestWebhook>> GetWebhooksAsync(RequestOptions options = null)
=> throw new NotImplementedException();

public Task LeaveAsync()
{
return Task.CompletedTask;
}
/// <inheritdoc/>
/// <remarks>
/// <b>This method is not supported in threads.</b>
/// </remarks>
public override Task ModifyAsync(Action<TextChannelProperties> func, RequestOptions options = null)
=> throw new NotImplementedException();

public Task AddThreadMember(IGuildUser user)
{
return Task.CompletedTask;
}
/// <inheritdoc/>
/// <remarks>
/// <b>This method is not supported in threads.</b>
/// </remarks>
public override Task RemovePermissionOverwriteAsync(IRole role, RequestOptions options = null)
=> throw new NotImplementedException();

public Task RemoveThreadMember(IGuildUser user)
{
return Task.CompletedTask;
}
/// <inheritdoc/>
/// <remarks>
/// <b>This method is not supported in threads.</b>
/// </remarks>
public override Task RemovePermissionOverwriteAsync(IUser user, RequestOptions options = null)
=> throw new NotImplementedException();

/// <inheritdoc/>
/// <remarks>
/// <b>This method is not supported in threads.</b>
/// </remarks>
public override IReadOnlyCollection<Overwrite> PermissionOverwrites
=> throw new NotImplementedException();

/// <inheritdoc/>
/// <remarks>
/// <b>This method is not supported in threads.</b>
/// </remarks>
public override Task SyncPermissionsAsync(RequestOptions options = null)
=> throw new NotImplementedException();
}
}

+ 8
- 0
src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs View File

@@ -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<ulong, SocketGuildUser>(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.Members.Length * 1.05));


+ 41
- 52
src/Discord.Net.WebSocket/Entities/Users/SocketThreadUser.cs View File

@@ -10,7 +10,10 @@ using System.Collections.Immutable;

namespace Discord.WebSocket
{
public class SocketThreadUser : IGuildUser
/// <summary>
/// Represents a thread user received over the gateway.
/// </summary>
public class SocketThreadUser : SocketUser, IGuildUser
{
/// <summary>
/// Gets the <see cref="SocketThreadChannel"/> this user is in.
@@ -44,56 +47,36 @@ namespace Discord.WebSocket
=> GuildUser.IsPending;

/// <inheritdoc/>
public string AvatarId
=> GuildUser.AvatarId;

/// <inheritdoc/>
public string Discriminator
=> GuildUser.Discriminator;
public override string AvatarId
{
get => GuildUser.AvatarId;
internal set => GuildUser.AvatarId = value;
}

/// <inheritdoc/>
public ushort DiscriminatorValue
=> GuildUser.DiscriminatorValue;
public override ushort DiscriminatorValue
{
get => GuildUser.DiscriminatorValue;
internal set => GuildUser.DiscriminatorValue = value;
}

/// <inheritdoc/>
public bool IsBot
=> GuildUser.IsBot;
public override bool IsBot
{
get => GuildUser.IsBot;
internal set => GuildUser.IsBot = value;
}

/// <inheritdoc/>
public bool IsWebhook
public override bool IsWebhook
=> GuildUser.IsWebhook;

/// <inheritdoc/>
public string Username
=> GuildUser.Username;

/// <inheritdoc/>
public UserProperties? PublicFlags
=> GuildUser.PublicFlags;

/// <inheritdoc/>
public DateTimeOffset CreatedAt
=> GuildUser.CreatedAt;

/// <inheritdoc/>
public ulong Id
=> GuildUser.Id;

/// <inheritdoc/>
public string Mention
=> GuildUser.Mention;

/// <inheritdoc/>
public UserStatus Status
=> GuildUser.Status;

/// <inheritdoc/>
public IImmutableSet<ClientType> ActiveClients
=> GuildUser.ActiveClients;

/// <inheritdoc/>
public IImmutableList<IActivity> Activities
=> GuildUser.Activities;
public override string Username
{
get => GuildUser.Username;
internal set => GuildUser.Username = value;
}

/// <inheritdoc/>
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
/// <inheritdoc/>
public Task RemoveRolesAsync(IEnumerable<IRole> roles, RequestOptions options = null) => GuildUser.RemoveRolesAsync(roles, options);

/// <inheritdoc/>
public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) => GuildUser.GetAvatarUrl(format, size);

/// <inheritdoc/>
public string GetDefaultAvatarUrl() => GuildUser.GetDefaultAvatarUrl();

/// <inheritdoc/>
public Task<IDMChannel> CreateDMChannelAsync(RequestOptions options = null) => GuildUser.CreateDMChannelAsync(options);

/// <inheritdoc/>
GuildPermissions IGuildUser.GuildPermissions => this.GuildUser.GuildPermissions;

@@ -203,6 +188,10 @@ namespace Discord.WebSocket
/// <inheritdoc/>
IReadOnlyCollection<ulong> 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; }

/// <summary>
/// Gets the guild user of this thread user.
/// </summary>


+ 1
- 1
test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs View File

@@ -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));
}
}
}

+ 2
- 0
test/Discord.Net.Tests.Unit/MockedEntities/MockedTextChannel.cs View File

@@ -210,5 +210,7 @@ namespace Discord
{
throw new NotImplementedException();
}

public Task<IThreadChannel> CreateThreadAsync(string name, ThreadType type = ThreadType.PublicThread, ThreadArchiveDuration autoArchiveDuration = ThreadArchiveDuration.OneDay, IMessage message = null, RequestOptions options = null) => throw new NotImplementedException();
}
}

Loading…
Cancel
Save