| @@ -6,6 +6,6 @@ namespace Discord.Commands | |||
| [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true, Inherited = true)] | |||
| 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; | |||
| } | |||
| 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; | |||
| @@ -20,7 +20,7 @@ namespace Discord.Commands | |||
| 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; | |||
| @@ -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 readonly object _instance; | |||
| private readonly Func<IMessage, IReadOnlyList<object>, Task> _action; | |||
| private readonly Func<IUserMessage, IReadOnlyList<object>, Task> _action; | |||
| public MethodInfo Source { get; } | |||
| public Module Module { get; } | |||
| @@ -66,7 +66,7 @@ namespace Discord.Commands | |||
| _action = BuildAction(source); | |||
| } | |||
| public async Task<PreconditionResult> CheckPreconditions(IMessage context) | |||
| public async Task<PreconditionResult> CheckPreconditions(IUserMessage context) | |||
| { | |||
| foreach (PreconditionAttribute precondition in Module.Preconditions) | |||
| { | |||
| @@ -85,7 +85,7 @@ namespace Discord.Commands | |||
| 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) | |||
| 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<ExecuteResult> Execute(IMessage msg, ParseResult parseResult) | |||
| public Task<ExecuteResult> 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<ExecuteResult> Execute(IMessage msg, IEnumerable<object> argList, IEnumerable<object> paramList) | |||
| public async Task<ExecuteResult> Execute(IUserMessage context, IEnumerable<object> argList, IEnumerable<object> 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<CommandParameter> 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<CommandParameter>(parameters.Length - 1); | |||
| for (int i = 1; i < parameters.Length; i++) | |||
| @@ -190,7 +190,7 @@ namespace Discord.Commands | |||
| } | |||
| 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)) | |||
| throw new InvalidOperationException("Commands must return a non-generic Task."); | |||
| @@ -32,7 +32,7 @@ namespace Discord.Commands | |||
| 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); | |||
| } | |||
| @@ -13,7 +13,7 @@ namespace Discord.Commands | |||
| 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; | |||
| StringBuilder argBuilder = new StringBuilder(input.Length); | |||
| @@ -41,8 +41,9 @@ namespace Discord.Commands | |||
| [typeof(DateTime)] = new SimpleTypeReader<DateTime>(), | |||
| [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(IDMChannel)] = new ChannelTypeReader<IDMChannel>(), | |||
| [typeof(IGroupChannel)] = new ChannelTypeReader<IGroupChannel>(), | |||
| @@ -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<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); | |||
| 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); | |||
| if (!searchResult.IsSuccess) | |||
| @@ -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; | |||
| @@ -9,7 +9,7 @@ namespace Discord.Commands | |||
| internal class ChannelTypeReader<T> : TypeReader | |||
| 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; | |||
| @@ -42,7 +42,7 @@ namespace Discord.Commands | |||
| _enumsByValue = byValueBuilder.ToImmutable(); | |||
| } | |||
| public override Task<TypeReaderResult> Read(IMessage context, string input) | |||
| public override Task<TypeReaderResult> Read(IUserMessage context, string input) | |||
| { | |||
| T baseValue; | |||
| object enumValue; | |||
| @@ -3,16 +3,17 @@ using System.Threading.Tasks; | |||
| 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; | |||
| //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)); | |||
| } | |||
| @@ -9,7 +9,7 @@ namespace Discord.Commands | |||
| internal class RoleTypeReader<T> : TypeReader | |||
| 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; | |||
| ulong id; | |||
| @@ -11,7 +11,7 @@ namespace Discord.Commands | |||
| _tryParse = PrimitiveParsers.Get<T>(); | |||
| } | |||
| public override Task<TypeReaderResult> Read(IMessage context, string input) | |||
| public override Task<TypeReaderResult> Read(IUserMessage context, string input) | |||
| { | |||
| T value; | |||
| if (_tryParse(input, out value)) | |||
| @@ -4,6 +4,6 @@ namespace Discord.Commands | |||
| { | |||
| 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 | |||
| 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 guild = (context.Channel as IGuildChannel)?.Guild; | |||
| @@ -1,6 +1,4 @@ | |||
| #pragma warning disable CS1591 | |||
| using Discord.Rest; | |||
| namespace Discord.API.Rest | |||
| { | |||
| public class GetChannelMessagesParams | |||
| @@ -10,11 +10,11 @@ namespace Discord | |||
| IReadOnlyCollection<IMessage> CachedMessages { get; } | |||
| /// <summary> Sends a message to this message channel. </summary> | |||
| Task<IMessage> SendMessageAsync(string text, bool isTTS = false); | |||
| Task<IUserMessage> SendMessageAsync(string text, bool isTTS = false); | |||
| /// <summary> Sends a file to this text channel, with an optional caption. </summary> | |||
| Task<IMessage> SendFileAsync(string filePath, string text = null, bool isTTS = false); | |||
| Task<IUserMessage> SendFileAsync(string filePath, string text = null, bool isTTS = false); | |||
| /// <summary> Sends a file to this text channel, with an optional caption. </summary> | |||
| Task<IMessage> SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false); | |||
| Task<IUserMessage> SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false); | |||
| /// <summary> Gets a message from this message channel with the given id, or null if not found. </summary> | |||
| Task<IMessage> GetMessageAsync(ulong id); | |||
| /// <summary> Gets the message from this channel's cache with the given id, or null if not found. </summary> | |||
| @@ -1,14 +1,10 @@ | |||
| using System; | |||
| using System.Threading.Tasks; | |||
| using Discord.API.Rest; | |||
| using System.Collections.Generic; | |||
| namespace Discord | |||
| { | |||
| public interface IMessage : IDeletable, ISnowflakeEntity, IUpdateable | |||
| public interface IMessage : ISnowflakeEntity, IUpdateable | |||
| { | |||
| /// <summary> Gets the time of this message's last edit, if any. </summary> | |||
| DateTimeOffset? EditedTimestamp { get; } | |||
| /// <summary> Returns true if this message was sent as a text-to-speech message. </summary> | |||
| bool IsTTS { get; } | |||
| /// <summary> Returns true if this message was added to its channel's pinned messages. </summary> | |||
| @@ -17,13 +13,14 @@ namespace Discord | |||
| string Content { get; } | |||
| /// <summary> Gets the time this message was sent. </summary> | |||
| DateTimeOffset Timestamp { get; } | |||
| /// <summary> Gets the type of this message. </summary> | |||
| MessageType Type { get; } | |||
| /// <summary> Gets the time of this message's last edit, if any. </summary> | |||
| DateTimeOffset? EditedTimestamp { get; } | |||
| /// <summary> Gets the channel this message was sent to. </summary> | |||
| IMessageChannel Channel { get; } | |||
| /// <summary> Gets the author of this message. </summary> | |||
| IUser Author { get; } | |||
| /// <summary> Returns a collection of all attachments included in this message. </summary> | |||
| IReadOnlyCollection<IAttachment> Attachments { get; } | |||
| /// <summary> Returns a collection of all embeds included in this message. </summary> | |||
| @@ -34,25 +31,5 @@ namespace Discord | |||
| IReadOnlyCollection<IRole> MentionedRoles { get; } | |||
| /// <summary> Returns a collection of users mentioned in this message. </summary> | |||
| IReadOnlyCollection<IUser> MentionedUsers { get; } | |||
| /// <summary> Modifies this message. </summary> | |||
| Task ModifyAsync(Action<ModifyMessageParams> func); | |||
| /// <summary> Adds this message to its channel's pinned messages. </summary> | |||
| Task PinAsync(); | |||
| /// <summary> Removes this message from its channel's pinned messages. </summary> | |||
| Task UnpinAsync(); | |||
| /// <summary> Transforms this message's text into a human readable form, resolving things like mentions to that object's name. </summary> | |||
| string Resolve(int startIndex, int length, | |||
| UserMentionHandling userHandling = UserMentionHandling.Name, | |||
| ChannelMentionHandling channelHandling = ChannelMentionHandling.Name, | |||
| RoleMentionHandling roleHandling = RoleMentionHandling.Name, | |||
| EveryoneMentionHandling everyoneHandling = EveryoneMentionHandling.Ignore); | |||
| /// <summary> Transforms this message's text into a human readable form, resolving things like mentions to that object's name. </summary> | |||
| string Resolve( | |||
| UserMentionHandling userHandling = UserMentionHandling.Name, | |||
| ChannelMentionHandling channelHandling = ChannelMentionHandling.Name, | |||
| RoleMentionHandling roleHandling = RoleMentionHandling.Name, | |||
| EveryoneMentionHandling everyoneHandling = EveryoneMentionHandling.Ignore); | |||
| } | |||
| } | |||
| @@ -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.Threading.Tasks; | |||
| using Model = Discord.API.Channel; | |||
| using MessageModel = Discord.API.Message; | |||
| namespace Discord.Rest | |||
| { | |||
| @@ -62,46 +63,46 @@ namespace Discord.Rest | |||
| return ImmutableArray.Create(currentUser, Recipient); | |||
| } | |||
| public async Task<IMessage> SendMessageAsync(string text, bool isTTS) | |||
| public async Task<IUserMessage> SendMessageAsync(string text, bool isTTS) | |||
| { | |||
| var args = new CreateMessageParams { Content = text, IsTTS = isTTS }; | |||
| var model = await Discord.ApiClient.CreateDMMessageAsync(Id, args).ConfigureAwait(false); | |||
| return new Message(this, new User(model.Author.Value), model); | |||
| return CreateOutgoingMessage(model); | |||
| } | |||
| public async Task<IMessage> SendFileAsync(string filePath, string text, bool isTTS) | |||
| public async Task<IUserMessage> SendFileAsync(string filePath, string text, bool isTTS) | |||
| { | |||
| string filename = Path.GetFileName(filePath); | |||
| using (var file = File.OpenRead(filePath)) | |||
| { | |||
| var args = new UploadFileParams(file) { Filename = filename, Content = text, IsTTS = isTTS }; | |||
| var model = await Discord.ApiClient.UploadDMFileAsync(Id, args).ConfigureAwait(false); | |||
| return new Message(this, new User(model.Author.Value), model); | |||
| return CreateOutgoingMessage(model); | |||
| } | |||
| } | |||
| public async Task<IMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS) | |||
| public async Task<IUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS) | |||
| { | |||
| var args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS }; | |||
| var model = await Discord.ApiClient.UploadDMFileAsync(Id, args).ConfigureAwait(false); | |||
| return new Message(this, new User(model.Author.Value), model); | |||
| return CreateOutgoingMessage(model); | |||
| } | |||
| public virtual async Task<IMessage> GetMessageAsync(ulong id) | |||
| { | |||
| var model = await Discord.ApiClient.GetChannelMessageAsync(Id, id).ConfigureAwait(false); | |||
| if (model != null) | |||
| return new Message(this, new User(model.Author.Value), model); | |||
| return CreateIncomingMessage(model); | |||
| return null; | |||
| } | |||
| public virtual async Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(int limit) | |||
| { | |||
| var args = new GetChannelMessagesParams { Limit = limit }; | |||
| var models = await Discord.ApiClient.GetChannelMessagesAsync(Id, args).ConfigureAwait(false); | |||
| return models.Select(x => new Message(this, new User(x.Author.Value), x)).ToImmutableArray(); | |||
| return models.Select(x => CreateIncomingMessage(x)).ToImmutableArray(); | |||
| } | |||
| public virtual async Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit) | |||
| { | |||
| var args = new GetChannelMessagesParams { Limit = limit, RelativeMessageId = fromMessageId, RelativeDirection = dir }; | |||
| var models = await Discord.ApiClient.GetChannelMessagesAsync(Id, args).ConfigureAwait(false); | |||
| return models.Select(x => new Message(this, new User(x.Author.Value), x)).ToImmutableArray(); | |||
| return models.Select(x => CreateIncomingMessage(x)).ToImmutableArray(); | |||
| } | |||
| public async Task DeleteMessagesAsync(IEnumerable<IMessage> messages) | |||
| { | |||
| @@ -110,14 +111,26 @@ namespace Discord.Rest | |||
| public async Task<IReadOnlyCollection<IMessage>> GetPinnedMessagesAsync() | |||
| { | |||
| var models = await Discord.ApiClient.GetPinsAsync(Id); | |||
| return models.Select(x => new Message(this, new User(x.Author.Value), x)).ToImmutableArray(); | |||
| return models.Select(x => CreateIncomingMessage(x)).ToImmutableArray(); | |||
| } | |||
| public async Task TriggerTypingAsync() | |||
| { | |||
| await Discord.ApiClient.TriggerTypingIndicatorAsync(Id).ConfigureAwait(false); | |||
| } | |||
| } | |||
| private UserMessage CreateOutgoingMessage(MessageModel model) | |||
| { | |||
| return new UserMessage(this, new User(model.Author.Value), model); | |||
| } | |||
| private Message CreateIncomingMessage(MessageModel model) | |||
| { | |||
| if (model.Type == MessageType.Default) | |||
| return new UserMessage(this, new User(model.Author.Value), model); | |||
| else | |||
| return new SystemMessage(this, new User(model.Author.Value), model); | |||
| } | |||
| public override string ToString() => '@' + Recipient.ToString(); | |||
| private string DebuggerDisplay => $"@{Recipient} ({Id}, DM)"; | |||
| @@ -8,6 +8,7 @@ using System.IO; | |||
| using System.Linq; | |||
| using System.Threading.Tasks; | |||
| using Model = Discord.API.Channel; | |||
| using MessageModel = Discord.API.Message; | |||
| namespace Discord.Rest | |||
| { | |||
| @@ -87,46 +88,46 @@ namespace Discord.Rest | |||
| return _users.Select(x => x.Value).Concat<IUser>(ImmutableArray.Create(currentUser)).ToReadOnlyCollection(_users); | |||
| } | |||
| public async Task<IMessage> SendMessageAsync(string text, bool isTTS) | |||
| public async Task<IUserMessage> SendMessageAsync(string text, bool isTTS) | |||
| { | |||
| var args = new CreateMessageParams { Content = text, IsTTS = isTTS }; | |||
| var model = await Discord.ApiClient.CreateDMMessageAsync(Id, args).ConfigureAwait(false); | |||
| return new Message(this, new User(model.Author.Value), model); | |||
| return CreateOutgoingMessage(model); | |||
| } | |||
| public async Task<IMessage> SendFileAsync(string filePath, string text, bool isTTS) | |||
| public async Task<IUserMessage> SendFileAsync(string filePath, string text, bool isTTS) | |||
| { | |||
| string filename = Path.GetFileName(filePath); | |||
| using (var file = File.OpenRead(filePath)) | |||
| { | |||
| var args = new UploadFileParams(file) { Filename = filename, Content = text, IsTTS = isTTS }; | |||
| var model = await Discord.ApiClient.UploadDMFileAsync(Id, args).ConfigureAwait(false); | |||
| return new Message(this, new User(model.Author.Value), model); | |||
| return CreateOutgoingMessage(model); | |||
| } | |||
| } | |||
| public async Task<IMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS) | |||
| public async Task<IUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS) | |||
| { | |||
| var args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS }; | |||
| var model = await Discord.ApiClient.UploadDMFileAsync(Id, args).ConfigureAwait(false); | |||
| return new Message(this, new User(model.Author.Value), model); | |||
| return CreateOutgoingMessage(model); | |||
| } | |||
| public virtual async Task<IMessage> GetMessageAsync(ulong id) | |||
| { | |||
| var model = await Discord.ApiClient.GetChannelMessageAsync(Id, id).ConfigureAwait(false); | |||
| if (model != null) | |||
| return new Message(this, new User(model.Author.Value), model); | |||
| return CreateIncomingMessage(model); | |||
| return null; | |||
| } | |||
| public virtual async Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(int limit) | |||
| { | |||
| var args = new GetChannelMessagesParams { Limit = limit }; | |||
| var models = await Discord.ApiClient.GetChannelMessagesAsync(Id, args).ConfigureAwait(false); | |||
| return models.Select(x => new Message(this, new User(x.Author.Value), x)).ToImmutableArray(); | |||
| return models.Select(x => CreateIncomingMessage(x)).ToImmutableArray(); | |||
| } | |||
| public virtual async Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit) | |||
| { | |||
| var args = new GetChannelMessagesParams { Limit = limit, RelativeMessageId = fromMessageId, RelativeDirection = dir }; | |||
| var models = await Discord.ApiClient.GetChannelMessagesAsync(Id, args).ConfigureAwait(false); | |||
| return models.Select(x => new Message(this, new User(x.Author.Value), x)).ToImmutableArray(); | |||
| return models.Select(x => CreateIncomingMessage(x)).ToImmutableArray(); | |||
| } | |||
| public async Task DeleteMessagesAsync(IEnumerable<IMessage> messages) | |||
| { | |||
| @@ -135,7 +136,7 @@ namespace Discord.Rest | |||
| public async Task<IReadOnlyCollection<IMessage>> GetPinnedMessagesAsync() | |||
| { | |||
| var models = await Discord.ApiClient.GetPinsAsync(Id); | |||
| return models.Select(x => new Message(this, new User(x.Author.Value), x)).ToImmutableArray(); | |||
| return models.Select(x => CreateIncomingMessage(x)).ToImmutableArray(); | |||
| } | |||
| public async Task TriggerTypingAsync() | |||
| @@ -143,6 +144,18 @@ namespace Discord.Rest | |||
| await Discord.ApiClient.TriggerTypingIndicatorAsync(Id).ConfigureAwait(false); | |||
| } | |||
| private UserMessage CreateOutgoingMessage(MessageModel model) | |||
| { | |||
| return new UserMessage(this, new User(model.Author.Value), model); | |||
| } | |||
| private Message CreateIncomingMessage(MessageModel model) | |||
| { | |||
| if (model.Type == MessageType.Default) | |||
| return new UserMessage(this, new User(model.Author.Value), model); | |||
| else | |||
| return new SystemMessage(this, new User(model.Author.Value), model); | |||
| } | |||
| public override string ToString() => Name; | |||
| private string DebuggerDisplay => $"@{Name} ({Id}, Group)"; | |||
| @@ -7,6 +7,7 @@ using System.IO; | |||
| using System.Linq; | |||
| using System.Threading.Tasks; | |||
| using Model = Discord.API.Channel; | |||
| using MessageModel = Discord.API.Message; | |||
| namespace Discord.Rest | |||
| { | |||
| @@ -57,46 +58,46 @@ namespace Discord.Rest | |||
| return users.Where(x => Permissions.GetValue(Permissions.ResolveChannel(x, this, x.GuildPermissions.RawValue), ChannelPermission.ReadMessages)).ToImmutableArray(); | |||
| } | |||
| public async Task<IMessage> SendMessageAsync(string text, bool isTTS) | |||
| public async Task<IUserMessage> SendMessageAsync(string text, bool isTTS) | |||
| { | |||
| var args = new CreateMessageParams { Content = text, IsTTS = isTTS }; | |||
| var model = await Discord.ApiClient.CreateMessageAsync(Guild.Id, Id, args).ConfigureAwait(false); | |||
| return new Message(this, new User(model.Author.Value), model); | |||
| return CreateOutgoingMessage(model); | |||
| } | |||
| public async Task<IMessage> SendFileAsync(string filePath, string text, bool isTTS) | |||
| public async Task<IUserMessage> SendFileAsync(string filePath, string text, bool isTTS) | |||
| { | |||
| string filename = Path.GetFileName(filePath); | |||
| using (var file = File.OpenRead(filePath)) | |||
| { | |||
| var args = new UploadFileParams(file) { Filename = filename, Content = text, IsTTS = isTTS }; | |||
| var model = await Discord.ApiClient.UploadFileAsync(Guild.Id, Id, args).ConfigureAwait(false); | |||
| return new Message(this, new User(model.Author.Value), model); | |||
| return CreateOutgoingMessage(model); | |||
| } | |||
| } | |||
| public async Task<IMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS) | |||
| public async Task<IUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS) | |||
| { | |||
| var args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS }; | |||
| var model = await Discord.ApiClient.UploadFileAsync(Guild.Id, Id, args).ConfigureAwait(false); | |||
| return new Message(this, new User(model.Author.Value), model); | |||
| return CreateOutgoingMessage(model); | |||
| } | |||
| public virtual async Task<IMessage> GetMessageAsync(ulong id) | |||
| { | |||
| var model = await Discord.ApiClient.GetChannelMessageAsync(Id, id).ConfigureAwait(false); | |||
| if (model != null) | |||
| return new Message(this, new User(model.Author.Value), model); | |||
| return CreateIncomingMessage(model); | |||
| return null; | |||
| } | |||
| public virtual async Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(int limit) | |||
| { | |||
| var args = new GetChannelMessagesParams { Limit = limit }; | |||
| var models = await Discord.ApiClient.GetChannelMessagesAsync(Id, args).ConfigureAwait(false); | |||
| return models.Select(x => new Message(this, new User(x.Author.Value), x)).ToImmutableArray(); | |||
| return models.Select(x => CreateIncomingMessage(x)).ToImmutableArray(); | |||
| } | |||
| public virtual async Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit) | |||
| { | |||
| var args = new GetChannelMessagesParams { Limit = limit, RelativeMessageId = fromMessageId, RelativeDirection = dir }; | |||
| var models = await Discord.ApiClient.GetChannelMessagesAsync(Id, args).ConfigureAwait(false); | |||
| return models.Select(x => new Message(this, new User(x.Author.Value), x)).ToImmutableArray(); | |||
| return models.Select(x => CreateIncomingMessage(x)).ToImmutableArray(); | |||
| } | |||
| public async Task DeleteMessagesAsync(IEnumerable<IMessage> messages) | |||
| { | |||
| @@ -105,7 +106,7 @@ namespace Discord.Rest | |||
| public async Task<IReadOnlyCollection<IMessage>> GetPinnedMessagesAsync() | |||
| { | |||
| var models = await Discord.ApiClient.GetPinsAsync(Id); | |||
| return models.Select(x => new Message(this, new User(x.Author.Value), x)).ToImmutableArray(); | |||
| return models.Select(x => CreateIncomingMessage(x)).ToImmutableArray(); | |||
| } | |||
| public async Task TriggerTypingAsync() | |||
| @@ -113,6 +114,18 @@ namespace Discord.Rest | |||
| await Discord.ApiClient.TriggerTypingIndicatorAsync(Id).ConfigureAwait(false); | |||
| } | |||
| private UserMessage CreateOutgoingMessage(MessageModel model) | |||
| { | |||
| return new UserMessage(this, new User(model.Author.Value), model); | |||
| } | |||
| private Message CreateIncomingMessage(MessageModel model) | |||
| { | |||
| if (model.Type == MessageType.Default) | |||
| return new UserMessage(this, new User(model.Author.Value), model); | |||
| else | |||
| return new SystemMessage(this, new User(model.Author.Value), model); | |||
| } | |||
| private string DebuggerDisplay => $"{Name} ({Id}, Text)"; | |||
| IMessage IMessageChannel.GetCachedMessage(ulong id) => null; | |||
| @@ -9,28 +9,27 @@ using Model = Discord.API.Message; | |||
| namespace Discord.Rest | |||
| { | |||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
| internal class Message : SnowflakeEntity, IMessage | |||
| internal abstract class Message : SnowflakeEntity, IMessage | |||
| { | |||
| private bool _isMentioningEveryone; | |||
| private long _timestampTicks; | |||
| private long? _editedTimestampTicks; | |||
| public MessageType Type { get; } | |||
| public IMessageChannel Channel { get; } | |||
| public IUser Author { get; } | |||
| public bool IsTTS { get; private set; } | |||
| public string Content { get; private set; } | |||
| public bool IsPinned { get; private set; } | |||
| public IReadOnlyCollection<IAttachment> Attachments { get; private set; } | |||
| public IReadOnlyCollection<IEmbed> Embeds { get; private set; } | |||
| public IReadOnlyCollection<ulong> MentionedChannelIds { get; private set; } | |||
| public IReadOnlyCollection<IRole> MentionedRoles { get; private set; } | |||
| public IReadOnlyCollection<IUser> MentionedUsers { get; private set; } | |||
| public override DiscordRestClient Discord => (Channel as Entity<ulong>).Discord; | |||
| public DateTimeOffset? EditedTimestamp => DateTimeUtils.FromTicks(_editedTimestampTicks); | |||
| public virtual bool IsTTS => false; | |||
| public virtual bool IsPinned => false; | |||
| public virtual DateTimeOffset? EditedTimestamp => null; | |||
| public virtual IReadOnlyCollection<IAttachment> Attachments => ImmutableArray.Create<IAttachment>(); | |||
| public virtual IReadOnlyCollection<IEmbed> Embeds => ImmutableArray.Create<IEmbed>(); | |||
| public virtual IReadOnlyCollection<ulong> MentionedChannelIds => ImmutableArray.Create<ulong>(); | |||
| public virtual IReadOnlyCollection<IRole> MentionedRoles => ImmutableArray.Create<IRole>(); | |||
| public virtual IReadOnlyCollection<IUser> MentionedUsers => ImmutableArray.Create<IUser>(); | |||
| public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks); | |||
| public Message(IMessageChannel channel, IUser author, Model model) | |||
| @@ -38,86 +37,21 @@ namespace Discord.Rest | |||
| { | |||
| Channel = channel; | |||
| Author = author; | |||
| Type = model.Type; | |||
| MentionedUsers = ImmutableArray.Create<IUser>(); | |||
| MentionedChannelIds = ImmutableArray.Create<ulong>(); | |||
| MentionedRoles = ImmutableArray.Create<IRole>(); | |||
| Update(model, UpdateSource.Creation); | |||
| } | |||
| public void Update(Model model, UpdateSource source) | |||
| public virtual void Update(Model model, UpdateSource source) | |||
| { | |||
| if (source == UpdateSource.Rest && IsAttached) return; | |||
| var guildChannel = Channel as GuildChannel; | |||
| var guild = guildChannel?.Guild; | |||
| if (model.IsTextToSpeech.IsSpecified) | |||
| IsTTS = model.IsTextToSpeech.Value; | |||
| if (model.Pinned.IsSpecified) | |||
| IsPinned = model.Pinned.Value; | |||
| if (model.Timestamp.IsSpecified) | |||
| _timestampTicks = model.Timestamp.Value.UtcTicks; | |||
| if (model.EditedTimestamp.IsSpecified) | |||
| _editedTimestampTicks = model.EditedTimestamp.Value?.UtcTicks; | |||
| if (model.MentionEveryone.IsSpecified) | |||
| _isMentioningEveryone = model.MentionEveryone.Value; | |||
| if (model.Attachments.IsSpecified) | |||
| { | |||
| var value = model.Attachments.Value; | |||
| if (value.Length > 0) | |||
| { | |||
| var attachments = new Attachment[value.Length]; | |||
| for (int i = 0; i < attachments.Length; i++) | |||
| attachments[i] = new Attachment(value[i]); | |||
| Attachments = ImmutableArray.Create(attachments); | |||
| } | |||
| else | |||
| Attachments = ImmutableArray.Create<Attachment>(); | |||
| } | |||
| if (model.Embeds.IsSpecified) | |||
| { | |||
| var value = model.Embeds.Value; | |||
| if (value.Length > 0) | |||
| { | |||
| var embeds = new Embed[value.Length]; | |||
| for (int i = 0; i < embeds.Length; i++) | |||
| embeds[i] = new Embed(value[i]); | |||
| Embeds = ImmutableArray.Create(embeds); | |||
| } | |||
| else | |||
| Embeds = ImmutableArray.Create<Embed>(); | |||
| } | |||
| if (model.Mentions.IsSpecified) | |||
| { | |||
| var value = model.Mentions.Value; | |||
| if (value.Length > 0) | |||
| { | |||
| var mentions = new User[value.Length]; | |||
| for (int i = 0; i < value.Length; i++) | |||
| mentions[i] = new User(value[i]); | |||
| MentionedUsers = ImmutableArray.Create(mentions); | |||
| } | |||
| else | |||
| MentionedUsers = ImmutableArray.Create<IUser>(); | |||
| } | |||
| if (model.Content.IsSpecified) | |||
| { | |||
| var text = model.Content.Value; | |||
| if (guildChannel != null) | |||
| { | |||
| MentionedUsers = MentionUtils.GetUserMentions(text, Channel, MentionedUsers); | |||
| MentionedChannelIds = MentionUtils.GetChannelMentions(text, guildChannel.Guild); | |||
| MentionedRoles = MentionUtils.GetRoleMentions(text, guildChannel.Guild); | |||
| } | |||
| Content = text; | |||
| } | |||
| Content = model.Content.Value; | |||
| } | |||
| public async Task UpdateAsync() | |||
| @@ -159,23 +93,6 @@ namespace Discord.Rest | |||
| { | |||
| await Discord.ApiClient.RemovePinAsync(Channel.Id, Id).ConfigureAwait(false); | |||
| } | |||
| public string Resolve(int startIndex, int length, UserMentionHandling userHandling, ChannelMentionHandling channelHandling, | |||
| RoleMentionHandling roleHandling, EveryoneMentionHandling everyoneHandling) | |||
| => Resolve(Content.Substring(startIndex, length), userHandling, channelHandling, roleHandling, everyoneHandling); | |||
| public string Resolve(UserMentionHandling userHandling, ChannelMentionHandling channelHandling, | |||
| RoleMentionHandling roleHandling, EveryoneMentionHandling everyoneHandling) | |||
| => Resolve(Content, userHandling, channelHandling, roleHandling, everyoneHandling); | |||
| private string Resolve(string text, UserMentionHandling userHandling, ChannelMentionHandling channelHandling, | |||
| RoleMentionHandling roleHandling, EveryoneMentionHandling everyoneHandling) | |||
| { | |||
| text = MentionUtils.ResolveUserMentions(text, Channel, MentionedUsers, userHandling); | |||
| text = MentionUtils.ResolveChannelMentions(text, (Channel as IGuildChannel)?.Guild, channelHandling); | |||
| text = MentionUtils.ResolveRoleMentions(text, MentionedRoles, roleHandling); | |||
| text = MentionUtils.ResolveEveryoneMentions(text, everyoneHandling); | |||
| return text; | |||
| } | |||
| public override string ToString() => Content; | |||
| private string DebuggerDisplay => $"{Author}: {Content}{(Attachments.Count > 0 ? $" [{Attachments.Count} Attachments]" : "")}"; | |||
| @@ -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; | |||
| 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); | |||
| @@ -7,9 +7,10 @@ namespace Discord.WebSocket | |||
| { | |||
| IReadOnlyCollection<ISocketUser> Users { get; } | |||
| SocketMessage AddMessage(ISocketUser author, MessageModel model); | |||
| SocketMessage GetMessage(ulong id); | |||
| SocketMessage RemoveMessage(ulong id); | |||
| ISocketMessage CreateMessage(ISocketUser author, MessageModel model); | |||
| ISocketMessage AddMessage(ISocketUser author, MessageModel model); | |||
| ISocketMessage GetMessage(ulong id); | |||
| ISocketMessage RemoveMessage(ulong id); | |||
| ISocketUser GetUser(ulong id, bool skipCheck = false); | |||
| } | |||
| @@ -9,51 +9,51 @@ namespace Discord.WebSocket | |||
| { | |||
| internal class MessageCache : MessageManager | |||
| { | |||
| private readonly ConcurrentDictionary<ulong, SocketMessage> _messages; | |||
| private readonly ConcurrentDictionary<ulong, ISocketMessage> _messages; | |||
| private readonly ConcurrentQueue<ulong> _orderedMessages; | |||
| private readonly int _size; | |||
| public override IReadOnlyCollection<SocketMessage> Messages => _messages.ToReadOnlyCollection(); | |||
| public override IReadOnlyCollection<ISocketMessage> Messages => _messages.ToReadOnlyCollection(); | |||
| public MessageCache(DiscordSocketClient discord, ISocketMessageChannel channel) | |||
| : base(discord, channel) | |||
| { | |||
| _size = discord.MessageCacheSize; | |||
| _messages = new ConcurrentDictionary<ulong, SocketMessage>(1, (int)(_size * 1.05)); | |||
| _messages = new ConcurrentDictionary<ulong, ISocketMessage>(1, (int)(_size * 1.05)); | |||
| _orderedMessages = new ConcurrentQueue<ulong>(); | |||
| } | |||
| public override void Add(SocketMessage message) | |||
| public override void Add(ISocketMessage message) | |||
| { | |||
| if (_messages.TryAdd(message.Id, message)) | |||
| { | |||
| _orderedMessages.Enqueue(message.Id); | |||
| ulong msgId; | |||
| SocketMessage msg; | |||
| ISocketMessage msg; | |||
| while (_orderedMessages.Count > _size && _orderedMessages.TryDequeue(out msgId)) | |||
| _messages.TryRemove(msgId, out msg); | |||
| } | |||
| } | |||
| public override SocketMessage Remove(ulong id) | |||
| public override ISocketMessage Remove(ulong id) | |||
| { | |||
| SocketMessage msg; | |||
| ISocketMessage msg; | |||
| _messages.TryRemove(id, out msg); | |||
| return msg; | |||
| } | |||
| public override SocketMessage Get(ulong id) | |||
| public override ISocketMessage Get(ulong id) | |||
| { | |||
| SocketMessage result; | |||
| ISocketMessage result; | |||
| if (_messages.TryGetValue(id, out result)) | |||
| return result; | |||
| return null; | |||
| } | |||
| public override IImmutableList<SocketMessage> GetMany(ulong? fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | |||
| public override IImmutableList<ISocketMessage> GetMany(ulong? fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | |||
| { | |||
| if (limit < 0) throw new ArgumentOutOfRangeException(nameof(limit)); | |||
| if (limit == 0) return ImmutableArray<SocketMessage>.Empty; | |||
| if (limit == 0) return ImmutableArray<ISocketMessage>.Empty; | |||
| IEnumerable<ulong> cachedMessageIds; | |||
| if (fromMessageId == null) | |||
| @@ -67,7 +67,7 @@ namespace Discord.WebSocket | |||
| .Take(limit) | |||
| .Select(x => | |||
| { | |||
| SocketMessage msg; | |||
| ISocketMessage msg; | |||
| if (_messages.TryGetValue(x, out msg)) | |||
| return msg; | |||
| return null; | |||
| @@ -76,7 +76,7 @@ namespace Discord.WebSocket | |||
| .ToImmutableArray(); | |||
| } | |||
| public override async Task<SocketMessage> DownloadAsync(ulong id) | |||
| public override async Task<ISocketMessage> DownloadAsync(ulong id) | |||
| { | |||
| var msg = Get(id); | |||
| if (msg != null) | |||
| @@ -5,6 +5,7 @@ using System.Collections.Generic; | |||
| using System.Collections.Immutable; | |||
| using System.Linq; | |||
| using System.Threading.Tasks; | |||
| using Model = Discord.API.Message; | |||
| namespace Discord.WebSocket | |||
| { | |||
| @@ -13,8 +14,8 @@ namespace Discord.WebSocket | |||
| private readonly DiscordSocketClient _discord; | |||
| private readonly ISocketMessageChannel _channel; | |||
| public virtual IReadOnlyCollection<SocketMessage> Messages | |||
| => ImmutableArray.Create<SocketMessage>(); | |||
| public virtual IReadOnlyCollection<ISocketMessage> Messages | |||
| => ImmutableArray.Create<ISocketMessage>(); | |||
| public MessageManager(DiscordSocketClient discord, ISocketMessageChannel channel) | |||
| { | |||
| @@ -22,25 +23,25 @@ namespace Discord.WebSocket | |||
| _channel = channel; | |||
| } | |||
| public virtual void Add(SocketMessage message) { } | |||
| public virtual SocketMessage Remove(ulong id) => null; | |||
| public virtual SocketMessage Get(ulong id) => null; | |||
| public virtual void Add(ISocketMessage message) { } | |||
| public virtual ISocketMessage Remove(ulong id) => null; | |||
| public virtual ISocketMessage Get(ulong id) => null; | |||
| public virtual IImmutableList<SocketMessage> GetMany(ulong? fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | |||
| => ImmutableArray.Create<SocketMessage>(); | |||
| public virtual IImmutableList<ISocketMessage> GetMany(ulong? fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | |||
| => ImmutableArray.Create<ISocketMessage>(); | |||
| public virtual async Task<SocketMessage> DownloadAsync(ulong id) | |||
| public virtual async Task<ISocketMessage> DownloadAsync(ulong id) | |||
| { | |||
| var model = await _discord.ApiClient.GetChannelMessageAsync(_channel.Id, id).ConfigureAwait(false); | |||
| if (model != null) | |||
| return new SocketMessage(_channel, new User(model.Author.Value), model); | |||
| return Create(new User(model.Author.Value), model); | |||
| return null; | |||
| } | |||
| public async Task<IReadOnlyCollection<SocketMessage>> DownloadAsync(ulong? fromId, Direction dir, int limit) | |||
| public async Task<IReadOnlyCollection<ISocketMessage>> DownloadAsync(ulong? fromId, Direction dir, int limit) | |||
| { | |||
| //TODO: Test heavily, especially the ordering of messages | |||
| if (limit < 0) throw new ArgumentOutOfRangeException(nameof(limit)); | |||
| if (limit == 0) return ImmutableArray<SocketMessage>.Empty; | |||
| if (limit == 0) return ImmutableArray<ISocketMessage>.Empty; | |||
| var cachedMessages = GetMany(fromId, dir, limit); | |||
| if (cachedMessages.Count == limit) | |||
| @@ -75,9 +76,17 @@ namespace Discord.WebSocket | |||
| else | |||
| user = newUser; | |||
| } | |||
| return new SocketMessage(_channel, user, x); | |||
| return Create(user, x); | |||
| })).ToImmutableArray(); | |||
| } | |||
| } | |||
| public ISocketMessage Create(IUser author, Model model) | |||
| { | |||
| if (model.Type == MessageType.Default) | |||
| return new SocketUserMessage(_channel, author, model); | |||
| else | |||
| return new SocketSystemMessage(_channel, author, model); | |||
| } | |||
| } | |||
| } | |||
| @@ -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); | |||
| } | |||
| @@ -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); | |||
| } | |||
| @@ -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); | |||
| } | |||
| @@ -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 | |||
| { | |||
| 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; | |||
| } | |||
| } | |||