diff --git a/.eslintrc.json b/.eslintrc.json index d9f2f558..f785763f 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,4 +1,5 @@ { + "root": true, "extends": ["eslint:recommended", "plugin:prettier/recommended"], "plugins": ["import"], "parserOptions": { diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index c3c30e1c..66717370 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -15,10 +15,10 @@ jobs: - name: Checkout repository uses: actions/checkout@master - - name: Install Node v12 + - name: Install Node v14 uses: actions/setup-node@master with: - node-version: 12 + node-version: 14 - name: Install dependencies run: npm ci @@ -35,10 +35,10 @@ jobs: - name: Checkout repository uses: actions/checkout@master - - name: Install Node v12 + - name: Install Node v14 uses: actions/setup-node@master with: - node-version: 12 + node-version: 14 - name: Install dependencies run: npm ci diff --git a/.github/workflows/test-cron.yml b/.github/workflows/test-cron.yml index ec1fdb11..cbfd9eee 100644 --- a/.github/workflows/test-cron.yml +++ b/.github/workflows/test-cron.yml @@ -10,10 +10,10 @@ jobs: - name: Checkout repository uses: actions/checkout@v1 - - name: Install Node v12 + - name: Install Node v14 uses: actions/setup-node@v1 with: - node-version: 12 + node-version: 14 - name: Install dependencies run: npm ci @@ -28,10 +28,10 @@ jobs: - name: Checkout repository uses: actions/checkout@v1 - - name: Install Node v12 + - name: Install Node v14 uses: actions/setup-node@v1 with: - node-version: 12 + node-version: 14 - name: Install dependencies run: npm ci @@ -46,10 +46,10 @@ jobs: - name: Checkout repository uses: actions/checkout@v2 - - name: Install Node v12 + - name: Install Node v14 uses: actions/setup-node@v1 with: - node-version: 12 + node-version: 14 - name: Install dependencies run: npm ci @@ -67,10 +67,10 @@ jobs: - name: Checkout repository uses: actions/checkout@v1 - - name: Install Node v12 + - name: Install Node v14 uses: actions/setup-node@v1 with: - node-version: 12 + node-version: 14 - name: Install dependencies run: npm ci diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 43ab5c09..4a0f3b20 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,10 +8,10 @@ jobs: - name: Checkout repository uses: actions/checkout@v2 - - name: Install Node v12 + - name: Install Node v14 uses: actions/setup-node@v1 with: - node-version: 12 + node-version: 14 - name: Install dependencies run: npm ci @@ -26,10 +26,10 @@ jobs: - name: Checkout repository uses: actions/checkout@v2 - - name: Install Node v12 + - name: Install Node v14 uses: actions/setup-node@v1 with: - node-version: 12 + node-version: 14 - name: Install dependencies run: npm ci @@ -44,10 +44,10 @@ jobs: - name: Checkout repository uses: actions/checkout@v2 - - name: Install Node v12 + - name: Install Node v14 uses: actions/setup-node@v1 with: - node-version: 12 + node-version: 14 - name: Install dependencies run: npm ci @@ -65,10 +65,10 @@ jobs: - name: Checkout repository uses: actions/checkout@v2 - - name: Install Node v12 + - name: Install Node v14 uses: actions/setup-node@v1 with: - node-version: 12 + node-version: 14 - name: Install dependencies run: npm ci diff --git a/README.md b/README.md index 10a10461..146ee1af 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ discord.js is a powerful [Node.js](https://nodejs.org) module that allows you to ## Installation -**Node.js 12.0.0 or newer is required.** +**Node.js 14.0.0 or newer is required.** Ignore any warnings about unmet peer dependencies, as they're all optional. Without voice support: `npm install discord.js` diff --git a/docs/general/faq.md b/docs/general/faq.md index 6624a727..466cac1c 100644 --- a/docs/general/faq.md +++ b/docs/general/faq.md @@ -4,7 +4,7 @@ These questions are some of the most frequently asked. ## No matter what, I get `SyntaxError: Block-scoped declarations (let, const, function, class) not yet supported outside strict mode`‽ -Update to Node.js 12.0.0 or newer. +Update to Node.js 14.0.0 or newer. ## How do I get voice working? diff --git a/docs/general/welcome.md b/docs/general/welcome.md index 58d36129..0dde0486 100644 --- a/docs/general/welcome.md +++ b/docs/general/welcome.md @@ -33,7 +33,7 @@ discord.js is a powerful [Node.js](https://nodejs.org) module that allows you to ## Installation -**Node.js 12.0.0 or newer is required.** +**Node.js 14.0.0 or newer is required.** Ignore any warnings about unmet peer dependencies, as they're all optional. Without voice support: `npm install discord.js` diff --git a/package.json b/package.json index 83bf9031..33a0c78c 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "webpack-cli": "^3.3.12" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.0.0" }, "browser": { "@discordjs/opus": false, @@ -110,9 +110,9 @@ "src/client/voice/receiver/PacketHandler.js": false, "src/client/voice/receiver/Receiver.js": false, "src/client/voice/util/PlayInterface.js": false, - "src/client/voice/util/Secretbox.js": false, "src/client/voice/util/Silence.js": false, - "src/client/voice/util/VolumeInterface.js": false + "src/client/voice/util/VolumeInterface.js": false, + "src/util/Sodium.js": false }, "husky": { "hooks": { diff --git a/src/client/Client.js b/src/client/Client.js index a8ec7e49..8c20286b 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -1,6 +1,7 @@ 'use strict'; const BaseClient = require('./BaseClient'); +const InteractionClient = require('./InteractionClient'); const ActionsManager = require('./actions/ActionsManager'); const ClientVoiceManager = require('./voice/ClientVoiceManager'); const WebSocketManager = require('./websocket/WebSocketManager'); @@ -103,6 +104,12 @@ class Client extends BaseClient { ? ShardClientUtil.singleton(this, process.env.SHARDING_MANAGER_MODE) : null; + /** + * The interaction client. + * @type {InteractionClient} + */ + this.interactionClient = new InteractionClient(options, this); + /** * All of the {@link User} objects that have been cached at any point, mapped by their IDs * @type {UserManager} @@ -166,7 +173,7 @@ class Client extends BaseClient { /** * All custom emojis that the client has access to, mapped by their IDs - * @type {GuildEmojiManager} + * @type {BaseGuildEmojiManager} * @readonly */ get emojis() { diff --git a/src/client/InteractionClient.js b/src/client/InteractionClient.js new file mode 100644 index 00000000..631c5895 --- /dev/null +++ b/src/client/InteractionClient.js @@ -0,0 +1,207 @@ +'use strict'; + +const BaseClient = require('./BaseClient'); +const ApplicationCommand = require('../structures/ApplicationCommand'); +const Interaction = require('../structures/Interaction'); +const { Events, ApplicationCommandOptionType, InteractionType, InteractionResponseType } = require('../util/Constants'); + +let sodium; + +/** + * Interaction client is used for interactions. + * + * @example + * const client = new InteractionClient({ + * token: ABC, + * publicKey: XYZ, + * }); + * + * client.on('interactionCreate', () => { + * // automatically handles long responses + * if (will take a long time) { + * doSomethingLong.then((d) => { + * interaction.reply({ + * content: 'wow that took long', + * }); + * }); + * } else { + * interaction.reply('hi!'); + * } + * }); + * ``` + */ +class InteractionClient extends BaseClient { + /** + * @param {Options} options Options for the client. + * @param {undefined} client For internal use. + */ + constructor(options, client) { + super(options); + + Object.defineProperty(this, 'token', { + value: options.token, + writable: true, + }); + + Object.defineProperty(this, 'clientID', { + value: options.clientID, + writable: true, + }); + + Object.defineProperty(this, 'publicKey', { + value: options.publicKey ? Buffer.from(options.publicKey, 'hex') : undefined, + writable: true, + }); + + // Compat for direct usage + this.client = client || this; + this.interactionClient = this; + } + + /** + * Get registered slash commands. + * @param {Snowflake} [guildID] Optional guild ID. + * @returns {Command[]} + */ + async getCommands(guildID) { + let path = this.client.api.applications('@me'); + if (guildID) { + path = path.guilds(guildID); + } + const commands = await path.commands.get(); + return commands.map(c => new ApplicationCommand(this, c, guildID)); + } + + /** + * Create a command. + * @param {Object} command The command description. + * @param {Snowflake?} guildID Optional guild ID. + * @returns {ApplicationCommand} The created command. + */ + createCommand(command, guildID) { + let path = this.client.api.applications('@me'); + if (guildID) { + path = path.guilds(guildID); + } + const c = path.commands.post({ + data: { + name: command.name, + description: command.description, + options: command.options.map(function m(o) { + return { + type: ApplicationCommandOptionType[o.type], + name: o.name, + description: o.description, + default: o.default, + required: o.required, + choices: o.choices, + options: o.options ? o.options.map(m) : undefined, + }; + }), + }, + }); + return new ApplicationCommand(this, c, guildID); + } + + handle(data) { + switch (data.type) { + case InteractionType.PING: + return { + type: InteractionResponseType.PONG, + }; + case InteractionType.APPLICATION_COMMAND: { + let timedOut = false; + let resolve; + const directPromise = new Promise(r => { + resolve = r; + this.client.setTimeout(() => { + timedOut = true; + r({ + type: InteractionResponseType.ACKNOWLEDGE_WITH_SOURCE, + }); + }, 250); + }); + + const syncHandle = { + acknowledge() { + if (!timedOut) { + resolve({ + type: InteractionResponseType.ACKNOWLEDGE_WITH_SOURCE, + }); + } + }, + reply(resolved) { + if (timedOut) { + return false; + } + resolve({ + type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, + data: resolved.data, + }); + return true; + }, + }; + + const interaction = new Interaction(this.client, data, syncHandle); + + /** + * Emitted when an interaction is created. + * @event Client#interactionCreate + * @param {Interaction} interaction The interaction which was created. + */ + this.client.emit(Events.INTERACTION_CREATE, interaction); + + return directPromise; + } + default: + throw new RangeError('Invalid interaction data'); + } + } + + /** + * An express-like middleware factory which can be used + * with webhook interactions. + * @returns {Function} The middleware function. + */ + middleware() { + return async (req, res) => { + const timestamp = req.get('x-signature-timestamp'); + const signature = req.get('x-signature-ed25519'); + + const chunks = []; + for await (const chunk of req) { + chunks.push(chunk); + } + const body = Buffer.concat(chunks); + + if (sodium === undefined) { + sodium = require('../util/Sodium'); + } + if ( + !sodium.methods.verify( + Buffer.from(signature, 'hex'), + Buffer.concat([Buffer.from(timestamp), body]), + this.publicKey, + ) + ) { + res.status(403).end(); + return; + } + + const data = JSON.parse(body.toString()); + + const result = await this.handle(data); + res.status(200).end(JSON.stringify(result)); + }; + } + + async handleFromGateway(data) { + const result = await this.handle(data); + + await this.client.api.interactions(data.id, data.token).callback.post({ + data: result, + }); + } +} + +module.exports = InteractionClient; diff --git a/src/client/actions/ActionsManager.js b/src/client/actions/ActionsManager.js index 4055795a..31ba210c 100644 --- a/src/client/actions/ActionsManager.js +++ b/src/client/actions/ActionsManager.js @@ -37,6 +37,7 @@ class ActionsManager { this.register(require('./GuildIntegrationsUpdate')); this.register(require('./WebhooksUpdate')); this.register(require('./TypingStart')); + this.register(require('./InteractionCreate')); } register(Action) { diff --git a/src/client/actions/InteractionCreate.js b/src/client/actions/InteractionCreate.js new file mode 100644 index 00000000..c349a0eb --- /dev/null +++ b/src/client/actions/InteractionCreate.js @@ -0,0 +1,15 @@ +'use strict'; + +const Action = require('./Action'); + +class InteractionCreateAction extends Action { + handle(data) { + this.client.interactionClient.handleFromGateway(data).catch(e => { + this.client.emit('error', e); + }); + + return {}; + } +} + +module.exports = InteractionCreateAction; diff --git a/src/client/voice/dispatcher/StreamDispatcher.js b/src/client/voice/dispatcher/StreamDispatcher.js index bb1c7bba..bd41d4f4 100644 --- a/src/client/voice/dispatcher/StreamDispatcher.js +++ b/src/client/voice/dispatcher/StreamDispatcher.js @@ -1,7 +1,7 @@ 'use strict'; const { Writable } = require('stream'); -const secretbox = require('../util/Secretbox'); +const secretbox = require('../../../util/Sodium'); const Silence = require('../util/Silence'); const VolumeInterface = require('../util/VolumeInterface'); diff --git a/src/client/voice/receiver/PacketHandler.js b/src/client/voice/receiver/PacketHandler.js index bf1a220a..807593ff 100644 --- a/src/client/voice/receiver/PacketHandler.js +++ b/src/client/voice/receiver/PacketHandler.js @@ -1,8 +1,8 @@ 'use strict'; const EventEmitter = require('events'); +const sodium = require('../../../util/Sodium'); const Speaking = require('../../../util/Speaking'); -const secretbox = require('../util/Secretbox'); const { SILENCE_FRAME } = require('../util/Silence'); // The delay between packets when a user is considered to have stopped speaking @@ -58,7 +58,7 @@ class PacketHandler extends EventEmitter { } // Open packet - let packet = secretbox.methods.open(buffer.slice(12, end), this.nonce, secret_key); + let packet = sodium.methods.open(buffer.slice(12, end), this.nonce, secret_key); if (!packet) return new Error('Failed to decrypt voice packet'); packet = Buffer.from(packet); diff --git a/src/client/websocket/WebSocketManager.js b/src/client/websocket/WebSocketManager.js index f892985f..586c83d4 100644 --- a/src/client/websocket/WebSocketManager.js +++ b/src/client/websocket/WebSocketManager.js @@ -76,7 +76,7 @@ class WebSocketManager extends EventEmitter { /** * The current status of this WebSocketManager - * @type {number} + * @type {Status} */ this.status = Status.IDLE; diff --git a/src/client/websocket/handlers/INTERACTION_CREATE.js b/src/client/websocket/handlers/INTERACTION_CREATE.js new file mode 100644 index 00000000..5bf30fcc --- /dev/null +++ b/src/client/websocket/handlers/INTERACTION_CREATE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.InteractionCreate.handle(packet.d); +}; diff --git a/src/index.js b/src/index.js index d83e1bfa..fb1a09f6 100644 --- a/src/index.js +++ b/src/index.js @@ -6,6 +6,7 @@ module.exports = { // "Root" classes (starting points) BaseClient: require('./client/BaseClient'), Client: require('./client/Client'), + InteractionClient: require('./client/InteractionClient'), Shard: require('./sharding/Shard'), ShardClientUtil: require('./sharding/ShardClientUtil'), ShardingManager: require('./sharding/ShardingManager'), @@ -58,6 +59,7 @@ module.exports = { // Structures Application: require('./structures/interfaces/Application'), + ApplicationCommand: require('./structures/ApplicationCommand'), Base: require('./structures/Base'), Activity: require('./structures/Presence').Activity, APIMessage: require('./structures/APIMessage'), @@ -80,6 +82,7 @@ module.exports = { GuildPreview: require('./structures/GuildPreview'), GuildTemplate: require('./structures/GuildTemplate'), Integration: require('./structures/Integration'), + Interaction: require('./structures/Interaction'), Invite: require('./structures/Invite'), Message: require('./structures/Message'), MessageAttachment: require('./structures/MessageAttachment'), diff --git a/src/structures/APIMessage.js b/src/structures/APIMessage.js index bd7cc610..c24c87ea 100644 --- a/src/structures/APIMessage.js +++ b/src/structures/APIMessage.js @@ -74,6 +74,16 @@ class APIMessage { return this.target instanceof Message; } + /** + * Whether or not the target is an interaction + * @type {boolean} + * @readonly + */ + get isInteraction() { + const Interaction = require('./Interaction'); + return this.target instanceof Interaction; + } + /** * Makes the content of this message. * @returns {?(string|string[])} @@ -166,6 +176,8 @@ class APIMessage { if (this.isMessage) { // eslint-disable-next-line eqeqeq flags = this.options.flags != null ? new MessageFlags(this.options.flags).bitfield : this.target.flags.bitfield; + } else if (this.isInteraction) { + flags = this.options.ephemeral ? MessageFlags.EPHEMERAL : undefined; } let allowedMentions = diff --git a/src/structures/ApplicationCommand.js b/src/structures/ApplicationCommand.js new file mode 100644 index 00000000..560b95a8 --- /dev/null +++ b/src/structures/ApplicationCommand.js @@ -0,0 +1,102 @@ +'use strict'; + +const Base = require('./Base'); +const { ApplicationCommandOptionType } = require('../util/Constants'); +const Snowflake = require('../util/Snowflake'); + +/** + * Represents an application command, see {@link InteractionClient}. + * @extends {Base} + */ +class ApplicationCommand extends Base { + constructor(client, data, guildID) { + super(client); + + /** + * The ID of the guild this command is part of, if any. + * @type {Snowflake?} + * @readonly + */ + this.guildID = guildID || null; + + this._patch(data); + } + + _patch(data) { + /** + * The ID of this command. + * @type {Snowflake} + * @readonly + */ + this.id = data.id; + + /** + * The ID of the application which owns this command. + * @type {Snowflake} + * @readonly + */ + this.appplicationID = data.application_id; + + /** + * The name of this command. + * @type {string} + * @readonly + */ + this.name = data.name; + + /** + * The description of this command. + * @type {string} + * @readonly + */ + this.description = data.description; + + /** + * The options of this command. + * @type {Object[]} + * @readonly + */ + this.options = data.options.map(function m(o) { + return { + type: ApplicationCommandOptionType[o.type], + name: o.name, + description: o.description, + default: o.default, + required: o.required, + choices: o.choices, + options: o.options ? o.options.map(m) : undefined, + }; + }); + } + + /** + * The timestamp the command was created at. + * @type {number} + * @readonly + */ + get createdTimestamp() { + return Snowflake.deconstruct(this.id).timestamp; + } + + /** + * The time the command was created at. + * @type {Date} + * @readonly + */ + get createdAt() { + return new Date(this.createdTimestamp); + } + + /** + * Delete this command. + */ + async delete() { + let path = this.client.api.applications('@me'); + if (this.guildID) { + path = path.guilds(this.guildID); + } + await path.commands(this.id).delete(); + } +} + +module.exports = ApplicationCommand; diff --git a/src/structures/BaseGuildEmoji.js b/src/structures/BaseGuildEmoji.js index a2007c67..09dd04f3 100644 --- a/src/structures/BaseGuildEmoji.js +++ b/src/structures/BaseGuildEmoji.js @@ -17,7 +17,7 @@ class BaseGuildEmoji extends Emoji { */ this.guild = guild; - this.requireColons = null; + this.requiresColons = null; this.managed = null; this.available = null; diff --git a/src/structures/Guild.js b/src/structures/Guild.js index c9c4fe9f..524ea64c 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -1432,6 +1432,23 @@ class Guild extends Base { .then(() => this); } + /** + * Get the commands associated with this guild. + * @returns {ApplicationCommand[]} A list of commands. + */ + getCommands() { + return this.client.interactionClient.getCommands(this.id); + } + + /** + * Create a command. See {@link InteractionClient}. + * @param {Object} command The command description. + * @returns {ApplicationCommand} The created command. + */ + createCommand(command) { + return this.client.interactionClient.createCommand(command, this.id); + } + /** * Leaves the guild. * @returns {Promise} diff --git a/src/structures/Interaction.js b/src/structures/Interaction.js new file mode 100644 index 00000000..c7e47f06 --- /dev/null +++ b/src/structures/Interaction.js @@ -0,0 +1,133 @@ +'use strict'; + +const APIMessage = require('./APIMessage'); +const Base = require('./Base'); +const Snowflake = require('../util/Snowflake'); + +/** + * Represents an interaction, see {@link InteractionClient}. + * @extends {Base} + */ +class Interaction extends Base { + constructor(client, data, syncHandle) { + super(client); + this.syncHandle = syncHandle; + this._patch(data); + } + + _patch(data) { + /** + * The ID of this interaction. + * @type {Snowflake} + * @readonly + */ + this.id = data.id; + + /** + * The token of this interaction. + * @type {string} + * @readonly + */ + this.token = data.token; + + /** + * The ID of the invoked command. + * @type {Snowflake} + * @readonly + */ + this.commandID = data.data.id; + + /** + * The name of the invoked command. + * @type {string} + * @readonly + */ + this.commandName = data.data.name; + + /** + * The options passed to the command. + * @type {Object} + * @readonly + */ + this.options = data.data.options; + + /** + * The channel this interaction was sent in. + * @type {?Channel} + * @readonly + */ + this.channel = this.client.channels?.cache.get(data.channel_id) || null; + + /** + * The guild this interaction was sent in, if any. + * @type {?Guild} + * @readonly + */ + this.guild = data.guild_id ? this.client.guilds?.cache.get(data.guild_id) : null; + + /** + * If this interaction was sent in a guild, the member which sent it. + * @type {?Member} + * @readonly + */ + this.member = data.member ? this.guild?.members.add(data.member, false) : null; + } + + /** + * The timestamp the interaction was created at. + * @type {number} + * @readonly + */ + get createdTimestamp() { + return Snowflake.deconstruct(this.id).timestamp; + } + + /** + * The time the interaction was created at. + * @type {Date} + * @readonly + */ + get createdAt() { + return new Date(this.createdTimestamp); + } + + /** + * Acknowledge this interaction without content. + */ + async acknowledge() { + await this.syncHandle.acknowledge(); + } + + /** + * Reply to this interaction. + * @param {(StringResolvable | APIMessage)?} content The content for the message. + * @param {(MessageOptions | MessageAdditions)?} options The options to provide. + */ + async reply(content, options) { + let apiMessage; + + if (content instanceof APIMessage) { + apiMessage = content.resolveData(); + } else { + apiMessage = APIMessage.create(this, content, options).resolveData(); + if (Array.isArray(apiMessage.data.content)) { + throw new Error('Message is too long'); + } + } + + const resolved = await apiMessage.resolveFiles(); + + if (!this.syncHandle.reply(resolved)) { + const clientID = + this.client.interactionClient.clientID || (await this.client.api.oauth2.applications('@me').get()).id; + + await this.client.api.webhooks(clientID, this.token).post({ + auth: false, + data: resolved.data, + files: resolved.files, + }); + } + } +} + +module.exports = Interaction; diff --git a/src/structures/User.js b/src/structures/User.js index d7ebe03f..2811e862 100644 --- a/src/structures/User.js +++ b/src/structures/User.js @@ -278,7 +278,7 @@ class User extends Base { /** * Fetches this user's flags. - * @param {boolean} [force=false] Whether to skip the cache check and request the AP + * @param {boolean} [force=false] Whether to skip the cache check and request the API * @returns {Promise} */ async fetchFlags(force = false) { @@ -290,7 +290,7 @@ class User extends Base { /** * Fetches this user. - * @param {boolean} [force=false] Whether to skip the cache check and request the AP + * @param {boolean} [force=false] Whether to skip the cache check and request the API * @returns {Promise} */ fetch(force = false) { diff --git a/src/structures/interfaces/Collector.js b/src/structures/interfaces/Collector.js index 969b3d2c..e8745385 100644 --- a/src/structures/interfaces/Collector.js +++ b/src/structures/interfaces/Collector.js @@ -1,6 +1,7 @@ 'use strict'; const EventEmitter = require('events'); +const { TypeError } = require('../../errors'); const Collection = require('../../util/Collection'); const Util = require('../../util/Util'); @@ -74,6 +75,10 @@ class Collector extends EventEmitter { */ this._idletimeout = null; + if (typeof filter !== 'function') { + throw new TypeError('INVALID_TYPE', 'filter', 'function'); + } + this.handleCollect = this.handleCollect.bind(this); this.handleDispose = this.handleDispose.bind(this); diff --git a/src/util/Constants.js b/src/util/Constants.js index 1d84a51e..0429fa12 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -77,14 +77,14 @@ exports.DefaultOptions = { /** * HTTP options * @typedef {Object} HTTPOptions - * @property {number} [version=7] API version to use + * @property {number} [version=8] API version to use * @property {string} [api='https://discord.com/api'] Base url of the API * @property {string} [cdn='https://cdn.discordapp.com'] Base url of the CDN * @property {string} [invite='https://discord.gg'] Base url of invites * @property {string} [template='https://discord.new'] Base url of templates */ http: { - version: 7, + version: 8, api: 'https://discord.com/api', cdn: 'https://cdn.discordapp.com', invite: 'https://discord.gg', @@ -282,6 +282,7 @@ exports.Events = { SHARD_READY: 'shardReady', SHARD_RESUME: 'shardResume', INVALIDATED: 'invalidated', + INTERACTION_CREATE: 'interactionCreate', RAW: 'raw', }; @@ -345,6 +346,7 @@ exports.PartialTypes = keyMirror(['USER', 'CHANNEL', 'GUILD_MEMBER', 'MESSAGE', * * VOICE_STATE_UPDATE * * VOICE_SERVER_UPDATE * * WEBHOOKS_UPDATE + * * INTERACTION_CREATE * @typedef {string} WSEventType */ exports.WSEvents = keyMirror([ @@ -384,6 +386,7 @@ exports.WSEvents = keyMirror([ 'VOICE_STATE_UPDATE', 'VOICE_SERVER_UPDATE', 'WEBHOOKS_UPDATE', + 'INTERACTION_CREATE', ]); /** @@ -676,6 +679,33 @@ exports.WebhookTypes = [ 'Channel Follower', ]; +exports.ApplicationCommandOptionType = { + SUB_COMMAND: 1, + SUB_COMMAND_GROUP: 2, + STRING: 3, + INTEGER: 4, + BOOLEAN: 5, + USER: 6, + CHANNEL: 7, + ROLE: 8, +}; +Object.entries(exports.ApplicationCommandOptionType).forEach(([k, v]) => { + exports.ApplicationCommandOptionType[v] = k; +}); + +exports.InteractionType = { + PING: 1, + APPLICATION_COMMAND: 2, +}; + +exports.InteractionResponseType = { + PONG: 1, + ACKNOWLEDGE: 2, + CHANNEL_MESSAGE: 3, + CHANNEL_MESSAGE_WITH_SOURCE: 4, + ACKNOWLEDGE_WITH_SOURCE: 5, +}; + function keyMirror(arr) { let tmp = Object.create(null); for (const value of arr) tmp[value] = value; diff --git a/src/util/MessageFlags.js b/src/util/MessageFlags.js index 536cbd88..e693158e 100644 --- a/src/util/MessageFlags.js +++ b/src/util/MessageFlags.js @@ -22,6 +22,7 @@ class MessageFlags extends BitField {} * * `SUPPRESS_EMBEDS` * * `SOURCE_MESSAGE_DELETED` * * `URGENT` + * * `EPHEMERAL` * @type {Object} * @see {@link https://discord.com/developers/docs/resources/channel#message-object-message-flags} */ @@ -31,6 +32,7 @@ MessageFlags.FLAGS = { SUPPRESS_EMBEDS: 1 << 2, SOURCE_MESSAGE_DELETED: 1 << 3, URGENT: 1 << 4, + EPHEMERAL: 1 << 6, }; module.exports = MessageFlags; diff --git a/src/client/voice/util/Secretbox.js b/src/util/Sodium.js similarity index 84% rename from src/client/voice/util/Secretbox.js rename to src/util/Sodium.js index c16a4353..e19b5b9a 100644 --- a/src/client/voice/util/Secretbox.js +++ b/src/util/Sodium.js @@ -5,16 +5,19 @@ const libs = { open: sodium.api.crypto_secretbox_open_easy, close: sodium.api.crypto_secretbox_easy, random: n => sodium.randombytes_buf(n), + verify: sodium.api.crypto_sign_verify_detached, }), 'libsodium-wrappers': sodium => ({ open: sodium.crypto_secretbox_open_easy, close: sodium.crypto_secretbox_easy, random: n => sodium.randombytes_buf(n), + verify: sodium.crypto_sign_verify_detached, }), tweetnacl: tweetnacl => ({ open: tweetnacl.secretbox.open, close: tweetnacl.secretbox, random: n => tweetnacl.randomBytes(n), + verify: (s, d, p) => tweetnacl.sign.detached.verify(d, s, p), }), };