diff --git a/src/Discord.Net.Core/Utils/Cacheable.cs b/src/Discord.Net.Core/Utils/Cacheable.cs new file mode 100644 index 000000000..10b61be90 --- /dev/null +++ b/src/Discord.Net.Core/Utils/Cacheable.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Discord +{ + /// + /// Contains an entity that may be cached. + /// + /// The type of entity that is cached + /// The type of this entity's ID + public struct Cacheable + where TEntity : IEntity + where TId : IEquatable + { + /// + /// Is this entity cached? + /// + public bool HasValue { get; } + /// + /// The ID of this entity. + /// + public TId Id { get; } + /// + /// The entity, if it could be pulled from cache. + /// + /// + /// This value is not guaranteed to be set; in cases where the entity cannot be pulled from cache, it is null. + /// + public TEntity Value { get; } + private Func> DownloadFunc { get; } + + internal Cacheable(TEntity value, TId id, bool hasValue , Func> downloadFunc) + { + Value = value; + Id = id; + HasValue = hasValue; + DownloadFunc = downloadFunc; + } + + /// + /// Downloads this entity to cache. + /// + /// An awaitable Task containing the downloaded entity. + /// Thrown when used from a user account. + /// Thrown when the message is deleted. + public async Task DownloadAsync() + { + return await DownloadFunc(); + } + + /// + /// Returns the cached entity if it exists; otherwise downloads it. + /// + /// An awaitable Task containing a cached or downloaded entity. + /// Thrown when used from a user account. + /// Thrown when the message is deleted and is not in cache. + public async Task GetOrDownloadAsync() => HasValue ? Value : await DownloadAsync(); + } +} \ No newline at end of file diff --git a/src/Discord.Net.WebSocket/DiscordShardedClient.Events.cs b/src/Discord.Net.WebSocket/DiscordShardedClient.Events.cs index 449d30599..874062c56 100644 --- a/src/Discord.Net.WebSocket/DiscordShardedClient.Events.cs +++ b/src/Discord.Net.WebSocket/DiscordShardedClient.Events.cs @@ -1,5 +1,6 @@ using System; using System.Threading.Tasks; +using Discord.Net; namespace Discord.WebSocket { @@ -33,36 +34,36 @@ namespace Discord.WebSocket remove { _messageReceivedEvent.Remove(value); } } private readonly AsyncEvent> _messageReceivedEvent = new AsyncEvent>(); - public event Func, Task> MessageDeleted + public event Func, ISocketMessageChannel, Task> MessageDeleted { add { _messageDeletedEvent.Add(value); } remove { _messageDeletedEvent.Remove(value); } } - private readonly AsyncEvent, Task>> _messageDeletedEvent = new AsyncEvent, Task>>(); - public event Func, SocketMessage, Task> MessageUpdated + private readonly AsyncEvent, ISocketMessageChannel, Task>> _messageDeletedEvent = new AsyncEvent, ISocketMessageChannel, Task>>(); + public event Func, SocketMessage, ISocketMessageChannel, Task> MessageUpdated { add { _messageUpdatedEvent.Add(value); } remove { _messageUpdatedEvent.Remove(value); } } - private readonly AsyncEvent, SocketMessage, Task>> _messageUpdatedEvent = new AsyncEvent, SocketMessage, Task>>(); - public event Func, SocketReaction, Task> ReactionAdded + private readonly AsyncEvent, SocketMessage, ISocketMessageChannel, Task>> _messageUpdatedEvent = new AsyncEvent, SocketMessage, ISocketMessageChannel, Task>>(); + public event Func, ISocketMessageChannel, SocketReaction, Task> ReactionAdded { add { _reactionAddedEvent.Add(value); } remove { _reactionAddedEvent.Remove(value); } } - private readonly AsyncEvent, SocketReaction, Task>> _reactionAddedEvent = new AsyncEvent, SocketReaction, Task>>(); - public event Func, SocketReaction, Task> ReactionRemoved + private readonly AsyncEvent, ISocketMessageChannel, SocketReaction, Task>> _reactionAddedEvent = new AsyncEvent, ISocketMessageChannel, SocketReaction, Task>>(); + public event Func, ISocketMessageChannel, SocketReaction, Task> ReactionRemoved { add { _reactionRemovedEvent.Add(value); } remove { _reactionRemovedEvent.Remove(value); } } - private readonly AsyncEvent, SocketReaction, Task>> _reactionRemovedEvent = new AsyncEvent, SocketReaction, Task>>(); - public event Func, Task> ReactionsCleared + private readonly AsyncEvent, ISocketMessageChannel, SocketReaction, Task>> _reactionRemovedEvent = new AsyncEvent, ISocketMessageChannel, SocketReaction, Task>>(); + public event Func, ISocketMessageChannel, Task> ReactionsCleared { add { _reactionsClearedEvent.Add(value); } remove { _reactionsClearedEvent.Remove(value); } } - private readonly AsyncEvent, Task>> _reactionsClearedEvent = new AsyncEvent, Task>>(); + private readonly AsyncEvent, ISocketMessageChannel, Task>> _reactionsClearedEvent = new AsyncEvent, ISocketMessageChannel, Task>>(); //Roles public event Func RoleCreated diff --git a/src/Discord.Net.WebSocket/DiscordShardedClient.cs b/src/Discord.Net.WebSocket/DiscordShardedClient.cs index cadbda6d1..06f83c8dc 100644 --- a/src/Discord.Net.WebSocket/DiscordShardedClient.cs +++ b/src/Discord.Net.WebSocket/DiscordShardedClient.cs @@ -275,11 +275,11 @@ namespace Discord.WebSocket client.ChannelUpdated += (oldChannel, newChannel) => _channelUpdatedEvent.InvokeAsync(oldChannel, newChannel); client.MessageReceived += (msg) => _messageReceivedEvent.InvokeAsync(msg); - client.MessageDeleted += (id, msg) => _messageDeletedEvent.InvokeAsync(id, msg); - client.MessageUpdated += (oldMsg, newMsg) => _messageUpdatedEvent.InvokeAsync(oldMsg, newMsg); - client.ReactionAdded += (id, msg, reaction) => _reactionAddedEvent.InvokeAsync(id, msg, reaction); - client.ReactionRemoved += (id, msg, reaction) => _reactionRemovedEvent.InvokeAsync(id, msg, reaction); - client.ReactionsCleared += (id, msg) => _reactionsClearedEvent.InvokeAsync(id, msg); + client.MessageDeleted += (cache, channel) => _messageDeletedEvent.InvokeAsync(cache, channel); + client.MessageUpdated += (oldMsg, newMsg, channel) => _messageUpdatedEvent.InvokeAsync(oldMsg, newMsg, channel); + client.ReactionAdded += (cache, channel, reaction) => _reactionAddedEvent.InvokeAsync(cache, channel, reaction); + client.ReactionRemoved += (cache, channel, reaction) => _reactionRemovedEvent.InvokeAsync(cache, channel, reaction); + client.ReactionsCleared += (cache, channel) => _reactionsClearedEvent.InvokeAsync(cache, channel); client.RoleCreated += (role) => _roleCreatedEvent.InvokeAsync(role); client.RoleDeleted += (role) => _roleDeletedEvent.InvokeAsync(role); diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.Events.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.Events.cs index 2f952bdaa..313e661f3 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.Events.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.Events.cs @@ -59,36 +59,36 @@ namespace Discord.WebSocket remove { _messageReceivedEvent.Remove(value); } } private readonly AsyncEvent> _messageReceivedEvent = new AsyncEvent>(); - public event Func, Task> MessageDeleted + public event Func, ISocketMessageChannel, Task> MessageDeleted { add { _messageDeletedEvent.Add(value); } remove { _messageDeletedEvent.Remove(value); } } - private readonly AsyncEvent, Task>> _messageDeletedEvent = new AsyncEvent, Task>>(); - public event Func, SocketMessage, Task> MessageUpdated + private readonly AsyncEvent, ISocketMessageChannel, Task>> _messageDeletedEvent = new AsyncEvent, ISocketMessageChannel, Task>>(); + public event Func, SocketMessage, ISocketMessageChannel, Task> MessageUpdated { add { _messageUpdatedEvent.Add(value); } remove { _messageUpdatedEvent.Remove(value); } } - private readonly AsyncEvent, SocketMessage, Task>> _messageUpdatedEvent = new AsyncEvent, SocketMessage, Task>>(); - public event Func, SocketReaction, Task> ReactionAdded + private readonly AsyncEvent, SocketMessage, ISocketMessageChannel, Task>> _messageUpdatedEvent = new AsyncEvent, SocketMessage, ISocketMessageChannel, Task>>(); + public event Func, ISocketMessageChannel, SocketReaction, Task> ReactionAdded { add { _reactionAddedEvent.Add(value); } remove { _reactionAddedEvent.Remove(value); } } - private readonly AsyncEvent, SocketReaction, Task>> _reactionAddedEvent = new AsyncEvent, SocketReaction, Task>>(); - public event Func, SocketReaction, Task> ReactionRemoved + private readonly AsyncEvent, ISocketMessageChannel, SocketReaction, Task>> _reactionAddedEvent = new AsyncEvent, ISocketMessageChannel, SocketReaction, Task>>(); + public event Func, ISocketMessageChannel, SocketReaction, Task> ReactionRemoved { add { _reactionRemovedEvent.Add(value); } remove { _reactionRemovedEvent.Remove(value); } } - private readonly AsyncEvent, SocketReaction, Task>> _reactionRemovedEvent = new AsyncEvent, SocketReaction, Task>>(); - public event Func, Task> ReactionsCleared + private readonly AsyncEvent, ISocketMessageChannel, SocketReaction, Task>> _reactionRemovedEvent = new AsyncEvent, ISocketMessageChannel, SocketReaction, Task>>(); + public event Func, ISocketMessageChannel, Task> ReactionsCleared { add { _reactionsClearedEvent.Add(value); } remove { _reactionsClearedEvent.Remove(value); } } - private readonly AsyncEvent, Task>> _reactionsClearedEvent = new AsyncEvent, Task>>(); + private readonly AsyncEvent, ISocketMessageChannel, Task>> _reactionsClearedEvent = new AsyncEvent, ISocketMessageChannel, Task>>(); //Roles public event Func RoleCreated @@ -116,37 +116,37 @@ namespace Discord.WebSocket add { _joinedGuildEvent.Add(value); } remove { _joinedGuildEvent.Remove(value); } } - private AsyncEvent> _joinedGuildEvent = new AsyncEvent>(); + private readonly AsyncEvent> _joinedGuildEvent = new AsyncEvent>(); public event Func LeftGuild { add { _leftGuildEvent.Add(value); } remove { _leftGuildEvent.Remove(value); } } - private AsyncEvent> _leftGuildEvent = new AsyncEvent>(); + private readonly AsyncEvent> _leftGuildEvent = new AsyncEvent>(); public event Func GuildAvailable { add { _guildAvailableEvent.Add(value); } remove { _guildAvailableEvent.Remove(value); } } - private AsyncEvent> _guildAvailableEvent = new AsyncEvent>(); + private readonly AsyncEvent> _guildAvailableEvent = new AsyncEvent>(); public event Func GuildUnavailable { add { _guildUnavailableEvent.Add(value); } remove { _guildUnavailableEvent.Remove(value); } } - private AsyncEvent> _guildUnavailableEvent = new AsyncEvent>(); + private readonly AsyncEvent> _guildUnavailableEvent = new AsyncEvent>(); public event Func GuildMembersDownloaded { add { _guildMembersDownloadedEvent.Add(value); } remove { _guildMembersDownloadedEvent.Remove(value); } } - private AsyncEvent> _guildMembersDownloadedEvent = new AsyncEvent>(); + private readonly AsyncEvent> _guildMembersDownloadedEvent = new AsyncEvent>(); public event Func GuildUpdated { add { _guildUpdatedEvent.Add(value); } remove { _guildUpdatedEvent.Remove(value); } } - private AsyncEvent> _guildUpdatedEvent = new AsyncEvent>(); + private readonly AsyncEvent> _guildUpdatedEvent = new AsyncEvent>(); //Users public event Func UserJoined diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index d52de0af1..52d79a04b 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -1101,13 +1101,8 @@ namespace Discord.WebSocket return; } - SocketUser author; - if (guild != null) - author = guild.GetUser(data.Author.Value.Id); - else - author = (channel as SocketChannel).GetUser(data.Author.Value.Id); - if (author == null) - author = SocketSimpleUser.Create(this, State, data.Author.Value); + var author = (guild != null ? guild.GetUser(data.Author.Value.Id) : (channel as SocketChannel).GetUser(data.Author.Value.Id)) ?? + SocketSimpleUser.Create(this, State, data.Author.Value); if (author != null) { @@ -1138,14 +1133,15 @@ namespace Discord.WebSocket { var guild = (channel as SocketGuildChannel)?.Guild; if (guild != null && !guild.IsSynced) - { + { await _gatewayLogger.DebugAsync("Ignored MESSAGE_UPDATE, guild is not synced yet.").ConfigureAwait(false); return; } SocketMessage before = null, after = null; SocketMessage cachedMsg = channel.GetCachedMessage(data.Id); - if (cachedMsg != null) + bool isCached = cachedMsg != null; + if (isCached) { before = cachedMsg.Clone(); cachedMsg.Update(State, data); @@ -1164,10 +1160,9 @@ namespace Discord.WebSocket after = SocketMessage.Create(this, State, author, channel, data); } - if (before != null) - await _messageUpdatedEvent.InvokeAsync(before, after).ConfigureAwait(false); - else - await _messageUpdatedEvent.InvokeAsync(Optional.Create(), after).ConfigureAwait(false); + var cacheableBefore = new Cacheable(before, data.Id, isCached , async () => await channel.GetMessageAsync(data.Id)); + + await _messageUpdatedEvent.InvokeAsync(cacheableBefore, after, channel).ConfigureAwait(false); } else { @@ -1179,22 +1174,22 @@ namespace Discord.WebSocket case "MESSAGE_DELETE": { await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE)").ConfigureAwait(false); - + var data = (payload as JToken).ToObject(_serializer); var channel = State.GetChannel(data.ChannelId) as ISocketMessageChannel; if (channel != null) { if (!((channel as SocketGuildChannel)?.Guild.IsSynced ?? true)) - { + { await _gatewayLogger.DebugAsync("Ignored MESSAGE_DELETE, guild is not synced yet.").ConfigureAwait(false); return; } var msg = SocketChannelHelper.RemoveMessage(channel, this, data.Id); - if (msg != null) - await _messageDeletedEvent.InvokeAsync(data.Id, msg).ConfigureAwait(false); - else - await _messageDeletedEvent.InvokeAsync(data.Id, Optional.Create()).ConfigureAwait(false); + bool isCached = msg != null; + var cacheable = new Cacheable(msg, data.Id, isCached, async () => await channel.GetMessageAsync(data.Id)); + + await _messageDeletedEvent.InvokeAsync(cacheable, channel).ConfigureAwait(false); } else { @@ -1212,15 +1207,14 @@ namespace Discord.WebSocket if (channel != null) { SocketUserMessage cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; + bool isCached = cachedMsg != null; var user = await channel.GetUserAsync(data.UserId, CacheMode.CacheOnly); SocketReaction reaction = SocketReaction.Create(data, channel, cachedMsg, Optional.Create(user)); - if (cachedMsg != null) - { - cachedMsg.AddReaction(reaction); - await _reactionAddedEvent.InvokeAsync(data.MessageId, cachedMsg, reaction).ConfigureAwait(false); - return; - } - await _reactionAddedEvent.InvokeAsync(data.MessageId, Optional.Create(), reaction).ConfigureAwait(false); + var cacheable = new Cacheable(cachedMsg, data.MessageId, isCached, async () => await channel.GetMessageAsync(data.MessageId) as IUserMessage); + + cachedMsg?.AddReaction(reaction); + + await _reactionAddedEvent.InvokeAsync(cacheable, channel, reaction).ConfigureAwait(false); } else { @@ -1238,15 +1232,14 @@ namespace Discord.WebSocket if (channel != null) { SocketUserMessage cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; + bool isCached = cachedMsg != null; var user = await channel.GetUserAsync(data.UserId, CacheMode.CacheOnly); SocketReaction reaction = SocketReaction.Create(data, channel, cachedMsg, Optional.Create(user)); - if (cachedMsg != null) - { - cachedMsg.RemoveReaction(reaction); - await _reactionRemovedEvent.InvokeAsync(data.MessageId, cachedMsg, reaction).ConfigureAwait(false); - return; - } - await _reactionRemovedEvent.InvokeAsync(data.MessageId, Optional.Create(), reaction).ConfigureAwait(false); + var cacheable = new Cacheable(cachedMsg, data.MessageId, isCached, async () => await channel.GetMessageAsync(data.MessageId) as IUserMessage); + + cachedMsg?.RemoveReaction(reaction); + + await _reactionRemovedEvent.InvokeAsync(cacheable, channel, reaction).ConfigureAwait(false); } else { @@ -1264,20 +1257,18 @@ namespace Discord.WebSocket if (channel != null) { SocketUserMessage cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; - if (cachedMsg != null) - { - cachedMsg.ClearReactions(); - await _reactionsClearedEvent.InvokeAsync(data.MessageId, cachedMsg).ConfigureAwait(false); - return; - } - await _reactionsClearedEvent.InvokeAsync(data.MessageId, Optional.Create()); + bool isCached = cachedMsg != null; + var cacheable = new Cacheable(cachedMsg, data.MessageId, isCached, async () => await channel.GetMessageAsync(data.MessageId) as IUserMessage); + + cachedMsg?.ClearReactions(); + + await _reactionsClearedEvent.InvokeAsync(cacheable, channel).ConfigureAwait(false); } else { await _gatewayLogger.WarningAsync("MESSAGE_REACTION_REMOVE_ALL referenced an unknown channel.").ConfigureAwait(false); return; } - break; } case "MESSAGE_DELETE_BULK": @@ -1297,10 +1288,9 @@ namespace Discord.WebSocket foreach (var id in data.Ids) { var msg = SocketChannelHelper.RemoveMessage(channel, this, id); - if (msg != null) - await _messageDeletedEvent.InvokeAsync(id, msg).ConfigureAwait(false); - else - await _messageDeletedEvent.InvokeAsync(id, Optional.Create()).ConfigureAwait(false); + bool isCached = msg != null; + var cacheable = new Cacheable(msg, id, isCached, async () => await channel.GetMessageAsync(id)); + await _messageDeletedEvent.InvokeAsync(cacheable, channel).ConfigureAwait(false); } } else