Browse Source

Threads initial

pull/1923/head
quin lynch 3 years ago
parent
commit
4db858b1e4
25 changed files with 1473 additions and 2 deletions
  1. +115
    -0
      src/Discord.Net.Core/Discord.Net.Core.xml
  2. +44
    -0
      src/Discord.Net.Core/Entities/Channels/IThreadChannel.cs
  3. +40
    -0
      src/Discord.Net.Core/Entities/Channels/ThreadArchiveDuration.cs
  4. +45
    -0
      src/Discord.Net.Core/Entities/Channels/ThreadChannelProperties.cs
  5. +12
    -0
      src/Discord.Net.Core/Entities/Messages/MessageFlags.cs
  6. +16
    -0
      src/Discord.Net.Rest/API/Common/Channel.cs
  7. +21
    -0
      src/Discord.Net.Rest/API/Common/ChannelThreads.cs
  8. +2
    -0
      src/Discord.Net.Rest/API/Common/Guild.cs
  9. +24
    -0
      src/Discord.Net.Rest/API/Common/ThreadMember.cs
  10. +24
    -0
      src/Discord.Net.Rest/API/Common/ThreadMetadata.cs
  11. +3
    -0
      src/Discord.Net.Rest/API/Rest/ModifyInteractionResponseParams.cs
  12. +21
    -0
      src/Discord.Net.Rest/API/Rest/StartThreadParams.cs
  13. +154
    -0
      src/Discord.Net.Rest/DiscordRestApiClient.cs
  14. +1
    -0
      src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs
  15. +9
    -1
      src/Discord.Net.WebSocket/API/Gateway/ExtendedGuild.cs
  16. +24
    -0
      src/Discord.Net.WebSocket/API/Gateway/ThreadListSyncEvent.cs
  17. +32
    -0
      src/Discord.Net.WebSocket/BaseSocketClient.Events.cs
  18. +1
    -1
      src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj
  19. +203
    -0
      src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml
  20. +106
    -0
      src/Discord.Net.WebSocket/DiscordSocketClient.cs
  21. +2
    -0
      src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs
  22. +6
    -0
      src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs
  23. +348
    -0
      src/Discord.Net.WebSocket/Entities/Channels/SocketThreadChannel.cs
  24. +8
    -0
      src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
  25. +212
    -0
      src/Discord.Net.WebSocket/Entities/Users/SocketThreadUser.cs

+ 115
- 0
src/Discord.Net.Core/Discord.Net.Core.xml View File

@@ -1982,6 +1982,41 @@
of webhooks that is available in this channel.
</returns>
</member>
<member name="T:Discord.IThreadChannel">
<summary>
Represents a thread channel inside of a guild.
</summary>
</member>
<member name="P:Discord.IThreadChannel.Archived">
<summary>
<see langword="true"/> if the current thread is archived, otherwise <see langword="false"/>.
</summary>
</member>
<member name="P:Discord.IThreadChannel.AutoArchiveDuration">
<summary>
Duration to automatically archive the thread after recent activity.
</summary>
</member>
<member name="P:Discord.IThreadChannel.ArchiveTimestamp">
<summary>
Timestamp when the thread's archive status was last changed, used for calculating recent activity.
</summary>
</member>
<member name="P:Discord.IThreadChannel.Locked">
<summary>
<see langword="true"/> if the current thread is locked, otherwise <see langword="false"/>
</summary>
</member>
<member name="P:Discord.IThreadChannel.MemberCount">
<summary>
An approximate count of users in a thread, stops counting at 50.
</summary>
</member>
<member name="P:Discord.IThreadChannel.MessageCount">
<summary>
An approximate count of messages in a thread, stops counting at 50.
</summary>
</member>
<member name="T:Discord.IVoiceChannel">
<summary>
Represents a generic voice channel in a guild.
@@ -2081,6 +2116,71 @@
</remarks>
<exception cref="T:System.ArgumentOutOfRangeException">Thrown if the value does not fall within [0, 21600].</exception>
</member>
<member name="T:Discord.ThreadArchiveDuration">
<summary>
Represents the thread auto archive duration.
</summary>
</member>
<member name="F:Discord.ThreadArchiveDuration.OneHour">
<summary>
One hour (60 minutes).
</summary>
</member>
<member name="F:Discord.ThreadArchiveDuration.OneDay">
<summary>
One day (1440 minutes).
</summary>
</member>
<member name="F:Discord.ThreadArchiveDuration.ThreeDays">
<summary>
Three days (4320 minutes).
<remarks>
This option is explicity avaliable to nitro users.
</remarks>
</summary>
</member>
<member name="F:Discord.ThreadArchiveDuration.OneWeek">
<summary>
One week (10080 minutes).
<remarks>
This option is explicity avaliable to nitro users.
</remarks>
</summary>
</member>
<member name="P:Discord.Entities.ThreadChannelProperties.Archived">
<summary>
Gets or sets whether or not the thread is archived.
</summary>
</member>
<member name="P:Discord.Entities.ThreadChannelProperties.Locked">
<summary>
Gets or sets whether or not the thread is locked.
</summary>
</member>
<member name="P:Discord.Entities.ThreadChannelProperties.Name">
<summary>
Gets or sets the name of the thread.
</summary>
</member>
<member name="P:Discord.Entities.ThreadChannelProperties.AutoArchiveDuration">
<summary>
Gets or sets the auto archive duration.
</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.
@@ -7029,6 +7129,21 @@
Flag given to messages that came from the urgent message system.
</summary>
</member>
<member name="F:Discord.MessageFlags.HasThread">
<summary>
Flag given to messages has an associated thread, with the same id as the message
</summary>
</member>
<member name="F:Discord.MessageFlags.Ephemeral">
<summary>
Flag given to messages that is only visible to the user who invoked the Interaction.
</summary>
</member>
<member name="F:Discord.MessageFlags.Loading">
<summary>
Flag given to messages that is an Interaction Response and the bot is "thinking"
</summary>
</member>
<member name="T:Discord.MessageProperties">
<summary>
Properties that are used to modify an <see cref="T:Discord.IUserMessage" /> with the specified changes.


+ 44
- 0
src/Discord.Net.Core/Entities/Channels/IThreadChannel.cs View File

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

namespace Discord
{
/// <summary>
/// Represents a thread channel inside of a guild.
/// </summary>
public interface IThreadChannel : ITextChannel, IGuildChannel
{
/// <summary>
/// <see langword="true"/> if the current thread is archived, otherwise <see langword="false"/>.
/// </summary>
bool Archived { get; }

/// <summary>
/// Duration to automatically archive the thread after recent activity.
/// </summary>
ThreadArchiveDuration AutoArchiveDuration { get; }

/// <summary>
/// Timestamp when the thread's archive status was last changed, used for calculating recent activity.
/// </summary>
DateTimeOffset ArchiveTimestamp { get; }

/// <summary>
/// <see langword="true"/> if the current thread is locked, otherwise <see langword="false"/>
/// </summary>
bool Locked { get; }

/// <summary>
/// An approximate count of users in a thread, stops counting at 50.
/// </summary>
int MemberCount { get; }

/// <summary>
/// An approximate count of messages in a thread, stops counting at 50.
/// </summary>
int MessageCount { get; }
}
}

+ 40
- 0
src/Discord.Net.Core/Entities/Channels/ThreadArchiveDuration.cs View File

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

namespace Discord
{
/// <summary>
/// Represents the thread auto archive duration.
/// </summary>
public enum ThreadArchiveDuration
{
/// <summary>
/// One hour (60 minutes).
/// </summary>
OneHour = 60,

/// <summary>
/// One day (1440 minutes).
/// </summary>
OneDay = 1440,

/// <summary>
/// Three days (4320 minutes).
/// <remarks>
/// This option is explicity avaliable to nitro users.
/// </remarks>
/// </summary>
ThreeDays = 4320,

/// <summary>
/// One week (10080 minutes).
/// <remarks>
/// This option is explicity avaliable to nitro users.
/// </remarks>
/// </summary>
OneWeek = 10080,
}
}

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

@@ -0,0 +1,45 @@
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; }
}
}

+ 12
- 0
src/Discord.Net.Core/Entities/Messages/MessageFlags.cs View File

@@ -32,5 +32,17 @@ namespace Discord
/// Flag given to messages that came from the urgent message system.
/// </summary>
Urgent = 1 << 4,
/// <summary>
/// Flag given to messages has an associated thread, with the same id as the message
/// </summary>
HasThread = 1 << 5,
/// <summary>
/// Flag given to messages that is only visible to the user who invoked the Interaction.
/// </summary>
Ephemeral = 1 << 6,
/// <summary>
/// Flag given to messages that is an Interaction Response and the bot is "thinking"
/// </summary>
Loading = 1 << 7
}
}

+ 16
- 0
src/Discord.Net.Rest/API/Common/Channel.cs View File

@@ -49,5 +49,21 @@ namespace Discord.API
//GroupChannel
[JsonProperty("icon")]
public Optional<string> Icon { get; set; }

//ThreadChannel
[JsonProperty("member")]
public Optional<ThreadMember> ThreadMember { get; set; }

[JsonProperty("thread_metadata")]
public Optional<ThreadMetadata> ThreadMetadata { get; set; }

[JsonProperty("owner_id")]
public Optional<ulong> OwnerId { get; set; }

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

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

+ 21
- 0
src/Discord.Net.Rest/API/Common/ChannelThreads.cs View File

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

namespace Discord.API.Rest
{
internal class ChannelThreads
{
[JsonProperty("threads")]
public Channel[] Threads { get; set; }

[JsonProperty("members")]
public ThreadMember[] Members { get; set; }

[JsonProperty("has_more")]
public bool HasMore { get; set; }
}
}

+ 2
- 0
src/Discord.Net.Rest/API/Common/Guild.cs View File

@@ -76,5 +76,7 @@ namespace Discord.API
public Optional<int> ApproximateMemberCount { get; set; }
[JsonProperty("approximate_presence_count")]
public Optional<int> ApproximatePresenceCount { get; set; }
[JsonProperty("threads")]
public Optional<Channel[]> Threads { get; set; }
}
}

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

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

namespace Discord.API
{
internal class ThreadMember
{
[JsonProperty("id")]
public Optional<ulong> Id { get; set; }

[JsonProperty("user_id")]
public Optional<ulong> UserId { get; set; }

[JsonProperty("join_timestamp")]
public DateTimeOffset JoinTimestamp { get; set; }

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

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

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

namespace Discord.API
{
internal class ThreadMetadata
{
[JsonProperty("archived")]
public bool Archived { get; set; }

[JsonProperty("auto_archive_duration")]
public int AutoArchiveDuration { get; set; }

[JsonProperty("archive_timestamp")]
public DateTimeOffset ArchiveTimestamp { get; set; }

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

+ 3
- 0
src/Discord.Net.Rest/API/Rest/ModifyInteractionResponseParams.cs View File

@@ -20,5 +20,8 @@ namespace Discord.API.Rest

[JsonProperty("components")]
public Optional<API.ActionRowComponent[]> Components { get; set; }

[JsonProperty("flags")]
public Optional<MessageFlags?> Flags { get; set; }
}
}

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

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

namespace Discord.API.Rest
{
internal class StartThreadParams
{
[JsonProperty("name")]
public string Name { get; set; }

[JsonProperty("duration")]
public ThreadArchiveDuration Duration { get; set; }

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

+ 154
- 0
src/Discord.Net.Rest/DiscordRestApiClient.cs View File

@@ -413,6 +413,160 @@ namespace Discord.API
break;
}
}

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

options = RequestOptions.CreateOrClone(options);

var bucket = new BucketIds(0, channelId);

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

options = RequestOptions.CreateOrClone(options);

var bucket = new BucketIds(0, channelId);

return await SendJsonAsync<Channel>("POST", () => $"channels/{channelId}/threads", args, bucket, options: options).ConfigureAwait(false);
}

public async Task JoinThreadAsync(ulong channelId, RequestOptions options = null)
{
Preconditions.NotEqual(channelId, 0, nameof(channelId));

options = RequestOptions.CreateOrClone(options);

await SendAsync("PUT", $"channels/{channelId}/thread-members/@me", options: options).ConfigureAwait(false);
}

public async Task AddThreadMemberAsync(ulong channelId, ulong userId, RequestOptions options = null)
{
Preconditions.NotEqual(channelId, 0, nameof(channelId));
Preconditions.NotEqual(userId, 0, nameof(channelId));

options = RequestOptions.CreateOrClone(options);

await SendAsync("PUT", $"channels/{channelId}/thread-members/{userId}", options: options).ConfigureAwait(false);
}

public async Task LeaveThreadAsync(ulong channelId, RequestOptions options = null)
{
Preconditions.NotEqual(channelId, 0, nameof(channelId));

options = RequestOptions.CreateOrClone(options);

await SendAsync("DELETE", $"channels/{channelId}/thread-members/@me", options: options).ConfigureAwait(false);
}

public async Task RemoveThreadMemberAsync(ulong channelId, ulong userId, RequestOptions options = null)
{
Preconditions.NotEqual(channelId, 0, nameof(channelId));
Preconditions.NotEqual(userId, 0, nameof(channelId));

options = RequestOptions.CreateOrClone(options);

await SendAsync("DELETE", $"channels/{channelId}/thread-members/{userId}", options: options).ConfigureAwait(false);
}

public async Task<ThreadMember[]> ListThreadMembersAsync(ulong channelId, RequestOptions options = null)
{
Preconditions.NotEqual(channelId, 0, nameof(channelId));

options = RequestOptions.CreateOrClone(options);

var bucket = new BucketIds(channelId: channelId);

return await SendAsync<ThreadMember[]>("GET", () => $"/channels/{channelId}", bucket, options: options);
}

public async Task<ChannelThreads> GetActiveThreadsAsync(ulong channelId, RequestOptions options = null)
{
Preconditions.NotEqual(channelId, 0, nameof(channelId));

options = RequestOptions.CreateOrClone(options);

var bucket = new BucketIds(channelId: channelId);

return await SendAsync<ChannelThreads>("GET", $"channels/{channelId}/threads/active");
}

public async Task<ChannelThreads> GetPublicArchivedThreadsAsync(ulong channelId, DateTimeOffset? before = null, int? limit = null, RequestOptions options = null)
{
Preconditions.NotEqual(channelId, 0, nameof(channelId));

options = RequestOptions.CreateOrClone(options);

var bucket = new BucketIds(channelId: channelId);

string query = "";

if (limit.HasValue)
{
query = $"?before={before.GetValueOrDefault(DateTimeOffset.UtcNow).ToString("O")}&limit={limit.Value}";
}
else if (before.HasValue)
{
query = $"?before={before.Value.ToString("O")}";
}

return await SendAsync<ChannelThreads>("GET", () => $"channels/{channelId}/threads/archived/public{query}", bucket, options: options);
}

public async Task<ChannelThreads> GetPrivateArchivedThreadsAsync(ulong channelId, DateTimeOffset? before = null, int? limit = null,
RequestOptions options = null)
{
Preconditions.NotEqual(channelId, 0, nameof(channelId));

options = RequestOptions.CreateOrClone(options);

var bucket = new BucketIds(channelId: channelId);

string query = "";

if (limit.HasValue)
{
query = $"?before={before.GetValueOrDefault(DateTimeOffset.UtcNow).ToString("O")}&limit={limit.Value}";
}
else if (before.HasValue)
{
query = $"?before={before.Value.ToString("O")}";
}

return await SendAsync<ChannelThreads>("GET", () => $"channels/{channelId}/threads/archived/private{query}", bucket, options: options);
}

public async Task<ChannelThreads> GetJoinedPrivateArchivedThreadsAsync(ulong channelId, DateTimeOffset? before = null, int? limit = null,
RequestOptions options = null)
{
Preconditions.NotEqual(channelId, 0, nameof(channelId));

options = RequestOptions.CreateOrClone(options);

var bucket = new BucketIds(channelId: channelId);

string query = "";

if (limit.HasValue)
{
query = $"?before={SnowflakeUtils.ToSnowflake(before.GetValueOrDefault(DateTimeOffset.UtcNow))}&limit={limit.Value}";
}
else if (before.HasValue)
{
query = $"?before={before.Value.ToString("O")}";
}

return await SendAsync<ChannelThreads>("GET", () => $"channels/{channelId}/users/@me/threads/archived/private{query}", bucket, options: options);
}

// roles
public async Task AddRoleAsync(ulong guildId, ulong userId, ulong roleId, RequestOptions options = null)
{
Preconditions.NotEqual(guildId, 0, nameof(guildId));


+ 1
- 0
src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs View File

@@ -333,6 +333,7 @@ namespace Discord.Rest
Embeds = args.Embed.IsSpecified ? new API.Embed[] { args.Embed.Value.ToModel() } : Optional.Create<API.Embed[]>(),
AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value.ToModel() : Optional<API.AllowedMentions>.Unspecified,
Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional<API.ActionRowComponent[]>.Unspecified,
Flags = args.Flags
};

return await client.ApiClient.ModifyInteractionResponse(apiArgs, message.Token, options).ConfigureAwait(false);


+ 9
- 1
src/Discord.Net.WebSocket/API/Gateway/ExtendedGuild.cs View File

@@ -1,4 +1,4 @@
#pragma warning disable CS1591
#pragma warning disable CS1591
using Newtonsoft.Json;
using System;

@@ -8,18 +8,26 @@ namespace Discord.API.Gateway
{
[JsonProperty("unavailable")]
public bool? Unavailable { get; set; }

[JsonProperty("member_count")]
public int MemberCount { get; set; }

[JsonProperty("large")]
public bool Large { get; set; }

[JsonProperty("presences")]
public Presence[] Presences { get; set; }

[JsonProperty("members")]
public GuildMember[] Members { get; set; }

[JsonProperty("channels")]
public Channel[] Channels { get; set; }

[JsonProperty("joined_at")]
public DateTimeOffset JoinedAt { get; set; }

[JsonProperty("threads")]
public Channel[] Threads { get; set; }
}
}

+ 24
- 0
src/Discord.Net.WebSocket/API/Gateway/ThreadListSyncEvent.cs View File

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

namespace Discord.API.Gateway
{
internal class ThreadListSyncEvent
{
[JsonProperty("guild_id")]
public ulong GuildId { get; set; }

[JsonProperty("channel_ids")]
public Optional<ulong[]> ChannelIds { get; set; }

[JsonProperty("threads")]
public Channel[] Threads { get; set; }

[JsonProperty("members")]
public ThreadMember[] Members { get; set; }
}
}

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

@@ -540,5 +540,37 @@ namespace Discord.WebSocket
remove { _applicationCommandDeleted.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketApplicationCommand, Task>> _applicationCommandDeleted = new AsyncEvent<Func<SocketApplicationCommand, Task>>();

/// <summary>
/// Fired when a thread is created within a guild.
/// </summary>
public event Func<SocketThreadChannel, Task> ThreadCreated
{
add { _threadCreated.Add(value); }
remove { _threadCreated.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketThreadChannel, Task>> _threadCreated = new AsyncEvent<Func<SocketThreadChannel, Task>>();

/// <summary>
/// Fired when a thread is updated within a guild.
/// </summary>
public event Func<SocketThreadChannel, SocketThreadChannel, Task> ThreadUpdated
{
add { _threadUpdated.Add(value); }
remove { _threadUpdated.Remove(value); }
}

internal readonly AsyncEvent<Func<SocketThreadChannel, SocketThreadChannel, Task>> _threadUpdated = new AsyncEvent<Func<SocketThreadChannel, SocketThreadChannel, Task>>();

/// <summary>
/// Fired when a thread is deleted.
/// </summary>
public event Func<Cacheable<SocketThreadChannel, ulong>, Task> ThreadDeleted
{
add { _threadDeleted.Add(value); }
remove { _threadDeleted.Remove(value); }
}
internal readonly AsyncEvent<Func<Cacheable<SocketThreadChannel, ulong>, Task>> _threadDeleted = new AsyncEvent<Func<Cacheable<SocketThreadChannel, ulong>, Task>>();

}
}

+ 1
- 1
src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj View File

@@ -8,7 +8,7 @@
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">net461;netstandard2.0;netstandard2.1</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netstandard2.0;netstandard2.1</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Version>2.4.8</Version>
<Version>2.4.9</Version>
<RepositoryUrl>https://github.com/Discord-Net-Labs/Discord.Net-Labs</RepositoryUrl>
<PackageProjectUrl>https://github.com/Discord-Net-Labs/Discord.Net-Labs</PackageProjectUrl>
<PackageIcon>Temporary.png</PackageIcon>


+ 203
- 0
src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml View File

@@ -2355,6 +2355,209 @@
<member name="M:Discord.WebSocket.SocketTextChannel.Discord#INestedChannel#GetCategoryAsync(Discord.CacheMode,Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="T:Discord.WebSocket.SocketThreadChannel">
<summary>
Represents a thread channel inside of a guild.
</summary>
</member>
<member name="P:Discord.WebSocket.SocketThreadChannel.ParentChannel">
<inheritdoc/>
</member>
<member name="P:Discord.WebSocket.SocketThreadChannel.MessageCount">
<inheritdoc/>
</member>
<member name="P:Discord.WebSocket.SocketThreadChannel.MemberCount">
<inheritdoc/>
</member>
<member name="P:Discord.WebSocket.SocketThreadChannel.Archived">
<inheritdoc/>
</member>
<member name="P:Discord.WebSocket.SocketThreadChannel.ArchiveTimestamp">
<inheritdoc/>
</member>
<member name="P:Discord.WebSocket.SocketThreadChannel.AutoArchiveDuration">
<inheritdoc/>
</member>
<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>
<member name="M:Discord.WebSocket.SocketThreadChannel.GetCachedMessage(System.UInt64)">
<inheritdoc />
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.GetMessageAsync(System.UInt64,Discord.RequestOptions)">
<summary>
Gets a message from this message channel.
</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.
</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>
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.GetMessagesAsync(System.Int32,Discord.RequestOptions)">
<summary>
Gets the last N messages from this message channel.
</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>
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.GetMessagesAsync(System.UInt64,Discord.Direction,System.Int32,Discord.RequestOptions)">
<summary>
Gets a collection of messages in this channel.
</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="options">The options to be used when sending the request.</param>
<returns>
Paged collection of messages.
</returns>
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.GetMessagesAsync(Discord.IMessage,Discord.Direction,System.Int32,Discord.RequestOptions)">
<summary>
Gets a collection of messages in this channel.
</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="options">The options to be used when sending the request.</param>
<returns>
Paged collection of messages.
</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>
<member name="M:Discord.WebSocket.SocketThreadChannel.GetUser(System.UInt64)">
<inheritdoc />
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.Discord#ITextChannel#CreateWebhookAsync(System.String,System.IO.Stream,Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.Discord#ITextChannel#GetWebhookAsync(System.UInt64,Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.Discord#ITextChannel#GetWebhooksAsync(Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.Discord#IGuildChannel#GetUserAsync(System.UInt64,Discord.CacheMode,Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.Discord#IGuildChannel#GetUsersAsync(Discord.CacheMode,Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.Discord#IMessageChannel#GetMessageAsync(System.UInt64,Discord.CacheMode,Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.Discord#IMessageChannel#GetMessagesAsync(System.Int32,Discord.CacheMode,Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.Discord#IMessageChannel#GetMessagesAsync(System.UInt64,Discord.Direction,System.Int32,Discord.CacheMode,Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.Discord#IMessageChannel#GetMessagesAsync(Discord.IMessage,Discord.Direction,System.Int32,Discord.CacheMode,Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="M:Discord.WebSocket.SocketThreadChannel.Discord#IMessageChannel#GetPinnedMessagesAsync(Discord.RequestOptions)">
<inheritdoc />
</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>
<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>
<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>
<member name="M:Discord.WebSocket.SocketThreadChannel.Discord#INestedChannel#GetCategoryAsync(Discord.CacheMode,Discord.RequestOptions)">
<inheritdoc />
</member>
<member name="T:Discord.WebSocket.SocketVoiceChannel">
<summary>
Represents a WebSocket-based voice channel in a guild.


+ 106
- 0
src/Discord.Net.WebSocket/DiscordSocketClient.cs View File

@@ -1862,6 +1862,8 @@ namespace Discord.WebSocket
}
}
break;

// Interactions
case "INTERACTION_CREATE":
{
await _gatewayLogger.DebugAsync("Received Dispatch (INTERACTION_CREATE)").ConfigureAwait(false);
@@ -1969,6 +1971,110 @@ namespace Discord.WebSocket
await TimedInvokeAsync(_applicationCommandDeleted, nameof(ApplicationCommandDeleted), applicationCommand).ConfigureAwait(false);
}
break;

// Threads
case "THREAD_CREATE":
{
await _gatewayLogger.DebugAsync("Received Dispatch (THREAD_CREATE)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<Channel>(_serializer);

var guild = State.GetGuild(data.GuildId.Value);

if(guild == null)
{
await UnknownGuildAsync(type, data.GuildId.Value);
return;
}

var threadChannel = (SocketThreadChannel)guild.AddChannel(this.State, data);

await TimedInvokeAsync(_threadCreated, nameof(ThreadCreated), threadChannel).ConfigureAwait(false);
}

break;
case "THREAD_UPDATE":
{
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;
}

var channel = (SocketThreadChannel)guild.GetChannel(data.Id);

var before = channel.Clone();
channel.Update(State, data);

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":
{
await _gatewayLogger.DebugAsync("Received Dispatch (THREAD_DELETE)").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).ConfigureAwait(false);
return;
}

var thread = (SocketThreadChannel)guild.GetChannel(data.Id);

var cacheable = new Cacheable<SocketThreadChannel, ulong>(thread, data.Id, thread != null, null);

await TimedInvokeAsync(_threadDeleted, nameof(ThreadDeleted), cacheable).ConfigureAwait(false);
}
break;
case "THREAD_LIST_SYNC":
{
await _gatewayLogger.DebugAsync("Received Dispatch (THREAD_LIST_SYNC)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<API.Gateway.ThreadListSyncEvent>(_serializer);

var guild = State.GetGuild(data.GuildId);

if(guild == null)
{
await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false);
return;
}

foreach(var thread in data.Threads)
{
var entity = guild.ThreadChannels.FirstOrDefault(x => x.Id == thread.Id);

if(entity == null)
{
guild.AddChannel(this.State, thread);
}
else
{
entity.Update(this.State, thread);
}
}
}
break;
case "THREAD_MEMBER_UPDATE":
break;
case "THREAD_MEMBERS_UPDATE": // based on intents
break;

//Ignored (User only)
case "CHANNEL_PINS_ACK":
await _gatewayLogger.DebugAsync("Ignored Dispatch (CHANNEL_PINS_ACK)").ConfigureAwait(false);


+ 2
- 0
src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs View File

@@ -56,6 +56,8 @@ namespace Discord.WebSocket
return SocketVoiceChannel.Create(guild, state, model);
case ChannelType.Category:
return SocketCategoryChannel.Create(guild, state, model);
case ChannelType.PrivateThread or ChannelType.PublicThread or ChannelType.NewsThread:
return SocketThreadChannel.Create(guild, state, model);
default:
return new SocketGuildChannel(guild.Discord, model.Id, guild);
}


+ 6
- 0
src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs View File

@@ -50,6 +50,12 @@ namespace Discord.WebSocket
Permissions.ResolveChannel(Guild, x, this, Permissions.ResolveGuild(Guild, x)),
ChannelPermission.ViewChannel)).ToImmutableArray();

/// <summary>
/// Gets a collection of threads within this text channel.
/// </summary>
public IReadOnlyCollection<SocketThreadChannel> Threads
=> Guild.ThreadChannels.Where(x => x.ParentChannel.Id == this.Id).ToImmutableArray();

internal SocketTextChannel(DiscordSocketClient discord, ulong id, SocketGuild guild)
: base(discord, id, guild)
{


+ 348
- 0
src/Discord.Net.WebSocket/Entities/Channels/SocketThreadChannel.cs View File

@@ -0,0 +1,348 @@
using Discord.Rest;
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.WebSocket
{
/// <summary>
/// Represents a thread channel inside of a guild.
/// </summary>
public class SocketThreadChannel : SocketGuildChannel, IThreadChannel, ISocketMessageChannel
{
/// <summary>
/// <see langword="true"/> if this thread is private, otherwise <see langword="false"/>
/// </summary>
public bool IsPrivateThread { get; private set; }

/// <summary>
/// Gets the parent channel this thread resides in.
/// </summary>
public SocketTextChannel ParentChannel { get; private set; }

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

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

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

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

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

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

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

/// <inheritdoc />
public IReadOnlyCollection<SocketMessage> CachedMessages => _messages?.Messages ?? ImmutableArray.Create<SocketMessage>();

public new IReadOnlyCollection<SocketThreadUser> Users = ImmutableArray.Create<SocketThreadUser>();

private readonly MessageCache _messages;

internal SocketThreadChannel(DiscordSocketClient discord, SocketGuild guild, ulong id, SocketTextChannel parent)
: base(discord, id, guild)
{
this.ParentChannel = parent;
}

internal 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);
entity.Update(state, model);
return entity;
}

internal override void Update(ClientState state, Model model)
{
base.Update(state, model);

this.MessageCount = model.MessageCount.GetValueOrDefault(-1);
this.MemberCount = model.MemberCount.GetValueOrDefault(-1);

this.IsPrivateThread = model.Type == ChannelType.PrivateThread;
if (model.ThreadMetadata.IsSpecified)
{
this.Archived = model.ThreadMetadata.Value.Archived;
this.ArchiveTimestamp = model.ThreadMetadata.Value.ArchiveTimestamp;
this.AutoArchiveDuration = (ThreadArchiveDuration)model.ThreadMetadata.Value.AutoArchiveDuration;
this.Locked = model.ThreadMetadata.Value.Locked.GetValueOrDefault(false);
}
}

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

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

//Messages
/// <inheritdoc />
public SocketMessage GetCachedMessage(ulong id)
=> _messages?.Get(id);

/// <summary>
/// Gets a message from this message channel.
/// </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.
/// </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)
{
IMessage msg = _messages?.Get(id);
if (msg == null)
msg = await ChannelHelper.GetMessageAsync(this, Discord, id, options).ConfigureAwait(false);
return msg;
}

/// <summary>
/// Gets the last N messages from this message channel.
/// </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);

/// <summary>
/// Gets a collection of messages in this channel.
/// </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="options">The options to be used when sending the request.</param>
/// <returns>
/// Paged collection of messages.
/// </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);

/// <summary>
/// Gets a collection of messages in this channel.
/// </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="options">The options to be used when sending the request.</param>
/// <returns>
/// Paged collection of messages.
/// </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);

/// <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);

internal void AddMessage(SocketMessage msg)
=> _messages?.Add(msg);

internal SocketMessage RemoveMessage(ulong id)
=> _messages?.Remove(id);

//Users
/// <inheritdoc />
public new SocketThreadUser GetUser(ulong id)
{
var user = Users.FirstOrDefault(x => x.Id == id);
return user;
}

public Task GetUsersAsync()
{

}

private string DebuggerDisplay => $"{Name} ({Id}, Thread)";
internal new SocketThreadChannel Clone() => MemberwiseClone() as SocketThreadChannel;

//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");

//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();

//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 />
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);

// 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()
{

}

public Task LeaveAsync()
{

}

public Task AddThreadMember(IGuildUser user)
{

}

public Task RemoveThreadMember(IGuildUser user)
{

}


}
}

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

@@ -277,6 +277,14 @@ namespace Discord.WebSocket
public IReadOnlyCollection<SocketCategoryChannel> CategoryChannels
=> Channels.OfType<SocketCategoryChannel>().ToImmutableArray();
/// <summary>
/// Gets a collection of all thread channels in this guild.
/// </summary>
/// <returns>
/// A read-only collection of thread channels found within this guild.
/// </returns>
public IReadOnlyCollection<SocketThreadChannel> ThreadChannels
=> Channels.OfType<SocketThreadChannel>().ToImmutableArray();
/// <summary>
/// Gets the current logged-in user.
/// </summary>
public SocketGuildUser CurrentUser => _members.TryGetValue(Discord.CurrentUser.Id, out SocketGuildUser member) ? member : null;


+ 212
- 0
src/Discord.Net.WebSocket/Entities/Users/SocketThreadUser.cs View File

@@ -0,0 +1,212 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Model = Discord.API.ThreadMember;
using MemberModel = Discord.API.GuildMember;
using Discord.API;
using System.Collections.Immutable;

namespace Discord.WebSocket
{
public class SocketThreadUser : IGuildUser
{
/// <summary>
/// Gets the <see cref="SocketThreadChannel"/> this user is in.
/// </summary>
public SocketThreadChannel Thread { get; private set; }

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

/// <summary>
/// Gets the guild this user is in.
/// </summary>
public SocketGuild Guild { get; private set; }

/// <inheritdoc/>
public DateTimeOffset? JoinedAt
=> GuildUser.JoinedAt;

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

/// <inheritdoc/>
public DateTimeOffset? PremiumSince
=> GuildUser.PremiumSince;

/// <inheritdoc/>
public bool? IsPending
=> GuildUser.IsPending;

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

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

/// <inheritdoc/>
public ushort DiscriminatorValue
=> GuildUser.DiscriminatorValue;

/// <inheritdoc/>
public bool IsBot
=> GuildUser.IsBot;

/// <inheritdoc/>
public 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;

/// <inheritdoc/>
public bool IsDeafened
=> GuildUser.IsDeafened;

/// <inheritdoc/>
public bool IsMuted
=> GuildUser.IsMuted;

/// <inheritdoc/>
public bool IsSelfDeafened
=> GuildUser.IsSelfDeafened;

/// <inheritdoc/>
public bool IsSelfMuted
=> GuildUser.IsSelfMuted;

/// <inheritdoc/>
public bool IsSuppressed
=> GuildUser.IsSuppressed;

/// <inheritdoc/>
public IVoiceChannel VoiceChannel
=> GuildUser.VoiceChannel;

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

/// <inheritdoc/>
public bool IsStreaming
=> GuildUser.IsStreaming;

private SocketGuildUser GuildUser { get; set; }

internal SocketThreadUser(SocketGuild guild, SocketThreadChannel thread, SocketGuildUser member)
{
this.Thread = thread;
this.Guild = guild;
this.GuildUser = member;
}

internal SocketThreadUser Create(SocketGuild guild, SocketThreadChannel thread, Model model, SocketGuildUser member)
{
var entity = new SocketThreadUser(guild, thread, member);
entity.Update(model);
return entity;
}

internal void Update(Model model)
{
this.ThreadJoinedAt = model.JoinTimestamp;
}


/// <inheritdoc/>
public ChannelPermissions GetPermissions(IGuildChannel channel) => GuildUser.GetPermissions(channel);

/// <inheritdoc/>
public Task KickAsync(string reason = null, RequestOptions options = null) => GuildUser.KickAsync(reason, options);

/// <inheritdoc/>
public Task ModifyAsync(Action<GuildUserProperties> func, RequestOptions options = null) => GuildUser.ModifyAsync(func, options);

/// <inheritdoc/>
public Task AddRoleAsync(ulong roleId, RequestOptions options = null) => GuildUser.AddRoleAsync(roleId, options);

/// <inheritdoc/>
public Task AddRoleAsync(IRole role, RequestOptions options = null) => GuildUser.AddRoleAsync(role, options);

/// <inheritdoc/>
public Task AddRolesAsync(IEnumerable<ulong> roleIds, RequestOptions options = null) => GuildUser.AddRolesAsync(roleIds, options);

/// <inheritdoc/>
public Task AddRolesAsync(IEnumerable<IRole> roles, RequestOptions options = null) => GuildUser.AddRolesAsync(roles, options);

/// <inheritdoc/>
public Task RemoveRoleAsync(ulong roleId, RequestOptions options = null) => GuildUser.RemoveRoleAsync(roleId, options);

/// <inheritdoc/>
public Task RemoveRoleAsync(IRole role, RequestOptions options = null) => GuildUser.RemoveRoleAsync(role, options);

/// <inheritdoc/>
public Task RemoveRolesAsync(IEnumerable<ulong> roleIds, RequestOptions options = null) => GuildUser.RemoveRolesAsync(roleIds, options);

/// <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;

/// <inheritdoc/>
IGuild IGuildUser.Guild => this.Guild;

/// <inheritdoc/>
ulong IGuildUser.GuildId => this.Guild.Id;

/// <inheritdoc/>
IReadOnlyCollection<ulong> IGuildUser.RoleIds => this.GuildUser.Roles.Select(x => x.Id).ToImmutableArray();

/// <summary>
/// Gets the guild user of this thread user.
/// </summary>
/// <param name="user"></param>
public static explicit operator SocketGuildUser(SocketThreadUser user) => user.GuildUser;
}
}

Loading…
Cancel
Save