| @@ -36,6 +36,7 @@ namespace Discord.Rest | |||||
| entity.Update(model); | entity.Update(model); | ||||
| return entity; | return entity; | ||||
| } | } | ||||
| internal void Update(Model model) | internal void Update(Model model) | ||||
| { | { | ||||
| base.Update(model); | base.Update(model); | ||||
| @@ -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; } | |||||
| } | |||||
| } | |||||
| @@ -235,6 +235,14 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| internal readonly AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, Task>> _reactionsClearedEvent = new AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, Task>>(); | internal readonly AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, Task>> _reactionsClearedEvent = new AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, Task>>(); | ||||
| /// <summary> Fired when all reactions of a specific reaction are removed.</summary> | |||||
| public event Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task> ReactionsClearedEmoji { | |||||
| add { _reactionsClearedEmojiEvent.Add(value); } | |||||
| remove { _reactionsClearedEmojiEvent.Remove(value); } | |||||
| } | |||||
| internal readonly AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task>> _reactionsClearedEmojiEvent = new AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task>>(); | |||||
| //Roles | //Roles | ||||
| /// <summary> Fired when a role is created. </summary> | /// <summary> Fired when a role is created. </summary> | ||||
| public event Func<SocketRole, Task> RoleCreated { | public event Func<SocketRole, Task> RoleCreated { | ||||
| @@ -293,6 +301,21 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| internal readonly AsyncEvent<Func<SocketGuild, SocketGuild, Task>> _guildUpdatedEvent = new AsyncEvent<Func<SocketGuild, SocketGuild, Task>>(); | internal readonly AsyncEvent<Func<SocketGuild, SocketGuild, Task>> _guildUpdatedEvent = new AsyncEvent<Func<SocketGuild, SocketGuild, Task>>(); | ||||
| //Invites | |||||
| /// <summary> Fired when an invite is created.</summary | |||||
| public event Func<SocketInvite, Task> InviteCreated { | |||||
| add { _inviteCreatedEvent.Add(value); } | |||||
| remove { _inviteCreatedEvent.Remove(value); } | |||||
| } | |||||
| internal readonly AsyncEvent<Func<SocketInvite, Task>> _inviteCreatedEvent = new AsyncEvent<Func<SocketInvite, Task>>(); | |||||
| /// <summary>Fired when an invite is deleted.</summary> | |||||
| public event Func<SocketInvite, Task> InviteDeleted | |||||
| { | |||||
| add { _inviteDeletedEvent.Add(value); } | |||||
| remove { _inviteDeletedEvent.Remove(value); } | |||||
| } | |||||
| internal readonly AsyncEvent<Func<SocketInvite, Task>> _inviteDeletedEvent = new AsyncEvent<Func<SocketInvite, Task>>(); | |||||
| //Users | //Users | ||||
| /// <summary> Fired when a user joins a guild. </summary> | /// <summary> Fired when a user joins a guild. </summary> | ||||
| public event Func<SocketGuildUser, Task> UserJoined { | public event Func<SocketGuildUser, Task> UserJoined { | ||||
| @@ -313,6 +313,7 @@ namespace Discord.WebSocket | |||||
| client.ReactionAdded += (cache, channel, reaction) => _reactionAddedEvent.InvokeAsync(cache, channel, reaction); | client.ReactionAdded += (cache, channel, reaction) => _reactionAddedEvent.InvokeAsync(cache, channel, reaction); | ||||
| client.ReactionRemoved += (cache, channel, reaction) => _reactionRemovedEvent.InvokeAsync(cache, channel, reaction); | client.ReactionRemoved += (cache, channel, reaction) => _reactionRemovedEvent.InvokeAsync(cache, channel, reaction); | ||||
| client.ReactionsCleared += (cache, channel) => _reactionsClearedEvent.InvokeAsync(cache, channel); | 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.RoleCreated += (role) => _roleCreatedEvent.InvokeAsync(role); | ||||
| client.RoleDeleted += (role) => _roleDeletedEvent.InvokeAsync(role); | client.RoleDeleted += (role) => _roleDeletedEvent.InvokeAsync(role); | ||||
| @@ -325,6 +326,9 @@ namespace Discord.WebSocket | |||||
| client.GuildMembersDownloaded += (guild) => _guildMembersDownloadedEvent.InvokeAsync(guild); | client.GuildMembersDownloaded += (guild) => _guildMembersDownloadedEvent.InvokeAsync(guild); | ||||
| client.GuildUpdated += (oldGuild, newGuild) => _guildUpdatedEvent.InvokeAsync(oldGuild, newGuild); | 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.UserJoined += (user) => _userJoinedEvent.InvokeAsync(user); | ||||
| client.UserLeft += (user) => _userLeftEvent.InvokeAsync(user); | client.UserLeft += (user) => _userLeftEvent.InvokeAsync(user); | ||||
| client.UserBanned += (user, guild) => _userBannedEvent.InvokeAsync(user, guild); | client.UserBanned += (user, guild) => _userBannedEvent.InvokeAsync(user, guild); | ||||
| @@ -882,7 +882,6 @@ namespace Discord.WebSocket | |||||
| await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); | await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); | ||||
| return; | return; | ||||
| } | } | ||||
| if (user != null) | if (user != null) | ||||
| { | { | ||||
| var before = user.Clone(); | var before = user.Clone(); | ||||
| @@ -1213,7 +1212,6 @@ namespace Discord.WebSocket | |||||
| case "MESSAGE_UPDATE": | case "MESSAGE_UPDATE": | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_UPDATE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_UPDATE)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<API.Message>(_serializer); | var data = (payload as JToken).ToObject<API.Message>(_serializer); | ||||
| if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | ||||
| { | { | ||||
| @@ -1378,6 +1376,41 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| } | } | ||||
| break; | break; | ||||
| case "MESSAGE_REACTION_REMOVE_EMOJI": | |||||
| { | |||||
| await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_REMOVE_EMOJI)").ConfigureAwait(false); | |||||
| var data = (payload as JToken).ToObject<API.Gateway.Reaction>(_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<SocketUserMessage>() | |||||
| : Optional.Create(cachedMsg); | |||||
| var optionalUser = user is null | |||||
| ? Optional.Create<IUser>() | |||||
| : Optional.Create(user); | |||||
| var reaction = SocketReaction.Create(data, channel, optionalMsg, optionalUser); | |||||
| var cacheable = new Cacheable<IUserMessage, ulong>(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": | case "MESSAGE_DELETE_BULK": | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE_BULK)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE_BULK)").ConfigureAwait(false); | ||||
| @@ -1422,6 +1455,53 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| break; | break; | ||||
| //Invites | |||||
| case "INVITE_CREATE": | |||||
| { | |||||
| await _gatewayLogger.DebugAsync("Received Dispatch (INVITE_CREATE)").ConfigureAwait(false); | |||||
| var data = (payload as JToken).ToObject<InviteEvent>(_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<InviteEvent>(_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 | //Statuses | ||||
| case "PRESENCE_UPDATE": | case "PRESENCE_UPDATE": | ||||
| { | { | ||||
| @@ -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<string> | |||||
| { | |||||
| 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; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -197,9 +197,10 @@ namespace Discord.WebSocket | |||||
| _reactions.Remove(reaction); | _reactions.Remove(reaction); | ||||
| } | } | ||||
| internal void ClearReactions() | internal void ClearReactions() | ||||
| { | |||||
| _reactions.Clear(); | |||||
| } | |||||
| => _reactions.Clear(); | |||||
| internal void ClearReactionsEmoji(SocketReaction reaction) | |||||
| => _reactions.RemoveAll(r => r.Emote.Equals(reaction)); | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public Task AddReactionAsync(IEmote emote, RequestOptions options = null) | public Task AddReactionAsync(IEmote emote, RequestOptions options = null) | ||||