diff --git a/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs b/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs index 640420095..ae9457b92 100644 --- a/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs @@ -6,6 +6,6 @@ namespace Discord.Commands [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true, Inherited = true)] public abstract class PreconditionAttribute : Attribute { - public abstract Task CheckPermissions(IMessage context, Command executingCommand, object moduleInstance); + public abstract Task CheckPermissions(IUserMessage context, Command executingCommand, object moduleInstance); } } diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs index 41f4e4a97..48ada73d9 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs @@ -21,7 +21,7 @@ namespace Discord.Commands Contexts = contexts; } - public override Task CheckPermissions(IMessage context, Command executingCommand, object moduleInstance) + public override Task CheckPermissions(IUserMessage context, Command executingCommand, object moduleInstance) { bool isValid = false; diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequirePermissionAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequirePermissionAttribute.cs index db2668df4..b3215c419 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequirePermissionAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequirePermissionAttribute.cs @@ -20,7 +20,7 @@ namespace Discord.Commands GuildPermission = null; } - public override Task CheckPermissions(IMessage context, Command executingCommand, object moduleInstance) + public override Task CheckPermissions(IUserMessage context, Command executingCommand, object moduleInstance) { var guildUser = context.Author as IGuildUser; diff --git a/src/Discord.Net.Commands/Command.cs b/src/Discord.Net.Commands/Command.cs index f7bbc7d35..1f83c3a14 100644 --- a/src/Discord.Net.Commands/Command.cs +++ b/src/Discord.Net.Commands/Command.cs @@ -16,7 +16,7 @@ namespace Discord.Commands private static readonly ConcurrentDictionary, object>> _arrayConverters = new ConcurrentDictionary, object>>(); private readonly object _instance; - private readonly Func, Task> _action; + private readonly Func, Task> _action; public MethodInfo Source { get; } public Module Module { get; } @@ -66,7 +66,7 @@ namespace Discord.Commands _action = BuildAction(source); } - public async Task CheckPreconditions(IMessage context) + public async Task CheckPreconditions(IUserMessage context) { foreach (PreconditionAttribute precondition in Module.Preconditions) { @@ -85,7 +85,7 @@ namespace Discord.Commands return PreconditionResult.FromSuccess(); } - public async Task Parse(IMessage msg, SearchResult searchResult, PreconditionResult? preconditionResult = null) + public async Task Parse(IUserMessage context, SearchResult searchResult, PreconditionResult? preconditionResult = null) { if (!searchResult.IsSuccess) return ParseResult.FromError(searchResult); @@ -104,9 +104,9 @@ namespace Discord.Commands input = input.Substring(matchingAlias.Length); - return await CommandParser.ParseArgs(this, msg, input, 0).ConfigureAwait(false); + return await CommandParser.ParseArgs(this, context, input, 0).ConfigureAwait(false); } - public Task Execute(IMessage msg, ParseResult parseResult) + public Task Execute(IUserMessage context, ParseResult parseResult) { if (!parseResult.IsSuccess) return Task.FromResult(ExecuteResult.FromError(parseResult)); @@ -127,13 +127,13 @@ namespace Discord.Commands paramList[i] = parseResult.ParamValues[i].Values.First().Value; } - return Execute(msg, argList, paramList); + return Execute(context, argList, paramList); } - public async Task Execute(IMessage msg, IEnumerable argList, IEnumerable paramList) + public async Task Execute(IUserMessage context, IEnumerable argList, IEnumerable paramList) { try { - await _action.Invoke(msg, GenerateArgs(argList, paramList)).ConfigureAwait(false);//Note: This code may need context + await _action.Invoke(context, GenerateArgs(argList, paramList)).ConfigureAwait(false);//Note: This code may need context return ExecuteResult.FromSuccess(); } catch (Exception ex) @@ -150,8 +150,8 @@ namespace Discord.Commands private IReadOnlyList BuildParameters(MethodInfo methodInfo) { var parameters = methodInfo.GetParameters(); - if (parameters.Length == 0 || parameters[0].ParameterType != typeof(IMessage)) - throw new InvalidOperationException("The first parameter of a command must be IMessage."); + if (parameters.Length == 0 || parameters[0].ParameterType != typeof(IUserMessage)) + throw new InvalidOperationException($"The first parameter of a command must be {nameof(IUserMessage)}."); var paramBuilder = ImmutableArray.CreateBuilder(parameters.Length - 1); for (int i = 1; i < parameters.Length; i++) @@ -190,7 +190,7 @@ namespace Discord.Commands } return paramBuilder.ToImmutable(); } - private Func, Task> BuildAction(MethodInfo methodInfo) + private Func, Task> BuildAction(MethodInfo methodInfo) { if (methodInfo.ReturnType != typeof(Task)) throw new InvalidOperationException("Commands must return a non-generic Task."); diff --git a/src/Discord.Net.Commands/CommandParameter.cs b/src/Discord.Net.Commands/CommandParameter.cs index 1e358e8bf..f074876cf 100644 --- a/src/Discord.Net.Commands/CommandParameter.cs +++ b/src/Discord.Net.Commands/CommandParameter.cs @@ -32,7 +32,7 @@ namespace Discord.Commands DefaultValue = defaultValue; } - public async Task Parse(IMessage context, string input) + public async Task Parse(IUserMessage context, string input) { return await _reader.Read(context, input).ConfigureAwait(false); } diff --git a/src/Discord.Net.Commands/CommandParser.cs b/src/Discord.Net.Commands/CommandParser.cs index 78f1a691c..7338f6995 100644 --- a/src/Discord.Net.Commands/CommandParser.cs +++ b/src/Discord.Net.Commands/CommandParser.cs @@ -13,7 +13,7 @@ namespace Discord.Commands QuotedParameter } - public static async Task ParseArgs(Command command, IMessage context, string input, int startPos) + public static async Task ParseArgs(Command command, IUserMessage context, string input, int startPos) { CommandParameter curParam = null; StringBuilder argBuilder = new StringBuilder(input.Length); diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index 2a5cad097..814277b04 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -41,8 +41,9 @@ namespace Discord.Commands [typeof(DateTime)] = new SimpleTypeReader(), [typeof(DateTimeOffset)] = new SimpleTypeReader(), - [typeof(IMessage)] = new MessageTypeReader(), - + [typeof(IMessage)] = new MessageTypeReader(), + [typeof(IUserMessage)] = new MessageTypeReader(), + //[typeof(ISystemMessage)] = new MessageTypeReader(), [typeof(IChannel)] = new ChannelTypeReader(), [typeof(IDMChannel)] = new ChannelTypeReader(), [typeof(IGroupChannel)] = new ChannelTypeReader(), @@ -175,8 +176,8 @@ namespace Discord.Commands return false; } - public SearchResult Search(IMessage message, int argPos) => Search(message, message.Content.Substring(argPos)); - public SearchResult Search(IMessage message, string input) + public SearchResult Search(IUserMessage message, int argPos) => Search(message, message.Content.Substring(argPos)); + public SearchResult Search(IUserMessage message, string input) { string lowerInput = input.ToLowerInvariant(); var matches = _map.GetCommands(input).ToImmutableArray(); @@ -187,9 +188,9 @@ namespace Discord.Commands return SearchResult.FromError(CommandError.UnknownCommand, "Unknown command."); } - public Task Execute(IMessage message, int argPos, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) + public Task Execute(IUserMessage message, int argPos, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) => Execute(message, message.Content.Substring(argPos), multiMatchHandling); - public async Task Execute(IMessage message, string input, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) + public async Task Execute(IUserMessage message, string input, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) { var searchResult = Search(message, input); if (!searchResult.IsSuccess) diff --git a/src/Discord.Net.Commands/Extensions/MessageExtensions.cs b/src/Discord.Net.Commands/Extensions/MessageExtensions.cs index fffcf5175..05da07187 100644 --- a/src/Discord.Net.Commands/Extensions/MessageExtensions.cs +++ b/src/Discord.Net.Commands/Extensions/MessageExtensions.cs @@ -2,7 +2,7 @@ { public static class MessageExtensions { - public static bool HasCharPrefix(this IMessage msg, char c, ref int argPos) + public static bool HasCharPrefix(this IUserMessage msg, char c, ref int argPos) { var text = msg.Content; if (text.Length > 0 && text[0] == c) @@ -12,7 +12,7 @@ } return false; } - public static bool HasStringPrefix(this IMessage msg, string str, ref int argPos) + public static bool HasStringPrefix(this IUserMessage msg, string str, ref int argPos) { var text = msg.Content; if (text.StartsWith(str)) @@ -22,7 +22,7 @@ } return false; } - public static bool HasMentionPrefix(this IMessage msg, IUser user, ref int argPos) + public static bool HasMentionPrefix(this IUserMessage msg, IUser user, ref int argPos) { var text = msg.Content; if (text.Length <= 3 || text[0] != '<' || text[1] != '@') return false; diff --git a/src/Discord.Net.Commands/Readers/ChannelTypeReader.cs b/src/Discord.Net.Commands/Readers/ChannelTypeReader.cs index 0e2b930a3..463a9de4f 100644 --- a/src/Discord.Net.Commands/Readers/ChannelTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/ChannelTypeReader.cs @@ -9,7 +9,7 @@ namespace Discord.Commands internal class ChannelTypeReader : TypeReader where T : class, IChannel { - public override async Task Read(IMessage context, string input) + public override async Task Read(IUserMessage context, string input) { var guild = (context.Channel as IGuildChannel)?.Guild; diff --git a/src/Discord.Net.Commands/Readers/EnumTypeReader.cs b/src/Discord.Net.Commands/Readers/EnumTypeReader.cs index 031b007e0..54efa8024 100644 --- a/src/Discord.Net.Commands/Readers/EnumTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/EnumTypeReader.cs @@ -42,7 +42,7 @@ namespace Discord.Commands _enumsByValue = byValueBuilder.ToImmutable(); } - public override Task Read(IMessage context, string input) + public override Task Read(IUserMessage context, string input) { T baseValue; object enumValue; diff --git a/src/Discord.Net.Commands/Readers/MessageTypeReader.cs b/src/Discord.Net.Commands/Readers/MessageTypeReader.cs index 4ec25fe56..b509fc025 100644 --- a/src/Discord.Net.Commands/Readers/MessageTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/MessageTypeReader.cs @@ -3,16 +3,17 @@ using System.Threading.Tasks; namespace Discord.Commands { - internal class MessageTypeReader : TypeReader + internal class MessageTypeReader : TypeReader + where T : class, IMessage { - public override Task Read(IMessage context, string input) + public override Task Read(IUserMessage context, string input) { ulong id; //By Id (1.0) if (ulong.TryParse(input, NumberStyles.None, CultureInfo.InvariantCulture, out id)) { - var msg = context.Channel.GetCachedMessage(id); + var msg = context.Channel.GetCachedMessage(id) as T; if (msg != null) return Task.FromResult(TypeReaderResult.FromSuccess(msg)); } diff --git a/src/Discord.Net.Commands/Readers/RoleTypeReader.cs b/src/Discord.Net.Commands/Readers/RoleTypeReader.cs index 1ded6a43c..b386aba3c 100644 --- a/src/Discord.Net.Commands/Readers/RoleTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/RoleTypeReader.cs @@ -9,7 +9,7 @@ namespace Discord.Commands internal class RoleTypeReader : TypeReader where T : class, IRole { - public override Task Read(IMessage context, string input) + public override Task Read(IUserMessage context, string input) { var guild = (context.Channel as IGuildChannel)?.Guild; ulong id; diff --git a/src/Discord.Net.Commands/Readers/SimpleTypeReader.cs b/src/Discord.Net.Commands/Readers/SimpleTypeReader.cs index 615ec5014..72c729a3b 100644 --- a/src/Discord.Net.Commands/Readers/SimpleTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/SimpleTypeReader.cs @@ -11,7 +11,7 @@ namespace Discord.Commands _tryParse = PrimitiveParsers.Get(); } - public override Task Read(IMessage context, string input) + public override Task Read(IUserMessage context, string input) { T value; if (_tryParse(input, out value)) diff --git a/src/Discord.Net.Commands/Readers/TypeReader.cs b/src/Discord.Net.Commands/Readers/TypeReader.cs index d1dedd9c8..4d467ce55 100644 --- a/src/Discord.Net.Commands/Readers/TypeReader.cs +++ b/src/Discord.Net.Commands/Readers/TypeReader.cs @@ -4,6 +4,6 @@ namespace Discord.Commands { public abstract class TypeReader { - public abstract Task Read(IMessage context, string input); + public abstract Task Read(IUserMessage context, string input); } } diff --git a/src/Discord.Net.Commands/Readers/UserTypeReader.cs b/src/Discord.Net.Commands/Readers/UserTypeReader.cs index 0e881667d..46aaa777c 100644 --- a/src/Discord.Net.Commands/Readers/UserTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/UserTypeReader.cs @@ -9,7 +9,7 @@ namespace Discord.Commands internal class UserTypeReader : TypeReader where T : class, IUser { - public override async Task Read(IMessage context, string input) + public override async Task Read(IUserMessage context, string input) { var results = new Dictionary(); var guild = (context.Channel as IGuildChannel)?.Guild; diff --git a/src/Discord.Net/API/Rest/GetChannelMessagesParams.cs b/src/Discord.Net/API/Rest/GetChannelMessagesParams.cs index a5ec6c184..68660ab01 100644 --- a/src/Discord.Net/API/Rest/GetChannelMessagesParams.cs +++ b/src/Discord.Net/API/Rest/GetChannelMessagesParams.cs @@ -1,6 +1,4 @@ #pragma warning disable CS1591 -using Discord.Rest; - namespace Discord.API.Rest { public class GetChannelMessagesParams diff --git a/src/Discord.Net/Entities/Channels/IMessageChannel.cs b/src/Discord.Net/Entities/Channels/IMessageChannel.cs index 74e0188bc..a2288b0ee 100644 --- a/src/Discord.Net/Entities/Channels/IMessageChannel.cs +++ b/src/Discord.Net/Entities/Channels/IMessageChannel.cs @@ -10,11 +10,11 @@ namespace Discord IReadOnlyCollection CachedMessages { get; } /// Sends a message to this message channel. - Task SendMessageAsync(string text, bool isTTS = false); + Task SendMessageAsync(string text, bool isTTS = false); /// Sends a file to this text channel, with an optional caption. - Task SendFileAsync(string filePath, string text = null, bool isTTS = false); + Task SendFileAsync(string filePath, string text = null, bool isTTS = false); /// Sends a file to this text channel, with an optional caption. - Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false); + Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false); /// Gets a message from this message channel with the given id, or null if not found. Task GetMessageAsync(ulong id); /// Gets the message from this channel's cache with the given id, or null if not found. diff --git a/src/Discord.Net/Entities/Messages/IMessage.cs b/src/Discord.Net/Entities/Messages/IMessage.cs index ad20e5be6..0c83b1024 100644 --- a/src/Discord.Net/Entities/Messages/IMessage.cs +++ b/src/Discord.Net/Entities/Messages/IMessage.cs @@ -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 { - /// Gets the time of this message's last edit, if any. - DateTimeOffset? EditedTimestamp { get; } /// Returns true if this message was sent as a text-to-speech message. bool IsTTS { get; } /// Returns true if this message was added to its channel's pinned messages. @@ -17,13 +13,14 @@ namespace Discord string Content { get; } /// Gets the time this message was sent. DateTimeOffset Timestamp { get; } - - /// Gets the type of this message. - MessageType Type { get; } + /// Gets the time of this message's last edit, if any. + DateTimeOffset? EditedTimestamp { get; } + /// Gets the channel this message was sent to. IMessageChannel Channel { get; } /// Gets the author of this message. IUser Author { get; } + /// Returns a collection of all attachments included in this message. IReadOnlyCollection Attachments { get; } /// Returns a collection of all embeds included in this message. @@ -34,25 +31,5 @@ namespace Discord IReadOnlyCollection MentionedRoles { get; } /// Returns a collection of users mentioned in this message. IReadOnlyCollection MentionedUsers { get; } - - /// Modifies this message. - Task ModifyAsync(Action func); - /// Adds this message to its channel's pinned messages. - Task PinAsync(); - /// Removes this message from its channel's pinned messages. - Task UnpinAsync(); - - /// Transforms this message's text into a human readable form, resolving things like mentions to that object's name. - string Resolve(int startIndex, int length, - UserMentionHandling userHandling = UserMentionHandling.Name, - ChannelMentionHandling channelHandling = ChannelMentionHandling.Name, - RoleMentionHandling roleHandling = RoleMentionHandling.Name, - EveryoneMentionHandling everyoneHandling = EveryoneMentionHandling.Ignore); - /// Transforms this message's text into a human readable form, resolving things like mentions to that object's name. - string Resolve( - UserMentionHandling userHandling = UserMentionHandling.Name, - ChannelMentionHandling channelHandling = ChannelMentionHandling.Name, - RoleMentionHandling roleHandling = RoleMentionHandling.Name, - EveryoneMentionHandling everyoneHandling = EveryoneMentionHandling.Ignore); } } \ No newline at end of file diff --git a/src/Discord.Net/Entities/Messages/ISystemMessage.cs b/src/Discord.Net/Entities/Messages/ISystemMessage.cs new file mode 100644 index 000000000..d2e23d147 --- /dev/null +++ b/src/Discord.Net/Entities/Messages/ISystemMessage.cs @@ -0,0 +1,8 @@ +namespace Discord +{ + public interface ISystemMessage : IMessage + { + /// Gets the type of this system message. + MessageType Type { get; } + } +} diff --git a/src/Discord.Net/Entities/Messages/IUserMessage.cs b/src/Discord.Net/Entities/Messages/IUserMessage.cs new file mode 100644 index 000000000..fd170dacb --- /dev/null +++ b/src/Discord.Net/Entities/Messages/IUserMessage.cs @@ -0,0 +1,29 @@ +using Discord.API.Rest; +using System; +using System.Threading.Tasks; + +namespace Discord +{ + public interface IUserMessage : IMessage, IDeletable + { + /// Modifies this message. + Task ModifyAsync(Action func); + /// Adds this message to its channel's pinned messages. + Task PinAsync(); + /// Removes this message from its channel's pinned messages. + Task UnpinAsync(); + + /// Transforms this message's text into a human readable form, resolving mentions to that object's name. + string Resolve(int startIndex, int length, + UserMentionHandling userHandling = UserMentionHandling.Name, + ChannelMentionHandling channelHandling = ChannelMentionHandling.Name, + RoleMentionHandling roleHandling = RoleMentionHandling.Name, + EveryoneMentionHandling everyoneHandling = EveryoneMentionHandling.Ignore); + /// Transforms this message's text into a human readable form, resolving mentions to that object's name. + string Resolve( + UserMentionHandling userHandling = UserMentionHandling.Name, + ChannelMentionHandling channelHandling = ChannelMentionHandling.Name, + RoleMentionHandling roleHandling = RoleMentionHandling.Name, + EveryoneMentionHandling everyoneHandling = EveryoneMentionHandling.Ignore); + } +} diff --git a/src/Discord.Net/Rest/Entities/Channels/DMChannel.cs b/src/Discord.Net/Rest/Entities/Channels/DMChannel.cs index 79d52834f..6097a7f00 100644 --- a/src/Discord.Net/Rest/Entities/Channels/DMChannel.cs +++ b/src/Discord.Net/Rest/Entities/Channels/DMChannel.cs @@ -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 SendMessageAsync(string text, bool isTTS) + public async Task 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 SendFileAsync(string filePath, string text, bool isTTS) + public async Task 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 SendFileAsync(Stream stream, string filename, string text, bool isTTS) + public async Task 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 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> 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> 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 messages) { @@ -110,14 +111,26 @@ namespace Discord.Rest public async Task> 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)"; diff --git a/src/Discord.Net/Rest/Entities/Channels/GroupChannel.cs b/src/Discord.Net/Rest/Entities/Channels/GroupChannel.cs index 1077405bb..3ed544087 100644 --- a/src/Discord.Net/Rest/Entities/Channels/GroupChannel.cs +++ b/src/Discord.Net/Rest/Entities/Channels/GroupChannel.cs @@ -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(ImmutableArray.Create(currentUser)).ToReadOnlyCollection(_users); } - public async Task SendMessageAsync(string text, bool isTTS) + public async Task 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 SendFileAsync(string filePath, string text, bool isTTS) + public async Task 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 SendFileAsync(Stream stream, string filename, string text, bool isTTS) + public async Task 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 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> 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> 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 messages) { @@ -135,7 +136,7 @@ namespace Discord.Rest public async Task> 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)"; diff --git a/src/Discord.Net/Rest/Entities/Channels/TextChannel.cs b/src/Discord.Net/Rest/Entities/Channels/TextChannel.cs index 64ace81e6..5d97bca7c 100644 --- a/src/Discord.Net/Rest/Entities/Channels/TextChannel.cs +++ b/src/Discord.Net/Rest/Entities/Channels/TextChannel.cs @@ -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 SendMessageAsync(string text, bool isTTS) + public async Task 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 SendFileAsync(string filePath, string text, bool isTTS) + public async Task 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 SendFileAsync(Stream stream, string filename, string text, bool isTTS) + public async Task 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 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> 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> 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 messages) { @@ -105,7 +106,7 @@ namespace Discord.Rest public async Task> 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; diff --git a/src/Discord.Net/Rest/Entities/Messages/Message.cs b/src/Discord.Net/Rest/Entities/Messages/Message.cs index 3a805e166..1b5c025e9 100644 --- a/src/Discord.Net/Rest/Entities/Messages/Message.cs +++ b/src/Discord.Net/Rest/Entities/Messages/Message.cs @@ -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 Attachments { get; private set; } - public IReadOnlyCollection Embeds { get; private set; } - public IReadOnlyCollection MentionedChannelIds { get; private set; } - public IReadOnlyCollection MentionedRoles { get; private set; } - public IReadOnlyCollection MentionedUsers { get; private set; } public override DiscordRestClient Discord => (Channel as Entity).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 Attachments => ImmutableArray.Create(); + public virtual IReadOnlyCollection Embeds => ImmutableArray.Create(); + public virtual IReadOnlyCollection MentionedChannelIds => ImmutableArray.Create(); + public virtual IReadOnlyCollection MentionedRoles => ImmutableArray.Create(); + public virtual IReadOnlyCollection MentionedUsers => ImmutableArray.Create(); + 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(); - MentionedChannelIds = ImmutableArray.Create(); - MentionedRoles = ImmutableArray.Create(); 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(); - } - - 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(); - } - - 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(); - } 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]" : "")}"; diff --git a/src/Discord.Net/Rest/Entities/Messages/SystemMessage.cs b/src/Discord.Net/Rest/Entities/Messages/SystemMessage.cs new file mode 100644 index 000000000..a715c6493 --- /dev/null +++ b/src/Discord.Net/Rest/Entities/Messages/SystemMessage.cs @@ -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).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})" : "")}"; + } +} diff --git a/src/Discord.Net/Rest/Entities/Messages/UserMessage.cs b/src/Discord.Net/Rest/Entities/Messages/UserMessage.cs new file mode 100644 index 000000000..06c904232 --- /dev/null +++ b/src/Discord.Net/Rest/Entities/Messages/UserMessage.cs @@ -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 _attachments; + private IReadOnlyCollection _embeds; + private IReadOnlyCollection _mentionedChannelIds; + private IReadOnlyCollection _mentionedRoles; + private IReadOnlyCollection _mentionedUsers; + + public override DiscordRestClient Discord => (Channel as Entity).Discord; + public override bool IsTTS => _isTTS; + public override bool IsPinned => _isPinned; + public override DateTimeOffset? EditedTimestamp => DateTimeUtils.FromTicks(_editedTimestampTicks); + + public override IReadOnlyCollection Attachments => _attachments; + public override IReadOnlyCollection Embeds => _embeds; + public override IReadOnlyCollection MentionedChannelIds => _mentionedChannelIds; + public override IReadOnlyCollection MentionedRoles => _mentionedRoles; + public override IReadOnlyCollection MentionedUsers => _mentionedUsers; + + public UserMessage(IMessageChannel channel, IUser author, Model model) + : base(channel, author, model) + { + _mentionedChannelIds = ImmutableArray.Create(); + _mentionedRoles = ImmutableArray.Create(); + _mentionedUsers = ImmutableArray.Create(); + + 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(); + } + + 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(); + } + + ImmutableArray mentions = ImmutableArray.Create(); + 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 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]" : "")}"; + } +} diff --git a/src/Discord.Net/WebSocket/DiscordSocketClient.cs b/src/Discord.Net/WebSocket/DiscordSocketClient.cs index ba13b927c..a77110c66 100644 --- a/src/Discord.Net/WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net/WebSocket/DiscordSocketClient.cs @@ -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); diff --git a/src/Discord.Net/WebSocket/Entities/Channels/ISocketMessageChannel.cs b/src/Discord.Net/WebSocket/Entities/Channels/ISocketMessageChannel.cs index d576f0452..80706970f 100644 --- a/src/Discord.Net/WebSocket/Entities/Channels/ISocketMessageChannel.cs +++ b/src/Discord.Net/WebSocket/Entities/Channels/ISocketMessageChannel.cs @@ -7,9 +7,10 @@ namespace Discord.WebSocket { IReadOnlyCollection 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); } diff --git a/src/Discord.Net/WebSocket/Entities/Channels/MessageCache.cs b/src/Discord.Net/WebSocket/Entities/Channels/MessageCache.cs index e48135b03..7fafebba5 100644 --- a/src/Discord.Net/WebSocket/Entities/Channels/MessageCache.cs +++ b/src/Discord.Net/WebSocket/Entities/Channels/MessageCache.cs @@ -9,51 +9,51 @@ namespace Discord.WebSocket { internal class MessageCache : MessageManager { - private readonly ConcurrentDictionary _messages; + private readonly ConcurrentDictionary _messages; private readonly ConcurrentQueue _orderedMessages; private readonly int _size; - public override IReadOnlyCollection Messages => _messages.ToReadOnlyCollection(); + public override IReadOnlyCollection Messages => _messages.ToReadOnlyCollection(); public MessageCache(DiscordSocketClient discord, ISocketMessageChannel channel) : base(discord, channel) { _size = discord.MessageCacheSize; - _messages = new ConcurrentDictionary(1, (int)(_size * 1.05)); + _messages = new ConcurrentDictionary(1, (int)(_size * 1.05)); _orderedMessages = new ConcurrentQueue(); } - 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 GetMany(ulong? fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) + public override IImmutableList GetMany(ulong? fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) { if (limit < 0) throw new ArgumentOutOfRangeException(nameof(limit)); - if (limit == 0) return ImmutableArray.Empty; + if (limit == 0) return ImmutableArray.Empty; IEnumerable 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 DownloadAsync(ulong id) + public override async Task DownloadAsync(ulong id) { var msg = Get(id); if (msg != null) diff --git a/src/Discord.Net/WebSocket/Entities/Channels/MessageManager.cs b/src/Discord.Net/WebSocket/Entities/Channels/MessageManager.cs index 3eb253bea..713c5b635 100644 --- a/src/Discord.Net/WebSocket/Entities/Channels/MessageManager.cs +++ b/src/Discord.Net/WebSocket/Entities/Channels/MessageManager.cs @@ -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 Messages - => ImmutableArray.Create(); + public virtual IReadOnlyCollection Messages + => ImmutableArray.Create(); 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 GetMany(ulong? fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) - => ImmutableArray.Create(); + public virtual IImmutableList GetMany(ulong? fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) + => ImmutableArray.Create(); - public virtual async Task DownloadAsync(ulong id) + public virtual async Task 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> DownloadAsync(ulong? fromId, Direction dir, int limit) + public async Task> 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.Empty; + if (limit == 0) return ImmutableArray.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); + } } } diff --git a/src/Discord.Net/WebSocket/Entities/Channels/SocketDMChannel.cs b/src/Discord.Net/WebSocket/Entities/Channels/SocketDMChannel.cs index 6751f4899..958c71bba 100644 --- a/src/Discord.Net/WebSocket/Entities/Channels/SocketDMChannel.cs +++ b/src/Discord.Net/WebSocket/Entities/Channels/SocketDMChannel.cs @@ -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); } diff --git a/src/Discord.Net/WebSocket/Entities/Channels/SocketGroupChannel.cs b/src/Discord.Net/WebSocket/Entities/Channels/SocketGroupChannel.cs index 8751610cf..8d456bb55 100644 --- a/src/Discord.Net/WebSocket/Entities/Channels/SocketGroupChannel.cs +++ b/src/Discord.Net/WebSocket/Entities/Channels/SocketGroupChannel.cs @@ -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); } diff --git a/src/Discord.Net/WebSocket/Entities/Channels/SocketTextChannel.cs b/src/Discord.Net/WebSocket/Entities/Channels/SocketTextChannel.cs index 6046453c7..72f5717c3 100644 --- a/src/Discord.Net/WebSocket/Entities/Channels/SocketTextChannel.cs +++ b/src/Discord.Net/WebSocket/Entities/Channels/SocketTextChannel.cs @@ -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); } diff --git a/src/Discord.Net/WebSocket/Entities/Messages/ISocketMessage.cs b/src/Discord.Net/WebSocket/Entities/Messages/ISocketMessage.cs new file mode 100644 index 000000000..818d62ec0 --- /dev/null +++ b/src/Discord.Net/WebSocket/Entities/Messages/ISocketMessage.cs @@ -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(); + } +} diff --git a/src/Discord.Net/WebSocket/Entities/Messages/SocketSystemMessage.cs b/src/Discord.Net/WebSocket/Entities/Messages/SocketSystemMessage.cs new file mode 100644 index 000000000..bcd95ddf2 --- /dev/null +++ b/src/Discord.Net/WebSocket/Entities/Messages/SocketSystemMessage.cs @@ -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; + } +} diff --git a/src/Discord.Net/WebSocket/Entities/Messages/SocketMessage.cs b/src/Discord.Net/WebSocket/Entities/Messages/SocketUserMessage.cs similarity index 62% rename from src/Discord.Net/WebSocket/Entities/Messages/SocketMessage.cs rename to src/Discord.Net/WebSocket/Entities/Messages/SocketUserMessage.cs index 51e02a30c..31ef2082a 100644 --- a/src/Discord.Net/WebSocket/Entities/Messages/SocketMessage.cs +++ b/src/Discord.Net/WebSocket/Entities/Messages/SocketUserMessage.cs @@ -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; } }