diff --git a/src/client/Client.js b/src/client/Client.js index 0c29cff..c5772a0 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -13,6 +13,7 @@ const WebSocketManager = require('./websocket/WebSocketManager'); const { Error, TypeError, RangeError } = require('../errors'); const Discord = require('../index'); const BaseGuildEmojiManager = require('../managers/BaseGuildEmojiManager'); +const BillingManager = require('../managers/BillingManager'); const ChannelManager = require('../managers/ChannelManager'); const ClientSettingManager = require('../managers/ClientSettingManager'); const DeveloperPortalManager = require('../managers/DeveloperPortalManager'); @@ -160,6 +161,12 @@ class Client extends BaseClient { */ this.guilds = new GuildManager(this); + /** + * Manages the API methods + * @type {BillingManager} + */ + this.billing = new BillingManager(this); + /** * All of the sessions of the client * @type {SessionManager} diff --git a/src/managers/BillingManager.js b/src/managers/BillingManager.js new file mode 100644 index 00000000..7055339 --- /dev/null +++ b/src/managers/BillingManager.js @@ -0,0 +1,66 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const BaseManager = require('./BaseManager'); +const GuildBoost = require('../structures/GuildBoost'); + +/** + * Manages the API methods of a data model. + * @extends {CachedManager} + */ +class BillingManager extends BaseManager { + constructor(client) { + super(client); + /** + * All the payment sources of the client + * @type {Collection} + */ + this.paymentSources = new Collection(); + /** + * All the guild boosts of the client + * @type {Collection} + */ + this.guildBoosts = new Collection(); + /** + * The current subscription of the client + * @type {Collection} + */ + this.currentSubscription = new Collection(); + } + + /** + * Fetches all the payment sources of the client + * @returns {Collection} + */ + async fetchPaymentSources() { + // https://discord.com/api/v9/users/@me/billing/payment-sources + const d = await this.client.api.users('@me').billing['payment-sources'].get(); + // ! TODO: Create a PaymentSource class + this.paymentSources = new Collection(d.map(s => [s.id, s])); + return this.paymentSources; + } + + /** + * Fetches all the guild boosts of the client + * @returns {Collection} + */ + async fetchGuildBoosts() { + // https://discord.com/api/v9/users/@me/guilds/premium/subscription-slots + const d = await this.client.api.users('@me').guilds.premium['subscription-slots'].get(); + this.guildBoosts = new Collection(d.map(s => [s.id, new GuildBoost(this.client, s)])); + return this.guildBoosts; + } + + /** + * Fetches the current subscription of the client + * @returns {Collection} + */ + async fetchCurrentSubscription() { + // https://discord.com/api/v9/users/@me/billing/subscriptions + const d = await this.client.api.users('@me').billing.subscriptions.get(); + this.currentSubscription = new Collection(d.map(s => [s.id, s])); + return this.currentSubscription; + } +} + +module.exports = BillingManager; diff --git a/src/structures/GuildBoost.js b/src/structures/GuildBoost.js new file mode 100644 index 00000000..e656cf8 --- /dev/null +++ b/src/structures/GuildBoost.js @@ -0,0 +1,108 @@ +'use strict'; + +const Base = require('./Base'); + +/** + * Represents a guild boost in a guild on Discord. + * @extends {Base} + */ +class GuildBoost extends Base { + constructor(client, data) { + super(client); + this._patch(data); + } + + _patch(data) { + if ('id' in data) { + /** + * The id of the guild boost + * @type {Snowflake} + */ + this.id = data.id; + } + if ('subscription_id' in data) { + /** + * The id of the subscription + * @type {Snowflake} + */ + this.subscriptionId = data.subscription_id; + } + if (typeof data.premium_guild_subscription === 'object') { + /** + * The premium guild subscription id + * @type {?Snowflake} + */ + this.premiumGuildSubscriptionId = data.premium_guild_subscription.id; + /** + * Guild id + * @type {?Snowflake} + */ + this.guildId = data.premium_guild_subscription.guild_id; + /** + * Ended ??? + * @type {?boolean} + */ + this.ended = data.premium_guild_subscription.ended; + } + if ('canceled' in data) { + /** + * Whether the subscription is canceled + * @type {boolean} + */ + this.canceled = data.canceled; + } + if ('cooldown_ends_at' in data) { + /** + * The cooldown end date + * @type {Date} + */ + this.cooldownEndsAt = new Date(data.cooldown_ends_at); + } + } + /** + * The guild of the boost + * @type {?Guild} + * @readonly + */ + get guilld() { + return this.client.guilds.cache.get(this.guildId); + } + + /** + * Cancel the boost + * @returns {Promise} + */ + async unsubscribe() { + // https://discord.com/api/v9/guilds/:id/premium/subscriptions/:id + if (!this.guildId) throw new Error('BOOST_UNUSED'); + if (!this.premiumGuildSubscriptionId) throw new Error('BOOST_UNCACHED'); + await this.client.api.guilds(this.guildId).premium.subscriptions(this.premiumGuildSubscriptionId).delete(); + this.guildId = null; + this.premiumGuildSubscriptionId = null; + this.ended = null; + return this; + } + + /** + * Use the boost + * @param {GuildResolvable} guild The guild to use the boost on + * @returns {Promise} + */ + async subscribe(guild) { + // https://discord.com/api/v9/guilds/:id/premium/subscriptions + if (this.guildId || this.premiumGuildSubscriptionId) throw new Error('BOOST_USED'); + const id = this.client.guilds.resolveId(guild); + if (!id) throw new Error('UNKNOWN_GUILD'); + const d = await this.client.api.guilds(id).premium.subscriptions.put({ + data: { + user_premium_guild_subscription_slot_ids: [this.id], + }, + }); + this._patch({ + premium_guild_subscription: d, + }); + return this; + } +} + +module.exports = GuildBoost; diff --git a/typings/index.d.ts b/typings/index.d.ts index 7bb9b1a..3202dad 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -178,6 +178,30 @@ export class SessionManager extends CachedManager { public logoutAllDevices(mfaCode?: string): Promise; } +export class BillingManager extends BaseManager { + constructor(client: Client); + public paymentSources: Collection; + public fetchPaymentSources(): Promise>; + public guildBoosts: Collection; + public fetchGuildBoosts(): Promise>; + public currentSubscription: Collection; + public fetchCurrentSubscription(): Promise>; +} + +export class GuildBoost extends Base { + constructor(client: Client, data: object); + public id: Snowflake; + public guildId?: Snowflake; + public readonly guild: Guild | null; + public subscriptionId: Snowflake; + public premiumGuildSubscriptionId?: Snowflake; + public ended?: boolean; + public canceled: boolean; + public cooldownEndsAt: Date; + public unsubscribe(): Promise; + public subscribe(guild: GuildResolvable): Promise; +} + export class Session extends Base { constructor(client: Client); public id?: string; @@ -858,6 +882,8 @@ export class Client extends BaseClient { public relationships: RelationshipManager; public readonly callVoice?: VoiceConnection; public voiceStates: VoiceStateManager; + public sessions: SessionManager; + public billing: BillingManager; // End public channels: ChannelManager; public readonly emojis: BaseGuildEmojiManager;