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 16 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 @@ -382,24 +376,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 @@ -5,6 +5,7 @@ const { register } = require('./DJSError');
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_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
100 changes: 71 additions & 29 deletions src/structures/ClientApplication.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,41 +5,83 @@ const Application = require('./interfaces/Application');

/**
* Represents a Client OAuth2 Application.
* <info>The only guaranteed properties are `id` and `flags`. To get the entire
* application, you need to fetch it with {@link ClientApplication#fetch} first.</info>
* @extends {Application}
*/
class ClientApplication extends Application {
_patch(data) {
super._patch(data);

/**
* The app's cover image
* @type {?string}
*/
this.cover = data.cover_image || null;

/**
* The app's RPC origins, if enabled
* @type {string[]}
*/
this.rpcOrigins = data.rpc_origins || [];

/**
* 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;

/**
* If this app's bot is public
* @type {?boolean}
*/
this.botPublic = typeof data.bot_public !== 'undefined' ? data.bot_public : 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;
if ('flags' in data) {
/**
* The flags this application has
* @type {number}
*/
this.flags = data.flags;
}

if ('cover_image' in data) {
/**
* The app's cover image
* @type {string}
*/
this.cover = data.cover_image;
}

if ('rpc_origins' in data) {
/**
* The app's RPC origins, if enabled
* @type {string[]}
*/
this.rpcOrigins = data.rpc_origins;
}

if ('bot_require_code_grant' in data) {
/**
* If this app's bot requires a code grant when using the OAuth2 flow
* @type {boolean}
*/
this.botRequireCodeGrant = data.bot_require_code_grant;
}

if ('bot_public' in data) {
/**
* If this app's bot is public
* @type {boolean}
*/
this.botPublic = data.bot_public;
}

// Discord explicitly sends null for this field, if not owned by a team.
if (data.team) {
/**
* The owner of this OAuth application
* @type {User|Team}
*/
this.owner = new Team(this.client, data.team);
} else if ('owner' in data) {
this.owner = this.client.users.add(data.owner);
}
}

/**
* Whether this application is partial
* @type {boolean}
* @readonly
*/
get partial() {
return 'name' in this;
}

/**
* 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
44 changes: 25 additions & 19 deletions src/structures/interfaces/Application.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,29 @@ class Application extends Base {
*/
this.id = data.id;

/**
* The name of the app
* @type {string}
*/
this.name = data.name;

/**
* The app's description
* @type {string}
*/
this.description = data.description;

/**
* The app's icon hash
* @type {string}
*/
this.icon = data.icon;
if ('name' in data) {
/**
* The name of the app
* @type {string}
vaporoxx marked this conversation as resolved.
Show resolved Hide resolved
*/
this.name = data.name;
}

if ('description' in data) {
/**
* The app's description
* @type {string}
*/
this.description = data.description;
}

if ('icon' in data) {
/**
* The app's icon hash
* @type {?string}
*/
this.icon = data.icon;
}
}

/**
Expand Down Expand Up @@ -108,13 +114,13 @@ 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}`);
*/
toString() {
return this.name;
return this.name ?? null;
}

toJSON() {
Expand Down
27 changes: 15 additions & 12 deletions typings/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,15 +99,15 @@ 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;
public icon?: string | null;
public id: Snowflake;
public name: string;
public coverImage(options?: ImageURLOptions): string;
public name?: string;
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 Base {
Expand Down Expand Up @@ -198,6 +198,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 @@ -211,13 +212,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 @@ -248,11 +248,14 @@ declare module 'discord.js' {
}

export class ClientApplication extends Application {
public botPublic: boolean | null;
public botRequireCodeGrant: boolean | null;
public cover: string | null;
public owner: User | Team | null;
public botPublic?: boolean;
public botRequireCodeGrant?: boolean;
public cover?: string;
public flags: number;
public owner?: User | Team;
public readonly partial: boolean;
vaporoxx marked this conversation as resolved.
Show resolved Hide resolved
public rpcOrigins: string[];
public fetch(): Promise<Required<ClientApplication>>;
}

export class ClientUser extends User {
Expand Down