Browse Source

Decouple channel from messages

pull/1901/head
Paulo 3 years ago
parent
commit
dd9a9a5fc7
27 changed files with 234 additions and 118 deletions
  1. +3
    -2
      src/Discord.Net.Commands/Attributes/Preconditions/RequireBotPermissionAttribute.cs
  2. +2
    -8
      src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs
  3. +1
    -1
      src/Discord.Net.Commands/Attributes/Preconditions/RequireNsfwAttribute.cs
  4. +8
    -7
      src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs
  5. +7
    -4
      src/Discord.Net.Commands/CommandContext.cs
  6. +2
    -2
      src/Discord.Net.Commands/ModuleBase.cs
  7. +1
    -1
      src/Discord.Net.Commands/Readers/MessageTypeReader.cs
  8. +7
    -5
      src/Discord.Net.Commands/Readers/UserTypeReader.cs
  9. +7
    -2
      src/Discord.Net.Core/Commands/ICommandContext.cs
  10. +7
    -3
      src/Discord.Net.Core/Entities/Messages/IMessage.cs
  11. +3
    -2
      src/Discord.Net.Core/Extensions/MessageExtensions.cs
  12. +19
    -0
      src/Discord.Net.Core/IDiscordClient.cs
  13. +2
    -2
      src/Discord.Net.Examples/WebSocket/BaseSocketClient.Events.Examples.cs
  14. +4
    -0
      src/Discord.Net.Rest/BaseDiscordClient.cs
  15. +25
    -0
      src/Discord.Net.Rest/DiscordRestClient.cs
  16. +30
    -3
      src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs
  17. +15
    -4
      src/Discord.Net.Rest/Entities/Messages/RestMessage.cs
  18. +2
    -2
      src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs
  19. +4
    -4
      src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs
  20. +24
    -1
      src/Discord.Net.WebSocket/BaseSocketClient.cs
  21. +1
    -1
      src/Discord.Net.WebSocket/Commands/ShardedCommandContext.cs
  22. +9
    -7
      src/Discord.Net.WebSocket/Commands/SocketCommandContext.cs
  23. +2
    -2
      src/Discord.Net.WebSocket/DiscordShardedClient.cs
  24. +10
    -20
      src/Discord.Net.WebSocket/DiscordSocketClient.cs
  25. +19
    -22
      src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs
  26. +2
    -2
      src/Discord.Net.WebSocket/Entities/Messages/SocketSystemMessage.cs
  27. +18
    -11
      src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs

+ 3
- 2
src/Discord.Net.Commands/Attributes/Preconditions/RequireBotPermissionAttribute.cs View File

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


+ 2
- 8
src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs View File

@@ -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());


+ 1
- 1
src/Discord.Net.Commands/Attributes/Preconditions/RequireNsfwAttribute.cs View File

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


+ 8
- 7
src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs View File

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

+ 7
- 4
src/Discord.Net.Commands/CommandContext.cs View File

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


+ 2
- 2
src/Discord.Net.Commands/ModuleBase.cs View File

@@ -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.


+ 1
- 1
src/Discord.Net.Commands/Readers/MessageTypeReader.cs View File

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



+ 7
- 5
src/Discord.Net.Commands/Readers/UserTypeReader.cs View File

@@ -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)


+ 7
- 2
src/Discord.Net.Core/Commands/ICommandContext.cs View File

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


+ 7
- 3
src/Discord.Net.Core/Entities/Messages/IMessage.cs View File

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


+ 3
- 2
src/Discord.Net.Core/Extensions/MessageExtensions.cs View File

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

+ 19
- 0
src/Discord.Net.Core/IDiscordClient.cs View File

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

+ 2
- 2
src/Discord.Net.Examples/WebSocket/BaseSocketClient.Events.Examples.cs View File

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



+ 4
- 0
src/Discord.Net.Rest/BaseDiscordClient.cs View File

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


+ 25
- 0
src/Discord.Net.Rest/DiscordRestClient.cs View File

@@ -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)
{


+ 30
- 3
src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs View File

@@ -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,


+ 15
- 4
src/Discord.Net.Rest/Entities/Messages/RestMessage.cs View File

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



+ 2
- 2
src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs View File

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


+ 4
- 4
src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs View File

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


+ 24
- 1
src/Discord.Net.WebSocket/BaseSocketClient.cs View File

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


+ 1
- 1
src/Discord.Net.WebSocket/Commands/ShardedCommandContext.cs View File

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


+ 9
- 7
src/Discord.Net.WebSocket/Commands/SocketCommandContext.cs View File

@@ -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/>


+ 2
- 2
src/Discord.Net.WebSocket/DiscordShardedClient.cs View File

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


+ 10
- 20
src/Discord.Net.WebSocket/DiscordSocketClient.cs View File

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


+ 19
- 22
src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs View File

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


+ 2
- 2
src/Discord.Net.WebSocket/Entities/Messages/SocketSystemMessage.cs View File

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


+ 18
- 11
src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs View File

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


Loading…
Cancel
Save