| @@ -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."); | ||||
| } | } | ||||