@@ -1003,6 +1003,51 @@ | |||
A message was unpinned from this guild. | |||
</summary> | |||
</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"> | |||
<summary> | |||
Represents data applied to an <see cref="T:Discord.IAuditLogEntry"/>. | |||
@@ -6105,7 +6150,7 @@ | |||
The built embed object. | |||
</returns> | |||
<exception cref="T:System.InvalidOperationException">Total embed length exceeds <see cref="F:Discord.EmbedBuilder.MaxEmbedLength"/>.</exception> | |||
<exception cref="T:System.InvalidOperationException">Any Url must 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 name="T:Discord.EmbedFieldBuilder"> | |||
<summary> | |||
@@ -463,7 +463,9 @@ namespace Discord.API | |||
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) | |||
@@ -2187,6 +2187,38 @@ | |||
<member name="M:Discord.WebSocket.SocketTextChannel.ModifyAsync(System.Action{Discord.TextChannelProperties},Discord.RequestOptions)"> | |||
<inheritdoc /> | |||
</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)"> | |||
<inheritdoc /> | |||
</member> | |||
@@ -2352,6 +2384,9 @@ | |||
<member name="M:Discord.WebSocket.SocketTextChannel.Discord#ITextChannel#GetWebhooksAsync(Discord.RequestOptions)"> | |||
<inheritdoc /> | |||
</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)"> | |||
<inheritdoc /> | |||
</member> | |||
@@ -2390,6 +2425,9 @@ | |||
Represents a thread channel inside of a guild. | |||
</summary> | |||
</member> | |||
<member name="P:Discord.WebSocket.SocketThreadChannel.Type"> | |||
<inheritdoc/> | |||
</member> | |||
<member name="P:Discord.WebSocket.SocketThreadChannel.Owner"> | |||
<summary> | |||
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) | |||
{ | |||
threadChannel.Update(this.State, data); | |||
threadChannel.AddOrUpdateThreadMember(data.ThreadMember.Value, guild.CurrentUser); | |||
if(data.ThreadMember.IsSpecified) | |||
threadChannel.AddOrUpdateThreadMember(data.ThreadMember.Value, guild.CurrentUser); | |||
} | |||
else | |||
{ | |||
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); | |||
} | |||
} | |||
@@ -2092,12 +2095,19 @@ namespace Discord.WebSocket | |||
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; | |||
case "THREAD_MEMBERS_UPDATE": // based on intents | |||
case "THREAD_MEMBERS_UPDATE": | |||
{ | |||
await _gatewayLogger.DebugAsync("Received Dispatch (THREAD_MEMBERS_UPDATE)").ConfigureAwait(false); | |||
@@ -72,7 +72,7 @@ namespace Discord.WebSocket | |||
{ | |||
base.Update(state, model); | |||
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? | |||
_nsfw = model.Nsfw.GetValueOrDefault(); | |||
} | |||
@@ -115,7 +115,12 @@ namespace Discord.WebSocket | |||
ThreadArchiveDuration autoArchiveDuration = ThreadArchiveDuration.OneDay, IMessage message = null, RequestOptions options = null) | |||
{ | |||
var model = await ThreadHelper.CreateThreadAsync(Discord, this, name, type, autoArchiveDuration, message, options); | |||
return SocketThreadChannel.Create(this.Guild, Discord.State, model); | |||
var thread = (SocketThreadChannel)Guild.AddOrUpdateChannel(Discord.State, model); | |||
await thread.DownloadUsersAsync(); | |||
return thread; | |||
} | |||
//Messages | |||
@@ -146,7 +146,6 @@ namespace Discord.WebSocket | |||
return member; | |||
} | |||
//Users | |||
/// <inheritdoc /> | |||
public new SocketThreadUser GetUser(ulong id) | |||
{ | |||
@@ -310,7 +309,7 @@ namespace Discord.WebSocket | |||
/// <b>This method is not supported in threads.</b> | |||
/// </remarks> | |||
public override Task ModifyAsync(Action<TextChannelProperties> func, RequestOptions options = null) | |||
=> throw new NotImplementedException(); | |||
=> ThreadHelper.ModifyAsync(this, Discord, func, options); | |||
/// <inheritdoc/> | |||
/// <remarks> | |||
@@ -339,5 +338,7 @@ namespace Discord.WebSocket | |||
/// </remarks> | |||
public override Task SyncPermissionsAsync(RequestOptions options = null) | |||
=> throw new NotImplementedException(); | |||
string IChannel.Name => this.Name; | |||
} | |||
} |
@@ -32,7 +32,7 @@ namespace Discord.WebSocket | |||
private readonly SemaphoreSlim _audioLock; | |||
private TaskCompletionSource<bool> _syncPromise, _downloaderPromise; | |||
private TaskCompletionSource<AudioClient> _audioConnectPromise; | |||
private ConcurrentHashSet<ulong> _channels; | |||
private ConcurrentDictionary<ulong, SocketGuildChannel> _channels; | |||
private ConcurrentDictionary<ulong, SocketGuildUser> _members; | |||
private ConcurrentDictionary<ulong, SocketRole> _roles; | |||
private ConcurrentDictionary<ulong, SocketVoiceState> _voiceStates; | |||
@@ -307,7 +307,7 @@ namespace Discord.WebSocket | |||
{ | |||
var channels = _channels; | |||
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 /> | |||
@@ -363,7 +363,7 @@ namespace Discord.WebSocket | |||
if (!IsAvailable) | |||
{ | |||
if (_channels == null) | |||
_channels = new ConcurrentHashSet<ulong>(); | |||
_channels = new ConcurrentDictionary<ulong, SocketGuildChannel>(); | |||
if (_members == null) | |||
_members = new ConcurrentDictionary<ulong, SocketGuildUser>(); | |||
if (_roles == null) | |||
@@ -379,20 +379,20 @@ namespace Discord.WebSocket | |||
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++) | |||
{ | |||
var channel = SocketGuildChannel.Create(this, state, model.Channels[i]); | |||
state.AddChannel(channel); | |||
channels.TryAdd(channel.Id); | |||
channels.TryAdd(channel.Id, channel); | |||
} | |||
for(int i = 0; i < model.Threads.Length; i++) | |||
{ | |||
var threadChannel = SocketThreadChannel.Create(this, state, model.Threads[i]); | |||
state.AddChannel(threadChannel); | |||
channels.TryAdd(threadChannel.Id); | |||
channels.TryAdd(threadChannel.Id, threadChannel); | |||
} | |||
} | |||
@@ -703,20 +703,34 @@ namespace Discord.WebSocket | |||
internal SocketGuildChannel AddChannel(ClientState state, ChannelModel model) | |||
{ | |||
var channel = SocketGuildChannel.Create(this, state, model); | |||
_channels.TryAdd(model.Id); | |||
_channels.TryAdd(model.Id, channel); | |||
state.AddChannel(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) | |||
{ | |||
if (_channels.TryRemove(id)) | |||
if (_channels.TryRemove(id, out var _)) | |||
return state.RemoveChannel(id) as SocketGuildChannel; | |||
return null; | |||
} | |||
internal void PurgeChannelCache(ClientState state) | |||
{ | |||
foreach (var channelId in _channels) | |||
state.RemoveChannel(channelId); | |||
state.RemoveChannel(channelId.Key); | |||
_channels.Clear(); | |||
} | |||