From c955fd00c7ef5835e022db45ac16d8fe24689455 Mon Sep 17 00:00:00 2001 From: SpaceEEC Date: Sun, 2 Feb 2020 11:11:31 +0100 Subject: [PATCH] feat(Integration): add guild integrations (#3756) --- src/client/rest/RESTMethods.js | 49 +++++++++++ src/index.js | 1 + src/structures/Guild.js | 37 ++++++++ src/structures/Integration.js | 151 +++++++++++++++++++++++++++++++++ src/util/Constants.js | 1 + typings/index.d.ts | 36 ++++++++ 6 files changed, 275 insertions(+) create mode 100644 src/structures/Integration.js diff --git a/src/client/rest/RESTMethods.js b/src/client/rest/RESTMethods.js index 145c0be15863..858d66300ef0 100644 --- a/src/client/rest/RESTMethods.js +++ b/src/client/rest/RESTMethods.js @@ -1009,6 +1009,55 @@ class RESTMethods { patchClientUserGuildSettings(guildID, data) { return this.rest.makeRequest('patch', Constants.Endpoints.User('@me').Guild(guildID).settings, true, data); } + + getIntegrations(guild) { + return this.rest.makeRequest( + 'get', + Constants.Endpoints.Guild(guild.id).integrations, + true + ); + } + + createIntegration(guild, data, reason) { + return this.rest.makeRequest( + 'post', + Constants.Endpoints.Guild(guild.id).integrations, + true, + data, + undefined, + reason + ); + } + + syncIntegration(integration) { + return this.rest.makeRequest( + 'post', + Constants.Endpoints.Guild(integration.guild.id).Integration(integration.id), + true + ); + } + + editIntegration(integration, data, reason) { + return this.rest.makeRequest( + 'patch', + Constants.Endpoints.Guild(integration.guild.id).Integration(integration.id), + true, + data, + undefined, + reason + ); + } + + deleteIntegration(integration, reason) { + return this.rest.makeRequest( + 'delete', + Constants.Endpoints.Guild(integration.guild.id).Integration(integration.id), + true, + undefined, + undefined, + reason + ); + } } module.exports = RESTMethods; diff --git a/src/index.js b/src/index.js index 4624694ad917..47b1a0e82e4b 100644 --- a/src/index.js +++ b/src/index.js @@ -40,6 +40,7 @@ module.exports = { GuildAuditLogs: require('./structures/GuildAuditLogs'), GuildChannel: require('./structures/GuildChannel'), GuildMember: require('./structures/GuildMember'), + Integration: require('./structures/Integration'), Invite: require('./structures/Invite'), Message: require('./structures/Message'), MessageAttachment: require('./structures/MessageAttachment'), diff --git a/src/structures/Guild.js b/src/structures/Guild.js index b5de894549f1..0835114bcd7c 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -5,6 +5,7 @@ const Role = require('./Role'); const Emoji = require('./Emoji'); const Presence = require('./Presence').Presence; const GuildMember = require('./GuildMember'); +const Integration = require('./Integration'); const Constants = require('../util/Constants'); const Collection = require('../util/Collection'); const Util = require('../util/Util'); @@ -634,6 +635,42 @@ class Guild { }); } + /** + * Fetches a collection of integrations to this guild. + * Resolves with a collection mapping integrations by their ids. + * @returns {Promise>} + * @example + * // Fetch integrations + * guild.fetchIntegrations() + * .then(integrations => console.log(`Fetched ${integrations.size} integrations`)) + * .catch(console.error); + */ + fetchIntegrations() { + return this.client.rest.methods.getIntegrations(this).then(data => + data.reduce((collection, integration) => + collection.set(integration.id, new Integration(this.client, integration, this)), + new Collection()) + ); + } + + /** + * The data for creating an integration. + * @typedef {Object} IntegrationData + * @property {string} id The integration id + * @property {string} type The integration type + */ + + /** + * Creates an integration by attaching an integration object + * @param {IntegrationData} data The data for thes integration + * @param {string} reason Reason for creating the integration + * @returns {Promise} + */ + createIntegration(data, reason) { + return this.client.rest.methods.createIntegration(this, data, reason) + .then(() => this); + } + /** * Fetch a collection of invites to this guild. * Resolves with a collection mapping invites by their codes. diff --git a/src/structures/Integration.js b/src/structures/Integration.js new file mode 100644 index 000000000000..96af017e7db0 --- /dev/null +++ b/src/structures/Integration.js @@ -0,0 +1,151 @@ +/** + * The information account for an integration + * @typedef {Object} IntegrationAccount + * @property {string} id The id of the account + * @property {string} name The name of the account + */ + +/** + * Represents a guild integration. + */ +class Integration { + constructor(client, data, guild) { + /** + * The client that created this integration + * @name Integration#client + * @type {Client} + * @readonly + */ + Object.defineProperty(this, 'client', { value: client }); + + /** + * The guild this integration belongs to + * @type {Guild} + */ + this.guild = guild; + + /** + * The integration id + * @type {Snowflake} + */ + this.id = data.id; + + /** + * The integration name + * @type {string} + */ + this.name = data.name; + /** + * The integration type (twitch, youtube, etc) + * @type {string} + */ + this.type = data.type; + + /** + * Whether this integration is enabled + * @type {boolean} + */ + this.enabled = data.enabled; + + /** + * Whether this integration is syncing + * @type {boolean} + */ + this.syncing = data.syncing; + + /** + * The role that this integration uses for subscribers + * @type {Role} + */ + this.role = this.guild.roles.get(data.role_id); + + /** + * The user for this integration + * @type {User} + */ + this.user = this.client.dataManager.newUser(data.user); + + /** + * The account integration information + * @type {IntegrationAccount} + */ + this.account = data.account; + + /** + * The last time this integration was last synced + * @type {number} + */ + this.syncedAt = data.synced_at; + this._patch(data); + } + + _patch(data) { + /** + * The behavior of expiring subscribers + * @type {number} + */ + this.expireBehavior = data.expire_behavior; + + /** + * The grace period before expiring subscribers + * @type {number} + */ + this.expireGracePeriod = data.expire_grace_period; + } + + /** + * Syncs this integration + * @returns {Promise} + */ + sync() { + this.syncing = true; + return this.client.rest.methods.syncIntegration(this) + .then(() => { + this.syncing = false; + this.syncedAt = Date.now(); + return this; + }); + } + + /** + * The data for editing an integration. + * @typedef {Object} IntegrationEditData + * @property {number} [expireBehavior] The new behaviour of expiring subscribers + * @property {number} [expireGracePeriod] The new grace period before expiring subscribers + */ + + /** + * Edits this integration. + * @param {IntegrationEditData} data The data to edit this integration with + * @param {string} reason Reason for editing this integration + * @returns {Promise} + */ + edit(data, reason) { + if ('expireBehavior' in data) { + data.expire_behavior = data.expireBehavior; + data.expireBehavior = undefined; + } + if ('expireGracePeriod' in data) { + data.expire_grace_period = data.expireGracePeriod; + data.expireGracePeriod = undefined; + } + // The option enable_emoticons is only available for Twitch at this moment + return this.client.rest.methods.editIntegration(this, data, reason) + .then(() => { + this._patch(data); + return this; + }); + } + + /** + * Deletes this integration. + * @returns {Promise} + * @param {string} [reason] Reason for deleting this integration + */ + delete(reason) { + return this.client.rest.methods.deleteIntegration(this, reason) + .then(() => this); + } +} + +module.exports = Integration; diff --git a/src/util/Constants.js b/src/util/Constants.js index db274b4cfd65..667e88fbf31f 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -164,6 +164,7 @@ const Endpoints = exports.Endpoints = { nickname: `${base}/members/@me/nick`, }; }, + Integration: id => `${base}/integrations/${id}`, }; }, channels: '/channels', diff --git a/typings/index.d.ts b/typings/index.d.ts index b5b7cdc32075..8dcc99a301af 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -551,6 +551,7 @@ declare module 'discord.js' { public createChannel(name: string, options?: ChannelData): Promise; public createChannel(name: string, type?: 'category' | 'text' | 'voice' | 'news' | 'store', permissionOverwrites?: PermissionOverwrites[] | ChannelCreationOverwrites[], reason?: string): Promise; public createEmoji(attachment: BufferResolvable | Base64Resolvable, name: string, roles?: Collection | Role[], reason?: string): Promise; + public createIntegration(data: IntegrationData, reason?: string): Promise; public createRole(data?: RoleData, reason?: string): Promise; public delete(): Promise; public deleteEmoji(emoji: Emoji | string, reason?: string): Promise; @@ -563,6 +564,7 @@ declare module 'discord.js' { public fetchBans(withReasons: true): Promise>; public fetchBans(withReasons: boolean): Promise>; public fetchEmbed(): Promise; + public fetchIntegrations(): Promise>; public fetchInvites(): Promise>; public fetchMember(user: UserResolvable, cache?: boolean): Promise; public fetchMembers(query?: string, limit?: number): Promise; @@ -715,6 +717,25 @@ declare module 'discord.js' { public toString(): string; } + export class Integration { + constructor(client: Client, data: object, guild: Guild); + public account: IntegrationAccount; + public enabled: boolean; + public expireBehavior: number; + public expireGracePeriod: number; + public guild: Guild; + public id: Snowflake; + public name: string; + public role: Role; + public syncedAt: number; + public syncing: boolean; + public type: number; + public user: User; + public delete(reason?: string): Promise; + public edit(data: IntegrationEditData, reason?: string): Promise; + public sync(): Promise; + } + export class Invite { constructor(client: Client, data: object); public channel: GuildChannel | PartialGuildChannel; @@ -1929,6 +1950,21 @@ declare module 'discord.js' { cdn?: string; }; + type IntegrationData = { + id: string; + type: string; + } + + type IntegrationEditData = { + expireBehavior?: number; + expireGracePeriod?: number; + } + + type IntegrationAccount = { + id: string; + number: string; + } + type InviteOptions = { temporary?: boolean; maxAge?: number;