diff --git a/src/Discord.Net.Rest/Entities/Invites/RestInviteMetadata.cs b/src/Discord.Net.Rest/Entities/Invites/RestInviteMetadata.cs index 55acd5f45..4e70326e7 100644 --- a/src/Discord.Net.Rest/Entities/Invites/RestInviteMetadata.cs +++ b/src/Discord.Net.Rest/Entities/Invites/RestInviteMetadata.cs @@ -36,6 +36,7 @@ namespace Discord.Rest entity.Update(model); return entity; } + internal void Update(Model model) { base.Update(model); diff --git a/src/Discord.Net.WebSocket/API/InviteEvent.cs b/src/Discord.Net.WebSocket/API/InviteEvent.cs new file mode 100644 index 000000000..432eb571f --- /dev/null +++ b/src/Discord.Net.WebSocket/API/InviteEvent.cs @@ -0,0 +1,13 @@ +using Newtonsoft.Json; + +namespace Discord.API +{ + internal class InviteEvent : InviteMetadata + { + [JsonProperty("channel_id")] + public ulong ChannelId { get; set; } + + [JsonProperty("guild_id")] + public ulong GuildId { get; set; } + } +} diff --git a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs index 908314f6a..24c5c7c51 100644 --- a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs +++ b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs @@ -235,6 +235,14 @@ namespace Discord.WebSocket } internal readonly AsyncEvent, ISocketMessageChannel, Task>> _reactionsClearedEvent = new AsyncEvent, ISocketMessageChannel, Task>>(); + /// Fired when all reactions of a specific reaction are removed. + public event Func, ISocketMessageChannel, SocketReaction, Task> ReactionsClearedEmoji { + add { _reactionsClearedEmojiEvent.Add(value); } + remove { _reactionsClearedEmojiEvent.Remove(value); } + } + internal readonly AsyncEvent, ISocketMessageChannel, SocketReaction, Task>> _reactionsClearedEmojiEvent = new AsyncEvent, ISocketMessageChannel, SocketReaction, Task>>(); + + //Roles /// Fired when a role is created. public event Func RoleCreated { @@ -293,6 +301,21 @@ namespace Discord.WebSocket } internal readonly AsyncEvent> _guildUpdatedEvent = new AsyncEvent>(); + //Invites + /// Fired when an invite is created. InviteCreated { + add { _inviteCreatedEvent.Add(value); } + remove { _inviteCreatedEvent.Remove(value); } + } + internal readonly AsyncEvent> _inviteCreatedEvent = new AsyncEvent>(); + /// Fired when an invite is deleted. + public event Func InviteDeleted + { + add { _inviteDeletedEvent.Add(value); } + remove { _inviteDeletedEvent.Remove(value); } + } + internal readonly AsyncEvent> _inviteDeletedEvent = new AsyncEvent>(); + //Users /// Fired when a user joins a guild. public event Func UserJoined { diff --git a/src/Discord.Net.WebSocket/DiscordShardedClient.cs b/src/Discord.Net.WebSocket/DiscordShardedClient.cs index 0877abfd9..15b2e6536 100644 --- a/src/Discord.Net.WebSocket/DiscordShardedClient.cs +++ b/src/Discord.Net.WebSocket/DiscordShardedClient.cs @@ -313,6 +313,7 @@ namespace Discord.WebSocket 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.ReactionsClearedEmoji += (cache, channel, reaction) => _reactionsClearedEmojiEvent.InvokeAsync(cache, channel, reaction); client.RoleCreated += (role) => _roleCreatedEvent.InvokeAsync(role); client.RoleDeleted += (role) => _roleDeletedEvent.InvokeAsync(role); @@ -325,6 +326,9 @@ namespace Discord.WebSocket client.GuildMembersDownloaded += (guild) => _guildMembersDownloadedEvent.InvokeAsync(guild); client.GuildUpdated += (oldGuild, newGuild) => _guildUpdatedEvent.InvokeAsync(oldGuild, newGuild); + client.InviteCreated += (invite) => _inviteCreatedEvent.InvokeAsync(invite); + client.InviteDeleted += (invite) => _inviteDeletedEvent.InvokeAsync(invite); + client.UserJoined += (user) => _userJoinedEvent.InvokeAsync(user); client.UserLeft += (user) => _userLeftEvent.InvokeAsync(user); client.UserBanned += (user, guild) => _userBannedEvent.InvokeAsync(user, guild); diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index ed142d001..ae2856044 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -882,7 +882,6 @@ namespace Discord.WebSocket await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); return; } - if (user != null) { var before = user.Clone(); @@ -1213,7 +1212,6 @@ namespace Discord.WebSocket case "MESSAGE_UPDATE": { await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_UPDATE)").ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) { @@ -1378,6 +1376,41 @@ namespace Discord.WebSocket } } break; + + case "MESSAGE_REACTION_REMOVE_EMOJI": + { + 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 user = await channel.GetUserAsync(data.UserId, CacheMode.CacheOnly).ConfigureAwait(false); + + var optionalMsg = !isCached + ? Optional.Create() + : Optional.Create(cachedMsg); + + var optionalUser = user is null + ? Optional.Create() + : Optional.Create(user); + + 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); + + cachedMsg?.ClearReactionsEmoji(reaction); + + await TimedInvokeAsync(_reactionsClearedEmojiEvent, nameof(ReactionsClearedEmoji), cacheable, channel, reaction).ConfigureAwait(false); + } + else + { + await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); + return; + } + } + break; + case "MESSAGE_DELETE_BULK": { await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE_BULK)").ConfigureAwait(false); @@ -1422,6 +1455,53 @@ namespace Discord.WebSocket } break; + //Invites + case "INVITE_CREATE": + { + await _gatewayLogger.DebugAsync("Received Dispatch (INVITE_CREATE)").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 inviter = State.GetUser(data.Inviter.Id) as SocketUser; + + var invite = SocketInvite.Create(this, data, inviter, guild, channel); + + await TimedInvokeAsync(_inviteCreatedEvent, nameof(InviteCreated), invite); + } + else + await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); + } + break; + + case "INVITE_DELETE": + { + await _gatewayLogger.DebugAsync("Received Dispatch (INVITE_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 invite = SocketInvite.Create(this, data, guild, channel); + + await TimedInvokeAsync(_inviteDeletedEvent, nameof(InviteDeleted), invite).ConfigureAwait(false); + } + else + await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); + } + break; + //Statuses case "PRESENCE_UPDATE": { diff --git a/src/Discord.Net.WebSocket/Entities/Invites/SocketInvite.cs b/src/Discord.Net.WebSocket/Entities/Invites/SocketInvite.cs new file mode 100644 index 000000000..1d0d77c29 --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Invites/SocketInvite.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Model = Discord.API.InviteEvent; + +namespace Discord.WebSocket +{ + public class SocketInvite : SocketEntity + { + public ISocketMessageChannel Channel { get; private set; } + + public ulong ChannelId { get; private set; } + + public IGuild Guild { get; private set; } + + public ulong GuildId { get; private set; } + + public string Code { get; private set; } + + public SocketUser Inviter { get; private set; } + + public DateTimeOffset CreatedAt { get; private set; } + + public int MaxAge { get; private set; } + + public int MaxUses { get; private set; } + + public int Uses { get; set; } + + public bool Temporary { get; private set; } + + internal SocketInvite(DiscordSocketClient discord, Model model) + : base(discord, model.Code) + { + } + + internal static SocketInvite Create(DiscordSocketClient discord, Model model, SocketUser inviter, IGuild guild, ISocketMessageChannel channel) + { + var entity = new SocketInvite(discord, model); + entity.Update(model, inviter, guild, channel); + return entity; + } + + internal static SocketInvite Create(DiscordSocketClient discord, Model model, IGuild guild, ISocketMessageChannel channel) + { + var entity = new SocketInvite(discord, model); + entity.Update(model.Code, guild, channel); + return entity; + } + + internal void Update(Model model, SocketUser inviter, IGuild guild, ISocketMessageChannel channel) + { + Channel = channel; + ChannelId = model.ChannelId; + Guild = guild; + GuildId = model.GuildId; + Code = model.Code; + Inviter = inviter; + CreatedAt = model.CreatedAt.Value; + MaxAge = model.MaxAge.Value; + MaxUses = model.MaxUses.Value; + Uses = model.Uses.Value; + Temporary = model.Temporary; + } + + internal void Update(string code, IGuild guild, ISocketMessageChannel channel) + { + Code = code; + Guild = guild; + GuildId = guild.Id; + Channel = channel; + ChannelId = channel.Id; + } + + + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs index 7900b7ee7..b472e063a 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs @@ -197,9 +197,10 @@ namespace Discord.WebSocket _reactions.Remove(reaction); } internal void ClearReactions() - { - _reactions.Clear(); - } + => _reactions.Clear(); + + internal void ClearReactionsEmoji(SocketReaction reaction) + => _reactions.RemoveAll(r => r.Emote.Equals(reaction)); /// public Task AddReactionAsync(IEmote emote, RequestOptions options = null)