| @@ -6,6 +6,6 @@ namespace Discord.Commands | |||||
| [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true, Inherited = true)] | [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true, Inherited = true)] | ||||
| public abstract class PreconditionAttribute : Attribute | public abstract class PreconditionAttribute : Attribute | ||||
| { | { | ||||
| public abstract Task<PreconditionResult> CheckPermissions(IMessage context, Command executingCommand, object moduleInstance); | |||||
| public abstract Task<PreconditionResult> CheckPermissions(IUserMessage context, Command executingCommand, object moduleInstance); | |||||
| } | } | ||||
| } | } | ||||
| @@ -21,7 +21,7 @@ namespace Discord.Commands | |||||
| Contexts = contexts; | Contexts = contexts; | ||||
| } | } | ||||
| public override Task<PreconditionResult> CheckPermissions(IMessage context, Command executingCommand, object moduleInstance) | |||||
| public override Task<PreconditionResult> CheckPermissions(IUserMessage context, Command executingCommand, object moduleInstance) | |||||
| { | { | ||||
| bool isValid = false; | bool isValid = false; | ||||
| @@ -20,7 +20,7 @@ namespace Discord.Commands | |||||
| GuildPermission = null; | GuildPermission = null; | ||||
| } | } | ||||
| public override Task<PreconditionResult> CheckPermissions(IMessage context, Command executingCommand, object moduleInstance) | |||||
| public override Task<PreconditionResult> CheckPermissions(IUserMessage context, Command executingCommand, object moduleInstance) | |||||
| { | { | ||||
| var guildUser = context.Author as IGuildUser; | var guildUser = context.Author as IGuildUser; | ||||
| @@ -16,7 +16,7 @@ namespace Discord.Commands | |||||
| private static readonly ConcurrentDictionary<Type, Func<IEnumerable<object>, object>> _arrayConverters = new ConcurrentDictionary<Type, Func<IEnumerable<object>, object>>(); | private static readonly ConcurrentDictionary<Type, Func<IEnumerable<object>, object>> _arrayConverters = new ConcurrentDictionary<Type, Func<IEnumerable<object>, object>>(); | ||||
| private readonly object _instance; | private readonly object _instance; | ||||
| private readonly Func<IMessage, IReadOnlyList<object>, Task> _action; | |||||
| private readonly Func<IUserMessage, IReadOnlyList<object>, Task> _action; | |||||
| public MethodInfo Source { get; } | public MethodInfo Source { get; } | ||||
| public Module Module { get; } | public Module Module { get; } | ||||
| @@ -66,7 +66,7 @@ namespace Discord.Commands | |||||
| _action = BuildAction(source); | _action = BuildAction(source); | ||||
| } | } | ||||
| public async Task<PreconditionResult> CheckPreconditions(IMessage context) | |||||
| public async Task<PreconditionResult> CheckPreconditions(IUserMessage context) | |||||
| { | { | ||||
| foreach (PreconditionAttribute precondition in Module.Preconditions) | foreach (PreconditionAttribute precondition in Module.Preconditions) | ||||
| { | { | ||||
| @@ -85,7 +85,7 @@ namespace Discord.Commands | |||||
| return PreconditionResult.FromSuccess(); | return PreconditionResult.FromSuccess(); | ||||
| } | } | ||||
| public async Task<ParseResult> Parse(IMessage msg, SearchResult searchResult, PreconditionResult? preconditionResult = null) | |||||
| public async Task<ParseResult> Parse(IUserMessage context, SearchResult searchResult, PreconditionResult? preconditionResult = null) | |||||
| { | { | ||||
| if (!searchResult.IsSuccess) | if (!searchResult.IsSuccess) | ||||
| return ParseResult.FromError(searchResult); | return ParseResult.FromError(searchResult); | ||||
| @@ -104,9 +104,9 @@ namespace Discord.Commands | |||||
| input = input.Substring(matchingAlias.Length); | 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<ExecuteResult> Execute(IMessage msg, ParseResult parseResult) | |||||
| public Task<ExecuteResult> Execute(IUserMessage context, ParseResult parseResult) | |||||
| { | { | ||||
| if (!parseResult.IsSuccess) | if (!parseResult.IsSuccess) | ||||
| return Task.FromResult(ExecuteResult.FromError(parseResult)); | return Task.FromResult(ExecuteResult.FromError(parseResult)); | ||||
| @@ -127,13 +127,13 @@ namespace Discord.Commands | |||||
| paramList[i] = parseResult.ParamValues[i].Values.First().Value; | paramList[i] = parseResult.ParamValues[i].Values.First().Value; | ||||
| } | } | ||||
| return Execute(msg, argList, paramList); | |||||
| return Execute(context, argList, paramList); | |||||
| } | } | ||||
| public async Task<ExecuteResult> Execute(IMessage msg, IEnumerable<object> argList, IEnumerable<object> paramList) | |||||
| public async Task<ExecuteResult> Execute(IUserMessage context, IEnumerable<object> argList, IEnumerable<object> paramList) | |||||
| { | { | ||||
| try | 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(); | return ExecuteResult.FromSuccess(); | ||||
| } | } | ||||
| catch (Exception ex) | catch (Exception ex) | ||||
| @@ -150,8 +150,8 @@ namespace Discord.Commands | |||||
| private IReadOnlyList<CommandParameter> BuildParameters(MethodInfo methodInfo) | private IReadOnlyList<CommandParameter> BuildParameters(MethodInfo methodInfo) | ||||
| { | { | ||||
| var parameters = methodInfo.GetParameters(); | 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<CommandParameter>(parameters.Length - 1); | var paramBuilder = ImmutableArray.CreateBuilder<CommandParameter>(parameters.Length - 1); | ||||
| for (int i = 1; i < parameters.Length; i++) | for (int i = 1; i < parameters.Length; i++) | ||||
| @@ -190,7 +190,7 @@ namespace Discord.Commands | |||||
| } | } | ||||
| return paramBuilder.ToImmutable(); | return paramBuilder.ToImmutable(); | ||||
| } | } | ||||
| private Func<IMessage, IReadOnlyList<object>, Task> BuildAction(MethodInfo methodInfo) | |||||
| private Func<IUserMessage, IReadOnlyList<object>, Task> BuildAction(MethodInfo methodInfo) | |||||
| { | { | ||||
| if (methodInfo.ReturnType != typeof(Task)) | if (methodInfo.ReturnType != typeof(Task)) | ||||
| throw new InvalidOperationException("Commands must return a non-generic Task."); | throw new InvalidOperationException("Commands must return a non-generic Task."); | ||||
| @@ -32,7 +32,7 @@ namespace Discord.Commands | |||||
| DefaultValue = defaultValue; | DefaultValue = defaultValue; | ||||
| } | } | ||||
| public async Task<TypeReaderResult> Parse(IMessage context, string input) | |||||
| public async Task<TypeReaderResult> Parse(IUserMessage context, string input) | |||||
| { | { | ||||
| return await _reader.Read(context, input).ConfigureAwait(false); | return await _reader.Read(context, input).ConfigureAwait(false); | ||||
| } | } | ||||
| @@ -13,7 +13,7 @@ namespace Discord.Commands | |||||
| QuotedParameter | QuotedParameter | ||||
| } | } | ||||
| public static async Task<ParseResult> ParseArgs(Command command, IMessage context, string input, int startPos) | |||||
| public static async Task<ParseResult> ParseArgs(Command command, IUserMessage context, string input, int startPos) | |||||
| { | { | ||||
| CommandParameter curParam = null; | CommandParameter curParam = null; | ||||
| StringBuilder argBuilder = new StringBuilder(input.Length); | StringBuilder argBuilder = new StringBuilder(input.Length); | ||||
| @@ -41,8 +41,9 @@ namespace Discord.Commands | |||||
| [typeof(DateTime)] = new SimpleTypeReader<DateTime>(), | [typeof(DateTime)] = new SimpleTypeReader<DateTime>(), | ||||
| [typeof(DateTimeOffset)] = new SimpleTypeReader<DateTimeOffset>(), | [typeof(DateTimeOffset)] = new SimpleTypeReader<DateTimeOffset>(), | ||||
| [typeof(IMessage)] = new MessageTypeReader(), | |||||
| [typeof(IMessage)] = new MessageTypeReader<IMessage>(), | |||||
| [typeof(IUserMessage)] = new MessageTypeReader<IUserMessage>(), | |||||
| //[typeof(ISystemMessage)] = new MessageTypeReader<ISystemMessage>(), | |||||
| [typeof(IChannel)] = new ChannelTypeReader<IChannel>(), | [typeof(IChannel)] = new ChannelTypeReader<IChannel>(), | ||||
| [typeof(IDMChannel)] = new ChannelTypeReader<IDMChannel>(), | [typeof(IDMChannel)] = new ChannelTypeReader<IDMChannel>(), | ||||
| [typeof(IGroupChannel)] = new ChannelTypeReader<IGroupChannel>(), | [typeof(IGroupChannel)] = new ChannelTypeReader<IGroupChannel>(), | ||||
| @@ -175,8 +176,8 @@ namespace Discord.Commands | |||||
| return false; | 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(); | string lowerInput = input.ToLowerInvariant(); | ||||
| var matches = _map.GetCommands(input).ToImmutableArray(); | var matches = _map.GetCommands(input).ToImmutableArray(); | ||||
| @@ -187,9 +188,9 @@ namespace Discord.Commands | |||||
| return SearchResult.FromError(CommandError.UnknownCommand, "Unknown command."); | return SearchResult.FromError(CommandError.UnknownCommand, "Unknown command."); | ||||
| } | } | ||||
| public Task<IResult> Execute(IMessage message, int argPos, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) | |||||
| public Task<IResult> Execute(IUserMessage message, int argPos, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) | |||||
| => Execute(message, message.Content.Substring(argPos), multiMatchHandling); | => Execute(message, message.Content.Substring(argPos), multiMatchHandling); | ||||
| public async Task<IResult> Execute(IMessage message, string input, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) | |||||
| public async Task<IResult> Execute(IUserMessage message, string input, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) | |||||
| { | { | ||||
| var searchResult = Search(message, input); | var searchResult = Search(message, input); | ||||
| if (!searchResult.IsSuccess) | if (!searchResult.IsSuccess) | ||||
| @@ -2,7 +2,7 @@ | |||||
| { | { | ||||
| public static class MessageExtensions | 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; | var text = msg.Content; | ||||
| if (text.Length > 0 && text[0] == c) | if (text.Length > 0 && text[0] == c) | ||||
| @@ -12,7 +12,7 @@ | |||||
| } | } | ||||
| return false; | 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; | var text = msg.Content; | ||||
| if (text.StartsWith(str)) | if (text.StartsWith(str)) | ||||
| @@ -22,7 +22,7 @@ | |||||
| } | } | ||||
| return false; | 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; | var text = msg.Content; | ||||
| if (text.Length <= 3 || text[0] != '<' || text[1] != '@') return false; | if (text.Length <= 3 || text[0] != '<' || text[1] != '@') return false; | ||||
| @@ -9,7 +9,7 @@ namespace Discord.Commands | |||||
| internal class ChannelTypeReader<T> : TypeReader | internal class ChannelTypeReader<T> : TypeReader | ||||
| where T : class, IChannel | where T : class, IChannel | ||||
| { | { | ||||
| public override async Task<TypeReaderResult> Read(IMessage context, string input) | |||||
| public override async Task<TypeReaderResult> Read(IUserMessage context, string input) | |||||
| { | { | ||||
| var guild = (context.Channel as IGuildChannel)?.Guild; | var guild = (context.Channel as IGuildChannel)?.Guild; | ||||
| @@ -42,7 +42,7 @@ namespace Discord.Commands | |||||
| _enumsByValue = byValueBuilder.ToImmutable(); | _enumsByValue = byValueBuilder.ToImmutable(); | ||||
| } | } | ||||
| public override Task<TypeReaderResult> Read(IMessage context, string input) | |||||
| public override Task<TypeReaderResult> Read(IUserMessage context, string input) | |||||
| { | { | ||||
| T baseValue; | T baseValue; | ||||
| object enumValue; | object enumValue; | ||||
| @@ -3,16 +3,17 @@ using System.Threading.Tasks; | |||||
| namespace Discord.Commands | namespace Discord.Commands | ||||
| { | { | ||||
| internal class MessageTypeReader : TypeReader | |||||
| internal class MessageTypeReader<T> : TypeReader | |||||
| where T : class, IMessage | |||||
| { | { | ||||
| public override Task<TypeReaderResult> Read(IMessage context, string input) | |||||
| public override Task<TypeReaderResult> Read(IUserMessage context, string input) | |||||
| { | { | ||||
| ulong id; | ulong id; | ||||
| //By Id (1.0) | //By Id (1.0) | ||||
| if (ulong.TryParse(input, NumberStyles.None, CultureInfo.InvariantCulture, out id)) | 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) | if (msg != null) | ||||
| return Task.FromResult(TypeReaderResult.FromSuccess(msg)); | return Task.FromResult(TypeReaderResult.FromSuccess(msg)); | ||||
| } | } | ||||
| @@ -9,7 +9,7 @@ namespace Discord.Commands | |||||
| internal class RoleTypeReader<T> : TypeReader | internal class RoleTypeReader<T> : TypeReader | ||||
| where T : class, IRole | where T : class, IRole | ||||
| { | { | ||||
| public override Task<TypeReaderResult> Read(IMessage context, string input) | |||||
| public override Task<TypeReaderResult> Read(IUserMessage context, string input) | |||||
| { | { | ||||
| var guild = (context.Channel as IGuildChannel)?.Guild; | var guild = (context.Channel as IGuildChannel)?.Guild; | ||||
| ulong id; | ulong id; | ||||
| @@ -11,7 +11,7 @@ namespace Discord.Commands | |||||
| _tryParse = PrimitiveParsers.Get<T>(); | _tryParse = PrimitiveParsers.Get<T>(); | ||||
| } | } | ||||
| public override Task<TypeReaderResult> Read(IMessage context, string input) | |||||
| public override Task<TypeReaderResult> Read(IUserMessage context, string input) | |||||
| { | { | ||||
| T value; | T value; | ||||
| if (_tryParse(input, out value)) | if (_tryParse(input, out value)) | ||||
| @@ -4,6 +4,6 @@ namespace Discord.Commands | |||||
| { | { | ||||
| public abstract class TypeReader | public abstract class TypeReader | ||||
| { | { | ||||
| public abstract Task<TypeReaderResult> Read(IMessage context, string input); | |||||
| public abstract Task<TypeReaderResult> Read(IUserMessage context, string input); | |||||
| } | } | ||||
| } | } | ||||
| @@ -9,7 +9,7 @@ namespace Discord.Commands | |||||
| internal class UserTypeReader<T> : TypeReader | internal class UserTypeReader<T> : TypeReader | ||||
| where T : class, IUser | where T : class, IUser | ||||
| { | { | ||||
| public override async Task<TypeReaderResult> Read(IMessage context, string input) | |||||
| public override async Task<TypeReaderResult> Read(IUserMessage context, string input) | |||||
| { | { | ||||
| var results = new Dictionary<ulong, TypeReaderValue>(); | var results = new Dictionary<ulong, TypeReaderValue>(); | ||||
| var guild = (context.Channel as IGuildChannel)?.Guild; | var guild = (context.Channel as IGuildChannel)?.Guild; | ||||
| @@ -1,6 +1,4 @@ | |||||
| #pragma warning disable CS1591 | #pragma warning disable CS1591 | ||||
| using Discord.Rest; | |||||
| namespace Discord.API.Rest | namespace Discord.API.Rest | ||||
| { | { | ||||
| public class GetChannelMessagesParams | public class GetChannelMessagesParams | ||||
| @@ -10,11 +10,11 @@ namespace Discord | |||||
| IReadOnlyCollection<IMessage> CachedMessages { get; } | IReadOnlyCollection<IMessage> CachedMessages { get; } | ||||
| /// <summary> Sends a message to this message channel. </summary> | /// <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> | /// <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> | /// <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> | /// <summary> Gets a message from this message channel with the given id, or null if not found. </summary> | ||||
| Task<IMessage> GetMessageAsync(ulong id); | Task<IMessage> GetMessageAsync(ulong id); | ||||
| /// <summary> Gets the message from this channel's cache with the given id, or null if not found. </summary> | /// <summary> Gets the message from this channel's cache with the given id, or null if not found. </summary> | ||||
| @@ -1,14 +1,10 @@ | |||||
| using System; | using System; | ||||
| using System.Threading.Tasks; | |||||
| using Discord.API.Rest; | |||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| namespace Discord | 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> | /// <summary> Returns true if this message was sent as a text-to-speech message. </summary> | ||||
| bool IsTTS { get; } | bool IsTTS { get; } | ||||
| /// <summary> Returns true if this message was added to its channel's pinned messages. </summary> | /// <summary> Returns true if this message was added to its channel's pinned messages. </summary> | ||||
| @@ -17,13 +13,14 @@ namespace Discord | |||||
| string Content { get; } | string Content { get; } | ||||
| /// <summary> Gets the time this message was sent. </summary> | /// <summary> Gets the time this message was sent. </summary> | ||||
| DateTimeOffset Timestamp { get; } | 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> | /// <summary> Gets the channel this message was sent to. </summary> | ||||
| IMessageChannel Channel { get; } | IMessageChannel Channel { get; } | ||||
| /// <summary> Gets the author of this message. </summary> | /// <summary> Gets the author of this message. </summary> | ||||
| IUser Author { get; } | IUser Author { get; } | ||||
| /// <summary> Returns a collection of all attachments included in this message. </summary> | /// <summary> Returns a collection of all attachments included in this message. </summary> | ||||
| IReadOnlyCollection<IAttachment> Attachments { get; } | IReadOnlyCollection<IAttachment> Attachments { get; } | ||||
| /// <summary> Returns a collection of all embeds included in this message. </summary> | /// <summary> Returns a collection of all embeds included in this message. </summary> | ||||
| @@ -34,25 +31,5 @@ namespace Discord | |||||
| IReadOnlyCollection<IRole> MentionedRoles { get; } | IReadOnlyCollection<IRole> MentionedRoles { get; } | ||||
| /// <summary> Returns a collection of users mentioned in this message. </summary> | /// <summary> Returns a collection of users mentioned in this message. </summary> | ||||
| IReadOnlyCollection<IUser> MentionedUsers { get; } | 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); | |||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,8 @@ | |||||
| namespace Discord | |||||
| { | |||||
| public interface ISystemMessage : IMessage | |||||
| { | |||||
| /// <summary> Gets the type of this system message. </summary> | |||||
| MessageType Type { get; } | |||||
| } | |||||
| } | |||||
| @@ -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); | |||||
| } | |||||
| } | |||||
| @@ -7,6 +7,7 @@ using System.IO; | |||||
| using System.Linq; | using System.Linq; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using Model = Discord.API.Channel; | using Model = Discord.API.Channel; | ||||
| using MessageModel = Discord.API.Message; | |||||
| namespace Discord.Rest | namespace Discord.Rest | ||||
| { | { | ||||
| @@ -62,46 +63,46 @@ namespace Discord.Rest | |||||
| return ImmutableArray.Create(currentUser, Recipient); | 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 args = new CreateMessageParams { Content = text, IsTTS = isTTS }; | ||||
| var model = await Discord.ApiClient.CreateDMMessageAsync(Id, args).ConfigureAwait(false); | 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); | string filename = Path.GetFileName(filePath); | ||||
| using (var file = File.OpenRead(filePath)) | using (var file = File.OpenRead(filePath)) | ||||
| { | { | ||||
| var args = new UploadFileParams(file) { Filename = filename, Content = text, IsTTS = isTTS }; | var args = new UploadFileParams(file) { Filename = filename, Content = text, IsTTS = isTTS }; | ||||
| var model = await Discord.ApiClient.UploadDMFileAsync(Id, args).ConfigureAwait(false); | 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 args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS }; | ||||
| var model = await Discord.ApiClient.UploadDMFileAsync(Id, args).ConfigureAwait(false); | 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) | public virtual async Task<IMessage> GetMessageAsync(ulong id) | ||||
| { | { | ||||
| var model = await Discord.ApiClient.GetChannelMessageAsync(Id, id).ConfigureAwait(false); | var model = await Discord.ApiClient.GetChannelMessageAsync(Id, id).ConfigureAwait(false); | ||||
| if (model != null) | if (model != null) | ||||
| return new Message(this, new User(model.Author.Value), model); | |||||
| return CreateIncomingMessage(model); | |||||
| return null; | return null; | ||||
| } | } | ||||
| public virtual async Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(int limit) | public virtual async Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(int limit) | ||||
| { | { | ||||
| var args = new GetChannelMessagesParams { Limit = limit }; | var args = new GetChannelMessagesParams { Limit = limit }; | ||||
| var models = await Discord.ApiClient.GetChannelMessagesAsync(Id, args).ConfigureAwait(false); | 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) | public virtual async Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit) | ||||
| { | { | ||||
| var args = new GetChannelMessagesParams { Limit = limit, RelativeMessageId = fromMessageId, RelativeDirection = dir }; | var args = new GetChannelMessagesParams { Limit = limit, RelativeMessageId = fromMessageId, RelativeDirection = dir }; | ||||
| var models = await Discord.ApiClient.GetChannelMessagesAsync(Id, args).ConfigureAwait(false); | 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) | public async Task DeleteMessagesAsync(IEnumerable<IMessage> messages) | ||||
| { | { | ||||
| @@ -110,14 +111,26 @@ namespace Discord.Rest | |||||
| public async Task<IReadOnlyCollection<IMessage>> GetPinnedMessagesAsync() | public async Task<IReadOnlyCollection<IMessage>> GetPinnedMessagesAsync() | ||||
| { | { | ||||
| var models = await Discord.ApiClient.GetPinsAsync(Id); | 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() | public async Task TriggerTypingAsync() | ||||
| { | { | ||||
| await Discord.ApiClient.TriggerTypingIndicatorAsync(Id).ConfigureAwait(false); | 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(); | public override string ToString() => '@' + Recipient.ToString(); | ||||
| private string DebuggerDisplay => $"@{Recipient} ({Id}, DM)"; | private string DebuggerDisplay => $"@{Recipient} ({Id}, DM)"; | ||||
| @@ -8,6 +8,7 @@ using System.IO; | |||||
| using System.Linq; | using System.Linq; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using Model = Discord.API.Channel; | using Model = Discord.API.Channel; | ||||
| using MessageModel = Discord.API.Message; | |||||
| namespace Discord.Rest | namespace Discord.Rest | ||||
| { | { | ||||
| @@ -87,46 +88,46 @@ namespace Discord.Rest | |||||
| return _users.Select(x => x.Value).Concat<IUser>(ImmutableArray.Create(currentUser)).ToReadOnlyCollection(_users); | 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 args = new CreateMessageParams { Content = text, IsTTS = isTTS }; | ||||
| var model = await Discord.ApiClient.CreateDMMessageAsync(Id, args).ConfigureAwait(false); | 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); | string filename = Path.GetFileName(filePath); | ||||
| using (var file = File.OpenRead(filePath)) | using (var file = File.OpenRead(filePath)) | ||||
| { | { | ||||
| var args = new UploadFileParams(file) { Filename = filename, Content = text, IsTTS = isTTS }; | var args = new UploadFileParams(file) { Filename = filename, Content = text, IsTTS = isTTS }; | ||||
| var model = await Discord.ApiClient.UploadDMFileAsync(Id, args).ConfigureAwait(false); | 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 args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS }; | ||||
| var model = await Discord.ApiClient.UploadDMFileAsync(Id, args).ConfigureAwait(false); | 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) | public virtual async Task<IMessage> GetMessageAsync(ulong id) | ||||
| { | { | ||||
| var model = await Discord.ApiClient.GetChannelMessageAsync(Id, id).ConfigureAwait(false); | var model = await Discord.ApiClient.GetChannelMessageAsync(Id, id).ConfigureAwait(false); | ||||
| if (model != null) | if (model != null) | ||||
| return new Message(this, new User(model.Author.Value), model); | |||||
| return CreateIncomingMessage(model); | |||||
| return null; | return null; | ||||
| } | } | ||||
| public virtual async Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(int limit) | public virtual async Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(int limit) | ||||
| { | { | ||||
| var args = new GetChannelMessagesParams { Limit = limit }; | var args = new GetChannelMessagesParams { Limit = limit }; | ||||
| var models = await Discord.ApiClient.GetChannelMessagesAsync(Id, args).ConfigureAwait(false); | 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) | public virtual async Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit) | ||||
| { | { | ||||
| var args = new GetChannelMessagesParams { Limit = limit, RelativeMessageId = fromMessageId, RelativeDirection = dir }; | var args = new GetChannelMessagesParams { Limit = limit, RelativeMessageId = fromMessageId, RelativeDirection = dir }; | ||||
| var models = await Discord.ApiClient.GetChannelMessagesAsync(Id, args).ConfigureAwait(false); | 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) | public async Task DeleteMessagesAsync(IEnumerable<IMessage> messages) | ||||
| { | { | ||||
| @@ -135,7 +136,7 @@ namespace Discord.Rest | |||||
| public async Task<IReadOnlyCollection<IMessage>> GetPinnedMessagesAsync() | public async Task<IReadOnlyCollection<IMessage>> GetPinnedMessagesAsync() | ||||
| { | { | ||||
| var models = await Discord.ApiClient.GetPinsAsync(Id); | 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() | public async Task TriggerTypingAsync() | ||||
| @@ -143,6 +144,18 @@ namespace Discord.Rest | |||||
| await Discord.ApiClient.TriggerTypingIndicatorAsync(Id).ConfigureAwait(false); | 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; | public override string ToString() => Name; | ||||
| private string DebuggerDisplay => $"@{Name} ({Id}, Group)"; | private string DebuggerDisplay => $"@{Name} ({Id}, Group)"; | ||||
| @@ -7,6 +7,7 @@ using System.IO; | |||||
| using System.Linq; | using System.Linq; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using Model = Discord.API.Channel; | using Model = Discord.API.Channel; | ||||
| using MessageModel = Discord.API.Message; | |||||
| namespace Discord.Rest | 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(); | 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 args = new CreateMessageParams { Content = text, IsTTS = isTTS }; | ||||
| var model = await Discord.ApiClient.CreateMessageAsync(Guild.Id, Id, args).ConfigureAwait(false); | 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); | string filename = Path.GetFileName(filePath); | ||||
| using (var file = File.OpenRead(filePath)) | using (var file = File.OpenRead(filePath)) | ||||
| { | { | ||||
| var args = new UploadFileParams(file) { Filename = filename, Content = text, IsTTS = isTTS }; | var args = new UploadFileParams(file) { Filename = filename, Content = text, IsTTS = isTTS }; | ||||
| var model = await Discord.ApiClient.UploadFileAsync(Guild.Id, Id, args).ConfigureAwait(false); | 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 args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS }; | ||||
| var model = await Discord.ApiClient.UploadFileAsync(Guild.Id, Id, args).ConfigureAwait(false); | 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) | public virtual async Task<IMessage> GetMessageAsync(ulong id) | ||||
| { | { | ||||
| var model = await Discord.ApiClient.GetChannelMessageAsync(Id, id).ConfigureAwait(false); | var model = await Discord.ApiClient.GetChannelMessageAsync(Id, id).ConfigureAwait(false); | ||||
| if (model != null) | if (model != null) | ||||
| return new Message(this, new User(model.Author.Value), model); | |||||
| return CreateIncomingMessage(model); | |||||
| return null; | return null; | ||||
| } | } | ||||
| public virtual async Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(int limit) | public virtual async Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(int limit) | ||||
| { | { | ||||
| var args = new GetChannelMessagesParams { Limit = limit }; | var args = new GetChannelMessagesParams { Limit = limit }; | ||||
| var models = await Discord.ApiClient.GetChannelMessagesAsync(Id, args).ConfigureAwait(false); | 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) | public virtual async Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit) | ||||
| { | { | ||||
| var args = new GetChannelMessagesParams { Limit = limit, RelativeMessageId = fromMessageId, RelativeDirection = dir }; | var args = new GetChannelMessagesParams { Limit = limit, RelativeMessageId = fromMessageId, RelativeDirection = dir }; | ||||
| var models = await Discord.ApiClient.GetChannelMessagesAsync(Id, args).ConfigureAwait(false); | 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) | public async Task DeleteMessagesAsync(IEnumerable<IMessage> messages) | ||||
| { | { | ||||
| @@ -105,7 +106,7 @@ namespace Discord.Rest | |||||
| public async Task<IReadOnlyCollection<IMessage>> GetPinnedMessagesAsync() | public async Task<IReadOnlyCollection<IMessage>> GetPinnedMessagesAsync() | ||||
| { | { | ||||
| var models = await Discord.ApiClient.GetPinsAsync(Id); | 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() | public async Task TriggerTypingAsync() | ||||
| @@ -113,6 +114,18 @@ namespace Discord.Rest | |||||
| await Discord.ApiClient.TriggerTypingIndicatorAsync(Id).ConfigureAwait(false); | 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)"; | private string DebuggerDisplay => $"{Name} ({Id}, Text)"; | ||||
| IMessage IMessageChannel.GetCachedMessage(ulong id) => null; | IMessage IMessageChannel.GetCachedMessage(ulong id) => null; | ||||
| @@ -9,28 +9,27 @@ using Model = Discord.API.Message; | |||||
| namespace Discord.Rest | namespace Discord.Rest | ||||
| { | { | ||||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | ||||
| internal class Message : SnowflakeEntity, IMessage | |||||
| internal abstract class Message : SnowflakeEntity, IMessage | |||||
| { | { | ||||
| private bool _isMentioningEveryone; | |||||
| private long _timestampTicks; | private long _timestampTicks; | ||||
| private long? _editedTimestampTicks; | |||||
| public MessageType Type { get; } | |||||
| public IMessageChannel Channel { get; } | public IMessageChannel Channel { get; } | ||||
| public IUser Author { get; } | public IUser Author { get; } | ||||
| public bool IsTTS { get; private set; } | |||||
| public string Content { 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 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 DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks); | ||||
| public Message(IMessageChannel channel, IUser author, Model model) | public Message(IMessageChannel channel, IUser author, Model model) | ||||
| @@ -38,86 +37,21 @@ namespace Discord.Rest | |||||
| { | { | ||||
| Channel = channel; | Channel = channel; | ||||
| Author = author; | Author = author; | ||||
| Type = model.Type; | |||||
| MentionedUsers = ImmutableArray.Create<IUser>(); | |||||
| MentionedChannelIds = ImmutableArray.Create<ulong>(); | |||||
| MentionedRoles = ImmutableArray.Create<IRole>(); | |||||
| Update(model, UpdateSource.Creation); | 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; | if (source == UpdateSource.Rest && IsAttached) return; | ||||
| var guildChannel = Channel as GuildChannel; | var guildChannel = Channel as GuildChannel; | ||||
| var guild = guildChannel?.Guild; | var guild = guildChannel?.Guild; | ||||
| if (model.IsTextToSpeech.IsSpecified) | |||||
| IsTTS = model.IsTextToSpeech.Value; | |||||
| if (model.Pinned.IsSpecified) | |||||
| IsPinned = model.Pinned.Value; | |||||
| if (model.Timestamp.IsSpecified) | if (model.Timestamp.IsSpecified) | ||||
| _timestampTicks = model.Timestamp.Value.UtcTicks; | _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) | 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() | public async Task UpdateAsync() | ||||
| @@ -159,23 +93,6 @@ namespace Discord.Rest | |||||
| { | { | ||||
| await Discord.ApiClient.RemovePinAsync(Channel.Id, Id).ConfigureAwait(false); | 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; | public override string ToString() => Content; | ||||
| private string DebuggerDisplay => $"{Author}: {Content}{(Attachments.Count > 0 ? $" [{Attachments.Count} Attachments]" : "")}"; | private string DebuggerDisplay => $"{Author}: {Content}{(Attachments.Count > 0 ? $" [{Attachments.Count} Attachments]" : "")}"; | ||||
| @@ -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})" : "")}"; | |||||
| } | |||||
| } | |||||
| @@ -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]" : "")}"; | |||||
| } | |||||
| } | |||||
| @@ -1247,7 +1247,7 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| IMessage before = null, after = null; | IMessage before = null, after = null; | ||||
| SocketMessage cachedMsg = channel.GetMessage(data.Id); | |||||
| ISocketMessage cachedMsg = channel.GetMessage(data.Id); | |||||
| if (cachedMsg != null) | if (cachedMsg != null) | ||||
| { | { | ||||
| before = cachedMsg.Clone(); | before = cachedMsg.Clone(); | ||||
| @@ -1259,7 +1259,7 @@ namespace Discord.WebSocket | |||||
| //Edited message isnt in cache, create a detached one | //Edited message isnt in cache, create a detached one | ||||
| var author = channel.GetUser(data.Author.Value.Id, true); | var author = channel.GetUser(data.Author.Value.Id, true); | ||||
| if (author != null) | if (author != null) | ||||
| after = new Message(channel, author, data); | |||||
| after = channel.CreateMessage(author, data); | |||||
| } | } | ||||
| if (after != null) | if (after != null) | ||||
| await _messageUpdatedEvent.InvokeAsync(Optional.Create(before), after).ConfigureAwait(false); | await _messageUpdatedEvent.InvokeAsync(Optional.Create(before), after).ConfigureAwait(false); | ||||
| @@ -7,9 +7,10 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| IReadOnlyCollection<ISocketUser> Users { get; } | 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); | ISocketUser GetUser(ulong id, bool skipCheck = false); | ||||
| } | } | ||||
| @@ -9,51 +9,51 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| internal class MessageCache : MessageManager | internal class MessageCache : MessageManager | ||||
| { | { | ||||
| private readonly ConcurrentDictionary<ulong, SocketMessage> _messages; | |||||
| private readonly ConcurrentDictionary<ulong, ISocketMessage> _messages; | |||||
| private readonly ConcurrentQueue<ulong> _orderedMessages; | private readonly ConcurrentQueue<ulong> _orderedMessages; | ||||
| private readonly int _size; | private readonly int _size; | ||||
| public override IReadOnlyCollection<SocketMessage> Messages => _messages.ToReadOnlyCollection(); | |||||
| public override IReadOnlyCollection<ISocketMessage> Messages => _messages.ToReadOnlyCollection(); | |||||
| public MessageCache(DiscordSocketClient discord, ISocketMessageChannel channel) | public MessageCache(DiscordSocketClient discord, ISocketMessageChannel channel) | ||||
| : base(discord, channel) | : base(discord, channel) | ||||
| { | { | ||||
| _size = discord.MessageCacheSize; | _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>(); | _orderedMessages = new ConcurrentQueue<ulong>(); | ||||
| } | } | ||||
| public override void Add(SocketMessage message) | |||||
| public override void Add(ISocketMessage message) | |||||
| { | { | ||||
| if (_messages.TryAdd(message.Id, message)) | if (_messages.TryAdd(message.Id, message)) | ||||
| { | { | ||||
| _orderedMessages.Enqueue(message.Id); | _orderedMessages.Enqueue(message.Id); | ||||
| ulong msgId; | ulong msgId; | ||||
| SocketMessage msg; | |||||
| ISocketMessage msg; | |||||
| while (_orderedMessages.Count > _size && _orderedMessages.TryDequeue(out msgId)) | while (_orderedMessages.Count > _size && _orderedMessages.TryDequeue(out msgId)) | ||||
| _messages.TryRemove(msgId, out msg); | _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); | _messages.TryRemove(id, out msg); | ||||
| return 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)) | if (_messages.TryGetValue(id, out result)) | ||||
| return result; | return result; | ||||
| return null; | 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) throw new ArgumentOutOfRangeException(nameof(limit)); | ||||
| if (limit == 0) return ImmutableArray<SocketMessage>.Empty; | |||||
| if (limit == 0) return ImmutableArray<ISocketMessage>.Empty; | |||||
| IEnumerable<ulong> cachedMessageIds; | IEnumerable<ulong> cachedMessageIds; | ||||
| if (fromMessageId == null) | if (fromMessageId == null) | ||||
| @@ -67,7 +67,7 @@ namespace Discord.WebSocket | |||||
| .Take(limit) | .Take(limit) | ||||
| .Select(x => | .Select(x => | ||||
| { | { | ||||
| SocketMessage msg; | |||||
| ISocketMessage msg; | |||||
| if (_messages.TryGetValue(x, out msg)) | if (_messages.TryGetValue(x, out msg)) | ||||
| return msg; | return msg; | ||||
| return null; | return null; | ||||
| @@ -76,7 +76,7 @@ namespace Discord.WebSocket | |||||
| .ToImmutableArray(); | .ToImmutableArray(); | ||||
| } | } | ||||
| public override async Task<SocketMessage> DownloadAsync(ulong id) | |||||
| public override async Task<ISocketMessage> DownloadAsync(ulong id) | |||||
| { | { | ||||
| var msg = Get(id); | var msg = Get(id); | ||||
| if (msg != null) | if (msg != null) | ||||
| @@ -5,6 +5,7 @@ using System.Collections.Generic; | |||||
| using System.Collections.Immutable; | using System.Collections.Immutable; | ||||
| using System.Linq; | using System.Linq; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using Model = Discord.API.Message; | |||||
| namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
| { | { | ||||
| @@ -13,8 +14,8 @@ namespace Discord.WebSocket | |||||
| private readonly DiscordSocketClient _discord; | private readonly DiscordSocketClient _discord; | ||||
| private readonly ISocketMessageChannel _channel; | 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) | public MessageManager(DiscordSocketClient discord, ISocketMessageChannel channel) | ||||
| { | { | ||||
| @@ -22,25 +23,25 @@ namespace Discord.WebSocket | |||||
| _channel = channel; | _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); | var model = await _discord.ApiClient.GetChannelMessageAsync(_channel.Id, id).ConfigureAwait(false); | ||||
| if (model != null) | if (model != null) | ||||
| return new SocketMessage(_channel, new User(model.Author.Value), model); | |||||
| return Create(new User(model.Author.Value), model); | |||||
| return null; | 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 | //TODO: Test heavily, especially the ordering of messages | ||||
| if (limit < 0) throw new ArgumentOutOfRangeException(nameof(limit)); | 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); | var cachedMessages = GetMany(fromId, dir, limit); | ||||
| if (cachedMessages.Count == limit) | if (cachedMessages.Count == limit) | ||||
| @@ -75,9 +76,17 @@ namespace Discord.WebSocket | |||||
| else | else | ||||
| user = newUser; | user = newUser; | ||||
| } | } | ||||
| return new SocketMessage(_channel, user, x); | |||||
| return Create(user, x); | |||||
| })).ToImmutableArray(); | })).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); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -52,17 +52,21 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| return await _messages.DownloadAsync(fromMessageId, dir, limit).ConfigureAwait(false); | 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); | _messages.Add(msg); | ||||
| return msg; | return msg; | ||||
| } | } | ||||
| public SocketMessage GetMessage(ulong id) | |||||
| public ISocketMessage GetMessage(ulong id) | |||||
| { | { | ||||
| return _messages.Get(id); | return _messages.Get(id); | ||||
| } | } | ||||
| public SocketMessage RemoveMessage(ulong id) | |||||
| public ISocketMessage RemoveMessage(ulong id) | |||||
| { | { | ||||
| return _messages.Remove(id); | return _messages.Remove(id); | ||||
| } | } | ||||
| @@ -116,17 +116,21 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| return await _messages.DownloadAsync(fromMessageId, dir, limit).ConfigureAwait(false); | 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); | _messages.Add(msg); | ||||
| return msg; | return msg; | ||||
| } | } | ||||
| public SocketMessage GetMessage(ulong id) | |||||
| public ISocketMessage GetMessage(ulong id) | |||||
| { | { | ||||
| return _messages.Get(id); | return _messages.Get(id); | ||||
| } | } | ||||
| public SocketMessage RemoveMessage(ulong id) | |||||
| public ISocketMessage RemoveMessage(ulong id) | |||||
| { | { | ||||
| return _messages.Remove(id); | return _messages.Remove(id); | ||||
| } | } | ||||
| @@ -58,17 +58,21 @@ namespace Discord.WebSocket | |||||
| return await _messages.DownloadAsync(fromMessageId, dir, limit).ConfigureAwait(false); | 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); | _messages.Add(msg); | ||||
| return msg; | return msg; | ||||
| } | } | ||||
| public SocketMessage GetMessage(ulong id) | |||||
| public ISocketMessage GetMessage(ulong id) | |||||
| { | { | ||||
| return _messages.Get(id); | return _messages.Get(id); | ||||
| } | } | ||||
| public SocketMessage RemoveMessage(ulong id) | |||||
| public ISocketMessage RemoveMessage(ulong id) | |||||
| { | { | ||||
| return _messages.Remove(id); | return _messages.Remove(id); | ||||
| } | } | ||||
| @@ -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(); | |||||
| } | |||||
| } | |||||
| @@ -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; | |||||
| } | |||||
| } | |||||
| @@ -3,18 +3,18 @@ using Model = Discord.API.Message; | |||||
| namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
| { | { | ||||
| internal class SocketMessage : Message | |||||
| internal class SocketUserMessage : UserMessage, ISocketMessage | |||||
| { | { | ||||
| internal override bool IsAttached => true; | internal override bool IsAttached => true; | ||||
| public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient; | public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient; | ||||
| public new ISocketMessageChannel Channel => base.Channel as ISocketMessageChannel; | 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) | : base(channel, author, model) | ||||
| { | { | ||||
| } | } | ||||
| public SocketMessage Clone() => MemberwiseClone() as SocketMessage; | |||||
| public ISocketMessage Clone() => MemberwiseClone() as ISocketMessage; | |||||
| } | } | ||||
| } | } | ||||