* 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 IsSpecifiedtags/3.5.0
| @@ -1,21 +0,0 @@ | |||
| namespace Discord | |||
| { | |||
| /// <summary> | |||
| /// Provides properties used to modify an <see cref="IGuildIntegration" /> with the specified changes. | |||
| /// </summary> | |||
| public class GuildIntegrationProperties | |||
| { | |||
| /// <summary> | |||
| /// Gets or sets the behavior when an integration subscription lapses. | |||
| /// </summary> | |||
| public Optional<int> ExpireBehavior { get; set; } | |||
| /// <summary> | |||
| /// Gets or sets the period (in seconds) where the integration will ignore lapsed subscriptions. | |||
| /// </summary> | |||
| public Optional<int> ExpireGracePeriod { get; set; } | |||
| /// <summary> | |||
| /// Gets or sets whether emoticons should be synced for this integration. | |||
| /// </summary> | |||
| public Optional<bool> EnableEmoticons { get; set; } | |||
| } | |||
| } | |||
| @@ -718,8 +718,25 @@ namespace Discord | |||
| /// </returns> | |||
| Task<IReadOnlyCollection<IVoiceRegion>> GetVoiceRegionsAsync(RequestOptions options = null); | |||
| Task<IReadOnlyCollection<IGuildIntegration>> GetIntegrationsAsync(RequestOptions options = null); | |||
| Task<IGuildIntegration> CreateIntegrationAsync(ulong id, string type, RequestOptions options = null); | |||
| /// <summary> | |||
| /// Gets a collection of all the integrations this guild contains. | |||
| /// </summary> | |||
| /// <param name="options">The options to be used when sending the request.</param> | |||
| /// <returns> | |||
| /// A task that represents the asynchronous get operation. The task result contains a read-only collection of | |||
| /// integrations the guild can has. | |||
| /// </returns> | |||
| Task<IReadOnlyCollection<IIntegration>> GetIntegrationsAsync(RequestOptions options = null); | |||
| /// <summary> | |||
| /// Deletes an integration. | |||
| /// </summary> | |||
| /// <param name="id">The id for the integration.</param> | |||
| /// <param name="options">The options to be used when sending the request.</param> | |||
| /// <returns> | |||
| /// A task that represents the asynchronous removal operation. | |||
| /// </returns> | |||
| Task DeleteIntegrationAsync(ulong id, RequestOptions options = null); | |||
| /// <summary> | |||
| /// Gets a collection of all invites in this guild. | |||
| @@ -1,18 +0,0 @@ | |||
| using System.Diagnostics; | |||
| namespace Discord | |||
| { | |||
| [DebuggerDisplay("{DebuggerDisplay,nq}")] | |||
| public struct IntegrationAccount | |||
| { | |||
| /// <summary> Gets the ID of the account. </summary> | |||
| /// <returns> A <see cref="string"/> unique identifier of this integration account. </returns> | |||
| public string Id { get; } | |||
| /// <summary> Gets the name of the account. </summary> | |||
| /// <returns> A string containing the name of this integration account. </returns> | |||
| public string Name { get; private set; } | |||
| public override string ToString() => Name; | |||
| private string DebuggerDisplay => $"{Name} ({Id})"; | |||
| } | |||
| } | |||
| @@ -3,15 +3,16 @@ using System; | |||
| namespace Discord | |||
| { | |||
| /// <summary> | |||
| /// 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. | |||
| /// </summary> | |||
| public interface IGuildIntegration | |||
| public interface IIntegration | |||
| { | |||
| /// <summary> | |||
| /// Gets the integration ID. | |||
| /// </summary> | |||
| /// <returns> | |||
| /// An <see cref="UInt64"/> representing the unique identifier value of this integration. | |||
| /// A <see cref="ulong"/> representing the unique identifier value of this integration. | |||
| /// </returns> | |||
| ulong Id { get; } | |||
| /// <summary> | |||
| @@ -45,30 +46,52 @@ namespace Discord | |||
| /// <returns> | |||
| /// <c>true</c> if this integration is syncing; otherwise <c>false</c>. | |||
| /// </returns> | |||
| bool IsSyncing { get; } | |||
| bool? IsSyncing { get; } | |||
| /// <summary> | |||
| /// Gets the ID that this integration uses for "subscribers". | |||
| /// </summary> | |||
| ulong ExpireBehavior { get; } | |||
| ulong? RoleId { get; } | |||
| /// <summary> | |||
| /// Gets whether emoticons should be synced for this integration (twitch only currently). | |||
| /// </summary> | |||
| bool? HasEnabledEmoticons { get; } | |||
| /// <summary> | |||
| /// Gets the behavior of expiring subscribers. | |||
| /// </summary> | |||
| IntegrationExpireBehavior? ExpireBehavior { get; } | |||
| /// <summary> | |||
| /// Gets the grace period before expiring "subscribers". | |||
| /// </summary> | |||
| ulong ExpireGracePeriod { get; } | |||
| int? ExpireGracePeriod { get; } | |||
| /// <summary> | |||
| /// Gets the user for this integration. | |||
| /// </summary> | |||
| IUser User { get; } | |||
| /// <summary> | |||
| /// Gets integration account information. | |||
| /// </summary> | |||
| IIntegrationAccount Account { get; } | |||
| /// <summary> | |||
| /// Gets when this integration was last synced. | |||
| /// </summary> | |||
| /// <returns> | |||
| /// A <see cref="DateTimeOffset"/> containing a date and time of day when the integration was last synced. | |||
| /// </returns> | |||
| DateTimeOffset SyncedAt { get; } | |||
| DateTimeOffset? SyncedAt { get; } | |||
| /// <summary> | |||
| /// Gets integration account information. | |||
| /// Gets how many subscribers this integration has. | |||
| /// </summary> | |||
| IntegrationAccount Account { get; } | |||
| int? SubscriberCount { get; } | |||
| /// <summary> | |||
| /// Gets whether this integration been revoked. | |||
| /// </summary> | |||
| bool? IsRevoked { get; } | |||
| /// <summary> | |||
| /// Gets the bot/OAuth2 application for a discord integration. | |||
| /// </summary> | |||
| IIntegrationApplication Application { get; } | |||
| IGuild Guild { get; } | |||
| ulong GuildId { get; } | |||
| ulong RoleId { get; } | |||
| IUser User { get; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,23 @@ | |||
| namespace Discord | |||
| { | |||
| /// <summary> | |||
| /// Provides the account information for an <see cref="IIntegration" />. | |||
| /// </summary> | |||
| public interface IIntegrationAccount | |||
| { | |||
| /// <summary> | |||
| /// Gets the ID of the account. | |||
| /// </summary> | |||
| /// <returns> | |||
| /// A <see cref="string"/> unique identifier of this integration account. | |||
| /// </returns> | |||
| string Id { get; } | |||
| /// <summary> | |||
| /// Gets the name of the account. | |||
| /// </summary> | |||
| /// <returns> | |||
| /// A string containing the name of this integration account. | |||
| /// </returns> | |||
| string Name { get; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,33 @@ | |||
| namespace Discord | |||
| { | |||
| /// <summary> | |||
| /// Provides the bot/OAuth2 application for an <see cref="IIntegration" />. | |||
| /// </summary> | |||
| public interface IIntegrationApplication | |||
| { | |||
| /// <summary> | |||
| /// Gets the id of the app. | |||
| /// </summary> | |||
| ulong Id { get; } | |||
| /// <summary> | |||
| /// Gets the name of the app. | |||
| /// </summary> | |||
| string Name { get; } | |||
| /// <summary> | |||
| /// Gets the icon hash of the app. | |||
| /// </summary> | |||
| string Icon { get; } | |||
| /// <summary> | |||
| /// Gets the description of the app. | |||
| /// </summary> | |||
| string Description { get; } | |||
| /// <summary> | |||
| /// Gets the summary of the app. | |||
| /// </summary> | |||
| string Summary { get; } | |||
| /// <summary> | |||
| /// Gets the bot associated with this application. | |||
| /// </summary> | |||
| IUser Bot { get; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,17 @@ | |||
| namespace Discord | |||
| { | |||
| /// <summary> | |||
| /// The behavior of expiring subscribers for an <see cref="IIntegration" />. | |||
| /// </summary> | |||
| public enum IntegrationExpireBehavior | |||
| { | |||
| /// <summary> | |||
| /// Removes a role from an expired subscriber. | |||
| /// </summary> | |||
| RemoveRole = 0, | |||
| /// <summary> | |||
| /// Kicks an expired subscriber from the guild. | |||
| /// </summary> | |||
| Kick = 1 | |||
| } | |||
| } | |||
| @@ -0,0 +1,17 @@ | |||
| namespace Discord | |||
| { | |||
| /// <summary> | |||
| /// The visibility of the connected account. | |||
| /// </summary> | |||
| public enum ConnectionVisibility | |||
| { | |||
| /// <summary> | |||
| /// Invisible to everyone except the user themselves. | |||
| /// </summary> | |||
| None = 0, | |||
| /// <summary> | |||
| /// Visible to everyone. | |||
| /// </summary> | |||
| Everyone = 1 | |||
| } | |||
| } | |||
| @@ -4,24 +4,53 @@ namespace Discord | |||
| { | |||
| public interface IConnection | |||
| { | |||
| /// <summary> Gets the ID of the connection account. </summary> | |||
| /// <returns> A <see cref="string"/> representing the unique identifier value of this connection. </returns> | |||
| /// <summary> | |||
| /// Gets the ID of the connection account. | |||
| /// </summary> | |||
| /// <returns> | |||
| /// A <see cref="string"/> representing the unique identifier value of this connection. | |||
| /// </returns> | |||
| string Id { get; } | |||
| /// <summary> Gets the service of the connection (twitch, youtube). </summary> | |||
| /// <returns> A string containing the name of this type of connection. </returns> | |||
| string Type { get; } | |||
| /// <summary> Gets the username of the connection account. </summary> | |||
| /// <returns> A string containing the name of this connection. </returns> | |||
| /// <summary> | |||
| /// Gets the username of the connection account. | |||
| /// </summary> | |||
| /// <returns> | |||
| /// A string containing the name of this connection. | |||
| /// </returns> | |||
| string Name { get; } | |||
| /// <summary> Gets whether the connection is revoked. </summary> | |||
| /// <returns> A value which if true indicates that this connection has been revoked, otherwise false. </returns> | |||
| bool IsRevoked { get; } | |||
| /// <summary> Gets a <see cref="IReadOnlyCollection{T}"/> of integration IDs. </summary> | |||
| /// <summary> | |||
| /// Gets the service of the connection (twitch, youtube). | |||
| /// </summary> | |||
| /// <returns> | |||
| /// A string containing the name of this type of connection. | |||
| /// </returns> | |||
| string Type { get; } | |||
| /// <summary> | |||
| /// Gets whether the connection is revoked. | |||
| /// </summary> | |||
| /// <returns> | |||
| /// An <see cref="IReadOnlyCollection{T}"/> containing <see cref="ulong"/> | |||
| /// representations of unique identifier values of integrations. | |||
| /// A value which if true indicates that this connection has been revoked, otherwise false. | |||
| /// </returns> | |||
| IReadOnlyCollection<ulong> IntegrationIds { get; } | |||
| bool? IsRevoked { get; } | |||
| /// <summary> | |||
| /// Gets a <see cref="IReadOnlyCollection{T}"/> of integration parials. | |||
| /// </summary> | |||
| IReadOnlyCollection<IIntegration> Integrations { get; } | |||
| /// <summary> | |||
| /// Gets whether the connection is verified. | |||
| /// </summary> | |||
| bool Verified { get; } | |||
| /// <summary> | |||
| /// Gets whether friend sync is enabled for this connection. | |||
| /// </summary> | |||
| bool FriendSync { get; } | |||
| /// <summary> | |||
| /// Gets whether activities related to this connection will be shown in presence updates. | |||
| /// </summary> | |||
| bool ShowActivity { get; } | |||
| /// <summary> | |||
| /// Visibility of this connection. | |||
| /// </summary> | |||
| ConnectionVisibility Visibility { get; } | |||
| } | |||
| } | |||
| @@ -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<bool> Revoked { get; set; } | |||
| [JsonProperty("integrations")] | |||
| public IReadOnlyCollection<ulong> Integrations { get; set; } | |||
| public Optional<IReadOnlyCollection<Integration>> 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; } | |||
| } | |||
| } | |||
| @@ -5,6 +5,9 @@ namespace Discord.API | |||
| { | |||
| internal class Integration | |||
| { | |||
| [JsonProperty("guild_id")] | |||
| public Optional<ulong> 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<bool?> Syncing { get; set; } | |||
| [JsonProperty("role_id")] | |||
| public ulong RoleId { get; set; } | |||
| public Optional<ulong?> RoleId { get; set; } | |||
| [JsonProperty("enable_emoticons")] | |||
| public Optional<bool?> EnableEmoticons { get; set; } | |||
| [JsonProperty("expire_behavior")] | |||
| public ulong ExpireBehavior { get; set; } | |||
| public Optional<IntegrationExpireBehavior> ExpireBehavior { get; set; } | |||
| [JsonProperty("expire_grace_period")] | |||
| public ulong ExpireGracePeriod { get; set; } | |||
| public Optional<int?> ExpireGracePeriod { get; set; } | |||
| [JsonProperty("user")] | |||
| public User User { get; set; } | |||
| public Optional<User> User { get; set; } | |||
| [JsonProperty("account")] | |||
| public IntegrationAccount Account { get; set; } | |||
| public Optional<IntegrationAccount> Account { get; set; } | |||
| [JsonProperty("synced_at")] | |||
| public DateTimeOffset SyncedAt { get; set; } | |||
| public Optional<DateTimeOffset> SyncedAt { get; set; } | |||
| [JsonProperty("subscriber_count")] | |||
| public Optional<int?> SubscriberAccount { get; set; } | |||
| [JsonProperty("revoked")] | |||
| public Optional<bool?> Revoked { get; set; } | |||
| [JsonProperty("application")] | |||
| public Optional<IntegrationApplication> Application { get; set; } | |||
| } | |||
| } | |||
| @@ -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; } | |||
| } | |||
| @@ -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<string> Icon { get; set; } | |||
| [JsonProperty("description")] | |||
| public string Description { get; set; } | |||
| [JsonProperty("summary")] | |||
| public string Summary { get; set; } | |||
| [JsonProperty("bot")] | |||
| public Optional<User> Bot { get; set; } | |||
| } | |||
| } | |||
| @@ -49,7 +49,7 @@ namespace Discord.Rest | |||
| public static async Task<IReadOnlyCollection<RestConnection>> 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<RestInviteMetadata> GetInviteAsync(BaseDiscordClient client, | |||
| @@ -1626,7 +1626,7 @@ namespace Discord.API | |||
| #region Guild Integrations | |||
| /// <exception cref="ArgumentException"><paramref name="guildId"/> must not be equal to zero.</exception> | |||
| public async Task<IReadOnlyCollection<Integration>> GetGuildIntegrationsAsync(ulong guildId, RequestOptions options = null) | |||
| public async Task<IReadOnlyCollection<Integration>> 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<IReadOnlyCollection<Integration>>("GET", () => $"guilds/{guildId}/integrations", ids, options: options).ConfigureAwait(false); | |||
| } | |||
| /// <exception cref="ArgumentException"><paramref name="guildId"/> and <paramref name="args.Id"/> must not be equal to zero.</exception> | |||
| /// <exception cref="ArgumentNullException"><paramref name="args"/> must not be <see langword="null"/>.</exception> | |||
| public async Task<Integration> 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<Integration>("POST", () => $"guilds/{guildId}/integrations", ids, options: options).ConfigureAwait(false); | |||
| } | |||
| public async Task<Integration> 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<Integration>("DELETE", () => $"guilds/{guildId}/integrations/{integrationId}", ids, options: options).ConfigureAwait(false); | |||
| } | |||
| public async Task<Integration> 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<Integration>("PATCH", () => $"guilds/{guildId}/integrations/{integrationId}", args, ids, options: options).ConfigureAwait(false); | |||
| } | |||
| public async Task<Integration> 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<Integration>("POST", () => $"guilds/{guildId}/integrations/{integrationId}/sync", ids, options: options).ConfigureAwait(false); | |||
| await SendAsync("DELETE", () => $"guilds/{guildId}/integrations/{integrationId}", ids, options: options).ConfigureAwait(false); | |||
| } | |||
| #endregion | |||
| @@ -305,19 +305,15 @@ namespace Discord.Rest | |||
| #endregion | |||
| #region Integrations | |||
| public static async Task<IReadOnlyCollection<RestGuildIntegration>> GetIntegrationsAsync(IGuild guild, BaseDiscordClient client, | |||
| public static async Task<IReadOnlyCollection<RestIntegration>> 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<RestGuildIntegration> 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 | |||
| @@ -720,10 +720,10 @@ namespace Discord.Rest | |||
| #endregion | |||
| #region Integrations | |||
| public Task<IReadOnlyCollection<RestGuildIntegration>> GetIntegrationsAsync(RequestOptions options = null) | |||
| public Task<IReadOnlyCollection<RestIntegration>> GetIntegrationsAsync(RequestOptions options = null) | |||
| => GuildHelper.GetIntegrationsAsync(this, Discord, options); | |||
| public Task<RestGuildIntegration> 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); | |||
| /// <inheritdoc /> | |||
| async Task<IReadOnlyCollection<IGuildIntegration>> IGuild.GetIntegrationsAsync(RequestOptions options) | |||
| async Task<IReadOnlyCollection<IIntegration>> IGuild.GetIntegrationsAsync(RequestOptions options) | |||
| => await GetIntegrationsAsync(options).ConfigureAwait(false); | |||
| /// <inheritdoc /> | |||
| async Task<IGuildIntegration> 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); | |||
| /// <inheritdoc /> | |||
| async Task<IReadOnlyCollection<IInviteMetadata>> IGuild.GetInvitesAsync(RequestOptions options) | |||
| @@ -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<ulong>, IGuildIntegration | |||
| { | |||
| private long _syncedAtTicks; | |||
| /// <inheritdoc /> | |||
| public string Name { get; private set; } | |||
| /// <inheritdoc /> | |||
| public string Type { get; private set; } | |||
| /// <inheritdoc /> | |||
| public bool IsEnabled { get; private set; } | |||
| /// <inheritdoc /> | |||
| public bool IsSyncing { get; private set; } | |||
| /// <inheritdoc /> | |||
| public ulong ExpireBehavior { get; private set; } | |||
| /// <inheritdoc /> | |||
| public ulong ExpireGracePeriod { get; private set; } | |||
| /// <inheritdoc /> | |||
| public ulong GuildId { get; private set; } | |||
| /// <inheritdoc /> | |||
| public ulong RoleId { get; private set; } | |||
| public RestUser User { get; private set; } | |||
| /// <inheritdoc /> | |||
| public IntegrationAccount Account { get; private set; } | |||
| internal IGuild Guild { get; private set; } | |||
| /// <inheritdoc /> | |||
| 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<GuildIntegrationProperties> 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" : "")})"; | |||
| /// <inheritdoc /> | |||
| 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."); | |||
| } | |||
| } | |||
| /// <inheritdoc /> | |||
| IUser IGuildIntegration.User => User; | |||
| } | |||
| } | |||
| @@ -0,0 +1,102 @@ | |||
| using System; | |||
| using System.Diagnostics; | |||
| using System.Threading.Tasks; | |||
| using Model = Discord.API.Integration; | |||
| namespace Discord.Rest | |||
| { | |||
| /// <summary> | |||
| /// Represents a Rest-based implementation of <see cref="IIntegration"/>. | |||
| /// </summary> | |||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
| public class RestIntegration : RestEntity<ulong>, IIntegration | |||
| { | |||
| private long? _syncedAtTicks; | |||
| /// <inheritdoc /> | |||
| public string Name { get; private set; } | |||
| /// <inheritdoc /> | |||
| public string Type { get; private set; } | |||
| /// <inheritdoc /> | |||
| public bool IsEnabled { get; private set; } | |||
| /// <inheritdoc /> | |||
| public bool? IsSyncing { get; private set; } | |||
| /// <inheritdoc /> | |||
| public ulong? RoleId { get; private set; } | |||
| /// <inheritdoc /> | |||
| public bool? HasEnabledEmoticons { get; private set; } | |||
| /// <inheritdoc /> | |||
| public IntegrationExpireBehavior? ExpireBehavior { get; private set; } | |||
| /// <inheritdoc /> | |||
| public int? ExpireGracePeriod { get; private set; } | |||
| /// <inheritdoc /> | |||
| IUser IIntegration.User => User; | |||
| /// <inheritdoc /> | |||
| public IIntegrationAccount Account { get; private set; } | |||
| /// <inheritdoc /> | |||
| public DateTimeOffset? SyncedAt => DateTimeUtils.FromTicks(_syncedAtTicks); | |||
| /// <inheritdoc /> | |||
| public int? SubscriberCount { get; private set; } | |||
| /// <inheritdoc /> | |||
| public bool? IsRevoked { get; private set; } | |||
| /// <inheritdoc /> | |||
| 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" : "")})"; | |||
| /// <inheritdoc /> | |||
| public ulong GuildId { get; private set; } | |||
| /// <inheritdoc /> | |||
| 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."); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,29 @@ | |||
| using Model = Discord.API.IntegrationAccount; | |||
| namespace Discord.Rest | |||
| { | |||
| /// <summary> | |||
| /// Represents a Rest-based implementation of <see cref="IIntegrationAccount"/>. | |||
| /// </summary> | |||
| 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; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,39 @@ | |||
| using Model = Discord.API.IntegrationApplication; | |||
| namespace Discord.Rest | |||
| { | |||
| /// <summary> | |||
| /// Represents a Rest-based implementation of <see cref="IIntegrationApplication"/>. | |||
| /// </summary> | |||
| public class RestIntegrationApplication : RestEntity<ulong>, 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); | |||
| } | |||
| } | |||
| } | |||
| @@ -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 | |||
| { | |||
| /// <inheritdoc /> | |||
| public string Id { get; } | |||
| public string Id { get; private set; } | |||
| /// <inheritdoc /> | |||
| public string Type { get; } | |||
| public string Name { get; private set; } | |||
| /// <inheritdoc /> | |||
| public string Name { get; } | |||
| public string Type { get; private set; } | |||
| /// <inheritdoc /> | |||
| public bool IsRevoked { get; } | |||
| public bool? IsRevoked { get; private set; } | |||
| /// <inheritdoc /> | |||
| public IReadOnlyCollection<ulong> IntegrationIds { get; } | |||
| public IReadOnlyCollection<IIntegration> Integrations { get; private set; } | |||
| /// <inheritdoc /> | |||
| public bool Verified { get; private set; } | |||
| /// <inheritdoc /> | |||
| public bool FriendSync { get; private set; } | |||
| /// <inheritdoc /> | |||
| public bool ShowActivity { get; private set; } | |||
| /// <inheritdoc /> | |||
| public ConnectionVisibility Visibility { get; private set; } | |||
| internal RestConnection(string id, string type, string name, bool isRevoked, IReadOnlyCollection<ulong> 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; | |||
| } | |||
| /// <summary> | |||
| @@ -40,6 +63,6 @@ namespace Discord.Rest | |||
| /// Name of the connection. | |||
| /// </returns> | |||
| public override string ToString() => Name; | |||
| private string DebuggerDisplay => $"{Name} ({Id}, {Type}{(IsRevoked ? ", Revoked" : "")})"; | |||
| private string DebuggerDisplay => $"{Name} ({Id}, {Type}{(IsRevoked.GetValueOrDefault() ? ", Revoked" : "")})"; | |||
| } | |||
| } | |||
| @@ -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<ulong> ApplicationID { get; set; } | |||
| } | |||
| } | |||
| @@ -415,6 +415,32 @@ namespace Discord.WebSocket | |||
| #endregion | |||
| #region Integrations | |||
| /// <summary> Fired when an integration is created. </summary> | |||
| public event Func<IIntegration, Task> IntegrationCreated | |||
| { | |||
| add { _integrationCreated.Add(value); } | |||
| remove { _integrationCreated.Remove(value); } | |||
| } | |||
| internal readonly AsyncEvent<Func<IIntegration, Task>> _integrationCreated = new AsyncEvent<Func<IIntegration, Task>>(); | |||
| /// <summary> Fired when an integration is updated. </summary> | |||
| public event Func<IIntegration, Task> IntegrationUpdated | |||
| { | |||
| add { _integrationUpdated.Add(value); } | |||
| remove { _integrationUpdated.Remove(value); } | |||
| } | |||
| internal readonly AsyncEvent<Func<IIntegration, Task>> _integrationUpdated = new AsyncEvent<Func<IIntegration, Task>>(); | |||
| /// <summary> Fired when an integration is deleted. </summary> | |||
| public event Func<IGuild, ulong, Optional<ulong>, Task> IntegrationDeleted | |||
| { | |||
| add { _integrationDeleted.Add(value); } | |||
| remove { _integrationDeleted.Remove(value); } | |||
| } | |||
| internal readonly AsyncEvent<Func<IGuild, ulong, Optional<ulong>, Task>> _integrationDeleted = new AsyncEvent<Func<IGuild, ulong, Optional<ulong>, Task>>(); | |||
| #endregion | |||
| #region Users | |||
| /// <summary> Fired when a user joins a guild. </summary> | |||
| public event Func<SocketGuildUser, Task> UserJoined | |||
| @@ -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<Integration>(_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<Integration>(_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<IntegrationDeletedEvent>(_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": | |||
| { | |||
| @@ -847,10 +847,10 @@ namespace Discord.WebSocket | |||
| #endregion | |||
| #region Integrations | |||
| public Task<IReadOnlyCollection<RestGuildIntegration>> GetIntegrationsAsync(RequestOptions options = null) | |||
| public Task<IReadOnlyCollection<RestIntegration>> GetIntegrationsAsync(RequestOptions options = null) | |||
| => GuildHelper.GetIntegrationsAsync(this, Discord, options); | |||
| public Task<RestGuildIntegration> 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); | |||
| /// <inheritdoc /> | |||
| async Task<IReadOnlyCollection<IGuildIntegration>> IGuild.GetIntegrationsAsync(RequestOptions options) | |||
| async Task<IReadOnlyCollection<IIntegration>> IGuild.GetIntegrationsAsync(RequestOptions options) | |||
| => await GetIntegrationsAsync(options).ConfigureAwait(false); | |||
| /// <inheritdoc /> | |||
| async Task<IGuildIntegration> 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); | |||
| /// <inheritdoc /> | |||
| async Task<IReadOnlyCollection<IInviteMetadata>> IGuild.GetInvitesAsync(RequestOptions options) | |||