@@ -71,10 +71,11 @@ namespace Discord.Commands | |||||
if (ChannelPermission.HasValue) | if (ChannelPermission.HasValue) | ||||
{ | { | ||||
ChannelPermissions perms; | ChannelPermissions perms; | ||||
if (context.Channel is IGuildChannel guildChannel) | |||||
IMessageChannel channel = await context.Channel.GetOrDownloadAsync().ConfigureAwait(false); | |||||
if (channel is IGuildChannel guildChannel) | |||||
perms = guildUser.GetPermissions(guildChannel); | perms = guildUser.GetPermissions(guildChannel); | ||||
else | else | ||||
perms = ChannelPermissions.All(context.Channel); | |||||
perms = ChannelPermissions.All(channel); | |||||
if (!perms.Has(ChannelPermission.Value)) | if (!perms.Has(ChannelPermission.Value)) | ||||
return PreconditionResult.FromError(ErrorMessage ?? $"Bot requires channel permission {ChannelPermission.Value}."); | return PreconditionResult.FromError(ErrorMessage ?? $"Bot requires channel permission {ChannelPermission.Value}."); | ||||
@@ -17,10 +17,6 @@ namespace Discord.Commands | |||||
/// Specifies the command to be executed within a DM. | /// Specifies the command to be executed within a DM. | ||||
/// </summary> | /// </summary> | ||||
DM = 0x02, | DM = 0x02, | ||||
/// <summary> | |||||
/// Specifies the command to be executed within a group. | |||||
/// </summary> | |||||
Group = 0x04 | |||||
} | } | ||||
/// <summary> | /// <summary> | ||||
@@ -59,11 +55,9 @@ namespace Discord.Commands | |||||
bool isValid = false; | bool isValid = false; | ||||
if ((Contexts & ContextType.Guild) != 0) | if ((Contexts & ContextType.Guild) != 0) | ||||
isValid = context.Channel is IGuildChannel; | |||||
isValid = context.GuildId != null; | |||||
if ((Contexts & ContextType.DM) != 0) | if ((Contexts & ContextType.DM) != 0) | ||||
isValid = isValid || context.Channel is IDMChannel; | |||||
if ((Contexts & ContextType.Group) != 0) | |||||
isValid = isValid || context.Channel is IGroupChannel; | |||||
isValid = context.GuildId == null; | |||||
if (isValid) | if (isValid) | ||||
return Task.FromResult(PreconditionResult.FromSuccess()); | return Task.FromResult(PreconditionResult.FromSuccess()); | ||||
@@ -36,7 +36,7 @@ namespace Discord.Commands | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services) | public override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services) | ||||
{ | { | ||||
if (context.Channel is ITextChannel text && text.IsNsfw) | |||||
if (context.Channel.HasValue && context.Channel.Value is ITextChannel text && text.IsNsfw) | |||||
return Task.FromResult(PreconditionResult.FromSuccess()); | return Task.FromResult(PreconditionResult.FromSuccess()); | ||||
else | else | ||||
return Task.FromResult(PreconditionResult.FromError(ErrorMessage ?? "This command may only be invoked in an NSFW channel.")); | return Task.FromResult(PreconditionResult.FromError(ErrorMessage ?? "This command may only be invoked in an NSFW channel.")); | ||||
@@ -54,31 +54,32 @@ namespace Discord.Commands | |||||
} | } | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services) | |||||
public override async Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services) | |||||
{ | { | ||||
var guildUser = context.User as IGuildUser; | var guildUser = context.User as IGuildUser; | ||||
if (GuildPermission.HasValue) | if (GuildPermission.HasValue) | ||||
{ | { | ||||
if (guildUser == null) | if (guildUser == null) | ||||
return Task.FromResult(PreconditionResult.FromError(NotAGuildErrorMessage ?? "Command must be used in a guild channel.")); | |||||
return PreconditionResult.FromError(NotAGuildErrorMessage ?? "Command must be used in a guild channel."); | |||||
if (!guildUser.GuildPermissions.Has(GuildPermission.Value)) | if (!guildUser.GuildPermissions.Has(GuildPermission.Value)) | ||||
return Task.FromResult(PreconditionResult.FromError(ErrorMessage ?? $"User requires guild permission {GuildPermission.Value}.")); | |||||
return PreconditionResult.FromError(ErrorMessage ?? $"User requires guild permission {GuildPermission.Value}."); | |||||
} | } | ||||
if (ChannelPermission.HasValue) | if (ChannelPermission.HasValue) | ||||
{ | { | ||||
ChannelPermissions perms; | ChannelPermissions perms; | ||||
if (context.Channel is IGuildChannel guildChannel) | |||||
IMessageChannel channel = await context.Channel.GetOrDownloadAsync().ConfigureAwait(false); | |||||
if (channel is IGuildChannel guildChannel) | |||||
perms = guildUser.GetPermissions(guildChannel); | perms = guildUser.GetPermissions(guildChannel); | ||||
else | else | ||||
perms = ChannelPermissions.All(context.Channel); | |||||
perms = ChannelPermissions.All(channel); | |||||
if (!perms.Has(ChannelPermission.Value)) | if (!perms.Has(ChannelPermission.Value)) | ||||
return Task.FromResult(PreconditionResult.FromError(ErrorMessage ?? $"User requires channel permission {ChannelPermission.Value}.")); | |||||
return PreconditionResult.FromError(ErrorMessage ?? $"User requires channel permission {ChannelPermission.Value}."); | |||||
} | } | ||||
return Task.FromResult(PreconditionResult.FromSuccess()); | |||||
return PreconditionResult.FromSuccess(); | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -8,24 +8,27 @@ namespace Discord.Commands | |||||
/// <inheritdoc/> | /// <inheritdoc/> | ||||
public IGuild Guild { get; } | public IGuild Guild { get; } | ||||
/// <inheritdoc/> | /// <inheritdoc/> | ||||
public IMessageChannel Channel { get; } | |||||
public ulong? GuildId { get; } | |||||
/// <inheritdoc/> | |||||
public Cacheable<IMessageChannel, ulong> Channel { get; } | |||||
/// <inheritdoc/> | /// <inheritdoc/> | ||||
public IUser User { get; } | public IUser User { get; } | ||||
/// <inheritdoc/> | /// <inheritdoc/> | ||||
public IUserMessage Message { get; } | public IUserMessage Message { get; } | ||||
/// <summary> Indicates whether the channel that the command is executed in is a private channel. </summary> | /// <summary> Indicates whether the channel that the command is executed in is a private channel. </summary> | ||||
public bool IsPrivate => Channel is IPrivateChannel; | |||||
public bool IsPrivate => GuildId == null; | |||||
/// <summary> | /// <summary> | ||||
/// Initializes a new <see cref="CommandContext" /> class with the provided client and message. | /// Initializes a new <see cref="CommandContext" /> class with the provided client and message. | ||||
/// </summary> | /// </summary> | ||||
/// <param name="client">The underlying client.</param> | /// <param name="client">The underlying client.</param> | ||||
/// <param name="msg">The underlying message.</param> | /// <param name="msg">The underlying message.</param> | ||||
public CommandContext(IDiscordClient client, IUserMessage msg) | |||||
public CommandContext(IDiscordClient client, IUserMessage msg, IGuild guild) | |||||
{ | { | ||||
Client = client; | Client = client; | ||||
Guild = (msg.Channel as IGuildChannel)?.Guild; | |||||
GuildId = msg.GuildId; | |||||
Guild = guild; | |||||
Channel = msg.Channel; | Channel = msg.Channel; | ||||
User = msg.Author; | User = msg.Author; | ||||
Message = msg; | Message = msg; | ||||
@@ -36,9 +36,9 @@ namespace Discord.Commands | |||||
/// If <c>null</c>, all mentioned roles and users will be notified. | /// If <c>null</c>, all mentioned roles and users will be notified. | ||||
/// </param> | /// </param> | ||||
/// <param name="messageReference">The message references to be included. Used to reply to specific messages.</param> | /// <param name="messageReference">The message references to be included. Used to reply to specific messages.</param> | ||||
protected virtual async Task<IUserMessage> ReplyAsync(string message = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null) | |||||
protected virtual async Task<IUserMessage> ReplyAsync(string message = null, bool isTTS = false, Embed embed = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, RequestOptions options = null) | |||||
{ | { | ||||
return await Context.Channel.SendMessageAsync(message, isTTS, embed, options, allowedMentions, messageReference).ConfigureAwait(false); | |||||
return await Context.Client.SendMessageAsync(Context.Channel.Id, message, isTTS, embed, allowedMentions, messageReference, options).ConfigureAwait(false); | |||||
} | } | ||||
/// <summary> | /// <summary> | ||||
/// The method to execute before executing the command. | /// The method to execute before executing the command. | ||||
@@ -17,7 +17,7 @@ namespace Discord.Commands | |||||
//By Id (1.0) | //By Id (1.0) | ||||
if (ulong.TryParse(input, NumberStyles.None, CultureInfo.InvariantCulture, out ulong id)) | if (ulong.TryParse(input, NumberStyles.None, CultureInfo.InvariantCulture, out ulong id)) | ||||
{ | { | ||||
if (await context.Channel.GetMessageAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) is T msg) | |||||
if (context.Channel.HasValue && await context.Channel.Value.GetMessageAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) is T msg) | |||||
return TypeReaderResult.FromSuccess(msg); | return TypeReaderResult.FromSuccess(msg); | ||||
} | } | ||||
@@ -18,7 +18,9 @@ namespace Discord.Commands | |||||
public override async Task<TypeReaderResult> ReadAsync(ICommandContext context, string input, IServiceProvider services) | public override async Task<TypeReaderResult> ReadAsync(ICommandContext context, string input, IServiceProvider services) | ||||
{ | { | ||||
var results = new Dictionary<ulong, TypeReaderValue>(); | var results = new Dictionary<ulong, TypeReaderValue>(); | ||||
IAsyncEnumerable<IUser> channelUsers = context.Channel.GetUsersAsync(CacheMode.CacheOnly).Flatten(); // it's better | |||||
IAsyncEnumerable<IUser> channelUsers = context.Channel.HasValue | |||||
? context.Channel.Value.GetUsersAsync(CacheMode.CacheOnly).Flatten() | |||||
: AsyncEnumerable.Empty<IUser>(); | |||||
IReadOnlyCollection<IGuildUser> guildUsers = ImmutableArray.Create<IGuildUser>(); | IReadOnlyCollection<IGuildUser> guildUsers = ImmutableArray.Create<IGuildUser>(); | ||||
if (context.Guild != null) | if (context.Guild != null) | ||||
@@ -29,8 +31,8 @@ namespace Discord.Commands | |||||
{ | { | ||||
if (context.Guild != null) | if (context.Guild != null) | ||||
AddResult(results, await context.Guild.GetUserAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) as T, 1.00f); | AddResult(results, await context.Guild.GetUserAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) as T, 1.00f); | ||||
else | |||||
AddResult(results, await context.Channel.GetUserAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) as T, 1.00f); | |||||
else if (context.Channel.HasValue) | |||||
AddResult(results, await context.Channel.Value.GetUserAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) as T, 1.00f); | |||||
} | } | ||||
//By Id (0.9) | //By Id (0.9) | ||||
@@ -38,8 +40,8 @@ namespace Discord.Commands | |||||
{ | { | ||||
if (context.Guild != null) | if (context.Guild != null) | ||||
AddResult(results, await context.Guild.GetUserAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) as T, 0.90f); | AddResult(results, await context.Guild.GetUserAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) as T, 0.90f); | ||||
else | |||||
AddResult(results, await context.Channel.GetUserAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) as T, 0.90f); | |||||
else if (context.Channel.HasValue) | |||||
AddResult(results, await context.Channel.Value.GetUserAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) as T, 0.90f); | |||||
} | } | ||||
//By Username + Discriminator (0.7-0.85) | //By Username + Discriminator (0.7-0.85) | ||||
@@ -14,9 +14,14 @@ namespace Discord.Commands | |||||
/// </summary> | /// </summary> | ||||
IGuild Guild { get; } | IGuild Guild { get; } | ||||
/// <summary> | /// <summary> | ||||
/// Gets the <see cref="IMessageChannel" /> that the command is executed in. | |||||
/// Gets the guild id that the command is executed in if it was in one. | |||||
/// </summary> | /// </summary> | ||||
IMessageChannel Channel { get; } | |||||
ulong? GuildId { get; } | |||||
/// <summary> | |||||
/// Gets the <see cref="IMessageChannel" /> that the command is executed in if cached; | |||||
/// otherwise just the channel id. | |||||
/// </summary> | |||||
Cacheable<IMessageChannel, ulong> Channel { get; } | |||||
/// <summary> | /// <summary> | ||||
/// Gets the <see cref="IUser" /> who executed the command. | /// Gets the <see cref="IUser" /> who executed the command. | ||||
/// </summary> | /// </summary> | ||||
@@ -66,11 +66,15 @@ namespace Discord | |||||
/// Time of when the message was last edited; <c>null</c> if the message is never edited. | /// Time of when the message was last edited; <c>null</c> if the message is never edited. | ||||
/// </returns> | /// </returns> | ||||
DateTimeOffset? EditedTimestamp { get; } | DateTimeOffset? EditedTimestamp { get; } | ||||
/// <summary> | /// <summary> | ||||
/// Gets the source channel of the message. | /// Gets the source channel of the message. | ||||
/// </summary> | /// </summary> | ||||
IMessageChannel Channel { get; } | |||||
Cacheable<IMessageChannel, ulong> Channel { get; } | |||||
/// <summary> | |||||
/// Gets the source guild id of the message if it was sent in one. | |||||
/// </summary> | |||||
ulong? GuildId { get; } | |||||
/// <summary> | /// <summary> | ||||
/// Gets the author of this message. | /// Gets the author of this message. | ||||
/// </summary> | /// </summary> | ||||
@@ -171,7 +175,7 @@ namespace Discord | |||||
/// A read-only collection of sticker objects. | /// A read-only collection of sticker objects. | ||||
/// </returns> | /// </returns> | ||||
IReadOnlyCollection<ISticker> Stickers { get; } | IReadOnlyCollection<ISticker> Stickers { get; } | ||||
/// <summary> | /// <summary> | ||||
/// Gets the flags related to this message. | /// Gets the flags related to this message. | ||||
/// </summary> | /// </summary> | ||||
@@ -17,7 +17,7 @@ namespace Discord | |||||
public static string GetJumpUrl(this IMessage msg) | public static string GetJumpUrl(this IMessage msg) | ||||
{ | { | ||||
var channel = msg.Channel; | var channel = msg.Channel; | ||||
return $"https://discord.com/channels/{(channel is IDMChannel ? "@me" : $"{(channel as ITextChannel).GuildId}")}/{channel.Id}/{msg.Id}"; | |||||
return $"https://discord.com/channels/{(msg.GuildId.HasValue ? $"{msg.GuildId}" : "@me")}/{channel.Id}/{msg.Id}"; | |||||
} | } | ||||
/// <summary> | /// <summary> | ||||
@@ -89,7 +89,8 @@ namespace Discord | |||||
/// </returns> | /// </returns> | ||||
public static async Task<IUserMessage> ReplyAsync(this IUserMessage msg, string text = null, bool isTTS = false, Embed embed = null, AllowedMentions allowedMentions = null, RequestOptions options = null) | public static async Task<IUserMessage> ReplyAsync(this IUserMessage msg, string text = null, bool isTTS = false, Embed embed = null, AllowedMentions allowedMentions = null, RequestOptions options = null) | ||||
{ | { | ||||
return await msg.Channel.SendMessageAsync(text, isTTS, embed, options, allowedMentions, new MessageReference(messageId: msg.Id)).ConfigureAwait(false); | |||||
IMessageChannel channel = await msg.Channel.GetOrDownloadAsync().ConfigureAwait(false); | |||||
return await channel.SendMessageAsync(text, isTTS, embed, options, allowedMentions, new MessageReference(messageId: msg.Id)).ConfigureAwait(false); | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -284,5 +284,24 @@ namespace Discord | |||||
/// that represents the gateway information related to the bot. | /// that represents the gateway information related to the bot. | ||||
/// </returns> | /// </returns> | ||||
Task<BotGateway> GetBotGatewayAsync(RequestOptions options = null); | Task<BotGateway> GetBotGatewayAsync(RequestOptions options = null); | ||||
/// <summary> | |||||
/// Sends a message to a channel. | |||||
/// </summary> | |||||
/// <param name="channelId">The channel to send the message.</param> | |||||
/// <param name="text">The message to be sent.</param> | |||||
/// <param name="isTTS">Determines whether the message should be read aloud by Discord or not.</param> | |||||
/// <param name="embed">The <see cref="Discord.EmbedType.Rich"/> <see cref="Embed"/> to be sent.</param> | |||||
/// <param name="allowedMentions"> | |||||
/// Specifies if notifications are sent for mentioned users and roles in the message <paramref name="text"/>. | |||||
/// If <c>null</c>, all mentioned roles and users will be notified. | |||||
/// </param> | |||||
/// <param name="messageReference">The reference for this message.</param> | |||||
/// <param name="options">The options to be used when sending the request.</param> | |||||
/// <returns> | |||||
/// A task that represents an asynchronous send operation for delivering the message. The task result | |||||
/// contains the sent message. | |||||
/// </returns> | |||||
Task<IUserMessage> SendMessageAsync(ulong channelId, string text = null, bool isTTS = false, Embed embed = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, RequestOptions options = null); | |||||
} | } | ||||
} | } |
@@ -84,12 +84,12 @@ namespace Discord.Net.Examples.WebSocket | |||||
// check if the message is a user message as opposed to a system message (e.g. Clyde, pins, etc.) | // check if the message is a user message as opposed to a system message (e.g. Clyde, pins, etc.) | ||||
if (!(message is SocketUserMessage userMessage)) return Task.CompletedTask; | if (!(message is SocketUserMessage userMessage)) return Task.CompletedTask; | ||||
// check if the message origin is a guild message channel | // check if the message origin is a guild message channel | ||||
if (!(userMessage.Channel is SocketTextChannel textChannel)) return Task.CompletedTask; | |||||
if (userMessage.GuildId == null) return Task.CompletedTask; | |||||
// check if the target user was mentioned | // check if the target user was mentioned | ||||
var targetUsers = userMessage.MentionedUsers.Where(x => _targetUserIds.Contains(x.Id)); | var targetUsers = userMessage.MentionedUsers.Where(x => _targetUserIds.Contains(x.Id)); | ||||
foreach (var targetUser in targetUsers) | foreach (var targetUser in targetUsers) | ||||
Console.WriteLine( | Console.WriteLine( | ||||
$"{targetUser} was mentioned in the message '{message.Content}' by {message.Author} in {textChannel.Name}."); | |||||
$"{targetUser} was mentioned in the message '{message.Content}' by {message.Author} in {MentionUtils.MentionChannel(message.Channel.Id)}."); | |||||
return Task.CompletedTask; | return Task.CompletedTask; | ||||
} | } | ||||
@@ -216,6 +216,10 @@ namespace Discord.Rest | |||||
Task<IWebhook> IDiscordClient.GetWebhookAsync(ulong id, RequestOptions options) | Task<IWebhook> IDiscordClient.GetWebhookAsync(ulong id, RequestOptions options) | ||||
=> Task.FromResult<IWebhook>(null); | => Task.FromResult<IWebhook>(null); | ||||
/// <inheritdoc /> | |||||
Task<IUserMessage> IDiscordClient.SendMessageAsync(ulong channelId, string text, bool isTTS, Embed embed, AllowedMentions allowedMentions, MessageReference messageReference, RequestOptions options) | |||||
=> Task.FromResult<IUserMessage>(null); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
Task IDiscordClient.StartAsync() | Task IDiscordClient.StartAsync() | ||||
=> Task.Delay(0); | => Task.Delay(0); | ||||
@@ -114,11 +114,36 @@ namespace Discord.Rest | |||||
=> MessageHelper.RemoveAllReactionsAsync(channelId, messageId, this, options); | => MessageHelper.RemoveAllReactionsAsync(channelId, messageId, this, options); | ||||
public Task RemoveAllReactionsForEmoteAsync(ulong channelId, ulong messageId, IEmote emote, RequestOptions options = null) | public Task RemoveAllReactionsForEmoteAsync(ulong channelId, ulong messageId, IEmote emote, RequestOptions options = null) | ||||
=> MessageHelper.RemoveAllReactionsForEmoteAsync(channelId, messageId, emote, this, options); | => MessageHelper.RemoveAllReactionsForEmoteAsync(channelId, messageId, emote, this, options); | ||||
/// <summary> | |||||
/// Sends a message to a channel. | |||||
/// </summary> | |||||
/// <param name="channelId">The channel to send the message.</param> | |||||
/// <param name="text">The message to be sent.</param> | |||||
/// <param name="isTTS">Determines whether the message should be read aloud by Discord or not.</param> | |||||
/// <param name="embed">The <see cref="Discord.EmbedType.Rich"/> <see cref="Embed"/> to be sent.</param> | |||||
/// <param name="allowedMentions"> | |||||
/// Specifies if notifications are sent for mentioned users and roles in the message <paramref name="text"/>. | |||||
/// If <c>null</c>, all mentioned roles and users will be notified. | |||||
/// </param> | |||||
/// <param name="messageReference">The reference for this message.</param> | |||||
/// <param name="options">The options to be used when sending the request.</param> | |||||
/// <returns> | |||||
/// A task that represents an asynchronous send operation for delivering the message. The task result | |||||
/// contains the sent message. | |||||
/// </returns> | |||||
public Task<RestUserMessage> SendMessageAsync(ulong channelId, string text = null, bool isTTS = false, Embed embed = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, RequestOptions options = null) | |||||
=> ChannelHelper.SendMessageAsync(channelId, this, text, isTTS, embed, allowedMentions, messageReference, options ?? RequestOptions.Default); | |||||
//IDiscordClient | //IDiscordClient | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
async Task<IApplication> IDiscordClient.GetApplicationInfoAsync(RequestOptions options) | async Task<IApplication> IDiscordClient.GetApplicationInfoAsync(RequestOptions options) | ||||
=> await GetApplicationInfoAsync(options).ConfigureAwait(false); | => await GetApplicationInfoAsync(options).ConfigureAwait(false); | ||||
/// <inheritdoc /> | |||||
async Task<IUserMessage> IDiscordClient.SendMessageAsync(ulong channelId, string message, bool isTTS, Embed embed, AllowedMentions allowedMentions, MessageReference messageReference, RequestOptions options) | |||||
=> await SendMessageAsync(channelId, message, isTTS, embed, allowedMentions, messageReference, options ?? RequestOptions.Default).ConfigureAwait(false); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
async Task<IChannel> IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) | async Task<IChannel> IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) | ||||
{ | { | ||||
@@ -223,7 +223,34 @@ namespace Discord.Rest | |||||
var args = new CreateMessageParams(text) { IsTTS = isTTS, Embed = embed?.ToModel(), AllowedMentions = allowedMentions?.ToModel(), MessageReference = messageReference?.ToModel() }; | var args = new CreateMessageParams(text) { IsTTS = isTTS, Embed = embed?.ToModel(), AllowedMentions = allowedMentions?.ToModel(), MessageReference = messageReference?.ToModel() }; | ||||
var model = await client.ApiClient.CreateMessageAsync(channel.Id, args, options).ConfigureAwait(false); | var model = await client.ApiClient.CreateMessageAsync(channel.Id, args, options).ConfigureAwait(false); | ||||
return RestUserMessage.Create(client, channel, client.CurrentUser, model); | |||||
return (RestUserMessage)RestMessage.Create(client, channel, client.CurrentUser, model); | |||||
} | |||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | |||||
public static async Task<RestUserMessage> SendMessageAsync(ulong channelId, BaseDiscordClient client, | |||||
string text, bool isTTS, Embed embed, AllowedMentions allowedMentions, MessageReference messageReference, RequestOptions options) | |||||
{ | |||||
Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); | |||||
Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); | |||||
// check that user flag and user Id list are exclusive, same with role flag and role Id list | |||||
if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue) | |||||
{ | |||||
if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users) && | |||||
allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0) | |||||
{ | |||||
throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(allowedMentions)); | |||||
} | |||||
if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles) && | |||||
allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0) | |||||
{ | |||||
throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(allowedMentions)); | |||||
} | |||||
} | |||||
var args = new CreateMessageParams(text) { IsTTS = isTTS, Embed = embed?.ToModel(), AllowedMentions = allowedMentions?.ToModel(), MessageReference = messageReference?.ToModel() }; | |||||
var model = await client.ApiClient.CreateMessageAsync(channelId, args, options).ConfigureAwait(false); | |||||
return (RestUserMessage)RestMessage.Create(client, null, client.CurrentUser, model); | |||||
} | } | ||||
/// <exception cref="ArgumentException"> | /// <exception cref="ArgumentException"> | ||||
@@ -283,14 +310,14 @@ namespace Discord.Rest | |||||
var args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS, Embed = embed?.ToModel() ?? Optional<API.Embed>.Unspecified, AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified, MessageReference = messageReference?.ToModel() ?? Optional<API.MessageReference>.Unspecified, IsSpoiler = isSpoiler }; | var args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS, Embed = embed?.ToModel() ?? Optional<API.Embed>.Unspecified, AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified, MessageReference = messageReference?.ToModel() ?? Optional<API.MessageReference>.Unspecified, IsSpoiler = isSpoiler }; | ||||
var model = await client.ApiClient.UploadFileAsync(channel.Id, args, options).ConfigureAwait(false); | var model = await client.ApiClient.UploadFileAsync(channel.Id, args, options).ConfigureAwait(false); | ||||
return RestUserMessage.Create(client, channel, client.CurrentUser, model); | |||||
return (RestUserMessage)RestMessage.Create(client, channel, client.CurrentUser, model); | |||||
} | } | ||||
public static async Task<RestUserMessage> ModifyMessageAsync(IMessageChannel channel, ulong messageId, Action<MessageProperties> func, | public static async Task<RestUserMessage> ModifyMessageAsync(IMessageChannel channel, ulong messageId, Action<MessageProperties> func, | ||||
BaseDiscordClient client, RequestOptions options) | BaseDiscordClient client, RequestOptions options) | ||||
{ | { | ||||
var msgModel = await MessageHelper.ModifyAsync(channel.Id, messageId, client, func, options).ConfigureAwait(false); | var msgModel = await MessageHelper.ModifyAsync(channel.Id, messageId, client, func, options).ConfigureAwait(false); | ||||
return RestUserMessage.Create(client, channel, msgModel.Author.IsSpecified ? RestUser.Create(client, msgModel.Author.Value) : client.CurrentUser, msgModel); | |||||
return (RestUserMessage)RestMessage.Create(client, channel, msgModel.Author.IsSpecified ? RestUser.Create(client, msgModel.Author.Value) : client.CurrentUser, msgModel); | |||||
} | } | ||||
public static Task DeleteMessageAsync(IMessageChannel channel, ulong messageId, BaseDiscordClient client, | public static Task DeleteMessageAsync(IMessageChannel channel, ulong messageId, BaseDiscordClient client, | ||||
@@ -16,7 +16,9 @@ namespace Discord.Rest | |||||
private ImmutableArray<RestReaction> _reactions = ImmutableArray.Create<RestReaction>(); | private ImmutableArray<RestReaction> _reactions = ImmutableArray.Create<RestReaction>(); | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public IMessageChannel Channel { get; } | |||||
public Cacheable<IMessageChannel, ulong> Channel { get; } | |||||
/// <inheritdoc /> | |||||
public ulong? GuildId { get; private set; } | |||||
/// <summary> | /// <summary> | ||||
/// Gets the Author of the message. | /// Gets the Author of the message. | ||||
/// </summary> | /// </summary> | ||||
@@ -74,7 +76,7 @@ namespace Discord.Rest | |||||
/// <inheritdoc/> | /// <inheritdoc/> | ||||
public MessageType Type { get; private set; } | public MessageType Type { get; private set; } | ||||
internal RestMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, IUser author, MessageSource source) | |||||
internal RestMessage(BaseDiscordClient discord, ulong id, Cacheable<IMessageChannel, ulong> channel, IUser author, MessageSource source) | |||||
: base(discord, id) | : base(discord, id) | ||||
{ | { | ||||
Channel = channel; | Channel = channel; | ||||
@@ -83,15 +85,24 @@ namespace Discord.Rest | |||||
} | } | ||||
internal static RestMessage Create(BaseDiscordClient discord, IMessageChannel channel, IUser author, Model model) | internal static RestMessage Create(BaseDiscordClient discord, IMessageChannel channel, IUser author, Model model) | ||||
{ | { | ||||
var cacheableChannel = new Cacheable<IMessageChannel, ulong>( | |||||
channel, | |||||
model.ChannelId, | |||||
channel != null, | |||||
async () => (IMessageChannel)await ClientHelper.GetChannelAsync(discord, model.ChannelId, RequestOptions.Default).ConfigureAwait(false)); | |||||
if (model.Type == MessageType.Default || model.Type == MessageType.Reply) | if (model.Type == MessageType.Default || model.Type == MessageType.Reply) | ||||
return RestUserMessage.Create(discord, channel, author, model); | |||||
return RestUserMessage.Create(discord, cacheableChannel, author, model); | |||||
else | else | ||||
return RestSystemMessage.Create(discord, channel, author, model); | |||||
return RestSystemMessage.Create(discord, cacheableChannel, author, model); | |||||
} | } | ||||
internal virtual void Update(Model model) | internal virtual void Update(Model model) | ||||
{ | { | ||||
Type = model.Type; | Type = model.Type; | ||||
if (model.GuildId.IsSpecified) | |||||
GuildId = model.GuildId.Value; | |||||
if (model.Timestamp.IsSpecified) | if (model.Timestamp.IsSpecified) | ||||
_timestampTicks = model.Timestamp.Value.UtcTicks; | _timestampTicks = model.Timestamp.Value.UtcTicks; | ||||
@@ -9,11 +9,11 @@ namespace Discord.Rest | |||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] | [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | ||||
public class RestSystemMessage : RestMessage, ISystemMessage | public class RestSystemMessage : RestMessage, ISystemMessage | ||||
{ | { | ||||
internal RestSystemMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, IUser author) | |||||
internal RestSystemMessage(BaseDiscordClient discord, ulong id, Cacheable<IMessageChannel, ulong> channel, IUser author) | |||||
: base(discord, id, channel, author, MessageSource.System) | : base(discord, id, channel, author, MessageSource.System) | ||||
{ | { | ||||
} | } | ||||
internal new static RestSystemMessage Create(BaseDiscordClient discord, IMessageChannel channel, IUser author, Model model) | |||||
internal static RestSystemMessage Create(BaseDiscordClient discord, Cacheable<IMessageChannel, ulong> channel, IUser author, Model model) | |||||
{ | { | ||||
var entity = new RestSystemMessage(discord, model.Id, channel, author); | var entity = new RestSystemMessage(discord, model.Id, channel, author); | ||||
entity.Update(model); | entity.Update(model); | ||||
@@ -50,11 +50,11 @@ namespace Discord.Rest | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public IUserMessage ReferencedMessage => _referencedMessage; | public IUserMessage ReferencedMessage => _referencedMessage; | ||||
internal RestUserMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, IUser author, MessageSource source) | |||||
internal RestUserMessage(BaseDiscordClient discord, ulong id, Cacheable<IMessageChannel, ulong> channel, IUser author, MessageSource source) | |||||
: base(discord, id, channel, author, source) | : base(discord, id, channel, author, source) | ||||
{ | { | ||||
} | } | ||||
internal new static RestUserMessage Create(BaseDiscordClient discord, IMessageChannel channel, IUser author, Model model) | |||||
internal static RestUserMessage Create(BaseDiscordClient discord, Cacheable<IMessageChannel, ulong> channel, IUser author, Model model) | |||||
{ | { | ||||
var entity = new RestUserMessage(discord, model.Id, channel, author, MessageHelper.GetSource(model)); | var entity = new RestUserMessage(discord, model.Id, channel, author, MessageHelper.GetSource(model)); | ||||
entity.Update(model); | entity.Update(model); | ||||
@@ -120,7 +120,7 @@ namespace Discord.Rest | |||||
} | } | ||||
} | } | ||||
var guildId = (Channel as IGuildChannel)?.GuildId; | |||||
ulong? guildId = model.GuildId.IsSpecified ? model.GuildId.Value : null; | |||||
var guild = guildId != null ? (Discord as IDiscordClient).GetGuildAsync(guildId.Value, CacheMode.CacheOnly).Result : null; | var guild = guildId != null ? (Discord as IDiscordClient).GetGuildAsync(guildId.Value, CacheMode.CacheOnly).Result : null; | ||||
if (model.Content.IsSpecified) | if (model.Content.IsSpecified) | ||||
{ | { | ||||
@@ -177,7 +177,7 @@ namespace Discord.Rest | |||||
/// <exception cref="InvalidOperationException">This operation may only be called on a <see cref="INewsChannel"/> channel.</exception> | /// <exception cref="InvalidOperationException">This operation may only be called on a <see cref="INewsChannel"/> channel.</exception> | ||||
public async Task CrosspostAsync(RequestOptions options = null) | public async Task CrosspostAsync(RequestOptions options = null) | ||||
{ | { | ||||
if (!(Channel is INewsChannel)) | |||||
if (Channel.HasValue && !(Channel.Value is INewsChannel)) | |||||
{ | { | ||||
throw new InvalidOperationException("Publishing (crossposting) is only valid in news channels."); | throw new InvalidOperationException("Publishing (crossposting) is only valid in news channels."); | ||||
} | } | ||||
@@ -268,12 +268,35 @@ namespace Discord.WebSocket | |||||
/// </returns> | /// </returns> | ||||
public Task<RestInviteMetadata> GetInviteAsync(string inviteId, RequestOptions options = null) | public Task<RestInviteMetadata> GetInviteAsync(string inviteId, RequestOptions options = null) | ||||
=> ClientHelper.GetInviteAsync(this, inviteId, options ?? RequestOptions.Default); | => ClientHelper.GetInviteAsync(this, inviteId, options ?? RequestOptions.Default); | ||||
/// <summary> | |||||
/// Sends a message to a channel. | |||||
/// </summary> | |||||
/// <param name="channelId">The channel to send the message.</param> | |||||
/// <param name="text">The message to be sent.</param> | |||||
/// <param name="isTTS">Determines whether the message should be read aloud by Discord or not.</param> | |||||
/// <param name="embed">The <see cref="Discord.EmbedType.Rich"/> <see cref="Embed"/> to be sent.</param> | |||||
/// <param name="allowedMentions"> | |||||
/// Specifies if notifications are sent for mentioned users and roles in the message <paramref name="text"/>. | |||||
/// If <c>null</c>, all mentioned roles and users will be notified. | |||||
/// </param> | |||||
/// <param name="messageReference">The reference for this message.</param> | |||||
/// <param name="options">The options to be used when sending the request.</param> | |||||
/// <returns> | |||||
/// A task that represents an asynchronous send operation for delivering the message. The task result | |||||
/// contains the sent message. | |||||
/// </returns> | |||||
public Task<RestUserMessage> SendMessageAsync(ulong channelId, string text = null, bool isTTS = false, Embed embed = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, RequestOptions options = null) | |||||
=> ChannelHelper.SendMessageAsync(channelId, this, text, isTTS, embed, allowedMentions, messageReference, options ?? RequestOptions.Default); | |||||
// IDiscordClient | // IDiscordClient | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
async Task<IApplication> IDiscordClient.GetApplicationInfoAsync(RequestOptions options) | async Task<IApplication> IDiscordClient.GetApplicationInfoAsync(RequestOptions options) | ||||
=> await GetApplicationInfoAsync(options).ConfigureAwait(false); | => await GetApplicationInfoAsync(options).ConfigureAwait(false); | ||||
/// <inheritdoc /> | |||||
async Task<IUserMessage> IDiscordClient.SendMessageAsync(ulong channelId, string message, bool isTTS, Embed embed, AllowedMentions allowedMentions, MessageReference messageReference, RequestOptions options) | |||||
=> await SendMessageAsync(channelId, message, isTTS, embed, allowedMentions, messageReference, options ?? RequestOptions.Default).ConfigureAwait(false); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
Task<IChannel> IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) | Task<IChannel> IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) | ||||
=> Task.FromResult<IChannel>(GetChannel(id)); | => Task.FromResult<IChannel>(GetChannel(id)); | ||||
@@ -9,7 +9,7 @@ namespace Discord.Commands | |||||
public new DiscordShardedClient Client { get; } | public new DiscordShardedClient Client { get; } | ||||
public ShardedCommandContext(DiscordShardedClient client, SocketUserMessage msg) | public ShardedCommandContext(DiscordShardedClient client, SocketUserMessage msg) | ||||
: base(client.GetShard(GetShardId(client, (msg.Channel as SocketGuildChannel)?.Guild)), msg) | |||||
: base(client.GetShardFor(msg.GuildId ?? 0), msg) | |||||
{ | { | ||||
Client = client; | Client = client; | ||||
} | } | ||||
@@ -15,10 +15,10 @@ namespace Discord.Commands | |||||
/// Gets the <see cref="SocketGuild" /> that the command is executed in. | /// Gets the <see cref="SocketGuild" /> that the command is executed in. | ||||
/// </summary> | /// </summary> | ||||
public SocketGuild Guild { get; } | public SocketGuild Guild { get; } | ||||
/// <summary> | |||||
/// Gets the <see cref="ISocketMessageChannel" /> that the command is executed in. | |||||
/// </summary> | |||||
public ISocketMessageChannel Channel { get; } | |||||
/// <inheritdoc/> | |||||
public ulong? GuildId { get; } | |||||
/// <inheritdoc/> | |||||
public Cacheable<IMessageChannel, ulong> Channel { get; } | |||||
/// <summary> | /// <summary> | ||||
/// Gets the <see cref="SocketUser" /> who executed the command. | /// Gets the <see cref="SocketUser" /> who executed the command. | ||||
/// </summary> | /// </summary> | ||||
@@ -31,7 +31,7 @@ namespace Discord.Commands | |||||
/// <summary> | /// <summary> | ||||
/// Indicates whether the channel that the command is executed in is a private channel. | /// Indicates whether the channel that the command is executed in is a private channel. | ||||
/// </summary> | /// </summary> | ||||
public bool IsPrivate => Channel is IPrivateChannel; | |||||
public bool IsPrivate => GuildId == null; | |||||
/// <summary> | /// <summary> | ||||
/// Initializes a new <see cref="SocketCommandContext" /> class with the provided client and message. | /// Initializes a new <see cref="SocketCommandContext" /> class with the provided client and message. | ||||
@@ -41,7 +41,9 @@ namespace Discord.Commands | |||||
public SocketCommandContext(DiscordSocketClient client, SocketUserMessage msg) | public SocketCommandContext(DiscordSocketClient client, SocketUserMessage msg) | ||||
{ | { | ||||
Client = client; | Client = client; | ||||
Guild = (msg.Channel as SocketGuildChannel)?.Guild; | |||||
GuildId = msg.GuildId; | |||||
if (msg.GuildId != null) | |||||
Guild = client.GetGuild(msg.GuildId.Value); | |||||
Channel = msg.Channel; | Channel = msg.Channel; | ||||
User = msg.Author; | User = msg.Author; | ||||
Message = msg; | Message = msg; | ||||
@@ -53,7 +55,7 @@ namespace Discord.Commands | |||||
/// <inheritdoc/> | /// <inheritdoc/> | ||||
IGuild ICommandContext.Guild => Guild; | IGuild ICommandContext.Guild => Guild; | ||||
/// <inheritdoc/> | /// <inheritdoc/> | ||||
IMessageChannel ICommandContext.Channel => Channel; | |||||
Cacheable<IMessageChannel, ulong> ICommandContext.Channel => Channel; | |||||
/// <inheritdoc/> | /// <inheritdoc/> | ||||
IUser ICommandContext.User => User; | IUser ICommandContext.User => User; | ||||
/// <inheritdoc/> | /// <inheritdoc/> | ||||
@@ -179,11 +179,11 @@ namespace Discord.WebSocket | |||||
return _shards[id]; | return _shards[id]; | ||||
return null; | return null; | ||||
} | } | ||||
private int GetShardIdFor(ulong guildId) | |||||
public int GetShardIdFor(ulong guildId) | |||||
=> (int)((guildId >> 22) % (uint)_totalShards); | => (int)((guildId >> 22) % (uint)_totalShards); | ||||
public int GetShardIdFor(IGuild guild) | public int GetShardIdFor(IGuild guild) | ||||
=> GetShardIdFor(guild?.Id ?? 0); | => GetShardIdFor(guild?.Id ?? 0); | ||||
private DiscordSocketClient GetShardFor(ulong guildId) | |||||
public DiscordSocketClient GetShardFor(ulong guildId) | |||||
=> GetShard(GetShardIdFor(guildId)); | => GetShard(GetShardIdFor(guildId)); | ||||
public DiscordSocketClient GetShardFor(IGuild guild) | public DiscordSocketClient GetShardFor(IGuild guild) | ||||
=> GetShardFor(guild?.Id ?? 0); | => GetShardFor(guild?.Id ?? 0); | ||||
@@ -1263,24 +1263,16 @@ namespace Discord.WebSocket | |||||
var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; | var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; | ||||
var guild = (channel as SocketGuildChannel)?.Guild; | var guild = (channel as SocketGuildChannel)?.Guild; | ||||
if (channel == null && data.GuildId.IsSpecified) | |||||
guild = State.GetGuild(data.GuildId.Value); | |||||
if (guild != null && !guild.IsSynced) | if (guild != null && !guild.IsSynced) | ||||
{ | { | ||||
await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); | await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); | ||||
return; | return; | ||||
} | } | ||||
if (channel == null) | |||||
{ | |||||
if (!data.GuildId.IsSpecified) // assume it is a DM | |||||
{ | |||||
channel = CreateDMChannel(data.ChannelId, data.Author.Value, State); | |||||
} | |||||
else | |||||
{ | |||||
await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); | |||||
return; | |||||
} | |||||
} | |||||
if (channel == null && !data.GuildId.IsSpecified) // assume it is a DM | |||||
channel = CreateDMChannel(data.ChannelId, data.Author.Value, State); | |||||
SocketUser author; | SocketUser author; | ||||
if (guild != null) | if (guild != null) | ||||
@@ -1291,7 +1283,7 @@ namespace Discord.WebSocket | |||||
author = guild.GetUser(data.Author.Value.Id); | author = guild.GetUser(data.Author.Value.Id); | ||||
} | } | ||||
else | else | ||||
author = (channel as SocketChannel).GetUser(data.Author.Value.Id); | |||||
author = (channel as SocketChannel)?.GetUser(data.Author.Value.Id); | |||||
if (author == null) | if (author == null) | ||||
{ | { | ||||
@@ -1309,13 +1301,14 @@ namespace Discord.WebSocket | |||||
author = groupChannel.GetOrAddUser(data.Author.Value); | author = groupChannel.GetOrAddUser(data.Author.Value); | ||||
else | else | ||||
{ | { | ||||
await UnknownChannelUserAsync(type, data.Author.Value.Id, channel.Id).ConfigureAwait(false); | |||||
await UnknownChannelUserAsync(type, data.Author.Value.Id, data.ChannelId).ConfigureAwait(false); | |||||
return; | return; | ||||
} | } | ||||
} | } | ||||
var msg = SocketMessage.Create(this, State, author, channel, data); | var msg = SocketMessage.Create(this, State, author, channel, data); | ||||
SocketChannelHelper.AddMessage(channel, this, msg); | |||||
if (channel != null) | |||||
SocketChannelHelper.AddMessage(channel, this, msg); | |||||
await TimedInvokeAsync(_messageReceivedEvent, nameof(MessageReceived), msg).ConfigureAwait(false); | await TimedInvokeAsync(_messageReceivedEvent, nameof(MessageReceived), msg).ConfigureAwait(false); | ||||
} | } | ||||
break; | break; | ||||
@@ -1327,6 +1320,8 @@ namespace Discord.WebSocket | |||||
var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; | var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; | ||||
var guild = (channel as SocketGuildChannel)?.Guild; | var guild = (channel as SocketGuildChannel)?.Guild; | ||||
if (channel == null && data.GuildId.IsSpecified) | |||||
guild = State.GetGuild(data.GuildId.Value); | |||||
if (guild != null && !guild.IsSynced) | if (guild != null && !guild.IsSynced) | ||||
{ | { | ||||
await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); | await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); | ||||
@@ -1391,11 +1386,6 @@ namespace Discord.WebSocket | |||||
else | else | ||||
channel = CreateDMChannel(data.ChannelId, author, State); | channel = CreateDMChannel(data.ChannelId, author, State); | ||||
} | } | ||||
else | |||||
{ | |||||
await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); | |||||
return; | |||||
} | |||||
} | } | ||||
after = SocketMessage.Create(this, State, author, channel, data); | after = SocketMessage.Create(this, State, author, channel, data); | ||||
@@ -23,13 +23,10 @@ namespace Discord.WebSocket | |||||
/// A WebSocket-based user object. | /// A WebSocket-based user object. | ||||
/// </returns> | /// </returns> | ||||
public SocketUser Author { get; } | public SocketUser Author { get; } | ||||
/// <summary> | |||||
/// Gets the source channel of the message. | |||||
/// </summary> | |||||
/// <returns> | |||||
/// A WebSocket-based message channel. | |||||
/// </returns> | |||||
public ISocketMessageChannel Channel { get; } | |||||
/// <inheritdoc /> | |||||
public Cacheable<IMessageChannel, ulong> Channel { get; } | |||||
/// <inheritdoc /> | |||||
public ulong? GuildId { get; private set; } | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public MessageSource Source { get; } | public MessageSource Source { get; } | ||||
@@ -85,13 +82,8 @@ namespace Discord.WebSocket | |||||
/// Collection of WebSocket-based guild channels. | /// Collection of WebSocket-based guild channels. | ||||
/// </returns> | /// </returns> | ||||
public virtual IReadOnlyCollection<SocketGuildChannel> MentionedChannels => ImmutableArray.Create<SocketGuildChannel>(); | public virtual IReadOnlyCollection<SocketGuildChannel> MentionedChannels => ImmutableArray.Create<SocketGuildChannel>(); | ||||
/// <summary> | |||||
/// Returns the roles mentioned in this message. | |||||
/// </summary> | |||||
/// <returns> | |||||
/// Collection of WebSocket-based roles. | |||||
/// </returns> | |||||
public virtual IReadOnlyCollection<SocketRole> MentionedRoles => ImmutableArray.Create<SocketRole>(); | |||||
/// <inheritdoc/> | |||||
public virtual IReadOnlyCollection<ulong> MentionedRoleIds => ImmutableArray.Create<ulong>(); | |||||
/// <summary> | /// <summary> | ||||
/// Returns the users mentioned in this message. | /// Returns the users mentioned in this message. | ||||
/// </summary> | /// </summary> | ||||
@@ -109,24 +101,33 @@ namespace Discord.WebSocket | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks); | public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks); | ||||
internal SocketMessage(DiscordSocketClient discord, ulong id, ISocketMessageChannel channel, SocketUser author, MessageSource source) | |||||
internal SocketMessage(DiscordSocketClient discord, ulong id, Cacheable<IMessageChannel, ulong> channel, SocketUser author, MessageSource source) | |||||
: base(discord, id) | : base(discord, id) | ||||
{ | { | ||||
Channel = channel; | Channel = channel; | ||||
Author = author; | Author = author; | ||||
Source = source; | Source = source; | ||||
} | } | ||||
internal static SocketMessage Create(DiscordSocketClient discord, ClientState state, SocketUser author, ISocketMessageChannel channel, Model model) | |||||
internal static SocketMessage Create(DiscordSocketClient discord, ClientState state, SocketUser author, IMessageChannel channel, Model model) | |||||
{ | { | ||||
var cacheableChannel = new Cacheable<IMessageChannel, ulong>( | |||||
channel, | |||||
model.ChannelId, | |||||
channel != null, | |||||
async () => (IMessageChannel)await ClientHelper.GetChannelAsync(discord, model.ChannelId, RequestOptions.Default).ConfigureAwait(false)); | |||||
if (model.Type == MessageType.Default || model.Type == MessageType.Reply) | if (model.Type == MessageType.Default || model.Type == MessageType.Reply) | ||||
return SocketUserMessage.Create(discord, state, author, channel, model); | |||||
return SocketUserMessage.Create(discord, state, author, cacheableChannel, model); | |||||
else | else | ||||
return SocketSystemMessage.Create(discord, state, author, channel, model); | |||||
return SocketSystemMessage.Create(discord, state, author, cacheableChannel, model); | |||||
} | } | ||||
internal virtual void Update(ClientState state, Model model) | internal virtual void Update(ClientState state, Model model) | ||||
{ | { | ||||
Type = model.Type; | Type = model.Type; | ||||
if (model.GuildId.IsSpecified) | |||||
GuildId = model.GuildId.Value; | |||||
if (model.Timestamp.IsSpecified) | if (model.Timestamp.IsSpecified) | ||||
_timestampTicks = model.Timestamp.Value.UtcTicks; | _timestampTicks = model.Timestamp.Value.UtcTicks; | ||||
@@ -188,16 +189,12 @@ namespace Discord.WebSocket | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
IUser IMessage.Author => Author; | IUser IMessage.Author => Author; | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
IMessageChannel IMessage.Channel => Channel; | |||||
/// <inheritdoc /> | |||||
IReadOnlyCollection<IAttachment> IMessage.Attachments => Attachments; | IReadOnlyCollection<IAttachment> IMessage.Attachments => Attachments; | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
IReadOnlyCollection<IEmbed> IMessage.Embeds => Embeds; | IReadOnlyCollection<IEmbed> IMessage.Embeds => Embeds; | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
IReadOnlyCollection<ulong> IMessage.MentionedChannelIds => MentionedChannels.Select(x => x.Id).ToImmutableArray(); | IReadOnlyCollection<ulong> IMessage.MentionedChannelIds => MentionedChannels.Select(x => x.Id).ToImmutableArray(); | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
IReadOnlyCollection<ulong> IMessage.MentionedRoleIds => MentionedRoles.Select(x => x.Id).ToImmutableArray(); | |||||
/// <inheritdoc /> | |||||
IReadOnlyCollection<ulong> IMessage.MentionedUserIds => MentionedUsers.Select(x => x.Id).ToImmutableArray(); | IReadOnlyCollection<ulong> IMessage.MentionedUserIds => MentionedUsers.Select(x => x.Id).ToImmutableArray(); | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
IReadOnlyCollection<ISticker> IMessage.Stickers => Stickers; | IReadOnlyCollection<ISticker> IMessage.Stickers => Stickers; | ||||
@@ -9,11 +9,11 @@ namespace Discord.WebSocket | |||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] | [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | ||||
public class SocketSystemMessage : SocketMessage, ISystemMessage | public class SocketSystemMessage : SocketMessage, ISystemMessage | ||||
{ | { | ||||
internal SocketSystemMessage(DiscordSocketClient discord, ulong id, ISocketMessageChannel channel, SocketUser author) | |||||
internal SocketSystemMessage(DiscordSocketClient discord, ulong id, Cacheable<IMessageChannel, ulong> channel, SocketUser author) | |||||
: base(discord, id, channel, author, MessageSource.System) | : base(discord, id, channel, author, MessageSource.System) | ||||
{ | { | ||||
} | } | ||||
internal new static SocketSystemMessage Create(DiscordSocketClient discord, ClientState state, SocketUser author, ISocketMessageChannel channel, Model model) | |||||
internal static SocketSystemMessage Create(DiscordSocketClient discord, ClientState state, SocketUser author, Cacheable<IMessageChannel, ulong> channel, Model model) | |||||
{ | { | ||||
var entity = new SocketSystemMessage(discord, model.Id, channel, author); | var entity = new SocketSystemMessage(discord, model.Id, channel, author); | ||||
entity.Update(state, model); | entity.Update(state, model); | ||||
@@ -21,7 +21,7 @@ namespace Discord.WebSocket | |||||
private ImmutableArray<Attachment> _attachments = ImmutableArray.Create<Attachment>(); | private ImmutableArray<Attachment> _attachments = ImmutableArray.Create<Attachment>(); | ||||
private ImmutableArray<Embed> _embeds = ImmutableArray.Create<Embed>(); | private ImmutableArray<Embed> _embeds = ImmutableArray.Create<Embed>(); | ||||
private ImmutableArray<ITag> _tags = ImmutableArray.Create<ITag>(); | private ImmutableArray<ITag> _tags = ImmutableArray.Create<ITag>(); | ||||
private ImmutableArray<SocketRole> _roleMentions = ImmutableArray.Create<SocketRole>(); | |||||
private ImmutableArray<ulong> _roleIdMentions = ImmutableArray.Create<ulong>(); | |||||
private ImmutableArray<SocketUser> _userMentions = ImmutableArray.Create<SocketUser>(); | private ImmutableArray<SocketUser> _userMentions = ImmutableArray.Create<SocketUser>(); | ||||
private ImmutableArray<Sticker> _stickers = ImmutableArray.Create<Sticker>(); | private ImmutableArray<Sticker> _stickers = ImmutableArray.Create<Sticker>(); | ||||
@@ -44,7 +44,7 @@ namespace Discord.WebSocket | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public override IReadOnlyCollection<SocketGuildChannel> MentionedChannels => MessageHelper.FilterTagsByValue<SocketGuildChannel>(TagType.ChannelMention, _tags); | public override IReadOnlyCollection<SocketGuildChannel> MentionedChannels => MessageHelper.FilterTagsByValue<SocketGuildChannel>(TagType.ChannelMention, _tags); | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public override IReadOnlyCollection<SocketRole> MentionedRoles => _roleMentions; | |||||
public override IReadOnlyCollection<ulong> MentionedRoleIds => _roleIdMentions; | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public override IReadOnlyCollection<SocketUser> MentionedUsers => _userMentions; | public override IReadOnlyCollection<SocketUser> MentionedUsers => _userMentions; | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
@@ -52,11 +52,11 @@ namespace Discord.WebSocket | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public IUserMessage ReferencedMessage => _referencedMessage; | public IUserMessage ReferencedMessage => _referencedMessage; | ||||
internal SocketUserMessage(DiscordSocketClient discord, ulong id, ISocketMessageChannel channel, SocketUser author, MessageSource source) | |||||
internal SocketUserMessage(DiscordSocketClient discord, ulong id, Cacheable<IMessageChannel, ulong> channel, SocketUser author, MessageSource source) | |||||
: base(discord, id, channel, author, source) | : base(discord, id, channel, author, source) | ||||
{ | { | ||||
} | } | ||||
internal new static SocketUserMessage Create(DiscordSocketClient discord, ClientState state, SocketUser author, ISocketMessageChannel channel, Model model) | |||||
internal static SocketUserMessage Create(DiscordSocketClient discord, ClientState state, SocketUser author, Cacheable<IMessageChannel, ulong> channel, Model model) | |||||
{ | { | ||||
var entity = new SocketUserMessage(discord, model.Id, channel, author, MessageHelper.GetSource(model)); | var entity = new SocketUserMessage(discord, model.Id, channel, author, MessageHelper.GetSource(model)); | ||||
entity.Update(state, model); | entity.Update(state, model); | ||||
@@ -67,7 +67,11 @@ namespace Discord.WebSocket | |||||
{ | { | ||||
base.Update(state, model); | base.Update(state, model); | ||||
SocketGuild guild = (Channel as SocketGuildChannel)?.Guild; | |||||
SocketGuild guild = null; | |||||
if (model.GuildId.IsSpecified) | |||||
guild = state.GetGuild(model.GuildId.Value); | |||||
else if (Channel.HasValue) | |||||
guild = (Channel.Value as SocketGuildChannel)?.Guild; | |||||
if (model.IsTextToSpeech.IsSpecified) | if (model.IsTextToSpeech.IsSpecified) | ||||
_isTTS = model.IsTextToSpeech.Value; | _isTTS = model.IsTextToSpeech.Value; | ||||
@@ -78,7 +82,7 @@ namespace Discord.WebSocket | |||||
if (model.MentionEveryone.IsSpecified) | if (model.MentionEveryone.IsSpecified) | ||||
_isMentioningEveryone = model.MentionEveryone.Value; | _isMentioningEveryone = model.MentionEveryone.Value; | ||||
if (model.RoleMentions.IsSpecified) | if (model.RoleMentions.IsSpecified) | ||||
_roleMentions = model.RoleMentions.Value.Select(x => guild.GetRole(x)).ToImmutableArray(); | |||||
_roleIdMentions = model.RoleMentions.Value.ToImmutableArray(); | |||||
if (model.Attachments.IsSpecified) | if (model.Attachments.IsSpecified) | ||||
{ | { | ||||
@@ -119,7 +123,9 @@ namespace Discord.WebSocket | |||||
var val = value[i]; | var val = value[i]; | ||||
if (val.Object != null) | if (val.Object != null) | ||||
{ | { | ||||
var user = Channel.GetUserAsync(val.Object.Id, CacheMode.CacheOnly).GetAwaiter().GetResult() as SocketUser; | |||||
SocketUser user = null; | |||||
if (Channel.HasValue) | |||||
user = Channel.Value.GetUserAsync(val.Object.Id, CacheMode.CacheOnly).GetAwaiter().GetResult() as SocketUser; | |||||
if (user != null) | if (user != null) | ||||
newMentions.Add(user); | newMentions.Add(user); | ||||
else | else | ||||
@@ -133,7 +139,7 @@ namespace Discord.WebSocket | |||||
if (model.Content.IsSpecified) | if (model.Content.IsSpecified) | ||||
{ | { | ||||
var text = model.Content.Value; | var text = model.Content.Value; | ||||
_tags = MessageHelper.ParseTags(text, Channel, guild, _userMentions); | |||||
_tags = MessageHelper.ParseTags(text, Channel.HasValue ? Channel.Value : null, guild, _userMentions); | |||||
model.Content = text; | model.Content = text; | ||||
} | } | ||||
@@ -151,8 +157,9 @@ namespace Discord.WebSocket | |||||
else | else | ||||
refMsgAuthor = guild.GetUser(refMsg.Author.Value.Id); | refMsgAuthor = guild.GetUser(refMsg.Author.Value.Id); | ||||
} | } | ||||
else | |||||
refMsgAuthor = (Channel as SocketChannel).GetUser(refMsg.Author.Value.Id); | |||||
else if (Channel.HasValue) | |||||
refMsgAuthor = (Channel.Value as SocketChannel).GetUser(refMsg.Author.Value.Id); | |||||
if (refMsgAuthor == null) | if (refMsgAuthor == null) | ||||
refMsgAuthor = SocketUnknownUser.Create(Discord, state, refMsg.Author.Value); | refMsgAuthor = SocketUnknownUser.Create(Discord, state, refMsg.Author.Value); | ||||
} | } | ||||
@@ -202,7 +209,7 @@ namespace Discord.WebSocket | |||||
/// <exception cref="InvalidOperationException">This operation may only be called on a <see cref="INewsChannel"/> channel.</exception> | /// <exception cref="InvalidOperationException">This operation may only be called on a <see cref="INewsChannel"/> channel.</exception> | ||||
public async Task CrosspostAsync(RequestOptions options = null) | public async Task CrosspostAsync(RequestOptions options = null) | ||||
{ | { | ||||
if (!(Channel is INewsChannel)) | |||||
if (Channel.HasValue && !(Channel.Value is INewsChannel)) | |||||
{ | { | ||||
throw new InvalidOperationException("Publishing (crossposting) is only valid in news channels."); | throw new InvalidOperationException("Publishing (crossposting) is only valid in news channels."); | ||||
} | } | ||||