@@ -71,10 +71,11 @@ namespace Discord.Commands | |||
if (ChannelPermission.HasValue) | |||
{ | |||
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); | |||
else | |||
perms = ChannelPermissions.All(context.Channel); | |||
perms = ChannelPermissions.All(channel); | |||
if (!perms.Has(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. | |||
/// </summary> | |||
DM = 0x02, | |||
/// <summary> | |||
/// Specifies the command to be executed within a group. | |||
/// </summary> | |||
Group = 0x04 | |||
} | |||
/// <summary> | |||
@@ -59,11 +55,9 @@ namespace Discord.Commands | |||
bool isValid = false; | |||
if ((Contexts & ContextType.Guild) != 0) | |||
isValid = context.Channel is IGuildChannel; | |||
isValid = context.GuildId != null; | |||
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) | |||
return Task.FromResult(PreconditionResult.FromSuccess()); | |||
@@ -36,7 +36,7 @@ namespace Discord.Commands | |||
/// <inheritdoc /> | |||
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()); | |||
else | |||
return Task.FromResult(PreconditionResult.FromError(ErrorMessage ?? "This command may only be invoked in an NSFW channel.")); | |||
@@ -54,31 +54,32 @@ namespace Discord.Commands | |||
} | |||
/// <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; | |||
if (GuildPermission.HasValue) | |||
{ | |||
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)) | |||
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) | |||
{ | |||
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); | |||
else | |||
perms = ChannelPermissions.All(context.Channel); | |||
perms = ChannelPermissions.All(channel); | |||
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/> | |||
public IGuild Guild { get; } | |||
/// <inheritdoc/> | |||
public IMessageChannel Channel { get; } | |||
public ulong? GuildId { get; } | |||
/// <inheritdoc/> | |||
public Cacheable<IMessageChannel, ulong> Channel { get; } | |||
/// <inheritdoc/> | |||
public IUser User { get; } | |||
/// <inheritdoc/> | |||
public IUserMessage Message { get; } | |||
/// <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> | |||
/// Initializes a new <see cref="CommandContext" /> class with the provided client and message. | |||
/// </summary> | |||
/// <param name="client">The underlying client.</param> | |||
/// <param name="msg">The underlying message.</param> | |||
public CommandContext(IDiscordClient client, IUserMessage msg) | |||
public CommandContext(IDiscordClient client, IUserMessage msg, IGuild guild) | |||
{ | |||
Client = client; | |||
Guild = (msg.Channel as IGuildChannel)?.Guild; | |||
GuildId = msg.GuildId; | |||
Guild = guild; | |||
Channel = msg.Channel; | |||
User = msg.Author; | |||
Message = msg; | |||
@@ -36,9 +36,9 @@ namespace Discord.Commands | |||
/// If <c>null</c>, all mentioned roles and users will be notified. | |||
/// </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> | |||
/// The method to execute before executing the command. | |||
@@ -17,7 +17,7 @@ namespace Discord.Commands | |||
//By Id (1.0) | |||
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); | |||
} | |||
@@ -18,7 +18,9 @@ namespace Discord.Commands | |||
public override async Task<TypeReaderResult> ReadAsync(ICommandContext context, string input, IServiceProvider services) | |||
{ | |||
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>(); | |||
if (context.Guild != null) | |||
@@ -29,8 +31,8 @@ namespace Discord.Commands | |||
{ | |||
if (context.Guild != null) | |||
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) | |||
@@ -38,8 +40,8 @@ namespace Discord.Commands | |||
{ | |||
if (context.Guild != null) | |||
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) | |||
@@ -14,9 +14,14 @@ namespace Discord.Commands | |||
/// </summary> | |||
IGuild Guild { get; } | |||
/// <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> | |||
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> | |||
/// Gets the <see cref="IUser" /> who executed the command. | |||
/// </summary> | |||
@@ -66,11 +66,15 @@ namespace Discord | |||
/// Time of when the message was last edited; <c>null</c> if the message is never edited. | |||
/// </returns> | |||
DateTimeOffset? EditedTimestamp { get; } | |||
/// <summary> | |||
/// Gets the source channel of the message. | |||
/// </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> | |||
/// Gets the author of this message. | |||
/// </summary> | |||
@@ -171,7 +175,7 @@ namespace Discord | |||
/// A read-only collection of sticker objects. | |||
/// </returns> | |||
IReadOnlyCollection<ISticker> Stickers { get; } | |||
/// <summary> | |||
/// Gets the flags related to this message. | |||
/// </summary> | |||
@@ -17,7 +17,7 @@ namespace Discord | |||
public static string GetJumpUrl(this IMessage msg) | |||
{ | |||
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> | |||
@@ -89,7 +89,8 @@ namespace Discord | |||
/// </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) | |||
{ | |||
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. | |||
/// </returns> | |||
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.) | |||
if (!(message is SocketUserMessage userMessage)) return Task.CompletedTask; | |||
// 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 | |||
var targetUsers = userMessage.MentionedUsers.Where(x => _targetUserIds.Contains(x.Id)); | |||
foreach (var targetUser in targetUsers) | |||
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; | |||
} | |||
@@ -216,6 +216,10 @@ namespace Discord.Rest | |||
Task<IWebhook> IDiscordClient.GetWebhookAsync(ulong id, RequestOptions options) | |||
=> 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 /> | |||
Task IDiscordClient.StartAsync() | |||
=> Task.Delay(0); | |||
@@ -114,11 +114,36 @@ namespace Discord.Rest | |||
=> MessageHelper.RemoveAllReactionsAsync(channelId, messageId, this, options); | |||
public Task RemoveAllReactionsForEmoteAsync(ulong channelId, ulong messageId, IEmote emote, RequestOptions options = null) | |||
=> 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 | |||
/// <inheritdoc /> | |||
async Task<IApplication> IDiscordClient.GetApplicationInfoAsync(RequestOptions options) | |||
=> 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 /> | |||
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 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"> | |||
@@ -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 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, | |||
BaseDiscordClient client, RequestOptions options) | |||
{ | |||
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, | |||
@@ -16,7 +16,9 @@ namespace Discord.Rest | |||
private ImmutableArray<RestReaction> _reactions = ImmutableArray.Create<RestReaction>(); | |||
/// <inheritdoc /> | |||
public IMessageChannel Channel { get; } | |||
public Cacheable<IMessageChannel, ulong> Channel { get; } | |||
/// <inheritdoc /> | |||
public ulong? GuildId { get; private set; } | |||
/// <summary> | |||
/// Gets the Author of the message. | |||
/// </summary> | |||
@@ -74,7 +76,7 @@ namespace Discord.Rest | |||
/// <inheritdoc/> | |||
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) | |||
{ | |||
Channel = channel; | |||
@@ -83,15 +85,24 @@ namespace Discord.Rest | |||
} | |||
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) | |||
return RestUserMessage.Create(discord, channel, author, model); | |||
return RestUserMessage.Create(discord, cacheableChannel, author, model); | |||
else | |||
return RestSystemMessage.Create(discord, channel, author, model); | |||
return RestSystemMessage.Create(discord, cacheableChannel, author, model); | |||
} | |||
internal virtual void Update(Model model) | |||
{ | |||
Type = model.Type; | |||
if (model.GuildId.IsSpecified) | |||
GuildId = model.GuildId.Value; | |||
if (model.Timestamp.IsSpecified) | |||
_timestampTicks = model.Timestamp.Value.UtcTicks; | |||
@@ -9,11 +9,11 @@ namespace Discord.Rest | |||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
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) | |||
{ | |||
} | |||
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); | |||
entity.Update(model); | |||
@@ -50,11 +50,11 @@ namespace Discord.Rest | |||
/// <inheritdoc /> | |||
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) | |||
{ | |||
} | |||
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)); | |||
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; | |||
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> | |||
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."); | |||
} | |||
@@ -268,12 +268,35 @@ namespace Discord.WebSocket | |||
/// </returns> | |||
public Task<RestInviteMetadata> GetInviteAsync(string inviteId, RequestOptions options = null) | |||
=> 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 | |||
/// <inheritdoc /> | |||
async Task<IApplication> IDiscordClient.GetApplicationInfoAsync(RequestOptions options) | |||
=> 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 /> | |||
Task<IChannel> IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) | |||
=> Task.FromResult<IChannel>(GetChannel(id)); | |||
@@ -9,7 +9,7 @@ namespace Discord.Commands | |||
public new DiscordShardedClient Client { get; } | |||
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; | |||
} | |||
@@ -15,10 +15,10 @@ namespace Discord.Commands | |||
/// Gets the <see cref="SocketGuild" /> that the command is executed in. | |||
/// </summary> | |||
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> | |||
/// Gets the <see cref="SocketUser" /> who executed the command. | |||
/// </summary> | |||
@@ -31,7 +31,7 @@ namespace Discord.Commands | |||
/// <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> | |||
/// 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) | |||
{ | |||
Client = client; | |||
Guild = (msg.Channel as SocketGuildChannel)?.Guild; | |||
GuildId = msg.GuildId; | |||
if (msg.GuildId != null) | |||
Guild = client.GetGuild(msg.GuildId.Value); | |||
Channel = msg.Channel; | |||
User = msg.Author; | |||
Message = msg; | |||
@@ -53,7 +55,7 @@ namespace Discord.Commands | |||
/// <inheritdoc/> | |||
IGuild ICommandContext.Guild => Guild; | |||
/// <inheritdoc/> | |||
IMessageChannel ICommandContext.Channel => Channel; | |||
Cacheable<IMessageChannel, ulong> ICommandContext.Channel => Channel; | |||
/// <inheritdoc/> | |||
IUser ICommandContext.User => User; | |||
/// <inheritdoc/> | |||
@@ -179,11 +179,11 @@ namespace Discord.WebSocket | |||
return _shards[id]; | |||
return null; | |||
} | |||
private int GetShardIdFor(ulong guildId) | |||
public int GetShardIdFor(ulong guildId) | |||
=> (int)((guildId >> 22) % (uint)_totalShards); | |||
public int GetShardIdFor(IGuild guild) | |||
=> GetShardIdFor(guild?.Id ?? 0); | |||
private DiscordSocketClient GetShardFor(ulong guildId) | |||
public DiscordSocketClient GetShardFor(ulong guildId) | |||
=> GetShard(GetShardIdFor(guildId)); | |||
public DiscordSocketClient GetShardFor(IGuild guild) | |||
=> GetShardFor(guild?.Id ?? 0); | |||
@@ -1263,24 +1263,16 @@ namespace Discord.WebSocket | |||
var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; | |||
var guild = (channel as SocketGuildChannel)?.Guild; | |||
if (channel == null && data.GuildId.IsSpecified) | |||
guild = State.GetGuild(data.GuildId.Value); | |||
if (guild != null && !guild.IsSynced) | |||
{ | |||
await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); | |||
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; | |||
if (guild != null) | |||
@@ -1291,7 +1283,7 @@ namespace Discord.WebSocket | |||
author = guild.GetUser(data.Author.Value.Id); | |||
} | |||
else | |||
author = (channel as SocketChannel).GetUser(data.Author.Value.Id); | |||
author = (channel as SocketChannel)?.GetUser(data.Author.Value.Id); | |||
if (author == null) | |||
{ | |||
@@ -1309,13 +1301,14 @@ namespace Discord.WebSocket | |||
author = groupChannel.GetOrAddUser(data.Author.Value); | |||
else | |||
{ | |||
await UnknownChannelUserAsync(type, data.Author.Value.Id, channel.Id).ConfigureAwait(false); | |||
await UnknownChannelUserAsync(type, data.Author.Value.Id, data.ChannelId).ConfigureAwait(false); | |||
return; | |||
} | |||
} | |||
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); | |||
} | |||
break; | |||
@@ -1327,6 +1320,8 @@ namespace Discord.WebSocket | |||
var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; | |||
var guild = (channel as SocketGuildChannel)?.Guild; | |||
if (channel == null && data.GuildId.IsSpecified) | |||
guild = State.GetGuild(data.GuildId.Value); | |||
if (guild != null && !guild.IsSynced) | |||
{ | |||
await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); | |||
@@ -1391,11 +1386,6 @@ namespace Discord.WebSocket | |||
else | |||
channel = CreateDMChannel(data.ChannelId, author, State); | |||
} | |||
else | |||
{ | |||
await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); | |||
return; | |||
} | |||
} | |||
after = SocketMessage.Create(this, State, author, channel, data); | |||
@@ -23,13 +23,10 @@ namespace Discord.WebSocket | |||
/// A WebSocket-based user object. | |||
/// </returns> | |||
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 /> | |||
public MessageSource Source { get; } | |||
@@ -85,13 +82,8 @@ namespace Discord.WebSocket | |||
/// Collection of WebSocket-based guild channels. | |||
/// </returns> | |||
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> | |||
/// Returns the users mentioned in this message. | |||
/// </summary> | |||
@@ -109,24 +101,33 @@ namespace Discord.WebSocket | |||
/// <inheritdoc /> | |||
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) | |||
{ | |||
Channel = channel; | |||
Author = author; | |||
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) | |||
return SocketUserMessage.Create(discord, state, author, channel, model); | |||
return SocketUserMessage.Create(discord, state, author, cacheableChannel, model); | |||
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) | |||
{ | |||
Type = model.Type; | |||
if (model.GuildId.IsSpecified) | |||
GuildId = model.GuildId.Value; | |||
if (model.Timestamp.IsSpecified) | |||
_timestampTicks = model.Timestamp.Value.UtcTicks; | |||
@@ -188,16 +189,12 @@ namespace Discord.WebSocket | |||
/// <inheritdoc /> | |||
IUser IMessage.Author => Author; | |||
/// <inheritdoc /> | |||
IMessageChannel IMessage.Channel => Channel; | |||
/// <inheritdoc /> | |||
IReadOnlyCollection<IAttachment> IMessage.Attachments => Attachments; | |||
/// <inheritdoc /> | |||
IReadOnlyCollection<IEmbed> IMessage.Embeds => Embeds; | |||
/// <inheritdoc /> | |||
IReadOnlyCollection<ulong> IMessage.MentionedChannelIds => MentionedChannels.Select(x => x.Id).ToImmutableArray(); | |||
/// <inheritdoc /> | |||
IReadOnlyCollection<ulong> IMessage.MentionedRoleIds => MentionedRoles.Select(x => x.Id).ToImmutableArray(); | |||
/// <inheritdoc /> | |||
IReadOnlyCollection<ulong> IMessage.MentionedUserIds => MentionedUsers.Select(x => x.Id).ToImmutableArray(); | |||
/// <inheritdoc /> | |||
IReadOnlyCollection<ISticker> IMessage.Stickers => Stickers; | |||
@@ -9,11 +9,11 @@ namespace Discord.WebSocket | |||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
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) | |||
{ | |||
} | |||
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); | |||
entity.Update(state, model); | |||
@@ -21,7 +21,7 @@ namespace Discord.WebSocket | |||
private ImmutableArray<Attachment> _attachments = ImmutableArray.Create<Attachment>(); | |||
private ImmutableArray<Embed> _embeds = ImmutableArray.Create<Embed>(); | |||
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<Sticker> _stickers = ImmutableArray.Create<Sticker>(); | |||
@@ -44,7 +44,7 @@ namespace Discord.WebSocket | |||
/// <inheritdoc /> | |||
public override IReadOnlyCollection<SocketGuildChannel> MentionedChannels => MessageHelper.FilterTagsByValue<SocketGuildChannel>(TagType.ChannelMention, _tags); | |||
/// <inheritdoc /> | |||
public override IReadOnlyCollection<SocketRole> MentionedRoles => _roleMentions; | |||
public override IReadOnlyCollection<ulong> MentionedRoleIds => _roleIdMentions; | |||
/// <inheritdoc /> | |||
public override IReadOnlyCollection<SocketUser> MentionedUsers => _userMentions; | |||
/// <inheritdoc /> | |||
@@ -52,11 +52,11 @@ namespace Discord.WebSocket | |||
/// <inheritdoc /> | |||
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) | |||
{ | |||
} | |||
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)); | |||
entity.Update(state, model); | |||
@@ -67,7 +67,11 @@ namespace Discord.WebSocket | |||
{ | |||
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) | |||
_isTTS = model.IsTextToSpeech.Value; | |||
@@ -78,7 +82,7 @@ namespace Discord.WebSocket | |||
if (model.MentionEveryone.IsSpecified) | |||
_isMentioningEveryone = model.MentionEveryone.Value; | |||
if (model.RoleMentions.IsSpecified) | |||
_roleMentions = model.RoleMentions.Value.Select(x => guild.GetRole(x)).ToImmutableArray(); | |||
_roleIdMentions = model.RoleMentions.Value.ToImmutableArray(); | |||
if (model.Attachments.IsSpecified) | |||
{ | |||
@@ -119,7 +123,9 @@ namespace Discord.WebSocket | |||
var val = value[i]; | |||
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) | |||
newMentions.Add(user); | |||
else | |||
@@ -133,7 +139,7 @@ namespace Discord.WebSocket | |||
if (model.Content.IsSpecified) | |||
{ | |||
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; | |||
} | |||
@@ -151,8 +157,9 @@ namespace Discord.WebSocket | |||
else | |||
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) | |||
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> | |||
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."); | |||
} | |||