@@ -19,6 +19,11 @@ namespace Discord | |||||
Name = name; | Name = name; | ||||
} | } | ||||
internal static Emoji FromApi(API.Emoji emoji) | |||||
{ | |||||
return new Emoji(emoji.Id, emoji.Name); | |||||
} | |||||
public static Emoji Parse(string text) | public static Emoji Parse(string text) | ||||
{ | { | ||||
Emoji result; | Emoji result; | ||||
@@ -31,8 +31,6 @@ namespace Discord | |||||
IReadOnlyCollection<IAttachment> Attachments { get; } | IReadOnlyCollection<IAttachment> Attachments { get; } | ||||
/// <summary> Returns all embeds included in this message. </summary> | /// <summary> Returns all embeds included in this message. </summary> | ||||
IReadOnlyCollection<IEmbed> Embeds { get; } | IReadOnlyCollection<IEmbed> Embeds { get; } | ||||
/// <summary> Returns all reactions included in this message. </summary> | |||||
IReadOnlyCollection<IReaction> Reactions { get; } | |||||
/// <summary> Returns all tags included in this message's content. </summary> | /// <summary> Returns all tags included in this message's content. </summary> | ||||
IReadOnlyCollection<ITag> Tags { get; } | IReadOnlyCollection<ITag> Tags { get; } | ||||
/// <summary> Returns the ids of channels mentioned in this message. </summary> | /// <summary> Returns the ids of channels mentioned in this message. </summary> | ||||
@@ -7,6 +7,6 @@ namespace Discord | |||||
{ | { | ||||
public interface IReaction | public interface IReaction | ||||
{ | { | ||||
API.Emoji Emoji { get; } | |||||
Emoji Emoji { get; } | |||||
} | } | ||||
} | } |
@@ -14,6 +14,9 @@ namespace Discord | |||||
/// <summary> Removes this message from its channel's pinned messages. </summary> | /// <summary> Removes this message from its channel's pinned messages. </summary> | ||||
Task UnpinAsync(RequestOptions options = null); | 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> | /// <summary> Adds a reaction to this message. </summary> | ||||
Task AddReactionAsync(Emoji emoji, RequestOptions options = null); | Task AddReactionAsync(Emoji emoji, RequestOptions options = null); | ||||
/// <summary> Adds a reaction to this message. </summary> | /// <summary> Adds a reaction to this message. </summary> | ||||
@@ -27,7 +27,6 @@ namespace Discord.Rest | |||||
public virtual IReadOnlyCollection<ulong> MentionedRoleIds => ImmutableArray.Create<ulong>(); | public virtual IReadOnlyCollection<ulong> MentionedRoleIds => ImmutableArray.Create<ulong>(); | ||||
public virtual IReadOnlyCollection<RestUser> MentionedUsers => ImmutableArray.Create<RestUser>(); | public virtual IReadOnlyCollection<RestUser> MentionedUsers => ImmutableArray.Create<RestUser>(); | ||||
public virtual IReadOnlyCollection<ITag> Tags => ImmutableArray.Create<ITag>(); | public virtual IReadOnlyCollection<ITag> Tags => ImmutableArray.Create<ITag>(); | ||||
public virtual IReadOnlyCollection<IReaction> Reactions => ImmutableArray.Create<RestReaction>(); | |||||
public virtual ulong? WebhookId => null; | public virtual ulong? WebhookId => null; | ||||
public bool IsWebhook => WebhookId != null; | public bool IsWebhook => WebhookId != null; | ||||
@@ -71,6 +70,5 @@ namespace Discord.Rest | |||||
IReadOnlyCollection<IAttachment> IMessage.Attachments => Attachments; | IReadOnlyCollection<IAttachment> IMessage.Attachments => Attachments; | ||||
IReadOnlyCollection<IEmbed> IMessage.Embeds => Embeds; | IReadOnlyCollection<IEmbed> IMessage.Embeds => Embeds; | ||||
IReadOnlyCollection<ulong> IMessage.MentionedUserIds => MentionedUsers.Select(x => x.Id).ToImmutableArray(); | IReadOnlyCollection<ulong> IMessage.MentionedUserIds => MentionedUsers.Select(x => x.Id).ToImmutableArray(); | ||||
IReadOnlyCollection<IReaction> IMessage.Reactions => Reactions; | |||||
} | } | ||||
} | } |
@@ -10,17 +10,13 @@ namespace Discord | |||||
{ | { | ||||
internal RestReaction(Model model) | internal RestReaction(Model model) | ||||
{ | { | ||||
_emoji = model.Emoji; | |||||
_count = model.Count; | |||||
Emoji = Emoji.FromApi(model.Emoji); | |||||
Count = model.Count; | |||||
Me = model.Me; | |||||
} | } | ||||
internal readonly API.Emoji _emoji; | |||||
internal readonly int _count; | |||||
internal readonly bool _me; | |||||
public API.Emoji Emoji => _emoji; | |||||
public int Count => _count; | |||||
public bool Me => _me; | |||||
public Emoji Emoji { get; private set; } | |||||
public int Count { get; private set; } | |||||
public bool Me { get; private set; } | |||||
} | } | ||||
} | } |
@@ -30,7 +30,7 @@ namespace Discord.Rest | |||||
public override IReadOnlyCollection<ulong> MentionedRoleIds => MessageHelper.FilterTagsByKey(TagType.RoleMention, _tags); | public override IReadOnlyCollection<ulong> MentionedRoleIds => MessageHelper.FilterTagsByKey(TagType.RoleMention, _tags); | ||||
public override IReadOnlyCollection<RestUser> MentionedUsers => MessageHelper.FilterTagsByValue<RestUser>(TagType.UserMention, _tags); | public override IReadOnlyCollection<RestUser> MentionedUsers => MessageHelper.FilterTagsByValue<RestUser>(TagType.UserMention, _tags); | ||||
public override IReadOnlyCollection<ITag> Tags => _tags; | public override IReadOnlyCollection<ITag> Tags => _tags; | ||||
public override IReadOnlyCollection<IReaction> Reactions => _reactions; | |||||
public IReadOnlyDictionary<Emoji, int> Reactions => _reactions.ToDictionary(x => x.Emoji, x => x.Count); | |||||
internal RestUserMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, RestUser author, IGuild guild) | internal RestUserMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, RestUser author, IGuild guild) | ||||
: base(discord, id, channel, author, guild) | : base(discord, id, channel, author, guild) | ||||
@@ -28,7 +28,6 @@ namespace Discord.Rpc | |||||
public virtual IReadOnlyCollection<ulong> MentionedRoleIds => ImmutableArray.Create<ulong>(); | public virtual IReadOnlyCollection<ulong> MentionedRoleIds => ImmutableArray.Create<ulong>(); | ||||
public virtual IReadOnlyCollection<ulong> MentionedUserIds => ImmutableArray.Create<ulong>(); | public virtual IReadOnlyCollection<ulong> MentionedUserIds => ImmutableArray.Create<ulong>(); | ||||
public virtual IReadOnlyCollection<ITag> Tags => ImmutableArray.Create<ITag>(); | public virtual IReadOnlyCollection<ITag> Tags => ImmutableArray.Create<ITag>(); | ||||
public virtual IReadOnlyCollection<IReaction> Reactions => ImmutableArray.Create<IReaction>(); | |||||
public virtual ulong? WebhookId => null; | public virtual ulong? WebhookId => null; | ||||
public bool IsWebhook => WebhookId != null; | public bool IsWebhook => WebhookId != null; | ||||
@@ -30,6 +30,7 @@ namespace Discord.Rpc | |||||
public override IReadOnlyCollection<ulong> MentionedRoleIds => MessageHelper.FilterTagsByKey(TagType.RoleMention, _tags); | public override IReadOnlyCollection<ulong> MentionedRoleIds => MessageHelper.FilterTagsByKey(TagType.RoleMention, _tags); | ||||
public override IReadOnlyCollection<ulong> MentionedUserIds => MessageHelper.FilterTagsByKey(TagType.UserMention, _tags); | public override IReadOnlyCollection<ulong> MentionedUserIds => MessageHelper.FilterTagsByKey(TagType.UserMention, _tags); | ||||
public override IReadOnlyCollection<ITag> Tags => _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) | internal RpcUserMessage(DiscordRpcClient discord, ulong id, IMessageChannel channel, RpcUser author) | ||||
: base(discord, id, channel, author) | : base(discord, id, channel, author) | ||||
@@ -6,7 +6,7 @@ using Newtonsoft.Json; | |||||
namespace Discord.API.Gateway | namespace Discord.API.Gateway | ||||
{ | { | ||||
public class GatewayReaction : Reaction | |||||
public class GatewayReaction | |||||
{ | { | ||||
[JsonProperty("user_id")] | [JsonProperty("user_id")] | ||||
public ulong UserId { get; set; } | public ulong UserId { get; set; } | ||||
@@ -15,6 +15,6 @@ namespace Discord.API.Gateway | |||||
[JsonProperty("channel_id")] | [JsonProperty("channel_id")] | ||||
public ulong ChannelId { get; set; } | public ulong ChannelId { get; set; } | ||||
[JsonProperty("emoji")] | [JsonProperty("emoji")] | ||||
public Discord.API.Emoji Emoji { get; set; } | |||||
public Emoji Emoji { get; set; } | |||||
} | } | ||||
} | } |
@@ -71,6 +71,18 @@ namespace Discord.WebSocket | |||||
remove { _messageUpdatedEvent.Remove(value); } | remove { _messageUpdatedEvent.Remove(value); } | ||||
} | } | ||||
private readonly AsyncEvent<Func<Optional<SocketMessage>, SocketMessage, Task>> _messageUpdatedEvent = new AsyncEvent<Func<Optional<SocketMessage>, SocketMessage, Task>>(); | 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>>(); | |||||
//Roles | //Roles | ||||
public event Func<SocketRole, Task> RoleCreated | public event Func<SocketRole, Task> RoleCreated | ||||
@@ -1304,6 +1304,54 @@ namespace Discord.WebSocket | |||||
} | } | ||||
} | } | ||||
break; | break; | ||||
case "MESSAGE_REACTION_ADD": | |||||
{ | |||||
await _gatewayLogger.DebugAsync("Received Disbatch (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; | |||||
SocketReaction reaction = new SocketReaction(data); | |||||
if (cachedMsg != null) | |||||
{ | |||||
cachedMsg.AddReaction(reaction); | |||||
await _reactionAddedEvent.InvokeAsync(data.MessageId, cachedMsg, reaction).ConfigureAwait(false); | |||||
} | |||||
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 Disbatch (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; | |||||
SocketReaction reaction = new SocketReaction(data); | |||||
if (cachedMsg != null) | |||||
{ | |||||
cachedMsg.RemoveReaction(reaction); | |||||
await _reactionRemovedEvent.InvokeAsync(data.MessageId, cachedMsg, reaction).ConfigureAwait(false); | |||||
} | |||||
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_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); | ||||
@@ -27,7 +27,6 @@ namespace Discord.WebSocket | |||||
public virtual IReadOnlyCollection<SocketRole> MentionedRoles => ImmutableArray.Create<SocketRole>(); | public virtual IReadOnlyCollection<SocketRole> MentionedRoles => ImmutableArray.Create<SocketRole>(); | ||||
public virtual IReadOnlyCollection<SocketUser> MentionedUsers => ImmutableArray.Create<SocketUser>(); | public virtual IReadOnlyCollection<SocketUser> MentionedUsers => ImmutableArray.Create<SocketUser>(); | ||||
public virtual IReadOnlyCollection<ITag> Tags => ImmutableArray.Create<ITag>(); | public virtual IReadOnlyCollection<ITag> Tags => ImmutableArray.Create<ITag>(); | ||||
public virtual IReadOnlyCollection<IReaction> Reactions => ImmutableArray.Create<IReaction>(); | |||||
public virtual ulong? WebhookId => null; | public virtual ulong? WebhookId => null; | ||||
public bool IsWebhook => WebhookId != null; | public bool IsWebhook => WebhookId != null; | ||||
@@ -13,12 +13,12 @@ namespace Discord.WebSocket | |||||
UserId = model.UserId; | UserId = model.UserId; | ||||
MessageId = model.MessageId; | MessageId = model.MessageId; | ||||
ChannelId = model.ChannelId; | ChannelId = model.ChannelId; | ||||
Emoji = model.Emoji; | |||||
Emoji = Emoji.FromApi(model.Emoji); | |||||
} | } | ||||
public ulong UserId { get; internal set; } | |||||
public ulong MessageId { get; internal set; } | |||||
public ulong ChannelId { get; internal set; } | |||||
public API.Emoji Emoji { get; internal set; } | |||||
public ulong UserId { get; private set; } | |||||
public ulong MessageId { get; private set; } | |||||
public ulong ChannelId { get; private set; } | |||||
public Emoji Emoji { get; private set; } | |||||
} | } | ||||
} | } |
@@ -5,6 +5,7 @@ using System.Collections.Generic; | |||||
using System.Collections.Immutable; | using System.Collections.Immutable; | ||||
using System.Diagnostics; | using System.Diagnostics; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
using System.Linq; | |||||
using Discord.API.Gateway; | using Discord.API.Gateway; | ||||
using Model = Discord.API.Message; | using Model = Discord.API.Message; | ||||
@@ -19,7 +20,7 @@ namespace Discord.WebSocket | |||||
private ImmutableArray<Attachment> _attachments; | private ImmutableArray<Attachment> _attachments; | ||||
private ImmutableArray<Embed> _embeds; | private ImmutableArray<Embed> _embeds; | ||||
private ImmutableArray<ITag> _tags; | private ImmutableArray<ITag> _tags; | ||||
private ImmutableArray<IReaction> _reactions; | |||||
private List<SocketReaction> _reactions = new List<SocketReaction>(); | |||||
public override bool IsTTS => _isTTS; | public override bool IsTTS => _isTTS; | ||||
public override bool IsPinned => _isPinned; | public override bool IsPinned => _isPinned; | ||||
@@ -31,7 +32,7 @@ namespace Discord.WebSocket | |||||
public override IReadOnlyCollection<SocketGuildChannel> MentionedChannels => MessageHelper.FilterTagsByValue<SocketGuildChannel>(TagType.ChannelMention, _tags); | 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<SocketRole> MentionedRoles => MessageHelper.FilterTagsByValue<SocketRole>(TagType.RoleMention, _tags); | ||||
public override IReadOnlyCollection<SocketUser> MentionedUsers => MessageHelper.FilterTagsByValue<SocketUser>(TagType.UserMention, _tags); | public override IReadOnlyCollection<SocketUser> MentionedUsers => MessageHelper.FilterTagsByValue<SocketUser>(TagType.UserMention, _tags); | ||||
public override IReadOnlyCollection<IReaction> Reactions => _reactions; | |||||
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) | internal SocketUserMessage(DiscordSocketClient discord, ulong id, ISocketMessageChannel channel, SocketUser author) | ||||
: base(discord, id, channel, author) | : base(discord, id, channel, author) | ||||
@@ -104,20 +105,6 @@ namespace Discord.WebSocket | |||||
} | } | ||||
} | } | ||||
if (model.Reactions.IsSpecified) | |||||
{ | |||||
var value = model.Reactions.Value; | |||||
if (value.Length > 0) | |||||
{ | |||||
var reactions = ImmutableArray.CreateBuilder<IReaction>(value.Length); | |||||
for (int i = 0; i < value.Length; i++) | |||||
reactions.Add(new SocketReaction(value[i] as GatewayReaction)); | |||||
_reactions = reactions.ToImmutable(); | |||||
} | |||||
else | |||||
_reactions = ImmutableArray.Create<IReaction>(); | |||||
} | |||||
if (model.Content.IsSpecified) | if (model.Content.IsSpecified) | ||||
{ | { | ||||
var text = model.Content.Value; | var text = model.Content.Value; | ||||
@@ -126,6 +113,15 @@ namespace Discord.WebSocket | |||||
model.Content = text; | model.Content = text; | ||||
} | } | ||||
} | } | ||||
internal void AddReaction(SocketReaction reaction) | |||||
{ | |||||
_reactions.Add(reaction); | |||||
} | |||||
internal void RemoveReaction(SocketReaction reaction) | |||||
{ | |||||
if (_reactions.Contains(reaction)) | |||||
_reactions.Remove(reaction); | |||||
} | |||||
public Task ModifyAsync(Action<ModifyMessageParams> func, RequestOptions options = null) | public Task ModifyAsync(Action<ModifyMessageParams> func, RequestOptions options = null) | ||||
=> MessageHelper.ModifyAsync(this, Discord, func, options); | => MessageHelper.ModifyAsync(this, Discord, func, options); | ||||