@@ -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; | |||
} | |||
} |