Split IMessage into IUserMessage and ISystemMessage

This commit is contained in:
RogueException
2016-08-27 17:30:23 -03:00
parent 36d3257440
commit 23a0316252
36 changed files with 464 additions and 242 deletions

View File

@@ -1,6 +1,4 @@
#pragma warning disable CS1591
using Discord.Rest;
namespace Discord.API.Rest
{
public class GetChannelMessagesParams

View File

@@ -10,11 +10,11 @@ namespace Discord
IReadOnlyCollection<IMessage> CachedMessages { get; }
/// <summary> Sends a message to this message channel. </summary>
Task<IMessage> SendMessageAsync(string text, bool isTTS = false);
Task<IUserMessage> SendMessageAsync(string text, bool isTTS = false);
/// <summary> Sends a file to this text channel, with an optional caption. </summary>
Task<IMessage> SendFileAsync(string filePath, string text = null, bool isTTS = false);
Task<IUserMessage> SendFileAsync(string filePath, string text = null, bool isTTS = false);
/// <summary> Sends a file to this text channel, with an optional caption. </summary>
Task<IMessage> SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false);
Task<IUserMessage> SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false);
/// <summary> Gets a message from this message channel with the given id, or null if not found. </summary>
Task<IMessage> GetMessageAsync(ulong id);
/// <summary> Gets the message from this channel's cache with the given id, or null if not found. </summary>

View File

@@ -1,14 +1,10 @@
using System;
using System.Threading.Tasks;
using Discord.API.Rest;
using System.Collections.Generic;
namespace Discord
{
public interface IMessage : IDeletable, ISnowflakeEntity, IUpdateable
public interface IMessage : ISnowflakeEntity, IUpdateable
{
/// <summary> Gets the time of this message's last edit, if any. </summary>
DateTimeOffset? EditedTimestamp { get; }
/// <summary> Returns true if this message was sent as a text-to-speech message. </summary>
bool IsTTS { get; }
/// <summary> Returns true if this message was added to its channel's pinned messages. </summary>
@@ -17,13 +13,14 @@ namespace Discord
string Content { get; }
/// <summary> Gets the time this message was sent. </summary>
DateTimeOffset Timestamp { get; }
/// <summary> Gets the type of this message. </summary>
MessageType Type { get; }
/// <summary> Gets the time of this message's last edit, if any. </summary>
DateTimeOffset? EditedTimestamp { get; }
/// <summary> Gets the channel this message was sent to. </summary>
IMessageChannel Channel { get; }
/// <summary> Gets the author of this message. </summary>
IUser Author { get; }
/// <summary> Returns a collection of all attachments included in this message. </summary>
IReadOnlyCollection<IAttachment> Attachments { get; }
/// <summary> Returns a collection of all embeds included in this message. </summary>
@@ -34,25 +31,5 @@ namespace Discord
IReadOnlyCollection<IRole> MentionedRoles { get; }
/// <summary> Returns a collection of users mentioned in this message. </summary>
IReadOnlyCollection<IUser> MentionedUsers { get; }
/// <summary> Modifies this message. </summary>
Task ModifyAsync(Action<ModifyMessageParams> func);
/// <summary> Adds this message to its channel's pinned messages. </summary>
Task PinAsync();
/// <summary> Removes this message from its channel's pinned messages. </summary>
Task UnpinAsync();
/// <summary> Transforms this message's text into a human readable form, resolving things like mentions to that object's name. </summary>
string Resolve(int startIndex, int length,
UserMentionHandling userHandling = UserMentionHandling.Name,
ChannelMentionHandling channelHandling = ChannelMentionHandling.Name,
RoleMentionHandling roleHandling = RoleMentionHandling.Name,
EveryoneMentionHandling everyoneHandling = EveryoneMentionHandling.Ignore);
/// <summary> Transforms this message's text into a human readable form, resolving things like mentions to that object's name. </summary>
string Resolve(
UserMentionHandling userHandling = UserMentionHandling.Name,
ChannelMentionHandling channelHandling = ChannelMentionHandling.Name,
RoleMentionHandling roleHandling = RoleMentionHandling.Name,
EveryoneMentionHandling everyoneHandling = EveryoneMentionHandling.Ignore);
}
}

View File

@@ -0,0 +1,8 @@
namespace Discord
{
public interface ISystemMessage : IMessage
{
/// <summary> Gets the type of this system message. </summary>
MessageType Type { get; }
}
}

View File

@@ -0,0 +1,29 @@
using Discord.API.Rest;
using System;
using System.Threading.Tasks;
namespace Discord
{
public interface IUserMessage : IMessage, IDeletable
{
/// <summary> Modifies this message. </summary>
Task ModifyAsync(Action<ModifyMessageParams> func);
/// <summary> Adds this message to its channel's pinned messages. </summary>
Task PinAsync();
/// <summary> Removes this message from its channel's pinned messages. </summary>
Task UnpinAsync();
/// <summary> Transforms this message's text into a human readable form, resolving mentions to that object's name. </summary>
string Resolve(int startIndex, int length,
UserMentionHandling userHandling = UserMentionHandling.Name,
ChannelMentionHandling channelHandling = ChannelMentionHandling.Name,
RoleMentionHandling roleHandling = RoleMentionHandling.Name,
EveryoneMentionHandling everyoneHandling = EveryoneMentionHandling.Ignore);
/// <summary> Transforms this message's text into a human readable form, resolving mentions to that object's name. </summary>
string Resolve(
UserMentionHandling userHandling = UserMentionHandling.Name,
ChannelMentionHandling channelHandling = ChannelMentionHandling.Name,
RoleMentionHandling roleHandling = RoleMentionHandling.Name,
EveryoneMentionHandling everyoneHandling = EveryoneMentionHandling.Ignore);
}
}

View File

@@ -7,6 +7,7 @@ using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Model = Discord.API.Channel;
using MessageModel = Discord.API.Message;
namespace Discord.Rest
{
@@ -62,46 +63,46 @@ namespace Discord.Rest
return ImmutableArray.Create(currentUser, Recipient);
}
public async Task<IMessage> SendMessageAsync(string text, bool isTTS)
public async Task<IUserMessage> SendMessageAsync(string text, bool isTTS)
{
var args = new CreateMessageParams { Content = text, IsTTS = isTTS };
var model = await Discord.ApiClient.CreateDMMessageAsync(Id, args).ConfigureAwait(false);
return new Message(this, new User(model.Author.Value), model);
return CreateOutgoingMessage(model);
}
public async Task<IMessage> SendFileAsync(string filePath, string text, bool isTTS)
public async Task<IUserMessage> SendFileAsync(string filePath, string text, bool isTTS)
{
string filename = Path.GetFileName(filePath);
using (var file = File.OpenRead(filePath))
{
var args = new UploadFileParams(file) { Filename = filename, Content = text, IsTTS = isTTS };
var model = await Discord.ApiClient.UploadDMFileAsync(Id, args).ConfigureAwait(false);
return new Message(this, new User(model.Author.Value), model);
return CreateOutgoingMessage(model);
}
}
public async Task<IMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS)
public async Task<IUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS)
{
var args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS };
var model = await Discord.ApiClient.UploadDMFileAsync(Id, args).ConfigureAwait(false);
return new Message(this, new User(model.Author.Value), model);
return CreateOutgoingMessage(model);
}
public virtual async Task<IMessage> GetMessageAsync(ulong id)
{
var model = await Discord.ApiClient.GetChannelMessageAsync(Id, id).ConfigureAwait(false);
if (model != null)
return new Message(this, new User(model.Author.Value), model);
return CreateIncomingMessage(model);
return null;
}
public virtual async Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(int limit)
{
var args = new GetChannelMessagesParams { Limit = limit };
var models = await Discord.ApiClient.GetChannelMessagesAsync(Id, args).ConfigureAwait(false);
return models.Select(x => new Message(this, new User(x.Author.Value), x)).ToImmutableArray();
return models.Select(x => CreateIncomingMessage(x)).ToImmutableArray();
}
public virtual async Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit)
{
var args = new GetChannelMessagesParams { Limit = limit, RelativeMessageId = fromMessageId, RelativeDirection = dir };
var models = await Discord.ApiClient.GetChannelMessagesAsync(Id, args).ConfigureAwait(false);
return models.Select(x => new Message(this, new User(x.Author.Value), x)).ToImmutableArray();
return models.Select(x => CreateIncomingMessage(x)).ToImmutableArray();
}
public async Task DeleteMessagesAsync(IEnumerable<IMessage> messages)
{
@@ -110,14 +111,26 @@ namespace Discord.Rest
public async Task<IReadOnlyCollection<IMessage>> GetPinnedMessagesAsync()
{
var models = await Discord.ApiClient.GetPinsAsync(Id);
return models.Select(x => new Message(this, new User(x.Author.Value), x)).ToImmutableArray();
return models.Select(x => CreateIncomingMessage(x)).ToImmutableArray();
}
public async Task TriggerTypingAsync()
{
await Discord.ApiClient.TriggerTypingIndicatorAsync(Id).ConfigureAwait(false);
}
}
private UserMessage CreateOutgoingMessage(MessageModel model)
{
return new UserMessage(this, new User(model.Author.Value), model);
}
private Message CreateIncomingMessage(MessageModel model)
{
if (model.Type == MessageType.Default)
return new UserMessage(this, new User(model.Author.Value), model);
else
return new SystemMessage(this, new User(model.Author.Value), model);
}
public override string ToString() => '@' + Recipient.ToString();
private string DebuggerDisplay => $"@{Recipient} ({Id}, DM)";

View File

@@ -8,6 +8,7 @@ using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Model = Discord.API.Channel;
using MessageModel = Discord.API.Message;
namespace Discord.Rest
{
@@ -87,46 +88,46 @@ namespace Discord.Rest
return _users.Select(x => x.Value).Concat<IUser>(ImmutableArray.Create(currentUser)).ToReadOnlyCollection(_users);
}
public async Task<IMessage> SendMessageAsync(string text, bool isTTS)
public async Task<IUserMessage> SendMessageAsync(string text, bool isTTS)
{
var args = new CreateMessageParams { Content = text, IsTTS = isTTS };
var model = await Discord.ApiClient.CreateDMMessageAsync(Id, args).ConfigureAwait(false);
return new Message(this, new User(model.Author.Value), model);
return CreateOutgoingMessage(model);
}
public async Task<IMessage> SendFileAsync(string filePath, string text, bool isTTS)
public async Task<IUserMessage> SendFileAsync(string filePath, string text, bool isTTS)
{
string filename = Path.GetFileName(filePath);
using (var file = File.OpenRead(filePath))
{
var args = new UploadFileParams(file) { Filename = filename, Content = text, IsTTS = isTTS };
var model = await Discord.ApiClient.UploadDMFileAsync(Id, args).ConfigureAwait(false);
return new Message(this, new User(model.Author.Value), model);
return CreateOutgoingMessage(model);
}
}
public async Task<IMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS)
public async Task<IUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS)
{
var args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS };
var model = await Discord.ApiClient.UploadDMFileAsync(Id, args).ConfigureAwait(false);
return new Message(this, new User(model.Author.Value), model);
return CreateOutgoingMessage(model);
}
public virtual async Task<IMessage> GetMessageAsync(ulong id)
{
var model = await Discord.ApiClient.GetChannelMessageAsync(Id, id).ConfigureAwait(false);
if (model != null)
return new Message(this, new User(model.Author.Value), model);
return CreateIncomingMessage(model);
return null;
}
public virtual async Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(int limit)
{
var args = new GetChannelMessagesParams { Limit = limit };
var models = await Discord.ApiClient.GetChannelMessagesAsync(Id, args).ConfigureAwait(false);
return models.Select(x => new Message(this, new User(x.Author.Value), x)).ToImmutableArray();
return models.Select(x => CreateIncomingMessage(x)).ToImmutableArray();
}
public virtual async Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit)
{
var args = new GetChannelMessagesParams { Limit = limit, RelativeMessageId = fromMessageId, RelativeDirection = dir };
var models = await Discord.ApiClient.GetChannelMessagesAsync(Id, args).ConfigureAwait(false);
return models.Select(x => new Message(this, new User(x.Author.Value), x)).ToImmutableArray();
return models.Select(x => CreateIncomingMessage(x)).ToImmutableArray();
}
public async Task DeleteMessagesAsync(IEnumerable<IMessage> messages)
{
@@ -135,7 +136,7 @@ namespace Discord.Rest
public async Task<IReadOnlyCollection<IMessage>> GetPinnedMessagesAsync()
{
var models = await Discord.ApiClient.GetPinsAsync(Id);
return models.Select(x => new Message(this, new User(x.Author.Value), x)).ToImmutableArray();
return models.Select(x => CreateIncomingMessage(x)).ToImmutableArray();
}
public async Task TriggerTypingAsync()
@@ -143,6 +144,18 @@ namespace Discord.Rest
await Discord.ApiClient.TriggerTypingIndicatorAsync(Id).ConfigureAwait(false);
}
private UserMessage CreateOutgoingMessage(MessageModel model)
{
return new UserMessage(this, new User(model.Author.Value), model);
}
private Message CreateIncomingMessage(MessageModel model)
{
if (model.Type == MessageType.Default)
return new UserMessage(this, new User(model.Author.Value), model);
else
return new SystemMessage(this, new User(model.Author.Value), model);
}
public override string ToString() => Name;
private string DebuggerDisplay => $"@{Name} ({Id}, Group)";

View File

@@ -7,6 +7,7 @@ using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Model = Discord.API.Channel;
using MessageModel = Discord.API.Message;
namespace Discord.Rest
{
@@ -57,46 +58,46 @@ namespace Discord.Rest
return users.Where(x => Permissions.GetValue(Permissions.ResolveChannel(x, this, x.GuildPermissions.RawValue), ChannelPermission.ReadMessages)).ToImmutableArray();
}
public async Task<IMessage> SendMessageAsync(string text, bool isTTS)
public async Task<IUserMessage> SendMessageAsync(string text, bool isTTS)
{
var args = new CreateMessageParams { Content = text, IsTTS = isTTS };
var model = await Discord.ApiClient.CreateMessageAsync(Guild.Id, Id, args).ConfigureAwait(false);
return new Message(this, new User(model.Author.Value), model);
return CreateOutgoingMessage(model);
}
public async Task<IMessage> SendFileAsync(string filePath, string text, bool isTTS)
public async Task<IUserMessage> SendFileAsync(string filePath, string text, bool isTTS)
{
string filename = Path.GetFileName(filePath);
using (var file = File.OpenRead(filePath))
{
var args = new UploadFileParams(file) { Filename = filename, Content = text, IsTTS = isTTS };
var model = await Discord.ApiClient.UploadFileAsync(Guild.Id, Id, args).ConfigureAwait(false);
return new Message(this, new User(model.Author.Value), model);
return CreateOutgoingMessage(model);
}
}
public async Task<IMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS)
public async Task<IUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS)
{
var args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS };
var model = await Discord.ApiClient.UploadFileAsync(Guild.Id, Id, args).ConfigureAwait(false);
return new Message(this, new User(model.Author.Value), model);
return CreateOutgoingMessage(model);
}
public virtual async Task<IMessage> GetMessageAsync(ulong id)
{
var model = await Discord.ApiClient.GetChannelMessageAsync(Id, id).ConfigureAwait(false);
if (model != null)
return new Message(this, new User(model.Author.Value), model);
return CreateIncomingMessage(model);
return null;
}
public virtual async Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(int limit)
{
var args = new GetChannelMessagesParams { Limit = limit };
var models = await Discord.ApiClient.GetChannelMessagesAsync(Id, args).ConfigureAwait(false);
return models.Select(x => new Message(this, new User(x.Author.Value), x)).ToImmutableArray();
return models.Select(x => CreateIncomingMessage(x)).ToImmutableArray();
}
public virtual async Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit)
{
var args = new GetChannelMessagesParams { Limit = limit, RelativeMessageId = fromMessageId, RelativeDirection = dir };
var models = await Discord.ApiClient.GetChannelMessagesAsync(Id, args).ConfigureAwait(false);
return models.Select(x => new Message(this, new User(x.Author.Value), x)).ToImmutableArray();
return models.Select(x => CreateIncomingMessage(x)).ToImmutableArray();
}
public async Task DeleteMessagesAsync(IEnumerable<IMessage> messages)
{
@@ -105,7 +106,7 @@ namespace Discord.Rest
public async Task<IReadOnlyCollection<IMessage>> GetPinnedMessagesAsync()
{
var models = await Discord.ApiClient.GetPinsAsync(Id);
return models.Select(x => new Message(this, new User(x.Author.Value), x)).ToImmutableArray();
return models.Select(x => CreateIncomingMessage(x)).ToImmutableArray();
}
public async Task TriggerTypingAsync()
@@ -113,6 +114,18 @@ namespace Discord.Rest
await Discord.ApiClient.TriggerTypingIndicatorAsync(Id).ConfigureAwait(false);
}
private UserMessage CreateOutgoingMessage(MessageModel model)
{
return new UserMessage(this, new User(model.Author.Value), model);
}
private Message CreateIncomingMessage(MessageModel model)
{
if (model.Type == MessageType.Default)
return new UserMessage(this, new User(model.Author.Value), model);
else
return new SystemMessage(this, new User(model.Author.Value), model);
}
private string DebuggerDisplay => $"{Name} ({Id}, Text)";
IMessage IMessageChannel.GetCachedMessage(ulong id) => null;

View File

@@ -9,28 +9,27 @@ using Model = Discord.API.Message;
namespace Discord.Rest
{
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
internal class Message : SnowflakeEntity, IMessage
internal abstract class Message : SnowflakeEntity, IMessage
{
private bool _isMentioningEveryone;
private long _timestampTicks;
private long? _editedTimestampTicks;
public MessageType Type { get; }
public IMessageChannel Channel { get; }
public IUser Author { get; }
public bool IsTTS { get; private set; }
public string Content { get; private set; }
public bool IsPinned { get; private set; }
public IReadOnlyCollection<IAttachment> Attachments { get; private set; }
public IReadOnlyCollection<IEmbed> Embeds { get; private set; }
public IReadOnlyCollection<ulong> MentionedChannelIds { get; private set; }
public IReadOnlyCollection<IRole> MentionedRoles { get; private set; }
public IReadOnlyCollection<IUser> MentionedUsers { get; private set; }
public override DiscordRestClient Discord => (Channel as Entity<ulong>).Discord;
public DateTimeOffset? EditedTimestamp => DateTimeUtils.FromTicks(_editedTimestampTicks);
public virtual bool IsTTS => false;
public virtual bool IsPinned => false;
public virtual DateTimeOffset? EditedTimestamp => null;
public virtual IReadOnlyCollection<IAttachment> Attachments => ImmutableArray.Create<IAttachment>();
public virtual IReadOnlyCollection<IEmbed> Embeds => ImmutableArray.Create<IEmbed>();
public virtual IReadOnlyCollection<ulong> MentionedChannelIds => ImmutableArray.Create<ulong>();
public virtual IReadOnlyCollection<IRole> MentionedRoles => ImmutableArray.Create<IRole>();
public virtual IReadOnlyCollection<IUser> MentionedUsers => ImmutableArray.Create<IUser>();
public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks);
public Message(IMessageChannel channel, IUser author, Model model)
@@ -38,86 +37,21 @@ namespace Discord.Rest
{
Channel = channel;
Author = author;
Type = model.Type;
MentionedUsers = ImmutableArray.Create<IUser>();
MentionedChannelIds = ImmutableArray.Create<ulong>();
MentionedRoles = ImmutableArray.Create<IRole>();
Update(model, UpdateSource.Creation);
}
public void Update(Model model, UpdateSource source)
public virtual void Update(Model model, UpdateSource source)
{
if (source == UpdateSource.Rest && IsAttached) return;
var guildChannel = Channel as GuildChannel;
var guild = guildChannel?.Guild;
if (model.IsTextToSpeech.IsSpecified)
IsTTS = model.IsTextToSpeech.Value;
if (model.Pinned.IsSpecified)
IsPinned = model.Pinned.Value;
if (model.Timestamp.IsSpecified)
_timestampTicks = model.Timestamp.Value.UtcTicks;
if (model.EditedTimestamp.IsSpecified)
_editedTimestampTicks = model.EditedTimestamp.Value?.UtcTicks;
if (model.MentionEveryone.IsSpecified)
_isMentioningEveryone = model.MentionEveryone.Value;
if (model.Attachments.IsSpecified)
{
var value = model.Attachments.Value;
if (value.Length > 0)
{
var attachments = new Attachment[value.Length];
for (int i = 0; i < attachments.Length; i++)
attachments[i] = new Attachment(value[i]);
Attachments = ImmutableArray.Create(attachments);
}
else
Attachments = ImmutableArray.Create<Attachment>();
}
if (model.Embeds.IsSpecified)
{
var value = model.Embeds.Value;
if (value.Length > 0)
{
var embeds = new Embed[value.Length];
for (int i = 0; i < embeds.Length; i++)
embeds[i] = new Embed(value[i]);
Embeds = ImmutableArray.Create(embeds);
}
else
Embeds = ImmutableArray.Create<Embed>();
}
if (model.Mentions.IsSpecified)
{
var value = model.Mentions.Value;
if (value.Length > 0)
{
var mentions = new User[value.Length];
for (int i = 0; i < value.Length; i++)
mentions[i] = new User(value[i]);
MentionedUsers = ImmutableArray.Create(mentions);
}
else
MentionedUsers = ImmutableArray.Create<IUser>();
}
if (model.Content.IsSpecified)
{
var text = model.Content.Value;
if (guildChannel != null)
{
MentionedUsers = MentionUtils.GetUserMentions(text, Channel, MentionedUsers);
MentionedChannelIds = MentionUtils.GetChannelMentions(text, guildChannel.Guild);
MentionedRoles = MentionUtils.GetRoleMentions(text, guildChannel.Guild);
}
Content = text;
}
Content = model.Content.Value;
}
public async Task UpdateAsync()
@@ -159,23 +93,6 @@ namespace Discord.Rest
{
await Discord.ApiClient.RemovePinAsync(Channel.Id, Id).ConfigureAwait(false);
}
public string Resolve(int startIndex, int length, UserMentionHandling userHandling, ChannelMentionHandling channelHandling,
RoleMentionHandling roleHandling, EveryoneMentionHandling everyoneHandling)
=> Resolve(Content.Substring(startIndex, length), userHandling, channelHandling, roleHandling, everyoneHandling);
public string Resolve(UserMentionHandling userHandling, ChannelMentionHandling channelHandling,
RoleMentionHandling roleHandling, EveryoneMentionHandling everyoneHandling)
=> Resolve(Content, userHandling, channelHandling, roleHandling, everyoneHandling);
private string Resolve(string text, UserMentionHandling userHandling, ChannelMentionHandling channelHandling,
RoleMentionHandling roleHandling, EveryoneMentionHandling everyoneHandling)
{
text = MentionUtils.ResolveUserMentions(text, Channel, MentionedUsers, userHandling);
text = MentionUtils.ResolveChannelMentions(text, (Channel as IGuildChannel)?.Guild, channelHandling);
text = MentionUtils.ResolveRoleMentions(text, MentionedRoles, roleHandling);
text = MentionUtils.ResolveEveryoneMentions(text, everyoneHandling);
return text;
}
public override string ToString() => Content;
private string DebuggerDisplay => $"{Author}: {Content}{(Attachments.Count > 0 ? $" [{Attachments.Count} Attachments]" : "")}";

View File

@@ -0,0 +1,22 @@
using System.Diagnostics;
using Model = Discord.API.Message;
namespace Discord.Rest
{
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
internal class SystemMessage : Message, ISystemMessage
{
public MessageType Type { get; }
public override DiscordRestClient Discord => (Channel as Entity<ulong>).Discord;
public SystemMessage(IMessageChannel channel, IUser author, Model model)
: base(channel, author, model)
{
Type = model.Type;
}
public override string ToString() => Content;
private string DebuggerDisplay => $"[{Type}] {Author}{(!string.IsNullOrEmpty(Content) ? $": ({Content})" : "")}";
}
}

View File

@@ -0,0 +1,175 @@
using Discord.API.Rest;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Threading.Tasks;
using Model = Discord.API.Message;
namespace Discord.Rest
{
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
internal class UserMessage : Message, IUserMessage
{
private bool _isMentioningEveryone, _isTTS, _isPinned;
private long? _editedTimestampTicks;
private IReadOnlyCollection<IAttachment> _attachments;
private IReadOnlyCollection<IEmbed> _embeds;
private IReadOnlyCollection<ulong> _mentionedChannelIds;
private IReadOnlyCollection<IRole> _mentionedRoles;
private IReadOnlyCollection<IUser> _mentionedUsers;
public override DiscordRestClient Discord => (Channel as Entity<ulong>).Discord;
public override bool IsTTS => _isTTS;
public override bool IsPinned => _isPinned;
public override DateTimeOffset? EditedTimestamp => DateTimeUtils.FromTicks(_editedTimestampTicks);
public override IReadOnlyCollection<IAttachment> Attachments => _attachments;
public override IReadOnlyCollection<IEmbed> Embeds => _embeds;
public override IReadOnlyCollection<ulong> MentionedChannelIds => _mentionedChannelIds;
public override IReadOnlyCollection<IRole> MentionedRoles => _mentionedRoles;
public override IReadOnlyCollection<IUser> MentionedUsers => _mentionedUsers;
public UserMessage(IMessageChannel channel, IUser author, Model model)
: base(channel, author, model)
{
_mentionedChannelIds = ImmutableArray.Create<ulong>();
_mentionedRoles = ImmutableArray.Create<IRole>();
_mentionedUsers = ImmutableArray.Create<IUser>();
Update(model, UpdateSource.Creation);
}
public override void Update(Model model, UpdateSource source)
{
if (source == UpdateSource.Rest && IsAttached) return;
var guildChannel = Channel as GuildChannel;
var guild = guildChannel?.Guild;
if (model.IsTextToSpeech.IsSpecified)
_isTTS = model.IsTextToSpeech.Value;
if (model.Pinned.IsSpecified)
_isPinned = model.Pinned.Value;
if (model.EditedTimestamp.IsSpecified)
_editedTimestampTicks = model.EditedTimestamp.Value?.UtcTicks;
if (model.MentionEveryone.IsSpecified)
_isMentioningEveryone = model.MentionEveryone.Value;
if (model.Attachments.IsSpecified)
{
var value = model.Attachments.Value;
if (value.Length > 0)
{
var attachments = new Attachment[value.Length];
for (int i = 0; i < attachments.Length; i++)
attachments[i] = new Attachment(value[i]);
_attachments = ImmutableArray.Create(attachments);
}
else
_attachments = ImmutableArray.Create<Attachment>();
}
if (model.Embeds.IsSpecified)
{
var value = model.Embeds.Value;
if (value.Length > 0)
{
var embeds = new Embed[value.Length];
for (int i = 0; i < embeds.Length; i++)
embeds[i] = new Embed(value[i]);
_embeds = ImmutableArray.Create(embeds);
}
else
_embeds = ImmutableArray.Create<Embed>();
}
ImmutableArray<IUser> mentions = ImmutableArray.Create<IUser>();
if (model.Mentions.IsSpecified)
{
var value = model.Mentions.Value;
if (value.Length > 0)
{
var newMentions = new IUser[value.Length];
for (int i = 0; i < value.Length; i++)
newMentions[i] = new User(value[i]);
mentions = ImmutableArray.Create(newMentions);
}
}
if (model.Content.IsSpecified)
{
var text = model.Content.Value;
if (guildChannel != null)
{
_mentionedUsers = MentionUtils.GetUserMentions(text, Channel, mentions);
_mentionedChannelIds = MentionUtils.GetChannelMentions(text, guildChannel.Guild);
_mentionedRoles = MentionUtils.GetRoleMentions(text, guildChannel.Guild);
}
model.Content = text;
}
base.Update(model, source);
}
public async Task UpdateAsync()
{
if (IsAttached) throw new NotSupportedException();
var model = await Discord.ApiClient.GetChannelMessageAsync(Channel.Id, Id).ConfigureAwait(false);
Update(model, UpdateSource.Rest);
}
public async Task ModifyAsync(Action<ModifyMessageParams> func)
{
if (func == null) throw new NullReferenceException(nameof(func));
var args = new ModifyMessageParams();
func(args);
var guildChannel = Channel as GuildChannel;
Model model;
if (guildChannel != null)
model = await Discord.ApiClient.ModifyMessageAsync(guildChannel.Guild.Id, Channel.Id, Id, args).ConfigureAwait(false);
else
model = await Discord.ApiClient.ModifyDMMessageAsync(Channel.Id, Id, args).ConfigureAwait(false);
Update(model, UpdateSource.Rest);
}
public async Task DeleteAsync()
{
var guildChannel = Channel as GuildChannel;
if (guildChannel != null)
await Discord.ApiClient.DeleteMessageAsync(guildChannel.Id, Channel.Id, Id).ConfigureAwait(false);
else
await Discord.ApiClient.DeleteDMMessageAsync(Channel.Id, Id).ConfigureAwait(false);
}
public async Task PinAsync()
{
await Discord.ApiClient.AddPinAsync(Channel.Id, Id).ConfigureAwait(false);
}
public async Task UnpinAsync()
{
await Discord.ApiClient.RemovePinAsync(Channel.Id, Id).ConfigureAwait(false);
}
public string Resolve(int startIndex, int length, UserMentionHandling userHandling, ChannelMentionHandling channelHandling,
RoleMentionHandling roleHandling, EveryoneMentionHandling everyoneHandling)
=> Resolve(Content.Substring(startIndex, length), userHandling, channelHandling, roleHandling, everyoneHandling);
public string Resolve(UserMentionHandling userHandling, ChannelMentionHandling channelHandling,
RoleMentionHandling roleHandling, EveryoneMentionHandling everyoneHandling)
=> Resolve(Content, userHandling, channelHandling, roleHandling, everyoneHandling);
private string Resolve(string text, UserMentionHandling userHandling, ChannelMentionHandling channelHandling,
RoleMentionHandling roleHandling, EveryoneMentionHandling everyoneHandling)
{
text = MentionUtils.ResolveUserMentions(text, Channel, MentionedUsers, userHandling);
text = MentionUtils.ResolveChannelMentions(text, (Channel as IGuildChannel)?.Guild, channelHandling);
text = MentionUtils.ResolveRoleMentions(text, MentionedRoles, roleHandling);
text = MentionUtils.ResolveEveryoneMentions(text, everyoneHandling);
return text;
}
public override string ToString() => Content;
private string DebuggerDisplay => $"{Author}: {Content}{(Attachments.Count > 0 ? $" [{Attachments.Count} Attachments]" : "")}";
}
}

View File

@@ -1247,7 +1247,7 @@ namespace Discord.WebSocket
}
IMessage before = null, after = null;
SocketMessage cachedMsg = channel.GetMessage(data.Id);
ISocketMessage cachedMsg = channel.GetMessage(data.Id);
if (cachedMsg != null)
{
before = cachedMsg.Clone();
@@ -1259,7 +1259,7 @@ namespace Discord.WebSocket
//Edited message isnt in cache, create a detached one
var author = channel.GetUser(data.Author.Value.Id, true);
if (author != null)
after = new Message(channel, author, data);
after = channel.CreateMessage(author, data);
}
if (after != null)
await _messageUpdatedEvent.InvokeAsync(Optional.Create(before), after).ConfigureAwait(false);

View File

@@ -7,9 +7,10 @@ namespace Discord.WebSocket
{
IReadOnlyCollection<ISocketUser> Users { get; }
SocketMessage AddMessage(ISocketUser author, MessageModel model);
SocketMessage GetMessage(ulong id);
SocketMessage RemoveMessage(ulong id);
ISocketMessage CreateMessage(ISocketUser author, MessageModel model);
ISocketMessage AddMessage(ISocketUser author, MessageModel model);
ISocketMessage GetMessage(ulong id);
ISocketMessage RemoveMessage(ulong id);
ISocketUser GetUser(ulong id, bool skipCheck = false);
}

View File

@@ -9,51 +9,51 @@ namespace Discord.WebSocket
{
internal class MessageCache : MessageManager
{
private readonly ConcurrentDictionary<ulong, SocketMessage> _messages;
private readonly ConcurrentDictionary<ulong, ISocketMessage> _messages;
private readonly ConcurrentQueue<ulong> _orderedMessages;
private readonly int _size;
public override IReadOnlyCollection<SocketMessage> Messages => _messages.ToReadOnlyCollection();
public override IReadOnlyCollection<ISocketMessage> Messages => _messages.ToReadOnlyCollection();
public MessageCache(DiscordSocketClient discord, ISocketMessageChannel channel)
: base(discord, channel)
{
_size = discord.MessageCacheSize;
_messages = new ConcurrentDictionary<ulong, SocketMessage>(1, (int)(_size * 1.05));
_messages = new ConcurrentDictionary<ulong, ISocketMessage>(1, (int)(_size * 1.05));
_orderedMessages = new ConcurrentQueue<ulong>();
}
public override void Add(SocketMessage message)
public override void Add(ISocketMessage message)
{
if (_messages.TryAdd(message.Id, message))
{
_orderedMessages.Enqueue(message.Id);
ulong msgId;
SocketMessage msg;
ISocketMessage msg;
while (_orderedMessages.Count > _size && _orderedMessages.TryDequeue(out msgId))
_messages.TryRemove(msgId, out msg);
}
}
public override SocketMessage Remove(ulong id)
public override ISocketMessage Remove(ulong id)
{
SocketMessage msg;
ISocketMessage msg;
_messages.TryRemove(id, out msg);
return msg;
}
public override SocketMessage Get(ulong id)
public override ISocketMessage Get(ulong id)
{
SocketMessage result;
ISocketMessage result;
if (_messages.TryGetValue(id, out result))
return result;
return null;
}
public override IImmutableList<SocketMessage> GetMany(ulong? fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch)
public override IImmutableList<ISocketMessage> GetMany(ulong? fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch)
{
if (limit < 0) throw new ArgumentOutOfRangeException(nameof(limit));
if (limit == 0) return ImmutableArray<SocketMessage>.Empty;
if (limit == 0) return ImmutableArray<ISocketMessage>.Empty;
IEnumerable<ulong> cachedMessageIds;
if (fromMessageId == null)
@@ -67,7 +67,7 @@ namespace Discord.WebSocket
.Take(limit)
.Select(x =>
{
SocketMessage msg;
ISocketMessage msg;
if (_messages.TryGetValue(x, out msg))
return msg;
return null;
@@ -76,7 +76,7 @@ namespace Discord.WebSocket
.ToImmutableArray();
}
public override async Task<SocketMessage> DownloadAsync(ulong id)
public override async Task<ISocketMessage> DownloadAsync(ulong id)
{
var msg = Get(id);
if (msg != null)

View File

@@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using Model = Discord.API.Message;
namespace Discord.WebSocket
{
@@ -13,8 +14,8 @@ namespace Discord.WebSocket
private readonly DiscordSocketClient _discord;
private readonly ISocketMessageChannel _channel;
public virtual IReadOnlyCollection<SocketMessage> Messages
=> ImmutableArray.Create<SocketMessage>();
public virtual IReadOnlyCollection<ISocketMessage> Messages
=> ImmutableArray.Create<ISocketMessage>();
public MessageManager(DiscordSocketClient discord, ISocketMessageChannel channel)
{
@@ -22,25 +23,25 @@ namespace Discord.WebSocket
_channel = channel;
}
public virtual void Add(SocketMessage message) { }
public virtual SocketMessage Remove(ulong id) => null;
public virtual SocketMessage Get(ulong id) => null;
public virtual void Add(ISocketMessage message) { }
public virtual ISocketMessage Remove(ulong id) => null;
public virtual ISocketMessage Get(ulong id) => null;
public virtual IImmutableList<SocketMessage> GetMany(ulong? fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch)
=> ImmutableArray.Create<SocketMessage>();
public virtual IImmutableList<ISocketMessage> GetMany(ulong? fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch)
=> ImmutableArray.Create<ISocketMessage>();
public virtual async Task<SocketMessage> DownloadAsync(ulong id)
public virtual async Task<ISocketMessage> DownloadAsync(ulong id)
{
var model = await _discord.ApiClient.GetChannelMessageAsync(_channel.Id, id).ConfigureAwait(false);
if (model != null)
return new SocketMessage(_channel, new User(model.Author.Value), model);
return Create(new User(model.Author.Value), model);
return null;
}
public async Task<IReadOnlyCollection<SocketMessage>> DownloadAsync(ulong? fromId, Direction dir, int limit)
public async Task<IReadOnlyCollection<ISocketMessage>> DownloadAsync(ulong? fromId, Direction dir, int limit)
{
//TODO: Test heavily, especially the ordering of messages
if (limit < 0) throw new ArgumentOutOfRangeException(nameof(limit));
if (limit == 0) return ImmutableArray<SocketMessage>.Empty;
if (limit == 0) return ImmutableArray<ISocketMessage>.Empty;
var cachedMessages = GetMany(fromId, dir, limit);
if (cachedMessages.Count == limit)
@@ -75,9 +76,17 @@ namespace Discord.WebSocket
else
user = newUser;
}
return new SocketMessage(_channel, user, x);
return Create(user, x);
})).ToImmutableArray();
}
}
public ISocketMessage Create(IUser author, Model model)
{
if (model.Type == MessageType.Default)
return new SocketUserMessage(_channel, author, model);
else
return new SocketSystemMessage(_channel, author, model);
}
}
}

View File

@@ -52,17 +52,21 @@ namespace Discord.WebSocket
{
return await _messages.DownloadAsync(fromMessageId, dir, limit).ConfigureAwait(false);
}
public SocketMessage AddMessage(ISocketUser author, MessageModel model)
public ISocketMessage CreateMessage(ISocketUser author, MessageModel model)
{
var msg = new SocketMessage(this, author, model);
return _messages.Create(author, model);
}
public ISocketMessage AddMessage(ISocketUser author, MessageModel model)
{
var msg = _messages.Create(author, model);
_messages.Add(msg);
return msg;
}
public SocketMessage GetMessage(ulong id)
public ISocketMessage GetMessage(ulong id)
{
return _messages.Get(id);
}
public SocketMessage RemoveMessage(ulong id)
public ISocketMessage RemoveMessage(ulong id)
{
return _messages.Remove(id);
}

View File

@@ -116,17 +116,21 @@ namespace Discord.WebSocket
{
return await _messages.DownloadAsync(fromMessageId, dir, limit).ConfigureAwait(false);
}
public SocketMessage AddMessage(ISocketUser author, MessageModel model)
public ISocketMessage CreateMessage(ISocketUser author, MessageModel model)
{
var msg = new SocketMessage(this, author, model);
return _messages.Create(author, model);
}
public ISocketMessage AddMessage(ISocketUser author, MessageModel model)
{
var msg = _messages.Create(author, model);
_messages.Add(msg);
return msg;
}
public SocketMessage GetMessage(ulong id)
public ISocketMessage GetMessage(ulong id)
{
return _messages.Get(id);
}
public SocketMessage RemoveMessage(ulong id)
public ISocketMessage RemoveMessage(ulong id)
{
return _messages.Remove(id);
}

View File

@@ -58,17 +58,21 @@ namespace Discord.WebSocket
return await _messages.DownloadAsync(fromMessageId, dir, limit).ConfigureAwait(false);
}
public SocketMessage AddMessage(ISocketUser author, MessageModel model)
public ISocketMessage CreateMessage(ISocketUser author, MessageModel model)
{
var msg = new SocketMessage(this, author, model);
return _messages.Create(author, model);
}
public ISocketMessage AddMessage(ISocketUser author, MessageModel model)
{
var msg = _messages.Create(author, model);
_messages.Add(msg);
return msg;
}
public SocketMessage GetMessage(ulong id)
public ISocketMessage GetMessage(ulong id)
{
return _messages.Get(id);
}
public SocketMessage RemoveMessage(ulong id)
public ISocketMessage RemoveMessage(ulong id)
{
return _messages.Remove(id);
}

View File

@@ -0,0 +1,13 @@
using Model = Discord.API.Message;
namespace Discord.WebSocket
{
internal interface ISocketMessage : IMessage
{
DiscordSocketClient Discord { get; }
new ISocketMessageChannel Channel { get; }
void Update(Model model, UpdateSource source);
ISocketMessage Clone();
}
}

View File

@@ -0,0 +1,20 @@
using Discord.Rest;
using Model = Discord.API.Message;
namespace Discord.WebSocket
{
internal class SocketSystemMessage : SystemMessage, ISocketMessage
{
internal override bool IsAttached => true;
public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient;
public new ISocketMessageChannel Channel => base.Channel as ISocketMessageChannel;
public SocketSystemMessage(ISocketMessageChannel channel, IUser author, Model model)
: base(channel, author, model)
{
}
public ISocketMessage Clone() => MemberwiseClone() as ISocketMessage;
}
}

View File

@@ -3,18 +3,18 @@ using Model = Discord.API.Message;
namespace Discord.WebSocket
{
internal class SocketMessage : Message
internal class SocketUserMessage : UserMessage, ISocketMessage
{
internal override bool IsAttached => true;
public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient;
public new ISocketMessageChannel Channel => base.Channel as ISocketMessageChannel;
public SocketMessage(ISocketMessageChannel channel, IUser author, Model model)
public SocketUserMessage(ISocketMessageChannel channel, IUser author, Model model)
: base(channel, author, model)
{
}
public SocketMessage Clone() => MemberwiseClone() as SocketMessage;
public ISocketMessage Clone() => MemberwiseClone() as ISocketMessage;
}
}