From 06e9d86cb3dd11708c9cdd81f15970979e5b090d Mon Sep 17 00:00:00 2001 From: Jan <66554238+vaporox@users.noreply.github.com> Date: Wed, 31 Mar 2021 21:55:12 +0200 Subject: [PATCH] feat(Application): application flags (#5147) Co-authored-by: SpaceEEC Co-authored-by: Vlad Frangu --- src/client/Client.js | 33 +++++++--------- src/client/websocket/handlers/READY.js | 12 ++++-- src/errors/Messages.js | 1 + src/structures/ClientApplication.js | 40 ++++++++++++++++--- src/structures/IntegrationApplication.js | 14 +++---- src/structures/interfaces/Application.js | 14 +++---- src/util/ApplicationFlags.js | 49 ++++++++++++++++++++++++ src/util/MessageFlags.js | 2 +- typings/index.d.ts | 35 +++++++++++++---- 9 files changed, 147 insertions(+), 53 deletions(-) create mode 100644 src/util/ApplicationFlags.js diff --git a/src/client/Client.js b/src/client/Client.js index f38a0c0fb4ba..96bf271f4174 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -10,7 +10,6 @@ const ChannelManager = require('../managers/ChannelManager'); const GuildManager = require('../managers/GuildManager'); const UserManager = require('../managers/UserManager'); const ShardClientUtil = require('../sharding/ShardClientUtil'); -const ClientApplication = require('../structures/ClientApplication'); const GuildPreview = require('../structures/GuildPreview'); const GuildTemplate = require('../structures/GuildTemplate'); const Invite = require('../structures/Invite'); @@ -151,6 +150,12 @@ class Client extends BaseClient { */ this.user = null; + /** + * The application of this bot + * @type {?ClientApplication} + */ + this.application = null; + /** * Time at which the client was last regarded as being in the `READY` state * (each time the client disconnects and successfully reconnects, this will be overwritten) @@ -346,17 +351,6 @@ class Client extends BaseClient { return messages; } - /** - * Obtains the OAuth Application of this bot from Discord. - * @returns {Promise} - */ - fetchApplication() { - return this.api.oauth2 - .applications('@me') - .get() - .then(app => new ClientApplication(this, app)); - } - /** * Obtains a guild preview from Discord, available for all guilds the bot is in and all Discoverable guilds. * @param {GuildResolvable} guild The guild to fetch the preview for @@ -383,24 +377,23 @@ class Client extends BaseClient { /** * Generates a link that can be used to invite the bot to a guild. * @param {InviteGenerationOptions} [options={}] Options for the invite - * @returns {Promise} + * @returns {string} * @example - * client.generateInvite({ + * const link = client.generateInvite({ * permissions: [ * Permissions.FLAGS.SEND_MESSAGES, * Permissions.FLAGS.MANAGE_GUILD, * Permissions.FLAGS.MENTION_EVERYONE, * ], - * }) - * .then(link => console.log(`Generated bot invite link: ${link}`)) - * .catch(console.error); + * }); + * console.log(`Generated bot invite link: ${link}`); */ - async generateInvite(options = {}) { + generateInvite(options = {}) { if (typeof options !== 'object') throw new TypeError('INVALID_TYPE', 'options', 'object', true); + if (!this.application) throw new Error('CLIENT_NOT_READY', 'generate an invite link'); - const application = await this.fetchApplication(); const query = new URLSearchParams({ - client_id: application.id, + client_id: this.application.id, scope: 'bot', }); diff --git a/src/client/websocket/handlers/READY.js b/src/client/websocket/handlers/READY.js index c38b681c4ba6..e0021b2a82ec 100644 --- a/src/client/websocket/handlers/READY.js +++ b/src/client/websocket/handlers/READY.js @@ -1,5 +1,6 @@ 'use strict'; +const ClientApplication = require('../../../structures/ClientApplication'); let ClientUser; module.exports = (client, { d: data }, shard) => { @@ -7,9 +8,8 @@ module.exports = (client, { d: data }, shard) => { client.user._patch(data.user); } else { if (!ClientUser) ClientUser = require('../../../structures/ClientUser'); - const clientUser = new ClientUser(client, data.user); - client.user = clientUser; - client.users.cache.set(clientUser.id, clientUser); + client.user = new ClientUser(client, data.user); + client.users.cache.set(client.user.id, client.user); } for (const guild of data.guilds) { @@ -17,5 +17,11 @@ module.exports = (client, { d: data }, shard) => { client.guilds.add(guild); } + if (client.application) { + client.application._patch(data.application); + } else { + client.application = new ClientApplication(client, data.application); + } + shard.checkReady(); }; diff --git a/src/errors/Messages.js b/src/errors/Messages.js index e1b53de33e54..eed78f1f9aed 100644 --- a/src/errors/Messages.js +++ b/src/errors/Messages.js @@ -6,6 +6,7 @@ const Messages = { CLIENT_INVALID_OPTION: (prop, must) => `The ${prop} option must be ${must}`, CLIENT_INVALID_PROVIDED_SHARDS: 'None of the provided shards were valid.', CLIENT_MISSING_INTENTS: 'Valid intents must be provided for the Client.', + CLIENT_NOT_READY: action => `The client needs to be logged in to ${action}.`, TOKEN_INVALID: 'An invalid token was provided.', TOKEN_MISSING: 'Request to use token, but token was unavailable to the client.', diff --git a/src/structures/ClientApplication.js b/src/structures/ClientApplication.js index 7e35b9ea3d38..20968d742ae0 100644 --- a/src/structures/ClientApplication.js +++ b/src/structures/ClientApplication.js @@ -2,6 +2,7 @@ const Team = require('./Team'); const Application = require('./interfaces/Application'); +const ApplicationFlags = require('../util/ApplicationFlags'); /** * Represents a Client OAuth2 Application. @@ -11,35 +12,64 @@ class ClientApplication extends Application { _patch(data) { super._patch(data); + /** + * The flags this application has + * @type {ApplicationFlags} + */ + this.flags = 'flags' in data ? new ApplicationFlags(data.flags) : this.flags; + /** * The app's cover image * @type {?string} */ - this.cover = data.cover_image || null; + this.cover = data.cover_image ?? this.cover ?? null; /** * The app's RPC origins, if enabled * @type {string[]} */ - this.rpcOrigins = data.rpc_origins || []; + this.rpcOrigins = data.rpc_origins ?? this.rpcOrigins ?? []; /** * If this app's bot requires a code grant when using the OAuth2 flow * @type {?boolean} */ - this.botRequireCodeGrant = typeof data.bot_require_code_grant !== 'undefined' ? data.bot_require_code_grant : null; + this.botRequireCodeGrant = data.bot_require_code_grant ?? this.botRequireCodeGrant ?? null; /** * If this app's bot is public * @type {?boolean} */ - this.botPublic = typeof data.bot_public !== 'undefined' ? data.bot_public : null; + this.botPublic = data.bot_public ?? this.botPublic ?? null; /** * The owner of this OAuth application * @type {?User|Team} */ - this.owner = data.team ? new Team(this.client, data.team) : data.owner ? this.client.users.add(data.owner) : null; + this.owner = data.team + ? new Team(this.client, data.team) + : data.owner + ? this.client.users.add(data.owner) + : this.owner ?? null; + } + + /** + * Whether this application is partial + * @type {boolean} + * @readonly + */ + get partial() { + return !this.name; + } + + /** + * Obtains this application from Discord. + * @returns {Promise} + */ + async fetch() { + const app = await this.client.api.oauth2.applications('@me').get(); + this._patch(app); + return this; } } diff --git a/src/structures/IntegrationApplication.js b/src/structures/IntegrationApplication.js index 40f433abd398..d6372bfd0a22 100644 --- a/src/structures/IntegrationApplication.js +++ b/src/structures/IntegrationApplication.js @@ -10,15 +10,11 @@ class IntegrationApplication extends Application { _patch(data) { super._patch(data); - if (typeof data.bot !== 'undefined') { - /** - * The bot {@link User user} for this application - * @type {?User} - */ - this.bot = this.client.users.add(data.bot); - } else if (!this.bot) { - this.bot = null; - } + /** + * The bot user for this application + * @type {?User} + */ + this.bot = data.bot ? this.client.users.add(data.bot) : this.bot ?? null; } } diff --git a/src/structures/interfaces/Application.js b/src/structures/interfaces/Application.js index 9781bfa73038..14db69e46563 100644 --- a/src/structures/interfaces/Application.js +++ b/src/structures/interfaces/Application.js @@ -25,21 +25,21 @@ class Application extends Base { /** * The name of the app - * @type {string} + * @type {?string} */ - this.name = data.name; + this.name = data.name ?? this.name ?? null; /** * The app's description - * @type {string} + * @type {?string} */ - this.description = data.description; + this.description = data.description ?? this.description ?? null; /** * The app's icon hash - * @type {string} + * @type {?string} */ - this.icon = data.icon; + this.icon = data.icon ?? this.icon ?? null; } /** @@ -108,7 +108,7 @@ class Application extends Base { /** * When concatenated with a string, this automatically returns the application's name instead of the * Oauth2Application object. - * @returns {string} + * @returns {?string} * @example * // Logs: Application name: My App * console.log(`Application name: ${application}`); diff --git a/src/util/ApplicationFlags.js b/src/util/ApplicationFlags.js new file mode 100644 index 000000000000..c3f57356e758 --- /dev/null +++ b/src/util/ApplicationFlags.js @@ -0,0 +1,49 @@ +'use strict'; + +const BitField = require('./BitField'); + +/** + * Data structure that makes it easy to interact with a {@link ClientApplication#flags} bitfield. + * @extends {BitField} + */ +class ApplicationFlags extends BitField {} + +/** + * @name ApplicationFlags + * @kind constructor + * @memberof ApplicationFlags + * @param {BitFieldResolvable} [bits=0] Bit(s) to read from + */ + +/** + * Bitfield of the packed bits + * @type {number} + * @name ApplicationFlags#bitfield + */ + +/** + * Numeric application flags. All available properties: + * * `MANAGED_EMOJI` + * * `GROUP_DM_CREATE` + * * `RPC_HAS_CONNECTED` + * * `GATEWAY_PRESENCE` + * * `FATEWAY_PRESENCE_LIMITED` + * * `GATEWAY_GUILD_MEMBERS` + * * `GATEWAY_GUILD_MEMBERS_LIMITED` + * * `VERIFICATION_PENDING_GUILD_LIMIT` + * * `EMBEDDED` + * @type {Object} + */ +ApplicationFlags.FLAGS = { + MANAGED_EMOJI: 1 << 2, + GROUP_DM_CREATE: 1 << 4, + RPC_HAS_CONNECTED: 1 << 11, + GATEWAY_PRESENCE: 1 << 12, + GATEWAY_PRESENCE_LIMITED: 1 << 13, + GATEWAY_GUILD_MEMBERS: 1 << 14, + GATEWAY_GUILD_MEMBERS_LIMITED: 1 << 15, + VERIFICATION_PENDING_GUILD_LIMIT: 1 << 16, + EMBEDDED: 1 << 17, +}; + +module.exports = ApplicationFlags; diff --git a/src/util/MessageFlags.js b/src/util/MessageFlags.js index fb8d2abb6e66..ca4265555f89 100644 --- a/src/util/MessageFlags.js +++ b/src/util/MessageFlags.js @@ -3,7 +3,7 @@ const BitField = require('./BitField'); /** - * Data structure that makes it easy to interact with an {@link Message#flags} bitfield. + * Data structure that makes it easy to interact with a {@link Message#flags} bitfield. * @extends {BitField} */ class MessageFlags extends BitField {} diff --git a/typings/index.d.ts b/typings/index.d.ts index 7ecec7c8dbbd..2f0935c82e03 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -104,15 +104,20 @@ declare module 'discord.js' { constructor(client: Client, data: object); public readonly createdAt: Date; public readonly createdTimestamp: number; - public description: string; - public icon: string; + public description: string | null; + public icon: string | null; public id: Snowflake; - public name: string; - public coverImage(options?: ImageURLOptions): string; + public name: string | null; + public coverImage(options?: ImageURLOptions): string | null; public fetchAssets(): Promise; - public iconURL(options?: ImageURLOptions): string; + public iconURL(options?: ImageURLOptions): string | null; public toJSON(): object; - public toString(): string; + public toString(): string | null; + } + + export class ApplicationFlags extends BitField { + public static FLAGS: Record; + public static resolve(bit?: BitFieldResolvable): number; } export class Base { @@ -203,6 +208,7 @@ declare module 'discord.js' { private _eval(script: string): any; private _validateOptions(options: ClientOptions): void; + public application: ClientApplication | null; public channels: ChannelManager; public readonly emojis: BaseGuildEmojiManager; public guilds: GuildManager; @@ -217,13 +223,12 @@ declare module 'discord.js' { public voice: ClientVoiceManager; public ws: WebSocketManager; public destroy(): void; - public fetchApplication(): Promise; public fetchGuildPreview(guild: GuildResolvable): Promise; public fetchInvite(invite: InviteResolvable): Promise; public fetchGuildTemplate(template: GuildTemplateResolvable): Promise; public fetchVoiceRegions(): Promise>; public fetchWebhook(id: Snowflake, token?: string): Promise; - public generateInvite(options?: InviteGenerationOptions): Promise; + public generateInvite(options?: InviteGenerationOptions): string; public login(token?: string): Promise; public sweepMessages(lifetime?: number): number; public toJSON(): object; @@ -257,8 +262,11 @@ declare module 'discord.js' { public botPublic: boolean | null; public botRequireCodeGrant: boolean | null; public cover: string | null; + public flags: Readonly; public owner: User | Team | null; + public readonly partial: boolean; public rpcOrigins: string[]; + public fetch(): Promise; } export class ClientUser extends User { @@ -2299,6 +2307,17 @@ declare module 'discord.js' { type: 'BIG' | 'SMALL'; } + type ApplicationFlagsString = + | 'MANAGED_EMOJI' + | 'GROUP_DM_CREATE' + | 'RPC_HAS_CONNECTED' + | 'GATEWAY_PRESENCE' + | 'FATEWAY_PRESENCE_LIMITED' + | 'GATEWAY_GUILD_MEMBERS' + | 'GATEWAY_GUILD_MEMBERS_LIMITED' + | 'VERIFICATION_PENDING_GUILD_LIMIT' + | 'EMBEDDED'; + interface AuditLogChange { key: string; old?: any;