diff --git a/src/Discord.Net.Examples/WebSocket/BaseSocketClient.Events.Examples.cs b/src/Discord.Net.Examples/WebSocket/BaseSocketClient.Events.Examples.cs index 387584877..27d393c07 100644 --- a/src/Discord.Net.Examples/WebSocket/BaseSocketClient.Events.Examples.cs +++ b/src/Discord.Net.Examples/WebSocket/BaseSocketClient.Events.Examples.cs @@ -15,7 +15,7 @@ namespace Discord.Net.Examples.WebSocket => client.ReactionAdded += HandleReactionAddedAsync; public async Task HandleReactionAddedAsync(Cacheable cachedMessage, - ISocketMessageChannel originChannel, SocketReaction reaction) + Cacheable originChannel, SocketReaction reaction) { var message = await cachedMessage.GetOrDownloadAsync(); if (message != null && reaction.User.IsSpecified) @@ -100,16 +100,17 @@ namespace Discord.Net.Examples.WebSocket public void HookMessageDeleted(BaseSocketClient client) => client.MessageDeleted += HandleMessageDelete; - public Task HandleMessageDelete(Cacheable cachedMessage, ISocketMessageChannel channel) + public async Task HandleMessageDelete(Cacheable cachedMessage, Cacheable cachedChannel) { // check if the message exists in cache; if not, we cannot report what was removed - if (!cachedMessage.HasValue) return Task.CompletedTask; + if (!cachedMessage.HasValue) return; + // gets or downloads the channel if it's not in the cache + IMessageChannel channel = await cachedChannel.GetOrDownloadAsync(); var message = cachedMessage.Value; Console.WriteLine( $"A message ({message.Id}) from {message.Author} was removed from the channel {channel.Name} ({channel.Id}):" + Environment.NewLine + message.Content); - return Task.CompletedTask; } #endregion diff --git a/src/Discord.Net.WebSocket/API/Gateway/Reaction.cs b/src/Discord.Net.WebSocket/API/Gateway/Reaction.cs index a0a740868..0d17cbff8 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/Reaction.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/Reaction.cs @@ -10,6 +10,8 @@ namespace Discord.API.Gateway public ulong MessageId { get; set; } [JsonProperty("channel_id")] public ulong ChannelId { get; set; } + [JsonProperty("guild_id")] + public Optional GuildId { get; set; } [JsonProperty("emoji")] public Emoji Emoji { get; set; } [JsonProperty("member")] diff --git a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs index 966aec7fa..e15e6a687 100644 --- a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs +++ b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs @@ -124,11 +124,11 @@ namespace Discord.WebSocket /// /// - public event Func, ISocketMessageChannel, Task> MessageDeleted { + public event Func, Cacheable, Task> MessageDeleted { add { _messageDeletedEvent.Add(value); } remove { _messageDeletedEvent.Remove(value); } } - internal readonly AsyncEvent, ISocketMessageChannel, Task>> _messageDeletedEvent = new AsyncEvent, ISocketMessageChannel, Task>>(); + internal readonly AsyncEvent, Cacheable, Task>> _messageDeletedEvent = new AsyncEvent, Cacheable, Task>>(); /// Fired when multiple messages are bulk deleted. /// /// @@ -155,12 +155,12 @@ namespace Discord.WebSocket /// parameter. /// /// - public event Func>, ISocketMessageChannel, Task> MessagesBulkDeleted + public event Func>, Cacheable, Task> MessagesBulkDeleted { add { _messagesBulkDeletedEvent.Add(value); } remove { _messagesBulkDeletedEvent.Remove(value); } } - internal readonly AsyncEvent>, ISocketMessageChannel, Task>> _messagesBulkDeletedEvent = new AsyncEvent>, ISocketMessageChannel, Task>>(); + internal readonly AsyncEvent>, Cacheable, Task>> _messagesBulkDeletedEvent = new AsyncEvent>, Cacheable, Task>>(); /// Fired when a message is updated. /// /// @@ -217,23 +217,23 @@ namespace Discord.WebSocket /// /// - public event Func, ISocketMessageChannel, SocketReaction, Task> ReactionAdded { + public event Func, Cacheable, SocketReaction, Task> ReactionAdded { add { _reactionAddedEvent.Add(value); } remove { _reactionAddedEvent.Remove(value); } } - internal readonly AsyncEvent, ISocketMessageChannel, SocketReaction, Task>> _reactionAddedEvent = new AsyncEvent, ISocketMessageChannel, SocketReaction, Task>>(); + internal readonly AsyncEvent, Cacheable, SocketReaction, Task>> _reactionAddedEvent = new AsyncEvent, Cacheable, SocketReaction, Task>>(); /// Fired when a reaction is removed from a message. - public event Func, ISocketMessageChannel, SocketReaction, Task> ReactionRemoved { + public event Func, Cacheable, SocketReaction, Task> ReactionRemoved { add { _reactionRemovedEvent.Add(value); } remove { _reactionRemovedEvent.Remove(value); } } - internal readonly AsyncEvent, ISocketMessageChannel, SocketReaction, Task>> _reactionRemovedEvent = new AsyncEvent, ISocketMessageChannel, SocketReaction, Task>>(); + internal readonly AsyncEvent, Cacheable, SocketReaction, Task>> _reactionRemovedEvent = new AsyncEvent, Cacheable, SocketReaction, Task>>(); /// Fired when all reactions to a message are cleared. - public event Func, ISocketMessageChannel, Task> ReactionsCleared { + public event Func, Cacheable, Task> ReactionsCleared { add { _reactionsClearedEvent.Add(value); } remove { _reactionsClearedEvent.Remove(value); } } - internal readonly AsyncEvent, ISocketMessageChannel, Task>> _reactionsClearedEvent = new AsyncEvent, ISocketMessageChannel, Task>>(); + internal readonly AsyncEvent, Cacheable, Task>> _reactionsClearedEvent = new AsyncEvent, Cacheable, Task>>(); /// /// Fired when all reactions to a message with a specific emote are removed. /// @@ -250,12 +250,12 @@ namespace Discord.WebSocket /// The emoji that all reactions had and were removed will be passed into the parameter. /// /// - public event Func, ISocketMessageChannel, IEmote, Task> ReactionsRemovedForEmote + public event Func, Cacheable, IEmote, Task> ReactionsRemovedForEmote { add { _reactionsRemovedForEmoteEvent.Add(value); } remove { _reactionsRemovedForEmoteEvent.Remove(value); } } - internal readonly AsyncEvent, ISocketMessageChannel, IEmote, Task>> _reactionsRemovedForEmoteEvent = new AsyncEvent, ISocketMessageChannel, IEmote, Task>>(); + internal readonly AsyncEvent, Cacheable, IEmote, Task>> _reactionsRemovedForEmoteEvent = new AsyncEvent, Cacheable, IEmote, Task>>(); //Roles /// Fired when a role is created. @@ -372,11 +372,11 @@ namespace Discord.WebSocket } internal readonly AsyncEvent> _selfUpdatedEvent = new AsyncEvent>(); /// Fired when a user starts typing. - public event Func UserIsTyping { + public event Func, Cacheable, Task> UserIsTyping { add { _userIsTypingEvent.Add(value); } remove { _userIsTypingEvent.Remove(value); } } - internal readonly AsyncEvent> _userIsTypingEvent = new AsyncEvent>(); + internal readonly AsyncEvent, Cacheable, Task>> _userIsTypingEvent = new AsyncEvent, Cacheable, Task>>(); /// Fired when a user joins a group channel. public event Func RecipientAdded { add { _recipientAddedEvent.Add(value); } diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 025608a30..0e854421e 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -71,7 +71,6 @@ namespace Discord.WebSocket internal WebSocketProvider WebSocketProvider { get; private set; } internal bool AlwaysDownloadUsers { get; private set; } internal int? HandlerTimeout { get; private set; } - internal bool? ExclusiveBulkDelete { get; private set; } internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; /// @@ -132,7 +131,6 @@ namespace Discord.WebSocket WebSocketProvider = config.WebSocketProvider; AlwaysDownloadUsers = config.AlwaysDownloadUsers; HandlerTimeout = config.HandlerTimeout; - ExclusiveBulkDelete = config.ExclusiveBulkDelete; State = new ClientState(0, 0); Rest = new DiscordSocketRestClient(config, ApiClient); _heartbeatTimes = new ConcurrentQueue(); @@ -296,6 +294,44 @@ namespace Discord.WebSocket public override SocketChannel GetChannel(ulong id) => State.GetChannel(id); /// + /// Gets a generic channel from the cache or does a rest request if unavailable. + /// + /// + /// + /// var channel = await _client.GetChannelAsync(381889909113225237); + /// if (channel != null && channel is IMessageChannel msgChannel) + /// { + /// await msgChannel.SendMessageAsync($"{msgChannel} is created at {msgChannel.CreatedAt}"); + /// } + /// + /// + /// The snowflake identifier of the channel (e.g. `381889909113225237`). + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains the channel associated + /// with the snowflake identifier; null when the channel cannot be found. + /// + public async ValueTask GetChannelAsync(ulong id, RequestOptions options = null) + => GetChannel(id) ?? (IChannel)await ClientHelper.GetChannelAsync(this, id, options).ConfigureAwait(false); + /// + /// Gets a user from the cache or does a rest request if unavailable. + /// + /// + /// + /// var user = await _client.GetUserAsync(168693960628371456); + /// if (user != null) + /// Console.WriteLine($"{user} is created at {user.CreatedAt}."; + /// + /// + /// The snowflake identifier of the user (e.g. `168693960628371456`). + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains the user associated with + /// the snowflake identifier; null if the user is not found. + /// + public async ValueTask GetUserAsync(ulong id, RequestOptions options = null) + => await ClientHelper.GetUserAsync(this, id, options).ConfigureAwait(false); + /// /// Clears all cached channels from the client. /// public void PurgeChannelCache() => State.PurgeAllChannels(); @@ -1227,7 +1263,7 @@ namespace Discord.WebSocket await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_CREATE)").ConfigureAwait(false); var data = (payload as JToken).ToObject(_serializer); - var channel = State.GetChannel(data.ChannelId) as ISocketMessageChannel; + var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; var guild = (channel as SocketGuildChannel)?.Guild; if (guild != null && !guild.IsSynced) @@ -1291,52 +1327,77 @@ namespace Discord.WebSocket await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_UPDATE)").ConfigureAwait(false); var data = (payload as JToken).ToObject(_serializer); - if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) + var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; + + var guild = (channel as SocketGuildChannel)?.Guild; + if (guild != null && !guild.IsSynced) { - var guild = (channel as SocketGuildChannel)?.Guild; - if (guild != null && !guild.IsSynced) + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + return; + } + + if (channel == null) + { + if (!data.GuildId.IsSpecified) // assume it is a DM { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + channel = CreateDMChannel(data.ChannelId, data.Author.Value, State); + } + else + { + await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); return; } + } - SocketMessage before = null, after = null; - SocketMessage cachedMsg = channel.GetCachedMessage(data.Id); - bool isCached = cachedMsg != null; - if (isCached) + SocketMessage before = null, after = null; + SocketMessage cachedMsg = channel.GetCachedMessage(data.Id); + bool isCached = cachedMsg != null; + if (isCached) + { + before = cachedMsg.Clone(); + cachedMsg.Update(State, data); + after = cachedMsg; + } + else + { + //Edited message isnt in cache, create a detached one + SocketUser author; + if (guild != null) { - before = cachedMsg.Clone(); - cachedMsg.Update(State, data); - after = cachedMsg; + if (data.WebhookId.IsSpecified) + author = SocketWebhookUser.Create(guild, State, data.Author.Value, data.WebhookId.Value); + else + author = guild.GetUser(data.Author.Value.Id); } else + author = (channel as SocketChannel).GetUser(data.Author.Value.Id); + + if (author == null) { - //Edited message isnt in cache, create a detached one - SocketUser author; - if (data.Author.IsSpecified) + if (guild != null) { - if (guild != null) - author = guild.GetUser(data.Author.Value.Id); + if (data.Member.IsSpecified) // member isn't always included, but use it when we can + { + data.Member.Value.User = data.Author.Value; + author = guild.AddOrUpdateUser(data.Member.Value); + } else - author = (channel as SocketChannel).GetUser(data.Author.Value.Id); - if (author == null) - author = SocketUnknownUser.Create(this, State, data.Author.Value); + author = guild.AddOrUpdateUser(data.Author.Value); // user has no guild-specific data } + else if (channel is SocketGroupChannel groupChannel) + author = groupChannel.GetOrAddUser(data.Author.Value); else - // Message author wasn't specified in the payload, so create a completely anonymous unknown user - author = new SocketUnknownUser(this, id: 0); - - after = SocketMessage.Create(this, State, author, channel, data); + { + await UnknownChannelUserAsync(type, data.Author.Value.Id, channel.Id).ConfigureAwait(false); + return; + } } - var cacheableBefore = new Cacheable(before, data.Id, isCached, async () => await channel.GetMessageAsync(data.Id).ConfigureAwait(false)); - await TimedInvokeAsync(_messageUpdatedEvent, nameof(MessageUpdated), cacheableBefore, after, channel).ConfigureAwait(false); - } - else - { - await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); - return; + after = SocketMessage.Create(this, State, author, channel, data); } + var cacheableBefore = new Cacheable(before, data.Id, isCached, async () => await channel.GetMessageAsync(data.Id).ConfigureAwait(false)); + + await TimedInvokeAsync(_messageUpdatedEvent, nameof(MessageUpdated), cacheableBefore, after, channel).ConfigureAwait(false); } break; case "MESSAGE_DELETE": @@ -1344,26 +1405,22 @@ namespace Discord.WebSocket await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE)").ConfigureAwait(false); var data = (payload as JToken).ToObject(_serializer); - if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) - { - var guild = (channel as SocketGuildChannel)?.Guild; - if (!(guild?.IsSynced ?? true)) - { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); - return; - } + var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; - var msg = SocketChannelHelper.RemoveMessage(channel, this, data.Id); - bool isCached = msg != null; - var cacheable = new Cacheable(msg, data.Id, isCached, async () => await channel.GetMessageAsync(data.Id).ConfigureAwait(false)); - - await TimedInvokeAsync(_messageDeletedEvent, nameof(MessageDeleted), cacheable, channel).ConfigureAwait(false); - } - else + var guild = (channel as SocketGuildChannel)?.Guild; + if (!(guild?.IsSynced ?? true)) { - await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); return; } + + SocketMessage msg = null; + if (channel != null) + msg = SocketChannelHelper.RemoveMessage(channel, this, data.Id); + var cacheableMsg = new Cacheable(msg, data.Id, msg != null, () => Task.FromResult((IMessage)null)); + var cacheableChannel = new Cacheable(channel, data.ChannelId, channel != null, async () => await GetChannelAsync(data.ChannelId).ConfigureAwait(false) as IMessageChannel); + + await TimedInvokeAsync(_messageDeletedEvent, nameof(MessageDeleted), cacheableMsg, cacheableChannel).ConfigureAwait(false); } break; case "MESSAGE_REACTION_ADD": @@ -1371,40 +1428,43 @@ namespace Discord.WebSocket await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_ADD)").ConfigureAwait(false); var data = (payload as JToken).ToObject(_serializer); - if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) - { - var cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; - bool isCached = cachedMsg != null; - var user = await channel.GetUserAsync(data.UserId, CacheMode.CacheOnly).ConfigureAwait(false); + var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; - var optionalMsg = !isCached - ? Optional.Create() - : Optional.Create(cachedMsg); - - if (data.Member.IsSpecified) - { - var guild = (channel as SocketGuildChannel)?.Guild; - - if (guild != null) - user = guild.AddOrUpdateUser(data.Member.Value); - } - - var optionalUser = user is null - ? Optional.Create() - : Optional.Create(user); + var cachedMsg = channel?.GetCachedMessage(data.MessageId) as SocketUserMessage; + bool isMsgCached = cachedMsg != null; + IUser user = null; + if (channel != null) + user = await channel.GetUserAsync(data.UserId, CacheMode.CacheOnly).ConfigureAwait(false); - var reaction = SocketReaction.Create(data, channel, optionalMsg, optionalUser); - var cacheable = new Cacheable(cachedMsg, data.MessageId, isCached, async () => await channel.GetMessageAsync(data.MessageId).ConfigureAwait(false) as IUserMessage); + var optionalMsg = !isMsgCached + ? Optional.Create() + : Optional.Create(cachedMsg); - cachedMsg?.AddReaction(reaction); + if (data.Member.IsSpecified) + { + var guild = (channel as SocketGuildChannel)?.Guild; - await TimedInvokeAsync(_reactionAddedEvent, nameof(ReactionAdded), cacheable, channel, reaction).ConfigureAwait(false); + if (guild != null) + user = guild.AddOrUpdateUser(data.Member.Value); } else + user = GetUser(data.UserId); + + var optionalUser = user is null + ? Optional.Create() + : Optional.Create(user); + + var cacheableChannel = new Cacheable(channel, data.ChannelId, channel != null, async () => await GetChannelAsync(data.ChannelId).ConfigureAwait(false) as IMessageChannel); + var cacheableMsg = new Cacheable(cachedMsg, data.MessageId, isMsgCached, async () => { - await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); - return; - } + var channelObj = await cacheableChannel.GetOrDownloadAsync().ConfigureAwait(false); + return await channelObj.GetMessageAsync(data.MessageId).ConfigureAwait(false) as IUserMessage; + }); + var reaction = SocketReaction.Create(data, channel, optionalMsg, optionalUser); + + cachedMsg?.AddReaction(reaction); + + await TimedInvokeAsync(_reactionAddedEvent, nameof(ReactionAdded), cacheableMsg, cacheableChannel, reaction).ConfigureAwait(false); } break; case "MESSAGE_REACTION_REMOVE": @@ -1412,32 +1472,35 @@ namespace Discord.WebSocket await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_REMOVE)").ConfigureAwait(false); var data = (payload as JToken).ToObject(_serializer); - if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) - { - var cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; - bool isCached = cachedMsg != null; - var user = await channel.GetUserAsync(data.UserId, CacheMode.CacheOnly).ConfigureAwait(false); + var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; - var optionalMsg = !isCached - ? Optional.Create() - : Optional.Create(cachedMsg); - - var optionalUser = user is null - ? Optional.Create() - : Optional.Create(user); + var cachedMsg = channel?.GetCachedMessage(data.MessageId) as SocketUserMessage; + bool isMsgCached = cachedMsg != null; + IUser user = null; + if (channel != null) + user = await channel.GetUserAsync(data.UserId, CacheMode.CacheOnly).ConfigureAwait(false); + else if (!data.GuildId.IsSpecified) + user = GetUser(data.UserId); - var reaction = SocketReaction.Create(data, channel, optionalMsg, optionalUser); - var cacheable = new Cacheable(cachedMsg, data.MessageId, isCached, async () => await channel.GetMessageAsync(data.MessageId).ConfigureAwait(false) as IUserMessage); + var optionalMsg = !isMsgCached + ? Optional.Create() + : Optional.Create(cachedMsg); - cachedMsg?.RemoveReaction(reaction); + var optionalUser = user is null + ? Optional.Create() + : Optional.Create(user); - await TimedInvokeAsync(_reactionRemovedEvent, nameof(ReactionRemoved), cacheable, channel, reaction).ConfigureAwait(false); - } - else + var cacheableChannel = new Cacheable(channel, data.ChannelId, channel != null, async () => await GetChannelAsync(data.ChannelId).ConfigureAwait(false) as IMessageChannel); + var cacheableMsg = new Cacheable(cachedMsg, data.MessageId, isMsgCached, async () => { - await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); - return; - } + var channelObj = await cacheableChannel.GetOrDownloadAsync().ConfigureAwait(false); + return await channelObj.GetMessageAsync(data.MessageId).ConfigureAwait(false) as IUserMessage; + }); + var reaction = SocketReaction.Create(data, channel, optionalMsg, optionalUser); + + cachedMsg?.RemoveReaction(reaction); + + await TimedInvokeAsync(_reactionRemovedEvent, nameof(ReactionRemoved), cacheableMsg, cacheableChannel, reaction).ConfigureAwait(false); } break; case "MESSAGE_REACTION_REMOVE_ALL": @@ -1445,21 +1508,20 @@ namespace Discord.WebSocket await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_REMOVE_ALL)").ConfigureAwait(false); var data = (payload as JToken).ToObject(_serializer); - if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) + var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; + + var cacheableChannel = new Cacheable(channel, data.ChannelId, channel != null, async () => await GetChannelAsync(data.ChannelId).ConfigureAwait(false) as IMessageChannel); + var cachedMsg = channel?.GetCachedMessage(data.MessageId) as SocketUserMessage; + bool isMsgCached = cachedMsg != null; + var cacheableMsg = new Cacheable(cachedMsg, data.MessageId, isMsgCached, async () => { - var cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; - bool isCached = cachedMsg != null; - var cacheable = new Cacheable(cachedMsg, data.MessageId, isCached, async () => (await channel.GetMessageAsync(data.MessageId).ConfigureAwait(false)) as IUserMessage); + var channelObj = await cacheableChannel.GetOrDownloadAsync().ConfigureAwait(false); + return await channelObj.GetMessageAsync(data.MessageId).ConfigureAwait(false) as IUserMessage; + }); - cachedMsg?.ClearReactions(); + cachedMsg?.ClearReactions(); - await TimedInvokeAsync(_reactionsClearedEvent, nameof(ReactionsCleared), cacheable, channel).ConfigureAwait(false); - } - else - { - await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); - return; - } + await TimedInvokeAsync(_reactionsClearedEvent, nameof(ReactionsCleared), cacheableMsg, cacheableChannel).ConfigureAwait(false); } break; case "MESSAGE_REACTION_REMOVE_EMOJI": @@ -1467,70 +1529,55 @@ namespace Discord.WebSocket await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_REMOVE_EMOJI)").ConfigureAwait(false); var data = (payload as JToken).ToObject(_serializer); - if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) - { - var cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; - bool isCached = cachedMsg != null; + var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; - var optionalMsg = !isCached - ? Optional.Create() - : Optional.Create(cachedMsg); + var cachedMsg = channel?.GetCachedMessage(data.MessageId) as SocketUserMessage; + bool isMsgCached = cachedMsg != null; - var cacheable = new Cacheable(cachedMsg, data.MessageId, isCached, async () => await channel.GetMessageAsync(data.MessageId).ConfigureAwait(false) as IUserMessage); - var emote = data.Emoji.ToIEmote(); + var optionalMsg = !isMsgCached + ? Optional.Create() + : Optional.Create(cachedMsg); - cachedMsg?.RemoveReactionsForEmote(emote); - - await TimedInvokeAsync(_reactionsRemovedForEmoteEvent, nameof(ReactionsRemovedForEmote), cacheable, channel, emote).ConfigureAwait(false); - } - else + var cacheableChannel = new Cacheable(channel, data.ChannelId, channel != null, async () => await GetChannelAsync(data.ChannelId).ConfigureAwait(false) as IMessageChannel); + var cacheableMsg = new Cacheable(cachedMsg, data.MessageId, isMsgCached, async () => { - await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); - return; - } + var channelObj = await cacheableChannel.GetOrDownloadAsync().ConfigureAwait(false); + return await channelObj.GetMessageAsync(data.MessageId).ConfigureAwait(false) as IUserMessage; + }); + var emote = data.Emoji.ToIEmote(); + + cachedMsg?.RemoveReactionsForEmote(emote); + + await TimedInvokeAsync(_reactionsRemovedForEmoteEvent, nameof(ReactionsRemovedForEmote), cacheableMsg, cacheableChannel, emote).ConfigureAwait(false); } break; case "MESSAGE_DELETE_BULK": { await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE_BULK)").ConfigureAwait(false); - if (!ExclusiveBulkDelete.HasValue) - { - await _gatewayLogger.WarningAsync("A bulk delete event has been received, but the event handling behavior has not been set. " + - "To suppress this message, set the ExclusiveBulkDelete configuration property. " + - "This message will appear only once."); - ExclusiveBulkDelete = false; - } - var data = (payload as JToken).ToObject(_serializer); - if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) - { - var guild = (channel as SocketGuildChannel)?.Guild; - if (!(guild?.IsSynced ?? true)) - { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); - return; - } - - var cacheableList = new List>(data.Ids.Length); - foreach (ulong id in data.Ids) - { - var msg = SocketChannelHelper.RemoveMessage(channel, this, id); - bool isCached = msg != null; - var cacheable = new Cacheable(msg, id, isCached, async () => await channel.GetMessageAsync(id).ConfigureAwait(false)); - cacheableList.Add(cacheable); - - if (!ExclusiveBulkDelete ?? false) // this shouldn't happen, but we'll play it safe anyways - await TimedInvokeAsync(_messageDeletedEvent, nameof(MessageDeleted), cacheable, channel).ConfigureAwait(false); - } + var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; - await TimedInvokeAsync(_messagesBulkDeletedEvent, nameof(MessagesBulkDeleted), cacheableList, channel).ConfigureAwait(false); - } - else + var guild = (channel as SocketGuildChannel)?.Guild; + if (!(guild?.IsSynced ?? true)) { - await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); return; } + + var cacheableChannel = new Cacheable(channel, data.ChannelId, channel != null, async () => await GetChannelAsync(data.ChannelId).ConfigureAwait(false) as IMessageChannel); + var cacheableList = new List>(data.Ids.Length); + foreach (ulong id in data.Ids) + { + SocketMessage msg = null; + if (channel != null) + msg = SocketChannelHelper.RemoveMessage(channel, this, id); + bool isMsgCached = msg != null; + var cacheableMsg = new Cacheable(msg, id, isMsgCached, () => Task.FromResult((IMessage)null)); + cacheableList.Add(cacheableMsg); + } + + await TimedInvokeAsync(_messagesBulkDeletedEvent, nameof(MessagesBulkDeleted), cacheableList, cacheableChannel).ConfigureAwait(false); } break; @@ -1599,24 +1646,26 @@ namespace Discord.WebSocket await _gatewayLogger.DebugAsync("Received Dispatch (TYPING_START)").ConfigureAwait(false); var data = (payload as JToken).ToObject(_serializer); - if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) + var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; + + var guild = (channel as SocketGuildChannel)?.Guild; + if (!(guild?.IsSynced ?? true)) { - var guild = (channel as SocketGuildChannel)?.Guild; - if (!(guild?.IsSynced ?? true)) - { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); - return; - } + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + return; + } - var user = (channel as SocketChannel).GetUser(data.UserId); - if (user == null) - { - if (guild != null) - user = guild.AddOrUpdateUser(data.Member); - } - if (user != null) - await TimedInvokeAsync(_userIsTypingEvent, nameof(UserIsTyping), user, channel).ConfigureAwait(false); + var cacheableChannel = new Cacheable(channel, data.ChannelId, channel != null, async () => await GetChannelAsync(data.ChannelId).ConfigureAwait(false) as IMessageChannel); + + var user = (channel as SocketChannel)?.GetUser(data.UserId); + if (user == null) + { + if (guild != null) + user = guild.AddOrUpdateUser(data.Member); } + var cacheableUser = new Cacheable(user, data.UserId, user != null, async () => await GetUserAsync(data.UserId).ConfigureAwait(false)); + + await TimedInvokeAsync(_userIsTypingEvent, nameof(UserIsTyping), cacheableUser, cacheableChannel).ConfigureAwait(false); } break; @@ -1688,7 +1737,7 @@ namespace Discord.WebSocket } else { - var groupChannel = State.GetChannel(data.ChannelId.Value) as SocketGroupChannel; + var groupChannel = GetChannel(data.ChannelId.Value) as SocketGroupChannel; if (groupChannel == null) { await UnknownChannelAsync(type, data.ChannelId.Value).ConfigureAwait(false); @@ -2113,8 +2162,8 @@ namespace Discord.WebSocket => await GetApplicationInfoAsync().ConfigureAwait(false); /// - Task IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) - => Task.FromResult(GetChannel(id)); + async Task IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) + => mode == CacheMode.AllowDownload ? await GetChannelAsync(id, options).ConfigureAwait(false) : GetChannel(id); /// Task> IDiscordClient.GetPrivateChannelsAsync(CacheMode mode, RequestOptions options) => Task.FromResult>(PrivateChannels); @@ -2144,8 +2193,8 @@ namespace Discord.WebSocket => await CreateGuildAsync(name, region, jpegIcon).ConfigureAwait(false); /// - Task IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) - => Task.FromResult(GetUser(id)); + async Task IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) + => mode == CacheMode.AllowDownload ? await GetUserAsync(id, options).ConfigureAwait(false) : GetUser(id); /// Task IDiscordClient.GetUserAsync(string username, string discriminator, RequestOptions options) => Task.FromResult(GetUser(username, discriminator)); diff --git a/src/Discord.Net.WebSocket/DiscordSocketConfig.cs b/src/Discord.Net.WebSocket/DiscordSocketConfig.cs index 90b746787..22a201c67 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketConfig.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketConfig.cs @@ -111,22 +111,6 @@ namespace Discord.WebSocket /// public int? HandlerTimeout { get; set; } = 3000; - /// - /// Gets or sets the behavior for on bulk deletes. - /// - /// - /// - /// If true, the event will not be raised for bulk - /// deletes, and only the will be raised. If false - /// , both events will be raised. - /// - /// - /// If unset, both events will be raised, but a warning will be raised the first time a bulk delete event is - /// received. - /// - /// - public bool? ExclusiveBulkDelete { get; set; } = null; - /// /// Gets or sets the maximum identify concurrency. ///