| @@ -1982,6 +1982,41 @@ | |||||
| of webhooks that is available in this channel. | of webhooks that is available in this channel. | ||||
| </returns> | </returns> | ||||
| </member> | </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"> | <member name="T:Discord.IVoiceChannel"> | ||||
| <summary> | <summary> | ||||
| Represents a generic voice channel in a guild. | Represents a generic voice channel in a guild. | ||||
| @@ -2081,6 +2116,71 @@ | |||||
| </remarks> | </remarks> | ||||
| <exception cref="T:System.ArgumentOutOfRangeException">Thrown if the value does not fall within [0, 21600].</exception> | <exception cref="T:System.ArgumentOutOfRangeException">Thrown if the value does not fall within [0, 21600].</exception> | ||||
| </member> | </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"> | <member name="T:Discord.VoiceChannelProperties"> | ||||
| <summary> | <summary> | ||||
| Provides properties that are used to modify an <see cref="T:Discord.IVoiceChannel" /> with the specified changes. | 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. | Flag given to messages that came from the urgent message system. | ||||
| </summary> | </summary> | ||||
| </member> | </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"> | <member name="T:Discord.MessageProperties"> | ||||
| <summary> | <summary> | ||||
| Properties that are used to modify an <see cref="T:Discord.IUserMessage" /> with the specified changes. | Properties that are used to modify an <see cref="T:Discord.IUserMessage" /> with the specified changes. | ||||
| @@ -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; } | |||||
| } | |||||
| } | |||||
| @@ -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, | |||||
| } | |||||
| } | |||||
| @@ -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; } | |||||
| } | |||||
| } | |||||
| @@ -32,5 +32,17 @@ namespace Discord | |||||
| /// Flag given to messages that came from the urgent message system. | /// Flag given to messages that came from the urgent message system. | ||||
| /// </summary> | /// </summary> | ||||
| Urgent = 1 << 4, | 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 | |||||
| } | } | ||||
| } | } | ||||
| @@ -49,5 +49,21 @@ namespace Discord.API | |||||
| //GroupChannel | //GroupChannel | ||||
| [JsonProperty("icon")] | [JsonProperty("icon")] | ||||
| public Optional<string> Icon { get; set; } | 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; } | |||||
| } | } | ||||
| } | } | ||||
| @@ -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; } | |||||
| } | |||||
| } | |||||
| @@ -76,5 +76,7 @@ namespace Discord.API | |||||
| public Optional<int> ApproximateMemberCount { get; set; } | public Optional<int> ApproximateMemberCount { get; set; } | ||||
| [JsonProperty("approximate_presence_count")] | [JsonProperty("approximate_presence_count")] | ||||
| public Optional<int> ApproximatePresenceCount { get; set; } | public Optional<int> ApproximatePresenceCount { get; set; } | ||||
| [JsonProperty("threads")] | |||||
| public Optional<Channel[]> Threads { get; set; } | |||||
| } | } | ||||
| } | } | ||||
| @@ -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?) | |||||
| } | |||||
| } | |||||
| @@ -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; } | |||||
| } | |||||
| } | |||||
| @@ -20,5 +20,8 @@ namespace Discord.API.Rest | |||||
| [JsonProperty("components")] | [JsonProperty("components")] | ||||
| public Optional<API.ActionRowComponent[]> Components { get; set; } | public Optional<API.ActionRowComponent[]> Components { get; set; } | ||||
| [JsonProperty("flags")] | |||||
| public Optional<MessageFlags?> Flags { get; set; } | |||||
| } | } | ||||
| } | } | ||||
| @@ -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; } | |||||
| } | |||||
| } | |||||
| @@ -413,6 +413,160 @@ namespace Discord.API | |||||
| break; | 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) | public async Task AddRoleAsync(ulong guildId, ulong userId, ulong roleId, RequestOptions options = null) | ||||
| { | { | ||||
| Preconditions.NotEqual(guildId, 0, nameof(guildId)); | Preconditions.NotEqual(guildId, 0, nameof(guildId)); | ||||
| @@ -333,6 +333,7 @@ namespace Discord.Rest | |||||
| Embeds = args.Embed.IsSpecified ? new API.Embed[] { args.Embed.Value.ToModel() } : Optional.Create<API.Embed[]>(), | 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, | 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, | 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); | return await client.ApiClient.ModifyInteractionResponse(apiArgs, message.Token, options).ConfigureAwait(false); | ||||
| @@ -1,4 +1,4 @@ | |||||
| #pragma warning disable CS1591 | |||||
| #pragma warning disable CS1591 | |||||
| using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
| using System; | using System; | ||||
| @@ -8,18 +8,26 @@ namespace Discord.API.Gateway | |||||
| { | { | ||||
| [JsonProperty("unavailable")] | [JsonProperty("unavailable")] | ||||
| public bool? Unavailable { get; set; } | public bool? Unavailable { get; set; } | ||||
| [JsonProperty("member_count")] | [JsonProperty("member_count")] | ||||
| public int MemberCount { get; set; } | public int MemberCount { get; set; } | ||||
| [JsonProperty("large")] | [JsonProperty("large")] | ||||
| public bool Large { get; set; } | public bool Large { get; set; } | ||||
| [JsonProperty("presences")] | [JsonProperty("presences")] | ||||
| public Presence[] Presences { get; set; } | public Presence[] Presences { get; set; } | ||||
| [JsonProperty("members")] | [JsonProperty("members")] | ||||
| public GuildMember[] Members { get; set; } | public GuildMember[] Members { get; set; } | ||||
| [JsonProperty("channels")] | [JsonProperty("channels")] | ||||
| public Channel[] Channels { get; set; } | public Channel[] Channels { get; set; } | ||||
| [JsonProperty("joined_at")] | [JsonProperty("joined_at")] | ||||
| public DateTimeOffset JoinedAt { get; set; } | public DateTimeOffset JoinedAt { get; set; } | ||||
| [JsonProperty("threads")] | |||||
| public Channel[] Threads { get; set; } | |||||
| } | } | ||||
| } | } | ||||
| @@ -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; } | |||||
| } | |||||
| } | |||||
| @@ -540,5 +540,37 @@ namespace Discord.WebSocket | |||||
| remove { _applicationCommandDeleted.Remove(value); } | remove { _applicationCommandDeleted.Remove(value); } | ||||
| } | } | ||||
| internal readonly AsyncEvent<Func<SocketApplicationCommand, Task>> _applicationCommandDeleted = new AsyncEvent<Func<SocketApplicationCommand, Task>>(); | 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>>(); | |||||
| } | } | ||||
| } | } | ||||
| @@ -8,7 +8,7 @@ | |||||
| <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">net461;netstandard2.0;netstandard2.1</TargetFrameworks> | <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">net461;netstandard2.0;netstandard2.1</TargetFrameworks> | ||||
| <TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netstandard2.0;netstandard2.1</TargetFrameworks> | <TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netstandard2.0;netstandard2.1</TargetFrameworks> | ||||
| <AllowUnsafeBlocks>true</AllowUnsafeBlocks> | <AllowUnsafeBlocks>true</AllowUnsafeBlocks> | ||||
| <Version>2.4.8</Version> | |||||
| <Version>2.4.9</Version> | |||||
| <RepositoryUrl>https://github.com/Discord-Net-Labs/Discord.Net-Labs</RepositoryUrl> | <RepositoryUrl>https://github.com/Discord-Net-Labs/Discord.Net-Labs</RepositoryUrl> | ||||
| <PackageProjectUrl>https://github.com/Discord-Net-Labs/Discord.Net-Labs</PackageProjectUrl> | <PackageProjectUrl>https://github.com/Discord-Net-Labs/Discord.Net-Labs</PackageProjectUrl> | ||||
| <PackageIcon>Temporary.png</PackageIcon> | <PackageIcon>Temporary.png</PackageIcon> | ||||
| @@ -2355,6 +2355,209 @@ | |||||
| <member name="M:Discord.WebSocket.SocketTextChannel.Discord#INestedChannel#GetCategoryAsync(Discord.CacheMode,Discord.RequestOptions)"> | <member name="M:Discord.WebSocket.SocketTextChannel.Discord#INestedChannel#GetCategoryAsync(Discord.CacheMode,Discord.RequestOptions)"> | ||||
| <inheritdoc /> | <inheritdoc /> | ||||
| </member> | </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"> | <member name="T:Discord.WebSocket.SocketVoiceChannel"> | ||||
| <summary> | <summary> | ||||
| Represents a WebSocket-based voice channel in a guild. | Represents a WebSocket-based voice channel in a guild. | ||||
| @@ -1862,6 +1862,8 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| } | } | ||||
| break; | break; | ||||
| // Interactions | |||||
| case "INTERACTION_CREATE": | case "INTERACTION_CREATE": | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (INTERACTION_CREATE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (INTERACTION_CREATE)").ConfigureAwait(false); | ||||
| @@ -1969,6 +1971,110 @@ namespace Discord.WebSocket | |||||
| await TimedInvokeAsync(_applicationCommandDeleted, nameof(ApplicationCommandDeleted), applicationCommand).ConfigureAwait(false); | await TimedInvokeAsync(_applicationCommandDeleted, nameof(ApplicationCommandDeleted), applicationCommand).ConfigureAwait(false); | ||||
| } | } | ||||
| break; | 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) | //Ignored (User only) | ||||
| case "CHANNEL_PINS_ACK": | case "CHANNEL_PINS_ACK": | ||||
| await _gatewayLogger.DebugAsync("Ignored Dispatch (CHANNEL_PINS_ACK)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Ignored Dispatch (CHANNEL_PINS_ACK)").ConfigureAwait(false); | ||||
| @@ -56,6 +56,8 @@ namespace Discord.WebSocket | |||||
| return SocketVoiceChannel.Create(guild, state, model); | return SocketVoiceChannel.Create(guild, state, model); | ||||
| case ChannelType.Category: | case ChannelType.Category: | ||||
| return SocketCategoryChannel.Create(guild, state, model); | return SocketCategoryChannel.Create(guild, state, model); | ||||
| case ChannelType.PrivateThread or ChannelType.PublicThread or ChannelType.NewsThread: | |||||
| return SocketThreadChannel.Create(guild, state, model); | |||||
| default: | default: | ||||
| return new SocketGuildChannel(guild.Discord, model.Id, guild); | return new SocketGuildChannel(guild.Discord, model.Id, guild); | ||||
| } | } | ||||
| @@ -50,6 +50,12 @@ namespace Discord.WebSocket | |||||
| Permissions.ResolveChannel(Guild, x, this, Permissions.ResolveGuild(Guild, x)), | Permissions.ResolveChannel(Guild, x, this, Permissions.ResolveGuild(Guild, x)), | ||||
| ChannelPermission.ViewChannel)).ToImmutableArray(); | 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) | internal SocketTextChannel(DiscordSocketClient discord, ulong id, SocketGuild guild) | ||||
| : base(discord, id, guild) | : base(discord, id, guild) | ||||
| { | { | ||||
| @@ -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) | |||||
| { | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -277,6 +277,14 @@ namespace Discord.WebSocket | |||||
| public IReadOnlyCollection<SocketCategoryChannel> CategoryChannels | public IReadOnlyCollection<SocketCategoryChannel> CategoryChannels | ||||
| => Channels.OfType<SocketCategoryChannel>().ToImmutableArray(); | => Channels.OfType<SocketCategoryChannel>().ToImmutableArray(); | ||||
| /// <summary> | /// <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. | /// Gets the current logged-in user. | ||||
| /// </summary> | /// </summary> | ||||
| public SocketGuildUser CurrentUser => _members.TryGetValue(Discord.CurrentUser.Id, out SocketGuildUser member) ? member : null; | public SocketGuildUser CurrentUser => _members.TryGetValue(Discord.CurrentUser.Id, out SocketGuildUser member) ? member : null; | ||||
| @@ -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; | |||||
| } | |||||
| } | |||||