diff --git a/README.md b/README.md index fe534e74..b6d86a4a 100644 --- a/README.md +++ b/README.md @@ -41,12 +41,12 @@ discord.js is a powerful [Node.js](https://nodejs.org) module that allows you to ## Installation -**Node.js 11.0.0 or newer is required.** +**Node.js 12.0.0 or newer is required.** Ignore any warnings about unmet peer dependencies, as they're all optional. -Without voice support: `npm install discordjs/discord.js` -With voice support ([@discordjs/opus](https://www.npmjs.com/package/@discordjs/opus)): `npm install discordjs/discord.js @discordjs/opus` -With voice support ([opusscript](https://www.npmjs.com/package/opusscript)): `npm install discordjs/discord.js opusscript` +Without voice support: `npm install discord.js` +With voice support ([@discordjs/opus](https://www.npmjs.com/package/@discordjs/opus)): `npm install discord.js @discordjs/opus` +With voice support ([opusscript](https://www.npmjs.com/package/opusscript)): `npm install discord.js opusscript` ### Audio engines diff --git a/docs/examples/avatars.js b/docs/examples/avatars.js index 136fd2e9..57972f57 100644 --- a/docs/examples/avatars.js +++ b/docs/examples/avatars.js @@ -1,3 +1,5 @@ +'use strict'; + /** * Send a user a link to their avatar */ diff --git a/docs/examples/greeting.js b/docs/examples/greeting.js index 314a7598..f61b1c5f 100644 --- a/docs/examples/greeting.js +++ b/docs/examples/greeting.js @@ -1,3 +1,5 @@ +'use strict'; + /** * A bot that welcomes new guild members when they join */ diff --git a/docs/examples/ping.js b/docs/examples/ping.js index 1b14332d..55888e88 100644 --- a/docs/examples/ping.js +++ b/docs/examples/ping.js @@ -1,3 +1,5 @@ +'use strict'; + /** * A ping pong bot, whenever you send "ping", it replies "pong". */ diff --git a/docs/examples/webhook.js b/docs/examples/webhook.js index 77f58c44..451ac58b 100644 --- a/docs/examples/webhook.js +++ b/docs/examples/webhook.js @@ -1,3 +1,5 @@ +'use strict'; + /** * Send a message using a webhook */ diff --git a/docs/general/faq.md b/docs/general/faq.md index b0835890..e265f81c 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 11.0.0 or newer. +Update to Node.js 12.0.0 or newer. ## How do I get voice working? diff --git a/docs/general/updating.md b/docs/general/updating.md index 9c7cff42..85fee04e 100644 --- a/docs/general/updating.md +++ b/docs/general/updating.md @@ -1,3 +1,9 @@ +# Version 12.0.0 + +v12.0.0 contains many new and improved features, optimisations, and bug fixes. +See [the changelog](https://github.com/discordjs/discord.js/releases/tag/12.0.0) for a full list of changes. +You can also visit [the guide](https://discordjs.guide/additional-info/changes-in-v12.html) for help with updating your v11 code to v12. + # Version 11.1.0 v11.1.0 features improved voice and gateway stability, as well as support for new features such as audit logs and searching for messages. diff --git a/docs/general/welcome.md b/docs/general/welcome.md index b3168194..a83717e6 100644 --- a/docs/general/welcome.md +++ b/docs/general/welcome.md @@ -21,9 +21,6 @@ Welcome to the discord.js v12 documentation. -v12 is still very much a work-in-progress, as we're aiming to make it the best it can possibly be before releasing. -Only use it if you are fond of living life on the bleeding edge. - ## About discord.js is a powerful [Node.js](https://nodejs.org) module that allows you to easily interact with the @@ -36,12 +33,12 @@ discord.js is a powerful [Node.js](https://nodejs.org) module that allows you to ## Installation -**Node.js 11.0.0 or newer is required.** +**Node.js 12.0.0 or newer is required.** Ignore any warnings about unmet peer dependencies, as they're all optional. -Without voice support: `npm install discordjs/discord.js` -With voice support ([@discordjs/opus](https://www.npmjs.com/package/@discordjs/opus)): `npm install discordjs/discord.js @discordjs/opus` -With voice support ([opusscript](https://www.npmjs.com/package/opusscript)): `npm install discordjs/discord.js opusscript` +Without voice support: `npm install discord.js` +With voice support ([@discordjs/opus](https://www.npmjs.com/package/@discordjs/opus)): `npm install discord.js @discordjs/opus` +With voice support ([opusscript](https://www.npmjs.com/package/opusscript)): `npm install discord.js opusscript` ### Audio engines diff --git a/package.json b/package.json index 99e61132..f6d32957 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "discord.js", - "version": "12.0.0-dev", + "version": "12.0.0", "description": "A powerful library for interacting with the Discord API", "main": "./src/index", "types": "./typings/index.d.ts", @@ -141,7 +141,9 @@ "*.ts": "prettier --write --single-quote --print-width 120 --trailing-comma all --end-of-line lf" }, "commitlint": { - "extends": ["@commitlint/config-angular"], + "extends": [ + "@commitlint/config-angular" + ], "rules": { "type-enum": [ 2, diff --git a/src/client/Client.js b/src/client/Client.js index c2567137..b63f5a51 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -17,6 +17,7 @@ const Webhook = require('../structures/Webhook'); const Collection = require('../util/Collection'); const { Events, browser, DefaultOptions } = require('../util/Constants'); const DataResolver = require('../util/DataResolver'); +const Intents = require('../util/Intents'); const Permissions = require('../util/Permissions'); const Structures = require('../util/Structures'); @@ -381,6 +382,9 @@ class Client extends BaseClient { * @private */ _validateOptions(options = this.options) { + if (typeof options.ws.intents !== 'undefined') { + options.ws.intents = Intents.resolve(options.ws.intents); + } if (typeof options.shardCount !== 'number' || isNaN(options.shardCount) || options.shardCount < 1) { throw new TypeError('CLIENT_INVALID_OPTION', 'shardCount', 'a number greater than or equal to 1'); } @@ -415,9 +419,6 @@ class Client extends BaseClient { if (typeof options.restSweepInterval !== 'number' || isNaN(options.restSweepInterval)) { throw new TypeError('CLIENT_INVALID_OPTION', 'restSweepInterval', 'a number'); } - if (!Array.isArray(options.disabledEvents)) { - throw new TypeError('CLIENT_INVALID_OPTION', 'disabledEvents', 'an Array'); - } if (typeof options.retryLimit !== 'number' || isNaN(options.retryLimit)) { throw new TypeError('CLIENT_INVALID_OPTION', 'retryLimit', 'a number'); } diff --git a/src/client/voice/util/PlayInterface.js b/src/client/voice/util/PlayInterface.js index 0b08272d..dd812124 100644 --- a/src/client/voice/util/PlayInterface.js +++ b/src/client/voice/util/PlayInterface.js @@ -60,7 +60,8 @@ class PlayInterface { * @returns {StreamDispatcher} */ play(resource, options = {}) { - if (resource instanceof Broadcast) { + const VoiceBroadcast = require('../VoiceBroadcast'); + if (resource instanceof VoiceBroadcast) { if (!this.player.playBroadcast) throw new Error('VOICE_PLAY_INTERFACE_NO_BROADCAST'); return this.player.playBroadcast(resource, options); } @@ -91,6 +92,3 @@ class PlayInterface { } module.exports = PlayInterface; - -// eslint-disable-next-line import/order -const Broadcast = require('../VoiceBroadcast'); diff --git a/src/client/websocket/WebSocketManager.js b/src/client/websocket/WebSocketManager.js index 905626b6..18011063 100644 --- a/src/client/websocket/WebSocketManager.js +++ b/src/client/websocket/WebSocketManager.js @@ -382,7 +382,7 @@ class WebSocketManager extends EventEmitter { }); } - if (packet && !this.client.options.disabledEvents.includes(packet.t) && PacketHandlers[packet.t]) { + if (packet && PacketHandlers[packet.t]) { PacketHandlers[packet.t](this.client, packet, shard); } diff --git a/src/errors/Messages.js b/src/errors/Messages.js index c3e37960..5b4299d4 100644 --- a/src/errors/Messages.js +++ b/src/errors/Messages.js @@ -17,6 +17,8 @@ const Messages = { SHARDING_INVALID: 'Invalid shard settings were provided.', SHARDING_REQUIRED: 'This session would have handled too many guilds - Sharding is required.', + INVALID_INTENTS: 'Invalid intent provided for WebSocket intents.', + DISALLOWED_INTENTS: 'Privileged intent provided is not enabled or whitelisted.', SHARDING_NO_SHARDS: 'No shards have been spawned.', SHARDING_IN_PROCESS: 'Shards are still being spawned.', SHARDING_ALREADY_SPAWNED: count => `Already spawned ${count} shards.`, diff --git a/src/index.js b/src/index.js index 155d2f53..406d4f72 100644 --- a/src/index.js +++ b/src/index.js @@ -17,11 +17,11 @@ module.exports = { Collection: require('./util/Collection'), Constants: require('./util/Constants'), DataResolver: require('./util/DataResolver'), - LimitedCollection: require('./util/LimitedCollection'), BaseManager: require('./managers/BaseManager'), DiscordAPIError: require('./rest/DiscordAPIError'), HTTPError: require('./rest/HTTPError'), MessageFlags: require('./util/MessageFlags'), + Intents: require('./util/Intents'), Permissions: require('./util/Permissions'), Speaking: require('./util/Speaking'), Snowflake: require('./util/Snowflake'), diff --git a/src/managers/GuildManager.js b/src/managers/GuildManager.js index b485c723..42cbcd49 100644 --- a/src/managers/GuildManager.js +++ b/src/managers/GuildManager.js @@ -46,7 +46,7 @@ class GuildManager extends BaseManager { /** * Partial data for a Role. * @typedef {Object} PartialRoleData - * @property {number} id The ID for this role, used to set channel overrides, + * @property {number} [id] The ID for this role, used to set channel overrides, * this is a placeholder and will be replaced by the API after consumption * @property {string} [name] The name of the role * @property {ColorResolvable} [color] The color of the role, either a hex string or a base 10 number diff --git a/src/managers/MessageManager.js b/src/managers/MessageManager.js index 321dc455..2c0db3b7 100644 --- a/src/managers/MessageManager.js +++ b/src/managers/MessageManager.js @@ -21,7 +21,7 @@ class MessageManager extends BaseManager { /** * The cache of Messages - * @type {LimitedCollection} + * @type {Collection} * @name MessageManager#cache */ diff --git a/src/sharding/Shard.js b/src/sharding/Shard.js index 555cef61..39237e0e 100644 --- a/src/sharding/Shard.js +++ b/src/sharding/Shard.js @@ -220,7 +220,7 @@ class Shard extends EventEmitter { * @param {string} prop Name of the client property to get, using periods for nesting * @returns {Promise<*>} * @example - * shard.fetchClientValue('guilds.size') + * shard.fetchClientValue('guilds.cache.size') * .then(count => console.log(`${count} guilds in shard ${shard.id}`)) * .catch(console.error); */ diff --git a/src/structures/GuildMember.js b/src/structures/GuildMember.js index 6a8891b2..f9495174 100644 --- a/src/structures/GuildMember.js +++ b/src/structures/GuildMember.js @@ -212,7 +212,8 @@ class GuildMember extends Base { } /** - * Whether this member is manageable in terms of role hierarchy by the client user + * Whether the client user is above this user in the hierarchy, according to role position and guild ownership. + * This is a prerequisite for many moderative actions. * @type {boolean} * @readonly */ diff --git a/src/structures/interfaces/TextBasedChannel.js b/src/structures/interfaces/TextBasedChannel.js index 501f7c95..2af26c33 100644 --- a/src/structures/interfaces/TextBasedChannel.js +++ b/src/structures/interfaces/TextBasedChannel.js @@ -59,7 +59,7 @@ class TextBasedChannel { * @property {MessageEmbed|Object} [embed] An embed for the message * (see [here](https://discordapp.com/developers/docs/resources/channel#embed-object) for more details) * @property {'none' | 'all' | 'everyone'} [disableMentions=this.client.options.disableMentions] Whether or not - * all mentionsor everyone/here mentions should be sanitized to prevent unexpected mentions + * all mentions or everyone/here mentions should be sanitized to prevent unexpected mentions * @property {FileOptions[]|BufferResolvable[]} [files] Files to send with the message * @property {string|boolean} [code] Language for optional codeblock formatting to apply * @property {boolean|SplitOptions} [split=false] Whether or not the message should be split into multiple messages if diff --git a/src/util/Constants.js b/src/util/Constants.js index 030fe8c4..ed673b5f 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -37,10 +37,6 @@ const browser = (exports.browser = typeof window !== 'undefined'); * (or 0 for never) * @property {number} [retryLimit=1] How many times to retry on 5XX errors (Infinity for indefinite amount of retries) * @property {PresenceData} [presence] Presence data to use upon login - * @property {WSEventType[]} [disabledEvents] An array of disabled websocket events. Events in this array will not be - * processed, potentially resulting in performance improvements for larger bots. Only disable events you are - * 100% certain you don't need, as many are important, but not obviously so. The safest one to disable with the - * most impact is typically `TYPING_START`. * @property {WebsocketOptions} [ws] Options for the WebSocket * @property {HTTPOptions} [http] HTTP options */ @@ -60,7 +56,6 @@ exports.DefaultOptions = { */ replyPrefixer: null, restWsBridgeTimeout: 5000, - disabledEvents: [], restRequestTimeout: 15000, retryLimit: 1, restTimeOffset: 500, @@ -71,6 +66,7 @@ exports.DefaultOptions = { * WebSocket options (these are left as snake_case to match the API) * @typedef {Object} WebsocketOptions * @property {number} [large_threshold=250] Number of members in a guild to be considered large + * @property {IntentsResolvable} [intents] Intents to enable for this connection */ ws: { large_threshold: 250, @@ -108,6 +104,8 @@ exports.WSCodes = { 4004: 'TOKEN_INVALID', 4010: 'SHARDING_INVALID', 4011: 'SHARDING_REQUIRED', + 4013: 'INVALID_INTENTS', + 4014: 'DISALLOWED_INTENTS', }; const AllowedImageFormats = ['webp', 'png', 'jpg', 'gif']; diff --git a/src/util/Intents.js b/src/util/Intents.js new file mode 100644 index 00000000..06ef470b --- /dev/null +++ b/src/util/Intents.js @@ -0,0 +1,76 @@ +'use strict'; +const BitField = require('./BitField'); + +/** + * Data structure that makes it easy to calculate intents. + * @extends {BitField} + */ +class Intents extends BitField {} + +/** + * Data that can be resolved to give a permission number. This can be: + * * A string (see {@link Intents.FLAGS}) + * * An intents flag + * * An instance of Intents + * * An array of IntentsResolvable + * @typedef {string|number|Intents|IntentsResolvable[]} IntentsResolvable + */ + +/** + * Numeric websocket intents. All available properties: + * * `GUILDS` + * * `GUILD_MEMBERS` + * * `GUILD_BANS` + * * `GUILD_EMOJIS` + * * `GUILD_INTEGRATIONS` + * * `GUILD_WEBHOOKS` + * * `GUILD_INVITES` + * * `GUILD_VOICE_STATES` + * * `GUILD_PRESENCES` + * * `GUILD_MESSAGES` + * * `GUILD_MESSAGE_REACTIONS` + * * `GUILD_MESSAGE_TYPING` + * * `DIRECT_MESSAGES` + * * `DIRECT_MESSAGE_REACTIONS` + * * `DIRECT_MESSAGE_TYPING` + * @type {Object} + * @see {@link https://discordapp.com/developers/docs/topics/gateway#list-of-intents} + */ +Intents.FLAGS = { + GUILDS: 1 << 0, + GUILD_MEMBERS: 1 << 1, + GUILD_BANS: 1 << 2, + GUILD_EMOJIS: 1 << 3, + GUILD_INTEGRATIONS: 1 << 4, + GUILD_WEBHOOKS: 1 << 5, + GUILD_INVITES: 1 << 6, + GUILD_VOICE_STATES: 1 << 7, + GUILD_PRESENCES: 1 << 8, + GUILD_MESSAGES: 1 << 9, + GUILD_MESSAGE_REACTIONS: 1 << 10, + GUILD_MESSAGE_TYPING: 1 << 11, + DIRECT_MESSAGES: 1 << 12, + DIRECT_MESSAGE_REACTIONS: 1 << 13, + DIRECT_MESSAGE_TYPING: 1 << 14, +}; + +/** + * Bitfield representing all privileged intents + * @type {number} + * @see {@link https://discordapp.com/developers/docs/topics/gateway#privileged-intents} + */ +Intents.PRIVILEGED = Intents.FLAGS.GUILD_MEMBERS | Intents.FLAGS.GUILD_PRESENCES; + +/** + * Bitfield representing all intents combined + * @type {number} + */ +Intents.ALL = Object.values(Intents.FLAGS).reduce((acc, p) => acc | p, 0); + +/** + * Bitfield representing all non-privileged intents + * @type {number} + */ +Intents.NON_PRIVILEGED = Intents.ALL & ~Intents.PRIVILEGED; + +module.exports = Intents; diff --git a/src/util/LimitedCollection.js b/src/util/LimitedCollection.js index 5719a9fa..caa49780 100644 --- a/src/util/LimitedCollection.js +++ b/src/util/LimitedCollection.js @@ -8,6 +8,7 @@ const Collection = require('./Collection.js'); * @extends {Collection} * @param {number} [maxSize=0] The maximum size of the Collection * @param {Iterable} [iterable=null] Optional entries passed to the Map constructor. + * @private */ class LimitedCollection extends Collection { constructor(maxSize = 0, iterable = null) { @@ -24,6 +25,10 @@ class LimitedCollection extends Collection { if (this.size >= this.maxSize && !this.has(key)) this.delete(this.firstKey()); return super.set(key, value); } + + static get [Symbol.species]() { + return Collection; + } } module.exports = LimitedCollection; diff --git a/test/disableMentions.js b/test/disableMentions.js index 428d605e..97077e20 100644 --- a/test/disableMentions.js +++ b/test/disableMentions.js @@ -5,10 +5,10 @@ const Discord = require('../src'); const { Util } = Discord; const client = new Discord.Client({ - // To see a difference, comment out disableMentions and run the same tests using disableEveryone - // You will notice that all messages will mention @everyone - //disableEveryone: true - disableMentions: 'everyone' + // To see a difference, comment out disableMentions and run the same tests using disableEveryone + // You will notice that all messages will mention @everyone + // disableEveryone: true + disableMentions: 'everyone', }); const tests = [ diff --git a/test/sharder.js b/test/sharder.js index e130128d..195b14bd 100644 --- a/test/sharder.js +++ b/test/sharder.js @@ -1,5 +1,7 @@ -const Discord = require('../'); +'use strict'; + const { token } = require('./auth'); +const Discord = require('../'); const sharder = new Discord.ShardingManager(`${process.cwd()}/test/shard.js`, { token, respawn: false }); diff --git a/typings/index.d.ts b/typings/index.d.ts index b11becce..2a59bceb 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -1,12 +1,12 @@ declare enum ChannelType { - text, - dm, - voice, - group, - category, - news, - store, - unknown, + text = 0, + dm = 1, + voice = 2, + group = 3, + category = 4, + news = 5, + store = 6, + unknown = 7, } declare module 'discord.js' { @@ -133,6 +133,7 @@ declare module 'discord.js' { export class CategoryChannel extends GuildChannel { public readonly children: Collection; + public type: 'category'; } export class Channel extends Base { @@ -552,7 +553,6 @@ declare module 'discord.js' { DARK_BUT_NOT_BLACK: 0x2c2f33; NOT_QUITE_BLACK: 0x23272a; }; - VerificationLevels: ['None', 'Low', 'Medium', '(╯°□°)╯︵ ┻━┻', '┻━┻ ミヽ(ಠ益ಠ)ノ彡┻━┻']; Status: { READY: 0; CONNECTING: 1; @@ -689,6 +689,7 @@ declare module 'discord.js' { public messages: MessageManager; public recipient: User; public readonly partial: false; + public type: 'dm'; public fetch(): Promise; } @@ -861,6 +862,7 @@ declare module 'discord.js' { public readonly permissionsLocked: boolean | null; public readonly position: number; public rawPosition: number; + public type: Exclude; public readonly viewable: boolean; public clone(options?: GuildChannelCloneOptions): Promise; public createInvite(options?: InviteOptions): Promise; @@ -977,6 +979,14 @@ declare module 'discord.js' { public sync(): Promise; } + export class Intents extends BitField { + public static FLAGS: Record; + public static PRIVILEGED: number; + public static ALL: number; + public static NON_PRIVILEGED: number; + public static resolve(bit?: BitFieldResolvable): number; + } + export class Invite extends Base { constructor(client: Client, data: object); public channel: GuildChannel | PartialGroupDMChannel; @@ -1211,6 +1221,7 @@ declare module 'discord.js' { public messages: MessageManager; public nsfw: boolean; public topic: string | null; + public type: 'news'; public createWebhook( name: string, options?: { avatar?: BufferResolvable | Base64Resolvable; reason?: string }, @@ -1546,6 +1557,7 @@ declare module 'discord.js' { constructor(guild: Guild, data?: object); public messages: MessageManager; public nsfw: boolean; + public type: 'text'; public rateLimitPerUser: number; public topic: string | null; public createWebhook( @@ -1654,6 +1666,7 @@ declare module 'discord.js' { public readonly full: boolean; public readonly joinable: boolean; public readonly speakable: boolean; + public type: 'voice'; public userLimit: number; public join(): Promise; public leave(): void; @@ -1901,11 +1914,6 @@ declare module 'discord.js' { public toJSON(): object; } - export class LimitedCollection extends Collection { - public constructor(maxSize: number, iterable: Iterable); - public maxSize: number; - } - //#endregion //#region Managers @@ -2003,7 +2011,7 @@ declare module 'discord.js' { export class MessageManager extends BaseManager { constructor(channel: TextChannel | DMChannel, iterable?: Iterable); public channel: TextBasedChannelFields; - public cache: LimitedCollection; + public cache: Collection; public fetch(message: Snowflake, cache?: boolean): Promise; public fetch(options?: ChannelLogsQueryOptions, cache?: boolean): Promise>; public fetchPinned(cache?: boolean): Promise>; @@ -2293,7 +2301,6 @@ declare module 'discord.js' { restSweepInterval?: number; retryLimit?: number; presence?: PresenceData; - disabledEvents?: WSEventType[]; ws?: WebSocketOptions; http?: HTTPOptions; } @@ -2513,7 +2520,10 @@ declare module 'discord.js' { interface GuildCreateChannelOptions { permissionOverwrites?: OverwriteResolvable[] | Collection; topic?: string; - type?: Exclude; + type?: Exclude< + keyof typeof ChannelType | ChannelType, + 'dm' | 'group' | 'unknown' | ChannelType.dm | ChannelType.group | ChannelType.unknown + >; nsfw?: boolean; parent?: ChannelResolvable; bitrate?: number; @@ -2623,6 +2633,23 @@ declare module 'discord.js' { name: string; } + type IntentsString = + | 'GUILDS' + | 'GUILD_MEMBERS' + | 'GUILD_BANS' + | 'GUILD_EMOJIS' + | 'GUILD_INTEGRATIONS' + | 'GUILD_WEBHOOKS' + | 'GUILD_INVITES' + | 'GUILD_VOICE_STATES' + | 'GUILD_PRESENCES' + | 'GUILD_MESSAGES' + | 'GUILD_MESSAGE_REACTIONS' + | 'GUILD_MESSAGE_TYPING' + | 'DIRECT_MESSAGES' + | 'DIRECT_MESSAGE_REACTIONS' + | 'DIRECT_MESSAGE_TYPING'; + interface InviteOptions { temporary?: boolean; maxAge?: number; @@ -2981,6 +3008,7 @@ declare module 'discord.js' { interface WebSocketOptions { large_threshold?: number; compress?: boolean; + intents?: BitFieldResolvable | number; } type WSEventType =