| @@ -1003,6 +1003,51 @@ | |||||
| A message was unpinned from this guild. | A message was unpinned from this guild. | ||||
| </summary> | </summary> | ||||
| </member> | </member> | ||||
| <member name="F:Discord.ActionType.IntegrationCreated"> | |||||
| <summary> | |||||
| A integration was created | |||||
| </summary> | |||||
| </member> | |||||
| <member name="F:Discord.ActionType.IntegrationUpdated"> | |||||
| <summary> | |||||
| A integration was updated | |||||
| </summary> | |||||
| </member> | |||||
| <member name="F:Discord.ActionType.IntegrationDeleted"> | |||||
| <summary> | |||||
| An integration was deleted | |||||
| </summary> | |||||
| </member> | |||||
| <member name="F:Discord.ActionType.StageInstanceCreated"> | |||||
| <summary> | |||||
| A stage instance was created. | |||||
| </summary> | |||||
| </member> | |||||
| <member name="F:Discord.ActionType.StageInstanceUpdated"> | |||||
| <summary> | |||||
| A stage instance was updated. | |||||
| </summary> | |||||
| </member> | |||||
| <member name="F:Discord.ActionType.StageInstanceDeleted"> | |||||
| <summary> | |||||
| A stage instance was deleted. | |||||
| </summary> | |||||
| </member> | |||||
| <member name="F:Discord.ActionType.StickerCreated"> | |||||
| <summary> | |||||
| A sticker was created. | |||||
| </summary> | |||||
| </member> | |||||
| <member name="F:Discord.ActionType.StickerUpdated"> | |||||
| <summary> | |||||
| A sticker was updated. | |||||
| </summary> | |||||
| </member> | |||||
| <member name="F:Discord.ActionType.StickerDeleted"> | |||||
| <summary> | |||||
| A sticker was deleted. | |||||
| </summary> | |||||
| </member> | |||||
| <member name="T:Discord.IAuditLogData"> | <member name="T:Discord.IAuditLogData"> | ||||
| <summary> | <summary> | ||||
| Represents data applied to an <see cref="T:Discord.IAuditLogEntry"/>. | Represents data applied to an <see cref="T:Discord.IAuditLogEntry"/>. | ||||
| @@ -6105,7 +6150,7 @@ | |||||
| The built embed object. | The built embed object. | ||||
| </returns> | </returns> | ||||
| <exception cref="T:System.InvalidOperationException">Total embed length exceeds <see cref="F:Discord.EmbedBuilder.MaxEmbedLength"/>.</exception> | <exception cref="T:System.InvalidOperationException">Total embed length exceeds <see cref="F:Discord.EmbedBuilder.MaxEmbedLength"/>.</exception> | ||||
| <exception cref="T:System.InvalidOperationException">Any Url must be well formatted include its protocols (i.e http:// or https://).</exception> | |||||
| <exception cref="T:System.InvalidOperationException">Any Url must include its protocols (i.e http:// or https://).</exception> | |||||
| </member> | </member> | ||||
| <member name="T:Discord.EmbedFieldBuilder"> | <member name="T:Discord.EmbedFieldBuilder"> | ||||
| <summary> | <summary> | ||||
| @@ -463,7 +463,9 @@ namespace Discord.API | |||||
| options = RequestOptions.CreateOrClone(options); | options = RequestOptions.CreateOrClone(options); | ||||
| await SendAsync("PUT", $"channels/{channelId}/thread-members/{userId}", options: options).ConfigureAwait(false); | |||||
| var bucket = new BucketIds(channelId: channelId); | |||||
| await SendAsync("PUT", () => $"channels/{channelId}/thread-members/{userId}", bucket, options: options).ConfigureAwait(false); | |||||
| } | } | ||||
| public async Task LeaveThreadAsync(ulong channelId, RequestOptions options = null) | public async Task LeaveThreadAsync(ulong channelId, RequestOptions options = null) | ||||
| @@ -2187,6 +2187,38 @@ | |||||
| <member name="M:Discord.WebSocket.SocketTextChannel.ModifyAsync(System.Action{Discord.TextChannelProperties},Discord.RequestOptions)"> | <member name="M:Discord.WebSocket.SocketTextChannel.ModifyAsync(System.Action{Discord.TextChannelProperties},Discord.RequestOptions)"> | ||||
| <inheritdoc /> | <inheritdoc /> | ||||
| </member> | </member> | ||||
| <member name="M:Discord.WebSocket.SocketTextChannel.CreateThreadAsync(System.String,Discord.ThreadType,Discord.ThreadArchiveDuration,Discord.IMessage,Discord.RequestOptions)"> | |||||
| <summary> | |||||
| Creates a thread within this <see cref="T:Discord.ITextChannel"/>. | |||||
| </summary> | |||||
| <remarks> | |||||
| When <paramref name="message"/> is <see langword="null"/> the thread type will be based off of the | |||||
| channel its created in. When called on a <see cref="T:Discord.ITextChannel"/>, it creates a <see cref="F:Discord.ThreadType.PublicThread"/>. | |||||
| When called on a <see cref="T:Discord.INewsChannel"/>, it creates a <see cref="F:Discord.ThreadType.NewsThread"/>. The id of the created | |||||
| thread will be the same as the id of the message, and as such a message can only have a | |||||
| single thread created from it. | |||||
| </remarks> | |||||
| <param name="name">The name of the thread.</param> | |||||
| <param name="type"> | |||||
| The type of the thread. | |||||
| <para> | |||||
| <b>Note: </b>This parameter is not used if the <paramref name="message"/> parameter is not specified. | |||||
| </para> | |||||
| </param> | |||||
| <param name="autoArchiveDuration"> | |||||
| The duration on which this thread archives after. | |||||
| <para> | |||||
| <b>Note: </b> Options <see cref="F:Discord.ThreadArchiveDuration.OneWeek"/> and <see cref="F:Discord.ThreadArchiveDuration.ThreeDays"/> | |||||
| are only available for guilds that are boosted. You can check in the <see cref="P:Discord.IGuild.Features"/> to see if the | |||||
| guild has the <b>THREE_DAY_THREAD_ARCHIVE</b> and <b>SEVEN_DAY_THREAD_ARCHIVE</b>. | |||||
| </para> | |||||
| </param> | |||||
| <param name="message">The message which to start the thread from.</param> | |||||
| <param name="options">The options to be used when sending the request.</param> | |||||
| <returns> | |||||
| A task that represents the asynchronous create operation. The task result contains a <see cref="T:Discord.IThreadChannel"/> | |||||
| </returns> | |||||
| </member> | |||||
| <member name="M:Discord.WebSocket.SocketTextChannel.GetCachedMessage(System.UInt64)"> | <member name="M:Discord.WebSocket.SocketTextChannel.GetCachedMessage(System.UInt64)"> | ||||
| <inheritdoc /> | <inheritdoc /> | ||||
| </member> | </member> | ||||
| @@ -2352,6 +2384,9 @@ | |||||
| <member name="M:Discord.WebSocket.SocketTextChannel.Discord#ITextChannel#GetWebhooksAsync(Discord.RequestOptions)"> | <member name="M:Discord.WebSocket.SocketTextChannel.Discord#ITextChannel#GetWebhooksAsync(Discord.RequestOptions)"> | ||||
| <inheritdoc /> | <inheritdoc /> | ||||
| </member> | </member> | ||||
| <member name="M:Discord.WebSocket.SocketTextChannel.Discord#ITextChannel#CreateThreadAsync(System.String,Discord.ThreadType,Discord.ThreadArchiveDuration,Discord.IMessage,Discord.RequestOptions)"> | |||||
| <inheritdoc /> | |||||
| </member> | |||||
| <member name="M:Discord.WebSocket.SocketTextChannel.Discord#IGuildChannel#GetUserAsync(System.UInt64,Discord.CacheMode,Discord.RequestOptions)"> | <member name="M:Discord.WebSocket.SocketTextChannel.Discord#IGuildChannel#GetUserAsync(System.UInt64,Discord.CacheMode,Discord.RequestOptions)"> | ||||
| <inheritdoc /> | <inheritdoc /> | ||||
| </member> | </member> | ||||
| @@ -2390,6 +2425,9 @@ | |||||
| Represents a thread channel inside of a guild. | Represents a thread channel inside of a guild. | ||||
| </summary> | </summary> | ||||
| </member> | </member> | ||||
| <member name="P:Discord.WebSocket.SocketThreadChannel.Type"> | |||||
| <inheritdoc/> | |||||
| </member> | |||||
| <member name="P:Discord.WebSocket.SocketThreadChannel.Owner"> | <member name="P:Discord.WebSocket.SocketThreadChannel.Owner"> | ||||
| <summary> | <summary> | ||||
| Gets the owner of the current thread. | Gets the owner of the current thread. | ||||
| @@ -1992,12 +1992,15 @@ namespace Discord.WebSocket | |||||
| if ((threadChannel = guild.ThreadChannels.FirstOrDefault(x => x.Id == data.Id)) != null) | if ((threadChannel = guild.ThreadChannels.FirstOrDefault(x => x.Id == data.Id)) != null) | ||||
| { | { | ||||
| threadChannel.Update(this.State, data); | threadChannel.Update(this.State, data); | ||||
| threadChannel.AddOrUpdateThreadMember(data.ThreadMember.Value, guild.CurrentUser); | |||||
| if(data.ThreadMember.IsSpecified) | |||||
| threadChannel.AddOrUpdateThreadMember(data.ThreadMember.Value, guild.CurrentUser); | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| threadChannel = (SocketThreadChannel)guild.AddChannel(this.State, data); | threadChannel = (SocketThreadChannel)guild.AddChannel(this.State, data); | ||||
| threadChannel.AddOrUpdateThreadMember(data.ThreadMember.Value, guild.CurrentUser); | |||||
| if (data.ThreadMember.IsSpecified) | |||||
| threadChannel.AddOrUpdateThreadMember(data.ThreadMember.Value, guild.CurrentUser); | |||||
| await TimedInvokeAsync(_threadCreated, nameof(ThreadCreated), threadChannel).ConfigureAwait(false); | await TimedInvokeAsync(_threadCreated, nameof(ThreadCreated), threadChannel).ConfigureAwait(false); | ||||
| } | } | ||||
| } | } | ||||
| @@ -2092,12 +2095,19 @@ namespace Discord.WebSocket | |||||
| var data = (payload as JToken).ToObject<ThreadMember>(_serializer); | var data = (payload as JToken).ToObject<ThreadMember>(_serializer); | ||||
| //var guild = State.GetGuild(data.) | |||||
| var thread = (SocketThreadChannel)State.GetChannel(data.Id.Value); | |||||
| if (thread == null) | |||||
| { | |||||
| await UnknownChannelAsync(type, data.Id.Value); | |||||
| return; | |||||
| } | |||||
| thread.AddOrUpdateThreadMember(data, thread.Guild.CurrentUser); | |||||
| } | } | ||||
| break; | break; | ||||
| case "THREAD_MEMBERS_UPDATE": // based on intents | |||||
| case "THREAD_MEMBERS_UPDATE": | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (THREAD_MEMBERS_UPDATE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (THREAD_MEMBERS_UPDATE)").ConfigureAwait(false); | ||||
| @@ -72,7 +72,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| base.Update(state, model); | base.Update(state, model); | ||||
| CategoryId = model.CategoryId; | CategoryId = model.CategoryId; | ||||
| Topic = model.Topic.Value; | |||||
| Topic = model.Topic.GetValueOrDefault(); | |||||
| SlowModeInterval = model.SlowMode.GetValueOrDefault(); // some guilds haven't been patched to include this yet? | SlowModeInterval = model.SlowMode.GetValueOrDefault(); // some guilds haven't been patched to include this yet? | ||||
| _nsfw = model.Nsfw.GetValueOrDefault(); | _nsfw = model.Nsfw.GetValueOrDefault(); | ||||
| } | } | ||||
| @@ -115,7 +115,12 @@ namespace Discord.WebSocket | |||||
| ThreadArchiveDuration autoArchiveDuration = ThreadArchiveDuration.OneDay, IMessage message = null, RequestOptions options = null) | ThreadArchiveDuration autoArchiveDuration = ThreadArchiveDuration.OneDay, IMessage message = null, RequestOptions options = null) | ||||
| { | { | ||||
| var model = await ThreadHelper.CreateThreadAsync(Discord, this, name, type, autoArchiveDuration, message, options); | var model = await ThreadHelper.CreateThreadAsync(Discord, this, name, type, autoArchiveDuration, message, options); | ||||
| return SocketThreadChannel.Create(this.Guild, Discord.State, model); | |||||
| var thread = (SocketThreadChannel)Guild.AddOrUpdateChannel(Discord.State, model); | |||||
| await thread.DownloadUsersAsync(); | |||||
| return thread; | |||||
| } | } | ||||
| //Messages | //Messages | ||||
| @@ -146,7 +146,6 @@ namespace Discord.WebSocket | |||||
| return member; | return member; | ||||
| } | } | ||||
| //Users | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public new SocketThreadUser GetUser(ulong id) | public new SocketThreadUser GetUser(ulong id) | ||||
| { | { | ||||
| @@ -310,7 +309,7 @@ namespace Discord.WebSocket | |||||
| /// <b>This method is not supported in threads.</b> | /// <b>This method is not supported in threads.</b> | ||||
| /// </remarks> | /// </remarks> | ||||
| public override Task ModifyAsync(Action<TextChannelProperties> func, RequestOptions options = null) | public override Task ModifyAsync(Action<TextChannelProperties> func, RequestOptions options = null) | ||||
| => throw new NotImplementedException(); | |||||
| => ThreadHelper.ModifyAsync(this, Discord, func, options); | |||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||
| /// <remarks> | /// <remarks> | ||||
| @@ -339,5 +338,7 @@ namespace Discord.WebSocket | |||||
| /// </remarks> | /// </remarks> | ||||
| public override Task SyncPermissionsAsync(RequestOptions options = null) | public override Task SyncPermissionsAsync(RequestOptions options = null) | ||||
| => throw new NotImplementedException(); | => throw new NotImplementedException(); | ||||
| string IChannel.Name => this.Name; | |||||
| } | } | ||||
| } | } | ||||
| @@ -32,7 +32,7 @@ namespace Discord.WebSocket | |||||
| private readonly SemaphoreSlim _audioLock; | private readonly SemaphoreSlim _audioLock; | ||||
| private TaskCompletionSource<bool> _syncPromise, _downloaderPromise; | private TaskCompletionSource<bool> _syncPromise, _downloaderPromise; | ||||
| private TaskCompletionSource<AudioClient> _audioConnectPromise; | private TaskCompletionSource<AudioClient> _audioConnectPromise; | ||||
| private ConcurrentHashSet<ulong> _channels; | |||||
| private ConcurrentDictionary<ulong, SocketGuildChannel> _channels; | |||||
| private ConcurrentDictionary<ulong, SocketGuildUser> _members; | private ConcurrentDictionary<ulong, SocketGuildUser> _members; | ||||
| private ConcurrentDictionary<ulong, SocketRole> _roles; | private ConcurrentDictionary<ulong, SocketRole> _roles; | ||||
| private ConcurrentDictionary<ulong, SocketVoiceState> _voiceStates; | private ConcurrentDictionary<ulong, SocketVoiceState> _voiceStates; | ||||
| @@ -307,7 +307,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| var channels = _channels; | var channels = _channels; | ||||
| var state = Discord.State; | var state = Discord.State; | ||||
| return channels.Select(x => state.GetChannel(x) as SocketGuildChannel).Where(x => x != null).ToReadOnlyCollection(channels); | |||||
| return channels.Select(x => x.Value).Where(x => x != null).ToReadOnlyCollection(channels); | |||||
| } | } | ||||
| } | } | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| @@ -363,7 +363,7 @@ namespace Discord.WebSocket | |||||
| if (!IsAvailable) | if (!IsAvailable) | ||||
| { | { | ||||
| if (_channels == null) | if (_channels == null) | ||||
| _channels = new ConcurrentHashSet<ulong>(); | |||||
| _channels = new ConcurrentDictionary<ulong, SocketGuildChannel>(); | |||||
| if (_members == null) | if (_members == null) | ||||
| _members = new ConcurrentDictionary<ulong, SocketGuildUser>(); | _members = new ConcurrentDictionary<ulong, SocketGuildUser>(); | ||||
| if (_roles == null) | if (_roles == null) | ||||
| @@ -379,20 +379,20 @@ namespace Discord.WebSocket | |||||
| Update(state, model as Model); | Update(state, model as Model); | ||||
| var channels = new ConcurrentHashSet<ulong>(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.Channels.Length * 1.05)); | |||||
| var channels = new ConcurrentDictionary<ulong, SocketGuildChannel>(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.Channels.Length * 1.05)); | |||||
| { | { | ||||
| for (int i = 0; i < model.Channels.Length; i++) | for (int i = 0; i < model.Channels.Length; i++) | ||||
| { | { | ||||
| var channel = SocketGuildChannel.Create(this, state, model.Channels[i]); | var channel = SocketGuildChannel.Create(this, state, model.Channels[i]); | ||||
| state.AddChannel(channel); | state.AddChannel(channel); | ||||
| channels.TryAdd(channel.Id); | |||||
| channels.TryAdd(channel.Id, channel); | |||||
| } | } | ||||
| for(int i = 0; i < model.Threads.Length; i++) | for(int i = 0; i < model.Threads.Length; i++) | ||||
| { | { | ||||
| var threadChannel = SocketThreadChannel.Create(this, state, model.Threads[i]); | var threadChannel = SocketThreadChannel.Create(this, state, model.Threads[i]); | ||||
| state.AddChannel(threadChannel); | state.AddChannel(threadChannel); | ||||
| channels.TryAdd(threadChannel.Id); | |||||
| channels.TryAdd(threadChannel.Id, threadChannel); | |||||
| } | } | ||||
| } | } | ||||
| @@ -703,20 +703,34 @@ namespace Discord.WebSocket | |||||
| internal SocketGuildChannel AddChannel(ClientState state, ChannelModel model) | internal SocketGuildChannel AddChannel(ClientState state, ChannelModel model) | ||||
| { | { | ||||
| var channel = SocketGuildChannel.Create(this, state, model); | var channel = SocketGuildChannel.Create(this, state, model); | ||||
| _channels.TryAdd(model.Id); | |||||
| _channels.TryAdd(model.Id, channel); | |||||
| state.AddChannel(channel); | state.AddChannel(channel); | ||||
| return channel; | return channel; | ||||
| } | } | ||||
| internal SocketGuildChannel AddOrUpdateChannel(ClientState state, ChannelModel model) | |||||
| { | |||||
| if (_channels.TryGetValue(model.Id, out SocketGuildChannel channel)) | |||||
| channel.Update(Discord.State, model); | |||||
| else | |||||
| { | |||||
| channel = SocketGuildChannel.Create(this, Discord.State, model); | |||||
| _channels[channel.Id] = channel; | |||||
| state.AddChannel(channel); | |||||
| } | |||||
| return channel; | |||||
| } | |||||
| internal SocketGuildChannel RemoveChannel(ClientState state, ulong id) | internal SocketGuildChannel RemoveChannel(ClientState state, ulong id) | ||||
| { | { | ||||
| if (_channels.TryRemove(id)) | |||||
| if (_channels.TryRemove(id, out var _)) | |||||
| return state.RemoveChannel(id) as SocketGuildChannel; | return state.RemoveChannel(id) as SocketGuildChannel; | ||||
| return null; | return null; | ||||
| } | } | ||||
| internal void PurgeChannelCache(ClientState state) | internal void PurgeChannelCache(ClientState state) | ||||
| { | { | ||||
| foreach (var channelId in _channels) | foreach (var channelId in _channels) | ||||
| state.RemoveChannel(channelId); | |||||
| state.RemoveChannel(channelId.Key); | |||||
| _channels.Clear(); | _channels.Clear(); | ||||
| } | } | ||||