diff --git a/azure/build.yml b/azure/build.yml
index 412e4a823..3399d7e3d 100644
--- a/azure/build.yml
+++ b/azure/build.yml
@@ -1,5 +1,5 @@
steps:
-- script: dotnet restore -v minimal Discord.Net.sln
+- script: dotnet restore --no-cache Discord.Net.sln
displayName: Restore packages
- script: dotnet build "Discord.Net.sln" --no-restore -v minimal -c $(buildConfiguration) /p:BuildNumber=$(buildNumber) /p:IsTagBuild=$(buildTag)
diff --git a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs
index aec8dcbe3..28037b0fa 100644
--- a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs
+++ b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs
@@ -135,7 +135,8 @@ namespace Discord.Commands
if (builder.Name == null)
builder.Name = typeInfo.Name;
- var validCommands = typeInfo.DeclaredMethods.Where(IsValidCommandDefinition);
+ // Get all methods (including from inherited members), that are valid commands
+ var validCommands = typeInfo.GetMethods().Where(IsValidCommandDefinition);
foreach (var method in validCommands)
{
diff --git a/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs b/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs
index f5b986295..030a278bc 100644
--- a/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs
+++ b/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs
@@ -59,11 +59,15 @@ namespace Discord
/// The to be sent.
/// The options to be used when sending the request.
/// Whether the message attachment should be hidden as a spoiler.
+ ///
+ /// Specifies if notifications are sent for mentioned users and roles in the message .
+ /// If null, all mentioned roles and users will be notified.
+ ///
///
/// A task that represents an asynchronous send operation for delivering the message. The task result
/// contains the sent message.
///
- Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false);
+ Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null);
///
/// Sends a file to this message channel with an optional caption.
///
@@ -88,11 +92,15 @@ namespace Discord
/// The to be sent.
/// The options to be used when sending the request.
/// Whether the message attachment should be hidden as a spoiler.
+ ///
+ /// Specifies if notifications are sent for mentioned users and roles in the message .
+ /// If null, all mentioned roles and users will be notified.
+ ///
///
/// A task that represents an asynchronous send operation for delivering the message. The task result
/// contains the sent message.
///
- Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false);
+ Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null);
///
/// Gets a message from this message channel.
diff --git a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs
index b39a49776..81b5e8dd9 100644
--- a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs
+++ b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs
@@ -683,6 +683,9 @@ namespace Discord
///
/// Downloads all users for this guild if the current list is incomplete.
///
+ ///
+ /// This method downloads all users found within this guild throught the Gateway and caches them.
+ ///
///
/// A task that represents the asynchronous download operation.
///
@@ -707,6 +710,22 @@ namespace Discord
/// be or has been removed from this guild.
///
Task PruneUsersAsync(int days = 30, bool simulate = false, RequestOptions options = null);
+ ///
+ /// Gets a collection of users in this guild that the name or nickname starts with the
+ /// provided at .
+ ///
+ ///
+ /// The can not be higher than .
+ ///
+ /// The partial name or nickname to search.
+ /// The maximum number of users to be gotten.
+ /// The that determines whether the object should be fetched from cache.
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous get operation. The task result contains a collection of guild
+ /// users that the name or nickname starts with the provided at .
+ ///
+ Task> SearchUsersAsync(string query, int limit = DiscordConfig.MaxUsersPerBatch, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
///
/// Gets the specified number of audit log entries for this guild.
diff --git a/src/Discord.Net.Core/Entities/Messages/AllowedMentionTypes.cs b/src/Discord.Net.Core/Entities/Messages/AllowedMentionTypes.cs
index 3ce6531b7..ecd872d83 100644
--- a/src/Discord.Net.Core/Entities/Messages/AllowedMentionTypes.cs
+++ b/src/Discord.Net.Core/Entities/Messages/AllowedMentionTypes.cs
@@ -8,17 +8,27 @@ namespace Discord
[Flags]
public enum AllowedMentionTypes
{
+ ///
+ /// No flag is set.
+ ///
+ ///
+ /// This flag is not used to control mentions.
+ ///
+ /// It will always be present and does not mean mentions will not be allowed.
+ ///
+ ///
+ None = 0,
///
/// Controls role mentions.
///
- Roles,
+ Roles = 1,
///
/// Controls user mentions.
///
- Users,
+ Users = 2,
///
/// Controls @everyone
and @here
mentions.
///
- Everyone,
+ Everyone = 4,
}
}
diff --git a/src/Discord.Net.Core/Entities/Messages/AllowedMentions.cs b/src/Discord.Net.Core/Entities/Messages/AllowedMentions.cs
index 9b168bbd0..d52feaa7d 100644
--- a/src/Discord.Net.Core/Entities/Messages/AllowedMentions.cs
+++ b/src/Discord.Net.Core/Entities/Messages/AllowedMentions.cs
@@ -39,7 +39,7 @@ namespace Discord
/// flag of the property. If the flag is set, the value of this property
/// must be null or empty.
///
- public List RoleIds { get; set; }
+ public List RoleIds { get; set; } = new List();
///
/// Gets or sets the list of all user ids that will be mentioned.
@@ -47,7 +47,7 @@ namespace Discord
/// flag of the property. If the flag is set, the value of this property
/// must be null or empty.
///
- public List UserIds { get; set; }
+ public List UserIds { get; set; } = new List();
///
/// Initializes a new instance of the class.
diff --git a/src/Discord.Net.Core/Entities/Messages/IMessage.cs b/src/Discord.Net.Core/Entities/Messages/IMessage.cs
index aac526831..530c1cd82 100644
--- a/src/Discord.Net.Core/Entities/Messages/IMessage.cs
+++ b/src/Discord.Net.Core/Entities/Messages/IMessage.cs
@@ -215,6 +215,15 @@ namespace Discord
/// A task that represents the asynchronous removal operation.
///
Task RemoveAllReactionsAsync(RequestOptions options = null);
+ ///
+ /// Removes all reactions with a specific emoji from this message.
+ ///
+ /// The emoji used to react to this message.
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous removal operation.
+ ///
+ Task RemoveAllReactionsForEmoteAsync(IEmote emote, RequestOptions options = null);
///
/// Gets all users that reacted to a message with a given emote.
diff --git a/src/Discord.Net.Core/Entities/Users/IGuildUser.cs b/src/Discord.Net.Core/Entities/Users/IGuildUser.cs
index 60fa06cbd..92b146e05 100644
--- a/src/Discord.Net.Core/Entities/Users/IGuildUser.cs
+++ b/src/Discord.Net.Core/Entities/Users/IGuildUser.cs
@@ -73,7 +73,7 @@ namespace Discord
///
///
/// The following example checks if the current user has the ability to send a message with attachment in
- /// this channel; if so, uploads a file via .
+ /// this channel; if so, uploads a file via .
///
/// if (currentUser?.GetPermissions(targetChannel)?.AttachFiles)
/// await targetChannel.SendFileAsync("fortnite.png");
diff --git a/src/Discord.Net.Core/GatewayIntents.cs b/src/Discord.Net.Core/GatewayIntents.cs
new file mode 100644
index 000000000..e58fc07d1
--- /dev/null
+++ b/src/Discord.Net.Core/GatewayIntents.cs
@@ -0,0 +1,41 @@
+using System;
+
+namespace Discord
+{
+ [Flags]
+ public enum GatewayIntents
+ {
+ /// This intent includes no events
+ None = 0,
+ /// This intent includes GUILD_CREATE, GUILD_UPDATE, GUILD_DELETE, GUILD_ROLE_CREATE, GUILD_ROLE_UPDATE, GUILD_ROLE_DELETE, CHANNEL_CREATE, CHANNEL_UPDATE, CHANNEL_DELETE, CHANNEL_PINS_UPDATE
+ Guilds = 1 << 0,
+ /// This intent includes GUILD_MEMBER_ADD, GUILD_MEMBER_UPDATE, GUILD_MEMBER_REMOVE
+ GuildMembers = 1 << 1,
+ /// This intent includes GUILD_BAN_ADD, GUILD_BAN_REMOVE
+ GuildBans = 1 << 2,
+ /// This intent includes GUILD_EMOJIS_UPDATE
+ GuildEmojis = 1 << 3,
+ /// This intent includes GUILD_INTEGRATIONS_UPDATE
+ GuildIntegrations = 1 << 4,
+ /// This intent includes WEBHOOKS_UPDATE
+ GuildWebhooks = 1 << 5,
+ /// This intent includes INVITE_CREATE, INVITE_DELETE
+ GuildInvites = 1 << 6,
+ /// This intent includes VOICE_STATE_UPDATE
+ GuildVoiceStates = 1 << 7,
+ /// This intent includes PRESENCE_UPDATE
+ GuildPresences = 1 << 8,
+ /// This intent includes MESSAGE_CREATE, MESSAGE_UPDATE, MESSAGE_DELETE, MESSAGE_DELETE_BULK
+ GuildMessages = 1 << 9,
+ /// This intent includes MESSAGE_REACTION_ADD, MESSAGE_REACTION_REMOVE, MESSAGE_REACTION_REMOVE_ALL, MESSAGE_REACTION_REMOVE_EMOJI
+ GuildMessageReactions = 1 << 10,
+ /// This intent includes TYPING_START
+ GuildMessageTyping = 1 << 11,
+ /// This intent includes CHANNEL_CREATE, MESSAGE_CREATE, MESSAGE_UPDATE, MESSAGE_DELETE, CHANNEL_PINS_UPDATE
+ DirectMessages = 1 << 12,
+ /// This intent includes MESSAGE_REACTION_ADD, MESSAGE_REACTION_REMOVE, MESSAGE_REACTION_REMOVE_ALL, MESSAGE_REACTION_REMOVE_EMOJI
+ DirectMessageReactions = 1 << 13,
+ /// This intent includes TYPING_START
+ DirectMessageTyping = 1 << 14,
+ }
+}
diff --git a/src/Discord.Net.Core/Utils/Comparers.cs b/src/Discord.Net.Core/Utils/Comparers.cs
index 7ec9f5c74..3c7b8aa3c 100644
--- a/src/Discord.Net.Core/Utils/Comparers.cs
+++ b/src/Discord.Net.Core/Utils/Comparers.cs
@@ -41,16 +41,13 @@ namespace Discord
{
public override bool Equals(TEntity x, TEntity y)
{
- bool xNull = x == null;
- bool yNull = y == null;
-
- if (xNull && yNull)
- return true;
-
- if (xNull ^ yNull)
- return false;
-
- return x.Id.Equals(y.Id);
+ return (x, y) switch
+ {
+ (null, null) => true,
+ (null, _) => false,
+ (_, null) => false,
+ var (l, r) => l.Id.Equals(r.Id)
+ };
}
public override int GetHashCode(TEntity obj)
diff --git a/src/Discord.Net.Rest/API/Rest/SearchGuildMembersParams.cs b/src/Discord.Net.Rest/API/Rest/SearchGuildMembersParams.cs
new file mode 100644
index 000000000..7c933ff82
--- /dev/null
+++ b/src/Discord.Net.Rest/API/Rest/SearchGuildMembersParams.cs
@@ -0,0 +1,9 @@
+#pragma warning disable CS1591
+namespace Discord.API.Rest
+{
+ internal class SearchGuildMembersParams
+ {
+ public string Query { get; set; }
+ public Optional Limit { get; set; }
+ }
+}
diff --git a/src/Discord.Net.Rest/API/Rest/UploadFileParams.cs b/src/Discord.Net.Rest/API/Rest/UploadFileParams.cs
index 7ba21d012..64535e6d7 100644
--- a/src/Discord.Net.Rest/API/Rest/UploadFileParams.cs
+++ b/src/Discord.Net.Rest/API/Rest/UploadFileParams.cs
@@ -19,6 +19,7 @@ namespace Discord.API.Rest
public Optional Nonce { get; set; }
public Optional IsTTS { get; set; }
public Optional
///
/// This method follows the same behavior as described in
- /// . Please visit
+ /// . Please visit
/// its documentation for more details on this method.
///
/// The file path of the file.
@@ -42,16 +42,21 @@ namespace Discord.Rest
/// Whether the message should be read aloud by Discord or not.
/// The to be sent.
/// The options to be used when sending the request.
+ /// Whether the message attachment should be hidden as a spoiler.
+ ///
+ /// Specifies if notifications are sent for mentioned users and roles in the message .
+ /// If null, all mentioned roles and users will be notified.
+ ///
///
/// A task that represents an asynchronous send operation for delivering the message. The task result
/// contains the sent message.
///
- new Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false);
+ new Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null);
///
/// Sends a file to this message channel with an optional caption.
///
///
- /// This method follows the same behavior as described in .
+ /// This method follows the same behavior as described in .
/// Please visit its documentation for more details on this method.
///
/// The of the file to be sent.
@@ -60,11 +65,16 @@ namespace Discord.Rest
/// Whether the message should be read aloud by Discord or not.
/// The to be sent.
/// The options to be used when sending the request.
+ /// Whether the message attachment should be hidden as a spoiler.
+ ///
+ /// Specifies if notifications are sent for mentioned users and roles in the message .
+ /// If null, all mentioned roles and users will be notified.
+ ///
///
/// A task that represents an asynchronous send operation for delivering the message. The task result
/// contains the sent message.
///
- new Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false);
+ new Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null);
///
/// Gets a message from this message channel.
diff --git a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs
index 732af2d81..0f29f9d77 100644
--- a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs
+++ b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs
@@ -121,12 +121,12 @@ namespace Discord.Rest
/// is in an invalid format.
/// An I/O error occurred while opening the file.
/// Message content is too long, length must be less or equal to .
- public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false)
- => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options, isSpoiler);
+ public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null)
+ => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, allowedMentions, options, isSpoiler);
///
/// Message content is too long, length must be less or equal to .
- public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false)
- => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options, isSpoiler);
+ public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null)
+ => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, allowedMentions, options, isSpoiler);
///
public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null)
@@ -200,11 +200,11 @@ namespace Discord.Rest
async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options)
=> await GetPinnedMessagesAsync(options).ConfigureAwait(false);
///
- async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler)
- => await SendFileAsync(filePath, text, isTTS, embed, options, isSpoiler).ConfigureAwait(false);
+ async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions)
+ => await SendFileAsync(filePath, text, isTTS, embed, options, isSpoiler, allowedMentions).ConfigureAwait(false);
///
- async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler)
- => await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler).ConfigureAwait(false);
+ async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions)
+ => await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler, allowedMentions).ConfigureAwait(false);
///
async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions)
=> await SendMessageAsync(text, isTTS, embed, options, allowedMentions).ConfigureAwait(false);
diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs
index 3c21bd95f..4361fd281 100644
--- a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs
+++ b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs
@@ -123,12 +123,12 @@ namespace Discord.Rest
/// is in an invalid format.
/// An I/O error occurred while opening the file.
/// Message content is too long, length must be less or equal to .
- public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false)
- => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options, isSpoiler);
+ public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null)
+ => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, allowedMentions, options, isSpoiler);
///
/// Message content is too long, length must be less or equal to .
- public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false)
- => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options, isSpoiler);
+ public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null)
+ => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, allowedMentions, options, isSpoiler);
///
public Task TriggerTypingAsync(RequestOptions options = null)
@@ -178,11 +178,11 @@ namespace Discord.Rest
async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options)
=> await GetPinnedMessagesAsync(options).ConfigureAwait(false);
- async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler)
- => await SendFileAsync(filePath, text, isTTS, embed, options, isSpoiler).ConfigureAwait(false);
+ async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions)
+ => await SendFileAsync(filePath, text, isTTS, embed, options, isSpoiler, allowedMentions).ConfigureAwait(false);
- async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler)
- => await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler).ConfigureAwait(false);
+ async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions)
+ => await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler, allowedMentions).ConfigureAwait(false);
async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions)
=> await SendMessageAsync(text, isTTS, embed, options, allowedMentions).ConfigureAwait(false);
diff --git a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs
index cecd0a4d2..c7ff7fa65 100644
--- a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs
+++ b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs
@@ -129,13 +129,13 @@ namespace Discord.Rest
/// is in an invalid format.
/// An I/O error occurred while opening the file.
/// Message content is too long, length must be less or equal to .
- public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false)
- => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options, isSpoiler);
+ public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null)
+ => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, allowedMentions, options, isSpoiler);
///
/// Message content is too long, length must be less or equal to .
- public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false)
- => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options, isSpoiler);
+ public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null)
+ => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, allowedMentions, options, isSpoiler);
///
public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null)
@@ -266,12 +266,12 @@ namespace Discord.Rest
=> await GetPinnedMessagesAsync(options).ConfigureAwait(false);
///
- async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler)
- => await SendFileAsync(filePath, text, isTTS, embed, options, isSpoiler).ConfigureAwait(false);
+ async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions)
+ => await SendFileAsync(filePath, text, isTTS, embed, options, isSpoiler, allowedMentions).ConfigureAwait(false);
///
- async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler)
- => await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler).ConfigureAwait(false);
+ async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions)
+ => await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler, allowedMentions).ConfigureAwait(false);
///
async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions)
=> await SendMessageAsync(text, isTTS, embed, options, allowedMentions).ConfigureAwait(false);
diff --git a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs
index 790b1e5c3..675847b58 100644
--- a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs
+++ b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs
@@ -264,19 +264,18 @@ namespace Discord.Rest
{
if (name == null) throw new ArgumentNullException(paramName: nameof(name));
- var model = await client.ApiClient.CreateGuildRoleAsync(guild.Id, options).ConfigureAwait(false);
- var role = RestRole.Create(client, guild, model);
-
- await role.ModifyAsync(x =>
+ var createGuildRoleParams = new API.Rest.ModifyGuildRoleParams
{
- x.Name = name;
- x.Permissions = (permissions ?? role.Permissions);
- x.Color = (color ?? Color.Default);
- x.Hoist = isHoisted;
- x.Mentionable = isMentionable;
- }, options).ConfigureAwait(false);
+ Color = color?.RawValue ?? Optional.Create(),
+ Hoist = isHoisted,
+ Mentionable = isMentionable,
+ Name = name,
+ Permissions = permissions?.RawValue ?? Optional.Create()
+ };
+
+ var model = await client.ApiClient.CreateGuildRoleAsync(guild.Id, createGuildRoleParams, options).ConfigureAwait(false);
- return role;
+ return RestRole.Create(client, guild, model);
}
//Users
@@ -387,6 +386,17 @@ namespace Discord.Rest
model = await client.ApiClient.BeginGuildPruneAsync(guild.Id, args, options).ConfigureAwait(false);
return model.Pruned;
}
+ public static async Task> SearchUsersAsync(IGuild guild, BaseDiscordClient client,
+ string query, int? limit, RequestOptions options)
+ {
+ var apiArgs = new SearchGuildMembersParams
+ {
+ Query = query,
+ Limit = limit ?? Optional.Create()
+ };
+ var models = await client.ApiClient.SearchGuildMembersAsync(guild.Id, apiArgs, options).ConfigureAwait(false);
+ return models.Select(x => RestGuildUser.Create(client, guild, x)).ToImmutableArray();
+ }
// Audit logs
public static IAsyncEnumerable> GetAuditLogsAsync(IGuild guild, BaseDiscordClient client,
diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs
index 900f5045e..f0b5be0f7 100644
--- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs
+++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs
@@ -634,6 +634,23 @@ namespace Discord.Rest
public Task PruneUsersAsync(int days = 30, bool simulate = false, RequestOptions options = null)
=> GuildHelper.PruneUsersAsync(this, Discord, days, simulate, options);
+ ///
+ /// Gets a collection of users in this guild that the name or nickname starts with the
+ /// provided at .
+ ///
+ ///
+ /// The can not be higher than .
+ ///
+ /// The partial name or nickname to search.
+ /// The maximum number of users to be gotten.
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous get operation. The task result contains a collection of guild
+ /// users that the name or nickname starts with the provided at .
+ ///
+ public Task> SearchUsersAsync(string query, int limit = DiscordConfig.MaxUsersPerBatch, RequestOptions options = null)
+ => GuildHelper.SearchUsersAsync(this, Discord, query, limit, options);
+
//Audit logs
///
/// Gets the specified number of audit log entries for this guild.
@@ -884,6 +901,14 @@ namespace Discord.Rest
/// Downloading users is not supported for a REST-based guild.
Task IGuild.DownloadUsersAsync() =>
throw new NotSupportedException();
+ ///
+ async Task> IGuild.SearchUsersAsync(string query, int limit, CacheMode mode, RequestOptions options)
+ {
+ if (mode == CacheMode.AllowDownload)
+ return await SearchUsersAsync(query, limit, options).ConfigureAwait(false);
+ else
+ return ImmutableArray.Create();
+ }
async Task> IGuild.GetAuditLogsAsync(int limit, CacheMode cacheMode, RequestOptions options,
ulong? beforeId, ulong? userId, ActionType? actionType)
diff --git a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs
index 57f8b2509..d6a718b3a 100644
--- a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs
+++ b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs
@@ -78,6 +78,11 @@ namespace Discord.Rest
await client.ApiClient.RemoveAllReactionsAsync(msg.Channel.Id, msg.Id, options).ConfigureAwait(false);
}
+ public static async Task RemoveAllReactionsForEmoteAsync(IMessage msg, IEmote emote, BaseDiscordClient client, RequestOptions options)
+ {
+ await client.ApiClient.RemoveAllReactionsForEmoteAsync(msg.Channel.Id, msg.Id, emote is Emote e ? $"{e.Name}:{e.Id}" : emote.Name, options).ConfigureAwait(false);
+ }
+
public static IAsyncEnumerable> GetReactionUsersAsync(IMessage msg, IEmote emote,
int? limit, BaseDiscordClient client, RequestOptions options)
{
diff --git a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs
index b4a33c76c..809a55e9c 100644
--- a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs
+++ b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs
@@ -182,6 +182,9 @@ namespace Discord.Rest
public Task RemoveAllReactionsAsync(RequestOptions options = null)
=> MessageHelper.RemoveAllReactionsAsync(this, Discord, options);
///
+ public Task RemoveAllReactionsForEmoteAsync(IEmote emote, RequestOptions options = null)
+ => MessageHelper.RemoveAllReactionsForEmoteAsync(this, emote, Discord, options);
+ ///
public IAsyncEnumerable> GetReactionUsersAsync(IEmote emote, int limit, RequestOptions options = null)
=> MessageHelper.GetReactionUsersAsync(this, emote, limit, Discord, options);
}
diff --git a/src/Discord.Net.WebSocket/API/Gateway/IdentifyParams.cs b/src/Discord.Net.WebSocket/API/Gateway/IdentifyParams.cs
index 1e0bf71c2..e3e24491d 100644
--- a/src/Discord.Net.WebSocket/API/Gateway/IdentifyParams.cs
+++ b/src/Discord.Net.WebSocket/API/Gateway/IdentifyParams.cs
@@ -1,4 +1,4 @@
-#pragma warning disable CS1591
+#pragma warning disable CS1591
using Newtonsoft.Json;
using System.Collections.Generic;
@@ -17,5 +17,7 @@ namespace Discord.API.Gateway
public Optional ShardingParams { get; set; }
[JsonProperty("guild_subscriptions")]
public Optional GuildSubscriptions { get; set; }
+ [JsonProperty("intents")]
+ public Optional Intents { get; set; }
}
}
diff --git a/src/Discord.Net.WebSocket/API/Gateway/RemoveAllReactionsForEmoteEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/RemoveAllReactionsForEmoteEvent.cs
new file mode 100644
index 000000000..7f804d3f5
--- /dev/null
+++ b/src/Discord.Net.WebSocket/API/Gateway/RemoveAllReactionsForEmoteEvent.cs
@@ -0,0 +1,16 @@
+using Newtonsoft.Json;
+
+namespace Discord.API.Gateway
+{
+ internal class RemoveAllReactionsForEmoteEvent
+ {
+ [JsonProperty("channel_id")]
+ public ulong ChannelId { get; set; }
+ [JsonProperty("guild_id")]
+ public Optional GuildId { get; set; }
+ [JsonProperty("message_id")]
+ public ulong MessageId { get; set; }
+ [JsonProperty("emoji")]
+ public Emoji Emoji { get; set; }
+ }
+}
diff --git a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs
index 908314f6a..2cd62b3e8 100644
--- a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs
+++ b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs
@@ -234,6 +234,28 @@ namespace Discord.WebSocket
remove { _reactionsClearedEvent.Remove(value); }
}
internal readonly AsyncEvent, ISocketMessageChannel, Task>> _reactionsClearedEvent = new AsyncEvent, ISocketMessageChannel, Task>>();
+ ///
+ /// Fired when all reactions to a message with a specific emote are removed.
+ ///
+ ///
+ ///
+ /// This event is fired when all reactions to a message with a specific emote are removed.
+ /// The event handler must return a and accept a and
+ /// a as its parameters.
+ ///
+ ///
+ /// The channel where this message was sent will be passed into the parameter.
+ ///
+ ///
+ /// The emoji that all reactions had and were removed will be passed into the parameter.
+ ///
+ ///
+ public event Func, ISocketMessageChannel, IEmote, Task> ReactionsRemovedForEmote
+ {
+ add { _reactionsRemovedForEmoteEvent.Add(value); }
+ remove { _reactionsRemovedForEmoteEvent.Remove(value); }
+ }
+ internal readonly AsyncEvent, ISocketMessageChannel, IEmote, Task>> _reactionsRemovedForEmoteEvent = new AsyncEvent, ISocketMessageChannel, IEmote, Task>>();
//Roles
/// Fired when a role is created.
diff --git a/src/Discord.Net.WebSocket/DiscordShardedClient.cs b/src/Discord.Net.WebSocket/DiscordShardedClient.cs
index 8359ca048..930ea1585 100644
--- a/src/Discord.Net.WebSocket/DiscordShardedClient.cs
+++ b/src/Discord.Net.WebSocket/DiscordShardedClient.cs
@@ -313,6 +313,7 @@ namespace Discord.WebSocket
client.ReactionAdded += (cache, channel, reaction) => _reactionAddedEvent.InvokeAsync(cache, channel, reaction);
client.ReactionRemoved += (cache, channel, reaction) => _reactionRemovedEvent.InvokeAsync(cache, channel, reaction);
client.ReactionsCleared += (cache, channel) => _reactionsClearedEvent.InvokeAsync(cache, channel);
+ client.ReactionsRemovedForEmote += (cache, channel, emote) => _reactionsRemovedForEmoteEvent.InvokeAsync(cache, channel, emote);
client.RoleCreated += (role) => _roleCreatedEvent.InvokeAsync(role);
client.RoleDeleted += (role) => _roleDeletedEvent.InvokeAsync(role);
diff --git a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs
index ef97615e2..1b21bd666 100644
--- a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs
+++ b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs
@@ -209,7 +209,7 @@ namespace Discord.API
await _sentGatewayMessageEvent.InvokeAsync(opCode).ConfigureAwait(false);
}
- public async Task SendIdentifyAsync(int largeThreshold = 100, int shardID = 0, int totalShards = 1, bool guildSubscriptions = true, RequestOptions options = null)
+ public async Task SendIdentifyAsync(int largeThreshold = 100, int shardID = 0, int totalShards = 1, bool guildSubscriptions = true, GatewayIntents? gatewayIntents = null, RequestOptions options = null)
{
options = RequestOptions.CreateOrClone(options);
var props = new Dictionary
@@ -220,12 +220,16 @@ namespace Discord.API
{
Token = AuthToken,
Properties = props,
- LargeThreshold = largeThreshold,
- GuildSubscriptions = guildSubscriptions
+ LargeThreshold = largeThreshold
};
if (totalShards > 1)
msg.ShardingParams = new int[] { shardID, totalShards };
+ if (gatewayIntents.HasValue)
+ msg.Intents = (int)gatewayIntents.Value;
+ else
+ msg.GuildSubscriptions = guildSubscriptions;
+
await SendGatewayAsync(GatewayOpCode.Identify, msg, options: options).ConfigureAwait(false);
}
public async Task SendResumeAsync(string sessionId, int lastSeq, RequestOptions options = null)
diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs
index 31e49b3dd..1bfa467b6 100644
--- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs
+++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs
@@ -44,6 +44,7 @@ namespace Discord.WebSocket
private RestApplication _applicationInfo;
private bool _isDisposed;
private bool _guildSubscriptions;
+ private GatewayIntents? _gatewayIntents;
///
/// Provides access to a REST-only client with a shared state from this client.
@@ -137,6 +138,7 @@ namespace Discord.WebSocket
Rest = new DiscordSocketRestClient(config, ApiClient);
_heartbeatTimes = new ConcurrentQueue();
_guildSubscriptions = config.GuildSubscriptions;
+ _gatewayIntents = config.GatewayIntents;
_stateLock = new SemaphoreSlim(1, 1);
_gatewayLogger = LogManager.CreateLogger(ShardId == 0 && TotalShards == 1 ? "Gateway" : $"Shard #{ShardId}");
@@ -242,7 +244,7 @@ namespace Discord.WebSocket
else
{
await _gatewayLogger.DebugAsync("Identifying").ConfigureAwait(false);
- await ApiClient.SendIdentifyAsync(shardID: ShardId, totalShards: TotalShards, guildSubscriptions: _guildSubscriptions).ConfigureAwait(false);
+ await ApiClient.SendIdentifyAsync(shardID: ShardId, totalShards: TotalShards, guildSubscriptions: _guildSubscriptions, gatewayIntents: _gatewayIntents).ConfigureAwait(false);
}
//Wait for READY
@@ -517,7 +519,7 @@ namespace Discord.WebSocket
_sessionId = null;
_lastSeq = 0;
- await ApiClient.SendIdentifyAsync(shardID: ShardId, totalShards: TotalShards).ConfigureAwait(false);
+ await ApiClient.SendIdentifyAsync(shardID: ShardId, totalShards: TotalShards, guildSubscriptions: _guildSubscriptions, gatewayIntents: _gatewayIntents).ConfigureAwait(false);
}
break;
case GatewayOpCode.Reconnect:
@@ -1394,6 +1396,34 @@ namespace Discord.WebSocket
}
}
break;
+ case "MESSAGE_REACTION_REMOVE_EMOJI":
+ {
+ await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_REMOVE_EMOJI)").ConfigureAwait(false);
+
+ var data = (payload as JToken).ToObject(_serializer);
+ if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel)
+ {
+ var cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage;
+ bool isCached = cachedMsg != null;
+
+ var optionalMsg = !isCached
+ ? Optional.Create()
+ : Optional.Create(cachedMsg);
+
+ var cacheable = new Cacheable(cachedMsg, data.MessageId, isCached, async () => await channel.GetMessageAsync(data.MessageId).ConfigureAwait(false) as IUserMessage);
+ var emote = data.Emoji.ToIEmote();
+
+ cachedMsg?.RemoveAllReactionsForEmoteAsync(emote);
+
+ await TimedInvokeAsync(_reactionsRemovedForEmoteEvent, nameof(ReactionsRemovedForEmote), cacheable, channel, emote).ConfigureAwait(false);
+ }
+ else
+ {
+ await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false);
+ return;
+ }
+ }
+ break;
case "MESSAGE_DELETE_BULK":
{
await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE_BULK)").ConfigureAwait(false);
diff --git a/src/Discord.Net.WebSocket/DiscordSocketConfig.cs b/src/Discord.Net.WebSocket/DiscordSocketConfig.cs
index 5b9d68619..877ccd875 100644
--- a/src/Discord.Net.WebSocket/DiscordSocketConfig.cs
+++ b/src/Discord.Net.WebSocket/DiscordSocketConfig.cs
@@ -121,6 +121,7 @@ namespace Discord.WebSocket
///
/// Gets or sets enabling dispatching of guild subscription events e.g. presence and typing events.
+ /// This is not used if are provided.
///
public bool GuildSubscriptions { get; set; } = true;
@@ -149,6 +150,15 @@ namespace Discord.WebSocket
}
}
private int _maxWaitForGuildAvailable = 10000;
+
+ /// Gets or sets gateway intents to limit what events are sent from Discord. Allows for more granular control than the property.
+ ///
+ ///
+ /// For more information, please see
+ /// GatewayIntents
+ /// on the official Discord API documentation.
+ ///
+ public GatewayIntents? GatewayIntents { get; set; }
///
/// Initializes a default configuration.
diff --git a/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs
index 378478dcc..e8511f1f5 100644
--- a/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs
+++ b/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs
@@ -42,7 +42,7 @@ namespace Discord.WebSocket
/// Sends a file to this message channel with an optional caption.
///
///
- /// This method follows the same behavior as described in .
+ /// This method follows the same behavior as described in .
/// Please visit its documentation for more details on this method.
///
/// The file path of the file.
@@ -51,16 +51,20 @@ namespace Discord.WebSocket
/// The to be sent.
/// The options to be used when sending the request.
/// Whether the message attachment should be hidden as a spoiler.
+ ///
+ /// Specifies if notifications are sent for mentioned users and roles in the message .
+ /// If null, all mentioned roles and users will be notified.
+ ///
///
/// A task that represents an asynchronous send operation for delivering the message. The task result
/// contains the sent message.
///
- new Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false);
+ new Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null);
///
/// Sends a file to this message channel with an optional caption.
///
///
- /// This method follows the same behavior as described in .
+ /// This method follows the same behavior as described in .
/// Please visit its documentation for more details on this method.
///
/// The of the file to be sent.
@@ -70,11 +74,15 @@ namespace Discord.WebSocket
/// The to be sent.
/// The options to be used when sending the request.
/// Whether the message attachment should be hidden as a spoiler.
+ ///
+ /// Specifies if notifications are sent for mentioned users and roles in the message .
+ /// If null, all mentioned roles and users will be notified.
+ ///
///
/// A task that represents an asynchronous send operation for delivering the message. The task result
/// contains the sent message.
///
- new Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false);
+ new Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null);
///
/// Gets a cached message from this channel.
diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs
index e6339b6d9..5cfbcc1a8 100644
--- a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs
+++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs
@@ -11,23 +11,11 @@ namespace Discord.WebSocket
public static IAsyncEnumerable> GetMessagesAsync(ISocketMessageChannel channel, DiscordSocketClient discord, MessageCache messages,
ulong? fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options)
{
- if (dir == Direction.Around)
- throw new NotImplementedException(); //TODO: Impl
-
- IReadOnlyCollection cachedMessages = null;
- IAsyncEnumerable> result = null;
-
if (dir == Direction.After && fromMessageId == null)
return AsyncEnumerable.Empty>();
- if (dir == Direction.Before || mode == CacheMode.CacheOnly)
- {
- if (messages != null) //Cache enabled
- cachedMessages = messages.GetMany(fromMessageId, dir, limit);
- else
- cachedMessages = ImmutableArray.Create();
- result = ImmutableArray.Create(cachedMessages).ToAsyncEnumerable>();
- }
+ var cachedMessages = GetCachedMessages(channel, discord, messages, fromMessageId, dir, limit);
+ var result = ImmutableArray.Create(cachedMessages).ToAsyncEnumerable>();
if (dir == Direction.Before)
{
@@ -38,18 +26,35 @@ namespace Discord.WebSocket
//Download remaining messages
ulong? minId = cachedMessages.Count > 0 ? cachedMessages.Min(x => x.Id) : fromMessageId;
var downloadedMessages = ChannelHelper.GetMessagesAsync(channel, discord, minId, dir, limit, options);
- return result.Concat(downloadedMessages);
+ if (cachedMessages.Count != 0)
+ return result.Concat(downloadedMessages);
+ else
+ return downloadedMessages;
}
- else
+ else if (dir == Direction.After)
+ {
+ limit -= cachedMessages.Count;
+ if (mode == CacheMode.CacheOnly || limit <= 0)
+ return result;
+
+ //Download remaining messages
+ ulong maxId = cachedMessages.Count > 0 ? cachedMessages.Max(x => x.Id) : fromMessageId.Value;
+ var downloadedMessages = ChannelHelper.GetMessagesAsync(channel, discord, maxId, dir, limit, options);
+ if (cachedMessages.Count != 0)
+ return result.Concat(downloadedMessages);
+ else
+ return downloadedMessages;
+ }
+ else //Direction.Around
{
- if (mode == CacheMode.CacheOnly)
+ if (mode == CacheMode.CacheOnly || limit <= cachedMessages.Count)
return result;
- //Dont use cache in this case
+ //Cache isn't useful here since Discord will send them anyways
return ChannelHelper.GetMessagesAsync(channel, discord, fromMessageId, dir, limit, options);
}
}
- public static IReadOnlyCollection GetCachedMessages(SocketChannel channel, DiscordSocketClient discord, MessageCache messages,
+ public static IReadOnlyCollection GetCachedMessages(ISocketMessageChannel channel, DiscordSocketClient discord, MessageCache messages,
ulong? fromMessageId, Direction dir, int limit)
{
if (messages != null) //Cache enabled
diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs
index 11259a31e..527685578 100644
--- a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs
+++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs
@@ -139,12 +139,12 @@ namespace Discord.WebSocket
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, allowedMentions, options);
///
- public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false)
- => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options, isSpoiler);
+ public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null)
+ => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, allowedMentions, options, isSpoiler);
///
/// Message content is too long, length must be less or equal to .
- public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false)
- => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options, isSpoiler);
+ public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null)
+ => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, allowedMentions, options, isSpoiler);
///
public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null)
=> ChannelHelper.DeleteMessageAsync(this, messageId, Discord, options);
@@ -229,11 +229,11 @@ namespace Discord.WebSocket
async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options)
=> await GetPinnedMessagesAsync(options).ConfigureAwait(false);
///
- async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler)
- => await SendFileAsync(filePath, text, isTTS, embed, options, isSpoiler).ConfigureAwait(false);
+ async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions)
+ => await SendFileAsync(filePath, text, isTTS, embed, options, isSpoiler, allowedMentions).ConfigureAwait(false);
///
- async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler)
- => await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler).ConfigureAwait(false);
+ async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions)
+ => await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler, allowedMentions).ConfigureAwait(false);
///
async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions)
=> await SendMessageAsync(text, isTTS, embed, options, allowedMentions).ConfigureAwait(false);
diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs
index c57c37db2..b95bbffc1 100644
--- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs
+++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs
@@ -167,11 +167,11 @@ namespace Discord.WebSocket
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, allowedMentions, options);
///
- public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false)
- => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options, isSpoiler);
+ public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null)
+ => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, allowedMentions, options, isSpoiler);
///
- public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false)
- => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options, isSpoiler);
+ public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null)
+ => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, allowedMentions, options, isSpoiler);
///
public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null)
@@ -293,11 +293,11 @@ namespace Discord.WebSocket
=> await GetPinnedMessagesAsync(options).ConfigureAwait(false);
///
- async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler)
- => await SendFileAsync(filePath, text, isTTS, embed, options, isSpoiler).ConfigureAwait(false);
+ async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions)
+ => await SendFileAsync(filePath, text, isTTS, embed, options, isSpoiler, allowedMentions).ConfigureAwait(false);
///
- async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler)
- => await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler).ConfigureAwait(false);
+ async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions)
+ => await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler, allowedMentions).ConfigureAwait(false);
///
async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions)
=> await SendMessageAsync(text, isTTS, embed, options, allowedMentions).ConfigureAwait(false);
diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs
index 1b3b5bcd7..e49e3ed37 100644
--- a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs
+++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs
@@ -165,13 +165,13 @@ namespace Discord.WebSocket
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, allowedMentions, options);
///
- public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false)
- => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options, isSpoiler);
+ public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null)
+ => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, allowedMentions, options, isSpoiler);
///
/// Message content is too long, length must be less or equal to .
- public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false)
- => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options, isSpoiler);
+ public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null)
+ => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, allowedMentions, options, isSpoiler);
///
public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null)
@@ -302,11 +302,11 @@ namespace Discord.WebSocket
=> await GetPinnedMessagesAsync(options).ConfigureAwait(false);
///
- async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler)
- => await SendFileAsync(filePath, text, isTTS, embed, options, isSpoiler).ConfigureAwait(false);
+ async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions)
+ => await SendFileAsync(filePath, text, isTTS, embed, options, isSpoiler, allowedMentions).ConfigureAwait(false);
///
- async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler)
- => await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler).ConfigureAwait(false);
+ async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler, AllowedMentions allowedMentions)
+ => await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler, allowedMentions).ConfigureAwait(false);
///
async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options, AllowedMentions allowedMentions)
=> await SendMessageAsync(text, isTTS, embed, options, allowedMentions).ConfigureAwait(false);
diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
index e556853f2..d2d759bb3 100644
--- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
+++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
@@ -821,6 +821,25 @@ namespace Discord.WebSocket
}
}
+ ///
+ /// Gets a collection of all users in this guild.
+ ///
+ ///
+ /// This method retrieves all users found within this guild throught REST.
+ /// Users returned by this method are not cached.
+ ///
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous get operation. The task result contains a collection of guild
+ /// users found within this guild.
+ ///
+ public IAsyncEnumerable> GetUsersAsync(RequestOptions options = null)
+ {
+ if (HasAllMembers)
+ return ImmutableArray.Create(Users).ToAsyncEnumerable>();
+ return GuildHelper.GetUsersAsync(this, Discord, null, null, options);
+ }
+
///
public async Task DownloadUsersAsync()
{
@@ -831,6 +850,23 @@ namespace Discord.WebSocket
_downloaderPromise.TrySetResultAsync(true);
}
+ ///
+ /// Gets a collection of users in this guild that the name or nickname starts with the
+ /// provided at .
+ ///
+ ///
+ /// The can not be higher than .
+ ///
+ /// The partial name or nickname to search.
+ /// The maximum number of users to be gotten.
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous get operation. The task result contains a collection of guild
+ /// users that the name or nickname starts with the provided at .
+ ///
+ public Task> SearchUsersAsync(string query, int limit = DiscordConfig.MaxUsersPerBatch, RequestOptions options = null)
+ => GuildHelper.SearchUsersAsync(this, Discord, query, limit, options);
+
//Audit logs
///
/// Gets the specified number of audit log entries for this guild.
@@ -1185,8 +1221,13 @@ namespace Discord.WebSocket
=> await CreateRoleAsync(name, permissions, color, isHoisted, isMentionable, options).ConfigureAwait(false);
///
- Task> IGuild.GetUsersAsync(CacheMode mode, RequestOptions options)
- => Task.FromResult>(Users);
+ async Task> IGuild.GetUsersAsync(CacheMode mode, RequestOptions options)
+ {
+ if (mode == CacheMode.AllowDownload && !HasAllMembers)
+ return (await GetUsersAsync(options).FlattenAsync().ConfigureAwait(false)).ToImmutableArray();
+ else
+ return Users;
+ }
///
async Task IGuild.AddGuildUserAsync(ulong userId, string accessToken, Action func, RequestOptions options)
@@ -1200,6 +1241,14 @@ namespace Discord.WebSocket
///
Task IGuild.GetOwnerAsync(CacheMode mode, RequestOptions options)
=> Task.FromResult(Owner);
+ ///
+ async Task> IGuild.SearchUsersAsync(string query, int limit, CacheMode mode, RequestOptions options)
+ {
+ if (mode == CacheMode.AllowDownload)
+ return await SearchUsersAsync(query, limit, options).ConfigureAwait(false);
+ else
+ return ImmutableArray.Create();
+ }
///
async Task> IGuild.GetAuditLogsAsync(int limit, CacheMode cacheMode, RequestOptions options,
diff --git a/src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs b/src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs
index 24e46df46..6baf56879 100644
--- a/src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs
+++ b/src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs
@@ -56,11 +56,23 @@ namespace Discord.WebSocket
cachedMessageIds = _orderedMessages;
else if (dir == Direction.Before)
cachedMessageIds = _orderedMessages.Where(x => x < fromMessageId.Value);
- else
+ else if (dir == Direction.After)
cachedMessageIds = _orderedMessages.Where(x => x > fromMessageId.Value);
+ else //Direction.Around
+ {
+ if (!_messages.TryGetValue(fromMessageId.Value, out SocketMessage msg))
+ return ImmutableArray.Empty;
+ int around = limit / 2;
+ var before = GetMany(fromMessageId, Direction.Before, around);
+ var after = GetMany(fromMessageId, Direction.After, around).Reverse();
+
+ return after.Concat(new SocketMessage[] { msg }).Concat(before).ToImmutableArray();
+ }
if (dir == Direction.Before)
cachedMessageIds = cachedMessageIds.Reverse();
+ if (dir == Direction.Around) //Only happens if fromMessageId is null, should only get "around" and itself (+1)
+ limit = limit / 2 + 1;
return cachedMessageIds
.Select(x =>
diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs
index 7900b7ee7..f392614ad 100644
--- a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs
+++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs
@@ -140,7 +140,7 @@ namespace Discord.WebSocket
Activity = new MessageActivity()
{
Type = model.Activity.Value.Type.Value,
- PartyId = model.Activity.Value.PartyId.Value
+ PartyId = model.Activity.Value.PartyId.GetValueOrDefault()
};
}
@@ -200,6 +200,10 @@ namespace Discord.WebSocket
{
_reactions.Clear();
}
+ internal void RemoveReactionsForEmote(IEmote emote)
+ {
+ _reactions.RemoveAll(x => x.Emote.Equals(emote));
+ }
///
public Task AddReactionAsync(IEmote emote, RequestOptions options = null)
@@ -214,6 +218,9 @@ namespace Discord.WebSocket
public Task RemoveAllReactionsAsync(RequestOptions options = null)
=> MessageHelper.RemoveAllReactionsAsync(this, Discord, options);
///
+ public Task RemoveAllReactionsForEmoteAsync(IEmote emote, RequestOptions options = null)
+ => MessageHelper.RemoveAllReactionsForEmoteAsync(this, emote, Discord, options);
+ ///
public IAsyncEnumerable> GetReactionUsersAsync(IEmote emote, int limit, RequestOptions options = null)
=> MessageHelper.GetReactionUsersAsync(this, emote, limit, Discord, options);
}
diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs
index 09c4165f4..b830ce79c 100644
--- a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs
+++ b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs
@@ -44,8 +44,11 @@ namespace Discord.WebSocket
///
/// Gets mutual guilds shared with this user.
///
+ ///
+ /// This property will only include guilds in the same .
+ ///
public IReadOnlyCollection MutualGuilds
- => Discord.Guilds.Where(g => g.Users.Any(u => u.Id == Id)).ToImmutableArray();
+ => Discord.Guilds.Where(g => g.GetUser(Id) != null).ToImmutableArray();
internal SocketUser(DiscordSocketClient discord, ulong id)
: base(discord, id)
diff --git a/src/Discord.Net.Webhook/DiscordWebhookClient.cs b/src/Discord.Net.Webhook/DiscordWebhookClient.cs
index 9c90df565..353345ded 100644
--- a/src/Discord.Net.Webhook/DiscordWebhookClient.cs
+++ b/src/Discord.Net.Webhook/DiscordWebhookClient.cs
@@ -33,7 +33,7 @@ namespace Discord.Webhook
: this(webhookUrl, new DiscordRestConfig()) { }
// regex pattern to match webhook urls
- private static Regex WebhookUrlRegex = new Regex(@"^.*discordapp\.com\/api\/webhooks\/([\d]+)\/([a-z0-9_-]+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
+ private static Regex WebhookUrlRegex = new Regex(@"^.*(discord|discordapp)\.com\/api\/webhooks\/([\d]+)\/([a-z0-9_-]+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
/// Creates a new Webhook Discord client.
public DiscordWebhookClient(ulong webhookId, string webhookToken, DiscordRestConfig config)
@@ -132,13 +132,13 @@ namespace Discord.Webhook
if (match != null)
{
// ensure that the first group is a ulong, set the _webhookId
- // 0th group is always the entire match, so start at index 1
- if (!(match.Groups[1].Success && ulong.TryParse(match.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out webhookId)))
+ // 0th group is always the entire match, and 1 is the domain; so start at index 2
+ if (!(match.Groups[2].Success && ulong.TryParse(match.Groups[2].Value, NumberStyles.None, CultureInfo.InvariantCulture, out webhookId)))
throw ex("The webhook Id could not be parsed.");
- if (!match.Groups[2].Success)
+ if (!match.Groups[3].Success)
throw ex("The webhook token could not be parsed.");
- webhookToken = match.Groups[2].Value;
+ webhookToken = match.Groups[3].Value;
}
else
throw ex();
diff --git a/test/Discord.Net.Tests.Unit/MockedEntities/MockedDMChannel.cs b/test/Discord.Net.Tests.Unit/MockedEntities/MockedDMChannel.cs
index c8d68fb4d..870c05812 100644
--- a/test/Discord.Net.Tests.Unit/MockedEntities/MockedDMChannel.cs
+++ b/test/Discord.Net.Tests.Unit/MockedEntities/MockedDMChannel.cs
@@ -73,12 +73,12 @@ namespace Discord
throw new NotImplementedException();
}
- public Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false)
+ public Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null)
{
throw new NotImplementedException();
}
- public Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false)
+ public Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null)
{
throw new NotImplementedException();
}
diff --git a/test/Discord.Net.Tests.Unit/MockedEntities/MockedGroupChannel.cs b/test/Discord.Net.Tests.Unit/MockedEntities/MockedGroupChannel.cs
index 5a26b713f..31df719da 100644
--- a/test/Discord.Net.Tests.Unit/MockedEntities/MockedGroupChannel.cs
+++ b/test/Discord.Net.Tests.Unit/MockedEntities/MockedGroupChannel.cs
@@ -81,12 +81,12 @@ namespace Discord
throw new NotImplementedException();
}
- public Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false)
+ public Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null)
{
throw new NotImplementedException();
}
- public Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false)
+ public Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null)
{
throw new NotImplementedException();
}
diff --git a/test/Discord.Net.Tests.Unit/MockedEntities/MockedTextChannel.cs b/test/Discord.Net.Tests.Unit/MockedEntities/MockedTextChannel.cs
index a57c72899..a95a91f5c 100644
--- a/test/Discord.Net.Tests.Unit/MockedEntities/MockedTextChannel.cs
+++ b/test/Discord.Net.Tests.Unit/MockedEntities/MockedTextChannel.cs
@@ -167,12 +167,12 @@ namespace Discord
throw new NotImplementedException();
}
- public Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false)
+ public Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null)
{
throw new NotImplementedException();
}
- public Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false)
+ public Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null)
{
throw new NotImplementedException();
}