From 305d7f9e137b86e50412204e7dd4aa2dfe733094 Mon Sep 17 00:00:00 2001 From: FeroxFoxxo Date: Sun, 27 Mar 2022 01:52:31 +1300 Subject: [PATCH] Fix: Integration model from GuildIntegration and added INTEGRATION gateway events (#2168) * fix integration models; add integration events * fix description on IGUILD for integration * fix typo in integration documentation * fix documentation in connection visibility * removed public identitiers from app and connection * Removed REST endpoints that are not part of the API. * Added documentation for rest integrations * added optional types * Fixed rest interaction field with not being IsSpecified --- .../Guilds/GuildIntegrationProperties.cs | 21 ---- .../Entities/Guilds/IGuild.cs | 21 +++- .../Entities/Guilds/IntegrationAccount.cs | 18 --- .../IIntegration.cs} | 47 ++++++-- .../Integrations/IIntegrationAccount.cs | 23 ++++ .../Integrations/IIntegrationApplication.cs | 33 ++++++ .../Integrations/IntegrationExpireBehavior.cs | 17 +++ .../Entities/Users/ConnectionVisibility.cs | 17 +++ .../Entities/Users/IConnection.cs | 59 +++++++--- src/Discord.Net.Rest/API/Common/Connection.cs | 18 ++- .../API/Common/Integration.cs | 25 +++-- .../API/Common/IntegrationAccount.cs | 2 +- .../API/Common/IntegrationApplication.cs | 20 ++++ src/Discord.Net.Rest/ClientHelper.cs | 2 +- src/Discord.Net.Rest/DiscordRestApiClient.cs | 39 +------ .../Entities/Guilds/GuildHelper.cs | 16 +-- .../Entities/Guilds/RestGuild.cs | 12 +- .../Entities/Guilds/RestGuildIntegration.cs | 104 ------------------ .../Entities/Integrations/RestIntegration.cs | 102 +++++++++++++++++ .../Integrations/RestIntegrationAccount.cs | 29 +++++ .../RestIntegrationApplication.cs | 39 +++++++ .../Entities/Users/RestConnection.cs | 53 ++++++--- .../API/Gateway/IntegrationDeletedEvent.cs | 14 +++ .../BaseSocketClient.Events.cs | 26 +++++ .../DiscordSocketClient.cs | 86 +++++++++++++++ .../Entities/Guilds/SocketGuild.cs | 12 +- 26 files changed, 596 insertions(+), 259 deletions(-) delete mode 100644 src/Discord.Net.Core/Entities/Guilds/GuildIntegrationProperties.cs delete mode 100644 src/Discord.Net.Core/Entities/Guilds/IntegrationAccount.cs rename src/Discord.Net.Core/Entities/{Guilds/IGuildIntegration.cs => Integrations/IIntegration.cs} (61%) create mode 100644 src/Discord.Net.Core/Entities/Integrations/IIntegrationAccount.cs create mode 100644 src/Discord.Net.Core/Entities/Integrations/IIntegrationApplication.cs create mode 100644 src/Discord.Net.Core/Entities/Integrations/IntegrationExpireBehavior.cs create mode 100644 src/Discord.Net.Core/Entities/Users/ConnectionVisibility.cs create mode 100644 src/Discord.Net.Rest/API/Common/IntegrationApplication.cs delete mode 100644 src/Discord.Net.Rest/Entities/Guilds/RestGuildIntegration.cs create mode 100644 src/Discord.Net.Rest/Entities/Integrations/RestIntegration.cs create mode 100644 src/Discord.Net.Rest/Entities/Integrations/RestIntegrationAccount.cs create mode 100644 src/Discord.Net.Rest/Entities/Integrations/RestIntegrationApplication.cs create mode 100644 src/Discord.Net.WebSocket/API/Gateway/IntegrationDeletedEvent.cs diff --git a/src/Discord.Net.Core/Entities/Guilds/GuildIntegrationProperties.cs b/src/Discord.Net.Core/Entities/Guilds/GuildIntegrationProperties.cs deleted file mode 100644 index 2ca19b50a..000000000 --- a/src/Discord.Net.Core/Entities/Guilds/GuildIntegrationProperties.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace Discord -{ - /// - /// Provides properties used to modify an with the specified changes. - /// - public class GuildIntegrationProperties - { - /// - /// Gets or sets the behavior when an integration subscription lapses. - /// - public Optional ExpireBehavior { get; set; } - /// - /// Gets or sets the period (in seconds) where the integration will ignore lapsed subscriptions. - /// - public Optional ExpireGracePeriod { get; set; } - /// - /// Gets or sets whether emoticons should be synced for this integration. - /// - public Optional EnableEmoticons { get; set; } - } -} diff --git a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs index 3111ff495..b4625abbf 100644 --- a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs +++ b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs @@ -718,8 +718,25 @@ namespace Discord /// Task> GetVoiceRegionsAsync(RequestOptions options = null); - Task> GetIntegrationsAsync(RequestOptions options = null); - Task CreateIntegrationAsync(ulong id, string type, RequestOptions options = null); + /// + /// Gets a collection of all the integrations this guild contains. + /// + /// 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 + /// integrations the guild can has. + /// + Task> GetIntegrationsAsync(RequestOptions options = null); + + /// + /// Deletes an integration. + /// + /// The id for the integration. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous removal operation. + /// + Task DeleteIntegrationAsync(ulong id, RequestOptions options = null); /// /// Gets a collection of all invites in this guild. diff --git a/src/Discord.Net.Core/Entities/Guilds/IntegrationAccount.cs b/src/Discord.Net.Core/Entities/Guilds/IntegrationAccount.cs deleted file mode 100644 index 340115fde..000000000 --- a/src/Discord.Net.Core/Entities/Guilds/IntegrationAccount.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Diagnostics; - -namespace Discord -{ - [DebuggerDisplay("{DebuggerDisplay,nq}")] - public struct IntegrationAccount - { - /// Gets the ID of the account. - /// A unique identifier of this integration account. - public string Id { get; } - /// Gets the name of the account. - /// A string containing the name of this integration account. - public string Name { get; private set; } - - public override string ToString() => Name; - private string DebuggerDisplay => $"{Name} ({Id})"; - } -} diff --git a/src/Discord.Net.Core/Entities/Guilds/IGuildIntegration.cs b/src/Discord.Net.Core/Entities/Integrations/IIntegration.cs similarity index 61% rename from src/Discord.Net.Core/Entities/Guilds/IGuildIntegration.cs rename to src/Discord.Net.Core/Entities/Integrations/IIntegration.cs index 6fe3f7b55..304d58792 100644 --- a/src/Discord.Net.Core/Entities/Guilds/IGuildIntegration.cs +++ b/src/Discord.Net.Core/Entities/Integrations/IIntegration.cs @@ -3,15 +3,16 @@ using System; namespace Discord { /// - /// Holds information for a guild integration feature. + /// Holds information for an integration feature. + /// Nullable fields not provided for Discord bot integrations, but are for Twitch etc. /// - public interface IGuildIntegration + public interface IIntegration { /// /// Gets the integration ID. /// /// - /// An representing the unique identifier value of this integration. + /// A representing the unique identifier value of this integration. /// ulong Id { get; } /// @@ -45,30 +46,52 @@ namespace Discord /// /// true if this integration is syncing; otherwise false. /// - bool IsSyncing { get; } + bool? IsSyncing { get; } /// /// Gets the ID that this integration uses for "subscribers". /// - ulong ExpireBehavior { get; } + ulong? RoleId { get; } + /// + /// Gets whether emoticons should be synced for this integration (twitch only currently). + /// + bool? HasEnabledEmoticons { get; } + /// + /// Gets the behavior of expiring subscribers. + /// + IntegrationExpireBehavior? ExpireBehavior { get; } /// /// Gets the grace period before expiring "subscribers". /// - ulong ExpireGracePeriod { get; } + int? ExpireGracePeriod { get; } + /// + /// Gets the user for this integration. + /// + IUser User { get; } + /// + /// Gets integration account information. + /// + IIntegrationAccount Account { get; } /// /// Gets when this integration was last synced. /// /// /// A containing a date and time of day when the integration was last synced. /// - DateTimeOffset SyncedAt { get; } + DateTimeOffset? SyncedAt { get; } /// - /// Gets integration account information. + /// Gets how many subscribers this integration has. /// - IntegrationAccount Account { get; } - + int? SubscriberCount { get; } + /// + /// Gets whether this integration been revoked. + /// + bool? IsRevoked { get; } + /// + /// Gets the bot/OAuth2 application for a discord integration. + /// + IIntegrationApplication Application { get; } + IGuild Guild { get; } ulong GuildId { get; } - ulong RoleId { get; } - IUser User { get; } } } diff --git a/src/Discord.Net.Core/Entities/Integrations/IIntegrationAccount.cs b/src/Discord.Net.Core/Entities/Integrations/IIntegrationAccount.cs new file mode 100644 index 000000000..322ffa5c2 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Integrations/IIntegrationAccount.cs @@ -0,0 +1,23 @@ +namespace Discord +{ + /// + /// Provides the account information for an . + /// + public interface IIntegrationAccount + { + /// + /// Gets the ID of the account. + /// + /// + /// A unique identifier of this integration account. + /// + string Id { get; } + /// + /// Gets the name of the account. + /// + /// + /// A string containing the name of this integration account. + /// + string Name { get; } + } +} diff --git a/src/Discord.Net.Core/Entities/Integrations/IIntegrationApplication.cs b/src/Discord.Net.Core/Entities/Integrations/IIntegrationApplication.cs new file mode 100644 index 000000000..9085ae686 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Integrations/IIntegrationApplication.cs @@ -0,0 +1,33 @@ +namespace Discord +{ + /// + /// Provides the bot/OAuth2 application for an . + /// + public interface IIntegrationApplication + { + /// + /// Gets the id of the app. + /// + ulong Id { get; } + /// + /// Gets the name of the app. + /// + string Name { get; } + /// + /// Gets the icon hash of the app. + /// + string Icon { get; } + /// + /// Gets the description of the app. + /// + string Description { get; } + /// + /// Gets the summary of the app. + /// + string Summary { get; } + /// + /// Gets the bot associated with this application. + /// + IUser Bot { get; } + } +} diff --git a/src/Discord.Net.Core/Entities/Integrations/IntegrationExpireBehavior.cs b/src/Discord.Net.Core/Entities/Integrations/IntegrationExpireBehavior.cs new file mode 100644 index 000000000..642e247eb --- /dev/null +++ b/src/Discord.Net.Core/Entities/Integrations/IntegrationExpireBehavior.cs @@ -0,0 +1,17 @@ +namespace Discord +{ + /// + /// The behavior of expiring subscribers for an . + /// + public enum IntegrationExpireBehavior + { + /// + /// Removes a role from an expired subscriber. + /// + RemoveRole = 0, + /// + /// Kicks an expired subscriber from the guild. + /// + Kick = 1 + } +} diff --git a/src/Discord.Net.Core/Entities/Users/ConnectionVisibility.cs b/src/Discord.Net.Core/Entities/Users/ConnectionVisibility.cs new file mode 100644 index 000000000..ed041c9f9 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Users/ConnectionVisibility.cs @@ -0,0 +1,17 @@ +namespace Discord +{ + /// + /// The visibility of the connected account. + /// + public enum ConnectionVisibility + { + /// + /// Invisible to everyone except the user themselves. + /// + None = 0, + /// + /// Visible to everyone. + /// + Everyone = 1 + } +} diff --git a/src/Discord.Net.Core/Entities/Users/IConnection.cs b/src/Discord.Net.Core/Entities/Users/IConnection.cs index 1e65d971f..94b23a4b5 100644 --- a/src/Discord.Net.Core/Entities/Users/IConnection.cs +++ b/src/Discord.Net.Core/Entities/Users/IConnection.cs @@ -4,24 +4,53 @@ namespace Discord { public interface IConnection { - /// Gets the ID of the connection account. - /// A representing the unique identifier value of this connection. + /// + /// Gets the ID of the connection account. + /// + /// + /// A representing the unique identifier value of this connection. + /// string Id { get; } - /// Gets the service of the connection (twitch, youtube). - /// A string containing the name of this type of connection. - string Type { get; } - /// Gets the username of the connection account. - /// A string containing the name of this connection. + /// + /// Gets the username of the connection account. + /// + /// + /// A string containing the name of this connection. + /// string Name { get; } - /// Gets whether the connection is revoked. - /// A value which if true indicates that this connection has been revoked, otherwise false. - bool IsRevoked { get; } - - /// Gets a of integration IDs. + /// + /// Gets the service of the connection (twitch, youtube). + /// + /// + /// A string containing the name of this type of connection. + /// + string Type { get; } + /// + /// Gets whether the connection is revoked. + /// /// - /// An containing - /// representations of unique identifier values of integrations. + /// A value which if true indicates that this connection has been revoked, otherwise false. /// - IReadOnlyCollection IntegrationIds { get; } + bool? IsRevoked { get; } + /// + /// Gets a of integration parials. + /// + IReadOnlyCollection Integrations { get; } + /// + /// Gets whether the connection is verified. + /// + bool Verified { get; } + /// + /// Gets whether friend sync is enabled for this connection. + /// + bool FriendSync { get; } + /// + /// Gets whether activities related to this connection will be shown in presence updates. + /// + bool ShowActivity { get; } + /// + /// Visibility of this connection. + /// + ConnectionVisibility Visibility { get; } } } diff --git a/src/Discord.Net.Rest/API/Common/Connection.cs b/src/Discord.Net.Rest/API/Common/Connection.cs index bd8de3902..0a9940e23 100644 --- a/src/Discord.Net.Rest/API/Common/Connection.cs +++ b/src/Discord.Net.Rest/API/Common/Connection.cs @@ -7,14 +7,22 @@ namespace Discord.API { [JsonProperty("id")] public string Id { get; set; } - [JsonProperty("type")] - public string Type { get; set; } [JsonProperty("name")] public string Name { get; set; } + [JsonProperty("type")] + public string Type { get; set; } [JsonProperty("revoked")] - public bool Revoked { get; set; } - + public Optional Revoked { get; set; } [JsonProperty("integrations")] - public IReadOnlyCollection Integrations { get; set; } + public Optional> Integrations { get; set; } + [JsonProperty("verified")] + public bool Verified { get; set; } + [JsonProperty("friend_sync")] + public bool FriendSync { get; set; } + [JsonProperty("show_activity")] + public bool ShowActivity { get; set; } + [JsonProperty("visibility")] + public ConnectionVisibility Visibility { get; set; } + } } diff --git a/src/Discord.Net.Rest/API/Common/Integration.cs b/src/Discord.Net.Rest/API/Common/Integration.cs index 47d67e149..5a2b00001 100644 --- a/src/Discord.Net.Rest/API/Common/Integration.cs +++ b/src/Discord.Net.Rest/API/Common/Integration.cs @@ -5,6 +5,9 @@ namespace Discord.API { internal class Integration { + [JsonProperty("guild_id")] + public Optional GuildId { get; set; } + [JsonProperty("id")] public ulong Id { get; set; } [JsonProperty("name")] @@ -14,18 +17,26 @@ namespace Discord.API [JsonProperty("enabled")] public bool Enabled { get; set; } [JsonProperty("syncing")] - public bool Syncing { get; set; } + public Optional Syncing { get; set; } [JsonProperty("role_id")] - public ulong RoleId { get; set; } + public Optional RoleId { get; set; } + [JsonProperty("enable_emoticons")] + public Optional EnableEmoticons { get; set; } [JsonProperty("expire_behavior")] - public ulong ExpireBehavior { get; set; } + public Optional ExpireBehavior { get; set; } [JsonProperty("expire_grace_period")] - public ulong ExpireGracePeriod { get; set; } + public Optional ExpireGracePeriod { get; set; } [JsonProperty("user")] - public User User { get; set; } + public Optional User { get; set; } [JsonProperty("account")] - public IntegrationAccount Account { get; set; } + public Optional Account { get; set; } [JsonProperty("synced_at")] - public DateTimeOffset SyncedAt { get; set; } + public Optional SyncedAt { get; set; } + [JsonProperty("subscriber_count")] + public Optional SubscriberAccount { get; set; } + [JsonProperty("revoked")] + public Optional Revoked { get; set; } + [JsonProperty("application")] + public Optional Application { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/IntegrationAccount.cs b/src/Discord.Net.Rest/API/Common/IntegrationAccount.cs index a8d33931c..6b8328074 100644 --- a/src/Discord.Net.Rest/API/Common/IntegrationAccount.cs +++ b/src/Discord.Net.Rest/API/Common/IntegrationAccount.cs @@ -5,7 +5,7 @@ namespace Discord.API internal class IntegrationAccount { [JsonProperty("id")] - public ulong Id { get; set; } + public string Id { get; set; } [JsonProperty("name")] public string Name { get; set; } } diff --git a/src/Discord.Net.Rest/API/Common/IntegrationApplication.cs b/src/Discord.Net.Rest/API/Common/IntegrationApplication.cs new file mode 100644 index 000000000..4e07398b8 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/IntegrationApplication.cs @@ -0,0 +1,20 @@ +using Newtonsoft.Json; + +namespace Discord.API +{ + internal class IntegrationApplication + { + [JsonProperty("id")] + public ulong Id { get; set; } + [JsonProperty("name")] + public string Name { get; set; } + [JsonProperty("icon")] + public Optional Icon { get; set; } + [JsonProperty("description")] + public string Description { get; set; } + [JsonProperty("summary")] + public string Summary { get; set; } + [JsonProperty("bot")] + public Optional Bot { get; set; } + } +} diff --git a/src/Discord.Net.Rest/ClientHelper.cs b/src/Discord.Net.Rest/ClientHelper.cs index 5debea27e..c6ad6a9fb 100644 --- a/src/Discord.Net.Rest/ClientHelper.cs +++ b/src/Discord.Net.Rest/ClientHelper.cs @@ -49,7 +49,7 @@ namespace Discord.Rest public static async Task> GetConnectionsAsync(BaseDiscordClient client, RequestOptions options) { var models = await client.ApiClient.GetMyConnectionsAsync(options).ConfigureAwait(false); - return models.Select(RestConnection.Create).ToImmutableArray(); + return models.Select(model => RestConnection.Create(client, model)).ToImmutableArray(); } public static async Task GetInviteAsync(BaseDiscordClient client, diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index f6d579d79..645e6711c 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -1626,7 +1626,7 @@ namespace Discord.API #region Guild Integrations /// must not be equal to zero. - public async Task> GetGuildIntegrationsAsync(ulong guildId, RequestOptions options = null) + public async Task> GetIntegrationsAsync(ulong guildId, RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); options = RequestOptions.CreateOrClone(options); @@ -1634,47 +1634,14 @@ namespace Discord.API var ids = new BucketIds(guildId: guildId); return await SendAsync>("GET", () => $"guilds/{guildId}/integrations", ids, options: options).ConfigureAwait(false); } - /// and must not be equal to zero. - /// must not be . - public async Task CreateGuildIntegrationAsync(ulong guildId, CreateGuildIntegrationParams args, RequestOptions options = null) - { - Preconditions.NotEqual(guildId, 0, nameof(guildId)); - Preconditions.NotNull(args, nameof(args)); - Preconditions.NotEqual(args.Id, 0, nameof(args.Id)); - options = RequestOptions.CreateOrClone(options); - - var ids = new BucketIds(guildId: guildId); - return await SendAsync("POST", () => $"guilds/{guildId}/integrations", ids, options: options).ConfigureAwait(false); - } - public async Task DeleteGuildIntegrationAsync(ulong guildId, ulong integrationId, RequestOptions options = null) - { - Preconditions.NotEqual(guildId, 0, nameof(guildId)); - Preconditions.NotEqual(integrationId, 0, nameof(integrationId)); - options = RequestOptions.CreateOrClone(options); - - var ids = new BucketIds(guildId: guildId); - return await SendAsync("DELETE", () => $"guilds/{guildId}/integrations/{integrationId}", ids, options: options).ConfigureAwait(false); - } - public async Task ModifyGuildIntegrationAsync(ulong guildId, ulong integrationId, Rest.ModifyGuildIntegrationParams args, RequestOptions options = null) - { - Preconditions.NotEqual(guildId, 0, nameof(guildId)); - Preconditions.NotEqual(integrationId, 0, nameof(integrationId)); - Preconditions.NotNull(args, nameof(args)); - Preconditions.AtLeast(args.ExpireBehavior, 0, nameof(args.ExpireBehavior)); - Preconditions.AtLeast(args.ExpireGracePeriod, 0, nameof(args.ExpireGracePeriod)); - options = RequestOptions.CreateOrClone(options); - - var ids = new BucketIds(guildId: guildId); - return await SendJsonAsync("PATCH", () => $"guilds/{guildId}/integrations/{integrationId}", args, ids, options: options).ConfigureAwait(false); - } - public async Task SyncGuildIntegrationAsync(ulong guildId, ulong integrationId, RequestOptions options = null) + public async Task DeleteIntegrationAsync(ulong guildId, ulong integrationId, RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); Preconditions.NotEqual(integrationId, 0, nameof(integrationId)); options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(guildId: guildId); - return await SendAsync("POST", () => $"guilds/{guildId}/integrations/{integrationId}/sync", ids, options: options).ConfigureAwait(false); + await SendAsync("DELETE", () => $"guilds/{guildId}/integrations/{integrationId}", ids, options: options).ConfigureAwait(false); } #endregion diff --git a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs index 25f474dcc..7dbe20881 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs @@ -305,19 +305,15 @@ namespace Discord.Rest #endregion #region Integrations - public static async Task> GetIntegrationsAsync(IGuild guild, BaseDiscordClient client, + public static async Task> GetIntegrationsAsync(IGuild guild, BaseDiscordClient client, RequestOptions options) { - var models = await client.ApiClient.GetGuildIntegrationsAsync(guild.Id, options).ConfigureAwait(false); - return models.Select(x => RestGuildIntegration.Create(client, guild, x)).ToImmutableArray(); - } - public static async Task CreateIntegrationAsync(IGuild guild, BaseDiscordClient client, - ulong id, string type, RequestOptions options) - { - var args = new CreateGuildIntegrationParams(id, type); - var model = await client.ApiClient.CreateGuildIntegrationAsync(guild.Id, args, options).ConfigureAwait(false); - return RestGuildIntegration.Create(client, guild, model); + var models = await client.ApiClient.GetIntegrationsAsync(guild.Id, options).ConfigureAwait(false); + return models.Select(x => RestIntegration.Create(client, guild, x)).ToImmutableArray(); } + public static async Task DeleteIntegrationAsync(IGuild guild, BaseDiscordClient client, ulong id, + RequestOptions options) => + await client.ApiClient.DeleteIntegrationAsync(guild.Id, id, options).ConfigureAwait(false); #endregion #region Interactions diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs index e89096f00..d7ab65a55 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs @@ -720,10 +720,10 @@ namespace Discord.Rest #endregion #region Integrations - public Task> GetIntegrationsAsync(RequestOptions options = null) + public Task> GetIntegrationsAsync(RequestOptions options = null) => GuildHelper.GetIntegrationsAsync(this, Discord, options); - public Task CreateIntegrationAsync(ulong id, string type, RequestOptions options = null) - => GuildHelper.CreateIntegrationAsync(this, Discord, id, type, options); + public Task DeleteIntegrationAsync(ulong id, RequestOptions options = null) + => GuildHelper.DeleteIntegrationAsync(this, Discord, id, options); #endregion #region Invites @@ -1370,11 +1370,11 @@ namespace Discord.Rest => await GetVoiceRegionsAsync(options).ConfigureAwait(false); /// - async Task> IGuild.GetIntegrationsAsync(RequestOptions options) + async Task> IGuild.GetIntegrationsAsync(RequestOptions options) => await GetIntegrationsAsync(options).ConfigureAwait(false); /// - async Task IGuild.CreateIntegrationAsync(ulong id, string type, RequestOptions options) - => await CreateIntegrationAsync(id, type, options).ConfigureAwait(false); + async Task IGuild.DeleteIntegrationAsync(ulong id, RequestOptions options) + => await DeleteIntegrationAsync(id, options).ConfigureAwait(false); /// async Task> IGuild.GetInvitesAsync(RequestOptions options) diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuildIntegration.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuildIntegration.cs deleted file mode 100644 index 9759e64d2..000000000 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuildIntegration.cs +++ /dev/null @@ -1,104 +0,0 @@ -using System; -using System.Diagnostics; -using System.Threading.Tasks; -using Model = Discord.API.Integration; - -namespace Discord.Rest -{ - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public class RestGuildIntegration : RestEntity, IGuildIntegration - { - private long _syncedAtTicks; - - /// - public string Name { get; private set; } - /// - public string Type { get; private set; } - /// - public bool IsEnabled { get; private set; } - /// - public bool IsSyncing { get; private set; } - /// - public ulong ExpireBehavior { get; private set; } - /// - public ulong ExpireGracePeriod { get; private set; } - /// - public ulong GuildId { get; private set; } - /// - public ulong RoleId { get; private set; } - public RestUser User { get; private set; } - /// - public IntegrationAccount Account { get; private set; } - internal IGuild Guild { get; private set; } - - /// - public DateTimeOffset SyncedAt => DateTimeUtils.FromTicks(_syncedAtTicks); - - internal RestGuildIntegration(BaseDiscordClient discord, IGuild guild, ulong id) - : base(discord, id) - { - Guild = guild; - } - internal static RestGuildIntegration Create(BaseDiscordClient discord, IGuild guild, Model model) - { - var entity = new RestGuildIntegration(discord, guild, model.Id); - entity.Update(model); - return entity; - } - - internal void Update(Model model) - { - Name = model.Name; - Type = model.Type; - IsEnabled = model.Enabled; - IsSyncing = model.Syncing; - ExpireBehavior = model.ExpireBehavior; - ExpireGracePeriod = model.ExpireGracePeriod; - _syncedAtTicks = model.SyncedAt.UtcTicks; - - RoleId = model.RoleId; - User = RestUser.Create(Discord, model.User); - } - - public async Task DeleteAsync() - { - await Discord.ApiClient.DeleteGuildIntegrationAsync(GuildId, Id).ConfigureAwait(false); - } - public async Task ModifyAsync(Action func) - { - if (func == null) throw new NullReferenceException(nameof(func)); - - var args = new GuildIntegrationProperties(); - func(args); - var apiArgs = new API.Rest.ModifyGuildIntegrationParams - { - EnableEmoticons = args.EnableEmoticons, - ExpireBehavior = args.ExpireBehavior, - ExpireGracePeriod = args.ExpireGracePeriod - }; - var model = await Discord.ApiClient.ModifyGuildIntegrationAsync(GuildId, Id, apiArgs).ConfigureAwait(false); - - Update(model); - } - public async Task SyncAsync() - { - await Discord.ApiClient.SyncGuildIntegrationAsync(GuildId, Id).ConfigureAwait(false); - } - - public override string ToString() => Name; - private string DebuggerDisplay => $"{Name} ({Id}{(IsEnabled ? ", Enabled" : "")})"; - - /// - IGuild IGuildIntegration.Guild - { - get - { - if (Guild != null) - return Guild; - throw new InvalidOperationException("Unable to return this entity's parent unless it was fetched through that object."); - } - } - /// - IUser IGuildIntegration.User => User; - } -} diff --git a/src/Discord.Net.Rest/Entities/Integrations/RestIntegration.cs b/src/Discord.Net.Rest/Entities/Integrations/RestIntegration.cs new file mode 100644 index 000000000..e92ecdded --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Integrations/RestIntegration.cs @@ -0,0 +1,102 @@ +using System; +using System.Diagnostics; +using System.Threading.Tasks; +using Model = Discord.API.Integration; + +namespace Discord.Rest +{ + /// + /// Represents a Rest-based implementation of . + /// + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public class RestIntegration : RestEntity, IIntegration + { + private long? _syncedAtTicks; + + /// + public string Name { get; private set; } + /// + public string Type { get; private set; } + /// + public bool IsEnabled { get; private set; } + /// + public bool? IsSyncing { get; private set; } + /// + public ulong? RoleId { get; private set; } + /// + public bool? HasEnabledEmoticons { get; private set; } + /// + public IntegrationExpireBehavior? ExpireBehavior { get; private set; } + /// + public int? ExpireGracePeriod { get; private set; } + /// + IUser IIntegration.User => User; + /// + public IIntegrationAccount Account { get; private set; } + /// + public DateTimeOffset? SyncedAt => DateTimeUtils.FromTicks(_syncedAtTicks); + /// + public int? SubscriberCount { get; private set; } + /// + public bool? IsRevoked { get; private set; } + /// + public IIntegrationApplication Application { get; private set; } + + internal IGuild Guild { get; private set; } + public RestUser User { get; private set; } + + internal RestIntegration(BaseDiscordClient discord, IGuild guild, ulong id) + : base(discord, id) + { + Guild = guild; + } + internal static RestIntegration Create(BaseDiscordClient discord, IGuild guild, Model model) + { + var entity = new RestIntegration(discord, guild, model.Id); + entity.Update(model); + return entity; + } + + internal void Update(Model model) + { + Name = model.Name; + Type = model.Type; + IsEnabled = model.Enabled; + + IsSyncing = model.Syncing.IsSpecified ? model.Syncing.Value : null; + RoleId = model.RoleId.IsSpecified ? model.RoleId.Value : null; + HasEnabledEmoticons = model.EnableEmoticons.IsSpecified ? model.EnableEmoticons.Value : null; + ExpireBehavior = model.ExpireBehavior.IsSpecified ? model.ExpireBehavior.Value : null; + ExpireGracePeriod = model.ExpireGracePeriod.IsSpecified ? model.ExpireGracePeriod.Value : null; + User = model.User.IsSpecified ? RestUser.Create(Discord, model.User.Value) : null; + Account = model.Account.IsSpecified ? RestIntegrationAccount.Create(model.Account.Value) : null; + SubscriberCount = model.SubscriberAccount.IsSpecified ? model.SubscriberAccount.Value : null; + IsRevoked = model.Revoked.IsSpecified ? model.Revoked.Value : null; + Application = model.Application.IsSpecified ? RestIntegrationApplication.Create(Discord, model.Application.Value) : null; + + _syncedAtTicks = model.SyncedAt.IsSpecified ? model.SyncedAt.Value.UtcTicks : null; + } + + public async Task DeleteAsync() + { + await Discord.ApiClient.DeleteIntegrationAsync(GuildId, Id).ConfigureAwait(false); + } + + public override string ToString() => Name; + private string DebuggerDisplay => $"{Name} ({Id}{(IsEnabled ? ", Enabled" : "")})"; + + /// + public ulong GuildId { get; private set; } + + /// + IGuild IIntegration.Guild + { + get + { + if (Guild != null) + return Guild; + throw new InvalidOperationException("Unable to return this entity's parent unless it was fetched through that object."); + } + } + } +} diff --git a/src/Discord.Net.Rest/Entities/Integrations/RestIntegrationAccount.cs b/src/Discord.Net.Rest/Entities/Integrations/RestIntegrationAccount.cs new file mode 100644 index 000000000..6d83aa1f0 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Integrations/RestIntegrationAccount.cs @@ -0,0 +1,29 @@ +using Model = Discord.API.IntegrationAccount; + +namespace Discord.Rest +{ + /// + /// Represents a Rest-based implementation of . + /// + public class RestIntegrationAccount : IIntegrationAccount + { + internal RestIntegrationAccount() { } + + public string Id { get; private set; } + + public string Name { get; private set; } + + internal static RestIntegrationAccount Create(Model model) + { + var entity = new RestIntegrationAccount(); + entity.Update(model); + return entity; + } + + internal void Update(Model model) + { + model.Name = Name; + model.Id = Id; + } + } +} diff --git a/src/Discord.Net.Rest/Entities/Integrations/RestIntegrationApplication.cs b/src/Discord.Net.Rest/Entities/Integrations/RestIntegrationApplication.cs new file mode 100644 index 000000000..e532ac970 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Integrations/RestIntegrationApplication.cs @@ -0,0 +1,39 @@ +using Model = Discord.API.IntegrationApplication; + +namespace Discord.Rest +{ + /// + /// Represents a Rest-based implementation of . + /// + public class RestIntegrationApplication : RestEntity, IIntegrationApplication + { + public string Name { get; private set; } + + public string Icon { get; private set; } + + public string Description { get; private set; } + + public string Summary { get; private set; } + + public IUser Bot { get; private set; } + + internal RestIntegrationApplication(BaseDiscordClient discord, ulong id) + : base(discord, id) { } + + internal static RestIntegrationApplication Create(BaseDiscordClient discord, Model model) + { + var entity = new RestIntegrationApplication(discord, model.Id); + entity.Update(model); + return entity; + } + + internal void Update(Model model) + { + Name = model.Name; + Icon = model.Icon.IsSpecified ? model.Icon.Value : null; + Description = model.Description; + Summary = model.Summary; + Bot = RestUser.Create(Discord, model.Bot.Value); + } + } +} diff --git a/src/Discord.Net.Rest/Entities/Users/RestConnection.cs b/src/Discord.Net.Rest/Entities/Users/RestConnection.cs index 1afb813c0..496279727 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestConnection.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestConnection.cs @@ -1,6 +1,8 @@ using System.Collections.Generic; using System.Collections.Immutable; +using System.Collections.ObjectModel; using System.Diagnostics; +using System.Linq; using Model = Discord.API.Connection; namespace Discord.Rest @@ -9,28 +11,49 @@ namespace Discord.Rest public class RestConnection : IConnection { /// - public string Id { get; } + public string Id { get; private set; } /// - public string Type { get; } + public string Name { get; private set; } /// - public string Name { get; } + public string Type { get; private set; } /// - public bool IsRevoked { get; } + public bool? IsRevoked { get; private set; } /// - public IReadOnlyCollection IntegrationIds { get; } + public IReadOnlyCollection Integrations { get; private set; } + /// + public bool Verified { get; private set; } + /// + public bool FriendSync { get; private set; } + /// + public bool ShowActivity { get; private set; } + /// + public ConnectionVisibility Visibility { get; private set; } - internal RestConnection(string id, string type, string name, bool isRevoked, IReadOnlyCollection integrationIds) - { - Id = id; - Type = type; - Name = name; - IsRevoked = isRevoked; + internal BaseDiscordClient Discord { get; } - IntegrationIds = integrationIds; + internal RestConnection(BaseDiscordClient discord) { + Discord = discord; } - internal static RestConnection Create(Model model) + + internal static RestConnection Create(BaseDiscordClient discord, Model model) + { + var entity = new RestConnection(discord); + entity.Update(model); + return entity; + } + + internal void Update(Model model) { - return new RestConnection(model.Id, model.Type, model.Name, model.Revoked, model.Integrations.ToImmutableArray()); + Id = model.Id; + Name = model.Name; + Type = model.Type; + IsRevoked = model.Revoked.IsSpecified ? model.Revoked.Value : null; + Integrations = model.Integrations.IsSpecified ?model.Integrations.Value + .Select(intergration => RestIntegration.Create(Discord, null, intergration)).ToImmutableArray() : null; + Verified = model.Verified; + FriendSync = model.FriendSync; + ShowActivity = model.ShowActivity; + Visibility = model.Visibility; } /// @@ -40,6 +63,6 @@ namespace Discord.Rest /// Name of the connection. /// public override string ToString() => Name; - private string DebuggerDisplay => $"{Name} ({Id}, {Type}{(IsRevoked ? ", Revoked" : "")})"; + private string DebuggerDisplay => $"{Name} ({Id}, {Type}{(IsRevoked.GetValueOrDefault() ? ", Revoked" : "")})"; } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/IntegrationDeletedEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/IntegrationDeletedEvent.cs new file mode 100644 index 000000000..cf6e70ca6 --- /dev/null +++ b/src/Discord.Net.WebSocket/API/Gateway/IntegrationDeletedEvent.cs @@ -0,0 +1,14 @@ +using Newtonsoft.Json; + +namespace Discord.API.Gateway +{ + internal class IntegrationDeletedEvent + { + [JsonProperty("id")] + public ulong Id { get; set; } + [JsonProperty("guild_id")] + public ulong GuildId { get; set; } + [JsonProperty("application_id")] + public Optional ApplicationID { get; set; } + } +} diff --git a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs index b8d3b6a10..c47591418 100644 --- a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs +++ b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs @@ -415,6 +415,32 @@ namespace Discord.WebSocket #endregion + #region Integrations + /// Fired when an integration is created. + public event Func IntegrationCreated + { + add { _integrationCreated.Add(value); } + remove { _integrationCreated.Remove(value); } + } + internal readonly AsyncEvent> _integrationCreated = new AsyncEvent>(); + + /// Fired when an integration is updated. + public event Func IntegrationUpdated + { + add { _integrationUpdated.Add(value); } + remove { _integrationUpdated.Remove(value); } + } + internal readonly AsyncEvent> _integrationUpdated = new AsyncEvent>(); + + /// Fired when an integration is deleted. + public event Func, Task> IntegrationDeleted + { + add { _integrationDeleted.Add(value); } + remove { _integrationDeleted.Remove(value); } + } + internal readonly AsyncEvent, Task>> _integrationDeleted = new AsyncEvent, Task>>(); + #endregion + #region 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 b692f0691..f33d89047 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -2017,6 +2017,92 @@ namespace Discord.WebSocket break; #endregion + #region Integrations + case "INTEGRATION_CREATE": + { + await _gatewayLogger.DebugAsync("Received Dispatch (INTEGRATION_CREATE)").ConfigureAwait(false); + + var data = (payload as JToken).ToObject(_serializer); + + // Integrations from Gateway should always have guild IDs specified. + if (!data.GuildId.IsSpecified) + return; + + var guild = State.GetGuild(data.GuildId.Value); + + if (guild != null) + { + if (!guild.IsSynced) + { + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + return; + } + + await TimedInvokeAsync(_integrationCreated, nameof(IntegrationCreated), RestIntegration.Create(this, guild, data)).ConfigureAwait(false); + } + else + { + await UnknownGuildAsync(type, data.GuildId.Value).ConfigureAwait(false); + return; + } + } + break; + case "INTEGRATION_UPDATE": + { + await _gatewayLogger.DebugAsync("Received Dispatch (INTEGRATION_UPDATE)").ConfigureAwait(false); + + var data = (payload as JToken).ToObject(_serializer); + + // Integrations from Gateway should always have guild IDs specified. + if (!data.GuildId.IsSpecified) + return; + + var guild = State.GetGuild(data.GuildId.Value); + + if (guild != null) + { + if (!guild.IsSynced) + { + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + return; + } + + await TimedInvokeAsync(_integrationUpdated, nameof(IntegrationUpdated), RestIntegration.Create(this, guild, data)).ConfigureAwait(false); + } + else + { + await UnknownGuildAsync(type, data.GuildId.Value).ConfigureAwait(false); + return; + } + } + break; + case "INTEGRATION_DELETE": + { + await _gatewayLogger.DebugAsync("Received Dispatch (INTEGRATION_DELETE)").ConfigureAwait(false); + + var data = (payload as JToken).ToObject(_serializer); + + var guild = State.GetGuild(data.GuildId); + + if (guild != null) + { + if (!guild.IsSynced) + { + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + return; + } + + await TimedInvokeAsync(_integrationDeleted, nameof(IntegrationDeleted), guild, data.Id, data.ApplicationID).ConfigureAwait(false); + } + else + { + await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); + return; + } + } + break; + #endregion + #region Users case "USER_UPDATE": { diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index c4b756410..47bd57552 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -847,10 +847,10 @@ namespace Discord.WebSocket #endregion #region Integrations - public Task> GetIntegrationsAsync(RequestOptions options = null) + public Task> GetIntegrationsAsync(RequestOptions options = null) => GuildHelper.GetIntegrationsAsync(this, Discord, options); - public Task CreateIntegrationAsync(ulong id, string type, RequestOptions options = null) - => GuildHelper.CreateIntegrationAsync(this, Discord, id, type, options); + public Task DeleteIntegrationAsync(ulong id, RequestOptions options = null) + => GuildHelper.DeleteIntegrationAsync(this, Discord, id, options); #endregion #region Interactions @@ -1888,11 +1888,11 @@ namespace Discord.WebSocket => await GetVoiceRegionsAsync(options).ConfigureAwait(false); /// - async Task> IGuild.GetIntegrationsAsync(RequestOptions options) + async Task> IGuild.GetIntegrationsAsync(RequestOptions options) => await GetIntegrationsAsync(options).ConfigureAwait(false); /// - async Task IGuild.CreateIntegrationAsync(ulong id, string type, RequestOptions options) - => await CreateIntegrationAsync(id, type, options).ConfigureAwait(false); + async Task IGuild.DeleteIntegrationAsync(ulong id, RequestOptions options) + => await DeleteIntegrationAsync(id, options).ConfigureAwait(false); /// async Task> IGuild.GetInvitesAsync(RequestOptions options)