diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f4041a7a..21e37b295 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,36 @@ # Changelog +## [2.4.0] - 2021-05-22 +### Added +- #1726 Add stickers (91a9063) +- #1753 Webhook message edit & delete functionality (f67cd8e) +- #1757 Add ability to add/remove roles by id (4c9910c) +- #1781 Add GetEmotesAsync to IGuild (df23d57) +- #1801 Add missing property to MESSAGE_REACTION_ADD event (0715d7d) +- #1828 Add methods to interact with reactions without a message object (5b244f2) +- #1830 Add ModifyMessageAsync to IMessageChannel (365a848) +- #1844 Add Discord Certified Moderator user flag (4b8d444) + +### Fixed +- #1486 Add type reader when entity type reader exists (c46daaa) +- #1835 Cached message emoji cleanup at MESSAGE_REACTION_REMOVE_EMOJI (8afef82) + +### Misc +- #1778 Remove URI check from EmbedBuilder (25b04c4) +- #1800 Fix spelling in SnowflakeUtils.FromSnowflake (6aff419) + +## [2.3.1] - 2021-03-10 +### Fixed +- #1761 Deadlock in DiscordShardedClient when Ready is never received (73e5cc2) +- #1773 Private methods aren't added as commands (0fc713a) +- #1780 NullReferenceException in pin/unpin audit logs (f794163) +- #1786 Add ChannelType property to ChannelInfo audit log (6ac5ea1) +- #1791 Update Webhook ChannelId from model change (d2518db) +- #1794 Audit log UserId can be null (d41aeee) + +### Misc +- #1774 Add remark regarding CustomStatus as the activity (51b7afe) + ## [2.3.0] - 2021-01-28 ### Added - #1491 Add INVITE_CREATE and INVITE_DELETE events (1ab670b) diff --git a/Discord.Net.targets b/Discord.Net.targets index a7e2c8a51..febd921d1 100644 --- a/Discord.Net.targets +++ b/Discord.Net.targets @@ -1,6 +1,6 @@ - 2.3.1 + 3.0.0 dev latest Discord.Net Contributors diff --git a/docs/_overwrites/Common/EmbedBuilder.Overwrites.md b/docs/_overwrites/Common/EmbedBuilder.Overwrites.md index 409a78e94..85c292dd2 100644 --- a/docs/_overwrites/Common/EmbedBuilder.Overwrites.md +++ b/docs/_overwrites/Common/EmbedBuilder.Overwrites.md @@ -28,7 +28,7 @@ public async Task SendRichEmbedAsync() var embed = new EmbedBuilder { // Embed property can be set within object initializer - Title = "Hello world!" + Title = "Hello world!", Description = "I am a description set by initializer." }; // Or with methods diff --git a/docs/guides/commands/intro.md b/docs/guides/commands/intro.md index abe7065c1..14341a32b 100644 --- a/docs/guides/commands/intro.md +++ b/docs/guides/commands/intro.md @@ -134,7 +134,7 @@ If, for whatever reason, you have two commands which are ambiguous to each other, you may use the @Discord.Commands.PriorityAttribute to specify which should be tested before the other. -The `Priority` attributes are sorted in ascending order; the higher +The `Priority` attributes are sorted in descending order; the higher priority will be called first. ### Command Context diff --git a/docs/guides/getting_started/first-bot.md b/docs/guides/getting_started/first-bot.md index 150466be1..e1af20d30 100644 --- a/docs/guides/getting_started/first-bot.md +++ b/docs/guides/getting_started/first-bot.md @@ -80,15 +80,11 @@ recommended for these operations to be awaited in a properly established async context whenever possible. To establish an async context, we will be creating an async main method -in your console application, and rewriting the static main method to -invoke the new async main. +in your console application. [!code-csharp[Async Context](samples/first-bot/async-context.cs)] -As a result of this, your program will now start and immediately -jump into an async context. This allows us to create a connection -to Discord later on without having to worry about setting up the -correct async implementation. +As a result of this, your program will now start into an async context. > [!WARNING] > If your application throws any exceptions within an async context, diff --git a/docs/guides/getting_started/samples/first-bot/async-context.cs b/docs/guides/getting_started/samples/first-bot/async-context.cs index 3c98c9e46..98a3cea15 100644 --- a/docs/guides/getting_started/samples/first-bot/async-context.cs +++ b/docs/guides/getting_started/samples/first-bot/async-context.cs @@ -1,7 +1,6 @@ public class Program { - public static void Main(string[] args) - => new Program().MainAsync().GetAwaiter().GetResult(); + public static Task Main(string[] args) => new Program().MainAsync(); public async Task MainAsync() { diff --git a/docs/guides/getting_started/samples/first-bot/complete.cs b/docs/guides/getting_started/samples/first-bot/complete.cs index 871641e23..542056435 100644 --- a/docs/guides/getting_started/samples/first-bot/complete.cs +++ b/docs/guides/getting_started/samples/first-bot/complete.cs @@ -2,8 +2,7 @@ public class Program { private DiscordSocketClient _client; - public static void Main(string[] args) - => new Program().MainAsync().GetAwaiter().GetResult(); + public static Task Main(string[] args) => new Program().MainAsync(); public async Task MainAsync() { diff --git a/docs/guides/getting_started/samples/first-bot/structure.cs b/docs/guides/getting_started/samples/first-bot/structure.cs index 5165e2fdb..4e64b1732 100644 --- a/docs/guides/getting_started/samples/first-bot/structure.cs +++ b/docs/guides/getting_started/samples/first-bot/structure.cs @@ -10,11 +10,11 @@ using Discord.WebSocket; class Program { // Program entry point - static void Main(string[] args) + static Task Main(string[] args) { // Call the Program constructor, followed by the // MainAsync method and wait until it finishes (which should be never). - new Program().MainAsync().GetAwaiter().GetResult(); + return new Program().MainAsync(); } private readonly DiscordSocketClient _client; diff --git a/samples/03_sharded_client/Modules/PublicModule.cs b/samples/03_sharded_client/Modules/PublicModule.cs index 60e57563a..fad2ba98c 100644 --- a/samples/03_sharded_client/Modules/PublicModule.cs +++ b/samples/03_sharded_client/Modules/PublicModule.cs @@ -9,7 +9,7 @@ namespace _03_sharded_client.Modules [Command("info")] public async Task InfoAsync() { - var msg = $@"Hi {Context.User}! There are currently {Context.Client.Shards} shards! + var msg = $@"Hi {Context.User}! There are currently {Context.Client.Shards.Count} shards! This guild is being served by shard number {Context.Client.GetShardFor(Context.Guild).ShardId}"; await ReplyAsync(msg); } diff --git a/src/Discord.Net.Core/DiscordConfig.cs b/src/Discord.Net.Core/DiscordConfig.cs index 429ad7b0c..da8525644 100644 --- a/src/Discord.Net.Core/DiscordConfig.cs +++ b/src/Discord.Net.Core/DiscordConfig.cs @@ -16,7 +16,7 @@ namespace Discord /// Discord API documentation /// . /// - public const int APIVersion = 6; + public const int APIVersion = 9; /// /// Returns the Voice API version Discord.Net uses. /// @@ -43,7 +43,7 @@ namespace Discord /// /// The user agent used in each Discord.Net request. /// - public static string UserAgent { get; } = $"DiscordBot (https://github.com/RogueException/Discord.Net, v{Version})"; + public static string UserAgent { get; } = $"DiscordBot (https://github.com/discord-net/Discord.Net, v{Version})"; /// /// Returns the base Discord API URL. /// @@ -141,18 +141,6 @@ namespace Discord /// internal bool DisplayInitialLog { get; set; } = true; - /// - /// Gets or sets the level of precision of the rate limit reset response. - /// - /// - /// If set to , this value will be rounded up to the - /// nearest second. - /// - /// - /// The currently set . - /// - public RateLimitPrecision RateLimitPrecision { get; set; } = RateLimitPrecision.Millisecond; - /// /// Gets or sets whether or not rate-limits should use the system clock. /// diff --git a/src/Discord.Net.Core/Entities/Guilds/GuildEmbedProperties.cs b/src/Discord.Net.Core/Entities/Guilds/GuildEmbedProperties.cs deleted file mode 100644 index 34473e93c..000000000 --- a/src/Discord.Net.Core/Entities/Guilds/GuildEmbedProperties.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace Discord -{ - /// - /// Provides properties that are used to modify the widget of an with the specified changes. - /// - public class GuildEmbedProperties - { - /// - /// Sets whether the widget should be enabled. - /// - public Optional Enabled { get; set; } - /// - /// Sets the channel that the invite should place its users in, if not null. - /// - public Optional Channel { get; set; } - /// - /// Sets the channel the invite should place its users in, if not null. - /// - public Optional ChannelId { get; set; } - } -} diff --git a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs index 36d735157..b8fd858df 100644 --- a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs +++ b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs @@ -28,13 +28,6 @@ namespace Discord /// int AFKTimeout { get; } /// - /// Gets a value that indicates whether this guild is embeddable (i.e. can use widget). - /// - /// - /// if this guild has a widget enabled; otherwise . - /// - bool IsEmbeddable { get; } - /// /// Gets a value that indicates whether this guild has the widget enabled. /// /// @@ -132,29 +125,6 @@ namespace Discord /// ulong? AFKChannelId { get; } /// - /// Gets the ID of the default channel for this guild. - /// - /// - /// This property retrieves the snowflake identifier of the first viewable text channel for this guild. - /// - /// This channel does not guarantee the user can send message to it, as it only looks for the first viewable - /// text channel. - /// - /// - /// - /// A representing the snowflake identifier of the default text channel; 0 if - /// none can be found. - /// - ulong DefaultChannelId { get; } - /// - /// Gets the ID of the widget embed channel of this guild. - /// - /// - /// A representing the snowflake identifier of the embedded channel found within the - /// widget settings of this guild; if none is set. - /// - ulong? EmbedChannelId { get; } - /// /// Gets the ID of the channel assigned to the widget of this guild. /// /// @@ -364,16 +334,6 @@ namespace Discord /// Task ModifyAsync(Action func, RequestOptions options = null); /// - /// Modifies this guild's embed channel. - /// - /// The delegate containing the properties to modify the guild widget with. - /// The options to be used when sending the request. - /// - /// A task that represents the asynchronous modification operation. - /// - [Obsolete("This endpoint is deprecated, use ModifyWidgetAsync instead.")] - Task ModifyEmbedAsync(Action func, RequestOptions options = null); - /// /// Modifies this guild's widget. /// /// The delegate containing the properties to modify the guild widget with. @@ -592,17 +552,6 @@ namespace Discord /// Task GetDefaultChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); /// - /// Gets the embed channel (i.e. the channel set in the guild's widget settings) in this guild. - /// - /// 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 the embed channel set - /// within the server's widget settings; if none is set. - /// - [Obsolete("This endpoint is deprecated, use GetWidgetChannelAsync instead.")] - Task GetEmbedChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); - /// /// Gets the widget channel (i.e. the channel set in the guild's widget settings) in this guild. /// /// The that determines whether the object should be fetched from cache. diff --git a/src/Discord.Net.Core/Entities/Guilds/PermissionTarget.cs b/src/Discord.Net.Core/Entities/Guilds/PermissionTarget.cs index 3da2fb147..fb759e4c5 100644 --- a/src/Discord.Net.Core/Entities/Guilds/PermissionTarget.cs +++ b/src/Discord.Net.Core/Entities/Guilds/PermissionTarget.cs @@ -8,10 +8,10 @@ namespace Discord /// /// The target of the permission is a role. /// - Role, + Role = 0, /// /// The target of the permission is a user. /// - User + User = 1, } } diff --git a/src/Discord.Net.Core/Entities/Invites/IInviteMetadata.cs b/src/Discord.Net.Core/Entities/Invites/IInviteMetadata.cs index c64b3205b..c2580c853 100644 --- a/src/Discord.Net.Core/Entities/Invites/IInviteMetadata.cs +++ b/src/Discord.Net.Core/Entities/Invites/IInviteMetadata.cs @@ -16,14 +16,6 @@ namespace Discord /// bool IsTemporary { get; } /// - /// Gets a value that indicates whether the invite has been revoked. - /// - /// - /// true if this invite was revoked; otherwise false. - /// - [Obsolete("This property doesn't exist anymore and shouldn't be used.")] - bool IsRevoked { get; } - /// /// Gets the time (in seconds) until the invite expires. /// /// diff --git a/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs b/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs index 1589e2ae5..c2d0e13bc 100644 --- a/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs +++ b/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs @@ -36,18 +36,6 @@ namespace Discord /// Task ModifyAsync(Action func, RequestOptions options = null); /// - /// Modifies the suppression of this message. - /// - /// - /// This method modifies whether or not embeds in this message are suppressed (hidden). - /// - /// Whether or not embeds in this message should be suppressed. - /// The options to be used when sending the request. - /// - /// A task that represents the asynchronous modification operation. - /// - Task ModifySuppressionAsync(bool suppressEmbeds, RequestOptions options = null); - /// /// Adds this message to its channel's pinned messages. /// /// The options to be used when sending the request. diff --git a/src/Discord.Net.Core/Entities/Messages/MessageType.cs b/src/Discord.Net.Core/Entities/Messages/MessageType.cs index ad1f3a3cd..bfe763cad 100644 --- a/src/Discord.Net.Core/Entities/Messages/MessageType.cs +++ b/src/Discord.Net.Core/Entities/Messages/MessageType.cs @@ -60,9 +60,6 @@ namespace Discord /// /// The message is an inline reply. /// - /// - /// Only available in API v8. - /// Reply = 19, } } diff --git a/src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs b/src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs index e1f78373e..bf08887bd 100644 --- a/src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs +++ b/src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs @@ -22,11 +22,6 @@ namespace Discord /// AddReactions = 0x00_00_00_40, /// - /// Allows for reading of messages. This flag is obsolete, use instead. - /// - [Obsolete("Use ViewChannel instead.")] - ReadMessages = ViewChannel, - /// /// Allows guild members to view a channel, which includes reading messages in text channels. /// ViewChannel = 0x00_00_04_00, diff --git a/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs b/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs index ed675b5f3..d774cc51d 100644 --- a/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs @@ -45,9 +45,6 @@ namespace Discord /// If true, a user may add reactions. public bool AddReactions => Permissions.GetValue(RawValue, ChannelPermission.AddReactions); - /// If true, a user may join channels. - [Obsolete("Use ViewChannel instead.")] - public bool ReadMessages => ViewChannel; /// If true, a user may view channels. public bool ViewChannel => Permissions.GetValue(RawValue, ChannelPermission.ViewChannel); diff --git a/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs b/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs index 645b67489..31bd6164a 100644 --- a/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs +++ b/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs @@ -65,8 +65,6 @@ namespace Discord /// Allows for viewing of audit logs. /// ViewAuditLog = 0x00_00_00_80, - [Obsolete("Use ViewChannel instead.")] - ReadMessages = ViewChannel, ViewChannel = 0x00_00_04_00, SendMessages = 0x00_00_08_00, /// diff --git a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs index ba6757fc6..b03c0e1a8 100644 --- a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs @@ -37,9 +37,6 @@ namespace Discord /// If true, a user may view the guild insights. public bool ViewGuildInsights => Permissions.GetValue(RawValue, GuildPermission.ViewGuildInsights); - /// If True, a user may join channels. - [Obsolete("Use ViewChannel instead.")] - public bool ReadMessages => ViewChannel; /// If True, a user may view channels. public bool ViewChannel => Permissions.GetValue(RawValue, GuildPermission.ViewChannel); /// If True, a user may send messages. @@ -90,6 +87,9 @@ namespace Discord /// Creates a new with the provided packed value. public GuildPermissions(ulong rawValue) { RawValue = rawValue; } + /// Creates a new with the provided packed value after converting to ulong. + public GuildPermissions(string rawValue) { RawValue = ulong.Parse(rawValue); } + private GuildPermissions(ulong initialValue, bool? createInstantInvite = null, bool? kickMembers = null, diff --git a/src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs b/src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs index 7876d49ff..4f144c74b 100644 --- a/src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs @@ -43,9 +43,6 @@ namespace Discord /// If Allowed, a user may add reactions. public PermValue AddReactions => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.AddReactions); /// If Allowed, a user may join channels. - [Obsolete("Use ViewChannel instead.")] - public PermValue ReadMessages => ViewChannel; - /// If Allowed, a user may join channels. public PermValue ViewChannel => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.ViewChannel); /// If Allowed, a user may send messages. public PermValue SendMessages => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.SendMessages); @@ -93,6 +90,13 @@ namespace Discord DenyValue = denyValue; } + /// Creates a new OverwritePermissions with the provided allow and deny packed values after converting to ulong. + public OverwritePermissions(string allowValue, string denyValue) + { + AllowValue = ulong.Parse(allowValue); + DenyValue = ulong.Parse(denyValue); + } + private OverwritePermissions(ulong allowValue, ulong denyValue, PermValue? createInstantInvite = null, PermValue? manageChannel = null, diff --git a/src/Discord.Net.Core/Entities/Users/IPresence.cs b/src/Discord.Net.Core/Entities/Users/IPresence.cs index a17ac0df2..6972037f0 100644 --- a/src/Discord.Net.Core/Entities/Users/IPresence.cs +++ b/src/Discord.Net.Core/Entities/Users/IPresence.cs @@ -7,10 +7,6 @@ namespace Discord /// public interface IPresence { - /// - /// Gets the activity this user is currently doing. - /// - IActivity Activity { get; } /// /// Gets the current status of this user. /// diff --git a/src/Discord.Net.Core/Entities/Users/IUser.cs b/src/Discord.Net.Core/Entities/Users/IUser.cs index e6008aab6..9596a8338 100644 --- a/src/Discord.Net.Core/Entities/Users/IUser.cs +++ b/src/Discord.Net.Core/Entities/Users/IUser.cs @@ -87,7 +87,7 @@ namespace Discord UserProperties? PublicFlags { get; } /// - /// Gets the direct message channel of this user, or create one if it does not already exist. + /// Creates the direct message channel of this user. /// /// /// This method is used to obtain or create a channel used to send a direct message. @@ -102,7 +102,7 @@ namespace Discord /// /// The following example attempts to send a direct message to the target user and logs the incident should /// it fail. - /// /// /// The options to be used when sending the request. @@ -110,6 +110,6 @@ namespace Discord /// A task that represents the asynchronous operation for getting or creating a DM channel. The task result /// contains the DM channel associated with this user. /// - Task GetOrCreateDMChannelAsync(RequestOptions options = null); + Task CreateDMChannelAsync(RequestOptions options = null); } } diff --git a/src/Discord.Net.Core/Entities/Users/UserProperties.cs b/src/Discord.Net.Core/Entities/Users/UserProperties.cs index b6deb744b..68232b254 100644 --- a/src/Discord.Net.Core/Entities/Users/UserProperties.cs +++ b/src/Discord.Net.Core/Entities/Users/UserProperties.cs @@ -22,12 +22,6 @@ namespace Discord /// HypeSquadEvents = 1 << 2, /// - /// Flag given to users who have participated in the bug report program. - /// This flag is obsolete, use instead. - /// - [Obsolete("Use BugHunterLevel1 instead.")] - BugHunter = 1 << 3, - /// /// Flag given to users who have participated in the bug report program and are level 1. /// BugHunterLevel1 = 1 << 3, @@ -67,5 +61,9 @@ namespace Discord /// Flag given to users that developed bots and early verified their accounts. /// EarlyVerifiedBotDeveloper = 1 << 17, + /// + /// Flag given to users that are discord certified moderators who has give discord's exam. + /// + DiscordCertifiedModerator = 1 << 18, } } diff --git a/src/Discord.Net.Core/Extensions/StringExtensions.cs b/src/Discord.Net.Core/Extensions/StringExtensions.cs deleted file mode 100644 index c0ebb2626..000000000 --- a/src/Discord.Net.Core/Extensions/StringExtensions.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; - -namespace Discord -{ - internal static class StringExtensions - { - public static bool IsNullOrUri(this string url) => - string.IsNullOrEmpty(url) || Uri.IsWellFormedUriString(url, UriKind.Absolute); - } -} diff --git a/src/Discord.Net.Core/Extensions/UserExtensions.cs b/src/Discord.Net.Core/Extensions/UserExtensions.cs index 90f26828a..3e46308e6 100644 --- a/src/Discord.Net.Core/Extensions/UserExtensions.cs +++ b/src/Discord.Net.Core/Extensions/UserExtensions.cs @@ -42,7 +42,7 @@ namespace Discord RequestOptions options = null, AllowedMentions allowedMentions = null) { - return await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendMessageAsync(text, isTTS, embed, options, allowedMentions).ConfigureAwait(false); + return await (await user.CreateDMChannelAsync().ConfigureAwait(false)).SendMessageAsync(text, isTTS, embed, options, allowedMentions).ConfigureAwait(false); } /// @@ -94,7 +94,7 @@ namespace Discord RequestOptions options = null ) { - return await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false); + return await (await user.CreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false); } /// @@ -149,7 +149,7 @@ namespace Discord Embed embed = null, RequestOptions options = null) { - return await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); + return await (await user.CreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); } /// diff --git a/src/Discord.Net.Core/GatewayIntents.cs b/src/Discord.Net.Core/GatewayIntents.cs index f3dc5ceb9..6976806b2 100644 --- a/src/Discord.Net.Core/GatewayIntents.cs +++ b/src/Discord.Net.Core/GatewayIntents.cs @@ -39,5 +39,16 @@ namespace Discord DirectMessageReactions = 1 << 13, /// This intent includes TYPING_START DirectMessageTyping = 1 << 14, + /// + /// This intent includes all but and + /// that are privileged must be enabled for the application. + /// + AllUnprivileged = Guilds | GuildBans | GuildEmojis | GuildIntegrations | GuildWebhooks | GuildInvites | + GuildVoiceStates | GuildMessages | GuildMessageReactions | GuildMessageTyping | DirectMessages | + DirectMessageReactions | DirectMessageTyping, + /// + /// This intent includes all of them, including privileged ones. + /// + All = AllUnprivileged | GuildMembers | GuildPresences } } diff --git a/src/Discord.Net.Core/RateLimitPrecision.cs b/src/Discord.Net.Core/RateLimitPrecision.cs deleted file mode 100644 index fe3c1b90e..000000000 --- a/src/Discord.Net.Core/RateLimitPrecision.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Discord -{ - /// - /// Specifies the level of precision to request in the rate limit - /// response header. - /// - public enum RateLimitPrecision - { - /// - /// Specifies precision rounded up to the nearest whole second - /// - Second, - /// - /// Specifies precision rounded to the nearest millisecond. - /// - Millisecond - } -} diff --git a/src/Discord.Net.Core/TokenType.cs b/src/Discord.Net.Core/TokenType.cs index 8ca3f031c..03b840830 100644 --- a/src/Discord.Net.Core/TokenType.cs +++ b/src/Discord.Net.Core/TokenType.cs @@ -5,8 +5,6 @@ namespace Discord /// Specifies the type of token to use with the client. public enum TokenType { - [Obsolete("User logins are deprecated and may result in a ToS strike against your account - please see https://github.com/RogueException/Discord.Net/issues/827", error: true)] - User, /// /// An OAuth2 token type. /// diff --git a/src/Discord.Net.Examples/Core/Entities/Users/IUser.Examples.cs b/src/Discord.Net.Examples/Core/Entities/Users/IUser.Examples.cs index 79a90b46d..83daedaa0 100644 --- a/src/Discord.Net.Examples/Core/Entities/Users/IUser.Examples.cs +++ b/src/Discord.Net.Examples/Core/Entities/Users/IUser.Examples.cs @@ -18,11 +18,11 @@ namespace Discord.Net.Examples.Core.Entities.Users #endregion - #region GetOrCreateDMChannelAsync + #region CreateDMChannelAsync public async Task MessageUserAsync(IUser user) { - var channel = await user.GetOrCreateDMChannelAsync(); + var channel = await user.CreateDMChannelAsync(); try { await channel.SendMessageAsync("Awesome stuff!"); diff --git a/src/Discord.Net.Examples/WebSocket/BaseSocketClient.Events.Examples.cs b/src/Discord.Net.Examples/WebSocket/BaseSocketClient.Events.Examples.cs index 387584877..27d393c07 100644 --- a/src/Discord.Net.Examples/WebSocket/BaseSocketClient.Events.Examples.cs +++ b/src/Discord.Net.Examples/WebSocket/BaseSocketClient.Events.Examples.cs @@ -15,7 +15,7 @@ namespace Discord.Net.Examples.WebSocket => client.ReactionAdded += HandleReactionAddedAsync; public async Task HandleReactionAddedAsync(Cacheable cachedMessage, - ISocketMessageChannel originChannel, SocketReaction reaction) + Cacheable originChannel, SocketReaction reaction) { var message = await cachedMessage.GetOrDownloadAsync(); if (message != null && reaction.User.IsSpecified) @@ -100,16 +100,17 @@ namespace Discord.Net.Examples.WebSocket public void HookMessageDeleted(BaseSocketClient client) => client.MessageDeleted += HandleMessageDelete; - public Task HandleMessageDelete(Cacheable cachedMessage, ISocketMessageChannel channel) + public async Task HandleMessageDelete(Cacheable cachedMessage, Cacheable cachedChannel) { // check if the message exists in cache; if not, we cannot report what was removed - if (!cachedMessage.HasValue) return Task.CompletedTask; + if (!cachedMessage.HasValue) return; + // gets or downloads the channel if it's not in the cache + IMessageChannel channel = await cachedChannel.GetOrDownloadAsync(); var message = cachedMessage.Value; Console.WriteLine( $"A message ({message.Id}) from {message.Author} was removed from the channel {channel.Name} ({channel.Id}):" + Environment.NewLine + message.Content); - return Task.CompletedTask; } #endregion diff --git a/src/Discord.Net.Rest/API/Common/Guild.cs b/src/Discord.Net.Rest/API/Common/Guild.cs index 46075ce4d..bd25c7e1a 100644 --- a/src/Discord.Net.Rest/API/Common/Guild.cs +++ b/src/Discord.Net.Rest/API/Common/Guild.cs @@ -23,10 +23,6 @@ namespace Discord.API public ulong? AFKChannelId { get; set; } [JsonProperty("afk_timeout")] public int AFKTimeout { get; set; } - [JsonProperty("embed_enabled")] - public Optional EmbedEnabled { get; set; } - [JsonProperty("embed_channel_id")] - public Optional EmbedChannelId { get; set; } [JsonProperty("verification_level")] public VerificationLevel VerificationLevel { get; set; } [JsonProperty("default_message_notifications")] diff --git a/src/Discord.Net.Rest/API/Common/GuildEmbed.cs b/src/Discord.Net.Rest/API/Common/GuildEmbed.cs deleted file mode 100644 index d81632181..000000000 --- a/src/Discord.Net.Rest/API/Common/GuildEmbed.cs +++ /dev/null @@ -1,13 +0,0 @@ -#pragma warning disable CS1591 -using Newtonsoft.Json; - -namespace Discord.API -{ - internal class GuildEmbed - { - [JsonProperty("enabled")] - public bool Enabled { get; set; } - [JsonProperty("channel_id")] - public ulong? ChannelId { get; set; } - } -} diff --git a/src/Discord.Net.Rest/API/Common/InviteVanity.cs b/src/Discord.Net.Rest/API/Common/InviteVanity.cs index d39792674..a36ddee46 100644 --- a/src/Discord.Net.Rest/API/Common/InviteVanity.cs +++ b/src/Discord.Net.Rest/API/Common/InviteVanity.cs @@ -6,5 +6,7 @@ namespace Discord.API { [JsonProperty("code")] public string Code { get; set; } + [JsonProperty("uses")] + public int Uses { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/Overwrite.cs b/src/Discord.Net.Rest/API/Common/Overwrite.cs index 1f3548a1c..3d94b0640 100644 --- a/src/Discord.Net.Rest/API/Common/Overwrite.cs +++ b/src/Discord.Net.Rest/API/Common/Overwrite.cs @@ -10,8 +10,8 @@ namespace Discord.API [JsonProperty("type")] public PermissionTarget TargetType { get; set; } [JsonProperty("deny"), Int53] - public ulong Deny { get; set; } + public string Deny { get; set; } [JsonProperty("allow"), Int53] - public ulong Allow { get; set; } + public string Allow { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/Presence.cs b/src/Discord.Net.Rest/API/Common/Presence.cs index b37ad4229..b44e9185d 100644 --- a/src/Discord.Net.Rest/API/Common/Presence.cs +++ b/src/Discord.Net.Rest/API/Common/Presence.cs @@ -13,8 +13,6 @@ namespace Discord.API public Optional GuildId { get; set; } [JsonProperty("status")] public UserStatus Status { get; set; } - [JsonProperty("game")] - public Game Game { get; set; } [JsonProperty("roles")] public Optional Roles { get; set; } diff --git a/src/Discord.Net.Rest/API/Common/Role.cs b/src/Discord.Net.Rest/API/Common/Role.cs index 190ae25c0..c655175da 100644 --- a/src/Discord.Net.Rest/API/Common/Role.cs +++ b/src/Discord.Net.Rest/API/Common/Role.cs @@ -18,7 +18,7 @@ namespace Discord.API [JsonProperty("position")] public int Position { get; set; } [JsonProperty("permissions"), Int53] - public ulong Permissions { get; set; } + public string Permissions { get; set; } [JsonProperty("managed")] public bool Managed { get; set; } [JsonProperty("tags")] diff --git a/src/Discord.Net.Rest/API/Common/UserGuild.cs b/src/Discord.Net.Rest/API/Common/UserGuild.cs index f4f763885..825e9a09a 100644 --- a/src/Discord.Net.Rest/API/Common/UserGuild.cs +++ b/src/Discord.Net.Rest/API/Common/UserGuild.cs @@ -1,4 +1,4 @@ -#pragma warning disable CS1591 +#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API @@ -14,6 +14,6 @@ namespace Discord.API [JsonProperty("owner")] public bool Owner { get; set; } [JsonProperty("permissions"), Int53] - public ulong Permissions { get; set; } + public string Permissions { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/ModifyChannelPermissionsParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyChannelPermissionsParams.cs index 0fe5f7e5a..269111a61 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyChannelPermissionsParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyChannelPermissionsParams.cs @@ -1,4 +1,4 @@ -#pragma warning disable CS1591 +#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Rest @@ -7,13 +7,13 @@ namespace Discord.API.Rest internal class ModifyChannelPermissionsParams { [JsonProperty("type")] - public string Type { get; } + public int Type { get; } [JsonProperty("allow")] - public ulong Allow { get; } + public string Allow { get; } [JsonProperty("deny")] - public ulong Deny { get; } + public string Deny { get; } - public ModifyChannelPermissionsParams(string type, ulong allow, ulong deny) + public ModifyChannelPermissionsParams(int type, string allow, string deny) { Type = type; Allow = allow; diff --git a/src/Discord.Net.Rest/API/Rest/ModifyGuildRoleParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyGuildRoleParams.cs index 287e1cafe..8605411c5 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyGuildRoleParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyGuildRoleParams.cs @@ -1,4 +1,4 @@ -#pragma warning disable CS1591 +#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Rest @@ -9,7 +9,7 @@ namespace Discord.API.Rest [JsonProperty("name")] public Optional Name { get; set; } [JsonProperty("permissions")] - public Optional Permissions { get; set; } + public Optional Permissions { get; set; } [JsonProperty("color")] public Optional Color { get; set; } [JsonProperty("hoist")] diff --git a/src/Discord.Net.Rest/API/Rest/SuppressEmbedParams.cs b/src/Discord.Net.Rest/API/Rest/SuppressEmbedParams.cs deleted file mode 100644 index 9139627b8..000000000 --- a/src/Discord.Net.Rest/API/Rest/SuppressEmbedParams.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Newtonsoft.Json; - -namespace Discord.API.Rest -{ - [JsonObject(MemberSerialization = MemberSerialization.OptIn)] - internal class SuppressEmbedParams - { - [JsonProperty("suppress")] - public bool Suppressed { get; set; } - } -} diff --git a/src/Discord.Net.Rest/ClientHelper.cs b/src/Discord.Net.Rest/ClientHelper.cs index b60c02cab..3ff8212fe 100644 --- a/src/Discord.Net.Rest/ClientHelper.cs +++ b/src/Discord.Net.Rest/ClientHelper.cs @@ -69,14 +69,6 @@ namespace Discord.Rest return RestGuild.Create(client, model); return null; } - public static async Task GetGuildEmbedAsync(BaseDiscordClient client, - ulong id, RequestOptions options) - { - var model = await client.ApiClient.GetGuildEmbedAsync(id, options).ConfigureAwait(false); - if (model != null) - return RestGuildEmbed.Create(model); - return null; - } public static async Task GetGuildWidgetAsync(BaseDiscordClient client, ulong id, RequestOptions options) { diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index d3e4aa515..d7978db5c 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -45,20 +45,18 @@ namespace Discord.API internal string AuthToken { get; private set; } internal IRestClient RestClient { get; private set; } internal ulong? CurrentUserId { get; set; } - public RateLimitPrecision RateLimitPrecision { get; private set; } internal bool UseSystemClock { get; set; } internal JsonSerializer Serializer => _serializer; /// Unknown OAuth token type. public DiscordRestApiClient(RestClientProvider restClientProvider, string userAgent, RetryMode defaultRetryMode = RetryMode.AlwaysRetry, - JsonSerializer serializer = null, RateLimitPrecision rateLimitPrecision = RateLimitPrecision.Second, bool useSystemClock = true) + JsonSerializer serializer = null, bool useSystemClock = true) { _restClientProvider = restClientProvider; UserAgent = userAgent; DefaultRetryMode = defaultRetryMode; _serializer = serializer ?? new JsonSerializer { ContractResolver = new DiscordContractResolver() }; - RateLimitPrecision = rateLimitPrecision; UseSystemClock = useSystemClock; RequestQueue = new RequestQueue(); @@ -75,14 +73,12 @@ namespace Discord.API RestClient.SetHeader("accept", "*/*"); RestClient.SetHeader("user-agent", UserAgent); RestClient.SetHeader("authorization", GetPrefixedToken(AuthTokenType, AuthToken)); - RestClient.SetHeader("X-RateLimit-Precision", RateLimitPrecision.ToString().ToLower()); } /// Unknown OAuth token type. internal static string GetPrefixedToken(TokenType tokenType, string token) { return tokenType switch { - default(TokenType) => token, TokenType.Bot => $"Bot {token}", TokenType.Bearer => $"Bearer {token}", _ => throw new ArgumentException(message: "Unknown OAuth token type.", paramName: nameof(tokenType)), @@ -645,16 +641,6 @@ namespace Discord.API return await SendJsonAsync("PATCH", () => $"channels/{channelId}/messages/{messageId}", args, ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false); } - public async Task SuppressEmbedAsync(ulong channelId, ulong messageId, Rest.SuppressEmbedParams args, RequestOptions options = null) - { - Preconditions.NotEqual(channelId, 0, nameof(channelId)); - Preconditions.NotEqual(messageId, 0, nameof(messageId)); - options = RequestOptions.CreateOrClone(options); - - var ids = new BucketIds(channelId: channelId); - await SendJsonAsync("POST", () => $"channels/{channelId}/messages/{messageId}/suppress-embeds", args, ids, options: options).ConfigureAwait(false); - } - public async Task AddReactionAsync(ulong channelId, ulong messageId, string emoji, RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); @@ -936,7 +922,7 @@ namespace Discord.API var ids = new BucketIds(guildId: guildId); string reason = string.IsNullOrWhiteSpace(args.Reason) ? "" : $"&reason={Uri.EscapeDataString(args.Reason)}"; - await SendAsync("PUT", () => $"guilds/{guildId}/bans/{userId}?delete-message-days={args.DeleteMessageDays}{reason}", ids, options: options).ConfigureAwait(false); + await SendAsync("PUT", () => $"guilds/{guildId}/bans/{userId}?delete_message_days={args.DeleteMessageDays}{reason}", ids, options: options).ConfigureAwait(false); } /// and must not be equal to zero. public async Task RemoveGuildBanAsync(ulong guildId, ulong userId, RequestOptions options = null) @@ -949,32 +935,6 @@ namespace Discord.API await SendAsync("DELETE", () => $"guilds/{guildId}/bans/{userId}", ids, options: options).ConfigureAwait(false); } - //Guild Embeds - /// must not be equal to zero. - public async Task GetGuildEmbedAsync(ulong guildId, RequestOptions options = null) - { - Preconditions.NotEqual(guildId, 0, nameof(guildId)); - options = RequestOptions.CreateOrClone(options); - - try - { - var ids = new BucketIds(guildId: guildId); - return await SendAsync("GET", () => $"guilds/{guildId}/embed", ids, options: options).ConfigureAwait(false); - } - catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.NotFound) { return null; } - } - /// must not be equal to zero. - /// must not be . - public async Task ModifyGuildEmbedAsync(ulong guildId, Rest.ModifyGuildEmbedParams args, RequestOptions options = null) - { - Preconditions.NotNull(args, nameof(args)); - Preconditions.NotEqual(guildId, 0, nameof(guildId)); - options = RequestOptions.CreateOrClone(options); - - var ids = new BucketIds(guildId: guildId); - return await SendJsonAsync("PATCH", () => $"guilds/{guildId}/embed", args, ids, options: options).ConfigureAwait(false); - } - //Guild Widget /// must not be equal to zero. public async Task GetGuildWidgetAsync(ulong guildId, RequestOptions options = null) diff --git a/src/Discord.Net.Rest/DiscordRestClient.cs b/src/Discord.Net.Rest/DiscordRestClient.cs index 7eff7363c..9cdb8e409 100644 --- a/src/Discord.Net.Rest/DiscordRestClient.cs +++ b/src/Discord.Net.Rest/DiscordRestClient.cs @@ -29,10 +29,7 @@ namespace Discord.Rest internal DiscordRestClient(DiscordRestConfig config, API.DiscordRestApiClient api) : base(config, api) { } private static API.DiscordRestApiClient CreateApiClient(DiscordRestConfig config) - => new API.DiscordRestApiClient(config.RestClientProvider, - DiscordRestConfig.UserAgent, - rateLimitPrecision: config.RateLimitPrecision, - useSystemClock: config.UseSystemClock); + => new API.DiscordRestApiClient(config.RestClientProvider, DiscordRestConfig.UserAgent, useSystemClock: config.UseSystemClock); internal override void Dispose(bool disposing) { @@ -80,9 +77,6 @@ namespace Discord.Rest => ClientHelper.GetGuildAsync(this, id, false, options); public Task GetGuildAsync(ulong id, bool withCounts, RequestOptions options = null) => ClientHelper.GetGuildAsync(this, id, withCounts, options); - [Obsolete("This endpoint is deprecated, use GetGuildWidgetAsync instead.")] - public Task GetGuildEmbedAsync(ulong id, RequestOptions options = null) - => ClientHelper.GetGuildEmbedAsync(this, id, options); public Task GetGuildWidgetAsync(ulong id, RequestOptions options = null) => ClientHelper.GetGuildWidgetAsync(this, id, options); public IAsyncEnumerable> GetGuildSummariesAsync(RequestOptions options = null) diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index 7c4edb43e..22395ab3a 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -33,8 +33,8 @@ namespace Discord.Rest { TargetId = overwrite.TargetId, TargetType = overwrite.TargetType, - Allow = overwrite.Permissions.AllowValue, - Deny = overwrite.Permissions.DenyValue + Allow = overwrite.Permissions.AllowValue.ToString(), + Deny = overwrite.Permissions.DenyValue.ToString() }).ToArray() : Optional.Create(), }; @@ -59,8 +59,8 @@ namespace Discord.Rest { TargetId = overwrite.TargetId, TargetType = overwrite.TargetType, - Allow = overwrite.Permissions.AllowValue, - Deny = overwrite.Permissions.DenyValue + Allow = overwrite.Permissions.AllowValue.ToString(), + Deny = overwrite.Permissions.DenyValue.ToString() }).ToArray() : Optional.Create(), }; @@ -84,8 +84,8 @@ namespace Discord.Rest { TargetId = overwrite.TargetId, TargetType = overwrite.TargetType, - Allow = overwrite.Permissions.AllowValue, - Deny = overwrite.Permissions.DenyValue + Allow = overwrite.Permissions.AllowValue.ToString(), + Deny = overwrite.Permissions.DenyValue.ToString() }).ToArray() : Optional.Create(), }; @@ -328,13 +328,13 @@ namespace Discord.Rest public static async Task AddPermissionOverwriteAsync(IGuildChannel channel, BaseDiscordClient client, IUser user, OverwritePermissions perms, RequestOptions options) { - var args = new ModifyChannelPermissionsParams("member", perms.AllowValue, perms.DenyValue); + var args = new ModifyChannelPermissionsParams((int)PermissionTarget.User, perms.AllowValue.ToString(), perms.DenyValue.ToString()); await client.ApiClient.ModifyChannelPermissionsAsync(channel.Id, user.Id, args, options).ConfigureAwait(false); } public static async Task AddPermissionOverwriteAsync(IGuildChannel channel, BaseDiscordClient client, IRole role, OverwritePermissions perms, RequestOptions options) { - var args = new ModifyChannelPermissionsParams("role", perms.AllowValue, perms.DenyValue); + var args = new ModifyChannelPermissionsParams((int)PermissionTarget.Role, perms.AllowValue.ToString(), perms.DenyValue.ToString()); await client.ApiClient.ModifyChannelPermissionsAsync(channel.Id, role.Id, args, options).ConfigureAwait(false); } public static async Task RemovePermissionOverwriteAsync(IGuildChannel channel, BaseDiscordClient client, @@ -450,8 +450,8 @@ namespace Discord.Rest { TargetId = overwrite.TargetId, TargetType = overwrite.TargetType, - Allow = overwrite.Permissions.AllowValue, - Deny = overwrite.Permissions.DenyValue + Allow = overwrite.Permissions.AllowValue.ToString(), + Deny = overwrite.Permissions.DenyValue.ToString() }).ToArray() }; await client.ApiClient.ModifyGuildChannelAsync(channel.Id, apiArgs, options).ConfigureAwait(false); diff --git a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs index d10d046ee..58a4ea2c8 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading.Tasks; -using EmbedModel = Discord.API.GuildEmbed; using WidgetModel = Discord.API.GuildWidget; using Model = Discord.API.Guild; using RoleModel = Discord.API.Role; @@ -81,26 +80,6 @@ namespace Discord.Rest return await client.ApiClient.ModifyGuildAsync(guild.Id, apiArgs, options).ConfigureAwait(false); } /// is null. - public static async Task ModifyEmbedAsync(IGuild guild, BaseDiscordClient client, - Action func, RequestOptions options) - { - if (func == null) throw new ArgumentNullException(nameof(func)); - - var args = new GuildEmbedProperties(); - func(args); - var apiArgs = new API.Rest.ModifyGuildEmbedParams - { - Enabled = args.Enabled - }; - - if (args.Channel.IsSpecified) - apiArgs.ChannelId = args.Channel.Value?.Id; - else if (args.ChannelId.IsSpecified) - apiArgs.ChannelId = args.ChannelId.Value; - - return await client.ApiClient.ModifyGuildEmbedAsync(guild.Id, apiArgs, options).ConfigureAwait(false); - } - /// is null. public static async Task ModifyWidgetAsync(IGuild guild, BaseDiscordClient client, Action func, RequestOptions options) { @@ -205,8 +184,8 @@ namespace Discord.Rest { TargetId = overwrite.TargetId, TargetType = overwrite.TargetType, - Allow = overwrite.Permissions.AllowValue, - Deny = overwrite.Permissions.DenyValue + Allow = overwrite.Permissions.AllowValue.ToString(), + Deny = overwrite.Permissions.DenyValue.ToString() }).ToArray() : Optional.Create(), }; @@ -233,8 +212,8 @@ namespace Discord.Rest { TargetId = overwrite.TargetId, TargetType = overwrite.TargetType, - Allow = overwrite.Permissions.AllowValue, - Deny = overwrite.Permissions.DenyValue + Allow = overwrite.Permissions.AllowValue.ToString(), + Deny = overwrite.Permissions.DenyValue.ToString() }).ToArray() : Optional.Create(), }; @@ -258,8 +237,8 @@ namespace Discord.Rest { TargetId = overwrite.TargetId, TargetType = overwrite.TargetType, - Allow = overwrite.Permissions.AllowValue, - Deny = overwrite.Permissions.DenyValue + Allow = overwrite.Permissions.AllowValue.ToString(), + Deny = overwrite.Permissions.DenyValue.ToString() }).ToArray() : Optional.Create(), }; @@ -304,6 +283,7 @@ namespace Discord.Rest var vanityModel = await client.ApiClient.GetVanityInviteAsync(guild.Id, options).ConfigureAwait(false); if (vanityModel == null) throw new InvalidOperationException("This guild does not have a vanity URL."); var inviteModel = await client.ApiClient.GetInviteAsync(vanityModel.Code, options).ConfigureAwait(false); + inviteModel.Uses = vanityModel.Uses; return RestInviteMetadata.Create(client, guild, null, inviteModel); } @@ -320,7 +300,7 @@ namespace Discord.Rest Hoist = isHoisted, Mentionable = isMentionable, Name = name, - Permissions = permissions?.RawValue ?? Optional.Create() + Permissions = permissions?.RawValue.ToString() ?? Optional.Create() }; var model = await client.ApiClient.CreateGuildRoleAsync(guild.Id, createGuildRoleParams, options).ConfigureAwait(false); diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs index 57918a1e7..ea703a26a 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs @@ -6,7 +6,6 @@ using System.Diagnostics; using System.Globalization; using System.Linq; using System.Threading.Tasks; -using EmbedModel = Discord.API.GuildEmbed; using WidgetModel = Discord.API.GuildWidget; using Model = Discord.API.Guild; @@ -27,8 +26,6 @@ namespace Discord.Rest /// public int AFKTimeout { get; private set; } /// - public bool IsEmbeddable { get; private set; } - /// public bool IsWidgetEnabled { get; private set; } /// public VerificationLevel VerificationLevel { get; private set; } @@ -42,8 +39,6 @@ namespace Discord.Rest /// public ulong? AFKChannelId { get; private set; } /// - public ulong? EmbedChannelId { get; private set; } - /// public ulong? WidgetChannelId { get; private set; } /// public ulong? SystemChannelId { get; private set; } @@ -95,8 +90,6 @@ namespace Discord.Rest /// public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); - [Obsolete("DefaultChannelId is deprecated, use GetDefaultChannelAsync")] - public ulong DefaultChannelId => Id; /// public string IconUrl => CDN.GetGuildIconUrl(Id, IconId); /// @@ -133,16 +126,12 @@ namespace Discord.Rest internal void Update(Model model) { AFKChannelId = model.AFKChannelId; - if (model.EmbedChannelId.IsSpecified) - EmbedChannelId = model.EmbedChannelId.Value; if (model.WidgetChannelId.IsSpecified) WidgetChannelId = model.WidgetChannelId.Value; SystemChannelId = model.SystemChannelId; RulesChannelId = model.RulesChannelId; PublicUpdatesChannelId = model.PublicUpdatesChannelId; AFKTimeout = model.AFKTimeout; - if (model.EmbedEnabled.IsSpecified) - IsEmbeddable = model.EmbedEnabled.Value; if (model.WidgetEnabled.IsSpecified) IsWidgetEnabled = model.WidgetEnabled.Value; IconId = model.Icon; @@ -200,11 +189,6 @@ namespace Discord.Rest Available = true; } - internal void Update(EmbedModel model) - { - EmbedChannelId = model.ChannelId; - IsEmbeddable = model.Enabled; - } internal void Update(WidgetModel model) { WidgetChannelId = model.ChannelId; @@ -241,15 +225,6 @@ namespace Discord.Rest Update(model); } - /// - /// is . - [Obsolete("This endpoint is deprecated, use ModifyWidgetAsync instead.")] - public async Task ModifyEmbedAsync(Action func, RequestOptions options = null) - { - var model = await GuildHelper.ModifyEmbedAsync(this, Discord, func, options).ConfigureAwait(false); - Update(model); - } - /// /// is . public async Task ModifyWidgetAsync(Action func, RequestOptions options = null) @@ -463,23 +438,6 @@ namespace Discord.Rest .FirstOrDefault(); } - /// - /// Gets the embed channel (i.e. the channel set in the guild's widget settings) in this guild. - /// - /// The options to be used when sending the request. - /// - /// A task that represents the asynchronous get operation. The task result contains the embed channel set - /// within the server's widget settings; if none is set. - /// - [Obsolete("This endpoint is deprecated, use GetWidgetChannelAsync instead.")] - public async Task GetEmbedChannelAsync(RequestOptions options = null) - { - var embedId = EmbedChannelId; - if (embedId.HasValue) - return await GuildHelper.GetChannelAsync(this, Discord, embedId.Value, options).ConfigureAwait(false); - return null; - } - /// /// Gets the widget channel (i.e. the channel set in the guild's widget settings) in this guild. /// @@ -937,15 +895,6 @@ namespace Discord.Rest return null; } /// - [Obsolete("This endpoint is deprecated, use GetWidgetChannelAsync instead.")] - async Task IGuild.GetEmbedChannelAsync(CacheMode mode, RequestOptions options) - { - if (mode == CacheMode.AllowDownload) - return await GetEmbedChannelAsync(options).ConfigureAwait(false); - else - return null; - } - /// async Task IGuild.GetWidgetChannelAsync(CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuildEmbed.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuildEmbed.cs deleted file mode 100644 index 41c76eb06..000000000 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuildEmbed.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Diagnostics; -using Model = Discord.API.GuildEmbed; - -namespace Discord.Rest -{ - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public struct RestGuildEmbed - { - public bool IsEnabled { get; private set; } - public ulong? ChannelId { get; private set; } - - internal RestGuildEmbed(bool isEnabled, ulong? channelId) - { - ChannelId = channelId; - IsEnabled = isEnabled; - } - internal static RestGuildEmbed Create(Model model) - { - return new RestGuildEmbed(model.Enabled, model.ChannelId); - } - - public override string ToString() => ChannelId?.ToString() ?? "Unknown"; - private string DebuggerDisplay => $"{ChannelId} ({(IsEnabled ? "Enabled" : "Disabled")})"; - } -} diff --git a/src/Discord.Net.Rest/Entities/Invites/RestInviteMetadata.cs b/src/Discord.Net.Rest/Entities/Invites/RestInviteMetadata.cs index 07ee5851c..a0ed9ec81 100644 --- a/src/Discord.Net.Rest/Entities/Invites/RestInviteMetadata.cs +++ b/src/Discord.Net.Rest/Entities/Invites/RestInviteMetadata.cs @@ -8,9 +8,6 @@ namespace Discord.Rest { private long _createdAtTicks; - /// - [Obsolete("This property doesn't exist anymore and shouldn't be used.")] - public bool IsRevoked { get; private set; } /// public bool IsTemporary { get; private set; } /// diff --git a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs index 1bc284836..ad398fe8a 100644 --- a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs +++ b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs @@ -122,13 +122,9 @@ namespace Discord.Rest await client.ApiClient.DeleteMessageAsync(channelId, msgId, options).ConfigureAwait(false); } - public static async Task SuppressEmbedsAsync(IMessage msg, BaseDiscordClient client, bool suppress, RequestOptions options) + public static async Task AddReactionAsync(ulong channelId, ulong messageId, IEmote emote, BaseDiscordClient client, RequestOptions options) { - var apiArgs = new API.Rest.SuppressEmbedParams - { - Suppressed = suppress - }; - await client.ApiClient.SuppressEmbedAsync(msg.Channel.Id, msg.Id, apiArgs, options).ConfigureAwait(false); + await client.ApiClient.AddReactionAsync(channelId, messageId, emote is Emote e ? $"{e.Name}:{e.Id}" : UrlEncode(emote.Name), options).ConfigureAwait(false); } public static async Task AddReactionAsync(ulong channelId, ulong messageId, IEmote emote, BaseDiscordClient client, RequestOptions options) diff --git a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs index 1274f1fd3..aa6b44da6 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs @@ -164,9 +164,6 @@ namespace Discord.Rest /// public Task UnpinAsync(RequestOptions options = null) => MessageHelper.UnpinAsync(this, Discord, options); - /// - public Task ModifySuppressionAsync(bool suppressEmbeds, RequestOptions options = null) - => MessageHelper.SuppressEmbedsAsync(this, Discord, suppressEmbeds, options); public string Resolve(int startIndex, TagHandling userHandling = TagHandling.Name, TagHandling channelHandling = TagHandling.Name, TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, TagHandling emojiHandling = TagHandling.Name) diff --git a/src/Discord.Net.Rest/Entities/Roles/RoleHelper.cs b/src/Discord.Net.Rest/Entities/Roles/RoleHelper.cs index d570f078b..73ab7ca31 100644 --- a/src/Discord.Net.Rest/Entities/Roles/RoleHelper.cs +++ b/src/Discord.Net.Rest/Entities/Roles/RoleHelper.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading.Tasks; using Model = Discord.API.Role; using BulkParams = Discord.API.Rest.ModifyGuildRolesParams; @@ -24,7 +24,7 @@ namespace Discord.Rest Hoist = args.Hoist, Mentionable = args.Mentionable, Name = args.Name, - Permissions = args.Permissions.IsSpecified ? args.Permissions.Value.RawValue : Optional.Create() + Permissions = args.Permissions.IsSpecified ? args.Permissions.Value.RawValue.ToString() : Optional.Create() }; var model = await client.ApiClient.ModifyGuildRoleAsync(role.Guild.Id, role.Id, apiArgs, options).ConfigureAwait(false); diff --git a/src/Discord.Net.Rest/Entities/Users/RestUser.cs b/src/Discord.Net.Rest/Entities/Users/RestUser.cs index 131a4ec73..7bc1447fe 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestUser.cs @@ -79,13 +79,13 @@ namespace Discord.Rest } /// - /// Returns a direct message channel to this user, or create one if it does not already exist. + /// Creates a direct message channel to this user. /// /// The options to be used when sending the request. /// /// A task that represents the asynchronous get operation. The task result contains a rest DM channel where the user is the recipient. /// - public Task GetOrCreateDMChannelAsync(RequestOptions options = null) + public Task CreateDMChannelAsync(RequestOptions options = null) => UserHelper.CreateDMChannelAsync(this, Discord, options); /// @@ -107,7 +107,7 @@ namespace Discord.Rest //IUser /// - async Task IUser.GetOrCreateDMChannelAsync(RequestOptions options) - => await GetOrCreateDMChannelAsync(options).ConfigureAwait(false); + async Task IUser.CreateDMChannelAsync(RequestOptions options) + => await CreateDMChannelAsync(options).ConfigureAwait(false); } } diff --git a/src/Discord.Net.Rest/Net/Converters/DiscordContractResolver.cs b/src/Discord.Net.Rest/Net/Converters/DiscordContractResolver.cs index a1ed20c6f..931c0c4c9 100644 --- a/src/Discord.Net.Rest/Net/Converters/DiscordContractResolver.cs +++ b/src/Discord.Net.Rest/Net/Converters/DiscordContractResolver.cs @@ -73,8 +73,6 @@ namespace Discord.Net.Converters } //Enums - if (type == typeof(PermissionTarget)) - return PermissionTargetConverter.Instance; if (type == typeof(UserStatus)) return UserStatusConverter.Instance; if (type == typeof(EmbedType)) diff --git a/src/Discord.Net.Rest/Net/Converters/PermissionTargetConverter.cs b/src/Discord.Net.Rest/Net/Converters/PermissionTargetConverter.cs deleted file mode 100644 index de2e379d7..000000000 --- a/src/Discord.Net.Rest/Net/Converters/PermissionTargetConverter.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Newtonsoft.Json; -using System; - -namespace Discord.Net.Converters -{ - internal class PermissionTargetConverter : JsonConverter - { - public static readonly PermissionTargetConverter Instance = new PermissionTargetConverter(); - - public override bool CanConvert(Type objectType) => true; - public override bool CanRead => true; - public override bool CanWrite => true; - - /// Unknown permission target. - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - switch ((string)reader.Value) - { - case "member": - return PermissionTarget.User; - case "role": - return PermissionTarget.Role; - default: - throw new JsonSerializationException("Unknown permission target."); - } - } - - /// Invalid permission target. - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - switch ((PermissionTarget)value) - { - case PermissionTarget.User: - writer.WriteValue("member"); - break; - case PermissionTarget.Role: - writer.WriteValue("role"); - break; - default: - throw new JsonSerializationException("Invalid permission target."); - } - } - } -} diff --git a/src/Discord.Net.WebSocket/API/Gateway/IdentifyParams.cs b/src/Discord.Net.WebSocket/API/Gateway/IdentifyParams.cs index 92a494b71..bb54d4cdd 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/IdentifyParams.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/IdentifyParams.cs @@ -17,8 +17,6 @@ namespace Discord.API.Gateway public Optional ShardingParams { get; set; } [JsonProperty("presence")] public Optional Presence { 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/Reaction.cs b/src/Discord.Net.WebSocket/API/Gateway/Reaction.cs index a0a740868..0d17cbff8 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/Reaction.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/Reaction.cs @@ -10,6 +10,8 @@ namespace Discord.API.Gateway public ulong MessageId { get; set; } [JsonProperty("channel_id")] public ulong ChannelId { get; set; } + [JsonProperty("guild_id")] + public Optional GuildId { get; set; } [JsonProperty("emoji")] public Emoji Emoji { get; set; } [JsonProperty("member")] diff --git a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs index 966aec7fa..fcaa793fc 100644 --- a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs +++ b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs @@ -124,11 +124,11 @@ namespace Discord.WebSocket /// /// - public event Func, ISocketMessageChannel, Task> MessageDeleted { + public event Func, Cacheable, Task> MessageDeleted { add { _messageDeletedEvent.Add(value); } remove { _messageDeletedEvent.Remove(value); } } - internal readonly AsyncEvent, ISocketMessageChannel, Task>> _messageDeletedEvent = new AsyncEvent, ISocketMessageChannel, Task>>(); + internal readonly AsyncEvent, Cacheable, Task>> _messageDeletedEvent = new AsyncEvent, Cacheable, Task>>(); /// Fired when multiple messages are bulk deleted. /// /// @@ -155,12 +155,12 @@ namespace Discord.WebSocket /// parameter. /// /// - public event Func>, ISocketMessageChannel, Task> MessagesBulkDeleted + public event Func>, Cacheable, Task> MessagesBulkDeleted { add { _messagesBulkDeletedEvent.Add(value); } remove { _messagesBulkDeletedEvent.Remove(value); } } - internal readonly AsyncEvent>, ISocketMessageChannel, Task>> _messagesBulkDeletedEvent = new AsyncEvent>, ISocketMessageChannel, Task>>(); + internal readonly AsyncEvent>, Cacheable, Task>> _messagesBulkDeletedEvent = new AsyncEvent>, Cacheable, Task>>(); /// Fired when a message is updated. /// /// @@ -217,23 +217,23 @@ namespace Discord.WebSocket /// /// - public event Func, ISocketMessageChannel, SocketReaction, Task> ReactionAdded { + public event Func, Cacheable, SocketReaction, Task> ReactionAdded { add { _reactionAddedEvent.Add(value); } remove { _reactionAddedEvent.Remove(value); } } - internal readonly AsyncEvent, ISocketMessageChannel, SocketReaction, Task>> _reactionAddedEvent = new AsyncEvent, ISocketMessageChannel, SocketReaction, Task>>(); + internal readonly AsyncEvent, Cacheable, SocketReaction, Task>> _reactionAddedEvent = new AsyncEvent, Cacheable, SocketReaction, Task>>(); /// Fired when a reaction is removed from a message. - public event Func, ISocketMessageChannel, SocketReaction, Task> ReactionRemoved { + public event Func, Cacheable, SocketReaction, Task> ReactionRemoved { add { _reactionRemovedEvent.Add(value); } remove { _reactionRemovedEvent.Remove(value); } } - internal readonly AsyncEvent, ISocketMessageChannel, SocketReaction, Task>> _reactionRemovedEvent = new AsyncEvent, ISocketMessageChannel, SocketReaction, Task>>(); + internal readonly AsyncEvent, Cacheable, SocketReaction, Task>> _reactionRemovedEvent = new AsyncEvent, Cacheable, SocketReaction, Task>>(); /// Fired when all reactions to a message are cleared. - public event Func, ISocketMessageChannel, Task> ReactionsCleared { + public event Func, Cacheable, Task> ReactionsCleared { add { _reactionsClearedEvent.Add(value); } remove { _reactionsClearedEvent.Remove(value); } } - internal readonly AsyncEvent, ISocketMessageChannel, Task>> _reactionsClearedEvent = new AsyncEvent, ISocketMessageChannel, Task>>(); + internal readonly AsyncEvent, Cacheable, Task>> _reactionsClearedEvent = new AsyncEvent, Cacheable, Task>>(); /// /// Fired when all reactions to a message with a specific emote are removed. /// @@ -250,12 +250,12 @@ namespace Discord.WebSocket /// The emoji that all reactions had and were removed will be passed into the parameter. /// /// - public event Func, ISocketMessageChannel, IEmote, Task> ReactionsRemovedForEmote + public event Func, Cacheable, IEmote, Task> ReactionsRemovedForEmote { add { _reactionsRemovedForEmoteEvent.Add(value); } remove { _reactionsRemovedForEmoteEvent.Remove(value); } } - internal readonly AsyncEvent, ISocketMessageChannel, IEmote, Task>> _reactionsRemovedForEmoteEvent = new AsyncEvent, ISocketMessageChannel, IEmote, Task>>(); + internal readonly AsyncEvent, Cacheable, IEmote, Task>> _reactionsRemovedForEmoteEvent = new AsyncEvent, Cacheable, IEmote, Task>>(); //Roles /// Fired when a role is created. @@ -347,11 +347,11 @@ namespace Discord.WebSocket } internal readonly AsyncEvent> _userUpdatedEvent = new AsyncEvent>(); /// Fired when a guild member is updated, or a member presence is updated. - public event Func GuildMemberUpdated { + public event Func, SocketGuildUser, Task> GuildMemberUpdated { add { _guildMemberUpdatedEvent.Add(value); } remove { _guildMemberUpdatedEvent.Remove(value); } } - internal readonly AsyncEvent> _guildMemberUpdatedEvent = new AsyncEvent>(); + internal readonly AsyncEvent, SocketGuildUser, Task>> _guildMemberUpdatedEvent = new AsyncEvent, SocketGuildUser, Task>>(); /// Fired when a user joins, leaves, or moves voice channels. public event Func UserVoiceStateUpdated { add { _userVoiceStateUpdatedEvent.Add(value); } @@ -372,11 +372,11 @@ namespace Discord.WebSocket } internal readonly AsyncEvent> _selfUpdatedEvent = new AsyncEvent>(); /// Fired when a user starts typing. - public event Func UserIsTyping { + public event Func, Cacheable, Task> UserIsTyping { add { _userIsTypingEvent.Add(value); } remove { _userIsTypingEvent.Remove(value); } } - internal readonly AsyncEvent> _userIsTypingEvent = new AsyncEvent>(); + internal readonly AsyncEvent, Cacheable, Task>> _userIsTypingEvent = new AsyncEvent, Cacheable, Task>>(); /// Fired when a user joins a group channel. public event Func RecipientAdded { add { _recipientAddedEvent.Add(value); } diff --git a/src/Discord.Net.WebSocket/BaseSocketClient.cs b/src/Discord.Net.WebSocket/BaseSocketClient.cs index 36e6c02a9..1cfe6c8bf 100644 --- a/src/Discord.Net.WebSocket/BaseSocketClient.cs +++ b/src/Discord.Net.WebSocket/BaseSocketClient.cs @@ -47,7 +47,7 @@ namespace Discord.WebSocket /// /// Gets the current logged-in user. /// - public new SocketSelfUser CurrentUser { get => base.CurrentUser as SocketSelfUser; protected set => base.CurrentUser = value; } + public virtual new SocketSelfUser CurrentUser { get => base.CurrentUser as SocketSelfUser; protected set => base.CurrentUser = value; } /// /// Gets a collection of guilds that the user is currently in. /// @@ -70,20 +70,11 @@ namespace Discord.WebSocket /// A read-only collection of private channels that the user currently partakes in. /// public abstract IReadOnlyCollection PrivateChannels { get; } - /// - /// Gets a collection of available voice regions. - /// - /// - /// A read-only collection of voice regions that the user has access to. - /// - [Obsolete("This property is obsolete, use the GetVoiceRegionsAsync method instead.")] - public abstract IReadOnlyCollection VoiceRegions { get; } internal BaseSocketClient(DiscordSocketConfig config, DiscordRestApiClient client) : base(config, client) => BaseConfig = config; private static DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) => new DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent, - rateLimitPrecision: config.RateLimitPrecision, useSystemClock: config.UseSystemClock); /// @@ -164,16 +155,6 @@ namespace Discord.WebSocket /// public abstract SocketGuild GetGuild(ulong id); /// - /// Gets a voice region. - /// - /// The identifier of the voice region (e.g. eu-central ). - /// - /// A REST-based voice region associated with the identifier; null if the voice region is not - /// found. - /// - [Obsolete("This method is obsolete, use GetVoiceRegionAsync instead.")] - public abstract RestVoiceRegion GetVoiceRegion(string id); - /// /// Gets all voice regions. /// /// The options to be used when sending the request. @@ -327,10 +308,14 @@ namespace Discord.WebSocket => Task.FromResult(GetUser(username, discriminator)); /// - Task IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options) - => Task.FromResult(GetVoiceRegion(id)); + async Task IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options) + { + return await GetVoiceRegionAsync(id).ConfigureAwait(false); + } /// - Task> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options) - => Task.FromResult>(VoiceRegions); + async Task> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options) + { + return await GetVoiceRegionsAsync().ConfigureAwait(false); + } } } diff --git a/src/Discord.Net.WebSocket/DiscordShardedClient.cs b/src/Discord.Net.WebSocket/DiscordShardedClient.cs index 4c94b14e8..e6d05b525 100644 --- a/src/Discord.Net.WebSocket/DiscordShardedClient.cs +++ b/src/Discord.Net.WebSocket/DiscordShardedClient.cs @@ -36,14 +36,13 @@ namespace Discord.WebSocket /// public override IReadOnlyCollection PrivateChannels => GetPrivateChannels().ToReadOnlyCollection(GetPrivateChannelCount); public IReadOnlyCollection Shards => _shards; - /// - [Obsolete("This property is obsolete, use the GetVoiceRegionsAsync method instead.")] - public override IReadOnlyCollection VoiceRegions => _shards[0].VoiceRegions; /// /// Provides access to a REST-only client with a shared state from this client. /// - public override DiscordSocketRestClient Rest => _shards[0].Rest; + public override DiscordSocketRestClient Rest => _shards?[0].Rest; + + public override SocketSelfUser CurrentUser { get => _shards?.FirstOrDefault(x => x.CurrentUser != null)?.CurrentUser; protected set => throw new InvalidOperationException(); } /// Creates a new REST/WebSocket Discord client. public DiscordShardedClient() : this(null, new DiscordSocketConfig()) { } @@ -91,8 +90,7 @@ namespace Discord.WebSocket } } private static API.DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) - => new API.DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent, - rateLimitPrecision: config.RateLimitPrecision); + => new API.DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent); internal async Task AcquireIdentifyLockAsync(int shardId, CancellationToken token) { @@ -264,11 +262,6 @@ namespace Discord.WebSocket return null; } - /// - [Obsolete("This method is obsolete, use GetVoiceRegionAsync instead.")] - public override RestVoiceRegion GetVoiceRegion(string id) - => _shards[0].GetVoiceRegion(id); - /// public override async ValueTask> GetVoiceRegionsAsync(RequestOptions options = null) { @@ -339,14 +332,6 @@ namespace Discord.WebSocket } return Task.Delay(0); }; - if (isPrimary) - { - client.Ready += () => - { - CurrentUser = client.CurrentUser; - return Task.Delay(0); - }; - } client.Connected += () => _shardConnectedEvent.InvokeAsync(client); client.Disconnected += (exception) => _shardDisconnectedEvent.InvokeAsync(exception, client); @@ -432,11 +417,15 @@ namespace Discord.WebSocket => Task.FromResult(GetUser(username, discriminator)); /// - Task> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options) - => Task.FromResult>(VoiceRegions); + async Task> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options) + { + return await GetVoiceRegionsAsync().ConfigureAwait(false); + } /// - Task IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options) - => Task.FromResult(GetVoiceRegion(id)); + async Task IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options) + { + return await GetVoiceRegionAsync(id).ConfigureAwait(false); + } internal override void Dispose(bool disposing) { diff --git a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs index d14a314d6..d1407da01 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs @@ -40,9 +40,8 @@ namespace Discord.API public DiscordSocketApiClient(RestClientProvider restClientProvider, WebSocketProvider webSocketProvider, string userAgent, string url = null, RetryMode defaultRetryMode = RetryMode.AlwaysRetry, JsonSerializer serializer = null, - RateLimitPrecision rateLimitPrecision = RateLimitPrecision.Second, bool useSystemClock = true) - : base(restClientProvider, userAgent, defaultRetryMode, serializer, rateLimitPrecision, useSystemClock) + : base(restClientProvider, userAgent, defaultRetryMode, serializer, useSystemClock) { _gatewayUrl = url; if (url != null) @@ -216,7 +215,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, GatewayIntents? gatewayIntents = null, (UserStatus, bool, long?, GameModel)? presence = null, RequestOptions options = null) + public async Task SendIdentifyAsync(int largeThreshold = 100, int shardID = 0, int totalShards = 1, GatewayIntents gatewayIntents = GatewayIntents.AllUnprivileged, (UserStatus, bool, long?, GameModel)? presence = null, RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); var props = new Dictionary @@ -234,10 +233,7 @@ namespace Discord.API options.BucketId = GatewayBucket.Get(GatewayBucketType.Identify).Id; - if (gatewayIntents.HasValue) - msg.Intents = (int)gatewayIntents.Value; - else - msg.GuildSubscriptions = guildSubscriptions; + msg.Intents = (int)gatewayIntents; if (presence.HasValue) { diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index e284fd883..888055da9 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -43,8 +43,7 @@ namespace Discord.WebSocket private DateTimeOffset? _statusSince; private RestApplication _applicationInfo; private bool _isDisposed; - private bool _guildSubscriptions; - private GatewayIntents? _gatewayIntents; + private GatewayIntents _gatewayIntents; /// /// Provides access to a REST-only client with a shared state from this client. @@ -72,7 +71,6 @@ namespace Discord.WebSocket internal WebSocketProvider WebSocketProvider { get; private set; } internal bool AlwaysDownloadUsers { get; private set; } internal int? HandlerTimeout { get; private set; } - internal bool? ExclusiveBulkDelete { get; private set; } internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; /// @@ -109,9 +107,6 @@ namespace Discord.WebSocket /// public IReadOnlyCollection GroupChannels => State.PrivateChannels.OfType().ToImmutableArray(); - /// - [Obsolete("This property is obsolete, use the GetVoiceRegionsAsync method instead.")] - public override IReadOnlyCollection VoiceRegions => GetVoiceRegionsAsync().GetAwaiter().GetResult(); /// /// Initializes a new REST/WebSocket-based Discord client. @@ -136,11 +131,9 @@ namespace Discord.WebSocket WebSocketProvider = config.WebSocketProvider; AlwaysDownloadUsers = config.AlwaysDownloadUsers; HandlerTimeout = config.HandlerTimeout; - ExclusiveBulkDelete = config.ExclusiveBulkDelete; State = new ClientState(0, 0); Rest = new DiscordSocketRestClient(config, ApiClient); _heartbeatTimes = new ConcurrentQueue(); - _guildSubscriptions = config.GuildSubscriptions; _gatewayIntents = config.GatewayIntents; _stateLock = new SemaphoreSlim(1, 1); @@ -182,8 +175,7 @@ namespace Discord.WebSocket _largeGuilds = new ConcurrentQueue(); } private static API.DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) - => new API.DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent, config.GatewayHost, - rateLimitPrecision: config.RateLimitPrecision); + => new API.DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent, config.GatewayHost); /// internal override void Dispose(bool disposing) { @@ -201,11 +193,6 @@ namespace Discord.WebSocket base.Dispose(disposing); } - /// - internal override async Task OnLoginAsync(TokenType tokenType, string token) - { - await Rest.OnLoginAsync(tokenType, token); - } /// internal override async Task OnLogoutAsync() { @@ -243,7 +230,7 @@ namespace Discord.WebSocket else { await _gatewayLogger.DebugAsync("Identifying").ConfigureAwait(false); - await ApiClient.SendIdentifyAsync(shardID: ShardId, totalShards: TotalShards, guildSubscriptions: _guildSubscriptions, gatewayIntents: _gatewayIntents, presence: BuildCurrentStatus()).ConfigureAwait(false); + await ApiClient.SendIdentifyAsync(shardID: ShardId, totalShards: TotalShards, gatewayIntents: _gatewayIntents, presence: BuildCurrentStatus()).ConfigureAwait(false); } } finally @@ -302,13 +289,51 @@ namespace Discord.WebSocket public override SocketChannel GetChannel(ulong id) => State.GetChannel(id); /// + /// Gets a generic channel from the cache or does a rest request if unavailable. + /// + /// + /// + /// var channel = await _client.GetChannelAsync(381889909113225237); + /// if (channel != null && channel is IMessageChannel msgChannel) + /// { + /// await msgChannel.SendMessageAsync($"{msgChannel} is created at {msgChannel.CreatedAt}"); + /// } + /// + /// + /// The snowflake identifier of the channel (e.g. `381889909113225237`). + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains the channel associated + /// with the snowflake identifier; null when the channel cannot be found. + /// + public async ValueTask GetChannelAsync(ulong id, RequestOptions options = null) + => GetChannel(id) ?? (IChannel)await ClientHelper.GetChannelAsync(this, id, options).ConfigureAwait(false); + /// + /// Gets a user from the cache or does a rest request if unavailable. + /// + /// + /// + /// var user = await _client.GetUserAsync(168693960628371456); + /// if (user != null) + /// Console.WriteLine($"{user} is created at {user.CreatedAt}."; + /// + /// + /// The snowflake identifier of the user (e.g. `168693960628371456`). + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains the user associated with + /// the snowflake identifier; null if the user is not found. + /// + public async ValueTask GetUserAsync(ulong id, RequestOptions options = null) + => await ClientHelper.GetUserAsync(this, id, options).ConfigureAwait(false); + /// /// Clears all cached channels from the client. /// public void PurgeChannelCache() => State.PurgeAllChannels(); /// /// Clears cached DM channels from the client. /// - public void PurgeDMChannelCache() => State.PurgeDMChannels(); + public void PurgeDMChannelCache() => RemoveDMChannels(); /// public override SocketUser GetUser(ulong id) @@ -322,12 +347,11 @@ namespace Discord.WebSocket public void PurgeUserCache() => State.PurgeUsers(); internal SocketGlobalUser GetOrCreateUser(ClientState state, Discord.API.User model) { - return state.GetOrAddUser(model.Id, x => - { - var user = SocketGlobalUser.Create(this, state, model); - user.GlobalUser.AddRef(); - return user; - }); + return state.GetOrAddUser(model.Id, x => SocketGlobalUser.Create(this, state, model)); + } + internal SocketUser GetOrCreateTemporaryUser(ClientState state, Discord.API.User model) + { + return state.GetUser(model.Id) ?? (SocketUser)SocketUnknownUser.Create(this, state, model); } internal SocketGlobalUser GetOrCreateSelfUser(ClientState state, Discord.API.User model) { @@ -335,18 +359,13 @@ namespace Discord.WebSocket { var user = SocketGlobalUser.Create(this, state, model); user.GlobalUser.AddRef(); - user.Presence = new SocketPresence(UserStatus.Online, null, null, null); + user.Presence = new SocketPresence(UserStatus.Online, null, null); return user; }); } internal void RemoveUser(ulong id) => State.RemoveUser(id); - /// - [Obsolete("This method is obsolete, use GetVoiceRegionAsync instead.")] - public override RestVoiceRegion GetVoiceRegion(string id) - => GetVoiceRegionAsync(id).GetAwaiter().GetResult(); - /// public override async ValueTask> GetVoiceRegionsAsync(RequestOptions options = null) { @@ -469,7 +488,8 @@ namespace Discord.WebSocket { if (CurrentUser == null) return; - CurrentUser.Presence = new SocketPresence(Status, Activity, null, null); + var activities = _activity.IsSpecified ? ImmutableList.Create(_activity.Value) : null; + CurrentUser.Presence = new SocketPresence(Status, null, activities); var presence = BuildCurrentStatus() ?? (UserStatus.Online, false, null, null); @@ -564,7 +584,7 @@ namespace Discord.WebSocket await _shardedClient.AcquireIdentifyLockAsync(ShardId, _connection.CancelToken).ConfigureAwait(false); try { - await ApiClient.SendIdentifyAsync(shardID: ShardId, totalShards: TotalShards, guildSubscriptions: _guildSubscriptions, gatewayIntents: _gatewayIntents, presence: BuildCurrentStatus()).ConfigureAwait(false); + await ApiClient.SendIdentifyAsync(shardID: ShardId, totalShards: TotalShards, gatewayIntents: _gatewayIntents, presence: BuildCurrentStatus()).ConfigureAwait(false); } finally { @@ -572,7 +592,7 @@ namespace Discord.WebSocket } } else - await ApiClient.SendIdentifyAsync(shardID: ShardId, totalShards: TotalShards, guildSubscriptions: _guildSubscriptions, gatewayIntents: _gatewayIntents, presence: BuildCurrentStatus()).ConfigureAwait(false); + await ApiClient.SendIdentifyAsync(shardID: ShardId, totalShards: TotalShards, gatewayIntents: _gatewayIntents, presence: BuildCurrentStatus()).ConfigureAwait(false); } break; case GatewayOpCode.Reconnect: @@ -595,7 +615,8 @@ namespace Discord.WebSocket var state = new ClientState(data.Guilds.Length, data.PrivateChannels.Length); var currentUser = SocketSelfUser.Create(this, state, data.User); - currentUser.Presence = new SocketPresence(Status, Activity, null, null); + var activities = _activity.IsSpecified ? ImmutableList.Create(_activity.Value) : null; + currentUser.Presence = new SocketPresence(Status, null, activities); ApiClient.CurrentUserId = currentUser.Id; int unavailableGuilds = 0; for (int i = 0; i < data.Guilds.Length; i++) @@ -749,7 +770,8 @@ namespace Discord.WebSocket break; case "GUILD_SYNC": { - await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_SYNC)").ConfigureAwait(false); + await _gatewayLogger.DebugAsync("Ignored Dispatch (GUILD_SYNC)").ConfigureAwait(false); + /*await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_SYNC)").ConfigureAwait(false); //TODO remove? userbot related var data = (payload as JToken).ToObject(_serializer); var guild = State.GetGuild(data.Id); if (guild != null) @@ -766,7 +788,7 @@ namespace Discord.WebSocket { await UnknownGuildAsync(type, data.Id).ConfigureAwait(false); return; - } + }*/ } break; case "GUILD_DELETE": @@ -966,15 +988,15 @@ namespace Discord.WebSocket var before = user.Clone(); user.Update(State, data); - await TimedInvokeAsync(_guildMemberUpdatedEvent, nameof(GuildMemberUpdated), before, user).ConfigureAwait(false); + + var cacheableBefore = new Cacheable(before, user.Id, true, () => Task.FromResult((SocketGuildUser)null)); + await TimedInvokeAsync(_guildMemberUpdatedEvent, nameof(GuildMemberUpdated), cacheableBefore, user).ConfigureAwait(false); } else { - if (!guild.HasAllMembers) - await IncompleteGuildUserAsync(type, data.User.Id, data.GuildId).ConfigureAwait(false); - else - await UnknownGuildUserAsync(type, data.User.Id, data.GuildId).ConfigureAwait(false); - return; + user = guild.AddOrUpdateUser(data); + var cacheableBefore = new Cacheable(null, user.Id, false, () => Task.FromResult((SocketGuildUser)null)); + await TimedInvokeAsync(_guildMemberUpdatedEvent, nameof(GuildMemberUpdated), cacheableBefore, user).ConfigureAwait(false); } } else @@ -1237,56 +1259,63 @@ namespace Discord.WebSocket await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_CREATE)").ConfigureAwait(false); var data = (payload as JToken).ToObject(_serializer); - if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) + var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; + + var guild = (channel as SocketGuildChannel)?.Guild; + if (guild != null && !guild.IsSynced) { - var guild = (channel as SocketGuildChannel)?.Guild; - 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 { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); - return; + channel = CreateDMChannel(data.ChannelId, data.Author.Value, State); } - - SocketUser author; - if (guild != null) + else { - if (data.WebhookId.IsSpecified) - author = SocketWebhookUser.Create(guild, State, data.Author.Value, data.WebhookId.Value); - else - author = guild.GetUser(data.Author.Value.Id); + await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); + return; } + } + + SocketUser author; + if (guild != null) + { + if (data.WebhookId.IsSpecified) + author = SocketWebhookUser.Create(guild, State, data.Author.Value, data.WebhookId.Value); else - author = (channel as SocketChannel).GetUser(data.Author.Value.Id); + author = guild.GetUser(data.Author.Value.Id); + } + else + author = (channel as SocketChannel).GetUser(data.Author.Value.Id); - if (author == null) + if (author == null) + { + if (guild != null) { - if (guild != null) + if (data.Member.IsSpecified) // member isn't always included, but use it when we can { - if (data.Member.IsSpecified) // member isn't always included, but use it when we can - { - data.Member.Value.User = data.Author.Value; - author = guild.AddOrUpdateUser(data.Member.Value); - } - else - author = guild.AddOrUpdateUser(data.Author.Value); // user has no guild-specific data + data.Member.Value.User = data.Author.Value; + author = guild.AddOrUpdateUser(data.Member.Value); } - else if (channel is SocketGroupChannel) - author = (channel as SocketGroupChannel).GetOrAddUser(data.Author.Value); else - { - await UnknownChannelUserAsync(type, data.Author.Value.Id, channel.Id).ConfigureAwait(false); - return; - } + author = guild.AddOrUpdateUser(data.Author.Value); // user has no guild-specific data + } + else if (channel is SocketGroupChannel groupChannel) + author = groupChannel.GetOrAddUser(data.Author.Value); + else + { + await UnknownChannelUserAsync(type, data.Author.Value.Id, channel.Id).ConfigureAwait(false); + return; } - - var msg = SocketMessage.Create(this, State, author, channel, data); - SocketChannelHelper.AddMessage(channel, this, msg); - await TimedInvokeAsync(_messageReceivedEvent, nameof(MessageReceived), msg).ConfigureAwait(false); - } - else - { - await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); - return; } + + var msg = SocketMessage.Create(this, State, author, channel, data); + SocketChannelHelper.AddMessage(channel, this, msg); + await TimedInvokeAsync(_messageReceivedEvent, nameof(MessageReceived), msg).ConfigureAwait(false); } break; case "MESSAGE_UPDATE": @@ -1294,52 +1323,85 @@ namespace Discord.WebSocket await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_UPDATE)").ConfigureAwait(false); var data = (payload as JToken).ToObject(_serializer); - if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) + var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; + + var guild = (channel as SocketGuildChannel)?.Guild; + if (guild != null && !guild.IsSynced) { - var guild = (channel as SocketGuildChannel)?.Guild; - if (guild != null && !guild.IsSynced) - { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); - return; - } + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + return; + } - SocketMessage before = null, after = null; - SocketMessage cachedMsg = channel.GetCachedMessage(data.Id); - bool isCached = cachedMsg != null; - if (isCached) + SocketMessage before = null, after = null; + SocketMessage cachedMsg = channel?.GetCachedMessage(data.Id); + bool isCached = cachedMsg != null; + if (isCached) + { + before = cachedMsg.Clone(); + cachedMsg.Update(State, data); + after = cachedMsg; + } + else + { + //Edited message isnt in cache, create a detached one + SocketUser author; + if (data.Author.IsSpecified) { - before = cachedMsg.Clone(); - cachedMsg.Update(State, data); - after = cachedMsg; + if (guild != null) + { + if (data.WebhookId.IsSpecified) + author = SocketWebhookUser.Create(guild, State, data.Author.Value, data.WebhookId.Value); + else + author = guild.GetUser(data.Author.Value.Id); + } + else + author = (channel as SocketChannel)?.GetUser(data.Author.Value.Id); + + if (author == null) + { + if (guild != null) + { + if (data.Member.IsSpecified) // member isn't always included, but use it when we can + { + data.Member.Value.User = data.Author.Value; + author = guild.AddOrUpdateUser(data.Member.Value); + } + else + author = guild.AddOrUpdateUser(data.Author.Value); // user has no guild-specific data + } + else if (channel is SocketGroupChannel groupChannel) + author = groupChannel.GetOrAddUser(data.Author.Value); + } } else + // Message author wasn't specified in the payload, so create a completely anonymous unknown user + author = new SocketUnknownUser(this, id: 0); + + if (channel == null) { - //Edited message isnt in cache, create a detached one - SocketUser author; - if (data.Author.IsSpecified) + if (!data.GuildId.IsSpecified) // assume it is a DM { - if (guild != null) - author = guild.GetUser(data.Author.Value.Id); + if (data.Author.IsSpecified) + { + var dmChannel = CreateDMChannel(data.ChannelId, data.Author.Value, State); + channel = dmChannel; + author = dmChannel.Recipient; + } else - author = (channel as SocketChannel).GetUser(data.Author.Value.Id); - if (author == null) - author = SocketUnknownUser.Create(this, State, data.Author.Value); + channel = CreateDMChannel(data.ChannelId, author, State); } else - // Message author wasn't specified in the payload, so create a completely anonymous unknown user - author = new SocketUnknownUser(this, id: 0); - - after = SocketMessage.Create(this, State, author, channel, data); + { + await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); + return; + } } - var cacheableBefore = new Cacheable(before, data.Id, isCached, async () => await channel.GetMessageAsync(data.Id).ConfigureAwait(false)); - await TimedInvokeAsync(_messageUpdatedEvent, nameof(MessageUpdated), cacheableBefore, after, channel).ConfigureAwait(false); - } - else - { - await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); - return; + after = SocketMessage.Create(this, State, author, channel, data); } + var cacheableBefore = new Cacheable(before, data.Id, isCached, async () => await channel.GetMessageAsync(data.Id).ConfigureAwait(false)); + + await TimedInvokeAsync(_messageUpdatedEvent, nameof(MessageUpdated), cacheableBefore, after, channel).ConfigureAwait(false); } break; case "MESSAGE_DELETE": @@ -1347,26 +1409,22 @@ namespace Discord.WebSocket await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE)").ConfigureAwait(false); var data = (payload as JToken).ToObject(_serializer); - if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) - { - var guild = (channel as SocketGuildChannel)?.Guild; - if (!(guild?.IsSynced ?? true)) - { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); - return; - } - - var msg = SocketChannelHelper.RemoveMessage(channel, this, data.Id); - bool isCached = msg != null; - var cacheable = new Cacheable(msg, data.Id, isCached, async () => await channel.GetMessageAsync(data.Id).ConfigureAwait(false)); + var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; - await TimedInvokeAsync(_messageDeletedEvent, nameof(MessageDeleted), cacheable, channel).ConfigureAwait(false); - } - else + var guild = (channel as SocketGuildChannel)?.Guild; + if (!(guild?.IsSynced ?? true)) { - await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); return; } + + SocketMessage msg = null; + if (channel != null) + msg = SocketChannelHelper.RemoveMessage(channel, this, data.Id); + var cacheableMsg = new Cacheable(msg, data.Id, msg != null, () => Task.FromResult((IMessage)null)); + var cacheableChannel = new Cacheable(channel, data.ChannelId, channel != null, async () => await GetChannelAsync(data.ChannelId).ConfigureAwait(false) as IMessageChannel); + + await TimedInvokeAsync(_messageDeletedEvent, nameof(MessageDeleted), cacheableMsg, cacheableChannel).ConfigureAwait(false); } break; case "MESSAGE_REACTION_ADD": @@ -1374,40 +1432,43 @@ namespace Discord.WebSocket await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_ADD)").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 user = await channel.GetUserAsync(data.UserId, CacheMode.CacheOnly).ConfigureAwait(false); + var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; - var optionalMsg = !isCached - ? Optional.Create() - : Optional.Create(cachedMsg); - - if (data.Member.IsSpecified) - { - var guild = (channel as SocketGuildChannel)?.Guild; - - if (guild != null) - user = guild.AddOrUpdateUser(data.Member.Value); - } - - var optionalUser = user is null - ? Optional.Create() - : Optional.Create(user); + var cachedMsg = channel?.GetCachedMessage(data.MessageId) as SocketUserMessage; + bool isMsgCached = cachedMsg != null; + IUser user = null; + if (channel != null) + user = await channel.GetUserAsync(data.UserId, CacheMode.CacheOnly).ConfigureAwait(false); - var reaction = SocketReaction.Create(data, channel, optionalMsg, optionalUser); - var cacheable = new Cacheable(cachedMsg, data.MessageId, isCached, async () => await channel.GetMessageAsync(data.MessageId).ConfigureAwait(false) as IUserMessage); + var optionalMsg = !isMsgCached + ? Optional.Create() + : Optional.Create(cachedMsg); - cachedMsg?.AddReaction(reaction); + if (data.Member.IsSpecified) + { + var guild = (channel as SocketGuildChannel)?.Guild; - await TimedInvokeAsync(_reactionAddedEvent, nameof(ReactionAdded), cacheable, channel, reaction).ConfigureAwait(false); + if (guild != null) + user = guild.AddOrUpdateUser(data.Member.Value); } else + user = GetUser(data.UserId); + + var optionalUser = user is null + ? Optional.Create() + : Optional.Create(user); + + var cacheableChannel = new Cacheable(channel, data.ChannelId, channel != null, async () => await GetChannelAsync(data.ChannelId).ConfigureAwait(false) as IMessageChannel); + var cacheableMsg = new Cacheable(cachedMsg, data.MessageId, isMsgCached, async () => { - await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); - return; - } + var channelObj = await cacheableChannel.GetOrDownloadAsync().ConfigureAwait(false); + return await channelObj.GetMessageAsync(data.MessageId).ConfigureAwait(false) as IUserMessage; + }); + var reaction = SocketReaction.Create(data, channel, optionalMsg, optionalUser); + + cachedMsg?.AddReaction(reaction); + + await TimedInvokeAsync(_reactionAddedEvent, nameof(ReactionAdded), cacheableMsg, cacheableChannel, reaction).ConfigureAwait(false); } break; case "MESSAGE_REACTION_REMOVE": @@ -1415,32 +1476,35 @@ namespace Discord.WebSocket await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_REMOVE)").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 user = await channel.GetUserAsync(data.UserId, CacheMode.CacheOnly).ConfigureAwait(false); - - var optionalMsg = !isCached - ? Optional.Create() - : Optional.Create(cachedMsg); + var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; - var optionalUser = user is null - ? Optional.Create() - : Optional.Create(user); + var cachedMsg = channel?.GetCachedMessage(data.MessageId) as SocketUserMessage; + bool isMsgCached = cachedMsg != null; + IUser user = null; + if (channel != null) + user = await channel.GetUserAsync(data.UserId, CacheMode.CacheOnly).ConfigureAwait(false); + else if (!data.GuildId.IsSpecified) + user = GetUser(data.UserId); - var reaction = SocketReaction.Create(data, channel, optionalMsg, optionalUser); - var cacheable = new Cacheable(cachedMsg, data.MessageId, isCached, async () => await channel.GetMessageAsync(data.MessageId).ConfigureAwait(false) as IUserMessage); + var optionalMsg = !isMsgCached + ? Optional.Create() + : Optional.Create(cachedMsg); - cachedMsg?.RemoveReaction(reaction); + var optionalUser = user is null + ? Optional.Create() + : Optional.Create(user); - await TimedInvokeAsync(_reactionRemovedEvent, nameof(ReactionRemoved), cacheable, channel, reaction).ConfigureAwait(false); - } - else + var cacheableChannel = new Cacheable(channel, data.ChannelId, channel != null, async () => await GetChannelAsync(data.ChannelId).ConfigureAwait(false) as IMessageChannel); + var cacheableMsg = new Cacheable(cachedMsg, data.MessageId, isMsgCached, async () => { - await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); - return; - } + var channelObj = await cacheableChannel.GetOrDownloadAsync().ConfigureAwait(false); + return await channelObj.GetMessageAsync(data.MessageId).ConfigureAwait(false) as IUserMessage; + }); + var reaction = SocketReaction.Create(data, channel, optionalMsg, optionalUser); + + cachedMsg?.RemoveReaction(reaction); + + await TimedInvokeAsync(_reactionRemovedEvent, nameof(ReactionRemoved), cacheableMsg, cacheableChannel, reaction).ConfigureAwait(false); } break; case "MESSAGE_REACTION_REMOVE_ALL": @@ -1448,21 +1512,20 @@ namespace Discord.WebSocket await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_REMOVE_ALL)").ConfigureAwait(false); var data = (payload as JToken).ToObject(_serializer); - if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) + var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; + + var cacheableChannel = new Cacheable(channel, data.ChannelId, channel != null, async () => await GetChannelAsync(data.ChannelId).ConfigureAwait(false) as IMessageChannel); + var cachedMsg = channel?.GetCachedMessage(data.MessageId) as SocketUserMessage; + bool isMsgCached = cachedMsg != null; + var cacheableMsg = new Cacheable(cachedMsg, data.MessageId, isMsgCached, async () => { - var cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; - bool isCached = cachedMsg != null; - var cacheable = new Cacheable(cachedMsg, data.MessageId, isCached, async () => (await channel.GetMessageAsync(data.MessageId).ConfigureAwait(false)) as IUserMessage); + var channelObj = await cacheableChannel.GetOrDownloadAsync().ConfigureAwait(false); + return await channelObj.GetMessageAsync(data.MessageId).ConfigureAwait(false) as IUserMessage; + }); - cachedMsg?.ClearReactions(); + cachedMsg?.ClearReactions(); - await TimedInvokeAsync(_reactionsClearedEvent, nameof(ReactionsCleared), cacheable, channel).ConfigureAwait(false); - } - else - { - await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); - return; - } + await TimedInvokeAsync(_reactionsClearedEvent, nameof(ReactionsCleared), cacheableMsg, cacheableChannel).ConfigureAwait(false); } break; case "MESSAGE_REACTION_REMOVE_EMOJI": @@ -1470,70 +1533,55 @@ namespace Discord.WebSocket 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 channel = GetChannel(data.ChannelId) as ISocketMessageChannel; - var cacheable = new Cacheable(cachedMsg, data.MessageId, isCached, async () => await channel.GetMessageAsync(data.MessageId).ConfigureAwait(false) as IUserMessage); - var emote = data.Emoji.ToIEmote(); + var cachedMsg = channel?.GetCachedMessage(data.MessageId) as SocketUserMessage; + bool isMsgCached = cachedMsg != null; - cachedMsg?.RemoveReactionsForEmote(emote); + var optionalMsg = !isMsgCached + ? Optional.Create() + : Optional.Create(cachedMsg); - await TimedInvokeAsync(_reactionsRemovedForEmoteEvent, nameof(ReactionsRemovedForEmote), cacheable, channel, emote).ConfigureAwait(false); - } - else + var cacheableChannel = new Cacheable(channel, data.ChannelId, channel != null, async () => await GetChannelAsync(data.ChannelId).ConfigureAwait(false) as IMessageChannel); + var cacheableMsg = new Cacheable(cachedMsg, data.MessageId, isMsgCached, async () => { - await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); - return; - } + var channelObj = await cacheableChannel.GetOrDownloadAsync().ConfigureAwait(false); + return await channelObj.GetMessageAsync(data.MessageId).ConfigureAwait(false) as IUserMessage; + }); + var emote = data.Emoji.ToIEmote(); + + cachedMsg?.RemoveReactionsForEmote(emote); + + await TimedInvokeAsync(_reactionsRemovedForEmoteEvent, nameof(ReactionsRemovedForEmote), cacheableMsg, cacheableChannel, emote).ConfigureAwait(false); } break; case "MESSAGE_DELETE_BULK": { await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE_BULK)").ConfigureAwait(false); - if (!ExclusiveBulkDelete.HasValue) - { - await _gatewayLogger.WarningAsync("A bulk delete event has been received, but the event handling behavior has not been set. " + - "To suppress this message, set the ExclusiveBulkDelete configuration property. " + - "This message will appear only once."); - ExclusiveBulkDelete = false; - } - var data = (payload as JToken).ToObject(_serializer); - if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) - { - var guild = (channel as SocketGuildChannel)?.Guild; - if (!(guild?.IsSynced ?? true)) - { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); - return; - } - - var cacheableList = new List>(data.Ids.Length); - foreach (ulong id in data.Ids) - { - var msg = SocketChannelHelper.RemoveMessage(channel, this, id); - bool isCached = msg != null; - var cacheable = new Cacheable(msg, id, isCached, async () => await channel.GetMessageAsync(id).ConfigureAwait(false)); - cacheableList.Add(cacheable); - - if (!ExclusiveBulkDelete ?? false) // this shouldn't happen, but we'll play it safe anyways - await TimedInvokeAsync(_messageDeletedEvent, nameof(MessageDeleted), cacheable, channel).ConfigureAwait(false); - } + var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; - await TimedInvokeAsync(_messagesBulkDeletedEvent, nameof(MessagesBulkDeleted), cacheableList, channel).ConfigureAwait(false); - } - else + var guild = (channel as SocketGuildChannel)?.Guild; + if (!(guild?.IsSynced ?? true)) { - await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); return; } + + var cacheableChannel = new Cacheable(channel, data.ChannelId, channel != null, async () => await GetChannelAsync(data.ChannelId).ConfigureAwait(false) as IMessageChannel); + var cacheableList = new List>(data.Ids.Length); + foreach (ulong id in data.Ids) + { + SocketMessage msg = null; + if (channel != null) + msg = SocketChannelHelper.RemoveMessage(channel, this, id); + bool isMsgCached = msg != null; + var cacheableMsg = new Cacheable(msg, id, isMsgCached, () => Task.FromResult((IMessage)null)); + cacheableList.Add(cacheableMsg); + } + + await TimedInvokeAsync(_messagesBulkDeletedEvent, nameof(MessagesBulkDeleted), cacheableList, cacheableChannel).ConfigureAwait(false); } break; @@ -1579,7 +1627,8 @@ namespace Discord.WebSocket var before = user.Clone(); user.Update(State, data, true); - await TimedInvokeAsync(_guildMemberUpdatedEvent, nameof(GuildMemberUpdated), before, user).ConfigureAwait(false); + var cacheableBefore = new Cacheable(before, user.Id, true, () => Task.FromResult(user)); + await TimedInvokeAsync(_guildMemberUpdatedEvent, nameof(GuildMemberUpdated), cacheableBefore, user).ConfigureAwait(false); } else { @@ -1602,24 +1651,26 @@ namespace Discord.WebSocket await _gatewayLogger.DebugAsync("Received Dispatch (TYPING_START)").ConfigureAwait(false); var data = (payload as JToken).ToObject(_serializer); - if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) + var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; + + var guild = (channel as SocketGuildChannel)?.Guild; + if (!(guild?.IsSynced ?? true)) { - var guild = (channel as SocketGuildChannel)?.Guild; - if (!(guild?.IsSynced ?? true)) - { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); - return; - } + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + return; + } - var user = (channel as SocketChannel).GetUser(data.UserId); - if (user == null) - { - if (guild != null) - user = guild.AddOrUpdateUser(data.Member); - } - if (user != null) - await TimedInvokeAsync(_userIsTypingEvent, nameof(UserIsTyping), user, channel).ConfigureAwait(false); + var cacheableChannel = new Cacheable(channel, data.ChannelId, channel != null, async () => await GetChannelAsync(data.ChannelId).ConfigureAwait(false) as IMessageChannel); + + var user = (channel as SocketChannel)?.GetUser(data.UserId); + if (user == null) + { + if (guild != null) + user = guild.AddOrUpdateUser(data.Member); } + var cacheableUser = new Cacheable(user, data.UserId, user != null, async () => await GetUserAsync(data.UserId).ConfigureAwait(false)); + + await TimedInvokeAsync(_userIsTypingEvent, nameof(UserIsTyping), cacheableUser, cacheableChannel).ConfigureAwait(false); } break; @@ -1691,7 +1742,7 @@ namespace Discord.WebSocket } else { - var groupChannel = State.GetChannel(data.ChannelId.Value) as SocketGroupChannel; + var groupChannel = GetChannel(data.ChannelId.Value) as SocketGroupChannel; if (groupChannel == null) { await UnknownChannelAsync(type, data.ChannelId.Value).ConfigureAwait(false); @@ -1938,24 +1989,33 @@ namespace Discord.WebSocket { var channel = SocketChannel.CreatePrivate(this, state, model); state.AddChannel(channel as SocketChannel); - if (channel is SocketDMChannel dm) - dm.Recipient.GlobalUser.DMChannel = dm; - return channel; } + internal SocketDMChannel CreateDMChannel(ulong channelId, API.User model, ClientState state) + { + return SocketDMChannel.Create(this, state, channelId, model); + } + internal SocketDMChannel CreateDMChannel(ulong channelId, SocketUser user, ClientState state) + { + return new SocketDMChannel(this, channelId, user); + } internal ISocketPrivateChannel RemovePrivateChannel(ulong id) { var channel = State.RemoveChannel(id) as ISocketPrivateChannel; if (channel != null) { - if (channel is SocketDMChannel dmChannel) - dmChannel.Recipient.GlobalUser.DMChannel = null; - foreach (var recipient in channel.Recipients) recipient.GlobalUser.RemoveRef(this); } return channel; } + internal void RemoveDMChannels() + { + var channels = State.DMChannels; + State.PurgeDMChannels(); + foreach (var channel in channels) + channel.Recipient.GlobalUser.RemoveRef(this); + } private async Task GuildAvailableAsync(SocketGuild guild) { @@ -2111,8 +2171,8 @@ namespace Discord.WebSocket => await GetApplicationInfoAsync().ConfigureAwait(false); /// - Task IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) - => Task.FromResult(GetChannel(id)); + async Task IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) + => mode == CacheMode.AllowDownload ? await GetChannelAsync(id, options).ConfigureAwait(false) : GetChannel(id); /// Task> IDiscordClient.GetPrivateChannelsAsync(CacheMode mode, RequestOptions options) => Task.FromResult>(PrivateChannels); @@ -2142,8 +2202,8 @@ namespace Discord.WebSocket => await CreateGuildAsync(name, region, jpegIcon).ConfigureAwait(false); /// - Task IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) - => Task.FromResult(GetUser(id)); + async Task IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) + => mode == CacheMode.AllowDownload ? await GetUserAsync(id, options).ConfigureAwait(false) : GetUser(id); /// Task IDiscordClient.GetUserAsync(string username, string discriminator, RequestOptions options) => Task.FromResult(GetUser(username, discriminator)); diff --git a/src/Discord.Net.WebSocket/DiscordSocketConfig.cs b/src/Discord.Net.WebSocket/DiscordSocketConfig.cs index a45d4f5be..22a201c67 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketConfig.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketConfig.cs @@ -111,28 +111,6 @@ namespace Discord.WebSocket /// public int? HandlerTimeout { get; set; } = 3000; - /// - /// Gets or sets the behavior for on bulk deletes. - /// - /// - /// - /// If true, the event will not be raised for bulk - /// deletes, and only the will be raised. If false - /// , both events will be raised. - /// - /// - /// If unset, both events will be raised, but a warning will be raised the first time a bulk delete event is - /// received. - /// - /// - public bool? ExclusiveBulkDelete { get; set; } = null; - - /// - /// 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; - /// /// Gets or sets the maximum identify concurrency. /// @@ -172,14 +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. + /// Gets or sets gateway intents to limit what events are sent from Discord. + /// The default is . /// /// /// For more information, please see /// GatewayIntents /// on the official Discord API documentation. /// - public GatewayIntents? GatewayIntents { get; set; } + public GatewayIntents GatewayIntents { get; set; } = GatewayIntents.AllUnprivileged; /// /// Initializes a new instance of the class with the default configuration. diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs index 5de417036..459707dc7 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs @@ -16,32 +16,27 @@ namespace Discord.WebSocket [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class SocketDMChannel : SocketChannel, IDMChannel, ISocketPrivateChannel, ISocketMessageChannel { - private readonly MessageCache _messages; - /// /// Gets the recipient of the channel. /// public SocketUser Recipient { get; } /// - public IReadOnlyCollection CachedMessages => _messages?.Messages ?? ImmutableArray.Create(); + public IReadOnlyCollection CachedMessages => ImmutableArray.Create(); /// /// Gets a collection that is the current logged-in user and the recipient. /// public new IReadOnlyCollection Users => ImmutableArray.Create(Discord.CurrentUser, Recipient); - internal SocketDMChannel(DiscordSocketClient discord, ulong id, SocketGlobalUser recipient) + internal SocketDMChannel(DiscordSocketClient discord, ulong id, SocketUser recipient) : base(discord, id) { Recipient = recipient; - recipient.GlobalUser.AddRef(); - if (Discord.MessageCacheSize > 0) - _messages = new MessageCache(Discord); } internal static SocketDMChannel Create(DiscordSocketClient discord, ClientState state, Model model) { - var entity = new SocketDMChannel(discord, model.Id, discord.GetOrCreateUser(state, model.Recipients.Value[0])); + var entity = new SocketDMChannel(discord, model.Id, discord.GetOrCreateTemporaryUser(state, model.Recipients.Value[0])); entity.Update(state, model); return entity; } @@ -49,6 +44,16 @@ namespace Discord.WebSocket { Recipient.Update(state, model.Recipients.Value[0]); } + internal static SocketDMChannel Create(DiscordSocketClient discord, ClientState state, ulong channelId, API.User recipient) + { + var entity = new SocketDMChannel(discord, channelId, discord.GetOrCreateTemporaryUser(state, recipient)); + entity.Update(state, recipient); + return entity; + } + internal void Update(ClientState state, API.User recipient) + { + Recipient.Update(state, recipient); + } /// public Task CloseAsync(RequestOptions options = null) @@ -57,7 +62,7 @@ namespace Discord.WebSocket //Messages /// public SocketMessage GetCachedMessage(ulong id) - => _messages?.Get(id); + => null; /// /// Gets the message associated with the given . /// @@ -68,10 +73,7 @@ namespace Discord.WebSocket /// public async Task GetMessageAsync(ulong id, RequestOptions options = null) { - IMessage msg = _messages?.Get(id); - if (msg == null) - msg = await ChannelHelper.GetMessageAsync(this, Discord, id, options).ConfigureAwait(false); - return msg; + return await ChannelHelper.GetMessageAsync(this, Discord, id, options).ConfigureAwait(false); } /// @@ -87,7 +89,7 @@ namespace Discord.WebSocket /// Paged collection of messages. /// public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, CacheMode.AllowDownload, options); + => ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, options); /// /// Gets a collection of messages in this channel. /// @@ -103,7 +105,7 @@ namespace Discord.WebSocket /// Paged collection of messages. /// public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, CacheMode.AllowDownload, options); + => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, options); /// /// Gets a collection of messages in this channel. /// @@ -119,16 +121,16 @@ namespace Discord.WebSocket /// Paged collection of messages. /// public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, CacheMode.AllowDownload, options); + => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, options); /// public IReadOnlyCollection GetCachedMessages(int limit = DiscordConfig.MaxMessagesPerBatch) - => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, null, Direction.Before, limit); + => ImmutableArray.Create(); /// public IReadOnlyCollection GetCachedMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) - => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessageId, dir, limit); + => ImmutableArray.Create(); /// public IReadOnlyCollection GetCachedMessages(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) - => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessage.Id, dir, limit); + => ImmutableArray.Create(); /// public Task> GetPinnedMessagesAsync(RequestOptions options = null) => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); @@ -164,9 +166,12 @@ namespace Discord.WebSocket => ChannelHelper.EnterTypingState(this, Discord, options); internal void AddMessage(SocketMessage msg) - => _messages?.Add(msg); + { + } internal SocketMessage RemoveMessage(ulong id) - => _messages?.Remove(id); + { + return null; + } //Users /// @@ -222,13 +227,13 @@ namespace Discord.WebSocket } /// IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, mode, options); + => mode == CacheMode.CacheOnly ? null : GetMessagesAsync(limit, options); /// IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, mode, options); + => mode == CacheMode.CacheOnly ? null : GetMessagesAsync(fromMessageId, dir, limit, options); /// IAsyncEnumerable> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode, RequestOptions options) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, mode, options); + => mode == CacheMode.CacheOnly ? null : GetMessagesAsync(fromMessage.Id, dir, limit, options); /// async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) => await GetPinnedMessagesAsync(options).ConfigureAwait(false); diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index 0e36c6b50..9af4ad57e 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -46,8 +46,6 @@ namespace Discord.WebSocket /// public int AFKTimeout { get; private set; } /// - public bool IsEmbeddable { get; private set; } - /// public bool IsWidgetEnabled { get; private set; } /// public VerificationLevel VerificationLevel { get; private set; } @@ -84,7 +82,6 @@ namespace Discord.WebSocket public ulong? ApplicationId { get; internal set; } internal ulong? AFKChannelId { get; private set; } - internal ulong? EmbedChannelId { get; private set; } internal ulong? WidgetChannelId { get; private set; } internal ulong? SystemChannelId { get; private set; } internal ulong? RulesChannelId { get; private set; } @@ -198,21 +195,6 @@ namespace Discord.WebSocket } } /// - /// Gets the embed channel (i.e. the channel set in the guild's widget settings) in this guild. - /// - /// - /// A channel set within the server's widget settings; if none is set. - /// - [Obsolete("This property is deprecated, use WidgetChannel instead.")] - public SocketGuildChannel EmbedChannel - { - get - { - var id = EmbedChannelId; - return id.HasValue ? GetChannel(id.Value) : null; - } - } - /// /// Gets the widget channel (i.e. the channel set in the guild's widget settings) in this guild. /// /// @@ -405,7 +387,8 @@ namespace Discord.WebSocket for (int i = 0; i < model.Members.Length; i++) { var member = SocketGuildUser.Create(this, state, model.Members[i]); - members.TryAdd(member.Id, member); + if (members.TryAdd(member.Id, member)) + member.GlobalUser.AddRef(); } DownloadedMemberCount = members.Count; @@ -440,16 +423,12 @@ namespace Discord.WebSocket internal void Update(ClientState state, Model model) { AFKChannelId = model.AFKChannelId; - if (model.EmbedChannelId.IsSpecified) - EmbedChannelId = model.EmbedChannelId.Value; if (model.WidgetChannelId.IsSpecified) WidgetChannelId = model.WidgetChannelId.Value; SystemChannelId = model.SystemChannelId; RulesChannelId = model.RulesChannelId; PublicUpdatesChannelId = model.PublicUpdatesChannelId; AFKTimeout = model.AFKTimeout; - if (model.EmbedEnabled.IsSpecified) - IsEmbeddable = model.EmbedEnabled.Value; if (model.WidgetEnabled.IsSpecified) IsWidgetEnabled = model.WidgetEnabled.Value; IconId = model.Icon; @@ -504,7 +483,7 @@ namespace Discord.WebSocket } _roles = roles; } - internal void Update(ClientState state, GuildSyncModel model) + /*internal void Update(ClientState state, GuildSyncModel model) //TODO remove? userbot related { var members = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.Members.Length * 1.05)); { @@ -524,9 +503,9 @@ namespace Discord.WebSocket _members = members; var _ = _syncPromise.TrySetResultAsync(true); - /*if (!model.Large) - _ = _downloaderPromise.TrySetResultAsync(true);*/ - } + //if (!model.Large) + // _ = _downloaderPromise.TrySetResultAsync(true); + }*/ internal void Update(ClientState state, EmojiUpdateModel model) { @@ -548,11 +527,6 @@ namespace Discord.WebSocket /// /// is . - [Obsolete("This endpoint is deprecated, use ModifyWidgetAsync instead.")] - public Task ModifyEmbedAsync(Action func, RequestOptions options = null) - => GuildHelper.ModifyEmbedAsync(this, Discord, func, options); - /// - /// is . public Task ModifyWidgetAsync(Action func, RequestOptions options = null) => GuildHelper.ModifyWidgetAsync(this, Discord, func, options); /// @@ -869,16 +843,10 @@ namespace Discord.WebSocket else { member = SocketGuildUser.Create(this, Discord.State, model); - if (member == null) - throw new InvalidOperationException("SocketGuildUser.Create failed to produce a member"); // TODO 2.2rel: delete this - if (member.GlobalUser == null) - throw new InvalidOperationException("Member was created without global user"); // TODO 2.2rel: delete this member.GlobalUser.AddRef(); _members[member.Id] = member; DownloadedMemberCount++; } - if (member == null) - throw new InvalidOperationException("AddOrUpdateUser failed to produce a user"); // TODO 2.2rel: delete this return member; } internal SocketGuildUser AddOrUpdateUser(PresenceModel model) @@ -912,6 +880,7 @@ namespace Discord.WebSocket if (self != null) _members.TryAdd(self.Id, self); + _downloaderPromise = new TaskCompletionSource(); DownloadedMemberCount = _members.Count; foreach (var member in members) @@ -1232,10 +1201,6 @@ namespace Discord.WebSocket /// bool IGuild.Available => true; /// - ulong IGuild.DefaultChannelId => DefaultChannel?.Id ?? 0; - /// - ulong? IGuild.EmbedChannelId => EmbedChannelId; - /// ulong? IGuild.WidgetChannelId => WidgetChannelId; /// ulong? IGuild.SystemChannelId => SystemChannelId; @@ -1290,10 +1255,6 @@ namespace Discord.WebSocket Task IGuild.GetDefaultChannelAsync(CacheMode mode, RequestOptions options) => Task.FromResult(DefaultChannel); /// - [Obsolete("This method is deprecated, use GetWidgetChannelAsync instead.")] - Task IGuild.GetEmbedChannelAsync(CacheMode mode, RequestOptions options) - => Task.FromResult(EmbedChannel); - /// Task IGuild.GetWidgetChannelAsync(CacheMode mode, RequestOptions options) => Task.FromResult(WidgetChannel); /// diff --git a/src/Discord.Net.WebSocket/Entities/Invites/SocketInvite.cs b/src/Discord.Net.WebSocket/Entities/Invites/SocketInvite.cs index 5dc53a833..845b48b8b 100644 --- a/src/Discord.Net.WebSocket/Entities/Invites/SocketInvite.cs +++ b/src/Discord.Net.WebSocket/Entities/Invites/SocketInvite.cs @@ -49,9 +49,6 @@ namespace Discord.WebSocket /// int? IInvite.MemberCount => throw new NotImplementedException(); /// - [Obsolete("This property doesn't exist anymore and shouldn't be used.")] - bool IInviteMetadata.IsRevoked => throw new NotImplementedException(); - /// public bool IsTemporary { get; private set; } /// int? IInviteMetadata.MaxAge { get => MaxAge; } diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs index 2a8b45ca1..597544f4d 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs @@ -189,9 +189,6 @@ namespace Discord.WebSocket /// public Task UnpinAsync(RequestOptions options = null) => MessageHelper.UnpinAsync(this, Discord, options); - /// - public Task ModifySuppressionAsync(bool suppressEmbeds, RequestOptions options = null) - => MessageHelper.SuppressEmbedsAsync(this, Discord, suppressEmbeds, options); public string Resolve(int startIndex, TagHandling userHandling = TagHandling.Name, TagHandling channelHandling = TagHandling.Name, TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, TagHandling emojiHandling = TagHandling.Name) diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs index 48de7552a..15c5182fc 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs @@ -12,7 +12,6 @@ namespace Discord.WebSocket public override string Username { get; internal set; } public override ushort DiscriminatorValue { get; internal set; } public override string AvatarId { get; internal set; } - public SocketDMChannel DMChannel { get; internal set; } internal override SocketPresence Presence { get; set; } public override bool IsWebhook => false; @@ -52,7 +51,6 @@ namespace Discord.WebSocket internal void Update(ClientState state, PresenceModel model) { Presence = SocketPresence.Create(model); - DMChannel = state.DMChannels.FirstOrDefault(x => x.Recipient.Id == Id); } private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")}, Global)"; diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketPresence.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketPresence.cs index 407e14419..fe672a4d6 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketPresence.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketPresence.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; +using System.Linq; using Model = Discord.API.Presence; namespace Discord.WebSocket @@ -15,15 +16,12 @@ namespace Discord.WebSocket /// public UserStatus Status { get; } /// - public IActivity Activity { get; } - /// public IImmutableSet ActiveClients { get; } /// public IImmutableList Activities { get; } - internal SocketPresence(UserStatus status, IActivity activity, IImmutableSet activeClients, IImmutableList activities) + internal SocketPresence(UserStatus status, IImmutableSet activeClients, IImmutableList activities) { Status = status; - Activity = activity; ActiveClients = activeClients ?? ImmutableHashSet.Empty; Activities = activities ?? ImmutableList.Empty; } @@ -31,7 +29,7 @@ namespace Discord.WebSocket { var clients = ConvertClientTypesDict(model.ClientStatus.GetValueOrDefault()); var activities = ConvertActivitiesList(model.Activities); - return new SocketPresence(model.Status, model.Game?.ToEntity(), clients, activities); + return new SocketPresence(model.Status, clients, activities); } /// /// Creates a new containing all of the client types @@ -84,7 +82,7 @@ namespace Discord.WebSocket /// A string that resolves to . /// public override string ToString() => Status.ToString(); - private string DebuggerDisplay => $"{Status}{(Activity != null ? $", {Activity.Name}": "")}"; + private string DebuggerDisplay => $"{Status}{(Activities?.FirstOrDefault()?.Name ?? "")}"; internal SocketPresence Clone() => this; } diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketUnknownUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketUnknownUser.cs index dd2e747b4..840a1c30b 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketUnknownUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketUnknownUser.cs @@ -25,7 +25,7 @@ namespace Discord.WebSocket /// public override bool IsWebhook => false; /// - internal override SocketPresence Presence { get { return new SocketPresence(UserStatus.Offline, null, null, null); } set { } } + internal override SocketPresence Presence { get { return new SocketPresence(UserStatus.Offline, null, null); } set { } } /// /// This field is not supported for an unknown user. internal override SocketGlobalUser GlobalUser => diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs index 4e6d4b3f8..025daf29a 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs @@ -38,8 +38,6 @@ namespace Discord.WebSocket /// public string Mention => MentionUtils.MentionUser(Id); /// - public IActivity Activity => Presence.Activity; - /// public UserStatus Status => Presence.Status; /// public IImmutableSet ActiveClients => Presence.ActiveClients ?? ImmutableHashSet.Empty; @@ -94,8 +92,8 @@ namespace Discord.WebSocket } /// - public async Task GetOrCreateDMChannelAsync(RequestOptions options = null) - => GlobalUser.DMChannel ?? await UserHelper.CreateDMChannelAsync(this, Discord, options).ConfigureAwait(false) as IDMChannel; + public async Task CreateDMChannelAsync(RequestOptions options = null) + => await UserHelper.CreateDMChannelAsync(this, Discord, options).ConfigureAwait(false); /// public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs index c22164f95..404ab116d 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs @@ -30,7 +30,7 @@ namespace Discord.WebSocket /// public override bool IsWebhook => true; /// - internal override SocketPresence Presence { get { return new SocketPresence(UserStatus.Offline, null, null, null); } set { } } + internal override SocketPresence Presence { get { return new SocketPresence(UserStatus.Offline, null, null); } set { } } internal override SocketGlobalUser GlobalUser => throw new NotSupportedException(); diff --git a/src/Discord.Net/Discord.Net.nuspec b/src/Discord.Net/Discord.Net.nuspec index b0fe17439..e3f0150f9 100644 --- a/src/Discord.Net/Discord.Net.nuspec +++ b/src/Discord.Net/Discord.Net.nuspec @@ -2,7 +2,7 @@ Discord.Net - 2.4.0$suffix$ + 3.0.0-dev$suffix$ Discord.Net Discord.Net Contributors foxbot @@ -14,25 +14,25 @@ https://github.com/RogueException/Discord.Net/raw/dev/docs/marketing/logo/PackageLogo.png - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + diff --git a/test/Discord.Net.Tests.Unit/EmbedBuilderTests.cs b/test/Discord.Net.Tests.Unit/EmbedBuilderTests.cs index 12ec1a0bd..6cfdc83b2 100644 --- a/test/Discord.Net.Tests.Unit/EmbedBuilderTests.cs +++ b/test/Discord.Net.Tests.Unit/EmbedBuilderTests.cs @@ -190,42 +190,6 @@ namespace Discord Assert.Equal(result.ThumbnailUrl, url); } - /// - /// Tests that invalid urls throw an . - /// - /// The url to set. - [Theory] - [InlineData(" ")] - [InlineData("not a url")] - public void Url_Invalid(string url) - { - Assert.Throws(() - => new EmbedBuilder() - .WithUrl(url)); - Assert.Throws(() - => new EmbedBuilder() - .WithImageUrl(url)); - Assert.Throws(() - => new EmbedBuilder() - .WithThumbnailUrl(url)); - - Assert.Throws(() => - { - var b = new EmbedBuilder(); - b.Url = url; - }); - Assert.Throws(() => - { - var b = new EmbedBuilder(); - b.ImageUrl = url; - }); - Assert.Throws(() => - { - var b = new EmbedBuilder(); - b.ThumbnailUrl = url; - }); - } - /// /// Tests the value of the property when there are no fields set. /// @@ -343,24 +307,6 @@ namespace Discord Assert.Equal(name, footer.Text); } /// - /// Tests that invalid URLs throw an . - /// - [Fact] - public void EmbedFooterBuilder_InvalidURL() - { - IEnumerable InvalidUrls() - { - yield return "not a url"; - } - foreach (var url in InvalidUrls()) - { - Assert.Throws(() => - { - new EmbedFooterBuilder().WithIconUrl(url); - }); - } - } - /// /// Tests that invalid text throws an . /// [Fact] diff --git a/test/Discord.Net.Tests.Unit/MockedEntities/MockedDMChannel.cs b/test/Discord.Net.Tests.Unit/MockedEntities/MockedDMChannel.cs index f169fb717..593b9201a 100644 --- a/test/Discord.Net.Tests.Unit/MockedEntities/MockedDMChannel.cs +++ b/test/Discord.Net.Tests.Unit/MockedEntities/MockedDMChannel.cs @@ -33,6 +33,11 @@ namespace Discord throw new NotImplementedException(); } + public Task ModifyMessageAsync(ulong messageId, Action func, RequestOptions options = null) + { + throw new NotImplementedException(); + } + public IDisposable EnterTypingState(RequestOptions options = 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 e662d628a..6daf6a9c8 100644 --- a/test/Discord.Net.Tests.Unit/MockedEntities/MockedGroupChannel.cs +++ b/test/Discord.Net.Tests.Unit/MockedEntities/MockedGroupChannel.cs @@ -31,6 +31,11 @@ namespace Discord throw new NotImplementedException(); } + public Task ModifyMessageAsync(ulong messageId, Action func, RequestOptions options = null) + { + throw new NotImplementedException(); + } + public Task DisconnectAsync() { throw new NotImplementedException(); diff --git a/test/Discord.Net.Tests.Unit/MockedEntities/MockedTextChannel.cs b/test/Discord.Net.Tests.Unit/MockedEntities/MockedTextChannel.cs index fbaaf9a18..51aece5f2 100644 --- a/test/Discord.Net.Tests.Unit/MockedEntities/MockedTextChannel.cs +++ b/test/Discord.Net.Tests.Unit/MockedEntities/MockedTextChannel.cs @@ -77,6 +77,11 @@ namespace Discord throw new NotImplementedException(); } + public Task ModifyMessageAsync(ulong messageId, Action func, RequestOptions options = null) + { + throw new NotImplementedException(); + } + public IDisposable EnterTypingState(RequestOptions options = null) { throw new NotImplementedException(); diff --git a/test/Discord.Net.Tests.Unit/TokenUtilsTests.cs b/test/Discord.Net.Tests.Unit/TokenUtilsTests.cs index e9526b761..4306fa9e2 100644 --- a/test/Discord.Net.Tests.Unit/TokenUtilsTests.cs +++ b/test/Discord.Net.Tests.Unit/TokenUtilsTests.cs @@ -127,8 +127,6 @@ namespace Discord /// The type is treated as an invalid . /// [Theory] - // TokenType.User - [InlineData(0)] // out of range TokenType [InlineData(-1)] [InlineData(4)]