| @@ -6,7 +6,7 @@ namespace Discord.API | |||
| public class Emoji | |||
| { | |||
| [JsonProperty("id")] | |||
| public ulong Id { get; set; } | |||
| public ulong? Id { get; set; } | |||
| [JsonProperty("name")] | |||
| public string Name { get; set; } | |||
| [JsonProperty("roles")] | |||
| @@ -36,5 +36,7 @@ namespace Discord.API | |||
| public Optional<Embed[]> Embeds { get; set; } | |||
| [JsonProperty("pinned")] | |||
| public Optional<bool> Pinned { get; set; } | |||
| [JsonProperty("reactions")] | |||
| public Optional<Reaction[]> Reactions { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,18 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Threading.Tasks; | |||
| using Newtonsoft.Json; | |||
| namespace Discord.API | |||
| { | |||
| public class Reaction | |||
| { | |||
| [JsonProperty("count")] | |||
| public int Count { get; set; } | |||
| [JsonProperty("me")] | |||
| public bool Me { get; set; } | |||
| [JsonProperty("emoji")] | |||
| public Emoji Emoji { get; set; } | |||
| } | |||
| } | |||
| @@ -515,6 +515,59 @@ namespace Discord.API | |||
| var ids = new BucketIds(channelId: channelId); | |||
| return await SendJsonAsync<Message>("PATCH", () => $"channels/{channelId}/messages/{messageId}", args, ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false); | |||
| } | |||
| public async Task AddReactionAsync(ulong channelId, ulong messageId, string emoji, RequestOptions options = null) | |||
| { | |||
| Preconditions.NotEqual(channelId, 0, nameof(channelId)); | |||
| Preconditions.NotEqual(messageId, 0, nameof(messageId)); | |||
| Preconditions.NotNullOrWhitespace(emoji, nameof(emoji)); | |||
| options = RequestOptions.CreateOrClone(options); | |||
| var ids = new BucketIds(channelId: channelId); | |||
| await SendAsync("PUT", () => $"channels/{channelId}/messages/{messageId}/reactions/{emoji}/@me", ids, options: options).ConfigureAwait(false); | |||
| } | |||
| public async Task RemoveReactionAsync(ulong channelId, ulong messageId, ulong userId, string emoji, RequestOptions options = null) | |||
| { | |||
| Preconditions.NotEqual(channelId, 0, nameof(channelId)); | |||
| Preconditions.NotEqual(messageId, 0, nameof(messageId)); | |||
| Preconditions.NotNullOrWhitespace(emoji, nameof(emoji)); | |||
| options = RequestOptions.CreateOrClone(options); | |||
| var ids = new BucketIds(channelId: channelId); | |||
| await SendAsync("DELETE", () => $"channels/{channelId}/messages/{messageId}/reactions/{emoji}/{userId}", ids, options: options).ConfigureAwait(false); | |||
| } | |||
| public async Task RemoveAllReactionsAsync(ulong channelId, ulong messageId, RequestOptions options = null) | |||
| { | |||
| Preconditions.NotEqual(channelId, 0, nameof(channelId)); | |||
| Preconditions.NotEqual(messageId, 0, nameof(messageId)); | |||
| options = RequestOptions.CreateOrClone(options); | |||
| var ids = new BucketIds(channelId: channelId); | |||
| await SendAsync("DELETE", () => $"channels/{channelId}/messages/{messageId}/reactions", ids, options: options).ConfigureAwait(false); | |||
| } | |||
| public async Task<IReadOnlyCollection<User>> GetReactionUsersAsync(ulong channelId, ulong messageId, string emoji, GetReactionUsersParams args, RequestOptions options = null) | |||
| { | |||
| Preconditions.NotEqual(channelId, 0, nameof(channelId)); | |||
| Preconditions.NotEqual(messageId, 0, nameof(messageId)); | |||
| Preconditions.NotNullOrWhitespace(emoji, nameof(emoji)); | |||
| Preconditions.NotNull(args, nameof(args)); | |||
| Preconditions.GreaterThan(args.Limit, 0, nameof(args.Limit)); | |||
| Preconditions.AtMost(args.Limit, DiscordConfig.MaxUsersPerBatch, nameof(args.Limit)); | |||
| Preconditions.GreaterThan(args.AfterUserId, 0, nameof(args.AfterUserId)); | |||
| options = RequestOptions.CreateOrClone(options); | |||
| int limit = args.Limit.GetValueOrDefault(int.MaxValue); | |||
| ulong afterUserId = args.AfterUserId.GetValueOrDefault(0); | |||
| var ids = new BucketIds(channelId: channelId); | |||
| Expression<Func<string>> endpoint = () => $"channels/{channelId}/messages/{messageId}/reactions/{emoji}"; | |||
| return await SendAsync<IReadOnlyCollection<User>>("GET", endpoint, ids, options: options).ConfigureAwait(false); | |||
| } | |||
| public async Task AckMessageAsync(ulong channelId, ulong messageId, RequestOptions options = null) | |||
| { | |||
| Preconditions.NotEqual(channelId, 0, nameof(channelId)); | |||
| @@ -0,0 +1,8 @@ | |||
| namespace Discord.API.Rest | |||
| { | |||
| public class GetReactionUsersParams | |||
| { | |||
| public Optional<int> Limit { get; set; } | |||
| public Optional<ulong> AfterUserId { get; set; } | |||
| } | |||
| } | |||
| @@ -24,7 +24,7 @@ namespace Discord | |||
| } | |||
| internal static GuildEmoji Create(Model model) | |||
| { | |||
| return new GuildEmoji(model.Id, model.Name, model.Managed, model.RequireColons, ImmutableArray.Create(model.Roles)); | |||
| return new GuildEmoji(model.Id.Value, model.Name, model.Managed, model.RequireColons, ImmutableArray.Create(model.Roles)); | |||
| } | |||
| public override string ToString() => Name; | |||
| @@ -19,6 +19,11 @@ namespace Discord | |||
| Name = name; | |||
| } | |||
| internal static Emoji FromApi(API.Emoji emoji) | |||
| { | |||
| return new Emoji(emoji.Id.GetValueOrDefault(), emoji.Name); | |||
| } | |||
| public static Emoji Parse(string text) | |||
| { | |||
| Emoji result; | |||
| @@ -0,0 +1,12 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Threading.Tasks; | |||
| namespace Discord | |||
| { | |||
| public interface IReaction | |||
| { | |||
| Emoji Emoji { get; } | |||
| } | |||
| } | |||
| @@ -1,4 +1,5 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Threading.Tasks; | |||
| namespace Discord | |||
| @@ -11,7 +12,22 @@ namespace Discord | |||
| Task PinAsync(RequestOptions options = null); | |||
| /// <summary> Removes this message from its channel's pinned messages. </summary> | |||
| Task UnpinAsync(RequestOptions options = null); | |||
| /// <summary> Returns all reactions included in this message. </summary> | |||
| IReadOnlyDictionary<Emoji, int> Reactions { get; } | |||
| /// <summary> Adds a reaction to this message. </summary> | |||
| Task AddReactionAsync(Emoji emoji, RequestOptions options = null); | |||
| /// <summary> Adds a reaction to this message. </summary> | |||
| Task AddReactionAsync(string emoji, RequestOptions options = null); | |||
| /// <summary> Removes a reaction from message. </summary> | |||
| Task RemoveReactionAsync(Emoji emoji, IUser user, RequestOptions options = null); | |||
| /// <summary> Removes a reaction from this message. </summary> | |||
| Task RemoveReactionAsync(string emoji, IUser user, RequestOptions options = null); | |||
| /// <summary> Removes all reactions from this message. </summary> | |||
| Task RemoveAllReactionsAsync(RequestOptions options = null); | |||
| Task<IReadOnlyCollection<IUser>> GetReactionUsersAsync(string emoji, int limit = 100, ulong? afterUserId = null, RequestOptions options = null); | |||
| /// <summary> Transforms this message's text into a human readable form by resolving its tags. </summary> | |||
| string Resolve( | |||
| TagHandling userHandling = TagHandling.Name, | |||
| @@ -11,6 +11,7 @@ | |||
| //ManageGuild = 5, | |||
| //Text | |||
| AddReactions = 6, | |||
| ReadMessages = 10, | |||
| SendMessages = 11, | |||
| SendTTSMessages = 12, | |||
| @@ -10,7 +10,7 @@ namespace Discord | |||
| //TODO: C#7 Candidate for binary literals | |||
| private static ChannelPermissions _allDM { get; } = new ChannelPermissions(Convert.ToUInt64("00000000000001011100110000000000", 2)); | |||
| private static ChannelPermissions _allVoice { get; } = new ChannelPermissions(Convert.ToUInt64("00010011111100000000000000010001", 2)); | |||
| private static ChannelPermissions _allText { get; } = new ChannelPermissions(Convert.ToUInt64("00010000000001111111110000010001", 2)); | |||
| private static ChannelPermissions _allText { get; } = new ChannelPermissions(Convert.ToUInt64("00010000000001111111110001010001", 2)); | |||
| private static ChannelPermissions _allGroup { get; } = new ChannelPermissions(Convert.ToUInt64("00000000000001111110110000000000", 2)); | |||
| /// <summary> Gets a blank ChannelPermissions that grants no permissions. </summary> | |||
| @@ -35,6 +35,8 @@ namespace Discord | |||
| /// <summary> If True, a user may create, delete and modify this channel. </summary> | |||
| public bool ManageChannel => Permissions.GetValue(RawValue, ChannelPermission.ManageChannel); | |||
| /// <summary> If true, a user may add reactions. </summary> | |||
| public bool AddReactions => Permissions.GetValue(RawValue, ChannelPermission.AddReactions); | |||
| /// <summary> If True, a user may join channels. </summary> | |||
| public bool ReadMessages => Permissions.GetValue(RawValue, ChannelPermission.ReadMessages); | |||
| /// <summary> If True, a user may send messages. </summary> | |||
| @@ -11,6 +11,7 @@ | |||
| ManageGuild = 5, | |||
| //Text | |||
| AddReactions = 6, | |||
| ReadMessages = 10, | |||
| SendMessages = 11, | |||
| SendTTSMessages = 12, | |||
| @@ -11,7 +11,7 @@ namespace Discord | |||
| public static readonly GuildPermissions None = new GuildPermissions(); | |||
| /// <summary> Gets a GuildPermissions that grants all permissions. </summary> | |||
| //TODO: C#7 Candidate for binary literals | |||
| public static readonly GuildPermissions All = new GuildPermissions(Convert.ToUInt64("01111111111100111111110000111111", 2)); | |||
| public static readonly GuildPermissions All = new GuildPermissions(Convert.ToUInt64("01111111111100111111110001111111", 2)); | |||
| /// <summary> Gets a packed value representing all the permissions in this GuildPermissions. </summary> | |||
| public ulong RawValue { get; } | |||
| @@ -28,7 +28,9 @@ namespace Discord | |||
| public bool ManageChannels => Permissions.GetValue(RawValue, GuildPermission.ManageChannels); | |||
| /// <summary> If True, a user may adjust guild properties. </summary> | |||
| public bool ManageGuild => Permissions.GetValue(RawValue, GuildPermission.ManageGuild); | |||
| /// <summary> If true, a user may add reactions. </summary> | |||
| public bool AddReactions => Permissions.GetValue(RawValue, GuildPermission.AddReactions); | |||
| /// <summary> If True, a user may join channels. </summary> | |||
| public bool ReadMessages => Permissions.GetValue(RawValue, GuildPermission.ReadMessages); | |||
| /// <summary> If True, a user may send messages. </summary> | |||
| @@ -16,7 +16,7 @@ namespace Discord.Net.Converters | |||
| { | |||
| object value = reader.Value; | |||
| if (value != null) | |||
| return ulong.Parse((string)value, NumberStyles.None, CultureInfo.InvariantCulture); | |||
| return ulong.Parse(value.ToString(), NumberStyles.None, CultureInfo.InvariantCulture); | |||
| else | |||
| return null; | |||
| } | |||
| @@ -43,6 +43,7 @@ namespace Discord | |||
| if (obj.Value.Trim().Length == 0) throw CreateNotEmptyException(name, msg); | |||
| } | |||
| } | |||
| private static ArgumentException CreateNotEmptyException(string name, string msg) | |||
| { | |||
| @@ -28,6 +28,34 @@ namespace Discord.Rest | |||
| await client.ApiClient.DeleteMessageAsync(msg.Channel.Id, msg.Id, options).ConfigureAwait(false); | |||
| } | |||
| public static async Task AddReactionAsync(IMessage msg, Emoji emoji, BaseDiscordClient client, RequestOptions options) | |||
| => await AddReactionAsync(msg, $"{emoji.Name}:{emoji.Id}", client, options).ConfigureAwait(false); | |||
| public static async Task AddReactionAsync(IMessage msg, string emoji, BaseDiscordClient client, RequestOptions options) | |||
| { | |||
| await client.ApiClient.AddReactionAsync(msg.Channel.Id, msg.Id, emoji, options).ConfigureAwait(false); | |||
| } | |||
| public static async Task RemoveReactionAsync(IMessage msg, IUser user, Emoji emoji, BaseDiscordClient client, RequestOptions options) | |||
| => await RemoveReactionAsync(msg, user, $"{emoji.Name}:{emoji.Id}", client, options).ConfigureAwait(false); | |||
| public static async Task RemoveReactionAsync(IMessage msg, IUser user, string emoji, BaseDiscordClient client, | |||
| RequestOptions options) | |||
| { | |||
| await client.ApiClient.RemoveReactionAsync(msg.Channel.Id, msg.Id, user.Id, emoji, options).ConfigureAwait(false); | |||
| } | |||
| public static async Task RemoveAllReactionsAsync(IMessage msg, BaseDiscordClient client, RequestOptions options) | |||
| { | |||
| await client.ApiClient.RemoveAllReactionsAsync(msg.Channel.Id, msg.Id, options); | |||
| } | |||
| public static async Task<IReadOnlyCollection<IUser>> GetReactionUsersAsync(IMessage msg, string emoji, | |||
| Action<GetReactionUsersParams> func, BaseDiscordClient client, RequestOptions options) | |||
| { | |||
| var args = new GetReactionUsersParams(); | |||
| func(args); | |||
| return (await client.ApiClient.GetReactionUsersAsync(msg.Channel.Id, msg.Id, emoji, args, options).ConfigureAwait(false)).Select(u => u as IUser).Where(u => u != null).ToImmutableArray(); | |||
| } | |||
| public static async Task PinAsync(IMessage msg, BaseDiscordClient client, | |||
| RequestOptions options) | |||
| { | |||
| @@ -0,0 +1,22 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Threading.Tasks; | |||
| using Model = Discord.API.Reaction; | |||
| namespace Discord | |||
| { | |||
| public class RestReaction : IReaction | |||
| { | |||
| internal RestReaction(Model model) | |||
| { | |||
| Emoji = Emoji.FromApi(model.Emoji); | |||
| Count = model.Count; | |||
| Me = model.Me; | |||
| } | |||
| public Emoji Emoji { get; private set; } | |||
| public int Count { get; private set; } | |||
| public bool Me { get; private set; } | |||
| } | |||
| } | |||
| @@ -17,6 +17,7 @@ namespace Discord.Rest | |||
| private ImmutableArray<Attachment> _attachments; | |||
| private ImmutableArray<Embed> _embeds; | |||
| private ImmutableArray<ITag> _tags; | |||
| private ImmutableArray<RestReaction> _reactions; | |||
| public override bool IsTTS => _isTTS; | |||
| public override bool IsPinned => _isPinned; | |||
| @@ -28,6 +29,7 @@ namespace Discord.Rest | |||
| public override IReadOnlyCollection<ulong> MentionedRoleIds => MessageHelper.FilterTagsByKey(TagType.RoleMention, _tags); | |||
| public override IReadOnlyCollection<RestUser> MentionedUsers => MessageHelper.FilterTagsByValue<RestUser>(TagType.UserMention, _tags); | |||
| public override IReadOnlyCollection<ITag> Tags => _tags; | |||
| public IReadOnlyDictionary<Emoji, int> Reactions => _reactions.ToDictionary(x => x.Emoji, x => x.Count); | |||
| internal RestUserMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, IUser author) | |||
| : base(discord, id, channel, author) | |||
| @@ -100,6 +102,20 @@ namespace Discord.Rest | |||
| } | |||
| } | |||
| if (model.Reactions.IsSpecified) | |||
| { | |||
| var value = model.Reactions.Value; | |||
| if (value.Length > 0) | |||
| { | |||
| var reactions = ImmutableArray.CreateBuilder<RestReaction>(value.Length); | |||
| for (int i = 0; i < value.Length; i++) | |||
| reactions.Add(new RestReaction(value[i])); | |||
| _reactions = reactions.ToImmutable(); | |||
| } | |||
| else | |||
| _reactions = ImmutableArray.Create<RestReaction>(); | |||
| } | |||
| if (model.Content.IsSpecified) | |||
| { | |||
| var text = model.Content.Value; | |||
| @@ -116,6 +132,23 @@ namespace Discord.Rest | |||
| Update(model); | |||
| } | |||
| public Task AddReactionAsync(Emoji emoji, RequestOptions options = null) | |||
| => MessageHelper.AddReactionAsync(this, emoji, Discord, options); | |||
| public Task AddReactionAsync(string emoji, RequestOptions options = null) | |||
| => MessageHelper.AddReactionAsync(this, emoji, Discord, options); | |||
| public Task RemoveReactionAsync(Emoji emoji, IUser user, RequestOptions options = null) | |||
| => MessageHelper.RemoveReactionAsync(this, user, emoji, Discord, options); | |||
| public Task RemoveReactionAsync(string emoji, IUser user, RequestOptions options = null) | |||
| => MessageHelper.RemoveReactionAsync(this, user, emoji, Discord, options); | |||
| public Task RemoveAllReactionsAsync(RequestOptions options = null) | |||
| => MessageHelper.RemoveAllReactionsAsync(this, Discord, options); | |||
| public Task<IReadOnlyCollection<IUser>> GetReactionUsersAsync(string emoji, int limit = 100, ulong? afterUserId = null, RequestOptions options = null) | |||
| => MessageHelper.GetReactionUsersAsync(this, emoji, x => { x.Limit = limit; x.AfterUserId = afterUserId.HasValue ? afterUserId.Value : Optional.Create<ulong>(); }, Discord, options); | |||
| public Task PinAsync(RequestOptions options) | |||
| => MessageHelper.PinAsync(this, Discord, options); | |||
| public Task UnpinAsync(RequestOptions options) | |||
| @@ -30,6 +30,7 @@ namespace Discord.Rpc | |||
| public override IReadOnlyCollection<ulong> MentionedRoleIds => MessageHelper.FilterTagsByKey(TagType.RoleMention, _tags); | |||
| public override IReadOnlyCollection<ulong> MentionedUserIds => MessageHelper.FilterTagsByKey(TagType.UserMention, _tags); | |||
| public override IReadOnlyCollection<ITag> Tags => _tags; | |||
| public IReadOnlyDictionary<Emoji, int> Reactions => ImmutableDictionary.Create<Emoji, int>(); | |||
| internal RpcUserMessage(DiscordRpcClient discord, ulong id, IMessageChannel channel, RpcUser author) | |||
| : base(discord, id, channel, author) | |||
| @@ -101,6 +102,22 @@ namespace Discord.Rpc | |||
| public Task ModifyAsync(Action<ModifyMessageParams> func, RequestOptions options) | |||
| => MessageHelper.ModifyAsync(this, Discord, func, options); | |||
| public Task AddReactionAsync(Emoji emoji, RequestOptions options = null) | |||
| => MessageHelper.AddReactionAsync(this, emoji, Discord, options); | |||
| public Task AddReactionAsync(string emoji, RequestOptions options = null) | |||
| => MessageHelper.AddReactionAsync(this, emoji, Discord, options); | |||
| public Task RemoveReactionAsync(Emoji emoji, IUser user, RequestOptions options = null) | |||
| => MessageHelper.RemoveReactionAsync(this, user, emoji, Discord, options); | |||
| public Task RemoveReactionAsync(string emoji, IUser user, RequestOptions options = null) | |||
| => MessageHelper.RemoveReactionAsync(this, user, emoji, Discord, options); | |||
| public Task RemoveAllReactionsAsync(RequestOptions options = null) | |||
| => MessageHelper.RemoveAllReactionsAsync(this, Discord, options); | |||
| public Task<IReadOnlyCollection<IUser>> GetReactionUsersAsync(string emoji, int limit, ulong? afterUserId, RequestOptions options = null) | |||
| => MessageHelper.GetReactionUsersAsync(this, emoji, x => { x.Limit = limit; x.AfterUserId = afterUserId.HasValue ? afterUserId.Value : Optional.Create<ulong>(); }, Discord, options); | |||
| public Task PinAsync(RequestOptions options) | |||
| => MessageHelper.PinAsync(this, Discord, options); | |||
| public Task UnpinAsync(RequestOptions options) | |||
| @@ -0,0 +1,20 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Threading.Tasks; | |||
| using Newtonsoft.Json; | |||
| namespace Discord.API.Gateway | |||
| { | |||
| public class GatewayReaction | |||
| { | |||
| [JsonProperty("user_id")] | |||
| public ulong UserId { get; set; } | |||
| [JsonProperty("message_id")] | |||
| public ulong MessageId { get; set; } | |||
| [JsonProperty("channel_id")] | |||
| public ulong ChannelId { get; set; } | |||
| [JsonProperty("emoji")] | |||
| public Emoji Emoji { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,12 @@ | |||
| using Newtonsoft.Json; | |||
| namespace Discord.API.Gateway | |||
| { | |||
| public class RemoveAllReactionsEvent | |||
| { | |||
| [JsonProperty("channel_id")] | |||
| public ulong ChannelId { get; set; } | |||
| [JsonProperty("message_id")] | |||
| public ulong MessageId { get; set; } | |||
| } | |||
| } | |||
| @@ -71,6 +71,24 @@ namespace Discord.WebSocket | |||
| remove { _messageUpdatedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<Optional<SocketMessage>, SocketMessage, Task>> _messageUpdatedEvent = new AsyncEvent<Func<Optional<SocketMessage>, SocketMessage, Task>>(); | |||
| public event Func<ulong, Optional<SocketUserMessage>, SocketReaction, Task> ReactionAdded | |||
| { | |||
| add { _reactionAddedEvent.Add(value); } | |||
| remove { _reactionAddedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<ulong, Optional<SocketUserMessage>, SocketReaction, Task>> _reactionAddedEvent = new AsyncEvent<Func<ulong, Optional<SocketUserMessage>, SocketReaction, Task>>(); | |||
| public event Func<ulong, Optional<SocketUserMessage>, SocketReaction, Task> ReactionRemoved | |||
| { | |||
| add { _reactionRemovedEvent.Add(value); } | |||
| remove { _reactionRemovedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<ulong, Optional<SocketUserMessage>, SocketReaction, Task>> _reactionRemovedEvent = new AsyncEvent<Func<ulong, Optional<SocketUserMessage>, SocketReaction, Task>>(); | |||
| public event Func<ulong, Optional<SocketUserMessage>, Task> ReactionsCleared | |||
| { | |||
| add { _reactionsClearedEvent.Add(value); } | |||
| remove { _reactionsClearedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<ulong, Optional<SocketUserMessage>, Task>> _reactionsClearedEvent = new AsyncEvent<Func<ulong, Optional<SocketUserMessage>, Task>>(); | |||
| //Roles | |||
| public event Func<SocketRole, Task> RoleCreated | |||
| @@ -1308,6 +1308,84 @@ namespace Discord.WebSocket | |||
| } | |||
| } | |||
| break; | |||
| case "MESSAGE_REACTION_ADD": | |||
| { | |||
| await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_ADD)").ConfigureAwait(false); | |||
| var data = (payload as JToken).ToObject<GatewayReaction>(_serializer); | |||
| var channel = State.GetChannel(data.ChannelId) as ISocketMessageChannel; | |||
| if (channel != null) | |||
| { | |||
| SocketUserMessage cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; | |||
| var user = await channel.GetUserAsync(data.UserId, CacheMode.CacheOnly); | |||
| SocketReaction reaction = new SocketReaction(data, channel, Optional.Create(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<SocketUserMessage>(), reaction).ConfigureAwait(false); | |||
| } | |||
| else | |||
| { | |||
| await _gatewayLogger.WarningAsync("MESSAGE_REACTION_ADD referenced an unknown channel.").ConfigureAwait(false); | |||
| return; | |||
| } | |||
| break; | |||
| } | |||
| case "MESSAGE_REACTION_REMOVE": | |||
| { | |||
| await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_REMOVE)").ConfigureAwait(false); | |||
| var data = (payload as JToken).ToObject<GatewayReaction>(_serializer); | |||
| var channel = State.GetChannel(data.ChannelId) as ISocketMessageChannel; | |||
| if (channel != null) | |||
| { | |||
| SocketUserMessage cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; | |||
| var user = await channel.GetUserAsync(data.UserId, CacheMode.CacheOnly); | |||
| SocketReaction reaction = new SocketReaction(data, channel, Optional.Create(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<SocketUserMessage>(), reaction).ConfigureAwait(false); | |||
| } | |||
| else | |||
| { | |||
| await _gatewayLogger.WarningAsync("MESSAGE_REACTION_REMOVE referenced an unknown channel.").ConfigureAwait(false); | |||
| return; | |||
| } | |||
| break; | |||
| } | |||
| case "MESSAGE_REACTION_REMOVE_ALL": | |||
| { | |||
| await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_REMOVE_ALL)").ConfigureAwait(false); | |||
| var data = (payload as JToken).ToObject<RemoveAllReactionsEvent>(_serializer); | |||
| var channel = State.GetChannel(data.ChannelId) as ISocketMessageChannel; | |||
| 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<SocketUserMessage>()); | |||
| } | |||
| else | |||
| { | |||
| await _gatewayLogger.WarningAsync("MESSAGE_REACTION_REMOVE_ALL referenced an unknown channel.").ConfigureAwait(false); | |||
| return; | |||
| } | |||
| break; | |||
| } | |||
| case "MESSAGE_DELETE_BULK": | |||
| { | |||
| await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE_BULK)").ConfigureAwait(false); | |||
| @@ -0,0 +1,28 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Threading.Tasks; | |||
| using Model = Discord.API.Gateway.GatewayReaction; | |||
| namespace Discord.WebSocket | |||
| { | |||
| public class SocketReaction : IReaction | |||
| { | |||
| internal SocketReaction(Model model, ISocketMessageChannel channel, Optional<SocketUserMessage> message, Optional<IUser> user) | |||
| { | |||
| Channel = channel; | |||
| Message = message; | |||
| MessageId = model.MessageId; | |||
| User = user; | |||
| UserId = model.UserId; | |||
| Emoji = Emoji.FromApi(model.Emoji); | |||
| } | |||
| public ulong UserId { get; private set; } | |||
| public Optional<IUser> User { get; private set; } | |||
| public ulong MessageId { get; private set; } | |||
| public Optional<SocketUserMessage> Message { get; private set; } | |||
| public ISocketMessageChannel Channel { get; private set; } | |||
| public Emoji Emoji { get; private set; } | |||
| } | |||
| } | |||
| @@ -5,6 +5,8 @@ using System.Collections.Generic; | |||
| using System.Collections.Immutable; | |||
| using System.Diagnostics; | |||
| using System.Threading.Tasks; | |||
| using System.Linq; | |||
| using Discord.API.Gateway; | |||
| using Model = Discord.API.Message; | |||
| namespace Discord.WebSocket | |||
| @@ -18,6 +20,7 @@ namespace Discord.WebSocket | |||
| private ImmutableArray<Attachment> _attachments; | |||
| private ImmutableArray<Embed> _embeds; | |||
| private ImmutableArray<ITag> _tags; | |||
| private List<SocketReaction> _reactions = new List<SocketReaction>(); | |||
| public override bool IsTTS => _isTTS; | |||
| public override bool IsPinned => _isPinned; | |||
| @@ -29,6 +32,7 @@ namespace Discord.WebSocket | |||
| public override IReadOnlyCollection<SocketGuildChannel> MentionedChannels => MessageHelper.FilterTagsByValue<SocketGuildChannel>(TagType.ChannelMention, _tags); | |||
| public override IReadOnlyCollection<SocketRole> MentionedRoles => MessageHelper.FilterTagsByValue<SocketRole>(TagType.RoleMention, _tags); | |||
| public override IReadOnlyCollection<SocketUser> MentionedUsers => MessageHelper.FilterTagsByValue<SocketUser>(TagType.UserMention, _tags); | |||
| public IReadOnlyDictionary<Emoji, int> Reactions => _reactions.GroupBy(r => r.Emoji).ToDictionary(x => x.Key, x => x.Count()); | |||
| internal SocketUserMessage(DiscordSocketClient discord, ulong id, ISocketMessageChannel channel, SocketUser author) | |||
| : base(discord, id, channel, author) | |||
| @@ -109,10 +113,39 @@ namespace Discord.WebSocket | |||
| model.Content = text; | |||
| } | |||
| } | |||
| internal void AddReaction(SocketReaction reaction) | |||
| { | |||
| _reactions.Add(reaction); | |||
| } | |||
| internal void RemoveReaction(SocketReaction reaction) | |||
| { | |||
| if (_reactions.Contains(reaction)) | |||
| _reactions.Remove(reaction); | |||
| } | |||
| internal void ClearReactions() | |||
| { | |||
| _reactions.Clear(); | |||
| } | |||
| public Task ModifyAsync(Action<ModifyMessageParams> func, RequestOptions options = null) | |||
| => MessageHelper.ModifyAsync(this, Discord, func, options); | |||
| public Task AddReactionAsync(Emoji emoji, RequestOptions options = null) | |||
| => MessageHelper.AddReactionAsync(this, emoji, Discord, options); | |||
| public Task AddReactionAsync(string emoji, RequestOptions options = null) | |||
| => MessageHelper.AddReactionAsync(this, emoji, Discord, options); | |||
| public Task RemoveReactionAsync(Emoji emoji, IUser user, RequestOptions options = null) | |||
| => MessageHelper.RemoveReactionAsync(this, user, emoji, Discord, options); | |||
| public Task RemoveReactionAsync(string emoji, IUser user, RequestOptions options = null) | |||
| => MessageHelper.RemoveReactionAsync(this, user, emoji, Discord, options); | |||
| public Task RemoveAllReactionsAsync(RequestOptions options = null) | |||
| => MessageHelper.RemoveAllReactionsAsync(this, Discord, options); | |||
| public Task<IReadOnlyCollection<IUser>> GetReactionUsersAsync(string emoji, int limit = 100, ulong? afterUserId = null, RequestOptions options = null) | |||
| => MessageHelper.GetReactionUsersAsync(this, emoji, x => { x.Limit = limit; x.AfterUserId = afterUserId.HasValue ? afterUserId.Value : Optional.Create<ulong>(); }, Discord, options); | |||
| public Task PinAsync(RequestOptions options = null) | |||
| => MessageHelper.PinAsync(this, Discord, options); | |||
| public Task UnpinAsync(RequestOptions options = null) | |||