From dde4ef9e197d46542fd2fe7539b82716885869b4 Mon Sep 17 00:00:00 2001 From: quinchs <49576606+quinchs@users.noreply.github.com> Date: Tue, 7 Jul 2020 04:59:50 -0300 Subject: [PATCH 01/64] Added SocketGuildInvites --- .../API/Gateway/InviteCreatedEvent.cs | 32 +++++ .../API/Gateway/InviteDeletedEvent.cs | 19 +++ .../BaseSocketClient.Events.cs | 16 +++ .../DiscordSocketClient.cs | 72 ++++++++++- .../Entities/Guilds/SocketGuild.cs | 18 +++ .../Entities/Invites/ISocketInvite.cs | 42 +++++++ .../Entities/Invites/InviteCache.cs | 47 ++++++++ .../Entities/Invites/SocketGuildInvite.cs | 112 ++++++++++++++++++ .../Entities/Invites/SocketInviteHelper.cs | 17 +++ 9 files changed, 372 insertions(+), 3 deletions(-) create mode 100644 src/Discord.Net.WebSocket/API/Gateway/InviteCreatedEvent.cs create mode 100644 src/Discord.Net.WebSocket/API/Gateway/InviteDeletedEvent.cs create mode 100644 src/Discord.Net.WebSocket/Entities/Invites/ISocketInvite.cs create mode 100644 src/Discord.Net.WebSocket/Entities/Invites/InviteCache.cs create mode 100644 src/Discord.Net.WebSocket/Entities/Invites/SocketGuildInvite.cs create mode 100644 src/Discord.Net.WebSocket/Entities/Invites/SocketInviteHelper.cs diff --git a/src/Discord.Net.WebSocket/API/Gateway/InviteCreatedEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/InviteCreatedEvent.cs new file mode 100644 index 000000000..8f8002029 --- /dev/null +++ b/src/Discord.Net.WebSocket/API/Gateway/InviteCreatedEvent.cs @@ -0,0 +1,32 @@ +using Discord.API; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.API.Gateway +{ + internal class InviteCreatedEvent + { + [JsonProperty("channel_id")] + public ulong ChannelID { get; set; } + [JsonProperty("code")] + public string InviteCode { get; set; } + [JsonProperty("timestamp")] + public Optional RawTimestamp { get; set; } + [JsonProperty("guild_id")] + public ulong? GuildID { get; set; } + [JsonProperty("inviter")] + public Optional inviter { get; set; } + [JsonProperty("max_age")] + public int RawAge { get; set; } + [JsonProperty("max_uses")] + public int MaxUsers { get; set; } + [JsonProperty("temporary")] + public bool TempInvite { get; set; } + [JsonProperty("uses")] + public int Uses { get; set; } + } +} diff --git a/src/Discord.Net.WebSocket/API/Gateway/InviteDeletedEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/InviteDeletedEvent.cs new file mode 100644 index 000000000..6bdd337f5 --- /dev/null +++ b/src/Discord.Net.WebSocket/API/Gateway/InviteDeletedEvent.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.WebSocket +{ + internal class InviteDeletedEvent + { + [JsonProperty("channel_id")] + public ulong ChannelID { get; set; } + [JsonProperty("guild_id")] + public Optional GuildID { get; set; } + [JsonProperty("code")] + public string Code { get; set; } + } +} diff --git a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs index 2cd62b3e8..221067d22 100644 --- a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs +++ b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs @@ -315,6 +315,22 @@ namespace Discord.WebSocket } internal readonly AsyncEvent> _guildUpdatedEvent = new AsyncEvent>(); + //Invites + internal readonly AsyncEvent> _inviteCreatedEvent = new AsyncEvent>(); + /// Fired when a invite is created. + public event Func InviteCreated + { + add { _inviteCreatedEvent.Add(value); } + remove { _inviteCreatedEvent.Remove(value); } + } + internal readonly AsyncEvent, Task>> _inviteDeletedEvent = new AsyncEvent, Task>>(); + /// Fired when a invite is deleted. + public event Func, Task> InviteDeleted + { + add { _inviteDeletedEvent.Add(value); } + remove { _inviteDeletedEvent.Remove(value); } + } + //Users /// Fired when a user joins a guild. public event Func UserJoined { diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 1bfa467b6..f3cb054db 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -275,7 +275,8 @@ namespace Discord.WebSocket await heartbeatTask.ConfigureAwait(false); _heartbeatTask = null; - while (_heartbeatTimes.TryDequeue(out _)) { } + while (_heartbeatTimes.TryDequeue(out _)) + { } _lastMessageTime = 0; await _gatewayLogger.DebugAsync("Waiting for guild downloader").ConfigureAwait(false); @@ -286,7 +287,8 @@ namespace Discord.WebSocket //Clear large guild queue await _gatewayLogger.DebugAsync("Clearing large guild queue").ConfigureAwait(false); - while (_largeGuilds.TryDequeue(out _)) { } + while (_largeGuilds.TryDequeue(out _)) + { } //Raise virtual GUILD_UNAVAILABLEs await _gatewayLogger.DebugAsync("Raising virtual GuildUnavailables").ConfigureAwait(false); @@ -578,7 +580,7 @@ namespace Discord.WebSocket } else if (_connection.CancelToken.IsCancellationRequested) return; - + if (BaseConfig.AlwaysDownloadUsers) _ = DownloadUsersAsync(Guilds.Where(x => x.IsAvailable && !x.HasAllMembers)); @@ -1680,6 +1682,70 @@ namespace Discord.WebSocket } break; + case "INVITE_CREATE": + { + await _gatewayLogger.DebugAsync("Received Dispatch (INVITE_CREATE)").ConfigureAwait(false); + var data = (payload as JToken).ToObject(_serializer); + if(data.GuildID.HasValue) + { + var guild = State.GetGuild(data.GuildID.Value); + if (guild != null) + { + if (!guild.IsSynced) + { + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + return; + } + + var channel = guild.GetChannel(data.ChannelID); + + if (channel != null) + { + var invite = new SocketGuildInvite(this, guild, channel, data.InviteCode, data); + guild.AddSocketInvite(invite); + await TimedInvokeAsync(_inviteCreatedEvent, nameof(InviteCreated), invite).ConfigureAwait(false); + } + } + else + { + //add else + } + } + + } + break; + case "INVITE_DELETE": + { + await _gatewayLogger.DebugAsync("Received Dispatch (INVITE_DELETE)").ConfigureAwait(false); + var data = (payload as JToken).ToObject(_serializer); + if(data.GuildID.IsSpecified) + { + var guild = State.GetGuild(data.GuildID.Value); + if (guild != null) + { + if (!guild.IsSynced) + { + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + return; + } + + var channel = guild.GetChannel(data.ChannelID); + + if (channel != null) + { + var invite = guild.RemoveSocketInvite(data.Code); + var cache = new Cacheable(null, data.Code, invite != null, async () => await guild.GetSocketInviteAsync(data.Code)); + await TimedInvokeAsync(_inviteDeletedEvent, nameof(InviteDeleted), cache).ConfigureAwait(false); + } + } + else + { + //add else + } + } + + } + break; //Ignored (User only) case "CHANNEL_PINS_ACK": diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index d2d759bb3..58a07ff33 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -39,6 +39,7 @@ namespace Discord.WebSocket private ImmutableArray _emotes; private ImmutableArray _features; private AudioClient _audioClient; + private InviteCache _invites; #pragma warning restore IDISP002, IDISP006 /// @@ -280,6 +281,7 @@ namespace Discord.WebSocket _audioLock = new SemaphoreSlim(1, 1); _emotes = ImmutableArray.Create(); _features = ImmutableArray.Create(); + _invites = new InviteCache(client); } internal static SocketGuild Create(DiscordSocketClient discord, ClientState state, ExtendedModel model) { @@ -515,6 +517,22 @@ namespace Discord.WebSocket public Task RemoveBanAsync(ulong userId, RequestOptions options = null) => GuildHelper.RemoveBanAsync(this, Discord, userId, options); + //Invites + internal void AddSocketInvite(SocketGuildInvite invite) + => _invites.Add(invite); + internal SocketGuildInvite RemoveSocketInvite(string code) + => _invites.Remove(code); + internal async Task GetSocketInviteAsync(string code) + { + var invites = await this.GetInvitesAsync(); + RestInviteMetadata restInvite = invites.First(x => x.Code == code); + if (restInvite == null) + return null; + + var invite = new SocketGuildInvite(Discord, this, this.GetChannel(restInvite.ChannelId), code, restInvite); + return invite; + } + //Channels /// /// Gets a channel in this guild. diff --git a/src/Discord.Net.WebSocket/Entities/Invites/ISocketInvite.cs b/src/Discord.Net.WebSocket/Entities/Invites/ISocketInvite.cs new file mode 100644 index 000000000..976fa348a --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Invites/ISocketInvite.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.WebSocket +{ + public interface ISocketInvite + { + /// + /// Gets the unique identifier for this invite. + /// + /// + /// A string containing the invite code (e.g. FTqNnyS). + /// + string Code { get; } + /// + /// Gets the URL used to accept this invite using . + /// + /// + /// A string containing the full invite URL (e.g. https://discord.gg/FTqNnyS). + /// + string Url { get; } + + /// + /// Gets the channel this invite is linked to. + /// + /// + /// A generic channel that the invite points to. + /// + SocketGuildChannel Channel { get; } + + /// + /// Gets the guild this invite is linked to. + /// + /// + /// A guild object representing the guild that the invite points to. + /// + SocketGuild Guild { get; } + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Invites/InviteCache.cs b/src/Discord.Net.WebSocket/Entities/Invites/InviteCache.cs new file mode 100644 index 000000000..2346fb163 --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Invites/InviteCache.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.WebSocket +{ + internal class InviteCache + { + private readonly ConcurrentDictionary _invites; + private readonly ConcurrentQueue _queue; + private static int _size; + + public InviteCache(DiscordSocketClient client) + { + //NOTE: + //This should be an option in the client config. + _size = client.Guilds.Count * 20; + + _invites = new ConcurrentDictionary(); + _queue = new ConcurrentQueue(); + } + public void Add(SocketGuildInvite invite) + { + if(_invites.TryAdd(invite.Code, invite)) + { + _queue.Enqueue(invite.Code); + + while (_queue.Count > _size && _queue.TryDequeue(out string invCode)) + _invites.TryRemove(invCode, out _); + } + } + public SocketGuildInvite Remove(string inviteCode) + { + _invites.TryRemove(inviteCode, out SocketGuildInvite inv); + return inv; + } + public SocketGuildInvite Get(string inviteCode) + { + if(_invites.TryGetValue(inviteCode, out SocketGuildInvite inv)) + return inv; + return null; + } + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Invites/SocketGuildInvite.cs b/src/Discord.Net.WebSocket/Entities/Invites/SocketGuildInvite.cs new file mode 100644 index 000000000..35fdf237c --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Invites/SocketGuildInvite.cs @@ -0,0 +1,112 @@ +using Discord.Rest; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization.Formatters; +using System.Text; +using System.Threading.Tasks; +using InviteUpdate = Discord.API.Gateway.InviteCreatedEvent; + +namespace Discord.WebSocket +{ + /// + /// Represents a guild invite + /// + public class SocketGuildInvite : SocketEntity, ISocketInvite + { + public string Code { get; private set; } + public string Url => $"{DiscordConfig.InviteUrl}{Code}"; + public SocketGuildChannel Channel { get; private set; } + public SocketGuild Guild { get; private set; } + /// + /// Gets the unique invite code + /// + /// Returns the unique invite code + /// + /// + public string Id => Code; + /// + /// Gets the user who created the invite + /// + /// Returns the user who created the invite + /// + /// + public SocketGuildUser Inviter { get; private set; } + /// + /// Gets the maximum number of times the invite can be used, if there is no limit then the value will be 0 + /// + /// Returns the maximum number of times the invite can be used, if there is no limit then the value will be 0 + /// + /// + public int? MaxUses { get; private set; } + /// + /// Gets whether or not the invite is temporary (invited users will be kicked on disconnect unless they're assigned a role) + /// + /// Returns whether or not the invite is temporary (invited users will be kicked on disconnect unless they're assigned a role) + /// + /// + public bool Temporary { get; private set; } + /// + /// Gets the time at which the invite was created + /// + /// Returns the time at which the invite was created + /// + /// + public DateTimeOffset? CreatedAt { get; private set; } + /// + /// Gets how long the invite is valid for + /// + /// Returns how long the invite is valid for (in seconds) + /// + /// + public TimeSpan? MaxAge { get; private set; } + + internal SocketGuildInvite(DiscordSocketClient _client, SocketGuild guild, SocketGuildChannel channel, string inviteCode, RestInviteMetadata rest) : base(_client, inviteCode) + { + Code = inviteCode; + Guild = guild; + Channel = channel; + CreatedAt = rest.CreatedAt; + Temporary = rest.IsTemporary; + MaxUses = rest.MaxUses; + Inviter = guild.GetUser(rest.Inviter.Id); + if (rest.MaxAge.HasValue) + MaxAge = TimeSpan.FromSeconds(rest.MaxAge.Value); + } + internal SocketGuildInvite(DiscordSocketClient _client, SocketGuild guild, SocketGuildChannel channel, string inviteCode, InviteUpdate Update) : base(_client, inviteCode) + { + Code = inviteCode; + Guild = guild; + Channel = channel; + + if (Update.RawTimestamp.IsSpecified) + CreatedAt = Update.RawTimestamp.Value; + else + CreatedAt = DateTimeOffset.Now; + + if (Update.inviter.IsSpecified) + Inviter = guild.GetUser(Update.inviter.Value.Id); + + Temporary = Update.TempInvite; + MaxUses = Update.MaxUsers; + MaxAge = TimeSpan.FromSeconds(Update.RawAge); + } + internal static SocketGuildInvite Create(DiscordSocketClient _client, SocketGuild guild, SocketGuildChannel channel, string inviteCode, InviteUpdate Update) + { + var invite = new SocketGuildInvite(_client, guild, channel, inviteCode, Update); + return invite; + } + internal static SocketGuildInvite CreateFromRest(DiscordSocketClient _client, SocketGuild guild, SocketGuildChannel channel, string inviteCode, RestInviteMetadata rest) + { + var invite = new SocketGuildInvite(_client, guild, channel, inviteCode, rest); + return invite; + } + /// + /// Deletes the invite + /// + /// + /// + public Task DeleteAsync(RequestOptions options = null) + => SocketInviteHelper.DeleteAsync(this, Discord, options); + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Invites/SocketInviteHelper.cs b/src/Discord.Net.WebSocket/Entities/Invites/SocketInviteHelper.cs new file mode 100644 index 000000000..3781739a9 --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Invites/SocketInviteHelper.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.WebSocket +{ + internal class SocketInviteHelper + { + public static async Task DeleteAsync(ISocketInvite invite, BaseSocketClient client, + RequestOptions options) + { + await client.ApiClient.DeleteInviteAsync(invite.Code, options).ConfigureAwait(false); + } + } +} From e4c70d7ad18735a5fd1d0cff9c54ed3e967fb1cf Mon Sep 17 00:00:00 2001 From: quinchs <49576606+quinchs@users.noreply.github.com> Date: Tue, 7 Jul 2020 05:07:02 -0300 Subject: [PATCH 02/64] Fixed a few issues --- src/Discord.Net.WebSocket/DiscordSocketClient.cs | 15 ++------------- .../Entities/Invites/ISocketInvite.cs | 2 +- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index f3cb054db..d3a6f5e74 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -275,8 +275,7 @@ namespace Discord.WebSocket await heartbeatTask.ConfigureAwait(false); _heartbeatTask = null; - while (_heartbeatTimes.TryDequeue(out _)) - { } + while (_heartbeatTimes.TryDequeue(out _)) { } _lastMessageTime = 0; await _gatewayLogger.DebugAsync("Waiting for guild downloader").ConfigureAwait(false); @@ -287,8 +286,7 @@ namespace Discord.WebSocket //Clear large guild queue await _gatewayLogger.DebugAsync("Clearing large guild queue").ConfigureAwait(false); - while (_largeGuilds.TryDequeue(out _)) - { } + while (_largeGuilds.TryDequeue(out _)) { } //Raise virtual GUILD_UNAVAILABLEs await _gatewayLogger.DebugAsync("Raising virtual GuildUnavailables").ConfigureAwait(false); @@ -1706,12 +1704,7 @@ namespace Discord.WebSocket await TimedInvokeAsync(_inviteCreatedEvent, nameof(InviteCreated), invite).ConfigureAwait(false); } } - else - { - //add else - } } - } break; case "INVITE_DELETE": @@ -1738,10 +1731,6 @@ namespace Discord.WebSocket await TimedInvokeAsync(_inviteDeletedEvent, nameof(InviteDeleted), cache).ConfigureAwait(false); } } - else - { - //add else - } } } diff --git a/src/Discord.Net.WebSocket/Entities/Invites/ISocketInvite.cs b/src/Discord.Net.WebSocket/Entities/Invites/ISocketInvite.cs index 976fa348a..8fe82b089 100644 --- a/src/Discord.Net.WebSocket/Entities/Invites/ISocketInvite.cs +++ b/src/Discord.Net.WebSocket/Entities/Invites/ISocketInvite.cs @@ -16,7 +16,7 @@ namespace Discord.WebSocket /// string Code { get; } /// - /// Gets the URL used to accept this invite using . + /// Gets the URL used to accept this invite /// /// /// A string containing the full invite URL (e.g. https://discord.gg/FTqNnyS). From 272c3b7debac7a6c4d1ac33f33009756c14980d8 Mon Sep 17 00:00:00 2001 From: quinchs <49576606+quinchs@users.noreply.github.com> Date: Tue, 7 Jul 2020 05:16:30 -0300 Subject: [PATCH 03/64] Comment update --- src/Discord.Net.WebSocket/Entities/Invites/InviteCache.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.WebSocket/Entities/Invites/InviteCache.cs b/src/Discord.Net.WebSocket/Entities/Invites/InviteCache.cs index 2346fb163..a203b780f 100644 --- a/src/Discord.Net.WebSocket/Entities/Invites/InviteCache.cs +++ b/src/Discord.Net.WebSocket/Entities/Invites/InviteCache.cs @@ -16,7 +16,7 @@ namespace Discord.WebSocket public InviteCache(DiscordSocketClient client) { //NOTE: - //This should be an option in the client config. + //This should be an option in the client config. default for now is 20 invites per guild _size = client.Guilds.Count * 20; _invites = new ConcurrentDictionary(); From dd2e524ff926f88fd1f2f69ef238f1b98e83248b Mon Sep 17 00:00:00 2001 From: Fyers Date: Sat, 19 Dec 2020 15:39:09 +0100 Subject: [PATCH 04/64] fix: Invite and InviteMetadata properties (#1639) * fixes #1495 * keep obsolete properties and return types for compatibility * missing properties for SocketInvite * Restore xml docs and change obsolete message Co-authored-by: Paulo --- .../Entities/Invites/IInvite.cs | 21 +++++++++++++++++++ .../Entities/Invites/IInviteMetadata.cs | 16 +++++--------- src/Discord.Net.Rest/API/Common/Invite.cs | 6 ++++++ .../API/Common/InviteMetadata.cs | 12 ++++------- .../Entities/Invites/RestInvite.cs | 9 ++++++++ .../Entities/Invites/RestInviteMetadata.cs | 20 ++++++------------ .../Entities/Invites/SocketInvite.cs | 5 ++++- 7 files changed, 55 insertions(+), 34 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Invites/IInvite.cs b/src/Discord.Net.Core/Entities/Invites/IInvite.cs index 993f1f047..47ffffacb 100644 --- a/src/Discord.Net.Core/Entities/Invites/IInvite.cs +++ b/src/Discord.Net.Core/Entities/Invites/IInvite.cs @@ -20,6 +20,13 @@ namespace Discord /// string Url { get; } + /// + /// Gets the user that created this invite. + /// + /// + /// A user that created this invite. + /// + IUser Inviter { get; } /// /// Gets the channel this invite is linked to. /// @@ -83,5 +90,19 @@ namespace Discord /// invite points to; null if one cannot be obtained. /// int? MemberCount { get; } + /// + /// Gets the user this invite is linked to via . + /// + /// + /// A user that is linked to this invite. + /// + IUser TargetUser { get; } + /// + /// Gets the type of the linked for this invite. + /// + /// + /// The type of the linked user that is linked to this invite. + /// + TargetUserType TargetUserType { get; } } } diff --git a/src/Discord.Net.Core/Entities/Invites/IInviteMetadata.cs b/src/Discord.Net.Core/Entities/Invites/IInviteMetadata.cs index 471dc377f..c64b3205b 100644 --- a/src/Discord.Net.Core/Entities/Invites/IInviteMetadata.cs +++ b/src/Discord.Net.Core/Entities/Invites/IInviteMetadata.cs @@ -8,28 +8,22 @@ namespace Discord public interface IInviteMetadata : IInvite { /// - /// Gets the user that created this invite. + /// Gets a value that indicates whether the invite is a temporary one. /// /// - /// A user that created this invite. + /// true if users accepting this invite will be removed from the guild when they log off; otherwise + /// false. /// - IUser Inviter { get; } + 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 a value that indicates whether the invite is a temporary one. - /// - /// - /// true if users accepting this invite will be removed from the guild when they log off; otherwise - /// false. - /// - bool IsTemporary { get; } - /// /// Gets the time (in seconds) until the invite expires. /// /// diff --git a/src/Discord.Net.Rest/API/Common/Invite.cs b/src/Discord.Net.Rest/API/Common/Invite.cs index 649bc37ec..aba267f34 100644 --- a/src/Discord.Net.Rest/API/Common/Invite.cs +++ b/src/Discord.Net.Rest/API/Common/Invite.cs @@ -11,9 +11,15 @@ namespace Discord.API public Optional Guild { get; set; } [JsonProperty("channel")] public InviteChannel Channel { get; set; } + [JsonProperty("inviter")] + public Optional Inviter { get; set; } [JsonProperty("approximate_presence_count")] public Optional PresenceCount { get; set; } [JsonProperty("approximate_member_count")] public Optional MemberCount { get; set; } + [JsonProperty("target_user")] + public Optional TargetUser { get; set; } + [JsonProperty("target_user_type")] + public Optional TargetUserType { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/InviteMetadata.cs b/src/Discord.Net.Rest/API/Common/InviteMetadata.cs index ca019b79b..f818de699 100644 --- a/src/Discord.Net.Rest/API/Common/InviteMetadata.cs +++ b/src/Discord.Net.Rest/API/Common/InviteMetadata.cs @@ -6,19 +6,15 @@ namespace Discord.API { internal class InviteMetadata : Invite { - [JsonProperty("inviter")] - public User Inviter { get; set; } [JsonProperty("uses")] - public Optional Uses { get; set; } + public int Uses { get; set; } [JsonProperty("max_uses")] - public Optional MaxUses { get; set; } + public int MaxUses { get; set; } [JsonProperty("max_age")] - public Optional MaxAge { get; set; } + public int MaxAge { get; set; } [JsonProperty("temporary")] public bool Temporary { get; set; } [JsonProperty("created_at")] - public Optional CreatedAt { get; set; } - [JsonProperty("revoked")] - public bool Revoked { get; set; } + public DateTimeOffset CreatedAt { get; set; } } } diff --git a/src/Discord.Net.Rest/Entities/Invites/RestInvite.cs b/src/Discord.Net.Rest/Entities/Invites/RestInvite.cs index 153eb6c41..95b454c20 100644 --- a/src/Discord.Net.Rest/Entities/Invites/RestInvite.cs +++ b/src/Discord.Net.Rest/Entities/Invites/RestInvite.cs @@ -21,6 +21,12 @@ namespace Discord.Rest public ulong ChannelId { get; private set; } /// public ulong? GuildId { get; private set; } + /// + public IUser Inviter { get; private set; } + /// + public IUser TargetUser { get; private set; } + /// + public TargetUserType TargetUserType { get; private set; } internal IChannel Channel { get; } internal IGuild Guild { get; } @@ -50,6 +56,9 @@ namespace Discord.Rest MemberCount = model.MemberCount.IsSpecified ? model.MemberCount.Value : null; PresenceCount = model.PresenceCount.IsSpecified ? model.PresenceCount.Value : null; ChannelType = (ChannelType)model.Channel.Type; + Inviter = model.Inviter.IsSpecified ? RestUser.Create(Discord, model.Inviter.Value) : null; + TargetUser = model.TargetUser.IsSpecified ? RestUser.Create(Discord, model.TargetUser.Value) : null; + TargetUserType = model.TargetUserType.IsSpecified ? model.TargetUserType.Value : TargetUserType.Undefined; } /// diff --git a/src/Discord.Net.Rest/Entities/Invites/RestInviteMetadata.cs b/src/Discord.Net.Rest/Entities/Invites/RestInviteMetadata.cs index 55acd5f45..07ee5851c 100644 --- a/src/Discord.Net.Rest/Entities/Invites/RestInviteMetadata.cs +++ b/src/Discord.Net.Rest/Entities/Invites/RestInviteMetadata.cs @@ -6,9 +6,10 @@ namespace Discord.Rest /// Represents additional information regarding the REST-based invite object. public class RestInviteMetadata : RestInvite, IInviteMetadata { - private long? _createdAtTicks; + 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; } @@ -18,10 +19,6 @@ namespace Discord.Rest public int? MaxUses { get; private set; } /// public int? Uses { get; private set; } - /// - /// Gets the user that created this invite. - /// - public RestUser Inviter { get; private set; } /// public DateTimeOffset? CreatedAt => DateTimeUtils.FromTicks(_createdAtTicks); @@ -39,16 +36,11 @@ namespace Discord.Rest internal void Update(Model model) { base.Update(model); - Inviter = model.Inviter != null ? RestUser.Create(Discord, model.Inviter) : null; - IsRevoked = model.Revoked; IsTemporary = model.Temporary; - MaxAge = model.MaxAge.IsSpecified ? model.MaxAge.Value : (int?)null; - MaxUses = model.MaxUses.IsSpecified ? model.MaxUses.Value : (int?)null; - Uses = model.Uses.IsSpecified ? model.Uses.Value : (int?)null; - _createdAtTicks = model.CreatedAt.IsSpecified ? model.CreatedAt.Value.UtcTicks : (long?)null; + MaxAge = model.MaxAge; + MaxUses = model.MaxUses; + Uses = model.Uses; + _createdAtTicks = model.CreatedAt.UtcTicks; } - - /// - IUser IInviteMetadata.Inviter => Inviter; } } diff --git a/src/Discord.Net.WebSocket/Entities/Invites/SocketInvite.cs b/src/Discord.Net.WebSocket/Entities/Invites/SocketInvite.cs index 902f13935..5dc53a833 100644 --- a/src/Discord.Net.WebSocket/Entities/Invites/SocketInvite.cs +++ b/src/Discord.Net.WebSocket/Entities/Invites/SocketInvite.cs @@ -49,6 +49,7 @@ 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; } @@ -138,6 +139,8 @@ namespace Discord.WebSocket /// IChannel IInvite.Channel => Channel; /// - IUser IInviteMetadata.Inviter => Inviter; + IUser IInvite.Inviter => Inviter; + /// + IUser IInvite.TargetUser => TargetUser; } } From accd3510dfeb64623491580ae2c47fbc815c7da2 Mon Sep 17 00:00:00 2001 From: Still Hsu Date: Sun, 20 Dec 2020 18:46:58 +0900 Subject: [PATCH 05/64] docs: Add alternative documentation link --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9ace4f22e..34a633f72 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,12 @@ An unofficial .NET API Wrapper for the Discord client (https://discord.com). -Check out the [documentation](https://discord.foxbot.me/) or join the [Discord API Chat](https://discord.gg/jkrBmQR). +## Documentation + +- [Stable](https://discord.foxbot.me/) + - Hosted by @foxbot +- [Nightly](https://docs.stillu.cc/) + - [Latest CI repo](https://github.com/discord-net/docs-static) ## Installation ### Stable (NuGet) From a4d32d366d1526562ed3cf074e412161ef5ebe03 Mon Sep 17 00:00:00 2001 From: Paulo Date: Sun, 20 Dec 2020 07:09:44 -0300 Subject: [PATCH 06/64] fix: IMessage.Embeds docs remarks --- src/Discord.Net.Core/Entities/Messages/IMessage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.Core/Entities/Messages/IMessage.cs b/src/Discord.Net.Core/Entities/Messages/IMessage.cs index 35458d87d..1f3f5b945 100644 --- a/src/Discord.Net.Core/Entities/Messages/IMessage.cs +++ b/src/Discord.Net.Core/Entities/Messages/IMessage.cs @@ -92,10 +92,10 @@ namespace Discord /// Gets all embeds included in this message. /// /// - /// /// This property gets a read-only collection of embeds associated with this message. Depending on the /// message, a sent message may contain one or more embeds. This is usually true when multiple link previews /// are generated; however, only one can be featured. + /// /// /// A read-only collection of embed objects. /// From 4a7f8fec937f296bfdbdf60d4ed924e6897f9a75 Mon Sep 17 00:00:00 2001 From: Paulo Date: Sun, 20 Dec 2020 07:37:32 -0300 Subject: [PATCH 07/64] fix: Move and fix internal AllowedMentions object (#1727) --- .../{Entities/Messages => API/Common}/AllowedMentions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/Discord.Net.Rest/{Entities/Messages => API/Common}/AllowedMentions.cs (92%) diff --git a/src/Discord.Net.Rest/Entities/Messages/AllowedMentions.cs b/src/Discord.Net.Rest/API/Common/AllowedMentions.cs similarity index 92% rename from src/Discord.Net.Rest/Entities/Messages/AllowedMentions.cs rename to src/Discord.Net.Rest/API/Common/AllowedMentions.cs index 8b8870be2..7737a464f 100644 --- a/src/Discord.Net.Rest/Entities/Messages/AllowedMentions.cs +++ b/src/Discord.Net.Rest/API/Common/AllowedMentions.cs @@ -2,7 +2,7 @@ using Newtonsoft.Json; namespace Discord.API { - public class AllowedMentions + internal class AllowedMentions { [JsonProperty("parse")] public Optional Parse { get; set; } From 5934c7949a03d492dfc85c94c2c4fc920aaba670 Mon Sep 17 00:00:00 2001 From: Paulo Date: Wed, 23 Dec 2020 12:46:12 -0300 Subject: [PATCH 08/64] misc: VoiceRegions and related changes (#1720) --- src/Discord.Net.WebSocket/BaseSocketClient.cs | 21 +++++++ .../DiscordShardedClient.cs | 14 +++++ .../DiscordSocketClient.cs | 55 +++++++++++++------ 3 files changed, 73 insertions(+), 17 deletions(-) diff --git a/src/Discord.Net.WebSocket/BaseSocketClient.cs b/src/Discord.Net.WebSocket/BaseSocketClient.cs index 548bb75bf..425889613 100644 --- a/src/Discord.Net.WebSocket/BaseSocketClient.cs +++ b/src/Discord.Net.WebSocket/BaseSocketClient.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; @@ -75,6 +76,7 @@ namespace Discord.WebSocket /// /// 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) @@ -169,7 +171,26 @@ namespace Discord.WebSocket /// 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. + /// + /// A task that contains a read-only collection of REST-based voice regions. + /// + public abstract ValueTask> GetVoiceRegionsAsync(RequestOptions options = null); + /// + /// Gets a voice region. + /// + /// The identifier of the voice region (e.g. eu-central ). + /// The options to be used when sending the request. + /// + /// A task that contains a REST-based voice region associated with the identifier; null if the + /// voice region is not found. + /// + public abstract ValueTask GetVoiceRegionAsync(string id, RequestOptions options = null); /// public abstract Task StartAsync(); /// diff --git a/src/Discord.Net.WebSocket/DiscordShardedClient.cs b/src/Discord.Net.WebSocket/DiscordShardedClient.cs index a2c89d4e5..4c94b14e8 100644 --- a/src/Discord.Net.WebSocket/DiscordShardedClient.cs +++ b/src/Discord.Net.WebSocket/DiscordShardedClient.cs @@ -37,6 +37,7 @@ 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; /// @@ -264,9 +265,22 @@ namespace Discord.WebSocket } /// + [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) + { + return await _shards[0].GetVoiceRegionsAsync().ConfigureAwait(false); + } + + /// + public override async ValueTask GetVoiceRegionAsync(string id, RequestOptions options = null) + { + return await _shards[0].GetVoiceRegionAsync(id, options).ConfigureAwait(false); + } + /// /// is public override async Task DownloadUsersAsync(IEnumerable guilds) diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 0a2123ef2..d4c96ab26 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -110,7 +110,8 @@ namespace Discord.WebSocket public IReadOnlyCollection GroupChannels => State.PrivateChannels.OfType().ToImmutableArray(); /// - public override IReadOnlyCollection VoiceRegions => _voiceRegions.ToReadOnlyCollection(); + [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. @@ -178,7 +179,6 @@ namespace Discord.WebSocket return Task.Delay(0); }; - _voiceRegions = ImmutableDictionary.Create(); _largeGuilds = new ConcurrentQueue(); } private static API.DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) @@ -204,13 +204,6 @@ namespace Discord.WebSocket /// internal override async Task OnLoginAsync(TokenType tokenType, string token) { - if (_parentClient == null) - { - var voiceRegions = await ApiClient.GetVoiceRegionsAsync(new RequestOptions { IgnoreState = true, RetryMode = RetryMode.AlwaysRetry }).ConfigureAwait(false); - _voiceRegions = voiceRegions.Select(x => RestVoiceRegion.Create(this, x)).ToImmutableDictionary(x => x.Id); - } - else - _voiceRegions = _parentClient._voiceRegions; await Rest.OnLoginAsync(tokenType, token); } /// @@ -218,7 +211,7 @@ namespace Discord.WebSocket { await StopAsync().ConfigureAwait(false); _applicationInfo = null; - _voiceRegions = ImmutableDictionary.Create(); + _voiceRegions = null; await Rest.OnLogoutAsync(); } @@ -350,11 +343,39 @@ namespace Discord.WebSocket => 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) { - if (_voiceRegions.TryGetValue(id, out RestVoiceRegion region)) - return region; - return null; + if (_parentClient == null) + { + if (_voiceRegions == null) + { + options = RequestOptions.CreateOrClone(options); + options.IgnoreState = true; + var voiceRegions = await ApiClient.GetVoiceRegionsAsync(options).ConfigureAwait(false); + _voiceRegions = voiceRegions.Select(x => RestVoiceRegion.Create(this, x)).ToImmutableDictionary(x => x.Id); + } + return _voiceRegions.ToReadOnlyCollection(); + } + return await _parentClient.GetVoiceRegionsAsync().ConfigureAwait(false); + } + + /// + public override async ValueTask GetVoiceRegionAsync(string id, RequestOptions options = null) + { + if (_parentClient == null) + { + if (_voiceRegions == null) + await GetVoiceRegionsAsync().ConfigureAwait(false); + if (_voiceRegions.TryGetValue(id, out RestVoiceRegion region)) + return region; + return null; + } + return await _parentClient.GetVoiceRegionAsync(id, options).ConfigureAwait(false); } /// @@ -2120,11 +2141,11 @@ namespace Discord.WebSocket => Task.FromResult(GetUser(username, discriminator)); /// - Task> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options) - => Task.FromResult>(VoiceRegions); + async Task> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options) + => await GetVoiceRegionsAsync(options).ConfigureAwait(false); /// - Task IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options) - => Task.FromResult(GetVoiceRegion(id)); + async Task IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options) + => await GetVoiceRegionAsync(id, options).ConfigureAwait(false); /// async Task IDiscordClient.StartAsync() From 6a62c4770c363710e555c510619986f30b683324 Mon Sep 17 00:00:00 2001 From: Paulo Date: Wed, 23 Dec 2020 12:46:27 -0300 Subject: [PATCH 09/64] feature: Add role tags (#1721) --- src/Discord.Net.Core/Entities/Roles/IRole.cs | 7 ++++ .../Entities/Roles/RoleTags.cs | 40 +++++++++++++++++++ src/Discord.Net.Rest/API/Common/Role.cs | 4 +- src/Discord.Net.Rest/API/Common/RoleTags.cs | 15 +++++++ .../Entities/Roles/RestRole.cs | 4 ++ .../Extensions/EntityExtensions.cs | 7 ++++ .../Entities/Roles/SocketRole.cs | 4 ++ 7 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 src/Discord.Net.Core/Entities/Roles/RoleTags.cs create mode 100644 src/Discord.Net.Rest/API/Common/RoleTags.cs diff --git a/src/Discord.Net.Core/Entities/Roles/IRole.cs b/src/Discord.Net.Core/Entities/Roles/IRole.cs index 66556fc2c..c02322be9 100644 --- a/src/Discord.Net.Core/Entities/Roles/IRole.cs +++ b/src/Discord.Net.Core/Entities/Roles/IRole.cs @@ -65,6 +65,13 @@ namespace Discord /// An representing the position of the role in the role list of the guild. /// int Position { get; } + /// + /// Gets the tags related to this role. + /// + /// + /// A object containing all tags related to this role. + /// + RoleTags Tags { get; } /// /// Modifies this role. diff --git a/src/Discord.Net.Core/Entities/Roles/RoleTags.cs b/src/Discord.Net.Core/Entities/Roles/RoleTags.cs new file mode 100644 index 000000000..d0cbd3580 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Roles/RoleTags.cs @@ -0,0 +1,40 @@ +namespace Discord +{ + /// + /// Provides tags related to a discord role. + /// + public class RoleTags + { + /// + /// Gets the identifier of the bot that this role belongs to, if it does. + /// + /// + /// A if this role belongs to a bot; otherwise + /// . + /// + public ulong? BotId { get; } + /// + /// Gets the identifier of the integration that this role belongs to, if it does. + /// + /// + /// A if this role belongs to an integration; otherwise + /// . + /// + public ulong? IntegrationId { get; } + /// + /// Gets if this role is the guild's premium subscriber (booster) role. + /// + /// + /// if this role is the guild's premium subscriber role; + /// otherwise . + /// + public bool IsPremiumSubscriberRole { get; } + + internal RoleTags(ulong? botId, ulong? integrationId, bool isPremiumSubscriber) + { + BotId = botId; + IntegrationId = integrationId; + IsPremiumSubscriberRole = isPremiumSubscriber; + } + } +} diff --git a/src/Discord.Net.Rest/API/Common/Role.cs b/src/Discord.Net.Rest/API/Common/Role.cs index 856a8695f..190ae25c0 100644 --- a/src/Discord.Net.Rest/API/Common/Role.cs +++ b/src/Discord.Net.Rest/API/Common/Role.cs @@ -1,4 +1,4 @@ -#pragma warning disable CS1591 +#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API @@ -21,5 +21,7 @@ namespace Discord.API public ulong Permissions { get; set; } [JsonProperty("managed")] public bool Managed { get; set; } + [JsonProperty("tags")] + public Optional Tags { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/RoleTags.cs b/src/Discord.Net.Rest/API/Common/RoleTags.cs new file mode 100644 index 000000000..6446f2037 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/RoleTags.cs @@ -0,0 +1,15 @@ +#pragma warning disable CS1591 +using Newtonsoft.Json; + +namespace Discord.API +{ + internal class RoleTags + { + [JsonProperty("bot_id")] + public Optional BotId { get; set; } + [JsonProperty("integration_id")] + public Optional IntegrationId { get; set; } + [JsonProperty("premium_subscriber")] + public Optional IsPremiumSubscriber { get; set; } + } +} diff --git a/src/Discord.Net.Rest/Entities/Roles/RestRole.cs b/src/Discord.Net.Rest/Entities/Roles/RestRole.cs index 7c1a3aaa2..aa33ae7e5 100644 --- a/src/Discord.Net.Rest/Entities/Roles/RestRole.cs +++ b/src/Discord.Net.Rest/Entities/Roles/RestRole.cs @@ -26,6 +26,8 @@ namespace Discord.Rest public GuildPermissions Permissions { get; private set; } /// public int Position { get; private set; } + /// + public RoleTags Tags { get; private set; } /// public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); @@ -56,6 +58,8 @@ namespace Discord.Rest Position = model.Position; Color = new Color(model.Color); Permissions = new GuildPermissions(model.Permissions); + if (model.Tags.IsSpecified) + Tags = model.Tags.Value.ToEntity(); } /// diff --git a/src/Discord.Net.Rest/Extensions/EntityExtensions.cs b/src/Discord.Net.Rest/Extensions/EntityExtensions.cs index 8e1b9c6d8..f8676c783 100644 --- a/src/Discord.Net.Rest/Extensions/EntityExtensions.cs +++ b/src/Discord.Net.Rest/Extensions/EntityExtensions.cs @@ -34,6 +34,13 @@ namespace Discord.Rest model.Thumbnail.IsSpecified ? model.Thumbnail.Value.ToEntity() : (EmbedThumbnail?)null, model.Fields.IsSpecified ? model.Fields.Value.Select(x => x.ToEntity()).ToImmutableArray() : ImmutableArray.Create()); } + public static RoleTags ToEntity(this API.RoleTags model) + { + return new RoleTags( + model.BotId.IsSpecified ? model.BotId.Value : null, + model.IntegrationId.IsSpecified ? model.IntegrationId.Value : null, + model.IsPremiumSubscriber.IsSpecified ? true : false); + } public static API.Embed ToModel(this Embed entity) { if (entity == null) return null; diff --git a/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs b/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs index b5e26ad78..e6aac2c04 100644 --- a/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs +++ b/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs @@ -36,6 +36,8 @@ namespace Discord.WebSocket public GuildPermissions Permissions { get; private set; } /// public int Position { get; private set; } + /// + public RoleTags Tags { get; private set; } /// public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); @@ -71,6 +73,8 @@ namespace Discord.WebSocket Position = model.Position; Color = new Color(model.Color); Permissions = new GuildPermissions(model.Permissions); + if (model.Tags.IsSpecified) + Tags = model.Tags.Value.ToEntity(); } /// From c683b2901d5768bb402993765c9c329aa26537eb Mon Sep 17 00:00:00 2001 From: Paulo Date: Wed, 23 Dec 2020 12:46:42 -0300 Subject: [PATCH 10/64] feature: Add user public flags (#1722) --- src/Discord.Net.Core/Entities/Users/IUser.cs | 10 ++++ .../Entities/Users/UserProperties.cs | 48 +++++++++++++++---- src/Discord.Net.Rest/API/Common/User.cs | 2 + .../Entities/Users/RestUser.cs | 4 ++ .../Entities/Users/SocketUser.cs | 7 +++ 5 files changed, 62 insertions(+), 9 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Users/IUser.cs b/src/Discord.Net.Core/Entities/Users/IUser.cs index c36fb2326..e6008aab6 100644 --- a/src/Discord.Net.Core/Entities/Users/IUser.cs +++ b/src/Discord.Net.Core/Entities/Users/IUser.cs @@ -75,6 +75,16 @@ namespace Discord /// Gets the username for this user. /// string Username { get; } + /// + /// Gets the public flags that are applied to this user's account. + /// + /// + /// This value is determined by bitwise OR-ing values together. + /// + /// + /// The value of public flags for this user. + /// + UserProperties? PublicFlags { get; } /// /// Gets the direct message channel of this user, or create one if it does not already exist. diff --git a/src/Discord.Net.Core/Entities/Users/UserProperties.cs b/src/Discord.Net.Core/Entities/Users/UserProperties.cs index 4f7272daa..b6deb744b 100644 --- a/src/Discord.Net.Core/Entities/Users/UserProperties.cs +++ b/src/Discord.Net.Core/Entities/Users/UserProperties.cs @@ -10,32 +10,62 @@ namespace Discord /// None = 0, /// - /// Flag given to Discord staff. + /// Flag given to users who are a Discord employee. /// - Staff = 0b1, + Staff = 1 << 0, /// - /// Flag given to Discord partners. + /// Flag given to users who are owners of a partnered Discord server. /// - Partner = 0b10, + Partner = 1 << 1, + /// + /// Flag given to users in HypeSquad events. + /// + 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. /// - BugHunter = 0b1000, + BugHunterLevel1 = 1 << 3, /// /// Flag given to users who are in the HypeSquad House of Bravery. /// - HypeSquadBravery = 0b100_0000, + HypeSquadBravery = 1 << 6, /// /// Flag given to users who are in the HypeSquad House of Brilliance. /// - HypeSquadBrilliance = 0b1000_0000, + HypeSquadBrilliance = 1 << 7, /// /// Flag given to users who are in the HypeSquad House of Balance. /// - HypeSquadBalance = 0b1_0000_0000, + HypeSquadBalance = 1 << 8, /// /// Flag given to users who subscribed to Nitro before games were added. /// - EarlySupporter = 0b10_0000_0000, + EarlySupporter = 1 << 9, + /// + /// Flag given to users who are part of a team. + /// + TeamUser = 1 << 10, + /// + /// Flag given to users who represent Discord (System). + /// + System = 1 << 12, + /// + /// Flag given to users who have participated in the bug report program and are level 2. + /// + BugHunterLevel2 = 1 << 14, + /// + /// Flag given to users who are verified bots. + /// + VerifiedBot = 1 << 16, + /// + /// Flag given to users that developed bots and early verified their accounts. + /// + EarlyVerifiedBotDeveloper = 1 << 17, } } diff --git a/src/Discord.Net.Rest/API/Common/User.cs b/src/Discord.Net.Rest/API/Common/User.cs index 2eff3753d..d1f436afb 100644 --- a/src/Discord.Net.Rest/API/Common/User.cs +++ b/src/Discord.Net.Rest/API/Common/User.cs @@ -29,5 +29,7 @@ namespace Discord.API public Optional PremiumType { get; set; } [JsonProperty("locale")] public Optional Locale { get; set; } + [JsonProperty("public_flags")] + public Optional PublicFlags { get; set; } } } diff --git a/src/Discord.Net.Rest/Entities/Users/RestUser.cs b/src/Discord.Net.Rest/Entities/Users/RestUser.cs index f5becd3ff..131a4ec73 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestUser.cs @@ -21,6 +21,8 @@ namespace Discord.Rest public ushort DiscriminatorValue { get; private set; } /// public string AvatarId { get; private set; } + /// + public UserProperties? PublicFlags { get; private set; } /// public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); @@ -65,6 +67,8 @@ namespace Discord.Rest IsBot = model.Bot.Value; if (model.Username.IsSpecified) Username = model.Username.Value; + if (model.PublicFlags.IsSpecified) + PublicFlags = model.PublicFlags.Value; } /// diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs index 7d3c2d23b..4e6d4b3f8 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs @@ -26,6 +26,8 @@ namespace Discord.WebSocket public abstract string AvatarId { get; internal set; } /// public abstract bool IsWebhook { get; } + /// + public UserProperties? PublicFlags { get; private set; } internal abstract SocketGlobalUser GlobalUser { get; } internal abstract SocketPresence Presence { get; set; } @@ -83,6 +85,11 @@ namespace Discord.WebSocket Username = model.Username.Value; hasChanges = true; } + if (model.PublicFlags.IsSpecified && model.PublicFlags.Value != PublicFlags) + { + PublicFlags = model.PublicFlags.Value; + hasChanges = true; + } return hasChanges; } From 225550dc5ac64cec1a38e5cfc1699bd0db0813ee Mon Sep 17 00:00:00 2001 From: Paulo Date: Wed, 23 Dec 2020 12:47:20 -0300 Subject: [PATCH 11/64] feature: Add MessageFlags and AllowedMentions to message modify (#1724) * feature: Add MessageFlags and AllowedMentions to Modify * Change exception message --- .../Entities/Messages/IMessage.cs | 11 ++++++ .../Entities/Messages/MessageFlags.cs | 36 +++++++++++++++++++ .../Entities/Messages/MessageProperties.cs | 12 +++++++ .../API/Common/MessageFlags.cs | 10 ------ .../API/Rest/ModifyMessageParams.cs | 6 +++- .../Entities/Messages/MessageHelper.cs | 33 ++++++++++++++--- .../Entities/Messages/RestMessage.cs | 5 +++ .../Entities/Messages/RestUserMessage.cs | 8 ++--- .../Entities/Messages/SocketMessage.cs | 6 ++++ .../Entities/Messages/SocketUserMessage.cs | 8 ++--- 10 files changed, 108 insertions(+), 27 deletions(-) create mode 100644 src/Discord.Net.Core/Entities/Messages/MessageFlags.cs delete mode 100644 src/Discord.Net.Rest/API/Common/MessageFlags.cs diff --git a/src/Discord.Net.Core/Entities/Messages/IMessage.cs b/src/Discord.Net.Core/Entities/Messages/IMessage.cs index 1f3f5b945..80b1ffa68 100644 --- a/src/Discord.Net.Core/Entities/Messages/IMessage.cs +++ b/src/Discord.Net.Core/Entities/Messages/IMessage.cs @@ -164,6 +164,17 @@ namespace Discord /// IReadOnlyDictionary Reactions { get; } + /// + /// Gets the flags related to this message. + /// + /// + /// This value is determined by bitwise OR-ing values together. + /// + /// + /// A message's flags, if any is associated. + /// + MessageFlags? Flags { get; } + /// /// Adds a reaction to this message. /// diff --git a/src/Discord.Net.Core/Entities/Messages/MessageFlags.cs b/src/Discord.Net.Core/Entities/Messages/MessageFlags.cs new file mode 100644 index 000000000..52d0f0e9e --- /dev/null +++ b/src/Discord.Net.Core/Entities/Messages/MessageFlags.cs @@ -0,0 +1,36 @@ +using System; + +namespace Discord +{ + [Flags] + public enum MessageFlags + { + /// + /// Default value for flags, when none are given to a message. + /// + None = 0, + /// + /// Flag given to messages that have been published to subscribed + /// channels (via Channel Following). + /// + Crossposted = 1 << 0, + /// + /// Flag given to messages that originated from a message in another + /// channel (via Channel Following). + /// + IsCrosspost = 1 << 1, + /// + /// Flag given to messages that do not display any embeds. + /// + SuppressEmbeds = 1 << 2, + /// + /// Flag given to messages that the source message for this crosspost + /// has been deleted (via Channel Following). + /// + SourceMessageDeleted = 1 << 3, + /// + /// Flag given to messages that came from the urgent message system. + /// + Urgent = 1 << 4, + } +} diff --git a/src/Discord.Net.Core/Entities/Messages/MessageProperties.cs b/src/Discord.Net.Core/Entities/Messages/MessageProperties.cs index b632d6a18..9504e04cb 100644 --- a/src/Discord.Net.Core/Entities/Messages/MessageProperties.cs +++ b/src/Discord.Net.Core/Entities/Messages/MessageProperties.cs @@ -21,5 +21,17 @@ namespace Discord /// Gets or sets the embed the message should display. /// public Optional Embed { get; set; } + /// + /// Gets or sets the flags of the message. + /// + /// + /// Only can be set/unset and you need to be + /// the author of the message. + /// + public Optional Flags { get; set; } + /// + /// Gets or sets the allowed mentions of the message. + /// + public Optional AllowedMentions { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/MessageFlags.cs b/src/Discord.Net.Rest/API/Common/MessageFlags.cs deleted file mode 100644 index ebe4e80ca..000000000 --- a/src/Discord.Net.Rest/API/Common/MessageFlags.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; - -namespace Discord.API -{ - [Flags] - internal enum MessageFlags : byte // probably safe to constrain this to 8 values, if not, it's internal so who cares - { - Suppressed = 0x04, - } -} diff --git a/src/Discord.Net.Rest/API/Rest/ModifyMessageParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyMessageParams.cs index fdff4de15..3752df3a2 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyMessageParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyMessageParams.cs @@ -1,4 +1,4 @@ -#pragma warning disable CS1591 +#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Rest @@ -10,5 +10,9 @@ namespace Discord.API.Rest public Optional Content { get; set; } [JsonProperty("embed")] public Optional Embed { get; set; } + [JsonProperty("flags")] + public Optional Flags { get; set; } + [JsonProperty("allowed_mentions")] + public Optional AllowedMentions { get; set; } } } diff --git a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs index b86a5dbf3..e22ae71cd 100644 --- a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs +++ b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs @@ -27,21 +27,46 @@ namespace Discord.Rest public static async Task ModifyAsync(IMessage msg, BaseDiscordClient client, Action func, RequestOptions options) { - if (msg.Author.Id != client.CurrentUser.Id) - throw new InvalidOperationException("Only the author of a message may modify the message."); - var args = new MessageProperties(); func(args); + if (msg.Author.Id != client.CurrentUser.Id && (args.Content.IsSpecified || args.Embed.IsSpecified || args.AllowedMentions.IsSpecified)) + throw new InvalidOperationException("Only the author of a message may modify the message content, embed, or allowed mentions."); + bool hasText = args.Content.IsSpecified ? !string.IsNullOrEmpty(args.Content.Value) : !string.IsNullOrEmpty(msg.Content); bool hasEmbed = args.Embed.IsSpecified ? args.Embed.Value != null : msg.Embeds.Any(); if (!hasText && !hasEmbed) Preconditions.NotNullOrEmpty(args.Content.IsSpecified ? args.Content.Value : string.Empty, nameof(args.Content)); + if (args.AllowedMentions.IsSpecified) + { + AllowedMentions allowedMentions = args.AllowedMentions.Value; + Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); + Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); + + // check that user flag and user Id list are exclusive, same with role flag and role Id list + if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue) + { + if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users) && + allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0) + { + throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(allowedMentions)); + } + + if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles) && + allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0) + { + throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(allowedMentions)); + } + } + } + var apiArgs = new API.Rest.ModifyMessageParams { Content = args.Content, - Embed = args.Embed.IsSpecified ? args.Embed.Value.ToModel() : Optional.Create() + Embed = args.Embed.IsSpecified ? args.Embed.Value.ToModel() : Optional.Create(), + Flags = args.Flags.IsSpecified ? args.Flags.Value : Optional.Create(), + AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value.ToModel() : Optional.Create(), }; return await client.ApiClient.ModifyMessageAsync(msg.Channel.Id, msg.Id, apiArgs, options).ConfigureAwait(false); } diff --git a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs index 0169c9b8b..b2a745980 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs @@ -67,6 +67,8 @@ namespace Discord.Rest public MessageApplication Application { get; private set; } /// public MessageReference Reference { get; private set; } + /// + public MessageFlags? Flags { get; private set; } internal RestMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, IUser author, MessageSource source) : base(discord, id) @@ -124,6 +126,9 @@ namespace Discord.Rest }; } + if (model.Flags.IsSpecified) + Flags = model.Flags.Value; + if (model.Reactions.IsSpecified) { var value = model.Reactions.Value; diff --git a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs index 2c76a4253..cf025aea1 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs @@ -13,7 +13,7 @@ namespace Discord.Rest [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class RestUserMessage : RestMessage, IUserMessage { - private bool _isMentioningEveryone, _isTTS, _isPinned, _isSuppressed; + private bool _isMentioningEveryone, _isTTS, _isPinned; private long? _editedTimestampTicks; private IUserMessage _referencedMessage; private ImmutableArray _attachments = ImmutableArray.Create(); @@ -27,7 +27,7 @@ namespace Discord.Rest /// public override bool IsPinned => _isPinned; /// - public override bool IsSuppressed => _isSuppressed; + public override bool IsSuppressed => Flags.HasValue && Flags.Value.HasFlag(MessageFlags.SuppressEmbeds); /// public override DateTimeOffset? EditedTimestamp => DateTimeUtils.FromTicks(_editedTimestampTicks); /// @@ -70,10 +70,6 @@ namespace Discord.Rest _editedTimestampTicks = model.EditedTimestamp.Value?.UtcTicks; if (model.MentionEveryone.IsSpecified) _isMentioningEveryone = model.MentionEveryone.Value; - if (model.Flags.IsSpecified) - { - _isSuppressed = model.Flags.Value.HasFlag(API.MessageFlags.Suppressed); - } if (model.RoleMentions.IsSpecified) _roleMentionIds = model.RoleMentions.Value.ToImmutableArray(); diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs index 98fd4af07..2ca53cbb9 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs @@ -58,6 +58,9 @@ namespace Discord.WebSocket /// public MessageReference Reference { get; private set; } + /// + public MessageFlags? Flags { get; private set; } + /// /// Returns all attachments included in this message. /// @@ -156,6 +159,9 @@ namespace Discord.WebSocket MessageId = model.Reference.Value.MessageId }; } + + if (model.Flags.IsSpecified) + Flags = model.Flags.Value; } /// diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs index bea0f45eb..859b1b80a 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs @@ -15,7 +15,7 @@ namespace Discord.WebSocket [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class SocketUserMessage : SocketMessage, IUserMessage { - private bool _isMentioningEveryone, _isTTS, _isPinned, _isSuppressed; + private bool _isMentioningEveryone, _isTTS, _isPinned; private long? _editedTimestampTicks; private IUserMessage _referencedMessage; private ImmutableArray _attachments = ImmutableArray.Create(); @@ -29,7 +29,7 @@ namespace Discord.WebSocket /// public override bool IsPinned => _isPinned; /// - public override bool IsSuppressed => _isSuppressed; + public override bool IsSuppressed => Flags.HasValue && Flags.Value.HasFlag(MessageFlags.SuppressEmbeds); /// public override DateTimeOffset? EditedTimestamp => DateTimeUtils.FromTicks(_editedTimestampTicks); /// @@ -74,10 +74,6 @@ namespace Discord.WebSocket _editedTimestampTicks = model.EditedTimestamp.Value?.UtcTicks; if (model.MentionEveryone.IsSpecified) _isMentioningEveryone = model.MentionEveryone.Value; - if (model.Flags.IsSpecified) - { - _isSuppressed = model.Flags.Value.HasFlag(API.MessageFlags.Suppressed); - } if (model.RoleMentions.IsSpecified) _roleMentions = model.RoleMentions.Value.Select(x => guild.GetRole(x)).ToImmutableArray(); From 8b25c9bbfb88818f0a3eda6ca04ba3c06a4fec43 Mon Sep 17 00:00:00 2001 From: quinchs <49576606+quinchs@users.noreply.github.com> Date: Wed, 23 Dec 2020 11:47:54 -0400 Subject: [PATCH 12/64] feature: Add GuildUser IsPending property (#1731) * Implemented Pending property * Implemented changes --- src/Discord.Net.Core/Entities/Users/IGuildUser.cs | 5 +++++ src/Discord.Net.Rest/API/Common/GuildMember.cs | 2 ++ src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs | 4 ++++ src/Discord.Net.Rest/Entities/Users/RestWebhookUser.cs | 2 ++ src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs | 4 ++++ .../Entities/Users/SocketWebhookUser.cs | 2 ++ 6 files changed, 19 insertions(+) diff --git a/src/Discord.Net.Core/Entities/Users/IGuildUser.cs b/src/Discord.Net.Core/Entities/Users/IGuildUser.cs index 0e4004c46..582e9e7a7 100644 --- a/src/Discord.Net.Core/Entities/Users/IGuildUser.cs +++ b/src/Discord.Net.Core/Entities/Users/IGuildUser.cs @@ -68,6 +68,11 @@ namespace Discord /// IReadOnlyCollection RoleIds { get; } + /// + /// Whether the user has passed the guild's Membership Screening requirements. + /// + bool? IsPending { get; } + /// /// Gets the level permissions granted to this user to a given channel. /// diff --git a/src/Discord.Net.Rest/API/Common/GuildMember.cs b/src/Discord.Net.Rest/API/Common/GuildMember.cs index 940eb925a..fc2092d6c 100644 --- a/src/Discord.Net.Rest/API/Common/GuildMember.cs +++ b/src/Discord.Net.Rest/API/Common/GuildMember.cs @@ -18,6 +18,8 @@ namespace Discord.API public Optional Deaf { get; set; } [JsonProperty("mute")] public Optional Mute { get; set; } + [JsonProperty("pending")] + public Optional Pending { get; set; } [JsonProperty("premium_since")] public Optional PremiumSince { get; set; } } diff --git a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs index d6a8c2eda..4a6bc1025 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs @@ -29,6 +29,8 @@ namespace Discord.Rest public DateTimeOffset? PremiumSince => DateTimeUtils.FromTicks(_premiumSinceTicks); /// public ulong GuildId => Guild.Id; + /// + public bool? IsPending { get; private set; } /// /// Resolving permissions requires the parent guild to be downloaded. @@ -73,6 +75,8 @@ namespace Discord.Rest UpdateRoles(model.Roles.Value); if (model.PremiumSince.IsSpecified) _premiumSinceTicks = model.PremiumSince.Value?.UtcTicks; + if (model.Pending.IsSpecified) + IsPending = model.Pending.Value; } private void UpdateRoles(ulong[] roleIds) { diff --git a/src/Discord.Net.Rest/Entities/Users/RestWebhookUser.cs b/src/Discord.Net.Rest/Entities/Users/RestWebhookUser.cs index 8462cb8d4..a06916c9b 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestWebhookUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestWebhookUser.cs @@ -52,6 +52,8 @@ namespace Discord.Rest /// string IGuildUser.Nickname => null; /// + bool? IGuildUser.IsPending => null; + /// GuildPermissions IGuildUser.GuildPermissions => GuildPermissions.Webhook; /// diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs index a506a5d7f..c0a681d9d 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs @@ -57,6 +57,8 @@ namespace Discord.WebSocket /// public bool IsStreaming => VoiceState?.IsStreaming ?? false; /// + public bool? IsPending { get; private set; } + /// public DateTimeOffset? JoinedAt => DateTimeUtils.FromTicks(_joinedAtTicks); /// /// Returns a collection of roles that the user possesses. @@ -142,6 +144,8 @@ namespace Discord.WebSocket UpdateRoles(model.Roles.Value); if (model.PremiumSince.IsSpecified) _premiumSinceTicks = model.PremiumSince.Value?.UtcTicks; + if (model.Pending.IsSpecified) + IsPending = model.Pending.Value; } internal void Update(ClientState state, PresenceModel model, bool updatePresence) { diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs index d400e1ae7..5103aa8b3 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs @@ -65,6 +65,8 @@ namespace Discord.WebSocket /// DateTimeOffset? IGuildUser.PremiumSince => null; /// + bool? IGuildUser.IsPending => null; + /// GuildPermissions IGuildUser.GuildPermissions => GuildPermissions.Webhook; /// From 20957016653e2dba1a7a1007f2c684b7c02a6ee8 Mon Sep 17 00:00:00 2001 From: Paulo Date: Thu, 21 Jan 2021 18:08:46 -0300 Subject: [PATCH 13/64] fix: Missing MessageReference when sending files --- src/Discord.Net.Rest/API/Rest/UploadFileParams.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Discord.Net.Rest/API/Rest/UploadFileParams.cs b/src/Discord.Net.Rest/API/Rest/UploadFileParams.cs index a7d19b4ef..dd442a6de 100644 --- a/src/Discord.Net.Rest/API/Rest/UploadFileParams.cs +++ b/src/Discord.Net.Rest/API/Rest/UploadFileParams.cs @@ -49,6 +49,8 @@ namespace Discord.API.Rest payload["allowed_mentions"] = AllowedMentions.Value; if (IsSpoiler) payload["hasSpoiler"] = IsSpoiler.ToString(); + if (MessageReference.IsSpecified) + payload["message_reference"] = MessageReference.Value; var json = new StringBuilder(); using (var text = new StringWriter(json)) From 6bb54577ab15c192cc678fb37150c1e313d0a81a Mon Sep 17 00:00:00 2001 From: Paulo Date: Thu, 28 Jan 2021 19:13:18 -0300 Subject: [PATCH 14/64] meta: 2.3.0 --- CHANGELOG.md | 91 ++++++++++++++++++++++++++++++ Discord.Net.targets | 2 +- src/Discord.Net/Discord.Net.nuspec | 32 +++++------ 3 files changed, 108 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 886754052..7f4041a7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,96 @@ # Changelog +## [2.3.0] - 2021-01-28 +### Added +- #1491 Add INVITE_CREATE and INVITE_DELETE events (1ab670b) +- #1520 Support reading multiple activities (421a0c1) +- #1521 Allow for inherited commands in modules (a51cdf6) +- #1526 Add Direction.Around to GetMessagesAsync (f2130f8) +- #1537 Implement gateway ratelimit (ec673e1) +- #1544 Add MESSAGE_REACTION_REMOVE_EMOJI and RemoveAllReactionsForEmoteAsync (a89f076) +- #1549 Add GetUsersAsync to SocketGuild (30b5a83) +- #1566 Support Gateway Intents (d5d10d3) +- #1573 Add missing properties to Guild and deprecate GuildEmbed (ec212b1) +- #1581 Add includeRoleIds to PruneUsersAsync (a80e5ff) +- #1588 Add GetStreams to AudioClient (1e012ac) +- #1596 Add missing channel properties (2d80037) +- #1604 Add missing application properties (including Teams) (10fcde0) +- #1619 Add "View Guild Insights" to GuildPermission (2592264) +- #1637 Added CultureInvariant RegexOption to WebhookUrlRegex (e3925a7) +- #1659 Add inline replies (e3850e1) +- #1688 Send presence on Identify payload (25d5d36) +- #1721 Add role tags (6a62c47) +- #1722 Add user public flags (c683b29) +- #1724 Add MessageFlags and AllowedMentions to message modify (225550d) +- #1731 Add GuildUser IsPending property (8b25c9b) +- #1690 Add max bitrate value to SocketGuild (aacfea0) + +### Fixed +- #1244 Missing AddReactions permission for DM channels. (e40ca4a) +- #1469 unsupported property causes an exception (468f826) +- #1525 AllowedMentions and AllowedMentionTypes (3325031) +- #1531 Add AllowedMentions to SendFileAsync (ab32607) +- #1532 GuildEmbed.ChannelId as nullable per API documentation (971d519) +- #1546 Different ratelimits for the same route (implement discord buckets) (2f6c017) +- #1548 Incomplete Ready, DownloadUsersAsync, and optimize AlwaysDownloadUsers (dc8c959) +- #1555 InvalidOperationException at MESSAGE_CREATE (bd4672a) +- #1557 Sending 2 requests instead of 1 to create a Guild role. (5430cc8) +- #1571 Not using the new domain name. (df8a0f7) +- #1578 Trim token before passing it to the authorization header (42ba372) +- #1580 Stop TaskCanceledException from bubbling up (b8fa464) +- #1599 Invite audit log without inviter (b95b95b) +- #1602 Add AllowedMentions to webhooks (bd4516b) +- #1603 Cancel reconnection when 4014 (f396cd9) +- #1608 Voice overwrites and CategoryId remarks (43c8fc0) +- #1614 Check error 404 and return null for GetBanAsync (ae9fff6) +- #1621 Parse mentions from message payload (366ca9a) +- #1622 Do not update overwrite cache locally (3860da0) +- #1623 Invoke UserUpdated from GuildMemberUpdated if needed (3085e88) +- #1624 Handle null PreferredLocale in rare cases (c1d04b4) +- #1639 Invite and InviteMetadata properties (dd2e524) +- #1642 Add missing permissions (4b389f3) +- #1647 handicap member downloading for verified bots (fa5ef5e) +- #1652 Update README.MD to reflect new discord domain (03b831e) +- #1667 Audio stream dispose (a2af985) +- #1671 Crosspost throwing InvalidOperationException (9134443) +- #1672 Team is nullable, not optional (be60d81) +- #1681 Emoji url encode (04389a4) +- #1683 SocketGuild.HasAllMembers is false if a user left a guild (47f571e) +- #1686 Revert PremiumSubscriptionCount type (97e71cd) +- #1695 Possible NullReferenceException when receiving InvalidSession (5213916) +- #1702 Rollback Activities to Game (9d7cb39) +- #1727 Move and fix internal AllowedMentions object (4a7f8fe) +- limit request members batch size (084db25) +- UserMentions throwing NullRef (5ed01a3) +- Wrong author for SocketUserMessage.ReferencedMessage (1e9b252) +- Discord sends null when there's no team (05a1f0a) +- IMessage.Embeds docs remarks (a4d32d3) +- Missing MessageReference when sending files (2095701) + +### Misc +- #1545 MutualGuilds optimization (323a677) +- #1551 Update webhook regex to support discord.com (7585789) +- #1556 Add SearchUsersAsync (57880de) +- #1561 Minor refactor to switch expression (42826df) +- #1576 Updating comments for privileged intents (c42bfa6) +- #1678 Change ratelimit messages (47ed806) +- #1714 Update summary of SocketVoiceChannel.Users (e385c40) +- #1720 VoiceRegions and related changes (5934c79) +- Add updated libraries for LastModified (d761846) +- Add alternative documentation link (accd351) +- Temporarily disable StyleCops until all the fixes are impl'd (36de7b2) +- Remove redundant CreateGuildRoleParams (3df0539) +- Add minor tweaks to DiscordSocketConfig docs strings (2cd1880) +- Fix MaxWaitBetweenGuildAvailablesBeforeReady docs string (e31cdc7) +- Missing summary tag for GatewayIntents (3a10018) +- Add new method of role ID copy (857ef77) +- Resolve inheritdocs for IAttachment (9ea3291) +- Mark null as a specific langword in summary (13a41f8) +- Cleanup GatewayReconnectException docs (833ee42) +- Update Docfx.Plugins.LastModified to v1.2.4 (28a6f97) +- Update framework version for tests to Core 3.1 to comply with LTS (4988a07) +- Move bulk deletes remarks from to (62539f0) + ## [2.2.0] - 2020-04-16 ### Added - #1247 Implement Client Status Support (9da11b4) diff --git a/Discord.Net.targets b/Discord.Net.targets index 9502e91dd..503035b0c 100644 --- a/Discord.Net.targets +++ b/Discord.Net.targets @@ -1,7 +1,7 @@ 2.3.0 - dev + latest Discord.Net Contributors discord;discordapp diff --git a/src/Discord.Net/Discord.Net.nuspec b/src/Discord.Net/Discord.Net.nuspec index 5c5ea4072..c675f2fc5 100644 --- a/src/Discord.Net/Discord.Net.nuspec +++ b/src/Discord.Net/Discord.Net.nuspec @@ -2,7 +2,7 @@ Discord.Net - 2.3.0-dev$suffix$ + 2.3.0$suffix$ Discord.Net Discord.Net Contributors foxbot @@ -14,25 +14,25 @@ https://github.com/RogueException/Discord.Net/raw/dev/docs/marketing/logo/PackageLogo.png - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + From 83e88f4fb961bb78d5446e87e31fff4470f616dc Mon Sep 17 00:00:00 2001 From: Paulo Date: Sat, 30 Jan 2021 23:40:33 -0300 Subject: [PATCH 15/64] meta: Bump version to 2.3.1-dev --- Discord.Net.targets | 4 ++-- src/Discord.Net/Discord.Net.nuspec | 32 +++++++++++++++--------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Discord.Net.targets b/Discord.Net.targets index 503035b0c..a7e2c8a51 100644 --- a/Discord.Net.targets +++ b/Discord.Net.targets @@ -1,7 +1,7 @@ - 2.3.0 - + 2.3.1 + dev latest Discord.Net Contributors discord;discordapp diff --git a/src/Discord.Net/Discord.Net.nuspec b/src/Discord.Net/Discord.Net.nuspec index c675f2fc5..494e3ea43 100644 --- a/src/Discord.Net/Discord.Net.nuspec +++ b/src/Discord.Net/Discord.Net.nuspec @@ -2,7 +2,7 @@ Discord.Net - 2.3.0$suffix$ + 2.3.1-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 - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + From 73e5cc2fbcc7c974706165f5cb0703770a0be727 Mon Sep 17 00:00:00 2001 From: Yeba <31899118+yebafan@users.noreply.github.com> Date: Sun, 31 Jan 2021 07:13:09 +0100 Subject: [PATCH 16/64] fix: Deadlock in DiscordShardedClient when Ready is never received (#1761) * fixed a deadlock in DiscordShardedClient during a failed Identify due to InvalidSession * fixed log * Don't wait ready before releasing semaphore Co-authored-by: Paulo --- src/Discord.Net.WebSocket/DiscordSocketClient.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index d4c96ab26..b43db5d98 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -245,15 +245,15 @@ namespace Discord.WebSocket await _gatewayLogger.DebugAsync("Identifying").ConfigureAwait(false); await ApiClient.SendIdentifyAsync(shardID: ShardId, totalShards: TotalShards, guildSubscriptions: _guildSubscriptions, gatewayIntents: _gatewayIntents, presence: BuildCurrentStatus()).ConfigureAwait(false); } - - //Wait for READY - await _connection.WaitAsync().ConfigureAwait(false); } finally { if (locked) _shardedClient.ReleaseIdentifyLock(); } + + //Wait for READY + await _connection.WaitAsync().ConfigureAwait(false); } private async Task OnDisconnectingAsync(Exception ex) { @@ -632,7 +632,7 @@ namespace Discord.WebSocket } else if (_connection.CancelToken.IsCancellationRequested) return; - + if (BaseConfig.AlwaysDownloadUsers) _ = DownloadUsersAsync(Guilds.Where(x => x.IsAvailable && !x.HasAllMembers)); From 0fc713a8977d2474adf03dc83a46d02ffcd295e8 Mon Sep 17 00:00:00 2001 From: Antonio Zdravkov Nikolov Date: Wed, 10 Feb 2021 02:04:32 +0200 Subject: [PATCH 17/64] fix: Private methods aren't added as commands (#1773) Fix breaking change introduced by #1521 --- src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs index 28037b0fa..7a752090e 100644 --- a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs @@ -136,7 +136,7 @@ namespace Discord.Commands builder.Name = typeInfo.Name; // Get all methods (including from inherited members), that are valid commands - var validCommands = typeInfo.GetMethods().Where(IsValidCommandDefinition); + var validCommands = typeInfo.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Where(IsValidCommandDefinition); foreach (var method in validCommands) { From 6ac5ea1cbb8fe0e517af9b534bd128fefd212829 Mon Sep 17 00:00:00 2001 From: NeKz Date: Wed, 10 Mar 2021 18:50:48 +0100 Subject: [PATCH 18/64] fix: Add ChannelType property to ChannelInfo audit log (#1786) --- .../Entities/AuditLogs/DataTypes/ChannelInfo.cs | 10 +++++++++- .../AuditLogs/DataTypes/ChannelUpdateAuditLogData.cs | 7 +++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelInfo.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelInfo.cs index 0284b63f5..f50d9eeb3 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelInfo.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelInfo.cs @@ -5,13 +5,14 @@ namespace Discord.Rest /// public struct ChannelInfo { - internal ChannelInfo(string name, string topic, int? rateLimit, bool? nsfw, int? bitrate) + internal ChannelInfo(string name, string topic, int? rateLimit, bool? nsfw, int? bitrate, ChannelType? type) { Name = name; Topic = topic; SlowModeInterval = rateLimit; IsNsfw = nsfw; Bitrate = bitrate; + ChannelType = type; } /// @@ -53,5 +54,12 @@ namespace Discord.Rest /// null if this is not mentioned in this entry. /// public int? Bitrate { get; } + /// + /// Gets the type of this channel. + /// + /// + /// The channel type of this channel; null if not applicable. + /// + public ChannelType? ChannelType { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelUpdateAuditLogData.cs index fa5233145..b2294f183 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelUpdateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelUpdateAuditLogData.cs @@ -26,6 +26,7 @@ namespace Discord.Rest var rateLimitPerUserModel = changes.FirstOrDefault(x => x.ChangedProperty == "rate_limit_per_user"); var nsfwModel = changes.FirstOrDefault(x => x.ChangedProperty == "nsfw"); var bitrateModel = changes.FirstOrDefault(x => x.ChangedProperty == "bitrate"); + var typeModel = changes.FirstOrDefault(x => x.ChangedProperty == "type"); string oldName = nameModel?.OldValue?.ToObject(discord.ApiClient.Serializer), newName = nameModel?.NewValue?.ToObject(discord.ApiClient.Serializer); @@ -37,9 +38,11 @@ namespace Discord.Rest newNsfw = nsfwModel?.NewValue?.ToObject(discord.ApiClient.Serializer); int? oldBitrate = bitrateModel?.OldValue?.ToObject(discord.ApiClient.Serializer), newBitrate = bitrateModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + ChannelType? oldType = typeModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newType = typeModel?.NewValue?.ToObject(discord.ApiClient.Serializer); - var before = new ChannelInfo(oldName, oldTopic, oldRateLimitPerUser, oldNsfw, oldBitrate); - var after = new ChannelInfo(newName, newTopic, newRateLimitPerUser, newNsfw, newBitrate); + var before = new ChannelInfo(oldName, oldTopic, oldRateLimitPerUser, oldNsfw, oldBitrate, oldType); + var after = new ChannelInfo(newName, newTopic, newRateLimitPerUser, newNsfw, newBitrate, newType); return new ChannelUpdateAuditLogData(entry.TargetId.Value, before, after); } From f794163ffa4922f484981a936694e6dd80a1451c Mon Sep 17 00:00:00 2001 From: NeKz Date: Wed, 10 Mar 2021 20:07:19 +0100 Subject: [PATCH 19/64] fix: NullReferenceException in pin/unpin audit logs (#1780) --- .../AuditLogs/DataTypes/MessagePinAuditLogData.cs | 14 ++++++++++---- .../DataTypes/MessageUnpinAuditLogData.cs | 14 ++++++++++---- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessagePinAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessagePinAuditLogData.cs index 020171152..be66ac846 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessagePinAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessagePinAuditLogData.cs @@ -19,8 +19,14 @@ namespace Discord.Rest internal static MessagePinAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) { - var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId); - return new MessagePinAuditLogData(entry.Options.MessageId.Value, entry.Options.ChannelId.Value, RestUser.Create(discord, userInfo)); + RestUser user = null; + if (entry.TargetId.HasValue) + { + var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId); + user = RestUser.Create(discord, userInfo); + } + + return new MessagePinAuditLogData(entry.Options.MessageId.Value, entry.Options.ChannelId.Value, user); } /// @@ -38,10 +44,10 @@ namespace Discord.Rest /// public ulong ChannelId { get; } /// - /// Gets the user of the message that was pinned. + /// Gets the user of the message that was pinned if available. /// /// - /// A user object representing the user that created the pinned message. + /// A user object representing the user that created the pinned message or . /// public IUser Target { get; } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageUnpinAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageUnpinAuditLogData.cs index 1b3ff96f3..b4fa389cc 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageUnpinAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageUnpinAuditLogData.cs @@ -19,8 +19,14 @@ namespace Discord.Rest internal static MessageUnpinAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) { - var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId); - return new MessageUnpinAuditLogData(entry.Options.MessageId.Value, entry.Options.ChannelId.Value, RestUser.Create(discord, userInfo)); + RestUser user = null; + if (entry.TargetId.HasValue) + { + var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId); + user = RestUser.Create(discord, userInfo); + } + + return new MessageUnpinAuditLogData(entry.Options.MessageId.Value, entry.Options.ChannelId.Value, user); } /// @@ -38,10 +44,10 @@ namespace Discord.Rest /// public ulong ChannelId { get; } /// - /// Gets the user of the message that was unpinned. + /// Gets the user of the message that was unpinned if available. /// /// - /// A user object representing the user that created the unpinned message. + /// A user object representing the user that created the unpinned message or . /// public IUser Target { get; } } From 51b7afe2a6daaa07de24e96bb6c7abee7dd8bc10 Mon Sep 17 00:00:00 2001 From: Paulo Date: Wed, 10 Mar 2021 16:07:31 -0300 Subject: [PATCH 20/64] misc: Add remark regarding CustomStatus as the activity (#1774) * Throw exception with CustomStatus * Add remark instead of exception * Change wording --- src/Discord.Net.WebSocket/BaseSocketClient.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Discord.Net.WebSocket/BaseSocketClient.cs b/src/Discord.Net.WebSocket/BaseSocketClient.cs index 425889613..36e6c02a9 100644 --- a/src/Discord.Net.WebSocket/BaseSocketClient.cs +++ b/src/Discord.Net.WebSocket/BaseSocketClient.cs @@ -209,6 +209,12 @@ namespace Discord.WebSocket /// The name of the game. /// If streaming, the URL of the stream. Must be a valid Twitch URL. /// The type of the game. + /// + /// + /// Bot accounts cannot set as their activity + /// type and it will have no effect. + /// + /// /// /// A task that represents the asynchronous set operation. /// @@ -222,6 +228,10 @@ namespace Discord.WebSocket /// Discord will only accept setting of name and the type of activity. /// /// + /// Bot accounts cannot set as their activity + /// type and it will have no effect. + /// + /// /// Rich Presence cannot be set via this method or client. Rich Presence is strictly limited to RPC /// clients only. /// From d2518db9a8826dd60ca34ec29775c154825f6150 Mon Sep 17 00:00:00 2001 From: Alex Gravely Date: Wed, 10 Mar 2021 14:22:00 -0500 Subject: [PATCH 21/64] fix: Update Webhook ChannelId from model change (#1791) --- src/Discord.Net.Rest/Entities/Webhooks/RestWebhook.cs | 6 ++++-- .../Entities/Webhooks/RestInternalWebhook.cs | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Discord.Net.Rest/Entities/Webhooks/RestWebhook.cs b/src/Discord.Net.Rest/Entities/Webhooks/RestWebhook.cs index 1fdc95a63..9baddf003 100644 --- a/src/Discord.Net.Rest/Entities/Webhooks/RestWebhook.cs +++ b/src/Discord.Net.Rest/Entities/Webhooks/RestWebhook.cs @@ -11,11 +11,11 @@ namespace Discord.Rest internal IGuild Guild { get; private set; } internal ITextChannel Channel { get; private set; } - /// - public ulong ChannelId { get; } /// public string Token { get; } + /// + public ulong ChannelId { get; private set; } /// public string Name { get; private set; } /// @@ -56,6 +56,8 @@ namespace Discord.Rest internal void Update(Model model) { + if (ChannelId != model.ChannelId) + ChannelId = model.ChannelId; if (model.Avatar.IsSpecified) AvatarId = model.Avatar.Value; if (model.Creator.IsSpecified) diff --git a/src/Discord.Net.Webhook/Entities/Webhooks/RestInternalWebhook.cs b/src/Discord.Net.Webhook/Entities/Webhooks/RestInternalWebhook.cs index 60cb89ee2..bbb160fcd 100644 --- a/src/Discord.Net.Webhook/Entities/Webhooks/RestInternalWebhook.cs +++ b/src/Discord.Net.Webhook/Entities/Webhooks/RestInternalWebhook.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using System.Threading.Tasks; using Model = Discord.API.Webhook; @@ -11,9 +11,9 @@ namespace Discord.Webhook private DiscordWebhookClient _client; public ulong Id { get; } - public ulong ChannelId { get; } public string Token { get; } + public ulong ChannelId { get; private set; } public string Name { get; private set; } public string AvatarId { get; private set; } public ulong? GuildId { get; private set; } @@ -36,6 +36,8 @@ namespace Discord.Webhook internal void Update(Model model) { + if (ChannelId != model.ChannelId) + ChannelId = model.ChannelId; if (model.Avatar.IsSpecified) AvatarId = model.Avatar.Value; if (model.GuildId.IsSpecified) From d41aeee4fef3ecdefa04a4230dd78fba87999017 Mon Sep 17 00:00:00 2001 From: Paulo Date: Wed, 10 Mar 2021 16:50:14 -0300 Subject: [PATCH 22/64] fix: Audit log UserId can be null (#1794) --- src/Discord.Net.Rest/API/Common/AuditLogEntry.cs | 2 +- src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Discord.Net.Rest/API/Common/AuditLogEntry.cs b/src/Discord.Net.Rest/API/Common/AuditLogEntry.cs index 80d9a9e97..7458a19cb 100644 --- a/src/Discord.Net.Rest/API/Common/AuditLogEntry.cs +++ b/src/Discord.Net.Rest/API/Common/AuditLogEntry.cs @@ -7,7 +7,7 @@ namespace Discord.API [JsonProperty("target_id")] public ulong? TargetId { get; set; } [JsonProperty("user_id")] - public ulong UserId { get; set; } + public ulong? UserId { get; set; } [JsonProperty("changes")] public AuditLogChange[] Changes { get; set; } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs b/src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs index d604077f4..2176eab71 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs @@ -22,7 +22,7 @@ namespace Discord.Rest internal static RestAuditLogEntry Create(BaseDiscordClient discord, Model fullLog, EntryModel model) { - var userInfo = fullLog.Users.FirstOrDefault(x => x.Id == model.UserId); + var userInfo = model.UserId != null ? fullLog.Users.FirstOrDefault(x => x.Id == model.UserId) : null; IUser user = null; if (userInfo != null) user = RestUser.Create(discord, userInfo); From 1b64d19c845cb7c612a1c52288c8b44cff605105 Mon Sep 17 00:00:00 2001 From: Paulo Date: Wed, 10 Mar 2021 16:59:38 -0300 Subject: [PATCH 23/64] meta: 2.3.1 (#1795) --- CHANGELOG.md | 12 +++++++++++ Discord.Net.targets | 2 +- src/Discord.Net/Discord.Net.nuspec | 32 +++++++++++++++--------------- 3 files changed, 29 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f4041a7a..5988f90c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## [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..82e0d21fa 100644 --- a/Discord.Net.targets +++ b/Discord.Net.targets @@ -1,7 +1,7 @@ 2.3.1 - dev + latest Discord.Net Contributors discord;discordapp diff --git a/src/Discord.Net/Discord.Net.nuspec b/src/Discord.Net/Discord.Net.nuspec index 494e3ea43..db3f29ca7 100644 --- a/src/Discord.Net/Discord.Net.nuspec +++ b/src/Discord.Net/Discord.Net.nuspec @@ -2,7 +2,7 @@ Discord.Net - 2.3.1-dev$suffix$ + 2.3.1$suffix$ Discord.Net Discord.Net Contributors foxbot @@ -14,25 +14,25 @@ https://github.com/RogueException/Discord.Net/raw/dev/docs/marketing/logo/PackageLogo.png - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + From 452f2dca7cd803a160f5a5b5d17de5ffa9b8a579 Mon Sep 17 00:00:00 2001 From: Paulo Date: Wed, 28 Apr 2021 09:53:14 -0300 Subject: [PATCH 24/64] Remove foxbot.me docs link --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 34a633f72..32e1515af 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,6 @@ An unofficial .NET API Wrapper for the Discord client (https://discord.com). ## Documentation -- [Stable](https://discord.foxbot.me/) - - Hosted by @foxbot - [Nightly](https://docs.stillu.cc/) - [Latest CI repo](https://github.com/discord-net/docs-static) From 86dc89e821fb78b1ec4c3de24d50cc607fbe21af Mon Sep 17 00:00:00 2001 From: PoofImaFox <16710818+PoofImaFox@users.noreply.github.com> Date: Wed, 28 Apr 2021 08:55:57 -0400 Subject: [PATCH 25/64] Fix error in example. (#1807) --- samples/03_sharded_client/Modules/PublicModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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); } From c21cf48be31df80a285862f79143f884322a29f6 Mon Sep 17 00:00:00 2001 From: Paulo Date: Wed, 28 Apr 2021 10:03:30 -0300 Subject: [PATCH 26/64] meta: Bump version to 2.4.0-dev (#1827) --- Discord.Net.targets | 4 ++-- src/Discord.Net/Discord.Net.nuspec | 32 +++++++++++++++--------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Discord.Net.targets b/Discord.Net.targets index 82e0d21fa..7a48cf2a7 100644 --- a/Discord.Net.targets +++ b/Discord.Net.targets @@ -1,7 +1,7 @@ - 2.3.1 - + 2.4.0 + dev latest Discord.Net Contributors discord;discordapp diff --git a/src/Discord.Net/Discord.Net.nuspec b/src/Discord.Net/Discord.Net.nuspec index db3f29ca7..b0fe17439 100644 --- a/src/Discord.Net/Discord.Net.nuspec +++ b/src/Discord.Net/Discord.Net.nuspec @@ -2,7 +2,7 @@ Discord.Net - 2.3.1$suffix$ + 2.4.0$suffix$ Discord.Net Discord.Net Contributors foxbot @@ -14,25 +14,25 @@ https://github.com/RogueException/Discord.Net/raw/dev/docs/marketing/logo/PackageLogo.png - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + From 91a906397a2c4cbb7cc3cad660867f39557a8236 Mon Sep 17 00:00:00 2001 From: Paulo Date: Wed, 28 Apr 2021 10:11:28 -0300 Subject: [PATCH 27/64] feature: Add stickers (#1726) --- .../Entities/Messages/IMessage.cs | 8 +++ .../Entities/Messages/ISticker.cs | 67 +++++++++++++++++++ .../Entities/Messages/SticketFormatType.cs | 15 +++++ src/Discord.Net.Rest/API/Common/Message.cs | 2 + src/Discord.Net.Rest/API/Common/Sticker.cs | 25 +++++++ .../Entities/Messages/RestMessage.cs | 4 ++ .../Entities/Messages/RestUserMessage.cs | 17 +++++ .../Entities/Messages/Sticker.cs | 48 +++++++++++++ .../Entities/Messages/SocketMessage.cs | 4 ++ .../Entities/Messages/SocketUserMessage.cs | 17 +++++ 10 files changed, 207 insertions(+) create mode 100644 src/Discord.Net.Core/Entities/Messages/ISticker.cs create mode 100644 src/Discord.Net.Core/Entities/Messages/SticketFormatType.cs create mode 100644 src/Discord.Net.Rest/API/Common/Sticker.cs create mode 100644 src/Discord.Net.Rest/Entities/Messages/Sticker.cs diff --git a/src/Discord.Net.Core/Entities/Messages/IMessage.cs b/src/Discord.Net.Core/Entities/Messages/IMessage.cs index 80b1ffa68..eb135768c 100644 --- a/src/Discord.Net.Core/Entities/Messages/IMessage.cs +++ b/src/Discord.Net.Core/Entities/Messages/IMessage.cs @@ -164,6 +164,14 @@ namespace Discord /// IReadOnlyDictionary Reactions { get; } + /// + /// Gets all stickers included in this message. + /// + /// + /// A read-only collection of sticker objects. + /// + IReadOnlyCollection Stickers { get; } + /// /// Gets the flags related to this message. /// diff --git a/src/Discord.Net.Core/Entities/Messages/ISticker.cs b/src/Discord.Net.Core/Entities/Messages/ISticker.cs new file mode 100644 index 000000000..e7e4405b6 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Messages/ISticker.cs @@ -0,0 +1,67 @@ +using System.Collections.Generic; + +namespace Discord +{ + /// + /// Represents a discord sticker. + /// + public interface ISticker + { + /// + /// Gets the ID of this sticker. + /// + /// + /// A snowflake ID associated with this sticker. + /// + ulong Id { get; } + /// + /// Gets the ID of the pack of this sticker. + /// + /// + /// A snowflake ID associated with the pack of this sticker. + /// + ulong PackId { get; } + /// + /// Gets the name of this sticker. + /// + /// + /// A with the name of this sticker. + /// + string Name { get; } + /// + /// Gets the description of this sticker. + /// + /// + /// A with the description of this sticker. + /// + string Description { get; } + /// + /// Gets the list of tags of this sticker. + /// + /// + /// A read-only list with the tags of this sticker. + /// + IReadOnlyCollection Tags { get; } + /// + /// Gets the asset hash of this sticker. + /// + /// + /// A with the asset hash of this sticker. + /// + string Asset { get; } + /// + /// Gets the preview asset hash of this sticker. + /// + /// + /// A with the preview asset hash of this sticker. + /// + string PreviewAsset { get; } + /// + /// Gets the format type of this sticker. + /// + /// + /// A with the format type of this sticker. + /// + StickerFormatType FormatType { get; } + } +} diff --git a/src/Discord.Net.Core/Entities/Messages/SticketFormatType.cs b/src/Discord.Net.Core/Entities/Messages/SticketFormatType.cs new file mode 100644 index 000000000..d24a38534 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Messages/SticketFormatType.cs @@ -0,0 +1,15 @@ +namespace Discord +{ + /// Defines the types of formats for stickers. + public enum StickerFormatType + { + /// Default value for a sticker format type. + None = 0, + /// The sticker format type is png. + Png = 1, + /// The sticker format type is apng. + Apng = 2, + /// The sticker format type is lottie. + Lottie = 3, + } +} diff --git a/src/Discord.Net.Rest/API/Common/Message.cs b/src/Discord.Net.Rest/API/Common/Message.cs index b781de346..6ea2c29ff 100644 --- a/src/Discord.Net.Rest/API/Common/Message.cs +++ b/src/Discord.Net.Rest/API/Common/Message.cs @@ -58,5 +58,7 @@ namespace Discord.API public Optional AllowedMentions { get; set; } [JsonProperty("referenced_message")] public Optional ReferencedMessage { get; set; } + [JsonProperty("stickers")] + public Optional Stickers { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/Sticker.cs b/src/Discord.Net.Rest/API/Common/Sticker.cs new file mode 100644 index 000000000..0d1cac974 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/Sticker.cs @@ -0,0 +1,25 @@ +#pragma warning disable CS1591 +using Newtonsoft.Json; + +namespace Discord.API +{ + internal class Sticker + { + [JsonProperty("id")] + public ulong Id { get; set; } + [JsonProperty("pack_id")] + public ulong PackId { get; set; } + [JsonProperty("name")] + public string Name { get; set; } + [JsonProperty("description")] + public string Desription { get; set; } + [JsonProperty("tags")] + public Optional Tags { get; set; } + [JsonProperty("asset")] + public string Asset { get; set; } + [JsonProperty("preview_asset")] + public string PreviewAsset { get; set; } + [JsonProperty("format_type")] + public StickerFormatType FormatType { get; set; } + } +} diff --git a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs index b2a745980..793e89dff 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs @@ -58,6 +58,8 @@ namespace Discord.Rest public virtual IReadOnlyCollection MentionedUsers => ImmutableArray.Create(); /// public virtual IReadOnlyCollection Tags => ImmutableArray.Create(); + /// + public virtual IReadOnlyCollection Stickers => ImmutableArray.Create(); /// public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks); @@ -173,6 +175,8 @@ namespace Discord.Rest IReadOnlyCollection IMessage.Embeds => Embeds; /// IReadOnlyCollection IMessage.MentionedUserIds => MentionedUsers.Select(x => x.Id).ToImmutableArray(); + /// + IReadOnlyCollection IMessage.Stickers => Stickers; /// public IReadOnlyDictionary Reactions => _reactions.ToDictionary(x => x.Emote, x => new ReactionMetadata { ReactionCount = x.Count, IsMe = x.Me }); diff --git a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs index cf025aea1..1274f1fd3 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs @@ -21,6 +21,7 @@ namespace Discord.Rest private ImmutableArray _tags = ImmutableArray.Create(); private ImmutableArray _roleMentionIds = ImmutableArray.Create(); private ImmutableArray _userMentions = ImmutableArray.Create(); + private ImmutableArray _stickers = ImmutableArray.Create(); /// public override bool IsTTS => _isTTS; @@ -45,6 +46,8 @@ namespace Discord.Rest /// public override IReadOnlyCollection Tags => _tags; /// + public override IReadOnlyCollection Stickers => _stickers; + /// public IUserMessage ReferencedMessage => _referencedMessage; internal RestUserMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, IUser author, MessageSource source) @@ -132,6 +135,20 @@ namespace Discord.Rest IUser refMsgAuthor = MessageHelper.GetAuthor(Discord, guild, refMsg.Author.Value, refMsg.WebhookId.ToNullable()); _referencedMessage = RestUserMessage.Create(Discord, Channel, refMsgAuthor, refMsg); } + + if (model.Stickers.IsSpecified) + { + var value = model.Stickers.Value; + if (value.Length > 0) + { + var stickers = ImmutableArray.CreateBuilder(value.Length); + for (int i = 0; i < value.Length; i++) + stickers.Add(Sticker.Create(value[i])); + _stickers = stickers.ToImmutable(); + } + else + _stickers = ImmutableArray.Create(); + } } /// diff --git a/src/Discord.Net.Rest/Entities/Messages/Sticker.cs b/src/Discord.Net.Rest/Entities/Messages/Sticker.cs new file mode 100644 index 000000000..5482bed74 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Messages/Sticker.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using System.Diagnostics; +using Model = Discord.API.Sticker; + +namespace Discord +{ + /// + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public class Sticker : ISticker + { + /// + public ulong Id { get; } + /// + public ulong PackId { get; } + /// + public string Name { get; } + /// + public string Description { get; } + /// + public IReadOnlyCollection Tags { get; } + /// + public string Asset { get; } + /// + public string PreviewAsset { get; } + /// + public StickerFormatType FormatType { get; } + + internal Sticker(ulong id, ulong packId, string name, string description, string[] tags, string asset, string previewAsset, StickerFormatType formatType) + { + Id = id; + PackId = packId; + Name = name; + Description = description; + Tags = tags.ToReadOnlyCollection(); + Asset = asset; + PreviewAsset = previewAsset; + FormatType = formatType; + } + internal static Sticker Create(Model model) + { + return new Sticker(model.Id, model.PackId, model.Name, model.Desription, + model.Tags.IsSpecified ? model.Tags.Value.Split(',') : new string[0], + model.Asset, model.PreviewAsset, model.FormatType); + } + + private string DebuggerDisplay => $"{Name} ({Id})"; + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs index 2ca53cbb9..8b45d882b 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs @@ -99,6 +99,8 @@ namespace Discord.WebSocket /// public virtual IReadOnlyCollection Tags => ImmutableArray.Create(); /// + public virtual IReadOnlyCollection Stickers => ImmutableArray.Create(); + /// public IReadOnlyDictionary Reactions => _reactions.GroupBy(r => r.Emote).ToDictionary(x => x.Key, x => new ReactionMetadata { ReactionCount = x.Count(), IsMe = x.Any(y => y.UserId == Discord.CurrentUser.Id) }); /// @@ -194,6 +196,8 @@ namespace Discord.WebSocket IReadOnlyCollection IMessage.MentionedRoleIds => MentionedRoles.Select(x => x.Id).ToImmutableArray(); /// IReadOnlyCollection IMessage.MentionedUserIds => MentionedUsers.Select(x => x.Id).ToImmutableArray(); + /// + IReadOnlyCollection IMessage.Stickers => Stickers; internal void AddReaction(SocketReaction reaction) { diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs index 859b1b80a..2a8b45ca1 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs @@ -23,6 +23,7 @@ namespace Discord.WebSocket private ImmutableArray _tags = ImmutableArray.Create(); private ImmutableArray _roleMentions = ImmutableArray.Create(); private ImmutableArray _userMentions = ImmutableArray.Create(); + private ImmutableArray _stickers = ImmutableArray.Create(); /// public override bool IsTTS => _isTTS; @@ -47,6 +48,8 @@ namespace Discord.WebSocket /// public override IReadOnlyCollection MentionedUsers => _userMentions; /// + public override IReadOnlyCollection Stickers => _stickers; + /// public IUserMessage ReferencedMessage => _referencedMessage; internal SocketUserMessage(DiscordSocketClient discord, ulong id, ISocketMessageChannel channel, SocketUser author, MessageSource source) @@ -158,6 +161,20 @@ namespace Discord.WebSocket refMsgAuthor = new SocketUnknownUser(Discord, id: 0); _referencedMessage = SocketUserMessage.Create(Discord, state, refMsgAuthor, Channel, refMsg); } + + if (model.Stickers.IsSpecified) + { + var value = model.Stickers.Value; + if (value.Length > 0) + { + var stickers = ImmutableArray.CreateBuilder(value.Length); + for (int i = 0; i < value.Length; i++) + stickers.Add(Sticker.Create(value[i])); + _stickers = stickers.ToImmutable(); + } + else + _stickers = ImmutableArray.Create(); + } } /// From f67cd8ea55ccb80caf126fe76466857083da2ce3 Mon Sep 17 00:00:00 2001 From: Desmont <314857+Desmont@users.noreply.github.com> Date: Wed, 28 Apr 2021 15:15:16 +0200 Subject: [PATCH 28/64] feature: Webhook message edit & delete functionality (#1753) * feature: Webhook message edit & delete functionality * PR fixes: Rename Edit* to Modify*; Add more detailed docstrings; Small validation fixes * Fix spacing around docstrings * Make ModifyWebhookMessageParams.Content Optional * Change the Webhook message edit functionality to use a object delegate method instead providing the all parameters Co-authored-by: Desmont --- .../API/Rest/ModifyWebhookMessageParams.cs | 16 ++++++ src/Discord.Net.Rest/DiscordRestApiClient.cs | 37 +++++++++++++ .../DiscordWebhookClient.cs | 29 ++++++++++ .../Messages/WebhookMessageProperties.cs | 26 +++++++++ .../WebhookClientHelper.cs | 54 ++++++++++++++++++- 5 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 src/Discord.Net.Rest/API/Rest/ModifyWebhookMessageParams.cs create mode 100644 src/Discord.Net.Webhook/Entities/Messages/WebhookMessageProperties.cs diff --git a/src/Discord.Net.Rest/API/Rest/ModifyWebhookMessageParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyWebhookMessageParams.cs new file mode 100644 index 000000000..ba8fcbb4e --- /dev/null +++ b/src/Discord.Net.Rest/API/Rest/ModifyWebhookMessageParams.cs @@ -0,0 +1,16 @@ +#pragma warning disable CS1591 +using Newtonsoft.Json; + +namespace Discord.API.Rest +{ + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] + internal class ModifyWebhookMessageParams + { + [JsonProperty("content")] + public Optional Content { get; set; } + [JsonProperty("embeds")] + public Optional Embeds { get; set; } + [JsonProperty("allowed_mentions")] + public Optional AllowedMentions { get; set; } + } +} diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index 592ad7e92..023652fdf 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -523,6 +523,43 @@ namespace Discord.API var ids = new BucketIds(webhookId: webhookId); return await SendJsonAsync("POST", () => $"webhooks/{webhookId}/{AuthToken}?wait=true", args, ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false); } + + /// Message content is too long, length must be less or equal to . + /// This operation may only be called with a token. + public async Task ModifyWebhookMessageAsync(ulong webhookId, ulong messageId, ModifyWebhookMessageParams args, RequestOptions options = null) + { + if (AuthTokenType != TokenType.Webhook) + throw new InvalidOperationException($"This operation may only be called with a {nameof(TokenType.Webhook)} token."); + + Preconditions.NotNull(args, nameof(args)); + Preconditions.NotEqual(webhookId, 0, nameof(webhookId)); + Preconditions.NotEqual(messageId, 0, nameof(messageId)); + + if (args.Embeds.IsSpecified) + Preconditions.AtMost(args.Embeds.Value.Length, 10, nameof(args.Embeds), "A max of 10 Embeds are allowed."); + if (args.Content.IsSpecified && args.Content.Value.Length > DiscordConfig.MaxMessageSize) + throw new ArgumentException(message: $"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", paramName: nameof(args.Content)); + options = RequestOptions.CreateOrClone(options); + + var ids = new BucketIds(webhookId: webhookId); + await SendJsonAsync("PATCH", () => $"webhooks/{webhookId}/{AuthToken}/messages/{messageId}", args, ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false); + } + + /// This operation may only be called with a token. + public async Task DeleteWebhookMessageAsync(ulong webhookId, ulong messageId, RequestOptions options = null) + { + if (AuthTokenType != TokenType.Webhook) + throw new InvalidOperationException($"This operation may only be called with a {nameof(TokenType.Webhook)} token."); + + Preconditions.NotEqual(webhookId, 0, nameof(webhookId)); + Preconditions.NotEqual(messageId, 0, nameof(messageId)); + + options = RequestOptions.CreateOrClone(options); + + var ids = new BucketIds(webhookId: webhookId); + await SendAsync("DELETE", () => $"webhooks/{webhookId}/{AuthToken}/messages/{messageId}", ids, options: options).ConfigureAwait(false); + } + /// Message content is too long, length must be less or equal to . public async Task UploadFileAsync(ulong channelId, UploadFileParams args, RequestOptions options = null) { diff --git a/src/Discord.Net.Webhook/DiscordWebhookClient.cs b/src/Discord.Net.Webhook/DiscordWebhookClient.cs index a6d4ef183..91d077411 100644 --- a/src/Discord.Net.Webhook/DiscordWebhookClient.cs +++ b/src/Discord.Net.Webhook/DiscordWebhookClient.cs @@ -91,6 +91,35 @@ namespace Discord.Webhook string username = null, string avatarUrl = null, RequestOptions options = null, AllowedMentions allowedMentions = null) => WebhookClientHelper.SendMessageAsync(this, text, isTTS, embeds, username, avatarUrl, allowedMentions, options); + /// + /// Modifies a message posted using this webhook. + /// + /// + /// This method can only modify messages that were sent using the same webhook. + /// + /// ID of the modified message. + /// A delegate containing the properties to modify the message with. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous modification operation. + /// + public Task ModifyMessageAsync(ulong messageId, Action func, RequestOptions options = null) + => WebhookClientHelper.ModifyMessageAsync(this, messageId, func, options); + + /// + /// Deletes a message posted using this webhook. + /// + /// + /// This method can only delete messages that were sent using the same webhook. + /// + /// ID of the deleted message. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous deletion operation. + /// + public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null) + => WebhookClientHelper.DeleteMessageAsync(this, messageId, options); + /// Sends a message to the channel for this webhook with an attachment. /// Returns the ID of the created message. public Task SendFileAsync(string filePath, string text, bool isTTS = false, diff --git a/src/Discord.Net.Webhook/Entities/Messages/WebhookMessageProperties.cs b/src/Discord.Net.Webhook/Entities/Messages/WebhookMessageProperties.cs new file mode 100644 index 000000000..dec7b6e3b --- /dev/null +++ b/src/Discord.Net.Webhook/Entities/Messages/WebhookMessageProperties.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; + +namespace Discord.Webhook +{ + /// + /// Properties that are used to modify an Webhook message with the specified changes. + /// + public class WebhookMessageProperties + { + /// + /// Gets or sets the content of the message. + /// + /// + /// This must be less than the constant defined by . + /// + public Optional Content { get; set; } + /// + /// Gets or sets the embed array that the message should display. + /// + public Optional> Embeds { get; set; } + /// + /// Gets or sets the allowed mentions of the message. + /// + public Optional AllowedMentions { get; set; } + } +} diff --git a/src/Discord.Net.Webhook/WebhookClientHelper.cs b/src/Discord.Net.Webhook/WebhookClientHelper.cs index 4bc2eaca9..886ff234d 100644 --- a/src/Discord.Net.Webhook/WebhookClientHelper.cs +++ b/src/Discord.Net.Webhook/WebhookClientHelper.cs @@ -36,7 +36,59 @@ namespace Discord.Webhook var model = await client.ApiClient.CreateWebhookMessageAsync(client.Webhook.Id, args, options: options).ConfigureAwait(false); return model.Id; } - public static async Task SendFileAsync(DiscordWebhookClient client, string filePath, string text, bool isTTS, + public static async Task ModifyMessageAsync(DiscordWebhookClient client, ulong messageId, + Action func, RequestOptions options) + { + var args = new WebhookMessageProperties(); + func(args); + + if (args.AllowedMentions.IsSpecified) + { + var allowedMentions = args.AllowedMentions.Value; + Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), + "A max of 100 role Ids are allowed."); + Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), + "A max of 100 user Ids are allowed."); + + // check that user flag and user Id list are exclusive, same with role flag and role Id list + if (allowedMentions?.AllowedTypes != null) + { + if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users) && + allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0) + { + throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", + nameof(allowedMentions)); + } + + if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles) && + allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0) + { + throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", + nameof(allowedMentions)); + } + } + } + + var apiArgs = new ModifyWebhookMessageParams + { + Content = args.Content.IsSpecified ? args.Content.Value : Optional.Create(), + Embeds = + args.Embeds.IsSpecified + ? args.Embeds.Value.Select(embed => embed.ToModel()).ToArray() + : Optional.Create(), + AllowedMentions = args.AllowedMentions.IsSpecified + ? args.AllowedMentions.Value.ToModel() + : Optional.Create() + }; + + await client.ApiClient.ModifyWebhookMessageAsync(client.Webhook.Id, messageId, apiArgs, options) + .ConfigureAwait(false); + } + public static async Task DeleteMessageAsync(DiscordWebhookClient client, ulong messageId, RequestOptions options) + { + await client.ApiClient.DeleteWebhookMessageAsync(client.Webhook.Id, messageId, options).ConfigureAwait(false); + } + public static async Task SendFileAsync(DiscordWebhookClient client, string filePath, string text, bool isTTS, IEnumerable embeds, string username, string avatarUrl, AllowedMentions allowedMentions, RequestOptions options, bool isSpoiler) { string filename = Path.GetFileName(filePath); From 25b04c4a97fc7692b739b0e66d917176922d60c3 Mon Sep 17 00:00:00 2001 From: Yeba <31899118+yebafan@users.noreply.github.com> Date: Wed, 28 Apr 2021 15:48:15 +0200 Subject: [PATCH 29/64] misc: Remove URI check from EmbedBuilder (#1778) `Uri.IsWellFormedUriString()` doesn't return the expected result for specific urls, removed until the DotNet team actually resolves it ( https://github.com/dotnet/runtime/issues/21626 ) --- .../Entities/Messages/EmbedBuilder.cs | 56 ++----------------- .../Extensions/StringExtensions.cs | 10 ---- .../EmbedBuilderTests.cs | 54 ------------------ 3 files changed, 6 insertions(+), 114 deletions(-) delete mode 100644 src/Discord.Net.Core/Extensions/StringExtensions.cs diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs index 555fd95df..f1238ddcf 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs @@ -12,7 +12,6 @@ namespace Discord { private string _title; private string _description; - private string _url; private EmbedImage? _image; private EmbedThumbnail? _thumbnail; private List _fields; @@ -70,26 +69,14 @@ namespace Discord /// Gets or sets the URL of an . /// Url is not a well-formed . /// The URL of the embed. - public string Url - { - get => _url; - set - { - if (!value.IsNullOrUri()) throw new ArgumentException(message: "Url must be a well-formed URI.", paramName: nameof(Url)); - _url = value; - } - } + public string Url { get; set; } /// Gets or sets the thumbnail URL of an . /// Url is not a well-formed . /// The thumbnail URL of the embed. public string ThumbnailUrl { get => _thumbnail?.Url; - set - { - if (!value.IsNullOrUri()) throw new ArgumentException(message: "Url must be a well-formed URI.", paramName: nameof(ThumbnailUrl)); - _thumbnail = new EmbedThumbnail(value, null, null, null); - } + set => _thumbnail = new EmbedThumbnail(value, null, null, null); } /// Gets or sets the image URL of an . /// Url is not a well-formed . @@ -97,11 +84,7 @@ namespace Discord public string ImageUrl { get => _image?.Url; - set - { - if (!value.IsNullOrUri()) throw new ArgumentException(message: "Url must be a well-formed URI.", paramName: nameof(ImageUrl)); - _image = new EmbedImage(value, null, null, null); - } + set => _image = new EmbedImage(value, null, null, null); } /// Gets or sets the list of of an . @@ -553,8 +536,6 @@ namespace Discord public class EmbedAuthorBuilder { private string _name; - private string _url; - private string _iconUrl; /// /// Gets the maximum author name length allowed by Discord. /// @@ -585,15 +566,7 @@ namespace Discord /// /// The URL of the author field. /// - public string Url - { - get => _url; - set - { - if (!value.IsNullOrUri()) throw new ArgumentException(message: "Url must be a well-formed URI.", paramName: nameof(Url)); - _url = value; - } - } + public string Url { get; set; } /// /// Gets or sets the icon URL of the author field. /// @@ -601,15 +574,7 @@ namespace Discord /// /// The icon URL of the author field. /// - public string IconUrl - { - get => _iconUrl; - set - { - if (!value.IsNullOrUri()) throw new ArgumentException(message: "Url must be a well-formed URI.", paramName: nameof(IconUrl)); - _iconUrl = value; - } - } + public string IconUrl { get; set; } /// /// Sets the name of the author field. @@ -671,7 +636,6 @@ namespace Discord public class EmbedFooterBuilder { private string _text; - private string _iconUrl; /// /// Gets the maximum footer length allowed by Discord. @@ -703,15 +667,7 @@ namespace Discord /// /// The icon URL of the footer field. /// - public string IconUrl - { - get => _iconUrl; - set - { - if (!value.IsNullOrUri()) throw new ArgumentException(message: "Url must be a well-formed URI.", paramName: nameof(IconUrl)); - _iconUrl = value; - } - } + public string IconUrl { get; set; } /// /// Sets the name of the footer field. 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/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] From 6aff419edd9aa7df358a51c8a0333727c91f6d05 Mon Sep 17 00:00:00 2001 From: Waterball <46353821+Waterball12@users.noreply.github.com> Date: Wed, 28 Apr 2021 14:50:41 +0100 Subject: [PATCH 30/64] misc: Fix spelling in SnowflakeUtils.FromSnowflake (#1800) ```diff --- geenrated +++ generated ``` --- src/Discord.Net.Core/Utils/SnowflakeUtils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.Core/Utils/SnowflakeUtils.cs b/src/Discord.Net.Core/Utils/SnowflakeUtils.cs index dd8f8ca66..e52c99376 100644 --- a/src/Discord.Net.Core/Utils/SnowflakeUtils.cs +++ b/src/Discord.Net.Core/Utils/SnowflakeUtils.cs @@ -12,7 +12,7 @@ namespace Discord /// /// The snowflake identifier to resolve. /// - /// A representing the time for when the object is geenrated. + /// A representing the time for when the object is generated. /// public static DateTimeOffset FromSnowflake(ulong value) => DateTimeOffset.FromUnixTimeMilliseconds((long)((value >> 22) + 1420070400000UL)); From 0715d7db9662da182a7218de2a7118af3d62be7e Mon Sep 17 00:00:00 2001 From: Waterball <46353821+Waterball12@users.noreply.github.com> Date: Wed, 28 Apr 2021 14:59:31 +0100 Subject: [PATCH 31/64] Feature: Add missing property to MESSAGE_REACTION_ADD event (#1801) * Add guild member property for reaction * Use the guild member provided by the event when present --- src/Discord.Net.WebSocket/API/Gateway/Reaction.cs | 4 +++- src/Discord.Net.WebSocket/DiscordSocketClient.cs | 8 ++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Discord.Net.WebSocket/API/Gateway/Reaction.cs b/src/Discord.Net.WebSocket/API/Gateway/Reaction.cs index 62de456e2..a0a740868 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/Reaction.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/Reaction.cs @@ -1,4 +1,4 @@ -using Newtonsoft.Json; +using Newtonsoft.Json; namespace Discord.API.Gateway { @@ -12,5 +12,7 @@ namespace Discord.API.Gateway public ulong ChannelId { get; set; } [JsonProperty("emoji")] public Emoji Emoji { get; set; } + [JsonProperty("member")] + public Optional Member { get; set; } } } diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index b43db5d98..520f69073 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -1384,6 +1384,14 @@ namespace Discord.WebSocket ? 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); From df23d57458db65fb3107942000abdd5eaa83fbaf Mon Sep 17 00:00:00 2001 From: NeKz Date: Wed, 28 Apr 2021 16:08:56 +0200 Subject: [PATCH 32/64] feature: Add GetEmotesAsync to IGuild (#1781) --- src/Discord.Net.Core/Entities/Guilds/IGuild.cs | 9 +++++++++ src/Discord.Net.Rest/DiscordRestApiClient.cs | 9 +++++++++ src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs | 5 +++++ src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs | 3 +++ src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs | 3 +++ 5 files changed, 29 insertions(+) diff --git a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs index 6283508e5..36d735157 100644 --- a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs +++ b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs @@ -892,6 +892,15 @@ namespace Discord /// Task> GetWebhooksAsync(RequestOptions options = null); + /// + /// Gets a collection of emotes from this guild. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection + /// of emotes found within the guild. + /// + Task> GetEmotesAsync(RequestOptions options = null); /// /// Gets a specific emote from this guild. /// diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index 023652fdf..d3e4aa515 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -1280,6 +1280,15 @@ namespace Discord.API } //Guild emoji + public async Task> GetGuildEmotesAsync(ulong guildId, RequestOptions options = null) + { + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + options = RequestOptions.CreateOrClone(options); + + var ids = new BucketIds(guildId: guildId); + return await SendAsync>("GET", () => $"guilds/{guildId}/emojis", ids, options: options).ConfigureAwait(false); + } + public async Task GetGuildEmoteAsync(ulong guildId, ulong emoteId, RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); diff --git a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs index 04ec27930..d10d046ee 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs @@ -496,6 +496,11 @@ namespace Discord.Rest } //Emotes + public static async Task> GetEmotesAsync(IGuild guild, BaseDiscordClient client, RequestOptions options) + { + var models = await client.ApiClient.GetGuildEmotesAsync(guild.Id, options).ConfigureAwait(false); + return models.Select(x => x.ToEntity()).ToImmutableArray(); + } public static async Task GetEmoteAsync(IGuild guild, BaseDiscordClient client, ulong id, RequestOptions options) { var emote = await client.ApiClient.GetGuildEmoteAsync(guild.Id, id, options).ConfigureAwait(false); diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs index c74e128a8..57918a1e7 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs @@ -828,6 +828,9 @@ namespace Discord.Rest //Emotes /// + public Task> GetEmotesAsync(RequestOptions options = null) + => GuildHelper.GetEmotesAsync(this, Discord, options); + /// public Task GetEmoteAsync(ulong id, RequestOptions options = null) => GuildHelper.GetEmoteAsync(this, Discord, id, options); /// diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index e9e535998..0e36c6b50 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -1008,6 +1008,9 @@ namespace Discord.WebSocket //Emotes /// + public Task> GetEmotesAsync(RequestOptions options = null) + => GuildHelper.GetEmotesAsync(this, Discord, options); + /// public Task GetEmoteAsync(ulong id, RequestOptions options = null) => GuildHelper.GetEmoteAsync(this, Discord, id, options); /// From c46daaad19f86d4396c752010ccffc08aa144cee Mon Sep 17 00:00:00 2001 From: Adam Gauthier Date: Wed, 28 Apr 2021 10:30:16 -0400 Subject: [PATCH 33/64] fix: Add type reader when entity type reader exists (#1486) When adding a type reader that replaces a default type reader, CommandService checks that it replaces a type in its default type readers or entity type readers (IMessage, IChannel, IRole, IUser). For entity types, it checks that the target type is the same as the entity type or it implements its type reader. Adding a type reader for a default type reader doesn't make much sense and the original intent was likely to check that the target type is or implements the entity type. --- src/Discord.Net.Commands/CommandService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index 1d4b0e15a..8659b0130 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -408,7 +408,7 @@ namespace Discord.Commands var typeInfo = type.GetTypeInfo(); if (typeInfo.IsEnum) return true; - return _entityTypeReaders.Any(x => type == x.EntityType || typeInfo.ImplementedInterfaces.Contains(x.TypeReaderType)); + return _entityTypeReaders.Any(x => type == x.EntityType || typeInfo.ImplementedInterfaces.Contains(x.EntityType)); } internal void AddNullableTypeReader(Type valueType, TypeReader valueTypeReader) { @@ -511,7 +511,7 @@ namespace Discord.Commands await _commandExecutedEvent.InvokeAsync(Optional.Create(), context, searchResult).ConfigureAwait(false); return searchResult; } - + var commands = searchResult.Commands; var preconditionResults = new Dictionary(); From 5b244f215b76ec7c43e2329b72a62ddd6f6e32e2 Mon Sep 17 00:00:00 2001 From: Playwo <38554182+Playwo@users.noreply.github.com> Date: Wed, 28 Apr 2021 16:42:58 +0200 Subject: [PATCH 34/64] feature: Add methods to interact with reactions without a message object (#1828) --- src/Discord.Net.Rest/DiscordRestClient.cs | 8 ++++++++ .../Entities/Messages/MessageHelper.cs | 20 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/src/Discord.Net.Rest/DiscordRestClient.cs b/src/Discord.Net.Rest/DiscordRestClient.cs index 48c40fdfa..cdf312c3d 100644 --- a/src/Discord.Net.Rest/DiscordRestClient.cs +++ b/src/Discord.Net.Rest/DiscordRestClient.cs @@ -108,6 +108,14 @@ namespace Discord.Rest public Task GetWebhookAsync(ulong id, RequestOptions options = null) => ClientHelper.GetWebhookAsync(this, id, options); + public Task AddReactionAsync(ulong channelId, ulong messageId, IEmote emote, RequestOptions options = null) + => MessageHelper.AddReactionAsync(channelId, messageId, emote, this, options); + public Task RemoveReactionAsync(ulong channelId, ulong messageId, ulong userId, IEmote emote, RequestOptions options = null) + => MessageHelper.RemoveReactionAsync(channelId, messageId, userId, emote, this, options); + public Task RemoveAllReactionsAsync(ulong channelId, ulong messageId, RequestOptions options = null) + => MessageHelper.RemoveAllReactionsAsync(channelId, messageId, this, options); + public Task RemoveAllReactionsForEmoteAsync(ulong channelId, ulong messageId, IEmote emote, RequestOptions options = null) + => MessageHelper.RemoveAllReactionsForEmoteAsync(channelId, messageId, emote, this, options); //IDiscordClient /// async Task IDiscordClient.GetApplicationInfoAsync(RequestOptions options) diff --git a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs index e22ae71cd..b2584c8c1 100644 --- a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs +++ b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs @@ -89,21 +89,41 @@ namespace Discord.Rest await client.ApiClient.SuppressEmbedAsync(msg.Channel.Id, msg.Id, apiArgs, options).ConfigureAwait(false); } + public static async Task AddReactionAsync(ulong channelId, ulong messageId, IEmote emote, BaseDiscordClient client, RequestOptions options) + { + 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(IMessage msg, IEmote emote, BaseDiscordClient client, RequestOptions options) { await client.ApiClient.AddReactionAsync(msg.Channel.Id, msg.Id, emote is Emote e ? $"{e.Name}:{e.Id}" : UrlEncode(emote.Name), options).ConfigureAwait(false); } + public static async Task RemoveReactionAsync(ulong channelId, ulong messageId, ulong userId, IEmote emote, BaseDiscordClient client, RequestOptions options) + { + await client.ApiClient.RemoveReactionAsync(channelId, messageId, userId, emote is Emote e ? $"{e.Name}:{e.Id}" : UrlEncode(emote.Name), options).ConfigureAwait(false); + } + public static async Task RemoveReactionAsync(IMessage msg, ulong userId, IEmote emote, BaseDiscordClient client, RequestOptions options) { await client.ApiClient.RemoveReactionAsync(msg.Channel.Id, msg.Id, userId, emote is Emote e ? $"{e.Name}:{e.Id}" : UrlEncode(emote.Name), options).ConfigureAwait(false); } + public static async Task RemoveAllReactionsAsync(ulong channelId, ulong messageId, BaseDiscordClient client, RequestOptions options) + { + await client.ApiClient.RemoveAllReactionsAsync(channelId, messageId, options).ConfigureAwait(false); + } + public static async Task RemoveAllReactionsAsync(IMessage msg, BaseDiscordClient client, RequestOptions options) { await client.ApiClient.RemoveAllReactionsAsync(msg.Channel.Id, msg.Id, options).ConfigureAwait(false); } + public static async Task RemoveAllReactionsForEmoteAsync(ulong channelId, ulong messageId, IEmote emote, BaseDiscordClient client, RequestOptions options) + { + await client.ApiClient.RemoveAllReactionsForEmoteAsync(channelId, messageId, emote is Emote e ? $"{e.Name}:{e.Id}" : UrlEncode(emote.Name), options).ConfigureAwait(false); + } + public static async Task RemoveAllReactionsForEmoteAsync(IMessage msg, IEmote emote, BaseDiscordClient client, RequestOptions options) { await client.ApiClient.RemoveAllReactionsForEmoteAsync(msg.Channel.Id, msg.Id, emote is Emote e ? $"{e.Name}:{e.Id}" : UrlEncode(emote.Name), options).ConfigureAwait(false); From 365a848f7af43fdb7b15903fea89676c04bf8712 Mon Sep 17 00:00:00 2001 From: Paulo Date: Thu, 29 Apr 2021 11:33:39 -0300 Subject: [PATCH 35/64] (ifcbrk) feature: Add ModifyMessageAsync to IMessageChannel (#1830) --- .../Entities/Channels/IMessageChannel.cs | 15 +++++++ .../Entities/Channels/ChannelHelper.cs | 7 ++++ .../Entities/Channels/RestDMChannel.cs | 4 ++ .../Entities/Channels/RestGroupChannel.cs | 4 ++ .../Entities/Channels/RestTextChannel.cs | 4 ++ .../Entities/Messages/MessageHelper.cs | 42 +++++++++++++++++++ .../Entities/Channels/SocketDMChannel.cs | 4 ++ .../Entities/Channels/SocketGroupChannel.cs | 4 ++ .../Entities/Channels/SocketTextChannel.cs | 4 ++ .../MockedEntities/MockedDMChannel.cs | 5 +++ .../MockedEntities/MockedGroupChannel.cs | 5 +++ .../MockedEntities/MockedTextChannel.cs | 5 +++ 12 files changed, 103 insertions(+) diff --git a/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs b/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs index abd20e4f0..e60eb9c13 100644 --- a/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs @@ -257,6 +257,21 @@ namespace Discord /// Task DeleteMessageAsync(IMessage message, RequestOptions options = null); + /// + /// Modifies a message. + /// + /// + /// This method modifies this message with the specified properties. To see an example of this + /// method and what properties are available, please refer to . + /// + /// The snowflake identifier of the message that would be changed. + /// A delegate containing the properties to modify the message with. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous modification operation. + /// + Task ModifyMessageAsync(ulong messageId, Action func, RequestOptions options = null); + /// /// Broadcasts the "user is typing" message to all users in this channel, lasting 10 seconds. /// diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index c1d0ac294..7c4edb43e 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -286,6 +286,13 @@ namespace Discord.Rest return RestUserMessage.Create(client, channel, client.CurrentUser, model); } + public static async Task ModifyMessageAsync(IMessageChannel channel, ulong messageId, Action func, + BaseDiscordClient client, RequestOptions options) + { + var msgModel = await MessageHelper.ModifyAsync(channel.Id, messageId, client, func, options).ConfigureAwait(false); + return RestUserMessage.Create(client, channel, msgModel.Author.IsSpecified ? RestUser.Create(client, msgModel.Author.Value) : client.CurrentUser, msgModel); + } + public static Task DeleteMessageAsync(IMessageChannel channel, ulong messageId, BaseDiscordClient client, RequestOptions options) => MessageHelper.DeleteAsync(channel.Id, messageId, client, options); diff --git a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs index d59a10fb5..6ccfd204c 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs @@ -135,6 +135,10 @@ namespace Discord.Rest public Task DeleteMessageAsync(IMessage message, RequestOptions options = null) => ChannelHelper.DeleteMessageAsync(this, message.Id, Discord, options); + /// + public async Task ModifyMessageAsync(ulong messageId, Action func, RequestOptions options = null) + => await ChannelHelper.ModifyMessageAsync(this, messageId, func, Discord, options).ConfigureAwait(false); + /// public Task TriggerTypingAsync(RequestOptions options = null) => ChannelHelper.TriggerTypingAsync(this, Discord, options); diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs index 666d6a4f4..2b0ab8b42 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs @@ -93,6 +93,10 @@ namespace Discord.Rest public Task DeleteMessageAsync(IMessage message, RequestOptions options = null) => ChannelHelper.DeleteMessageAsync(this, message.Id, Discord, options); + /// + public async Task ModifyMessageAsync(ulong messageId, Action func, RequestOptions options = null) + => await ChannelHelper.ModifyMessageAsync(this, messageId, func, Discord, options).ConfigureAwait(false); + /// /// Message content is too long, length must be less or equal to . public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null) diff --git a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs index fdc4b7988..c6d0b0509 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs @@ -152,6 +152,10 @@ namespace Discord.Rest public Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null) => ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options); + /// + public async Task ModifyMessageAsync(ulong messageId, Action func, RequestOptions options = null) + => await ChannelHelper.ModifyMessageAsync(this, messageId, func, Discord, options).ConfigureAwait(false); + /// public Task TriggerTypingAsync(RequestOptions options = null) => ChannelHelper.TriggerTypingAsync(this, Discord, options); diff --git a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs index b2584c8c1..1bc284836 100644 --- a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs +++ b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs @@ -71,6 +71,48 @@ namespace Discord.Rest return await client.ApiClient.ModifyMessageAsync(msg.Channel.Id, msg.Id, apiArgs, options).ConfigureAwait(false); } + public static async Task ModifyAsync(ulong channelId, ulong msgId, BaseDiscordClient client, Action func, + RequestOptions options) + { + var args = new MessageProperties(); + func(args); + + if ((args.Content.IsSpecified && string.IsNullOrEmpty(args.Content.Value)) && (args.Embed.IsSpecified && args.Embed.Value == null)) + Preconditions.NotNullOrEmpty(args.Content.IsSpecified ? args.Content.Value : string.Empty, nameof(args.Content)); + + if (args.AllowedMentions.IsSpecified) + { + AllowedMentions allowedMentions = args.AllowedMentions.Value; + Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); + Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); + + // check that user flag and user Id list are exclusive, same with role flag and role Id list + if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue) + { + if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users) && + allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0) + { + throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(allowedMentions)); + } + + if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles) && + allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0) + { + throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(allowedMentions)); + } + } + } + + var apiArgs = new API.Rest.ModifyMessageParams + { + Content = args.Content, + Embed = args.Embed.IsSpecified ? args.Embed.Value.ToModel() : Optional.Create(), + Flags = args.Flags.IsSpecified ? args.Flags.Value : Optional.Create(), + AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value.ToModel() : Optional.Create(), + }; + return await client.ApiClient.ModifyMessageAsync(channelId, msgId, apiArgs, options).ConfigureAwait(false); + } + public static Task DeleteAsync(IMessage msg, BaseDiscordClient client, RequestOptions options) => DeleteAsync(msg.Channel.Id, msg.Id, client, options); diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs index c123a9d4e..5de417036 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs @@ -152,6 +152,10 @@ namespace Discord.WebSocket public Task DeleteMessageAsync(IMessage message, RequestOptions options = null) => ChannelHelper.DeleteMessageAsync(this, message.Id, Discord, options); + /// + public async Task ModifyMessageAsync(ulong messageId, Action func, RequestOptions options = null) + => await ChannelHelper.ModifyMessageAsync(this, messageId, func, Discord, options).ConfigureAwait(false); + /// public Task TriggerTypingAsync(RequestOptions options = null) => ChannelHelper.TriggerTypingAsync(this, Discord, options); diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs index dc5ac4222..ab8c76aeb 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs @@ -180,6 +180,10 @@ namespace Discord.WebSocket public Task DeleteMessageAsync(IMessage message, RequestOptions options = null) => ChannelHelper.DeleteMessageAsync(this, message.Id, Discord, options); + /// + public async Task ModifyMessageAsync(ulong messageId, Action func, RequestOptions options = null) + => await ChannelHelper.ModifyMessageAsync(this, messageId, func, Discord, options).ConfigureAwait(false); + /// public Task TriggerTypingAsync(RequestOptions options = null) => ChannelHelper.TriggerTypingAsync(this, Discord, options); diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs index d7d918f9f..71a20c198 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs @@ -180,6 +180,10 @@ namespace Discord.WebSocket public Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null) => ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options); + /// + public async Task ModifyMessageAsync(ulong messageId, Action func, RequestOptions options = null) + => await ChannelHelper.ModifyMessageAsync(this, messageId, func, Discord, options).ConfigureAwait(false); + /// public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null) => ChannelHelper.DeleteMessageAsync(this, messageId, Discord, options); 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(); From 4c9910cf71612cdc947f066b4e302788c912bbbc Mon Sep 17 00:00:00 2001 From: Zack Broderson Date: Thu, 29 Apr 2021 10:34:52 -0400 Subject: [PATCH 36/64] (ifcbrk) feature: Add ability to add/remove roles by id (#1757) * Scaffold Add/Remove roles by id. (needs impl & docs) * Add docs * Add implementation * Expose Add/Remove role endpoints * Formatting * Fix wrong method call --- .../Entities/Users/IGuildUser.cs | 37 ++++++++++++++++++- src/Discord.Net.Rest/ClientHelper.cs | 16 +++++--- src/Discord.Net.Rest/DiscordRestClient.cs | 4 ++ .../Entities/Users/RestGuildUser.cs | 20 ++++++++-- .../Entities/Users/RestWebhookUser.cs | 28 +++++++++----- .../Entities/Users/UserHelper.cs | 12 +++--- .../Entities/Users/SocketGuildUser.cs | 22 ++++++++--- .../Entities/Users/SocketWebhookUser.cs | 34 +++++++++++++---- 8 files changed, 134 insertions(+), 39 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Users/IGuildUser.cs b/src/Discord.Net.Core/Entities/Users/IGuildUser.cs index 582e9e7a7..492cb9566 100644 --- a/src/Discord.Net.Core/Entities/Users/IGuildUser.cs +++ b/src/Discord.Net.Core/Entities/Users/IGuildUser.cs @@ -113,7 +113,15 @@ namespace Discord /// A task that represents the asynchronous modification operation. /// Task ModifyAsync(Action func, RequestOptions options = null); - + /// + /// Adds the specified role to this user in the guild. + /// + /// The role to be added to the user. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous role addition operation. + /// + Task AddRoleAsync(ulong roleId, RequestOptions options = null); /// /// Adds the specified role to this user in the guild. /// @@ -124,6 +132,15 @@ namespace Discord /// Task AddRoleAsync(IRole role, RequestOptions options = null); /// + /// Adds the specified to this user in the guild. + /// + /// The roles to be added to the user. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous role addition operation. + /// + Task AddRolesAsync(IEnumerable roleIds, RequestOptions options = null); + /// /// Adds the specified to this user in the guild. /// /// The roles to be added to the user. @@ -133,6 +150,15 @@ namespace Discord /// Task AddRolesAsync(IEnumerable roles, RequestOptions options = null); /// + /// Removes the specified from this user in the guild. + /// + /// The role to be removed from the user. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous role removal operation. + /// + Task RemoveRoleAsync(ulong roleId, RequestOptions options = null); + /// /// Removes the specified from this user in the guild. /// /// The role to be removed from the user. @@ -142,6 +168,15 @@ namespace Discord /// Task RemoveRoleAsync(IRole role, RequestOptions options = null); /// + /// Removes the specified from this user in the guild. + /// + /// The roles to be removed from the user. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous role removal operation. + /// + Task RemoveRolesAsync(IEnumerable roleIds, RequestOptions options = null); + /// /// Removes the specified from this user in the guild. /// /// The roles to be removed from the user. diff --git a/src/Discord.Net.Rest/ClientHelper.cs b/src/Discord.Net.Rest/ClientHelper.cs index 8910e999a..b60c02cab 100644 --- a/src/Discord.Net.Rest/ClientHelper.cs +++ b/src/Discord.Net.Rest/ClientHelper.cs @@ -17,7 +17,7 @@ namespace Discord.Rest return RestApplication.Create(client, model); } - public static async Task GetChannelAsync(BaseDiscordClient client, + public static async Task GetChannelAsync(BaseDiscordClient client, ulong id, RequestOptions options) { var model = await client.ApiClient.GetChannelAsync(id, options).ConfigureAwait(false); @@ -45,13 +45,13 @@ namespace Discord.Rest .Where(x => x.Type == ChannelType.Group) .Select(x => RestGroupChannel.Create(client, x)).ToImmutableArray(); } - + public static async Task> GetConnectionsAsync(BaseDiscordClient client, RequestOptions options) { var models = await client.ApiClient.GetMyConnectionsAsync(options).ConfigureAwait(false); return models.Select(RestConnection.Create).ToImmutableArray(); } - + public static async Task GetInviteAsync(BaseDiscordClient client, string inviteId, RequestOptions options) { @@ -60,7 +60,7 @@ namespace Discord.Rest return RestInviteMetadata.Create(client, null, null, model); return null; } - + public static async Task GetGuildAsync(BaseDiscordClient client, ulong id, bool withCounts, RequestOptions options) { @@ -85,7 +85,7 @@ namespace Discord.Rest return RestGuildWidget.Create(model); return null; } - public static IAsyncEnumerable> GetGuildSummariesAsync(BaseDiscordClient client, + public static IAsyncEnumerable> GetGuildSummariesAsync(BaseDiscordClient client, ulong? fromGuildId, int? limit, RequestOptions options) { return new PagedAsyncEnumerable( @@ -136,7 +136,7 @@ namespace Discord.Rest var model = await client.ApiClient.CreateGuildAsync(args, options).ConfigureAwait(false); return RestGuild.Create(client, model); } - + public static async Task GetUserAsync(BaseDiscordClient client, ulong id, RequestOptions options) { @@ -201,5 +201,9 @@ namespace Discord.Rest } }; } + public static Task AddRoleAsync(BaseDiscordClient client, ulong guildId, ulong userId, ulong roleId, RequestOptions options = null) + => client.ApiClient.AddRoleAsync(guildId, userId, roleId, options); + public static Task RemoveRoleAsync(BaseDiscordClient client, ulong guildId, ulong userId, ulong roleId, RequestOptions options = null) + => client.ApiClient.RemoveRoleAsync(guildId, userId, roleId, options); } } diff --git a/src/Discord.Net.Rest/DiscordRestClient.cs b/src/Discord.Net.Rest/DiscordRestClient.cs index cdf312c3d..7eff7363c 100644 --- a/src/Discord.Net.Rest/DiscordRestClient.cs +++ b/src/Discord.Net.Rest/DiscordRestClient.cs @@ -107,6 +107,10 @@ namespace Discord.Rest => ClientHelper.GetVoiceRegionAsync(this, id, options); public Task GetWebhookAsync(ulong id, RequestOptions options = null) => ClientHelper.GetWebhookAsync(this, id, options); + public Task AddRoleAsync(ulong guildId, ulong userId, ulong roleId) + => ClientHelper.AddRoleAsync(this, guildId, userId, roleId); + public Task RemoveRoleAsync(ulong guildId, ulong userId, ulong roleId) + => ClientHelper.RemoveRoleAsync(this, guildId, userId, roleId); public Task AddReactionAsync(ulong channelId, ulong messageId, IEmote emote, RequestOptions options = null) => MessageHelper.AddReactionAsync(channelId, messageId, emote, this, options); diff --git a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs index 4a6bc1025..6e6bbe09c 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs @@ -112,17 +112,29 @@ namespace Discord.Rest public Task KickAsync(string reason = null, RequestOptions options = null) => UserHelper.KickAsync(this, Discord, reason, options); /// + public Task AddRoleAsync(ulong roleId, RequestOptions options = null) + => AddRolesAsync(new[] { roleId }, options); + /// public Task AddRoleAsync(IRole role, RequestOptions options = null) - => AddRolesAsync(new[] { role }, options); + => AddRoleAsync(role.Id, options); + /// + public Task AddRolesAsync(IEnumerable roleIds, RequestOptions options = null) + => UserHelper.AddRolesAsync(this, Discord, roleIds, options); /// public Task AddRolesAsync(IEnumerable roles, RequestOptions options = null) - => UserHelper.AddRolesAsync(this, Discord, roles, options); + => AddRolesAsync(roles.Select(x => x.Id), options); + /// + public Task RemoveRoleAsync(ulong roleId, RequestOptions options = null) + => RemoveRolesAsync(new[] { roleId }, options); /// public Task RemoveRoleAsync(IRole role, RequestOptions options = null) - => RemoveRolesAsync(new[] { role }, options); + => RemoveRoleAsync(role.Id, options); + /// + public Task RemoveRolesAsync(IEnumerable roleIds, RequestOptions options = null) + => UserHelper.RemoveRolesAsync(this, Discord, roleIds, options); /// public Task RemoveRolesAsync(IEnumerable roles, RequestOptions options = null) - => UserHelper.RemoveRolesAsync(this, Discord, roles, options); + => RemoveRolesAsync(roles.Select(x => x.Id)); /// /// Resolving permissions requires the parent guild to be downloaded. diff --git a/src/Discord.Net.Rest/Entities/Users/RestWebhookUser.cs b/src/Discord.Net.Rest/Entities/Users/RestWebhookUser.cs index a06916c9b..2131fec93 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestWebhookUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestWebhookUser.cs @@ -59,27 +59,35 @@ namespace Discord.Rest /// ChannelPermissions IGuildUser.GetPermissions(IGuildChannel channel) => Permissions.ToChannelPerms(channel, GuildPermissions.Webhook.RawValue); /// - Task IGuildUser.KickAsync(string reason, RequestOptions options) => + Task IGuildUser.KickAsync(string reason, RequestOptions options) => throw new NotSupportedException("Webhook users cannot be kicked."); /// - Task IGuildUser.ModifyAsync(Action func, RequestOptions options) => + Task IGuildUser.ModifyAsync(Action func, RequestOptions options) => throw new NotSupportedException("Webhook users cannot be modified."); - /// - Task IGuildUser.AddRoleAsync(IRole role, RequestOptions options) => + Task IGuildUser.AddRoleAsync(ulong role, RequestOptions options) => throw new NotSupportedException("Roles are not supported on webhook users."); - /// - Task IGuildUser.AddRolesAsync(IEnumerable roles, RequestOptions options) => + Task IGuildUser.AddRoleAsync(IRole role, RequestOptions options) => throw new NotSupportedException("Roles are not supported on webhook users."); - /// - Task IGuildUser.RemoveRoleAsync(IRole role, RequestOptions options) => + Task IGuildUser.AddRolesAsync(IEnumerable roles, RequestOptions options) => + throw new NotSupportedException("Roles are not supported on webhook users."); + /// + Task IGuildUser.AddRolesAsync(IEnumerable roles, RequestOptions options) => + throw new NotSupportedException("Roles are not supported on webhook users."); + /// + Task IGuildUser.RemoveRoleAsync(ulong role, RequestOptions options) => + throw new NotSupportedException("Roles are not supported on webhook users."); + /// + Task IGuildUser.RemoveRoleAsync(IRole role, RequestOptions options) => + throw new NotSupportedException("Roles are not supported on webhook users."); + /// + Task IGuildUser.RemoveRolesAsync(IEnumerable roles, RequestOptions options) => throw new NotSupportedException("Roles are not supported on webhook users."); - /// - Task IGuildUser.RemoveRolesAsync(IEnumerable roles, RequestOptions options) => + Task IGuildUser.RemoveRolesAsync(IEnumerable roles, RequestOptions options) => throw new NotSupportedException("Roles are not supported on webhook users."); //IVoiceState diff --git a/src/Discord.Net.Rest/Entities/Users/UserHelper.cs b/src/Discord.Net.Rest/Entities/Users/UserHelper.cs index 58e8cd417..3a19fcfc1 100644 --- a/src/Discord.Net.Rest/Entities/Users/UserHelper.cs +++ b/src/Discord.Net.Rest/Entities/Users/UserHelper.cs @@ -73,16 +73,16 @@ namespace Discord.Rest return RestDMChannel.Create(client, await client.ApiClient.CreateDMChannelAsync(args, options).ConfigureAwait(false)); } - public static async Task AddRolesAsync(IGuildUser user, BaseDiscordClient client, IEnumerable roles, RequestOptions options) + public static async Task AddRolesAsync(IGuildUser user, BaseDiscordClient client, IEnumerable roleIds, RequestOptions options) { - foreach (var role in roles) - await client.ApiClient.AddRoleAsync(user.Guild.Id, user.Id, role.Id, options).ConfigureAwait(false); + foreach (var roleId in roleIds) + await client.ApiClient.AddRoleAsync(user.Guild.Id, user.Id, roleId, options).ConfigureAwait(false); } - public static async Task RemoveRolesAsync(IGuildUser user, BaseDiscordClient client, IEnumerable roles, RequestOptions options) + public static async Task RemoveRolesAsync(IGuildUser user, BaseDiscordClient client, IEnumerable roleIds, RequestOptions options) { - foreach (var role in roles) - await client.ApiClient.RemoveRoleAsync(user.Guild.Id, user.Id, role.Id, options).ConfigureAwait(false); + foreach (var roleId in roleIds) + await client.ApiClient.RemoveRoleAsync(user.Guild.Id, user.Id, roleId, options).ConfigureAwait(false); } } } diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs index c0a681d9d..9263fe642 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs @@ -63,7 +63,7 @@ namespace Discord.WebSocket /// /// Returns a collection of roles that the user possesses. /// - public IReadOnlyCollection Roles + public IReadOnlyCollection Roles => _roleIds.Select(id => Guild.GetRole(id)).Where(x => x != null).ToReadOnlyCollection(() => _roleIds.Length); /// /// Returns the voice channel the user is in, or null if none. @@ -177,17 +177,29 @@ namespace Discord.WebSocket public Task KickAsync(string reason = null, RequestOptions options = null) => UserHelper.KickAsync(this, Discord, reason, options); /// + public Task AddRoleAsync(ulong roleId, RequestOptions options = null) + => AddRolesAsync(new[] { roleId }, options); + /// public Task AddRoleAsync(IRole role, RequestOptions options = null) - => AddRolesAsync(new[] { role }, options); + => AddRoleAsync(role.Id, options); + /// + public Task AddRolesAsync(IEnumerable roleIds, RequestOptions options = null) + => UserHelper.AddRolesAsync(this, Discord, roleIds, options); /// public Task AddRolesAsync(IEnumerable roles, RequestOptions options = null) - => UserHelper.AddRolesAsync(this, Discord, roles, options); + => AddRolesAsync(roles.Select(x => x.Id), options); + /// + public Task RemoveRoleAsync(ulong roleId, RequestOptions options = null) + => RemoveRolesAsync(new[] { roleId }, options); /// public Task RemoveRoleAsync(IRole role, RequestOptions options = null) - => RemoveRolesAsync(new[] { role }, options); + => RemoveRoleAsync(role.Id, options); + /// + public Task RemoveRolesAsync(IEnumerable roleIds, RequestOptions options = null) + => UserHelper.RemoveRolesAsync(this, Discord, roleIds, options); /// public Task RemoveRolesAsync(IEnumerable roles, RequestOptions options = null) - => UserHelper.RemoveRolesAsync(this, Discord, roles, options); + => RemoveRolesAsync(roles.Select(x => x.Id)); /// public ChannelPermissions GetPermissions(IGuildChannel channel) diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs index 5103aa8b3..c22164f95 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs @@ -31,7 +31,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 SocketGlobalUser GlobalUser => + internal override SocketGlobalUser GlobalUser => throw new NotSupportedException(); internal SocketWebhookUser(SocketGuild guild, ulong id, ulong webhookId) @@ -73,32 +73,52 @@ namespace Discord.WebSocket ChannelPermissions IGuildUser.GetPermissions(IGuildChannel channel) => Permissions.ToChannelPerms(channel, GuildPermissions.Webhook.RawValue); /// /// Webhook users cannot be kicked. - Task IGuildUser.KickAsync(string reason, RequestOptions options) => + Task IGuildUser.KickAsync(string reason, RequestOptions options) => throw new NotSupportedException("Webhook users cannot be kicked."); /// /// Webhook users cannot be modified. - Task IGuildUser.ModifyAsync(Action func, RequestOptions options) => + Task IGuildUser.ModifyAsync(Action func, RequestOptions options) => throw new NotSupportedException("Webhook users cannot be modified."); /// /// Roles are not supported on webhook users. - Task IGuildUser.AddRoleAsync(IRole role, RequestOptions options) => + Task IGuildUser.AddRoleAsync(ulong roleId, RequestOptions options) => throw new NotSupportedException("Roles are not supported on webhook users."); /// /// Roles are not supported on webhook users. - Task IGuildUser.AddRolesAsync(IEnumerable roles, RequestOptions options) => + Task IGuildUser.AddRoleAsync(IRole role, RequestOptions options) => throw new NotSupportedException("Roles are not supported on webhook users."); /// /// Roles are not supported on webhook users. - Task IGuildUser.RemoveRoleAsync(IRole role, RequestOptions options) => + Task IGuildUser.AddRolesAsync(IEnumerable roleIds, RequestOptions options) => throw new NotSupportedException("Roles are not supported on webhook users."); /// /// Roles are not supported on webhook users. - Task IGuildUser.RemoveRolesAsync(IEnumerable roles, RequestOptions options) => + Task IGuildUser.AddRolesAsync(IEnumerable roles, RequestOptions options) => + throw new NotSupportedException("Roles are not supported on webhook users."); + + /// + /// Roles are not supported on webhook users. + Task IGuildUser.RemoveRoleAsync(ulong roleId, RequestOptions options) => + throw new NotSupportedException("Roles are not supported on webhook users."); + + /// + /// Roles are not supported on webhook users. + Task IGuildUser.RemoveRoleAsync(IRole role, RequestOptions options) => + throw new NotSupportedException("Roles are not supported on webhook users."); + + /// + /// Roles are not supported on webhook users. + Task IGuildUser.RemoveRolesAsync(IEnumerable roles, RequestOptions options) => + throw new NotSupportedException("Roles are not supported on webhook users."); + + /// + /// Roles are not supported on webhook users. + Task IGuildUser.RemoveRolesAsync(IEnumerable roles, RequestOptions options) => throw new NotSupportedException("Roles are not supported on webhook users."); //IVoiceState From 8afef8245cfd1f8b56956dd4b4577ed3c6904be5 Mon Sep 17 00:00:00 2001 From: moiph Date: Sat, 8 May 2021 11:45:53 -0700 Subject: [PATCH 37/64] fix: Cached message emoji cleanup at MESSAGE_REACTION_REMOVE_EMOJI (#1835) MESSAGE_REACTION_REMOVE_EMOJI events were triggering REST calls by invoking `RemoveAllReactionsForEmoteAsync` instead of `RemoveReactionsForEmote`, the latter being to handle cached message state cleanup. --- src/Discord.Net.WebSocket/DiscordSocketClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 520f69073..e284fd883 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -1482,7 +1482,7 @@ namespace Discord.WebSocket var cacheable = new Cacheable(cachedMsg, data.MessageId, isCached, async () => await channel.GetMessageAsync(data.MessageId).ConfigureAwait(false) as IUserMessage); var emote = data.Emoji.ToIEmote(); - cachedMsg?.RemoveAllReactionsForEmoteAsync(emote); + cachedMsg?.RemoveReactionsForEmote(emote); await TimedInvokeAsync(_reactionsRemovedForEmoteEvent, nameof(ReactionsRemovedForEmote), cacheable, channel, emote).ConfigureAwait(false); } From f3684477294f1869d0c408e15f1ace0129b943f7 Mon Sep 17 00:00:00 2001 From: quin lynch Date: Tue, 11 May 2021 19:58:51 -0300 Subject: [PATCH 38/64] Pull d.net/dev Merged Discord.Net/dev into here --- src/Discord.Net.Commands/CommandService.cs | 4 +- .../Entities/Channels/IMessageChannel.cs | 15 +++++ .../Entities/Guilds/IGuild.cs | 9 +++ .../Entities/Messages/EmbedBuilder.cs | 56 ++-------------- .../Entities/Messages/IMessage.cs | 8 +++ .../Entities/Messages/ISticker.cs | 67 +++++++++++++++++++ .../Entities/Messages/SticketFormatType.cs | 15 +++++ .../Entities/Users/IGuildUser.cs | 37 +++++++++- src/Discord.Net.Core/Utils/SnowflakeUtils.cs | 2 +- .../API/Common/AuditLogEntry.cs | 2 +- src/Discord.Net.Rest/API/Common/Message.cs | 2 + src/Discord.Net.Rest/API/Common/Sticker.cs | 25 +++++++ .../API/Rest/ModifyWebhookMessageParams.cs | 16 +++++ src/Discord.Net.Rest/ClientHelper.cs | 16 +++-- src/Discord.Net.Rest/DiscordRestApiClient.cs | 46 +++++++++++++ src/Discord.Net.Rest/DiscordRestClient.cs | 14 +++- .../AuditLogs/DataTypes/ChannelInfo.cs | 10 ++- .../DataTypes/ChannelUpdateAuditLogData.cs | 7 +- .../DataTypes/MessagePinAuditLogData.cs | 14 ++-- .../DataTypes/MessageUnpinAuditLogData.cs | 14 ++-- .../Entities/AuditLogs/RestAuditLogEntry.cs | 2 +- .../Entities/Channels/ChannelHelper.cs | 7 ++ .../Entities/Channels/RestDMChannel.cs | 4 ++ .../Entities/Channels/RestGroupChannel.cs | 4 ++ .../Entities/Channels/RestTextChannel.cs | 4 ++ .../Entities/Guilds/GuildHelper.cs | 5 ++ .../Entities/Guilds/RestGuild.cs | 3 + .../Entities/Messages/MessageHelper.cs | 62 +++++++++++++++++ .../Entities/Messages/RestMessage.cs | 4 ++ .../Entities/Messages/RestUserMessage.cs | 17 +++++ .../Entities/Messages/Sticker.cs | 48 +++++++++++++ .../Entities/Users/RestGuildUser.cs | 20 ++++-- .../Entities/Users/RestWebhookUser.cs | 28 +++++--- .../Entities/Users/UserHelper.cs | 12 ++-- .../Entities/Webhooks/RestWebhook.cs | 6 +- .../API/Gateway/Reaction.cs | 4 +- .../BaseSocketClient.Events.cs | 16 ----- src/Discord.Net.WebSocket/BaseSocketClient.cs | 10 +++ .../DiscordSocketClient.cs | 65 +++--------------- .../Entities/Channels/SocketDMChannel.cs | 4 ++ .../Entities/Channels/SocketGroupChannel.cs | 4 ++ .../Entities/Channels/SocketTextChannel.cs | 4 ++ .../Entities/Guilds/SocketGuild.cs | 21 +----- .../Entities/Messages/SocketMessage.cs | 4 ++ .../Entities/Messages/SocketUserMessage.cs | 17 +++++ .../Entities/Users/SocketGuildUser.cs | 22 ++++-- .../Entities/Users/SocketWebhookUser.cs | 34 ++++++++-- .../DiscordWebhookClient.cs | 29 ++++++++ .../Messages/WebhookMessageProperties.cs | 26 +++++++ .../Entities/Webhooks/RestInternalWebhook.cs | 6 +- .../WebhookClientHelper.cs | 54 ++++++++++++++- src/Discord.Net/Discord.Net.nuspec | 32 ++++----- 52 files changed, 739 insertions(+), 218 deletions(-) create mode 100644 src/Discord.Net.Core/Entities/Messages/ISticker.cs create mode 100644 src/Discord.Net.Core/Entities/Messages/SticketFormatType.cs create mode 100644 src/Discord.Net.Rest/API/Common/Sticker.cs create mode 100644 src/Discord.Net.Rest/API/Rest/ModifyWebhookMessageParams.cs create mode 100644 src/Discord.Net.Rest/Entities/Messages/Sticker.cs create mode 100644 src/Discord.Net.Webhook/Entities/Messages/WebhookMessageProperties.cs diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index 1d4b0e15a..8659b0130 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -408,7 +408,7 @@ namespace Discord.Commands var typeInfo = type.GetTypeInfo(); if (typeInfo.IsEnum) return true; - return _entityTypeReaders.Any(x => type == x.EntityType || typeInfo.ImplementedInterfaces.Contains(x.TypeReaderType)); + return _entityTypeReaders.Any(x => type == x.EntityType || typeInfo.ImplementedInterfaces.Contains(x.EntityType)); } internal void AddNullableTypeReader(Type valueType, TypeReader valueTypeReader) { @@ -511,7 +511,7 @@ namespace Discord.Commands await _commandExecutedEvent.InvokeAsync(Optional.Create(), context, searchResult).ConfigureAwait(false); return searchResult; } - + var commands = searchResult.Commands; var preconditionResults = new Dictionary(); diff --git a/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs b/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs index abd20e4f0..e60eb9c13 100644 --- a/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs @@ -257,6 +257,21 @@ namespace Discord /// Task DeleteMessageAsync(IMessage message, RequestOptions options = null); + /// + /// Modifies a message. + /// + /// + /// This method modifies this message with the specified properties. To see an example of this + /// method and what properties are available, please refer to . + /// + /// The snowflake identifier of the message that would be changed. + /// A delegate containing the properties to modify the message with. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous modification operation. + /// + Task ModifyMessageAsync(ulong messageId, Action func, RequestOptions options = null); + /// /// Broadcasts the "user is typing" message to all users in this channel, lasting 10 seconds. /// diff --git a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs index 6283508e5..36d735157 100644 --- a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs +++ b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs @@ -892,6 +892,15 @@ namespace Discord /// Task> GetWebhooksAsync(RequestOptions options = null); + /// + /// Gets a collection of emotes from this guild. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection + /// of emotes found within the guild. + /// + Task> GetEmotesAsync(RequestOptions options = null); /// /// Gets a specific emote from this guild. /// diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs index 555fd95df..f1238ddcf 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs @@ -12,7 +12,6 @@ namespace Discord { private string _title; private string _description; - private string _url; private EmbedImage? _image; private EmbedThumbnail? _thumbnail; private List _fields; @@ -70,26 +69,14 @@ namespace Discord /// Gets or sets the URL of an . /// Url is not a well-formed . /// The URL of the embed. - public string Url - { - get => _url; - set - { - if (!value.IsNullOrUri()) throw new ArgumentException(message: "Url must be a well-formed URI.", paramName: nameof(Url)); - _url = value; - } - } + public string Url { get; set; } /// Gets or sets the thumbnail URL of an . /// Url is not a well-formed . /// The thumbnail URL of the embed. public string ThumbnailUrl { get => _thumbnail?.Url; - set - { - if (!value.IsNullOrUri()) throw new ArgumentException(message: "Url must be a well-formed URI.", paramName: nameof(ThumbnailUrl)); - _thumbnail = new EmbedThumbnail(value, null, null, null); - } + set => _thumbnail = new EmbedThumbnail(value, null, null, null); } /// Gets or sets the image URL of an . /// Url is not a well-formed . @@ -97,11 +84,7 @@ namespace Discord public string ImageUrl { get => _image?.Url; - set - { - if (!value.IsNullOrUri()) throw new ArgumentException(message: "Url must be a well-formed URI.", paramName: nameof(ImageUrl)); - _image = new EmbedImage(value, null, null, null); - } + set => _image = new EmbedImage(value, null, null, null); } /// Gets or sets the list of of an . @@ -553,8 +536,6 @@ namespace Discord public class EmbedAuthorBuilder { private string _name; - private string _url; - private string _iconUrl; /// /// Gets the maximum author name length allowed by Discord. /// @@ -585,15 +566,7 @@ namespace Discord /// /// The URL of the author field. /// - public string Url - { - get => _url; - set - { - if (!value.IsNullOrUri()) throw new ArgumentException(message: "Url must be a well-formed URI.", paramName: nameof(Url)); - _url = value; - } - } + public string Url { get; set; } /// /// Gets or sets the icon URL of the author field. /// @@ -601,15 +574,7 @@ namespace Discord /// /// The icon URL of the author field. /// - public string IconUrl - { - get => _iconUrl; - set - { - if (!value.IsNullOrUri()) throw new ArgumentException(message: "Url must be a well-formed URI.", paramName: nameof(IconUrl)); - _iconUrl = value; - } - } + public string IconUrl { get; set; } /// /// Sets the name of the author field. @@ -671,7 +636,6 @@ namespace Discord public class EmbedFooterBuilder { private string _text; - private string _iconUrl; /// /// Gets the maximum footer length allowed by Discord. @@ -703,15 +667,7 @@ namespace Discord /// /// The icon URL of the footer field. /// - public string IconUrl - { - get => _iconUrl; - set - { - if (!value.IsNullOrUri()) throw new ArgumentException(message: "Url must be a well-formed URI.", paramName: nameof(IconUrl)); - _iconUrl = value; - } - } + public string IconUrl { get; set; } /// /// Sets the name of the footer field. diff --git a/src/Discord.Net.Core/Entities/Messages/IMessage.cs b/src/Discord.Net.Core/Entities/Messages/IMessage.cs index 80b1ffa68..eb135768c 100644 --- a/src/Discord.Net.Core/Entities/Messages/IMessage.cs +++ b/src/Discord.Net.Core/Entities/Messages/IMessage.cs @@ -164,6 +164,14 @@ namespace Discord /// IReadOnlyDictionary Reactions { get; } + /// + /// Gets all stickers included in this message. + /// + /// + /// A read-only collection of sticker objects. + /// + IReadOnlyCollection Stickers { get; } + /// /// Gets the flags related to this message. /// diff --git a/src/Discord.Net.Core/Entities/Messages/ISticker.cs b/src/Discord.Net.Core/Entities/Messages/ISticker.cs new file mode 100644 index 000000000..e7e4405b6 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Messages/ISticker.cs @@ -0,0 +1,67 @@ +using System.Collections.Generic; + +namespace Discord +{ + /// + /// Represents a discord sticker. + /// + public interface ISticker + { + /// + /// Gets the ID of this sticker. + /// + /// + /// A snowflake ID associated with this sticker. + /// + ulong Id { get; } + /// + /// Gets the ID of the pack of this sticker. + /// + /// + /// A snowflake ID associated with the pack of this sticker. + /// + ulong PackId { get; } + /// + /// Gets the name of this sticker. + /// + /// + /// A with the name of this sticker. + /// + string Name { get; } + /// + /// Gets the description of this sticker. + /// + /// + /// A with the description of this sticker. + /// + string Description { get; } + /// + /// Gets the list of tags of this sticker. + /// + /// + /// A read-only list with the tags of this sticker. + /// + IReadOnlyCollection Tags { get; } + /// + /// Gets the asset hash of this sticker. + /// + /// + /// A with the asset hash of this sticker. + /// + string Asset { get; } + /// + /// Gets the preview asset hash of this sticker. + /// + /// + /// A with the preview asset hash of this sticker. + /// + string PreviewAsset { get; } + /// + /// Gets the format type of this sticker. + /// + /// + /// A with the format type of this sticker. + /// + StickerFormatType FormatType { get; } + } +} diff --git a/src/Discord.Net.Core/Entities/Messages/SticketFormatType.cs b/src/Discord.Net.Core/Entities/Messages/SticketFormatType.cs new file mode 100644 index 000000000..d24a38534 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Messages/SticketFormatType.cs @@ -0,0 +1,15 @@ +namespace Discord +{ + /// Defines the types of formats for stickers. + public enum StickerFormatType + { + /// Default value for a sticker format type. + None = 0, + /// The sticker format type is png. + Png = 1, + /// The sticker format type is apng. + Apng = 2, + /// The sticker format type is lottie. + Lottie = 3, + } +} diff --git a/src/Discord.Net.Core/Entities/Users/IGuildUser.cs b/src/Discord.Net.Core/Entities/Users/IGuildUser.cs index 582e9e7a7..492cb9566 100644 --- a/src/Discord.Net.Core/Entities/Users/IGuildUser.cs +++ b/src/Discord.Net.Core/Entities/Users/IGuildUser.cs @@ -113,7 +113,15 @@ namespace Discord /// A task that represents the asynchronous modification operation. /// Task ModifyAsync(Action func, RequestOptions options = null); - + /// + /// Adds the specified role to this user in the guild. + /// + /// The role to be added to the user. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous role addition operation. + /// + Task AddRoleAsync(ulong roleId, RequestOptions options = null); /// /// Adds the specified role to this user in the guild. /// @@ -124,6 +132,15 @@ namespace Discord /// Task AddRoleAsync(IRole role, RequestOptions options = null); /// + /// Adds the specified to this user in the guild. + /// + /// The roles to be added to the user. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous role addition operation. + /// + Task AddRolesAsync(IEnumerable roleIds, RequestOptions options = null); + /// /// Adds the specified to this user in the guild. /// /// The roles to be added to the user. @@ -133,6 +150,15 @@ namespace Discord /// Task AddRolesAsync(IEnumerable roles, RequestOptions options = null); /// + /// Removes the specified from this user in the guild. + /// + /// The role to be removed from the user. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous role removal operation. + /// + Task RemoveRoleAsync(ulong roleId, RequestOptions options = null); + /// /// Removes the specified from this user in the guild. /// /// The role to be removed from the user. @@ -142,6 +168,15 @@ namespace Discord /// Task RemoveRoleAsync(IRole role, RequestOptions options = null); /// + /// Removes the specified from this user in the guild. + /// + /// The roles to be removed from the user. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous role removal operation. + /// + Task RemoveRolesAsync(IEnumerable roleIds, RequestOptions options = null); + /// /// Removes the specified from this user in the guild. /// /// The roles to be removed from the user. diff --git a/src/Discord.Net.Core/Utils/SnowflakeUtils.cs b/src/Discord.Net.Core/Utils/SnowflakeUtils.cs index dd8f8ca66..e52c99376 100644 --- a/src/Discord.Net.Core/Utils/SnowflakeUtils.cs +++ b/src/Discord.Net.Core/Utils/SnowflakeUtils.cs @@ -12,7 +12,7 @@ namespace Discord /// /// The snowflake identifier to resolve. /// - /// A representing the time for when the object is geenrated. + /// A representing the time for when the object is generated. /// public static DateTimeOffset FromSnowflake(ulong value) => DateTimeOffset.FromUnixTimeMilliseconds((long)((value >> 22) + 1420070400000UL)); diff --git a/src/Discord.Net.Rest/API/Common/AuditLogEntry.cs b/src/Discord.Net.Rest/API/Common/AuditLogEntry.cs index 80d9a9e97..7458a19cb 100644 --- a/src/Discord.Net.Rest/API/Common/AuditLogEntry.cs +++ b/src/Discord.Net.Rest/API/Common/AuditLogEntry.cs @@ -7,7 +7,7 @@ namespace Discord.API [JsonProperty("target_id")] public ulong? TargetId { get; set; } [JsonProperty("user_id")] - public ulong UserId { get; set; } + public ulong? UserId { get; set; } [JsonProperty("changes")] public AuditLogChange[] Changes { get; set; } diff --git a/src/Discord.Net.Rest/API/Common/Message.cs b/src/Discord.Net.Rest/API/Common/Message.cs index b781de346..6ea2c29ff 100644 --- a/src/Discord.Net.Rest/API/Common/Message.cs +++ b/src/Discord.Net.Rest/API/Common/Message.cs @@ -58,5 +58,7 @@ namespace Discord.API public Optional AllowedMentions { get; set; } [JsonProperty("referenced_message")] public Optional ReferencedMessage { get; set; } + [JsonProperty("stickers")] + public Optional Stickers { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/Sticker.cs b/src/Discord.Net.Rest/API/Common/Sticker.cs new file mode 100644 index 000000000..0d1cac974 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/Sticker.cs @@ -0,0 +1,25 @@ +#pragma warning disable CS1591 +using Newtonsoft.Json; + +namespace Discord.API +{ + internal class Sticker + { + [JsonProperty("id")] + public ulong Id { get; set; } + [JsonProperty("pack_id")] + public ulong PackId { get; set; } + [JsonProperty("name")] + public string Name { get; set; } + [JsonProperty("description")] + public string Desription { get; set; } + [JsonProperty("tags")] + public Optional Tags { get; set; } + [JsonProperty("asset")] + public string Asset { get; set; } + [JsonProperty("preview_asset")] + public string PreviewAsset { get; set; } + [JsonProperty("format_type")] + public StickerFormatType FormatType { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Rest/ModifyWebhookMessageParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyWebhookMessageParams.cs new file mode 100644 index 000000000..ba8fcbb4e --- /dev/null +++ b/src/Discord.Net.Rest/API/Rest/ModifyWebhookMessageParams.cs @@ -0,0 +1,16 @@ +#pragma warning disable CS1591 +using Newtonsoft.Json; + +namespace Discord.API.Rest +{ + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] + internal class ModifyWebhookMessageParams + { + [JsonProperty("content")] + public Optional Content { get; set; } + [JsonProperty("embeds")] + public Optional Embeds { get; set; } + [JsonProperty("allowed_mentions")] + public Optional AllowedMentions { get; set; } + } +} diff --git a/src/Discord.Net.Rest/ClientHelper.cs b/src/Discord.Net.Rest/ClientHelper.cs index 8910e999a..b60c02cab 100644 --- a/src/Discord.Net.Rest/ClientHelper.cs +++ b/src/Discord.Net.Rest/ClientHelper.cs @@ -17,7 +17,7 @@ namespace Discord.Rest return RestApplication.Create(client, model); } - public static async Task GetChannelAsync(BaseDiscordClient client, + public static async Task GetChannelAsync(BaseDiscordClient client, ulong id, RequestOptions options) { var model = await client.ApiClient.GetChannelAsync(id, options).ConfigureAwait(false); @@ -45,13 +45,13 @@ namespace Discord.Rest .Where(x => x.Type == ChannelType.Group) .Select(x => RestGroupChannel.Create(client, x)).ToImmutableArray(); } - + public static async Task> GetConnectionsAsync(BaseDiscordClient client, RequestOptions options) { var models = await client.ApiClient.GetMyConnectionsAsync(options).ConfigureAwait(false); return models.Select(RestConnection.Create).ToImmutableArray(); } - + public static async Task GetInviteAsync(BaseDiscordClient client, string inviteId, RequestOptions options) { @@ -60,7 +60,7 @@ namespace Discord.Rest return RestInviteMetadata.Create(client, null, null, model); return null; } - + public static async Task GetGuildAsync(BaseDiscordClient client, ulong id, bool withCounts, RequestOptions options) { @@ -85,7 +85,7 @@ namespace Discord.Rest return RestGuildWidget.Create(model); return null; } - public static IAsyncEnumerable> GetGuildSummariesAsync(BaseDiscordClient client, + public static IAsyncEnumerable> GetGuildSummariesAsync(BaseDiscordClient client, ulong? fromGuildId, int? limit, RequestOptions options) { return new PagedAsyncEnumerable( @@ -136,7 +136,7 @@ namespace Discord.Rest var model = await client.ApiClient.CreateGuildAsync(args, options).ConfigureAwait(false); return RestGuild.Create(client, model); } - + public static async Task GetUserAsync(BaseDiscordClient client, ulong id, RequestOptions options) { @@ -201,5 +201,9 @@ namespace Discord.Rest } }; } + public static Task AddRoleAsync(BaseDiscordClient client, ulong guildId, ulong userId, ulong roleId, RequestOptions options = null) + => client.ApiClient.AddRoleAsync(guildId, userId, roleId, options); + public static Task RemoveRoleAsync(BaseDiscordClient client, ulong guildId, ulong userId, ulong roleId, RequestOptions options = null) + => client.ApiClient.RemoveRoleAsync(guildId, userId, roleId, options); } } diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index 592ad7e92..d3e4aa515 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -523,6 +523,43 @@ namespace Discord.API var ids = new BucketIds(webhookId: webhookId); return await SendJsonAsync("POST", () => $"webhooks/{webhookId}/{AuthToken}?wait=true", args, ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false); } + + /// Message content is too long, length must be less or equal to . + /// This operation may only be called with a token. + public async Task ModifyWebhookMessageAsync(ulong webhookId, ulong messageId, ModifyWebhookMessageParams args, RequestOptions options = null) + { + if (AuthTokenType != TokenType.Webhook) + throw new InvalidOperationException($"This operation may only be called with a {nameof(TokenType.Webhook)} token."); + + Preconditions.NotNull(args, nameof(args)); + Preconditions.NotEqual(webhookId, 0, nameof(webhookId)); + Preconditions.NotEqual(messageId, 0, nameof(messageId)); + + if (args.Embeds.IsSpecified) + Preconditions.AtMost(args.Embeds.Value.Length, 10, nameof(args.Embeds), "A max of 10 Embeds are allowed."); + if (args.Content.IsSpecified && args.Content.Value.Length > DiscordConfig.MaxMessageSize) + throw new ArgumentException(message: $"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", paramName: nameof(args.Content)); + options = RequestOptions.CreateOrClone(options); + + var ids = new BucketIds(webhookId: webhookId); + await SendJsonAsync("PATCH", () => $"webhooks/{webhookId}/{AuthToken}/messages/{messageId}", args, ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false); + } + + /// This operation may only be called with a token. + public async Task DeleteWebhookMessageAsync(ulong webhookId, ulong messageId, RequestOptions options = null) + { + if (AuthTokenType != TokenType.Webhook) + throw new InvalidOperationException($"This operation may only be called with a {nameof(TokenType.Webhook)} token."); + + Preconditions.NotEqual(webhookId, 0, nameof(webhookId)); + Preconditions.NotEqual(messageId, 0, nameof(messageId)); + + options = RequestOptions.CreateOrClone(options); + + var ids = new BucketIds(webhookId: webhookId); + await SendAsync("DELETE", () => $"webhooks/{webhookId}/{AuthToken}/messages/{messageId}", ids, options: options).ConfigureAwait(false); + } + /// Message content is too long, length must be less or equal to . public async Task UploadFileAsync(ulong channelId, UploadFileParams args, RequestOptions options = null) { @@ -1243,6 +1280,15 @@ namespace Discord.API } //Guild emoji + public async Task> GetGuildEmotesAsync(ulong guildId, RequestOptions options = null) + { + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + options = RequestOptions.CreateOrClone(options); + + var ids = new BucketIds(guildId: guildId); + return await SendAsync>("GET", () => $"guilds/{guildId}/emojis", ids, options: options).ConfigureAwait(false); + } + public async Task GetGuildEmoteAsync(ulong guildId, ulong emoteId, RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); diff --git a/src/Discord.Net.Rest/DiscordRestClient.cs b/src/Discord.Net.Rest/DiscordRestClient.cs index 48c40fdfa..7eff7363c 100644 --- a/src/Discord.Net.Rest/DiscordRestClient.cs +++ b/src/Discord.Net.Rest/DiscordRestClient.cs @@ -107,7 +107,19 @@ namespace Discord.Rest => ClientHelper.GetVoiceRegionAsync(this, id, options); public Task GetWebhookAsync(ulong id, RequestOptions options = null) => ClientHelper.GetWebhookAsync(this, id, options); - + public Task AddRoleAsync(ulong guildId, ulong userId, ulong roleId) + => ClientHelper.AddRoleAsync(this, guildId, userId, roleId); + public Task RemoveRoleAsync(ulong guildId, ulong userId, ulong roleId) + => ClientHelper.RemoveRoleAsync(this, guildId, userId, roleId); + + public Task AddReactionAsync(ulong channelId, ulong messageId, IEmote emote, RequestOptions options = null) + => MessageHelper.AddReactionAsync(channelId, messageId, emote, this, options); + public Task RemoveReactionAsync(ulong channelId, ulong messageId, ulong userId, IEmote emote, RequestOptions options = null) + => MessageHelper.RemoveReactionAsync(channelId, messageId, userId, emote, this, options); + public Task RemoveAllReactionsAsync(ulong channelId, ulong messageId, RequestOptions options = null) + => MessageHelper.RemoveAllReactionsAsync(channelId, messageId, this, options); + public Task RemoveAllReactionsForEmoteAsync(ulong channelId, ulong messageId, IEmote emote, RequestOptions options = null) + => MessageHelper.RemoveAllReactionsForEmoteAsync(channelId, messageId, emote, this, options); //IDiscordClient /// async Task IDiscordClient.GetApplicationInfoAsync(RequestOptions options) diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelInfo.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelInfo.cs index 0284b63f5..f50d9eeb3 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelInfo.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelInfo.cs @@ -5,13 +5,14 @@ namespace Discord.Rest /// public struct ChannelInfo { - internal ChannelInfo(string name, string topic, int? rateLimit, bool? nsfw, int? bitrate) + internal ChannelInfo(string name, string topic, int? rateLimit, bool? nsfw, int? bitrate, ChannelType? type) { Name = name; Topic = topic; SlowModeInterval = rateLimit; IsNsfw = nsfw; Bitrate = bitrate; + ChannelType = type; } /// @@ -53,5 +54,12 @@ namespace Discord.Rest /// null if this is not mentioned in this entry. /// public int? Bitrate { get; } + /// + /// Gets the type of this channel. + /// + /// + /// The channel type of this channel; null if not applicable. + /// + public ChannelType? ChannelType { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelUpdateAuditLogData.cs index fa5233145..b2294f183 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelUpdateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelUpdateAuditLogData.cs @@ -26,6 +26,7 @@ namespace Discord.Rest var rateLimitPerUserModel = changes.FirstOrDefault(x => x.ChangedProperty == "rate_limit_per_user"); var nsfwModel = changes.FirstOrDefault(x => x.ChangedProperty == "nsfw"); var bitrateModel = changes.FirstOrDefault(x => x.ChangedProperty == "bitrate"); + var typeModel = changes.FirstOrDefault(x => x.ChangedProperty == "type"); string oldName = nameModel?.OldValue?.ToObject(discord.ApiClient.Serializer), newName = nameModel?.NewValue?.ToObject(discord.ApiClient.Serializer); @@ -37,9 +38,11 @@ namespace Discord.Rest newNsfw = nsfwModel?.NewValue?.ToObject(discord.ApiClient.Serializer); int? oldBitrate = bitrateModel?.OldValue?.ToObject(discord.ApiClient.Serializer), newBitrate = bitrateModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + ChannelType? oldType = typeModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newType = typeModel?.NewValue?.ToObject(discord.ApiClient.Serializer); - var before = new ChannelInfo(oldName, oldTopic, oldRateLimitPerUser, oldNsfw, oldBitrate); - var after = new ChannelInfo(newName, newTopic, newRateLimitPerUser, newNsfw, newBitrate); + var before = new ChannelInfo(oldName, oldTopic, oldRateLimitPerUser, oldNsfw, oldBitrate, oldType); + var after = new ChannelInfo(newName, newTopic, newRateLimitPerUser, newNsfw, newBitrate, newType); return new ChannelUpdateAuditLogData(entry.TargetId.Value, before, after); } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessagePinAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessagePinAuditLogData.cs index 020171152..be66ac846 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessagePinAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessagePinAuditLogData.cs @@ -19,8 +19,14 @@ namespace Discord.Rest internal static MessagePinAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) { - var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId); - return new MessagePinAuditLogData(entry.Options.MessageId.Value, entry.Options.ChannelId.Value, RestUser.Create(discord, userInfo)); + RestUser user = null; + if (entry.TargetId.HasValue) + { + var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId); + user = RestUser.Create(discord, userInfo); + } + + return new MessagePinAuditLogData(entry.Options.MessageId.Value, entry.Options.ChannelId.Value, user); } /// @@ -38,10 +44,10 @@ namespace Discord.Rest /// public ulong ChannelId { get; } /// - /// Gets the user of the message that was pinned. + /// Gets the user of the message that was pinned if available. /// /// - /// A user object representing the user that created the pinned message. + /// A user object representing the user that created the pinned message or . /// public IUser Target { get; } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageUnpinAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageUnpinAuditLogData.cs index 1b3ff96f3..b4fa389cc 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageUnpinAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageUnpinAuditLogData.cs @@ -19,8 +19,14 @@ namespace Discord.Rest internal static MessageUnpinAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) { - var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId); - return new MessageUnpinAuditLogData(entry.Options.MessageId.Value, entry.Options.ChannelId.Value, RestUser.Create(discord, userInfo)); + RestUser user = null; + if (entry.TargetId.HasValue) + { + var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId); + user = RestUser.Create(discord, userInfo); + } + + return new MessageUnpinAuditLogData(entry.Options.MessageId.Value, entry.Options.ChannelId.Value, user); } /// @@ -38,10 +44,10 @@ namespace Discord.Rest /// public ulong ChannelId { get; } /// - /// Gets the user of the message that was unpinned. + /// Gets the user of the message that was unpinned if available. /// /// - /// A user object representing the user that created the unpinned message. + /// A user object representing the user that created the unpinned message or . /// public IUser Target { get; } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs b/src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs index d604077f4..2176eab71 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs @@ -22,7 +22,7 @@ namespace Discord.Rest internal static RestAuditLogEntry Create(BaseDiscordClient discord, Model fullLog, EntryModel model) { - var userInfo = fullLog.Users.FirstOrDefault(x => x.Id == model.UserId); + var userInfo = model.UserId != null ? fullLog.Users.FirstOrDefault(x => x.Id == model.UserId) : null; IUser user = null; if (userInfo != null) user = RestUser.Create(discord, userInfo); diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index c1d0ac294..7c4edb43e 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -286,6 +286,13 @@ namespace Discord.Rest return RestUserMessage.Create(client, channel, client.CurrentUser, model); } + public static async Task ModifyMessageAsync(IMessageChannel channel, ulong messageId, Action func, + BaseDiscordClient client, RequestOptions options) + { + var msgModel = await MessageHelper.ModifyAsync(channel.Id, messageId, client, func, options).ConfigureAwait(false); + return RestUserMessage.Create(client, channel, msgModel.Author.IsSpecified ? RestUser.Create(client, msgModel.Author.Value) : client.CurrentUser, msgModel); + } + public static Task DeleteMessageAsync(IMessageChannel channel, ulong messageId, BaseDiscordClient client, RequestOptions options) => MessageHelper.DeleteAsync(channel.Id, messageId, client, options); diff --git a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs index d59a10fb5..6ccfd204c 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs @@ -135,6 +135,10 @@ namespace Discord.Rest public Task DeleteMessageAsync(IMessage message, RequestOptions options = null) => ChannelHelper.DeleteMessageAsync(this, message.Id, Discord, options); + /// + public async Task ModifyMessageAsync(ulong messageId, Action func, RequestOptions options = null) + => await ChannelHelper.ModifyMessageAsync(this, messageId, func, Discord, options).ConfigureAwait(false); + /// public Task TriggerTypingAsync(RequestOptions options = null) => ChannelHelper.TriggerTypingAsync(this, Discord, options); diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs index 666d6a4f4..2b0ab8b42 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs @@ -93,6 +93,10 @@ namespace Discord.Rest public Task DeleteMessageAsync(IMessage message, RequestOptions options = null) => ChannelHelper.DeleteMessageAsync(this, message.Id, Discord, options); + /// + public async Task ModifyMessageAsync(ulong messageId, Action func, RequestOptions options = null) + => await ChannelHelper.ModifyMessageAsync(this, messageId, func, Discord, options).ConfigureAwait(false); + /// /// Message content is too long, length must be less or equal to . public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null) diff --git a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs index fdc4b7988..c6d0b0509 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs @@ -152,6 +152,10 @@ namespace Discord.Rest public Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null) => ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options); + /// + public async Task ModifyMessageAsync(ulong messageId, Action func, RequestOptions options = null) + => await ChannelHelper.ModifyMessageAsync(this, messageId, func, Discord, options).ConfigureAwait(false); + /// public Task TriggerTypingAsync(RequestOptions options = null) => ChannelHelper.TriggerTypingAsync(this, Discord, options); diff --git a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs index 04ec27930..d10d046ee 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs @@ -496,6 +496,11 @@ namespace Discord.Rest } //Emotes + public static async Task> GetEmotesAsync(IGuild guild, BaseDiscordClient client, RequestOptions options) + { + var models = await client.ApiClient.GetGuildEmotesAsync(guild.Id, options).ConfigureAwait(false); + return models.Select(x => x.ToEntity()).ToImmutableArray(); + } public static async Task GetEmoteAsync(IGuild guild, BaseDiscordClient client, ulong id, RequestOptions options) { var emote = await client.ApiClient.GetGuildEmoteAsync(guild.Id, id, options).ConfigureAwait(false); diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs index c74e128a8..57918a1e7 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs @@ -828,6 +828,9 @@ namespace Discord.Rest //Emotes /// + public Task> GetEmotesAsync(RequestOptions options = null) + => GuildHelper.GetEmotesAsync(this, Discord, options); + /// public Task GetEmoteAsync(ulong id, RequestOptions options = null) => GuildHelper.GetEmoteAsync(this, Discord, id, options); /// diff --git a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs index e22ae71cd..1bc284836 100644 --- a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs +++ b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs @@ -71,6 +71,48 @@ namespace Discord.Rest return await client.ApiClient.ModifyMessageAsync(msg.Channel.Id, msg.Id, apiArgs, options).ConfigureAwait(false); } + public static async Task ModifyAsync(ulong channelId, ulong msgId, BaseDiscordClient client, Action func, + RequestOptions options) + { + var args = new MessageProperties(); + func(args); + + if ((args.Content.IsSpecified && string.IsNullOrEmpty(args.Content.Value)) && (args.Embed.IsSpecified && args.Embed.Value == null)) + Preconditions.NotNullOrEmpty(args.Content.IsSpecified ? args.Content.Value : string.Empty, nameof(args.Content)); + + if (args.AllowedMentions.IsSpecified) + { + AllowedMentions allowedMentions = args.AllowedMentions.Value; + Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); + Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); + + // check that user flag and user Id list are exclusive, same with role flag and role Id list + if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue) + { + if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users) && + allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0) + { + throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(allowedMentions)); + } + + if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles) && + allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0) + { + throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(allowedMentions)); + } + } + } + + var apiArgs = new API.Rest.ModifyMessageParams + { + Content = args.Content, + Embed = args.Embed.IsSpecified ? args.Embed.Value.ToModel() : Optional.Create(), + Flags = args.Flags.IsSpecified ? args.Flags.Value : Optional.Create(), + AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value.ToModel() : Optional.Create(), + }; + return await client.ApiClient.ModifyMessageAsync(channelId, msgId, apiArgs, options).ConfigureAwait(false); + } + public static Task DeleteAsync(IMessage msg, BaseDiscordClient client, RequestOptions options) => DeleteAsync(msg.Channel.Id, msg.Id, client, options); @@ -89,21 +131,41 @@ namespace Discord.Rest await client.ApiClient.SuppressEmbedAsync(msg.Channel.Id, msg.Id, apiArgs, options).ConfigureAwait(false); } + public static async Task AddReactionAsync(ulong channelId, ulong messageId, IEmote emote, BaseDiscordClient client, RequestOptions options) + { + 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(IMessage msg, IEmote emote, BaseDiscordClient client, RequestOptions options) { await client.ApiClient.AddReactionAsync(msg.Channel.Id, msg.Id, emote is Emote e ? $"{e.Name}:{e.Id}" : UrlEncode(emote.Name), options).ConfigureAwait(false); } + public static async Task RemoveReactionAsync(ulong channelId, ulong messageId, ulong userId, IEmote emote, BaseDiscordClient client, RequestOptions options) + { + await client.ApiClient.RemoveReactionAsync(channelId, messageId, userId, emote is Emote e ? $"{e.Name}:{e.Id}" : UrlEncode(emote.Name), options).ConfigureAwait(false); + } + public static async Task RemoveReactionAsync(IMessage msg, ulong userId, IEmote emote, BaseDiscordClient client, RequestOptions options) { await client.ApiClient.RemoveReactionAsync(msg.Channel.Id, msg.Id, userId, emote is Emote e ? $"{e.Name}:{e.Id}" : UrlEncode(emote.Name), options).ConfigureAwait(false); } + public static async Task RemoveAllReactionsAsync(ulong channelId, ulong messageId, BaseDiscordClient client, RequestOptions options) + { + await client.ApiClient.RemoveAllReactionsAsync(channelId, messageId, options).ConfigureAwait(false); + } + public static async Task RemoveAllReactionsAsync(IMessage msg, BaseDiscordClient client, RequestOptions options) { await client.ApiClient.RemoveAllReactionsAsync(msg.Channel.Id, msg.Id, options).ConfigureAwait(false); } + public static async Task RemoveAllReactionsForEmoteAsync(ulong channelId, ulong messageId, IEmote emote, BaseDiscordClient client, RequestOptions options) + { + await client.ApiClient.RemoveAllReactionsForEmoteAsync(channelId, messageId, emote is Emote e ? $"{e.Name}:{e.Id}" : UrlEncode(emote.Name), options).ConfigureAwait(false); + } + public static async Task RemoveAllReactionsForEmoteAsync(IMessage msg, IEmote emote, BaseDiscordClient client, RequestOptions options) { await client.ApiClient.RemoveAllReactionsForEmoteAsync(msg.Channel.Id, msg.Id, emote is Emote e ? $"{e.Name}:{e.Id}" : UrlEncode(emote.Name), options).ConfigureAwait(false); diff --git a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs index b2a745980..793e89dff 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs @@ -58,6 +58,8 @@ namespace Discord.Rest public virtual IReadOnlyCollection MentionedUsers => ImmutableArray.Create(); /// public virtual IReadOnlyCollection Tags => ImmutableArray.Create(); + /// + public virtual IReadOnlyCollection Stickers => ImmutableArray.Create(); /// public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks); @@ -173,6 +175,8 @@ namespace Discord.Rest IReadOnlyCollection IMessage.Embeds => Embeds; /// IReadOnlyCollection IMessage.MentionedUserIds => MentionedUsers.Select(x => x.Id).ToImmutableArray(); + /// + IReadOnlyCollection IMessage.Stickers => Stickers; /// public IReadOnlyDictionary Reactions => _reactions.ToDictionary(x => x.Emote, x => new ReactionMetadata { ReactionCount = x.Count, IsMe = x.Me }); diff --git a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs index cf025aea1..1274f1fd3 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs @@ -21,6 +21,7 @@ namespace Discord.Rest private ImmutableArray _tags = ImmutableArray.Create(); private ImmutableArray _roleMentionIds = ImmutableArray.Create(); private ImmutableArray _userMentions = ImmutableArray.Create(); + private ImmutableArray _stickers = ImmutableArray.Create(); /// public override bool IsTTS => _isTTS; @@ -45,6 +46,8 @@ namespace Discord.Rest /// public override IReadOnlyCollection Tags => _tags; /// + public override IReadOnlyCollection Stickers => _stickers; + /// public IUserMessage ReferencedMessage => _referencedMessage; internal RestUserMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, IUser author, MessageSource source) @@ -132,6 +135,20 @@ namespace Discord.Rest IUser refMsgAuthor = MessageHelper.GetAuthor(Discord, guild, refMsg.Author.Value, refMsg.WebhookId.ToNullable()); _referencedMessage = RestUserMessage.Create(Discord, Channel, refMsgAuthor, refMsg); } + + if (model.Stickers.IsSpecified) + { + var value = model.Stickers.Value; + if (value.Length > 0) + { + var stickers = ImmutableArray.CreateBuilder(value.Length); + for (int i = 0; i < value.Length; i++) + stickers.Add(Sticker.Create(value[i])); + _stickers = stickers.ToImmutable(); + } + else + _stickers = ImmutableArray.Create(); + } } /// diff --git a/src/Discord.Net.Rest/Entities/Messages/Sticker.cs b/src/Discord.Net.Rest/Entities/Messages/Sticker.cs new file mode 100644 index 000000000..5482bed74 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Messages/Sticker.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using System.Diagnostics; +using Model = Discord.API.Sticker; + +namespace Discord +{ + /// + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public class Sticker : ISticker + { + /// + public ulong Id { get; } + /// + public ulong PackId { get; } + /// + public string Name { get; } + /// + public string Description { get; } + /// + public IReadOnlyCollection Tags { get; } + /// + public string Asset { get; } + /// + public string PreviewAsset { get; } + /// + public StickerFormatType FormatType { get; } + + internal Sticker(ulong id, ulong packId, string name, string description, string[] tags, string asset, string previewAsset, StickerFormatType formatType) + { + Id = id; + PackId = packId; + Name = name; + Description = description; + Tags = tags.ToReadOnlyCollection(); + Asset = asset; + PreviewAsset = previewAsset; + FormatType = formatType; + } + internal static Sticker Create(Model model) + { + return new Sticker(model.Id, model.PackId, model.Name, model.Desription, + model.Tags.IsSpecified ? model.Tags.Value.Split(',') : new string[0], + model.Asset, model.PreviewAsset, model.FormatType); + } + + private string DebuggerDisplay => $"{Name} ({Id})"; + } +} diff --git a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs index 4a6bc1025..6e6bbe09c 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs @@ -112,17 +112,29 @@ namespace Discord.Rest public Task KickAsync(string reason = null, RequestOptions options = null) => UserHelper.KickAsync(this, Discord, reason, options); /// + public Task AddRoleAsync(ulong roleId, RequestOptions options = null) + => AddRolesAsync(new[] { roleId }, options); + /// public Task AddRoleAsync(IRole role, RequestOptions options = null) - => AddRolesAsync(new[] { role }, options); + => AddRoleAsync(role.Id, options); + /// + public Task AddRolesAsync(IEnumerable roleIds, RequestOptions options = null) + => UserHelper.AddRolesAsync(this, Discord, roleIds, options); /// public Task AddRolesAsync(IEnumerable roles, RequestOptions options = null) - => UserHelper.AddRolesAsync(this, Discord, roles, options); + => AddRolesAsync(roles.Select(x => x.Id), options); + /// + public Task RemoveRoleAsync(ulong roleId, RequestOptions options = null) + => RemoveRolesAsync(new[] { roleId }, options); /// public Task RemoveRoleAsync(IRole role, RequestOptions options = null) - => RemoveRolesAsync(new[] { role }, options); + => RemoveRoleAsync(role.Id, options); + /// + public Task RemoveRolesAsync(IEnumerable roleIds, RequestOptions options = null) + => UserHelper.RemoveRolesAsync(this, Discord, roleIds, options); /// public Task RemoveRolesAsync(IEnumerable roles, RequestOptions options = null) - => UserHelper.RemoveRolesAsync(this, Discord, roles, options); + => RemoveRolesAsync(roles.Select(x => x.Id)); /// /// Resolving permissions requires the parent guild to be downloaded. diff --git a/src/Discord.Net.Rest/Entities/Users/RestWebhookUser.cs b/src/Discord.Net.Rest/Entities/Users/RestWebhookUser.cs index a06916c9b..2131fec93 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestWebhookUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestWebhookUser.cs @@ -59,27 +59,35 @@ namespace Discord.Rest /// ChannelPermissions IGuildUser.GetPermissions(IGuildChannel channel) => Permissions.ToChannelPerms(channel, GuildPermissions.Webhook.RawValue); /// - Task IGuildUser.KickAsync(string reason, RequestOptions options) => + Task IGuildUser.KickAsync(string reason, RequestOptions options) => throw new NotSupportedException("Webhook users cannot be kicked."); /// - Task IGuildUser.ModifyAsync(Action func, RequestOptions options) => + Task IGuildUser.ModifyAsync(Action func, RequestOptions options) => throw new NotSupportedException("Webhook users cannot be modified."); - /// - Task IGuildUser.AddRoleAsync(IRole role, RequestOptions options) => + Task IGuildUser.AddRoleAsync(ulong role, RequestOptions options) => throw new NotSupportedException("Roles are not supported on webhook users."); - /// - Task IGuildUser.AddRolesAsync(IEnumerable roles, RequestOptions options) => + Task IGuildUser.AddRoleAsync(IRole role, RequestOptions options) => throw new NotSupportedException("Roles are not supported on webhook users."); - /// - Task IGuildUser.RemoveRoleAsync(IRole role, RequestOptions options) => + Task IGuildUser.AddRolesAsync(IEnumerable roles, RequestOptions options) => + throw new NotSupportedException("Roles are not supported on webhook users."); + /// + Task IGuildUser.AddRolesAsync(IEnumerable roles, RequestOptions options) => + throw new NotSupportedException("Roles are not supported on webhook users."); + /// + Task IGuildUser.RemoveRoleAsync(ulong role, RequestOptions options) => + throw new NotSupportedException("Roles are not supported on webhook users."); + /// + Task IGuildUser.RemoveRoleAsync(IRole role, RequestOptions options) => + throw new NotSupportedException("Roles are not supported on webhook users."); + /// + Task IGuildUser.RemoveRolesAsync(IEnumerable roles, RequestOptions options) => throw new NotSupportedException("Roles are not supported on webhook users."); - /// - Task IGuildUser.RemoveRolesAsync(IEnumerable roles, RequestOptions options) => + Task IGuildUser.RemoveRolesAsync(IEnumerable roles, RequestOptions options) => throw new NotSupportedException("Roles are not supported on webhook users."); //IVoiceState diff --git a/src/Discord.Net.Rest/Entities/Users/UserHelper.cs b/src/Discord.Net.Rest/Entities/Users/UserHelper.cs index 58e8cd417..3a19fcfc1 100644 --- a/src/Discord.Net.Rest/Entities/Users/UserHelper.cs +++ b/src/Discord.Net.Rest/Entities/Users/UserHelper.cs @@ -73,16 +73,16 @@ namespace Discord.Rest return RestDMChannel.Create(client, await client.ApiClient.CreateDMChannelAsync(args, options).ConfigureAwait(false)); } - public static async Task AddRolesAsync(IGuildUser user, BaseDiscordClient client, IEnumerable roles, RequestOptions options) + public static async Task AddRolesAsync(IGuildUser user, BaseDiscordClient client, IEnumerable roleIds, RequestOptions options) { - foreach (var role in roles) - await client.ApiClient.AddRoleAsync(user.Guild.Id, user.Id, role.Id, options).ConfigureAwait(false); + foreach (var roleId in roleIds) + await client.ApiClient.AddRoleAsync(user.Guild.Id, user.Id, roleId, options).ConfigureAwait(false); } - public static async Task RemoveRolesAsync(IGuildUser user, BaseDiscordClient client, IEnumerable roles, RequestOptions options) + public static async Task RemoveRolesAsync(IGuildUser user, BaseDiscordClient client, IEnumerable roleIds, RequestOptions options) { - foreach (var role in roles) - await client.ApiClient.RemoveRoleAsync(user.Guild.Id, user.Id, role.Id, options).ConfigureAwait(false); + foreach (var roleId in roleIds) + await client.ApiClient.RemoveRoleAsync(user.Guild.Id, user.Id, roleId, options).ConfigureAwait(false); } } } diff --git a/src/Discord.Net.Rest/Entities/Webhooks/RestWebhook.cs b/src/Discord.Net.Rest/Entities/Webhooks/RestWebhook.cs index 1fdc95a63..9baddf003 100644 --- a/src/Discord.Net.Rest/Entities/Webhooks/RestWebhook.cs +++ b/src/Discord.Net.Rest/Entities/Webhooks/RestWebhook.cs @@ -11,11 +11,11 @@ namespace Discord.Rest internal IGuild Guild { get; private set; } internal ITextChannel Channel { get; private set; } - /// - public ulong ChannelId { get; } /// public string Token { get; } + /// + public ulong ChannelId { get; private set; } /// public string Name { get; private set; } /// @@ -56,6 +56,8 @@ namespace Discord.Rest internal void Update(Model model) { + if (ChannelId != model.ChannelId) + ChannelId = model.ChannelId; if (model.Avatar.IsSpecified) AvatarId = model.Avatar.Value; if (model.Creator.IsSpecified) diff --git a/src/Discord.Net.WebSocket/API/Gateway/Reaction.cs b/src/Discord.Net.WebSocket/API/Gateway/Reaction.cs index 62de456e2..a0a740868 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/Reaction.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/Reaction.cs @@ -1,4 +1,4 @@ -using Newtonsoft.Json; +using Newtonsoft.Json; namespace Discord.API.Gateway { @@ -12,5 +12,7 @@ namespace Discord.API.Gateway public ulong ChannelId { get; set; } [JsonProperty("emoji")] public Emoji Emoji { get; set; } + [JsonProperty("member")] + public Optional Member { get; set; } } } diff --git a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs index 110517999..966aec7fa 100644 --- a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs +++ b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs @@ -315,22 +315,6 @@ namespace Discord.WebSocket } internal readonly AsyncEvent> _guildUpdatedEvent = new AsyncEvent>(); - //Invites - internal readonly AsyncEvent> _inviteCreatedEvent = new AsyncEvent>(); - /// Fired when a invite is created. - public event Func InviteCreated - { - add { _inviteCreatedEvent.Add(value); } - remove { _inviteCreatedEvent.Remove(value); } - } - internal readonly AsyncEvent, Task>> _inviteDeletedEvent = new AsyncEvent, Task>>(); - /// Fired when a invite is deleted. - public event Func, Task> InviteDeleted - { - add { _inviteDeletedEvent.Add(value); } - remove { _inviteDeletedEvent.Remove(value); } - } - //Users /// Fired when a user joins a guild. public event Func UserJoined { diff --git a/src/Discord.Net.WebSocket/BaseSocketClient.cs b/src/Discord.Net.WebSocket/BaseSocketClient.cs index 425889613..36e6c02a9 100644 --- a/src/Discord.Net.WebSocket/BaseSocketClient.cs +++ b/src/Discord.Net.WebSocket/BaseSocketClient.cs @@ -209,6 +209,12 @@ namespace Discord.WebSocket /// The name of the game. /// If streaming, the URL of the stream. Must be a valid Twitch URL. /// The type of the game. + /// + /// + /// Bot accounts cannot set as their activity + /// type and it will have no effect. + /// + /// /// /// A task that represents the asynchronous set operation. /// @@ -222,6 +228,10 @@ namespace Discord.WebSocket /// Discord will only accept setting of name and the type of activity. /// /// + /// Bot accounts cannot set as their activity + /// type and it will have no effect. + /// + /// /// Rich Presence cannot be set via this method or client. Rich Presence is strictly limited to RPC /// clients only. /// diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index f3a8a6c2e..e284fd883 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -1384,6 +1384,14 @@ namespace Discord.WebSocket ? 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); @@ -1474,7 +1482,7 @@ namespace Discord.WebSocket var cacheable = new Cacheable(cachedMsg, data.MessageId, isCached, async () => await channel.GetMessageAsync(data.MessageId).ConfigureAwait(false) as IUserMessage); var emote = data.Emoji.ToIEmote(); - cachedMsg?.RemoveAllReactionsForEmoteAsync(emote); + cachedMsg?.RemoveReactionsForEmote(emote); await TimedInvokeAsync(_reactionsRemovedForEmoteEvent, nameof(ReactionsRemovedForEmote), cacheable, channel, emote).ConfigureAwait(false); } @@ -1741,61 +1749,6 @@ namespace Discord.WebSocket } break; - case "INVITE_CREATE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (INVITE_CREATE)").ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); - if(data.GuildID.HasValue) - { - var guild = State.GetGuild(data.GuildID.Value); - if (guild != null) - { - if (!guild.IsSynced) - { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); - return; - } - - var channel = guild.GetChannel(data.ChannelID); - - if (channel != null) - { - var invite = new SocketGuildInvite(this, guild, channel, data.InviteCode, data); - guild.AddSocketInvite(invite); - await TimedInvokeAsync(_inviteCreatedEvent, nameof(InviteCreated), invite).ConfigureAwait(false); - } - } - } - } - break; - case "INVITE_DELETE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (INVITE_DELETE)").ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); - if(data.GuildID.IsSpecified) - { - var guild = State.GetGuild(data.GuildID.Value); - if (guild != null) - { - if (!guild.IsSynced) - { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); - return; - } - - var channel = guild.GetChannel(data.ChannelID); - - if (channel != null) - { - var invite = guild.RemoveSocketInvite(data.Code); - var cache = new Cacheable(null, data.Code, invite != null, async () => await guild.GetSocketInviteAsync(data.Code)); - await TimedInvokeAsync(_inviteDeletedEvent, nameof(InviteDeleted), cache).ConfigureAwait(false); - } - } - } - - } - break; //Invites case "INVITE_CREATE": diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs index c123a9d4e..5de417036 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs @@ -152,6 +152,10 @@ namespace Discord.WebSocket public Task DeleteMessageAsync(IMessage message, RequestOptions options = null) => ChannelHelper.DeleteMessageAsync(this, message.Id, Discord, options); + /// + public async Task ModifyMessageAsync(ulong messageId, Action func, RequestOptions options = null) + => await ChannelHelper.ModifyMessageAsync(this, messageId, func, Discord, options).ConfigureAwait(false); + /// public Task TriggerTypingAsync(RequestOptions options = null) => ChannelHelper.TriggerTypingAsync(this, Discord, options); diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs index dc5ac4222..ab8c76aeb 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs @@ -180,6 +180,10 @@ namespace Discord.WebSocket public Task DeleteMessageAsync(IMessage message, RequestOptions options = null) => ChannelHelper.DeleteMessageAsync(this, message.Id, Discord, options); + /// + public async Task ModifyMessageAsync(ulong messageId, Action func, RequestOptions options = null) + => await ChannelHelper.ModifyMessageAsync(this, messageId, func, Discord, options).ConfigureAwait(false); + /// public Task TriggerTypingAsync(RequestOptions options = null) => ChannelHelper.TriggerTypingAsync(this, Discord, options); diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs index d7d918f9f..71a20c198 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs @@ -180,6 +180,10 @@ namespace Discord.WebSocket public Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null) => ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options); + /// + public async Task ModifyMessageAsync(ulong messageId, Action func, RequestOptions options = null) + => await ChannelHelper.ModifyMessageAsync(this, messageId, func, Discord, options).ConfigureAwait(false); + /// public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null) => ChannelHelper.DeleteMessageAsync(this, messageId, Discord, options); diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index 59dfbed2d..0e36c6b50 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -39,7 +39,6 @@ namespace Discord.WebSocket private ImmutableArray _emotes; private ImmutableArray _features; private AudioClient _audioClient; - private InviteCache _invites; #pragma warning restore IDISP002, IDISP006 /// @@ -361,7 +360,6 @@ namespace Discord.WebSocket _audioLock = new SemaphoreSlim(1, 1); _emotes = ImmutableArray.Create(); _features = ImmutableArray.Create(); - _invites = new InviteCache(client); } internal static SocketGuild Create(DiscordSocketClient discord, ClientState state, ExtendedModel model) { @@ -617,22 +615,6 @@ namespace Discord.WebSocket public Task RemoveBanAsync(ulong userId, RequestOptions options = null) => GuildHelper.RemoveBanAsync(this, Discord, userId, options); - //Invites - internal void AddSocketInvite(SocketGuildInvite invite) - => _invites.Add(invite); - internal SocketGuildInvite RemoveSocketInvite(string code) - => _invites.Remove(code); - internal async Task GetSocketInviteAsync(string code) - { - var invites = await this.GetInvitesAsync(); - RestInviteMetadata restInvite = invites.First(x => x.Code == code); - if (restInvite == null) - return null; - - var invite = new SocketGuildInvite(Discord, this, this.GetChannel(restInvite.ChannelId), code, restInvite); - return invite; - } - //Channels /// /// Gets a channel in this guild. @@ -1026,6 +1008,9 @@ namespace Discord.WebSocket //Emotes /// + public Task> GetEmotesAsync(RequestOptions options = null) + => GuildHelper.GetEmotesAsync(this, Discord, options); + /// public Task GetEmoteAsync(ulong id, RequestOptions options = null) => GuildHelper.GetEmoteAsync(this, Discord, id, options); /// diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs index 2ca53cbb9..8b45d882b 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs @@ -99,6 +99,8 @@ namespace Discord.WebSocket /// public virtual IReadOnlyCollection Tags => ImmutableArray.Create(); /// + public virtual IReadOnlyCollection Stickers => ImmutableArray.Create(); + /// public IReadOnlyDictionary Reactions => _reactions.GroupBy(r => r.Emote).ToDictionary(x => x.Key, x => new ReactionMetadata { ReactionCount = x.Count(), IsMe = x.Any(y => y.UserId == Discord.CurrentUser.Id) }); /// @@ -194,6 +196,8 @@ namespace Discord.WebSocket IReadOnlyCollection IMessage.MentionedRoleIds => MentionedRoles.Select(x => x.Id).ToImmutableArray(); /// IReadOnlyCollection IMessage.MentionedUserIds => MentionedUsers.Select(x => x.Id).ToImmutableArray(); + /// + IReadOnlyCollection IMessage.Stickers => Stickers; internal void AddReaction(SocketReaction reaction) { diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs index 859b1b80a..2a8b45ca1 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs @@ -23,6 +23,7 @@ namespace Discord.WebSocket private ImmutableArray _tags = ImmutableArray.Create(); private ImmutableArray _roleMentions = ImmutableArray.Create(); private ImmutableArray _userMentions = ImmutableArray.Create(); + private ImmutableArray _stickers = ImmutableArray.Create(); /// public override bool IsTTS => _isTTS; @@ -47,6 +48,8 @@ namespace Discord.WebSocket /// public override IReadOnlyCollection MentionedUsers => _userMentions; /// + public override IReadOnlyCollection Stickers => _stickers; + /// public IUserMessage ReferencedMessage => _referencedMessage; internal SocketUserMessage(DiscordSocketClient discord, ulong id, ISocketMessageChannel channel, SocketUser author, MessageSource source) @@ -158,6 +161,20 @@ namespace Discord.WebSocket refMsgAuthor = new SocketUnknownUser(Discord, id: 0); _referencedMessage = SocketUserMessage.Create(Discord, state, refMsgAuthor, Channel, refMsg); } + + if (model.Stickers.IsSpecified) + { + var value = model.Stickers.Value; + if (value.Length > 0) + { + var stickers = ImmutableArray.CreateBuilder(value.Length); + for (int i = 0; i < value.Length; i++) + stickers.Add(Sticker.Create(value[i])); + _stickers = stickers.ToImmutable(); + } + else + _stickers = ImmutableArray.Create(); + } } /// diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs index c0a681d9d..9263fe642 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs @@ -63,7 +63,7 @@ namespace Discord.WebSocket /// /// Returns a collection of roles that the user possesses. /// - public IReadOnlyCollection Roles + public IReadOnlyCollection Roles => _roleIds.Select(id => Guild.GetRole(id)).Where(x => x != null).ToReadOnlyCollection(() => _roleIds.Length); /// /// Returns the voice channel the user is in, or null if none. @@ -177,17 +177,29 @@ namespace Discord.WebSocket public Task KickAsync(string reason = null, RequestOptions options = null) => UserHelper.KickAsync(this, Discord, reason, options); /// + public Task AddRoleAsync(ulong roleId, RequestOptions options = null) + => AddRolesAsync(new[] { roleId }, options); + /// public Task AddRoleAsync(IRole role, RequestOptions options = null) - => AddRolesAsync(new[] { role }, options); + => AddRoleAsync(role.Id, options); + /// + public Task AddRolesAsync(IEnumerable roleIds, RequestOptions options = null) + => UserHelper.AddRolesAsync(this, Discord, roleIds, options); /// public Task AddRolesAsync(IEnumerable roles, RequestOptions options = null) - => UserHelper.AddRolesAsync(this, Discord, roles, options); + => AddRolesAsync(roles.Select(x => x.Id), options); + /// + public Task RemoveRoleAsync(ulong roleId, RequestOptions options = null) + => RemoveRolesAsync(new[] { roleId }, options); /// public Task RemoveRoleAsync(IRole role, RequestOptions options = null) - => RemoveRolesAsync(new[] { role }, options); + => RemoveRoleAsync(role.Id, options); + /// + public Task RemoveRolesAsync(IEnumerable roleIds, RequestOptions options = null) + => UserHelper.RemoveRolesAsync(this, Discord, roleIds, options); /// public Task RemoveRolesAsync(IEnumerable roles, RequestOptions options = null) - => UserHelper.RemoveRolesAsync(this, Discord, roles, options); + => RemoveRolesAsync(roles.Select(x => x.Id)); /// public ChannelPermissions GetPermissions(IGuildChannel channel) diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs index 5103aa8b3..c22164f95 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs @@ -31,7 +31,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 SocketGlobalUser GlobalUser => + internal override SocketGlobalUser GlobalUser => throw new NotSupportedException(); internal SocketWebhookUser(SocketGuild guild, ulong id, ulong webhookId) @@ -73,32 +73,52 @@ namespace Discord.WebSocket ChannelPermissions IGuildUser.GetPermissions(IGuildChannel channel) => Permissions.ToChannelPerms(channel, GuildPermissions.Webhook.RawValue); /// /// Webhook users cannot be kicked. - Task IGuildUser.KickAsync(string reason, RequestOptions options) => + Task IGuildUser.KickAsync(string reason, RequestOptions options) => throw new NotSupportedException("Webhook users cannot be kicked."); /// /// Webhook users cannot be modified. - Task IGuildUser.ModifyAsync(Action func, RequestOptions options) => + Task IGuildUser.ModifyAsync(Action func, RequestOptions options) => throw new NotSupportedException("Webhook users cannot be modified."); /// /// Roles are not supported on webhook users. - Task IGuildUser.AddRoleAsync(IRole role, RequestOptions options) => + Task IGuildUser.AddRoleAsync(ulong roleId, RequestOptions options) => throw new NotSupportedException("Roles are not supported on webhook users."); /// /// Roles are not supported on webhook users. - Task IGuildUser.AddRolesAsync(IEnumerable roles, RequestOptions options) => + Task IGuildUser.AddRoleAsync(IRole role, RequestOptions options) => throw new NotSupportedException("Roles are not supported on webhook users."); /// /// Roles are not supported on webhook users. - Task IGuildUser.RemoveRoleAsync(IRole role, RequestOptions options) => + Task IGuildUser.AddRolesAsync(IEnumerable roleIds, RequestOptions options) => throw new NotSupportedException("Roles are not supported on webhook users."); /// /// Roles are not supported on webhook users. - Task IGuildUser.RemoveRolesAsync(IEnumerable roles, RequestOptions options) => + Task IGuildUser.AddRolesAsync(IEnumerable roles, RequestOptions options) => + throw new NotSupportedException("Roles are not supported on webhook users."); + + /// + /// Roles are not supported on webhook users. + Task IGuildUser.RemoveRoleAsync(ulong roleId, RequestOptions options) => + throw new NotSupportedException("Roles are not supported on webhook users."); + + /// + /// Roles are not supported on webhook users. + Task IGuildUser.RemoveRoleAsync(IRole role, RequestOptions options) => + throw new NotSupportedException("Roles are not supported on webhook users."); + + /// + /// Roles are not supported on webhook users. + Task IGuildUser.RemoveRolesAsync(IEnumerable roles, RequestOptions options) => + throw new NotSupportedException("Roles are not supported on webhook users."); + + /// + /// Roles are not supported on webhook users. + Task IGuildUser.RemoveRolesAsync(IEnumerable roles, RequestOptions options) => throw new NotSupportedException("Roles are not supported on webhook users."); //IVoiceState diff --git a/src/Discord.Net.Webhook/DiscordWebhookClient.cs b/src/Discord.Net.Webhook/DiscordWebhookClient.cs index a6d4ef183..91d077411 100644 --- a/src/Discord.Net.Webhook/DiscordWebhookClient.cs +++ b/src/Discord.Net.Webhook/DiscordWebhookClient.cs @@ -91,6 +91,35 @@ namespace Discord.Webhook string username = null, string avatarUrl = null, RequestOptions options = null, AllowedMentions allowedMentions = null) => WebhookClientHelper.SendMessageAsync(this, text, isTTS, embeds, username, avatarUrl, allowedMentions, options); + /// + /// Modifies a message posted using this webhook. + /// + /// + /// This method can only modify messages that were sent using the same webhook. + /// + /// ID of the modified message. + /// A delegate containing the properties to modify the message with. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous modification operation. + /// + public Task ModifyMessageAsync(ulong messageId, Action func, RequestOptions options = null) + => WebhookClientHelper.ModifyMessageAsync(this, messageId, func, options); + + /// + /// Deletes a message posted using this webhook. + /// + /// + /// This method can only delete messages that were sent using the same webhook. + /// + /// ID of the deleted message. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous deletion operation. + /// + public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null) + => WebhookClientHelper.DeleteMessageAsync(this, messageId, options); + /// Sends a message to the channel for this webhook with an attachment. /// Returns the ID of the created message. public Task SendFileAsync(string filePath, string text, bool isTTS = false, diff --git a/src/Discord.Net.Webhook/Entities/Messages/WebhookMessageProperties.cs b/src/Discord.Net.Webhook/Entities/Messages/WebhookMessageProperties.cs new file mode 100644 index 000000000..dec7b6e3b --- /dev/null +++ b/src/Discord.Net.Webhook/Entities/Messages/WebhookMessageProperties.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; + +namespace Discord.Webhook +{ + /// + /// Properties that are used to modify an Webhook message with the specified changes. + /// + public class WebhookMessageProperties + { + /// + /// Gets or sets the content of the message. + /// + /// + /// This must be less than the constant defined by . + /// + public Optional Content { get; set; } + /// + /// Gets or sets the embed array that the message should display. + /// + public Optional> Embeds { get; set; } + /// + /// Gets or sets the allowed mentions of the message. + /// + public Optional AllowedMentions { get; set; } + } +} diff --git a/src/Discord.Net.Webhook/Entities/Webhooks/RestInternalWebhook.cs b/src/Discord.Net.Webhook/Entities/Webhooks/RestInternalWebhook.cs index 60cb89ee2..bbb160fcd 100644 --- a/src/Discord.Net.Webhook/Entities/Webhooks/RestInternalWebhook.cs +++ b/src/Discord.Net.Webhook/Entities/Webhooks/RestInternalWebhook.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using System.Threading.Tasks; using Model = Discord.API.Webhook; @@ -11,9 +11,9 @@ namespace Discord.Webhook private DiscordWebhookClient _client; public ulong Id { get; } - public ulong ChannelId { get; } public string Token { get; } + public ulong ChannelId { get; private set; } public string Name { get; private set; } public string AvatarId { get; private set; } public ulong? GuildId { get; private set; } @@ -36,6 +36,8 @@ namespace Discord.Webhook internal void Update(Model model) { + if (ChannelId != model.ChannelId) + ChannelId = model.ChannelId; if (model.Avatar.IsSpecified) AvatarId = model.Avatar.Value; if (model.GuildId.IsSpecified) diff --git a/src/Discord.Net.Webhook/WebhookClientHelper.cs b/src/Discord.Net.Webhook/WebhookClientHelper.cs index 4bc2eaca9..886ff234d 100644 --- a/src/Discord.Net.Webhook/WebhookClientHelper.cs +++ b/src/Discord.Net.Webhook/WebhookClientHelper.cs @@ -36,7 +36,59 @@ namespace Discord.Webhook var model = await client.ApiClient.CreateWebhookMessageAsync(client.Webhook.Id, args, options: options).ConfigureAwait(false); return model.Id; } - public static async Task SendFileAsync(DiscordWebhookClient client, string filePath, string text, bool isTTS, + public static async Task ModifyMessageAsync(DiscordWebhookClient client, ulong messageId, + Action func, RequestOptions options) + { + var args = new WebhookMessageProperties(); + func(args); + + if (args.AllowedMentions.IsSpecified) + { + var allowedMentions = args.AllowedMentions.Value; + Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), + "A max of 100 role Ids are allowed."); + Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), + "A max of 100 user Ids are allowed."); + + // check that user flag and user Id list are exclusive, same with role flag and role Id list + if (allowedMentions?.AllowedTypes != null) + { + if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users) && + allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0) + { + throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", + nameof(allowedMentions)); + } + + if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles) && + allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0) + { + throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", + nameof(allowedMentions)); + } + } + } + + var apiArgs = new ModifyWebhookMessageParams + { + Content = args.Content.IsSpecified ? args.Content.Value : Optional.Create(), + Embeds = + args.Embeds.IsSpecified + ? args.Embeds.Value.Select(embed => embed.ToModel()).ToArray() + : Optional.Create(), + AllowedMentions = args.AllowedMentions.IsSpecified + ? args.AllowedMentions.Value.ToModel() + : Optional.Create() + }; + + await client.ApiClient.ModifyWebhookMessageAsync(client.Webhook.Id, messageId, apiArgs, options) + .ConfigureAwait(false); + } + public static async Task DeleteMessageAsync(DiscordWebhookClient client, ulong messageId, RequestOptions options) + { + await client.ApiClient.DeleteWebhookMessageAsync(client.Webhook.Id, messageId, options).ConfigureAwait(false); + } + public static async Task SendFileAsync(DiscordWebhookClient client, string filePath, string text, bool isTTS, IEnumerable embeds, string username, string avatarUrl, AllowedMentions allowedMentions, RequestOptions options, bool isSpoiler) { string filename = Path.GetFileName(filePath); diff --git a/src/Discord.Net/Discord.Net.nuspec b/src/Discord.Net/Discord.Net.nuspec index 494e3ea43..b0fe17439 100644 --- a/src/Discord.Net/Discord.Net.nuspec +++ b/src/Discord.Net/Discord.Net.nuspec @@ -2,7 +2,7 @@ Discord.Net - 2.3.1-dev$suffix$ + 2.4.0$suffix$ Discord.Net Discord.Net Contributors foxbot @@ -14,25 +14,25 @@ https://github.com/RogueException/Discord.Net/raw/dev/docs/marketing/logo/PackageLogo.png - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + From 0bdede8bc3b21225527b93190bc1784494675f9d Mon Sep 17 00:00:00 2001 From: Quin Lynch <49576606+quinchs@users.noreply.github.com> Date: Wed, 12 May 2021 03:43:42 -0300 Subject: [PATCH 39/64] Update README.md --- README.md | 27 ++++----------------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 34a633f72..dffbdb32c 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,5 @@ -# Discord.Net -[![NuGet](https://img.shields.io/nuget/vpre/Discord.Net.svg?maxAge=2592000?style=plastic)](https://www.nuget.org/packages/Discord.Net) -[![MyGet](https://img.shields.io/myget/discord-net/vpre/Discord.Net.svg)](https://www.myget.org/feed/Packages/discord-net) -[![Build Status](https://dev.azure.com/discord-net/Discord.Net/_apis/build/status/discord-net.Discord.Net?branchName=dev)](https://dev.azure.com/discord-net/Discord.Net/_build/latest?definitionId=1&branchName=dev) -[![Discord](https://discord.com/api/guilds/81384788765712384/widget.png)](https://discord.gg/jkrBmQR) - -An unofficial .NET API Wrapper for the Discord client (https://discord.com). +# Discord.Net Labs +The development version of [Discord.Net](https://github.com/discord-net/Discord.Net). This version of the README is a placeholder for now. ## Documentation @@ -13,22 +8,8 @@ An unofficial .NET API Wrapper for the Discord client (https://discord.com). - [Nightly](https://docs.stillu.cc/) - [Latest CI repo](https://github.com/discord-net/docs-static) -## Installation -### Stable (NuGet) -Our stable builds available from NuGet through the Discord.Net metapackage: -- [Discord.Net](https://www.nuget.org/packages/Discord.Net/) - -The individual components may also be installed from NuGet: -- [Discord.Net.Commands](https://www.nuget.org/packages/Discord.Net.Commands/) -- [Discord.Net.Rest](https://www.nuget.org/packages/Discord.Net.Rest/) -- [Discord.Net.WebSocket](https://www.nuget.org/packages/Discord.Net.WebSocket/) -- [Discord.Net.Webhook](https://www.nuget.org/packages/Discord.Net.Webhook/) - -### Unstable (MyGet) -Nightly builds are available through our MyGet feed (`https://www.myget.org/F/discord-net/api/v3/index.json`). - ## Compiling -In order to compile Discord.Net, you require the following: +In order to compile Discord.Net Labs, you require the following: ### Using Visual Studio - [Visual Studio 2017](https://www.microsoft.com/net/core#windowsvs2017) @@ -50,7 +31,7 @@ This library generally abides by [Semantic Versioning](https://semver.org). Pack An increment of the PATCH component always indicates that an internal-only change was made, generally a bugfix. These changes will not affect the public-facing API in any way, and are always guaranteed to be forward- and backwards-compatible with your codebase, any pre-compiled dependencies of your codebase. -An increment of the MINOR component indicates that some addition was made to the library, and this addition is not backwards-compatible with prior versions. However, Discord.Net **does not guarantee forward-compatibility** on minor additions. In other words, we permit a limited set of breaking changes on a minor version bump. +An increment of the MINOR component indicates that some addition was made to the library, and this addition is not backwards-compatible with prior versions. However, Discord.Net Labs **does not guarantee forward-compatibility** on minor additions. In other words, we permit a limited set of breaking changes on a minor version bump. Due to the nature of the Discord API, we will oftentimes need to add a property to an entity to support the latest API changes. Discord.Net provides interfaces as a method of consuming entities; and as such, introducing a new field to an entity is technically a breaking change. Major version bumps generally indicate some major change to the library, and as such we are hesitant to bump the major version for every minor addition to the library. To compromise, we have decided that interfaces should be treated as **consumable only**, and your applications should typically not be implementing interfaces. (For applications where interfaces are implemented, such as in test mocks, we apologize for this inconsistency with SemVer). From 4b8d4441c5ca2afe1d40aa0406025b3f7c5127ea Mon Sep 17 00:00:00 2001 From: Max <73736326+Max2408@users.noreply.github.com> Date: Sat, 22 May 2021 23:00:08 +0530 Subject: [PATCH 40/64] feature: Add Discord Certified Moderator user flag (#1844) This pull request adds the Discord Certified Moderator badge flag to the User Flags PR in discord-api-docs: https://github.com/discord/discord-api-docs/pull/2946 --- src/Discord.Net.Core/Entities/Users/UserProperties.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Discord.Net.Core/Entities/Users/UserProperties.cs b/src/Discord.Net.Core/Entities/Users/UserProperties.cs index b6deb744b..21c453dce 100644 --- a/src/Discord.Net.Core/Entities/Users/UserProperties.cs +++ b/src/Discord.Net.Core/Entities/Users/UserProperties.cs @@ -67,5 +67,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, } } From 2ad0f0c2fe4d685442c40e62139c87a3256facdb Mon Sep 17 00:00:00 2001 From: Paulo Date: Sat, 22 May 2021 15:12:57 -0300 Subject: [PATCH 41/64] meta: 2.4.0 (#1845) --- CHANGELOG.md | 19 +++++++++++++++++++ Discord.Net.targets | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5988f90c3..21e37b295 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # 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) diff --git a/Discord.Net.targets b/Discord.Net.targets index 7a48cf2a7..d08d55535 100644 --- a/Discord.Net.targets +++ b/Discord.Net.targets @@ -1,7 +1,7 @@ 2.4.0 - dev + latest Discord.Net Contributors discord;discordapp From 690bc3ff9d808da405b90ea13e0960f4cbc7eb1e Mon Sep 17 00:00:00 2001 From: Paulo Date: Sun, 23 May 2021 13:36:39 -0300 Subject: [PATCH 42/64] meta: Bump version to 3.0.0-dev (#1846) --- Discord.Net.targets | 4 ++-- src/Discord.Net/Discord.Net.nuspec | 32 +++++++++++++++--------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Discord.Net.targets b/Discord.Net.targets index d08d55535..febd921d1 100644 --- a/Discord.Net.targets +++ b/Discord.Net.targets @@ -1,7 +1,7 @@ - 2.4.0 - + 3.0.0 + dev latest Discord.Net Contributors discord;discordapp 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 - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + From 06a64b78771cca5798051e13141aed2246ccce40 Mon Sep 17 00:00:00 2001 From: Paulo Date: Mon, 24 May 2021 11:39:20 -0300 Subject: [PATCH 43/64] feature: Bump API version to 9 (#1847) * First changes to the config * Lots of changes to fit the new version * Remove PermissionTargetConverter --- src/Discord.Net.Core/DiscordConfig.cs | 16 +----- .../Entities/Guilds/IGuild.cs | 36 -------------- .../Entities/Guilds/PermissionTarget.cs | 4 +- .../Entities/Messages/IUserMessage.cs | 12 ----- .../Entities/Messages/MessageType.cs | 3 -- .../Entities/Permissions/GuildPermissions.cs | 3 ++ .../Permissions/OverwritePermissions.cs | 7 +++ .../Entities/Users/IPresence.cs | 4 -- src/Discord.Net.Core/GatewayIntents.cs | 11 +++++ src/Discord.Net.Core/RateLimitPrecision.cs | 18 ------- src/Discord.Net.Rest/API/Common/Guild.cs | 4 -- src/Discord.Net.Rest/API/Common/GuildEmbed.cs | 13 ----- src/Discord.Net.Rest/API/Common/Overwrite.cs | 4 +- src/Discord.Net.Rest/API/Common/Presence.cs | 2 - src/Discord.Net.Rest/API/Common/Role.cs | 2 +- src/Discord.Net.Rest/API/Common/UserGuild.cs | 4 +- .../Rest/ModifyChannelPermissionsParams.cs | 10 ++-- .../API/Rest/ModifyGuildRoleParams.cs | 4 +- .../API/Rest/SuppressEmbedParams.cs | 11 ----- src/Discord.Net.Rest/ClientHelper.cs | 8 --- src/Discord.Net.Rest/DiscordRestApiClient.cs | 43 +--------------- src/Discord.Net.Rest/DiscordRestClient.cs | 8 +-- .../Entities/Channels/ChannelHelper.cs | 20 ++++---- .../Entities/Guilds/GuildHelper.cs | 35 +++---------- .../Entities/Guilds/RestGuild.cs | 49 ------------------- .../Entities/Guilds/RestGuildEmbed.cs | 25 ---------- .../Entities/Messages/MessageHelper.cs | 9 ---- .../Entities/Messages/RestUserMessage.cs | 3 -- .../Entities/Roles/RoleHelper.cs | 4 +- .../Net/Converters/DiscordContractResolver.cs | 2 - .../Converters/PermissionTargetConverter.cs | 44 ----------------- .../API/Gateway/IdentifyParams.cs | 2 - src/Discord.Net.WebSocket/BaseSocketClient.cs | 1 - .../DiscordShardedClient.cs | 3 +- .../DiscordSocketApiClient.cs | 10 ++-- .../DiscordSocketClient.cs | 21 ++++---- .../DiscordSocketConfig.cs | 11 ++--- .../Entities/Guilds/SocketGuild.cs | 33 ------------- .../Entities/Messages/SocketUserMessage.cs | 3 -- .../Entities/Users/SocketPresence.cs | 10 ++-- .../Entities/Users/SocketUnknownUser.cs | 2 +- .../Entities/Users/SocketUser.cs | 2 - .../Entities/Users/SocketWebhookUser.cs | 2 +- 43 files changed, 82 insertions(+), 436 deletions(-) delete mode 100644 src/Discord.Net.Core/RateLimitPrecision.cs delete mode 100644 src/Discord.Net.Rest/API/Common/GuildEmbed.cs delete mode 100644 src/Discord.Net.Rest/API/Rest/SuppressEmbedParams.cs delete mode 100644 src/Discord.Net.Rest/Entities/Guilds/RestGuildEmbed.cs delete mode 100644 src/Discord.Net.Rest/Net/Converters/PermissionTargetConverter.cs 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/IGuild.cs b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs index 36d735157..0bbd64786 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. /// /// @@ -147,14 +140,6 @@ namespace Discord /// 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 +349,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 +567,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/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/GuildPermissions.cs b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs index ba6757fc6..ef4e40a11 100644 --- a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs @@ -90,6 +90,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..6ee79b427 100644 --- a/src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs @@ -93,6 +93,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/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.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/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..002472e5a 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,7 +73,6 @@ 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) @@ -645,16 +642,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 +923,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 +936,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..331c6f615 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(), }; @@ -320,7 +299,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..980b0c5fb 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; } @@ -133,16 +128,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 +191,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 +227,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 +440,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 +897,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/Messages/MessageHelper.cs b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs index 1bc284836..31252466b 100644 --- a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs +++ b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs @@ -122,15 +122,6 @@ 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) - { - var apiArgs = new API.Rest.SuppressEmbedParams - { - Suppressed = suppress - }; - await client.ApiClient.SuppressEmbedAsync(msg.Channel.Id, msg.Id, apiArgs, options).ConfigureAwait(false); - } - public static async Task AddReactionAsync(ulong channelId, ulong messageId, IEmote emote, BaseDiscordClient client, RequestOptions options) { await client.ApiClient.AddReactionAsync(channelId, messageId, emote is Emote e ? $"{e.Name}:{e.Id}" : UrlEncode(emote.Name), options).ConfigureAwait(false); 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/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/BaseSocketClient.cs b/src/Discord.Net.WebSocket/BaseSocketClient.cs index 36e6c02a9..1cfcaa1ee 100644 --- a/src/Discord.Net.WebSocket/BaseSocketClient.cs +++ b/src/Discord.Net.WebSocket/BaseSocketClient.cs @@ -83,7 +83,6 @@ namespace Discord.WebSocket : 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); /// diff --git a/src/Discord.Net.WebSocket/DiscordShardedClient.cs b/src/Discord.Net.WebSocket/DiscordShardedClient.cs index 4c94b14e8..c406ead62 100644 --- a/src/Discord.Net.WebSocket/DiscordShardedClient.cs +++ b/src/Discord.Net.WebSocket/DiscordShardedClient.cs @@ -91,8 +91,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) { 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..b8eba1fc4 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. @@ -140,7 +139,6 @@ namespace Discord.WebSocket 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 +180,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) { @@ -243,7 +240,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 @@ -335,7 +332,7 @@ 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; }); } @@ -469,7 +466,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 +562,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 +570,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 +593,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++) diff --git a/src/Discord.Net.WebSocket/DiscordSocketConfig.cs b/src/Discord.Net.WebSocket/DiscordSocketConfig.cs index a45d4f5be..90b746787 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketConfig.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketConfig.cs @@ -127,12 +127,6 @@ namespace Discord.WebSocket /// 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 +166,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/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index 0e36c6b50..ed8a6c532 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. /// /// @@ -440,16 +422,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; @@ -548,11 +526,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); /// @@ -1234,8 +1207,6 @@ namespace Discord.WebSocket /// ulong IGuild.DefaultChannelId => DefaultChannel?.Id ?? 0; /// - ulong? IGuild.EmbedChannelId => EmbedChannelId; - /// ulong? IGuild.WidgetChannelId => WidgetChannelId; /// ulong? IGuild.SystemChannelId => SystemChannelId; @@ -1290,10 +1261,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/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/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..979314be3 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; 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(); From 76a878a3e1eebac02c5aaea474f0fe4e8ca0c572 Mon Sep 17 00:00:00 2001 From: Tsathoggua of Nkai <5774045+tsathoqqua@users.noreply.github.com> Date: Sat, 20 Feb 2021 18:30:06 +0100 Subject: [PATCH 44/64] Update EmbedBuilder.Overwrites.md (#20) Missing "," in the EmbedBuilder code. Do I get the pedantic unlocked now? --- docs/_overwrites/Common/EmbedBuilder.Overwrites.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 75b74e1a3f8e5f3078b254c135befac9af8b7c07 Mon Sep 17 00:00:00 2001 From: Joe4evr Date: Mon, 15 Mar 2021 02:48:59 +0100 Subject: [PATCH 45/64] Fix line about PriorityAttribute (#21) Sorting with the highest number first is *de*scending order. --- docs/guides/commands/intro.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 95bae786b8729de2d15fdde5f24b984f68377875 Mon Sep 17 00:00:00 2001 From: Paulo Date: Mon, 24 May 2021 17:19:11 -0300 Subject: [PATCH 46/64] fix: Create DM channel with id and author alone (#1850) * Create DM channel with id and author alone * Unneeded cast --- .../DiscordSocketClient.cs | 83 ++++++++++--------- .../Entities/Channels/SocketDMChannel.cs | 10 +++ 2 files changed, 55 insertions(+), 38 deletions(-) diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index b8eba1fc4..c6e38c1a9 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -1236,56 +1236,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 = State.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; + } - SocketUser author; - 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); - } + 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 } - - var msg = SocketMessage.Create(this, State, author, channel, data); - SocketChannelHelper.AddMessage(channel, this, msg); - await TimedInvokeAsync(_messageReceivedEvent, nameof(MessageReceived), msg).ConfigureAwait(false); + else if (channel is SocketGroupChannel groupChannel) + author = groupChannel.GetOrAddUser(data.Author.Value); + else + author = State.GetOrAddUser(data.Author.Value.Id, x => SocketGlobalUser.Create(this, State, data.Author.Value)); } - else + + if (channel == null) { - await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); - return; + if (!data.GuildId.IsSpecified) // assume it is a DM + { + var dm = SocketDMChannel.Create(this, State, data.ChannelId, data.Author.Value); + channel = dm; + State.AddChannel(dm); + dm.Recipient.GlobalUser.DMChannel = dm; + } + 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": diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs index 5de417036..7f07d10d0 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs @@ -49,6 +49,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.GetOrCreateUser(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) From 7a201e9ff1f580063670dd904a43ca0676a17a1c Mon Sep 17 00:00:00 2001 From: Paulo Date: Wed, 26 May 2021 17:35:49 -0300 Subject: [PATCH 47/64] feature: Remove DM cache and fix references (#1851) * Remove DM cache and fix references * Move line back to where it was --- src/Discord.Net.Core/Entities/Users/IUser.cs | 6 +- .../Extensions/UserExtensions.cs | 6 +- .../Core/Entities/Users/IUser.Examples.cs | 4 +- .../Entities/Users/RestUser.cs | 8 +-- .../DiscordSocketClient.cs | 60 ++++++++++--------- .../Entities/Channels/SocketDMChannel.cs | 43 ++++++------- .../Entities/Users/SocketGlobalUser.cs | 2 - .../Entities/Users/SocketGroupUser.cs | 2 +- .../Entities/Users/SocketGuildUser.cs | 6 +- .../Entities/Users/SocketUser.cs | 4 +- 10 files changed, 68 insertions(+), 73 deletions(-) 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/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.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.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.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index c6e38c1a9..7a1aaa6d6 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -305,7 +305,7 @@ namespace Discord.WebSocket /// /// Clears cached DM channels from the client. /// - public void PurgeDMChannelCache() => State.PurgeDMChannels(); + public void PurgeDMChannelCache() => RemoveDMChannels(); /// public override SocketUser GetUser(ulong id) @@ -317,14 +317,11 @@ namespace Discord.WebSocket /// Clears cached users from the client. /// public void PurgeUserCache() => State.PurgeUsers(); - internal SocketGlobalUser GetOrCreateUser(ClientState state, Discord.API.User model) + internal SocketGlobalUser GetOrCreateUser(ClientState state, Discord.API.User model, bool cache) { - return state.GetOrAddUser(model.Id, x => - { - var user = SocketGlobalUser.Create(this, state, model); - user.GlobalUser.AddRef(); - return user; - }); + if (cache) + return state.GetOrAddUser(model.Id, x => SocketGlobalUser.Create(this, state, model)); + return state.GetUser(model.Id) ?? SocketGlobalUser.Create(this, state, model); } internal SocketGlobalUser GetOrCreateSelfUser(ClientState state, Discord.API.User model) { @@ -1245,6 +1242,19 @@ namespace Discord.WebSocket return; } + if (channel == null) + { + if (!data.GuildId.IsSpecified) // assume it is a DM + { + channel = CreateDMChannel(data.ChannelId, data.Author.Value, State); + } + else + { + await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); + return; + } + } + SocketUser author; if (guild != null) { @@ -1254,7 +1264,7 @@ namespace Discord.WebSocket author = guild.GetUser(data.Author.Value.Id); } else - author = (channel as SocketChannel)?.GetUser(data.Author.Value.Id); + author = (channel as SocketChannel).GetUser(data.Author.Value.Id); if (author == null) { @@ -1270,22 +1280,9 @@ namespace Discord.WebSocket } else if (channel is SocketGroupChannel groupChannel) author = groupChannel.GetOrAddUser(data.Author.Value); - else - author = State.GetOrAddUser(data.Author.Value.Id, x => SocketGlobalUser.Create(this, State, data.Author.Value)); - } - - if (channel == null) - { - if (!data.GuildId.IsSpecified) // assume it is a DM - { - var dm = SocketDMChannel.Create(this, State, data.ChannelId, data.Author.Value); - channel = dm; - State.AddChannel(dm); - dm.Recipient.GlobalUser.DMChannel = dm; - } else { - await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); + await UnknownChannelUserAsync(type, data.Author.Value.Id, channel.Id).ConfigureAwait(false); return; } } @@ -1944,24 +1941,29 @@ 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 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) { diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs index 7f07d10d0..a42db3b5f 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs @@ -16,15 +16,13 @@ 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. @@ -35,13 +33,10 @@ namespace Discord.WebSocket : 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.GetOrCreateUser(state, model.Recipients.Value[0], false)); entity.Update(state, model); return entity; } @@ -51,7 +46,7 @@ namespace Discord.WebSocket } internal static SocketDMChannel Create(DiscordSocketClient discord, ClientState state, ulong channelId, API.User recipient) { - var entity = new SocketDMChannel(discord, channelId, discord.GetOrCreateUser(state, recipient)); + var entity = new SocketDMChannel(discord, channelId, discord.GetOrCreateUser(state, recipient, false)); entity.Update(state, recipient); return entity; } @@ -67,7 +62,7 @@ namespace Discord.WebSocket //Messages /// public SocketMessage GetCachedMessage(ulong id) - => _messages?.Get(id); + => null; /// /// Gets the message associated with the given . /// @@ -78,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); } /// @@ -97,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. /// @@ -113,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. /// @@ -129,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); @@ -174,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 /// @@ -232,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/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/SocketGroupUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs index 676c0a86c..9f66bed12 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs @@ -38,7 +38,7 @@ namespace Discord.WebSocket } internal static SocketGroupUser Create(SocketGroupChannel channel, ClientState state, Model model) { - var entity = new SocketGroupUser(channel, channel.Discord.GetOrCreateUser(state, model)); + var entity = new SocketGroupUser(channel, channel.Discord.GetOrCreateUser(state, model, true)); entity.Update(state, model); return entity; } diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs index 9263fe642..6362d4891 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs @@ -116,20 +116,20 @@ namespace Discord.WebSocket } internal static SocketGuildUser Create(SocketGuild guild, ClientState state, UserModel model) { - var entity = new SocketGuildUser(guild, guild.Discord.GetOrCreateUser(state, model)); + var entity = new SocketGuildUser(guild, guild.Discord.GetOrCreateUser(state, model, true)); entity.Update(state, model); entity.UpdateRoles(new ulong[0]); return entity; } internal static SocketGuildUser Create(SocketGuild guild, ClientState state, MemberModel model) { - var entity = new SocketGuildUser(guild, guild.Discord.GetOrCreateUser(state, model.User)); + var entity = new SocketGuildUser(guild, guild.Discord.GetOrCreateUser(state, model.User, true)); entity.Update(state, model); return entity; } internal static SocketGuildUser Create(SocketGuild guild, ClientState state, PresenceModel model) { - var entity = new SocketGuildUser(guild, guild.Discord.GetOrCreateUser(state, model.User)); + var entity = new SocketGuildUser(guild, guild.Discord.GetOrCreateUser(state, model.User, true)); entity.Update(state, model, false); return entity; } diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs index 979314be3..025daf29a 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs @@ -92,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) From ed8e57320a4a6d082aaabcc3b15fa07199c52316 Mon Sep 17 00:00:00 2001 From: Paulo Date: Wed, 26 May 2021 17:59:14 -0300 Subject: [PATCH 48/64] feature: Remove obsolete sync voice regions methods and properties (#1848) --- src/Discord.Net.WebSocket/BaseSocketClient.cs | 30 +++++-------------- .../DiscordShardedClient.cs | 20 +++++-------- .../DiscordSocketClient.cs | 8 ----- 3 files changed, 16 insertions(+), 42 deletions(-) diff --git a/src/Discord.Net.WebSocket/BaseSocketClient.cs b/src/Discord.Net.WebSocket/BaseSocketClient.cs index 1cfcaa1ee..b1b430178 100644 --- a/src/Discord.Net.WebSocket/BaseSocketClient.cs +++ b/src/Discord.Net.WebSocket/BaseSocketClient.cs @@ -70,14 +70,6 @@ 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; @@ -163,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. @@ -326,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 c406ead62..ea50a571e 100644 --- a/src/Discord.Net.WebSocket/DiscordShardedClient.cs +++ b/src/Discord.Net.WebSocket/DiscordShardedClient.cs @@ -36,9 +36,6 @@ 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. @@ -263,11 +260,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) { @@ -431,11 +423,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/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 7a1aaa6d6..d4ca6329d 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -108,9 +108,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. @@ -336,11 +333,6 @@ namespace Discord.WebSocket 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) { From 70aab6c4f651fe53d6b24d334fef7b30ff5a8105 Mon Sep 17 00:00:00 2001 From: Paulo Date: Wed, 26 May 2021 18:42:35 -0300 Subject: [PATCH 49/64] fix: Remove obsolete methods and properties (#1849) * Remove obsolete methods and properties * Remove rest of GuildEmbed * Remove TokenType.User * Changes regarding the removal of the user tokentype --- .../Entities/Guilds/GuildEmbedProperties.cs | 21 ------------------- .../Entities/Guilds/IGuild.cs | 15 ------------- .../Entities/Invites/IInviteMetadata.cs | 8 ------- .../Entities/Permissions/ChannelPermission.cs | 5 ----- .../Permissions/ChannelPermissions.cs | 3 --- .../Entities/Permissions/GuildPermission.cs | 2 -- .../Entities/Permissions/GuildPermissions.cs | 3 --- .../Permissions/OverwritePermissions.cs | 3 --- .../Entities/Users/UserProperties.cs | 6 ------ src/Discord.Net.Core/TokenType.cs | 2 -- src/Discord.Net.Rest/DiscordRestApiClient.cs | 1 - .../Entities/Guilds/RestGuild.cs | 2 -- .../Entities/Invites/RestInviteMetadata.cs | 3 --- .../Entities/Guilds/SocketGuild.cs | 2 -- .../Entities/Invites/SocketInvite.cs | 3 --- .../Discord.Net.Tests.Unit/TokenUtilsTests.cs | 2 -- 16 files changed, 81 deletions(-) delete mode 100644 src/Discord.Net.Core/Entities/Guilds/GuildEmbedProperties.cs 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 0bbd64786..b8fd858df 100644 --- a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs +++ b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs @@ -125,21 +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 channel assigned to the widget of this guild. /// /// 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/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 ef4e40a11..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. diff --git a/src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs b/src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs index 6ee79b427..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); diff --git a/src/Discord.Net.Core/Entities/Users/UserProperties.cs b/src/Discord.Net.Core/Entities/Users/UserProperties.cs index 21c453dce..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, 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.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index 002472e5a..d7978db5c 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -79,7 +79,6 @@ namespace Discord.API { 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)), diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs index 980b0c5fb..ea703a26a 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs @@ -90,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); /// 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.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index ed8a6c532..59f1c5956 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -1205,8 +1205,6 @@ namespace Discord.WebSocket /// bool IGuild.Available => true; /// - ulong IGuild.DefaultChannelId => DefaultChannel?.Id ?? 0; - /// ulong? IGuild.WidgetChannelId => WidgetChannelId; /// ulong? IGuild.SystemChannelId => SystemChannelId; 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/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)] From 8ed8714375d61af1696fccca1a34bb2645a2e283 Mon Sep 17 00:00:00 2001 From: Evan Raffel Date: Wed, 26 May 2021 17:46:14 -0400 Subject: [PATCH 50/64] fix: Grab correct Uses value for vanity urls (#1832) --- src/Discord.Net.Rest/API/Common/InviteVanity.cs | 2 ++ src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs | 1 + 2 files changed, 3 insertions(+) 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/Entities/Guilds/GuildHelper.cs b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs index 331c6f615..58a4ea2c8 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs @@ -283,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); } From dfaaa21e0ec57db5f385f62df9e654410c26f9e7 Mon Sep 17 00:00:00 2001 From: Paulo Date: Wed, 26 May 2021 20:08:15 -0300 Subject: [PATCH 51/64] misc: Internal change to GetOrCreateUser (#1852) --- src/Discord.Net.WebSocket/DiscordSocketClient.cs | 10 ++++++---- .../Entities/Channels/SocketDMChannel.cs | 6 +++--- .../Entities/Users/SocketGroupUser.cs | 2 +- .../Entities/Users/SocketGuildUser.cs | 6 +++--- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index d4ca6329d..025608a30 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -314,11 +314,13 @@ namespace Discord.WebSocket /// Clears cached users from the client. /// public void PurgeUserCache() => State.PurgeUsers(); - internal SocketGlobalUser GetOrCreateUser(ClientState state, Discord.API.User model, bool cache) + internal SocketGlobalUser GetOrCreateUser(ClientState state, Discord.API.User model) { - if (cache) - return state.GetOrAddUser(model.Id, x => SocketGlobalUser.Create(this, state, model)); - return state.GetUser(model.Id) ?? SocketGlobalUser.Create(this, state, model); + 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) { diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs index a42db3b5f..459707dc7 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs @@ -29,14 +29,14 @@ namespace Discord.WebSocket /// 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; } internal static SocketDMChannel Create(DiscordSocketClient discord, ClientState state, Model model) { - var entity = new SocketDMChannel(discord, model.Id, discord.GetOrCreateUser(state, model.Recipients.Value[0], false)); + var entity = new SocketDMChannel(discord, model.Id, discord.GetOrCreateTemporaryUser(state, model.Recipients.Value[0])); entity.Update(state, model); return entity; } @@ -46,7 +46,7 @@ namespace Discord.WebSocket } internal static SocketDMChannel Create(DiscordSocketClient discord, ClientState state, ulong channelId, API.User recipient) { - var entity = new SocketDMChannel(discord, channelId, discord.GetOrCreateUser(state, recipient, false)); + var entity = new SocketDMChannel(discord, channelId, discord.GetOrCreateTemporaryUser(state, recipient)); entity.Update(state, recipient); return entity; } diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs index 9f66bed12..676c0a86c 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs @@ -38,7 +38,7 @@ namespace Discord.WebSocket } internal static SocketGroupUser Create(SocketGroupChannel channel, ClientState state, Model model) { - var entity = new SocketGroupUser(channel, channel.Discord.GetOrCreateUser(state, model, true)); + var entity = new SocketGroupUser(channel, channel.Discord.GetOrCreateUser(state, model)); entity.Update(state, model); return entity; } diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs index 6362d4891..9263fe642 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs @@ -116,20 +116,20 @@ namespace Discord.WebSocket } internal static SocketGuildUser Create(SocketGuild guild, ClientState state, UserModel model) { - var entity = new SocketGuildUser(guild, guild.Discord.GetOrCreateUser(state, model, true)); + var entity = new SocketGuildUser(guild, guild.Discord.GetOrCreateUser(state, model)); entity.Update(state, model); entity.UpdateRoles(new ulong[0]); return entity; } internal static SocketGuildUser Create(SocketGuild guild, ClientState state, MemberModel model) { - var entity = new SocketGuildUser(guild, guild.Discord.GetOrCreateUser(state, model.User, true)); + var entity = new SocketGuildUser(guild, guild.Discord.GetOrCreateUser(state, model.User)); entity.Update(state, model); return entity; } internal static SocketGuildUser Create(SocketGuild guild, ClientState state, PresenceModel model) { - var entity = new SocketGuildUser(guild, guild.Discord.GetOrCreateUser(state, model.User, true)); + var entity = new SocketGuildUser(guild, guild.Discord.GetOrCreateUser(state, model.User)); entity.Update(state, model, false); return entity; } From 2e9c6df7b5c0e00f82092b208816f4c3e6849030 Mon Sep 17 00:00:00 2001 From: Quin Lynch <49576606+quinchs@users.noreply.github.com> Date: Thu, 27 May 2021 08:03:11 -0300 Subject: [PATCH 52/64] Update README.md --- README.md | 86 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 47 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index dffbdb32c..6452d54b6 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,48 @@ # Discord.Net Labs -The development version of [Discord.Net](https://github.com/discord-net/Discord.Net). This version of the README is a placeholder for now. - -## Documentation - -- [Stable](https://discord.foxbot.me/) - - Hosted by @foxbot -- [Nightly](https://docs.stillu.cc/) - - [Latest CI repo](https://github.com/discord-net/docs-static) - -## Compiling -In order to compile Discord.Net Labs, you require the following: - -### Using Visual Studio -- [Visual Studio 2017](https://www.microsoft.com/net/core#windowsvs2017) -- [.NET Core SDK](https://www.microsoft.com/net/download/core) - -The .NET Core workload must be selected during Visual Studio installation. - -### Using Command Line -- [.NET Core SDK](https://www.microsoft.com/net/download/core) - -## Known Issues - -### WebSockets (Win7 and earlier) -.NET Core 1.1 does not support WebSockets on Win7 and earlier. This issue has been fixed since the release of .NET Core 2.1. It is recommended to target .NET Core 2.1 or above for your project if you wish to run your bot on legacy platforms; alternatively, you may choose to install the [Discord.Net.Providers.WS4Net](https://www.nuget.org/packages/Discord.Net.Providers.WS4Net/) package. - -## Versioning Guarantees - -This library generally abides by [Semantic Versioning](https://semver.org). Packages are published in MAJOR.MINOR.PATCH version format. - -An increment of the PATCH component always indicates that an internal-only change was made, generally a bugfix. These changes will not affect the public-facing API in any way, and are always guaranteed to be forward- and backwards-compatible with your codebase, any pre-compiled dependencies of your codebase. - -An increment of the MINOR component indicates that some addition was made to the library, and this addition is not backwards-compatible with prior versions. However, Discord.Net Labs **does not guarantee forward-compatibility** on minor additions. In other words, we permit a limited set of breaking changes on a minor version bump. - -Due to the nature of the Discord API, we will oftentimes need to add a property to an entity to support the latest API changes. Discord.Net provides interfaces as a method of consuming entities; and as such, introducing a new field to an entity is technically a breaking change. Major version bumps generally indicate some major change to the library, and as such we are hesitant to bump the major version for every minor addition to the library. To compromise, we have decided that interfaces should be treated as **consumable only**, and your applications should typically not be implementing interfaces. (For applications where interfaces are implemented, such as in test mocks, we apologize for this inconsistency with SemVer). - -Furthermore, while we will never break the API (outside of interface changes) on minor builds, we will occasionally need to break the ABI, by introducing parameters to a method to match changes upstream with Discord. As such, a minor version increment may require you to recompile your code, and dependencies, such as addons, may also need to be recompiled and republished on the newer version. When a binary breaking change is made, the change will be noted in the release notes. - -An increment of the MAJOR component indicates that breaking changes have been made to the library; consumers should check the release notes to determine what changes need to be made. +This repo is a custom fork of Discord.Net that introduces the newest features of discord for testing and experimenting. Nothing here is guaranteed to work but you are more than welcome to submit bugs in the issues tabs + +## Branches +### Dev +The main branch we pull off of to introduce new features into, the dev branch is the same as Discord.Nets dev branch + +### Interactions +This branch is for anything todo with Discord Interactions, such as [Slash commands](https://discord.com/developers/docs/interactions/slash-commands) and [Message Components](https://discord.com/developers/docs/interactions/message-components). This branch is stable enough to use but does not contain all the features of interactions. + +### SlashCommandService +This branch is on pause and does not work currently, Once everything is stable with the Interaction branch we will continue working on a slash command service for it. + +### web/SlashCommandService +webmilio's spin on the SlashCommandService branch, again the state of this is unknown. + + +## Message Components +So, you want to use Message components? Well you're in luck! Below is a quick overview of how to use them + +#### Listening for button presses +```cs +// Subscribe to the InteractionCreated event +client.InteractionCreated += Client_InteractionCreated; + +... +private async Task Client_InteractionCreated(SocketInteraction arg) +{ + // If the type of the interaction is a message component + if(arg.Type == Discord.InteractionType.MessageComponent) + { + // parse the args + var parsedArg = (SocketMessageComponent)arg; + // respond with the update message response type. This edits the original message if you have set AlwaysAcknowledgeInteractions to false. + await parsedArg.RespondAsync($"Clicked {parsedArg.Data.CustomId}!", type: InteractionResponseType.UpdateMessage); + } +} +``` + +#### Sending messages with buttons +Theres a new field in all `SendMessageAsync` functions that takes in a `MessageComponent`, you can use it like so: +```cs +var builder = new ComponentBuilder().WithButton("Hello!", ButtonStyle.Primary, customId: "id_1"); +await Context.Channel.SendMessageAsync("Test buttons!", component: builder.Build()); +``` + +## Slash commands +Slash command example how to's can be found [here](https://github.com/Discord-Net-Labs/Discord.Net-Labs/blob/Interactions/docs/guides/commands/application-commands.md). If you want to read some code using slash commands, you can do that [here](https://github.com/quinchs/SwissbotCore/blob/master/SwissbotCore/Handlers/AutoMod/Censor.cs) From 79a12c4e4e34a28df945f129f697ee090acb9afc Mon Sep 17 00:00:00 2001 From: Quin Lynch <49576606+quinchs@users.noreply.github.com> Date: Thu, 27 May 2021 10:56:56 -0300 Subject: [PATCH 53/64] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 6452d54b6..ecc811342 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ # Discord.Net Labs +[![NuGet](https://img.shields.io/nuget/vpre/Discord.Net.Labs.Core.svg?maxAge=2592000?style=plastic)](https://www.nuget.org/packages/Discord.Net.Labs.Core/ "Core") + This repo is a custom fork of Discord.Net that introduces the newest features of discord for testing and experimenting. Nothing here is guaranteed to work but you are more than welcome to submit bugs in the issues tabs ## Branches From 783a2d05fff680a560bee2006e0508c44be1854f Mon Sep 17 00:00:00 2001 From: Quin Lynch <49576606+quinchs@users.noreply.github.com> Date: Thu, 27 May 2021 11:26:21 -0300 Subject: [PATCH 54/64] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ecc811342..8e2b5fe02 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Discord.Net Labs -[![NuGet](https://img.shields.io/nuget/vpre/Discord.Net.Labs.Core.svg?maxAge=2592000?style=plastic)](https://www.nuget.org/packages/Discord.Net.Labs.Core/ "Core") +[![NuGet](https://img.shields.io/nuget/vpre/Discord.Net.Labs.svg?maxAge=2592000?style=plastic)](https://www.nuget.org/packages/Discord.Net.Labs) This repo is a custom fork of Discord.Net that introduces the newest features of discord for testing and experimenting. Nothing here is guaranteed to work but you are more than welcome to submit bugs in the issues tabs From a7ff6ce0ec81092b201fcbdd4d93c158eada7c7a Mon Sep 17 00:00:00 2001 From: Paulo Date: Fri, 28 May 2021 10:09:05 -0300 Subject: [PATCH 55/64] fix: Gateway events for DMs (#1854) * Fix MessageUpdate when there's no channel cached * Fix message events * Fix cacheable type * Fix examples * Revert MessageUpdated --- .../BaseSocketClient.Events.Examples.cs | 9 +- .../API/Gateway/Reaction.cs | 2 + .../BaseSocketClient.Events.cs | 28 +- .../DiscordSocketClient.cs | 405 ++++++++++-------- .../DiscordSocketConfig.cs | 16 - 5 files changed, 248 insertions(+), 212 deletions(-) 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.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..e15e6a687 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. @@ -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/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 025608a30..0e854421e 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -71,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; /// @@ -132,7 +131,6 @@ 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(); @@ -296,6 +294,44 @@ 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(); @@ -1227,7 +1263,7 @@ namespace Discord.WebSocket await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_CREATE)").ConfigureAwait(false); var data = (payload as JToken).ToObject(_serializer); - var channel = State.GetChannel(data.ChannelId) as ISocketMessageChannel; + var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; var guild = (channel as SocketGuildChannel)?.Guild; if (guild != null && !guild.IsSynced) @@ -1291,52 +1327,77 @@ 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; + } + + if (channel == null) + { + if (!data.GuildId.IsSpecified) // assume it is a DM { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + channel = CreateDMChannel(data.ChannelId, data.Author.Value, State); + } + else + { + await UnknownChannelAsync(type, data.ChannelId).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 (guild != null) { - before = cachedMsg.Clone(); - cachedMsg.Update(State, data); - after = cachedMsg; + 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) { - //Edited message isnt in cache, create a detached one - SocketUser author; - if (data.Author.IsSpecified) + if (guild != null) { - if (guild != null) - author = guild.GetUser(data.Author.Value.Id); + 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 = (channel as SocketChannel).GetUser(data.Author.Value.Id); - if (author == null) - author = SocketUnknownUser.Create(this, State, data.Author.Value); + 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); - - after = SocketMessage.Create(this, State, author, channel, data); + { + await UnknownChannelUserAsync(type, data.Author.Value.Id, channel.Id).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": @@ -1344,26 +1405,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 channel = GetChannel(data.ChannelId) as ISocketMessageChannel; - 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)); - - 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": @@ -1371,40 +1428,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": @@ -1412,32 +1472,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 channel = GetChannel(data.ChannelId) as ISocketMessageChannel; - var optionalMsg = !isCached - ? Optional.Create() - : Optional.Create(cachedMsg); - - 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": @@ -1445,21 +1508,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": @@ -1467,70 +1529,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 channel = GetChannel(data.ChannelId) as ISocketMessageChannel; - var optionalMsg = !isCached - ? Optional.Create() - : Optional.Create(cachedMsg); + var cachedMsg = channel?.GetCachedMessage(data.MessageId) as SocketUserMessage; + bool isMsgCached = cachedMsg != null; - var cacheable = new Cacheable(cachedMsg, data.MessageId, isCached, async () => await channel.GetMessageAsync(data.MessageId).ConfigureAwait(false) as IUserMessage); - var emote = data.Emoji.ToIEmote(); + var optionalMsg = !isMsgCached + ? Optional.Create() + : Optional.Create(cachedMsg); - cachedMsg?.RemoveReactionsForEmote(emote); - - 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; @@ -1599,24 +1646,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; @@ -1688,7 +1737,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); @@ -2113,8 +2162,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); @@ -2144,8 +2193,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 90b746787..22a201c67 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketConfig.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketConfig.cs @@ -111,22 +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 the maximum identify concurrency. /// From d176fef5de3da891d9e0f6108c91abc54b8d653f Mon Sep 17 00:00:00 2001 From: Paulo Date: Fri, 28 May 2021 10:09:21 -0300 Subject: [PATCH 56/64] fix: Fire GuildMemberUpdated without cached user (#1853) --- src/Discord.Net.WebSocket/BaseSocketClient.Events.cs | 1 + src/Discord.Net.WebSocket/DiscordSocketClient.cs | 7 ++----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs index e15e6a687..c947e2d24 100644 --- a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs +++ b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs @@ -347,6 +347,7 @@ namespace Discord.WebSocket } internal readonly AsyncEvent> _userUpdatedEvent = new AsyncEvent>(); /// Fired when a guild member is updated, or a member presence is updated. + /// The first user (state before) might be null if they weren't in the cache. public event Func GuildMemberUpdated { add { _guildMemberUpdatedEvent.Add(value); } remove { _guildMemberUpdatedEvent.Remove(value); } diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 0e854421e..c0638af8f 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -996,11 +996,8 @@ namespace Discord.WebSocket } 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); + await TimedInvokeAsync(_guildMemberUpdatedEvent, nameof(GuildMemberUpdated), null, user).ConfigureAwait(false); } } else From 8b29e0feb4d46b78d5381a32960c776cd7c631a9 Mon Sep 17 00:00:00 2001 From: Paulo Date: Fri, 28 May 2021 13:39:38 -0300 Subject: [PATCH 57/64] fix: MessageUpdated without author (#1858) --- .../DiscordSocketClient.cs | 48 +++++++++++-------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index c0638af8f..9b5b8ab36 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -1359,36 +1359,42 @@ namespace Discord.WebSocket { //Edited message isnt in cache, create a detached one SocketUser author; - 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 (data.Author.IsSpecified) { 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); - } + if (data.WebhookId.IsSpecified) + author = SocketWebhookUser.Create(guild, State, data.Author.Value, data.WebhookId.Value); else - author = guild.AddOrUpdateUser(data.Author.Value); // user has no guild-specific data + author = guild.GetUser(data.Author.Value.Id); } - else if (channel is SocketGroupChannel groupChannel) - author = groupChannel.GetOrAddUser(data.Author.Value); else + author = (channel as SocketChannel).GetUser(data.Author.Value.Id); + + if (author == null) { - await UnknownChannelUserAsync(type, data.Author.Value.Id, channel.Id).ConfigureAwait(false); - return; + 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 + { + await UnknownChannelUserAsync(type, data.Author.Value.Id, channel.Id).ConfigureAwait(false); + return; + } } } + 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); } From de7f9b5effb963587cb4156158a320dd38ebb543 Mon Sep 17 00:00:00 2001 From: Paulo Date: Fri, 28 May 2021 18:00:31 -0300 Subject: [PATCH 58/64] fix: Add missing AddRef and related (#1859) --- .../DiscordSocketClient.cs | 5 +++-- .../Entities/Guilds/SocketGuild.cs | 18 +++++++----------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 9b5b8ab36..5103bf7c3 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -775,7 +775,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) @@ -792,7 +793,7 @@ namespace Discord.WebSocket { await UnknownGuildAsync(type, data.Id).ConfigureAwait(false); return; - } + }*/ } break; case "GUILD_DELETE": diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index 59f1c5956..9af4ad57e 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -387,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; @@ -482,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)); { @@ -502,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) { @@ -842,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) @@ -885,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) From 384ad85c647d606fd10fa2a193d83b35c6da3fe3 Mon Sep 17 00:00:00 2001 From: Paulo Date: Fri, 28 May 2021 18:40:39 -0300 Subject: [PATCH 59/64] feature: Remove /users/@me call for socket and rework sharded client a bit (#1860) * Remove /users/@me call for socket and rework sharded client a bit * Remove override for login --- src/Discord.Net.WebSocket/BaseSocketClient.cs | 2 +- src/Discord.Net.WebSocket/DiscordShardedClient.cs | 12 +++--------- src/Discord.Net.WebSocket/DiscordSocketClient.cs | 5 ----- 3 files changed, 4 insertions(+), 15 deletions(-) diff --git a/src/Discord.Net.WebSocket/BaseSocketClient.cs b/src/Discord.Net.WebSocket/BaseSocketClient.cs index b1b430178..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. /// diff --git a/src/Discord.Net.WebSocket/DiscordShardedClient.cs b/src/Discord.Net.WebSocket/DiscordShardedClient.cs index ea50a571e..e6d05b525 100644 --- a/src/Discord.Net.WebSocket/DiscordShardedClient.cs +++ b/src/Discord.Net.WebSocket/DiscordShardedClient.cs @@ -40,7 +40,9 @@ namespace Discord.WebSocket /// /// 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()) { } @@ -330,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); diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 5103bf7c3..c419bb764 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -193,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() { From fabe034daadc5dc9fe8e5cae99bbd028677b1015 Mon Sep 17 00:00:00 2001 From: Paulo Date: Fri, 28 May 2021 22:25:04 -0300 Subject: [PATCH 60/64] fix: Message update without author (#1862) --- .../DiscordSocketClient.cs | 46 +++++++++++-------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index c419bb764..5ae1a0350 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -1329,21 +1329,8 @@ namespace Discord.WebSocket return; } - if (channel == null) - { - if (!data.GuildId.IsSpecified) // assume it is a DM - { - channel = CreateDMChannel(data.ChannelId, data.Author.Value, State); - } - else - { - await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); - return; - } - } - SocketMessage before = null, after = null; - SocketMessage cachedMsg = channel.GetCachedMessage(data.Id); + SocketMessage cachedMsg = channel?.GetCachedMessage(data.Id); bool isCached = cachedMsg != null; if (isCached) { @@ -1365,7 +1352,7 @@ namespace Discord.WebSocket author = guild.GetUser(data.Author.Value.Id); } else - author = (channel as SocketChannel).GetUser(data.Author.Value.Id); + author = (channel as SocketChannel)?.GetUser(data.Author.Value.Id); if (author == null) { @@ -1381,17 +1368,32 @@ namespace Discord.WebSocket } 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; - } } } 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) + { + if (!data.GuildId.IsSpecified) // assume it is a DM + { + if (data.Author.IsSpecified) + { + var dmChannel = CreateDMChannel(data.ChannelId, data.Author.Value, State); + channel = dmChannel; + author = dmChannel.Recipient; + } + else + channel = CreateDMChannel(data.ChannelId, author, State); + } + 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)); @@ -1989,6 +1991,10 @@ namespace Discord.WebSocket { 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; From 72d0470600c20ad34c0ddf5311bc43a59f9c28c9 Mon Sep 17 00:00:00 2001 From: Quin Lynch <49576606+quinchs@users.noreply.github.com> Date: Sat, 29 May 2021 07:15:26 -0300 Subject: [PATCH 61/64] Update README.md --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 8e2b5fe02..f9b8049c6 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,15 @@ This repo is a custom fork of Discord.Net that introduces the newest features of discord for testing and experimenting. Nothing here is guaranteed to work but you are more than welcome to submit bugs in the issues tabs +## Known issues +Labs will not work with Playwo's [InteractivityAddon](https://github.com/Playwo/Discord.InteractivityAddon). The reason is that his package depends on the base discord.net lib, you can get around this by cloning his repo and building it with discord.net labs instead of discord.net. + +## How to use +Setting up labs in your project is really simple, here's how to do it: +1) Remove Discord.Net from your project +2) Add Discord.Net Labs nuget to your project +3) Enjoy! + ## Branches ### Dev The main branch we pull off of to introduce new features into, the dev branch is the same as Discord.Nets dev branch From 125f6c78620477208be635d035019fe15cfb5640 Mon Sep 17 00:00:00 2001 From: Hitmasu Date: Sat, 29 May 2021 10:25:58 -0300 Subject: [PATCH 62/64] docs: use async main * Removed text async * Updated code * Added main as proxy task --- docs/guides/getting_started/first-bot.md | 8 ++------ .../getting_started/samples/first-bot/async-context.cs | 3 +-- docs/guides/getting_started/samples/first-bot/complete.cs | 3 +-- .../guides/getting_started/samples/first-bot/structure.cs | 4 ++-- 4 files changed, 6 insertions(+), 12 deletions(-) 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; From 55355ce65ba1426056cf5820cb83e7b3162ad254 Mon Sep 17 00:00:00 2001 From: Quin Lynch <49576606+quinchs@users.noreply.github.com> Date: Sat, 29 May 2021 11:12:19 -0300 Subject: [PATCH 63/64] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f9b8049c6..981108982 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # Discord.Net Labs [![NuGet](https://img.shields.io/nuget/vpre/Discord.Net.Labs.svg?maxAge=2592000?style=plastic)](https://www.nuget.org/packages/Discord.Net.Labs) +[![Discord](https://discord.com/api/guilds/848176216011046962/widget.png)](https://discord.gg/dvSfUTet3K) This repo is a custom fork of Discord.Net that introduces the newest features of discord for testing and experimenting. Nothing here is guaranteed to work but you are more than welcome to submit bugs in the issues tabs From c2e87f56c05cc4eeeb7dee37673e7326a1516969 Mon Sep 17 00:00:00 2001 From: Paulo Date: Sun, 30 May 2021 15:53:18 -0300 Subject: [PATCH 64/64] feature: Change GuildMemberUpdate before state to cacheable (#1863) * Change GuildMemberUpdate before to cacheable * Update PresenceUpdate --- src/Discord.Net.WebSocket/BaseSocketClient.Events.cs | 5 ++--- src/Discord.Net.WebSocket/DiscordSocketClient.cs | 10 +++++++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs index c947e2d24..fcaa793fc 100644 --- a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs +++ b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs @@ -347,12 +347,11 @@ namespace Discord.WebSocket } internal readonly AsyncEvent> _userUpdatedEvent = new AsyncEvent>(); /// Fired when a guild member is updated, or a member presence is updated. - /// The first user (state before) might be null if they weren't in the cache. - 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); } diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 5ae1a0350..888055da9 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -988,12 +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 { user = guild.AddOrUpdateUser(data); - await TimedInvokeAsync(_guildMemberUpdatedEvent, nameof(GuildMemberUpdated), null, user).ConfigureAwait(false); + var cacheableBefore = new Cacheable(null, user.Id, false, () => Task.FromResult((SocketGuildUser)null)); + await TimedInvokeAsync(_guildMemberUpdatedEvent, nameof(GuildMemberUpdated), cacheableBefore, user).ConfigureAwait(false); } } else @@ -1624,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 {