Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Application): application flags #5147

Merged
merged 25 commits into from
Mar 31, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 13 additions & 20 deletions src/client/Client.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -346,17 +351,6 @@ class Client extends BaseClient {
return messages;
}

/**
* Obtains the OAuth Application of this bot from Discord.
* @returns {Promise<ClientApplication>}
*/
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
Expand All @@ -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<string>}
* @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',
});

Expand Down
12 changes: 9 additions & 3 deletions src/client/websocket/handlers/READY.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
'use strict';

const ClientApplication = require('../../../structures/ClientApplication');
let ClientUser;

module.exports = (client, { d: data }, shard) => {
if (client.user) {
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) {
guild.shardID = shard.id;
client.guilds.add(guild);
}

if (client.application) {
client.application._patch(data.application);
} else {
client.application = new ClientApplication(client, data.application);
}

shard.checkReady();
};
1 change: 1 addition & 0 deletions src/errors/Messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.',
Expand Down
40 changes: 35 additions & 5 deletions src/structures/ClientApplication.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

const Team = require('./Team');
const Application = require('./interfaces/Application');
const ApplicationFlags = require('../util/ApplicationFlags');

/**
* Represents a Client OAuth2 Application.
Expand All @@ -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;
vaporoxx marked this conversation as resolved.
Show resolved Hide resolved
vaporoxx marked this conversation as resolved.
Show resolved Hide resolved

/**
* 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<ClientApplication>}
*/
async fetch() {
const app = await this.client.api.oauth2.applications('@me').get();
this._patch(app);
return this;
}
}

Expand Down
14 changes: 5 additions & 9 deletions src/structures/IntegrationApplication.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}

Expand Down
14 changes: 7 additions & 7 deletions src/structures/interfaces/Application.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/**
Expand Down Expand Up @@ -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}`);
Expand Down
49 changes: 49 additions & 0 deletions src/util/ApplicationFlags.js
Original file line number Diff line number Diff line change
@@ -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;
2 changes: 1 addition & 1 deletion src/util/MessageFlags.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}
Expand Down
35 changes: 27 additions & 8 deletions typings/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ApplicationAsset[]>;
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<ApplicationFlagsString> {
public static FLAGS: Record<ApplicationFlagsString, number>;
public static resolve(bit?: BitFieldResolvable<ApplicationFlagsString, number>): number;
}

export class Base {
Expand Down Expand Up @@ -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;
Expand All @@ -217,13 +223,12 @@ declare module 'discord.js' {
public voice: ClientVoiceManager;
public ws: WebSocketManager;
public destroy(): void;
public fetchApplication(): Promise<ClientApplication>;
public fetchGuildPreview(guild: GuildResolvable): Promise<GuildPreview>;
public fetchInvite(invite: InviteResolvable): Promise<Invite>;
public fetchGuildTemplate(template: GuildTemplateResolvable): Promise<GuildTemplate>;
public fetchVoiceRegions(): Promise<Collection<string, VoiceRegion>>;
public fetchWebhook(id: Snowflake, token?: string): Promise<Webhook>;
public generateInvite(options?: InviteGenerationOptions): Promise<string>;
public generateInvite(options?: InviteGenerationOptions): string;
public login(token?: string): Promise<string>;
public sweepMessages(lifetime?: number): number;
public toJSON(): object;
Expand Down Expand Up @@ -257,8 +262,11 @@ declare module 'discord.js' {
public botPublic: boolean | null;
public botRequireCodeGrant: boolean | null;
public cover: string | null;
public flags: Readonly<ApplicationFlags>;
public owner: User | Team | null;
public readonly partial: boolean;
vaporoxx marked this conversation as resolved.
Show resolved Hide resolved
public rpcOrigins: string[];
public fetch(): Promise<ClientApplication>;
vaporoxx marked this conversation as resolved.
Show resolved Hide resolved
}

export class ClientUser extends User {
Expand Down Expand Up @@ -2305,6 +2313,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;
Expand Down