diff options
Diffstat (limited to 'node_modules/discord.js')
187 files changed, 24672 insertions, 0 deletions
diff --git a/node_modules/discord.js/.tern-project b/node_modules/discord.js/.tern-project new file mode 100644 index 0000000..f5ce948 --- /dev/null +++ b/node_modules/discord.js/.tern-project @@ -0,0 +1,17 @@ +{ + "ecmaVersion": 7, + "libs": [], + "loadEagerly": ["./src/*.js"], + "dontLoad": ["node_modules/**"], + "plugins": { + "es_modules": {}, + "node": {}, + "doc_comment": { + "fullDocs": true, + "strong": true + }, + "webpack": { + "configPath": "./webpack.config.js" + } + } +} diff --git a/node_modules/discord.js/LICENSE b/node_modules/discord.js/LICENSE new file mode 100644 index 0000000..9997d13 --- /dev/null +++ b/node_modules/discord.js/LICENSE @@ -0,0 +1,190 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2015 - 2020 Amish Shah + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/node_modules/discord.js/README.md b/node_modules/discord.js/README.md new file mode 100644 index 0000000..b6d86a4 --- /dev/null +++ b/node_modules/discord.js/README.md @@ -0,0 +1,111 @@ +<div align="center"> + <br /> + <p> + <a href="https://discord.js.org"><img src="https://discord.js.org/static/logo.svg" width="546" alt="discord.js" /></a> + </p> + <br /> + <p> + <a href="https://discord.gg/bRCvFy9"><img src="https://discordapp.com/api/guilds/222078108977594368/embed.png" alt="Discord server" /></a> + <a href="https://www.npmjs.com/package/discord.js"><img src="https://img.shields.io/npm/v/discord.js.svg?maxAge=3600" alt="NPM version" /></a> + <a href="https://www.npmjs.com/package/discord.js"><img src="https://img.shields.io/npm/dt/discord.js.svg?maxAge=3600" alt="NPM downloads" /></a> + <a href="https://github.com/discordjs/discord.js/actions"><img src="https://github.com/discordjs/discord.js/workflows/Testing/badge.svg" alt="Build status" /></a> + <a href="https://david-dm.org/discordjs/discord.js"><img src="https://img.shields.io/david/discordjs/discord.js.svg?maxAge=3600" alt="Dependencies" /></a> + <a href="https://www.patreon.com/discordjs"><img src="https://img.shields.io/badge/donate-patreon-F96854.svg" alt="Patreon" /></a> + </p> + <p> + <a href="https://nodei.co/npm/discord.js/"><img src="https://nodei.co/npm/discord.js.png?downloads=true&stars=true" alt="npm installnfo" /></a> + </p> +</div> + +## Table of contents + +- [About](#about) +- [Installation](#installation) + - [Audio engines](#audio-engines) + - [Optional packages](#optional-packages) +- [Example Usage](#example-usage) +- [Links](#links) + - [Extensions](#extensions) +- [Contributing](#contributing) +- [Help](#help) + +## About + +discord.js is a powerful [Node.js](https://nodejs.org) module that allows you to easily interact with the +[Discord API](https://discordapp.com/developers/docs/intro). + +- Object-oriented +- Predictable abstractions +- Performant +- 100% coverage of the Discord API + +## Installation + +**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 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 + +The preferred audio engine is @discordjs/opus, as it performs significantly better than opusscript. When both are available, discord.js will automatically choose @discordjs/opus. +Using opusscript is only recommended for development environments where @discordjs/opus is tough to get working. +For production bots, using @discordjs/opus should be considered a necessity, especially if they're going to be running on multiple servers. + +### Optional packages + +- [zlib-sync](https://www.npmjs.com/package/zlib-sync) for WebSocket data compression and inflation (`npm install zlib-sync`) +- [erlpack](https://github.com/discordapp/erlpack) for significantly faster WebSocket data (de)serialisation (`npm install discordapp/erlpack`) +- One of the following packages can be installed for faster voice packet encryption and decryption: + - [sodium](https://www.npmjs.com/package/sodium) (`npm install sodium`) + - [libsodium.js](https://www.npmjs.com/package/libsodium-wrappers) (`npm install libsodium-wrappers`) +- [bufferutil](https://www.npmjs.com/package/bufferutil) for a much faster WebSocket connection (`npm install bufferutil`) +- [utf-8-validate](https://www.npmjs.com/package/utf-8-validate) in combination with `bufferutil` for much faster WebSocket processing (`npm install utf-8-validate`) + +## Example usage + +```js +const Discord = require('discord.js'); +const client = new Discord.Client(); + +client.on('ready', () => { + console.log(`Logged in as ${client.user.tag}!`); +}); + +client.on('message', msg => { + if (msg.content === 'ping') { + msg.reply('pong'); + } +}); + +client.login('token'); +``` + +## Links + +- [Website](https://discord.js.org/) ([source](https://github.com/discordjs/website)) +- [Documentation](https://discord.js.org/#/docs/main/master/general/welcome) +- [Guide](https://discordjs.guide/) ([source](https://github.com/discordjs/guide)) - this is still for stable + See also the [Update Guide](https://discordjs.guide/additional-info/changes-in-v12.html), including updated and removed items in the library. +- [Discord.js Discord server](https://discord.gg/bRCvFy9) +- [Discord API Discord server](https://discord.gg/discord-api) +- [GitHub](https://github.com/discordjs/discord.js) +- [NPM](https://www.npmjs.com/package/discord.js) +- [Related libraries](https://discordapi.com/unofficial/libs.html) + +### Extensions + +- [RPC](https://www.npmjs.com/package/discord-rpc) ([source](https://github.com/discordjs/RPC)) + +## Contributing + +Before creating an issue, please ensure that it hasn't already been reported/suggested, and double-check the +[documentation](https://discord.js.org/#/docs). +See [the contribution guide](https://github.com/discordjs/discord.js/blob/master/.github/CONTRIBUTING.md) if you'd like to submit a PR. + +## Help + +If you don't understand something in the documentation, you are experiencing problems, or you just need a gentle +nudge in the right direction, please don't hesitate to join our official [Discord.js Server](https://discord.gg/bRCvFy9). diff --git a/node_modules/discord.js/esm/discord.mjs b/node_modules/discord.js/esm/discord.mjs new file mode 100644 index 0000000..29062fb --- /dev/null +++ b/node_modules/discord.js/esm/discord.mjs @@ -0,0 +1,89 @@ +import Discord from '../src/index.js'; + +export default Discord; + +export const { + BaseClient, + Client, + Shard, + ShardClientUtil, + ShardingManager, + WebhookClient, + ActivityFlags, + BitField, + Collection, + Constants, + DataResolver, + BaseManager, + DiscordAPIError, + HTTPError, + MessageFlags, + Intents, + Permissions, + Speaking, + Snowflake, + SnowflakeUtil, + Structures, + SystemChannelFlags, + Util, + version, + ChannelManager, + GuildChannelManager, + GuildEmojiManager, + GuildEmojiRoleManager, + GuildMemberManager, + GuildMemberRoleManager, + GuildManager, + ReactionUserManager, + MessageManager, + PresenceManager, + RoleManager, + UserManager, + discordSort, + escapeMarkdown, + fetchRecommendedShards, + resolveColor, + resolveString, + splitMessage, + Base, + Activity, + APIMessage, + CategoryChannel, + Channel, + ClientApplication, + ClientUser, + Collector, + DMChannel, + Emoji, + Guild, + GuildAuditLogs, + GuildChannel, + GuildEmoji, + GuildMember, + Integration, + Invite, + Message, + MessageAttachment, + MessageCollector, + MessageEmbed, + MessageMentions, + MessageReaction, + NewsChannel, + PermissionOverwrites, + Presence, + ClientPresence, + ReactionCollector, + ReactionEmoji, + RichPresenceAssets, + Role, + StoreChannel, + Team, + TeamMember, + TextChannel, + User, + VoiceChannel, + VoiceRegion, + VoiceState, + Webhook, + WebSocket +} = Discord; diff --git a/node_modules/discord.js/jsdoc.json b/node_modules/discord.js/jsdoc.json new file mode 100644 index 0000000..3bf2d09 --- /dev/null +++ b/node_modules/discord.js/jsdoc.json @@ -0,0 +1,3 @@ +{ + "plugins": ["node_modules/jsdoc-strip-async-await"] +} diff --git a/node_modules/discord.js/package.json b/node_modules/discord.js/package.json new file mode 100644 index 0000000..03095ee --- /dev/null +++ b/node_modules/discord.js/package.json @@ -0,0 +1,213 @@ +{ + "_from": "discord.js", + "_id": "[email protected]", + "_inBundle": false, + "_integrity": "sha512-Ueb/0SOsxXyqwvwFYFe0msMrGqH1OMqpp2Dpbplnlr4MzcRrFWwsBM9gKNZXPVBHWUKiQkwU8AihXBXIvTTSvg==", + "_location": "/discord.js", + "_phantomChildren": {}, + "_requested": { + "type": "tag", + "registry": true, + "raw": "discord.js", + "name": "discord.js", + "escapedName": "discord.js", + "rawSpec": "", + "saveSpec": null, + "fetchSpec": "latest" + }, + "_requiredBy": [ + "#USER", + "/" + ], + "_resolved": "https://registry.npmjs.org/discord.js/-/discord.js-12.2.0.tgz", + "_shasum": "31018732e42a495c92655055192221eab2ad11a9", + "_spec": "discord.js", + "_where": "E:\\Documents\\GitHub\\uppity", + "author": { + "name": "Amish Shah", + "email": "[email protected]" + }, + "browser": { + "@discordjs/opus": false, + "https": false, + "ws": false, + "erlpack": false, + "prism-media": false, + "opusscript": false, + "node-opus": false, + "tweetnacl": false, + "sodium": false, + "worker_threads": false, + "zlib-sync": false, + "src/sharding/Shard.js": false, + "src/sharding/ShardClientUtil.js": false, + "src/sharding/ShardingManager.js": false, + "src/client/voice/ClientVoiceManager.js": false, + "src/client/voice/VoiceBroadcast.js": false, + "src/client/voice/VoiceConnection.js": false, + "src/client/voice/dispatcher/BroadcastDispatcher.js": false, + "src/client/voice/dispatcher/StreamDispatcher.js": false, + "src/client/voice/networking/VoiceUDPClient.js": false, + "src/client/voice/networking/VoiceWebSocket.js": false, + "src/client/voice/player/AudioPlayer.js": false, + "src/client/voice/player/BasePlayer.js": false, + "src/client/voice/player/BroadcastAudioPlayer.js": false, + "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 + }, + "bugs": { + "url": "https://github.com/discordjs/discord.js/issues" + }, + "bundleDependencies": false, + "commitlint": { + "extends": [ + "@commitlint/config-angular" + ], + "rules": { + "scope-case": [ + 2, + "always", + "pascal-case" + ], + "type-enum": [ + 2, + "always", + [ + "chore", + "build", + "ci", + "docs", + "feat", + "fix", + "perf", + "refactor", + "revert", + "style", + "test" + ] + ] + } + }, + "dependencies": { + "@discordjs/collection": "^0.1.5", + "@discordjs/form-data": "^3.0.1", + "abort-controller": "^3.0.0", + "node-fetch": "^2.6.0", + "prism-media": "^1.2.0", + "setimmediate": "^1.0.5", + "tweetnacl": "^1.0.3", + "ws": "^7.2.1" + }, + "deprecated": false, + "description": "A powerful library for interacting with the Discord API", + "devDependencies": { + "@commitlint/cli": "^8.3.5", + "@commitlint/config-angular": "^8.3.4", + "@types/node": "^10.12.24", + "@types/ws": "^7.2.1", + "cross-env": "^7.0.2", + "discord.js-docgen": "github:discordjs/docgen", + "dtslint": "^3.0.0", + "eslint": "^6.8.0", + "eslint-config-prettier": "^6.10.0", + "eslint-plugin-import": "^2.20.1", + "eslint-plugin-prettier": "^3.1.2", + "husky": "^4.2.3", + "jest": "^25.1.0", + "json-filter-loader": "^1.0.0", + "lint-staged": "^10.0.8", + "prettier": "^1.19.1", + "terser-webpack-plugin": "^1.2.2", + "tslint": "^6.0.0", + "typescript": "^3.8.2", + "webpack": "^4.41.6", + "webpack-cli": "^3.3.11" + }, + "engines": { + "node": ">=12.0.0" + }, + "exports": { + ".": [ + { + "require": "./src/index.js", + "import": "./esm/discord.mjs" + }, + "./src/index.js" + ], + "./esm": "./esm/discord.mjs" + }, + "homepage": "https://github.com/discordjs/discord.js#readme", + "husky": { + "hooks": { + "pre-commit": "lint-staged", + "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" + } + }, + "keywords": [ + "discord", + "api", + "bot", + "client", + "node", + "discordapp" + ], + "license": "Apache-2.0", + "lint-staged": { + "*.js": "eslint --fix", + "*.ts": "prettier --write --single-quote --print-width 120 --trailing-comma all --end-of-line lf" + }, + "main": "./src/index", + "name": "discord.js", + "peerDependencies": { + "bufferutil": "^4.0.1", + "erlpack": "discordapp/erlpack", + "libsodium-wrappers": "^0.7.6", + "sodium": "^3.0.2", + "utf-8-validate": "^5.0.2", + "zlib-sync": "^0.1.6" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "erlpack": { + "optional": true + }, + "libsodium-wrappers": { + "optional": true + }, + "sodium": { + "optional": true + }, + "utf-8-validate": { + "optional": true + }, + "zlib-sync": { + "optional": true + } + }, + "repository": { + "type": "git", + "url": "git+https://github.com/discordjs/discord.js.git" + }, + "runkitExampleFilename": "./docs/examples/ping.js", + "scripts": { + "build:browser": "webpack", + "docs": "docgen --source src --custom docs/index.yml --output docs/docs.json", + "docs:test": "docgen --source src --custom docs/index.yml", + "lint": "eslint src", + "lint:fix": "eslint src --fix", + "lint:typings": "tslint typings/index.d.ts", + "prepublishOnly": "npm run test && cross-env NODE_ENV=production npm run build:browser", + "prettier": "prettier --write --single-quote --print-width 120 --trailing-comma all --end-of-line lf src/**/*.js typings/**/*.ts", + "test": "npm run lint && npm run docs:test && npm run lint:typings", + "test:typescript": "tsc" + }, + "types": "./typings/index.d.ts", + "unpkg": "./webpack/discord.min.js", + "version": "12.2.0" +} diff --git a/node_modules/discord.js/src/WebSocket.js b/node_modules/discord.js/src/WebSocket.js new file mode 100644 index 0000000..90dd51b --- /dev/null +++ b/node_modules/discord.js/src/WebSocket.js @@ -0,0 +1,49 @@ +'use strict'; + +const { browser } = require('./util/Constants'); + +let erlpack; + +try { + erlpack = require('erlpack'); + if (!erlpack.pack) erlpack = null; +} catch {} // eslint-disable-line no-empty + +let TextDecoder; + +if (browser) { + TextDecoder = window.TextDecoder; // eslint-disable-line no-undef + exports.WebSocket = window.WebSocket; // eslint-disable-line no-undef +} else { + TextDecoder = require('util').TextDecoder; + exports.WebSocket = require('ws'); +} + +const ab = new TextDecoder(); + +exports.encoding = erlpack ? 'etf' : 'json'; + +exports.pack = erlpack ? erlpack.pack : JSON.stringify; + +exports.unpack = (data, type) => { + if (exports.encoding === 'json' || type === 'json') { + if (typeof data !== 'string') { + data = ab.decode(data); + } + return JSON.parse(data); + } + if (!Buffer.isBuffer(data)) data = Buffer.from(new Uint8Array(data)); + return erlpack.unpack(data); +}; + +exports.create = (gateway, query = {}, ...args) => { + const [g, q] = gateway.split('?'); + query.encoding = exports.encoding; + query = new URLSearchParams(query); + if (q) new URLSearchParams(q).forEach((v, k) => query.set(k, v)); + const ws = new exports.WebSocket(`${g}?${query}`, ...args); + if (browser) ws.binaryType = 'arraybuffer'; + return ws; +}; + +for (const state of ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED']) exports[state] = exports.WebSocket[state]; diff --git a/node_modules/discord.js/src/client/BaseClient.js b/node_modules/discord.js/src/client/BaseClient.js new file mode 100644 index 0000000..a381e1a --- /dev/null +++ b/node_modules/discord.js/src/client/BaseClient.js @@ -0,0 +1,147 @@ +'use strict'; + +require('setimmediate'); +const EventEmitter = require('events'); +const RESTManager = require('../rest/RESTManager'); +const { DefaultOptions } = require('../util/Constants'); +const Util = require('../util/Util'); + +/** + * The base class for all clients. + * @extends {EventEmitter} + */ +class BaseClient extends EventEmitter { + constructor(options = {}) { + super(); + + /** + * Timeouts set by {@link BaseClient#setTimeout} that are still active + * @type {Set<Timeout>} + * @private + */ + this._timeouts = new Set(); + + /** + * Intervals set by {@link BaseClient#setInterval} that are still active + * @type {Set<Timeout>} + * @private + */ + this._intervals = new Set(); + + /** + * Intervals set by {@link BaseClient#setImmediate} that are still active + * @type {Set<Immediate>} + * @private + */ + this._immediates = new Set(); + + /** + * The options the client was instantiated with + * @type {ClientOptions} + */ + this.options = Util.mergeDefault(DefaultOptions, options); + + /** + * The REST manager of the client + * @type {RESTManager} + * @private + */ + this.rest = new RESTManager(this, options._tokenType); + } + + /** + * API shortcut + * @type {Object} + * @readonly + * @private + */ + get api() { + return this.rest.api; + } + + /** + * Destroys all assets used by the base client. + */ + destroy() { + for (const t of this._timeouts) this.clearTimeout(t); + for (const i of this._intervals) this.clearInterval(i); + for (const i of this._immediates) this.clearImmediate(i); + this._timeouts.clear(); + this._intervals.clear(); + this._immediates.clear(); + } + + /** + * Sets a timeout that will be automatically cancelled if the client is destroyed. + * @param {Function} fn Function to execute + * @param {number} delay Time to wait before executing (in milliseconds) + * @param {...*} args Arguments for the function + * @returns {Timeout} + */ + setTimeout(fn, delay, ...args) { + const timeout = setTimeout(() => { + fn(...args); + this._timeouts.delete(timeout); + }, delay); + this._timeouts.add(timeout); + return timeout; + } + + /** + * Clears a timeout. + * @param {Timeout} timeout Timeout to cancel + */ + clearTimeout(timeout) { + clearTimeout(timeout); + this._timeouts.delete(timeout); + } + + /** + * Sets an interval that will be automatically cancelled if the client is destroyed. + * @param {Function} fn Function to execute + * @param {number} delay Time to wait between executions (in milliseconds) + * @param {...*} args Arguments for the function + * @returns {Timeout} + */ + setInterval(fn, delay, ...args) { + const interval = setInterval(fn, delay, ...args); + this._intervals.add(interval); + return interval; + } + + /** + * Clears an interval. + * @param {Timeout} interval Interval to cancel + */ + clearInterval(interval) { + clearInterval(interval); + this._intervals.delete(interval); + } + + /** + * Sets an immediate that will be automatically cancelled if the client is destroyed. + * @param {Function} fn Function to execute + * @param {...*} args Arguments for the function + * @returns {Immediate} + */ + setImmediate(fn, ...args) { + const immediate = setImmediate(fn, ...args); + this._immediates.add(immediate); + return immediate; + } + + /** + * Clears an immediate. + * @param {Immediate} immediate Immediate to cancel + */ + clearImmediate(immediate) { + clearImmediate(immediate); + this._immediates.delete(immediate); + } + + toJSON(...props) { + return Util.flatten(this, { domain: false }, ...props); + } +} + +module.exports = BaseClient; diff --git a/node_modules/discord.js/src/client/Client.js b/node_modules/discord.js/src/client/Client.js new file mode 100644 index 0000000..4a12e85 --- /dev/null +++ b/node_modules/discord.js/src/client/Client.js @@ -0,0 +1,455 @@ +'use strict'; + +const BaseClient = require('./BaseClient'); +const ActionsManager = require('./actions/ActionsManager'); +const ClientVoiceManager = require('./voice/ClientVoiceManager'); +const WebSocketManager = require('./websocket/WebSocketManager'); +const { Error, TypeError, RangeError } = require('../errors'); +const ChannelManager = require('../managers/ChannelManager'); +const GuildEmojiManager = require('../managers/GuildEmojiManager'); +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 Invite = require('../structures/Invite'); +const VoiceRegion = require('../structures/VoiceRegion'); +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'); + +/** + * The main hub for interacting with the Discord API, and the starting point for any bot. + * @extends {BaseClient} + */ +class Client extends BaseClient { + /** + * @param {ClientOptions} [options] Options for the client + */ + constructor(options = {}) { + super(Object.assign({ _tokenType: 'Bot' }, options)); + + // Obtain shard details from environment or if present, worker threads + let data = process.env; + try { + // Test if worker threads module is present and used + data = require('worker_threads').workerData || data; + } catch { + // Do nothing + } + + if (this.options.shards === DefaultOptions.shards) { + if ('SHARDS' in data) { + this.options.shards = JSON.parse(data.SHARDS); + } + } + + if (this.options.shardCount === DefaultOptions.shardCount) { + if ('SHARD_COUNT' in data) { + this.options.shardCount = Number(data.SHARD_COUNT); + } else if (Array.isArray(this.options.shards)) { + this.options.shardCount = this.options.shards.length; + } + } + + const typeofShards = typeof this.options.shards; + + if (typeofShards === 'undefined' && typeof this.options.shardCount === 'number') { + this.options.shards = Array.from({ length: this.options.shardCount }, (_, i) => i); + } + + if (typeofShards === 'number') this.options.shards = [this.options.shards]; + + if (Array.isArray(this.options.shards)) { + this.options.shards = [ + ...new Set( + this.options.shards.filter(item => !isNaN(item) && item >= 0 && item < Infinity && item === (item | 0)), + ), + ]; + } + + this._validateOptions(); + + /** + * The WebSocket manager of the client + * @type {WebSocketManager} + */ + this.ws = new WebSocketManager(this); + + /** + * The action manager of the client + * @type {ActionsManager} + * @private + */ + this.actions = new ActionsManager(this); + + /** + * The voice manager of the client (`null` in browsers) + * @type {?ClientVoiceManager} + */ + this.voice = !browser ? new ClientVoiceManager(this) : null; + + /** + * Shard helpers for the client (only if the process was spawned from a {@link ShardingManager}) + * @type {?ShardClientUtil} + */ + this.shard = + !browser && process.env.SHARDING_MANAGER + ? ShardClientUtil.singleton(this, process.env.SHARDING_MANAGER_MODE) + : null; + + /** + * All of the {@link User} objects that have been cached at any point, mapped by their IDs + * @type {UserManager} + */ + this.users = new UserManager(this); + + /** + * All of the guilds the client is currently handling, mapped by their IDs - + * as long as sharding isn't being used, this will be *every* guild the bot is a member of + * @type {GuildManager} + */ + this.guilds = new GuildManager(this); + + /** + * All of the {@link Channel}s that the client is currently handling, mapped by their IDs - + * as long as sharding isn't being used, this will be *every* channel in *every* guild the bot + * is a member of. Note that DM channels will not be initially cached, and thus not be present + * in the Manager without their explicit fetching or use. + * @type {ChannelManager} + */ + this.channels = new ChannelManager(this); + + const ClientPresence = Structures.get('ClientPresence'); + /** + * The presence of the Client + * @private + * @type {ClientPresence} + */ + this.presence = new ClientPresence(this); + + Object.defineProperty(this, 'token', { writable: true }); + if (!browser && !this.token && 'DISCORD_TOKEN' in process.env) { + /** + * Authorization token for the logged in bot + * <warn>This should be kept private at all times.</warn> + * @type {?string} + */ + this.token = process.env.DISCORD_TOKEN; + } else { + this.token = null; + } + + /** + * User that the client is logged in as + * @type {?ClientUser} + */ + this.user = 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) + * @type {?Date} + */ + this.readyAt = null; + + if (this.options.messageSweepInterval > 0) { + this.setInterval(this.sweepMessages.bind(this), this.options.messageSweepInterval * 1000); + } + } + + /** + * All custom emojis that the client has access to, mapped by their IDs + * @type {GuildEmojiManager} + * @readonly + */ + get emojis() { + const emojis = new GuildEmojiManager({ client: this }); + for (const guild of this.guilds.cache.values()) { + if (guild.available) for (const emoji of guild.emojis.cache.values()) emojis.cache.set(emoji.id, emoji); + } + return emojis; + } + + /** + * Timestamp of the time the client was last `READY` at + * @type {?number} + * @readonly + */ + get readyTimestamp() { + return this.readyAt ? this.readyAt.getTime() : null; + } + + /** + * How long it has been since the client last entered the `READY` state in milliseconds + * @type {?number} + * @readonly + */ + get uptime() { + return this.readyAt ? Date.now() - this.readyAt : null; + } + + /** + * Logs the client in, establishing a websocket connection to Discord. + * @param {string} token Token of the account to log in with + * @returns {Promise<string>} Token of the account used + * @example + * client.login('my token'); + */ + async login(token = this.token) { + if (!token || typeof token !== 'string') throw new Error('TOKEN_INVALID'); + this.token = token = token.replace(/^(Bot|Bearer)\s*/i, ''); + this.emit( + Events.DEBUG, + `Provided token: ${token + .split('.') + .map((val, i) => (i > 1 ? val.replace(/./g, '*') : val)) + .join('.')}`, + ); + + if (this.options.presence) { + this.options.ws.presence = await this.presence._parse(this.options.presence); + } + + this.emit(Events.DEBUG, 'Preparing to connect to the gateway...'); + + try { + await this.ws.connect(); + return this.token; + } catch (error) { + this.destroy(); + throw error; + } + } + + /** + * Logs out, terminates the connection to Discord, and destroys the client. + * @returns {void} + */ + destroy() { + super.destroy(); + this.ws.destroy(); + this.token = null; + } + + /** + * Obtains an invite from Discord. + * @param {InviteResolvable} invite Invite code or URL + * @returns {Promise<Invite>} + * @example + * client.fetchInvite('https://discord.gg/bRCvFy9') + * .then(invite => console.log(`Obtained invite with code: ${invite.code}`)) + * .catch(console.error); + */ + fetchInvite(invite) { + const code = DataResolver.resolveInviteCode(invite); + return this.api + .invites(code) + .get({ query: { with_counts: true } }) + .then(data => new Invite(this, data)); + } + + /** + * Obtains a webhook from Discord. + * @param {Snowflake} id ID of the webhook + * @param {string} [token] Token for the webhook + * @returns {Promise<Webhook>} + * @example + * client.fetchWebhook('id', 'token') + * .then(webhook => console.log(`Obtained webhook with name: ${webhook.name}`)) + * .catch(console.error); + */ + fetchWebhook(id, token) { + return this.api + .webhooks(id, token) + .get() + .then(data => new Webhook(this, data)); + } + + /** + * Obtains the available voice regions from Discord. + * @returns {Promise<Collection<string, VoiceRegion>>} + * @example + * client.fetchVoiceRegions() + * .then(regions => console.log(`Available regions are: ${regions.map(region => region.name).join(', ')}`)) + * .catch(console.error); + */ + fetchVoiceRegions() { + return this.api.voice.regions.get().then(res => { + const regions = new Collection(); + for (const region of res) regions.set(region.id, new VoiceRegion(region)); + return regions; + }); + } + + /** + * Sweeps all text-based channels' messages and removes the ones older than the max message lifetime. + * If the message has been edited, the time of the edit is used rather than the time of the original message. + * @param {number} [lifetime=this.options.messageCacheLifetime] Messages that are older than this (in seconds) + * will be removed from the caches. The default is based on {@link ClientOptions#messageCacheLifetime} + * @returns {number} Amount of messages that were removed from the caches, + * or -1 if the message cache lifetime is unlimited + * @example + * // Remove all messages older than 1800 seconds from the messages cache + * const amount = client.sweepMessages(1800); + * console.log(`Successfully removed ${amount} messages from the cache.`); + */ + sweepMessages(lifetime = this.options.messageCacheLifetime) { + if (typeof lifetime !== 'number' || isNaN(lifetime)) { + throw new TypeError('INVALID_TYPE', 'lifetime', 'number'); + } + if (lifetime <= 0) { + this.emit(Events.DEBUG, "Didn't sweep messages - lifetime is unlimited"); + return -1; + } + + const lifetimeMs = lifetime * 1000; + const now = Date.now(); + let channels = 0; + let messages = 0; + + for (const channel of this.channels.cache.values()) { + if (!channel.messages) continue; + channels++; + + messages += channel.messages.cache.sweep( + message => now - (message.editedTimestamp || message.createdTimestamp) > lifetimeMs, + ); + } + + this.emit( + Events.DEBUG, + `Swept ${messages} messages older than ${lifetime} seconds in ${channels} text-based channels`, + ); + 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, only available for public guilds. + * @param {GuildResolvable} guild The guild to fetch the preview for + * @returns {Promise<GuildPreview>} + */ + fetchGuildPreview(guild) { + const id = this.guilds.resolveID(guild); + if (!id) throw new TypeError('INVALID_TYPE', 'guild', 'GuildResolvable'); + return this.api + .guilds(id) + .preview.get() + .then(data => new GuildPreview(this, data)); + } + + /** + * Generates a link that can be used to invite the bot to a guild. + * @param {PermissionResolvable} [permissions] Permissions to request + * @returns {Promise<string>} + * @example + * client.generateInvite(['SEND_MESSAGES', 'MANAGE_GUILD', 'MENTION_EVERYONE']) + * .then(link => console.log(`Generated bot invite link: ${link}`)) + * .catch(console.error); + */ + async generateInvite(permissions) { + permissions = Permissions.resolve(permissions); + const application = await this.fetchApplication(); + const query = new URLSearchParams({ + client_id: application.id, + permissions: permissions, + scope: 'bot', + }); + return `${this.options.http.api}${this.api.oauth2.authorize}?${query}`; + } + + toJSON() { + return super.toJSON({ + readyAt: false, + presences: false, + }); + } + + /** + * Calls {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval} on a script + * with the client as `this`. + * @param {string} script Script to eval + * @returns {*} + * @private + */ + _eval(script) { + return eval(script); + } + + /** + * Validates the client options. + * @param {ClientOptions} [options=this.options] Options to validate + * @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'); + } + if (options.shards && !(options.shards === 'auto' || Array.isArray(options.shards))) { + throw new TypeError('CLIENT_INVALID_OPTION', 'shards', "'auto', a number or array of numbers"); + } + if (options.shards && !options.shards.length) throw new RangeError('CLIENT_INVALID_PROVIDED_SHARDS'); + if (typeof options.messageCacheMaxSize !== 'number' || isNaN(options.messageCacheMaxSize)) { + throw new TypeError('CLIENT_INVALID_OPTION', 'messageCacheMaxSize', 'a number'); + } + if (typeof options.messageCacheLifetime !== 'number' || isNaN(options.messageCacheLifetime)) { + throw new TypeError('CLIENT_INVALID_OPTION', 'The messageCacheLifetime', 'a number'); + } + if (typeof options.messageSweepInterval !== 'number' || isNaN(options.messageSweepInterval)) { + throw new TypeError('CLIENT_INVALID_OPTION', 'messageSweepInterval', 'a number'); + } + if (typeof options.fetchAllMembers !== 'boolean') { + throw new TypeError('CLIENT_INVALID_OPTION', 'fetchAllMembers', 'a boolean'); + } + if (typeof options.disableMentions !== 'string') { + throw new TypeError('CLIENT_INVALID_OPTION', 'disableMentions', 'a string'); + } + if (!Array.isArray(options.partials)) { + throw new TypeError('CLIENT_INVALID_OPTION', 'partials', 'an Array'); + } + if (typeof options.restWsBridgeTimeout !== 'number' || isNaN(options.restWsBridgeTimeout)) { + throw new TypeError('CLIENT_INVALID_OPTION', 'restWsBridgeTimeout', 'a number'); + } + if (typeof options.restRequestTimeout !== 'number' || isNaN(options.restRequestTimeout)) { + throw new TypeError('CLIENT_INVALID_OPTION', 'restRequestTimeout', 'a number'); + } + if (typeof options.restSweepInterval !== 'number' || isNaN(options.restSweepInterval)) { + throw new TypeError('CLIENT_INVALID_OPTION', 'restSweepInterval', 'a number'); + } + if (typeof options.retryLimit !== 'number' || isNaN(options.retryLimit)) { + throw new TypeError('CLIENT_INVALID_OPTION', 'retryLimit', 'a number'); + } + } +} + +module.exports = Client; + +/** + * Emitted for general warnings. + * @event Client#warn + * @param {string} info The warning + */ + +/** + * Emitted for general debugging information. + * @event Client#debug + * @param {string} info The debug information + */ diff --git a/node_modules/discord.js/src/client/WebhookClient.js b/node_modules/discord.js/src/client/WebhookClient.js new file mode 100644 index 0000000..cea6631 --- /dev/null +++ b/node_modules/discord.js/src/client/WebhookClient.js @@ -0,0 +1,31 @@ +'use strict'; + +const BaseClient = require('./BaseClient'); +const Webhook = require('../structures/Webhook'); + +/** + * The webhook client. + * @implements {Webhook} + * @extends {BaseClient} + */ +class WebhookClient extends BaseClient { + /** + * @param {Snowflake} id ID of the webhook + * @param {string} token Token of the webhook + * @param {ClientOptions} [options] Options for the client + * @example + * // Create a new webhook and send a message + * const hook = new Discord.WebhookClient('1234', 'abcdef'); + * hook.send('This will send a message').catch(console.error); + */ + constructor(id, token, options) { + super(options); + Object.defineProperty(this, 'client', { value: this }); + this.id = id; + Object.defineProperty(this, 'token', { value: token, writable: true, configurable: true }); + } +} + +Webhook.applyToClass(WebhookClient); + +module.exports = WebhookClient; diff --git a/node_modules/discord.js/src/client/actions/Action.js b/node_modules/discord.js/src/client/actions/Action.js new file mode 100644 index 0000000..d9519b5 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/Action.js @@ -0,0 +1,103 @@ +'use strict'; + +const { PartialTypes } = require('../../util/Constants'); + +/* + +ABOUT ACTIONS + +Actions are similar to WebSocket Packet Handlers, but since introducing +the REST API methods, in order to prevent rewriting code to handle data, +"actions" have been introduced. They're basically what Packet Handlers +used to be but they're strictly for manipulating data and making sure +that WebSocket events don't clash with REST methods. + +*/ + +class GenericAction { + constructor(client) { + this.client = client; + } + + handle(data) { + return data; + } + + getPayload(data, manager, id, partialType, cache) { + const existing = manager.cache.get(id); + if (!existing && this.client.options.partials.includes(partialType)) { + return manager.add(data, cache); + } + return existing; + } + + getChannel(data) { + const id = data.channel_id || data.id; + return ( + data.channel || + this.getPayload( + { + id, + guild_id: data.guild_id, + recipients: [data.author || { id: data.user_id }], + }, + this.client.channels, + id, + PartialTypes.CHANNEL, + ) + ); + } + + getMessage(data, channel, cache) { + const id = data.message_id || data.id; + return ( + data.message || + this.getPayload( + { + id, + channel_id: channel.id, + guild_id: data.guild_id || (channel.guild ? channel.guild.id : null), + }, + channel.messages, + id, + PartialTypes.MESSAGE, + cache, + ) + ); + } + + getReaction(data, message, user) { + const id = data.emoji.id || decodeURIComponent(data.emoji.name); + return this.getPayload( + { + emoji: data.emoji, + count: message.partial ? null : 0, + me: user ? user.id === this.client.user.id : false, + }, + message.reactions, + id, + PartialTypes.REACTION, + ); + } + + getMember(data, guild) { + const id = data.user.id; + return this.getPayload( + { + user: { + id, + }, + }, + guild.members, + id, + PartialTypes.GUILD_MEMBER, + ); + } + + getUser(data) { + const id = data.user_id; + return data.user || this.getPayload({ id }, this.client.users, id, PartialTypes.USER); + } +} + +module.exports = GenericAction; diff --git a/node_modules/discord.js/src/client/actions/ActionsManager.js b/node_modules/discord.js/src/client/actions/ActionsManager.js new file mode 100644 index 0000000..7e1df12 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/ActionsManager.js @@ -0,0 +1,45 @@ +'use strict'; + +class ActionsManager { + constructor(client) { + this.client = client; + + this.register(require('./MessageCreate')); + this.register(require('./MessageDelete')); + this.register(require('./MessageDeleteBulk')); + this.register(require('./MessageUpdate')); + this.register(require('./MessageReactionAdd')); + this.register(require('./MessageReactionRemove')); + this.register(require('./MessageReactionRemoveAll')); + this.register(require('./MessageReactionRemoveEmoji')); + this.register(require('./ChannelCreate')); + this.register(require('./ChannelDelete')); + this.register(require('./ChannelUpdate')); + this.register(require('./GuildDelete')); + this.register(require('./GuildUpdate')); + this.register(require('./InviteCreate')); + this.register(require('./InviteDelete')); + this.register(require('./GuildMemberRemove')); + this.register(require('./GuildBanRemove')); + this.register(require('./GuildRoleCreate')); + this.register(require('./GuildRoleDelete')); + this.register(require('./GuildRoleUpdate')); + this.register(require('./PresenceUpdate')); + this.register(require('./UserUpdate')); + this.register(require('./VoiceStateUpdate')); + this.register(require('./GuildEmojiCreate')); + this.register(require('./GuildEmojiDelete')); + this.register(require('./GuildEmojiUpdate')); + this.register(require('./GuildEmojisUpdate')); + this.register(require('./GuildRolesPositionUpdate')); + this.register(require('./GuildChannelsPositionUpdate')); + this.register(require('./GuildIntegrationsUpdate')); + this.register(require('./WebhooksUpdate')); + } + + register(Action) { + this[Action.name.replace(/Action$/, '')] = new Action(this.client); + } +} + +module.exports = ActionsManager; diff --git a/node_modules/discord.js/src/client/actions/ChannelCreate.js b/node_modules/discord.js/src/client/actions/ChannelCreate.js new file mode 100644 index 0000000..fa60a0b --- /dev/null +++ b/node_modules/discord.js/src/client/actions/ChannelCreate.js @@ -0,0 +1,23 @@ +'use strict'; + +const Action = require('./Action'); +const { Events } = require('../../util/Constants'); + +class ChannelCreateAction extends Action { + handle(data) { + const client = this.client; + const existing = client.channels.cache.has(data.id); + const channel = client.channels.add(data); + if (!existing && channel) { + /** + * Emitted whenever a channel is created. + * @event Client#channelCreate + * @param {DMChannel|GuildChannel} channel The channel that was created + */ + client.emit(Events.CHANNEL_CREATE, channel); + } + return { channel }; + } +} + +module.exports = ChannelCreateAction; diff --git a/node_modules/discord.js/src/client/actions/ChannelDelete.js b/node_modules/discord.js/src/client/actions/ChannelDelete.js new file mode 100644 index 0000000..2956d72 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/ChannelDelete.js @@ -0,0 +1,37 @@ +'use strict'; + +const Action = require('./Action'); +const DMChannel = require('../../structures/DMChannel'); +const { Events } = require('../../util/Constants'); + +class ChannelDeleteAction extends Action { + constructor(client) { + super(client); + this.deleted = new Map(); + } + + handle(data) { + const client = this.client; + let channel = client.channels.cache.get(data.id); + + if (channel) { + client.channels.remove(channel.id); + channel.deleted = true; + if (channel.messages && !(channel instanceof DMChannel)) { + for (const message of channel.messages.cache.values()) { + message.deleted = true; + } + } + /** + * Emitted whenever a channel is deleted. + * @event Client#channelDelete + * @param {DMChannel|GuildChannel} channel The channel that was deleted + */ + client.emit(Events.CHANNEL_DELETE, channel); + } + + return { channel }; + } +} + +module.exports = ChannelDeleteAction; diff --git a/node_modules/discord.js/src/client/actions/ChannelUpdate.js b/node_modules/discord.js/src/client/actions/ChannelUpdate.js new file mode 100644 index 0000000..06bb71b --- /dev/null +++ b/node_modules/discord.js/src/client/actions/ChannelUpdate.js @@ -0,0 +1,33 @@ +'use strict'; + +const Action = require('./Action'); +const Channel = require('../../structures/Channel'); +const { ChannelTypes } = require('../../util/Constants'); + +class ChannelUpdateAction extends Action { + handle(data) { + const client = this.client; + + let channel = client.channels.cache.get(data.id); + if (channel) { + const old = channel._update(data); + + if (ChannelTypes[channel.type.toUpperCase()] !== data.type) { + const newChannel = Channel.create(this.client, data, channel.guild); + for (const [id, message] of channel.messages.cache) newChannel.messages.cache.set(id, message); + newChannel._typing = new Map(channel._typing); + channel = newChannel; + this.client.channels.cache.set(channel.id, channel); + } + + return { + old, + updated: channel, + }; + } + + return {}; + } +} + +module.exports = ChannelUpdateAction; diff --git a/node_modules/discord.js/src/client/actions/GuildBanRemove.js b/node_modules/discord.js/src/client/actions/GuildBanRemove.js new file mode 100644 index 0000000..fc28e11 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/GuildBanRemove.js @@ -0,0 +1,21 @@ +'use strict'; + +const Action = require('./Action'); +const { Events } = require('../../util/Constants'); + +class GuildBanRemove extends Action { + handle(data) { + const client = this.client; + const guild = client.guilds.cache.get(data.guild_id); + const user = client.users.add(data.user); + /** + * Emitted whenever a member is unbanned from a guild. + * @event Client#guildBanRemove + * @param {Guild} guild The guild that the unban occurred in + * @param {User} user The user that was unbanned + */ + if (guild && user) client.emit(Events.GUILD_BAN_REMOVE, guild, user); + } +} + +module.exports = GuildBanRemove; diff --git a/node_modules/discord.js/src/client/actions/GuildChannelsPositionUpdate.js b/node_modules/discord.js/src/client/actions/GuildChannelsPositionUpdate.js new file mode 100644 index 0000000..a393167 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/GuildChannelsPositionUpdate.js @@ -0,0 +1,21 @@ +'use strict'; + +const Action = require('./Action'); + +class GuildChannelsPositionUpdate extends Action { + handle(data) { + const client = this.client; + + const guild = client.guilds.cache.get(data.guild_id); + if (guild) { + for (const partialChannel of data.channels) { + const channel = guild.channels.cache.get(partialChannel.id); + if (channel) channel.rawPosition = partialChannel.position; + } + } + + return { guild }; + } +} + +module.exports = GuildChannelsPositionUpdate; diff --git a/node_modules/discord.js/src/client/actions/GuildDelete.js b/node_modules/discord.js/src/client/actions/GuildDelete.js new file mode 100644 index 0000000..8bb630f --- /dev/null +++ b/node_modules/discord.js/src/client/actions/GuildDelete.js @@ -0,0 +1,67 @@ +'use strict'; + +const Action = require('./Action'); +const { Events } = require('../../util/Constants'); + +class GuildDeleteAction extends Action { + constructor(client) { + super(client); + this.deleted = new Map(); + } + + handle(data) { + const client = this.client; + + let guild = client.guilds.cache.get(data.id); + if (guild) { + for (const channel of guild.channels.cache.values()) { + if (channel.type === 'text') channel.stopTyping(true); + } + + if (data.unavailable) { + // Guild is unavailable + guild.available = false; + + /** + * Emitted whenever a guild becomes unavailable, likely due to a server outage. + * @event Client#guildUnavailable + * @param {Guild} guild The guild that has become unavailable + */ + client.emit(Events.GUILD_UNAVAILABLE, guild); + + // Stops the GuildDelete packet thinking a guild was actually deleted, + // handles emitting of event itself + return { + guild: null, + }; + } + + for (const channel of guild.channels.cache.values()) this.client.channels.remove(channel.id); + if (guild.voice && guild.voice.connection) guild.voice.connection.disconnect(); + + // Delete guild + client.guilds.cache.delete(guild.id); + guild.deleted = true; + + /** + * Emitted whenever a guild kicks the client or the guild is deleted/left. + * @event Client#guildDelete + * @param {Guild} guild The guild that was deleted + */ + client.emit(Events.GUILD_DELETE, guild); + + this.deleted.set(guild.id, guild); + this.scheduleForDeletion(guild.id); + } else { + guild = this.deleted.get(data.id) || null; + } + + return { guild }; + } + + scheduleForDeletion(id) { + this.client.setTimeout(() => this.deleted.delete(id), this.client.options.restWsBridgeTimeout); + } +} + +module.exports = GuildDeleteAction; diff --git a/node_modules/discord.js/src/client/actions/GuildEmojiCreate.js b/node_modules/discord.js/src/client/actions/GuildEmojiCreate.js new file mode 100644 index 0000000..379c62e --- /dev/null +++ b/node_modules/discord.js/src/client/actions/GuildEmojiCreate.js @@ -0,0 +1,19 @@ +'use strict'; + +const Action = require('./Action'); +const { Events } = require('../../util/Constants'); + +class GuildEmojiCreateAction extends Action { + handle(guild, createdEmoji) { + const emoji = guild.emojis.add(createdEmoji); + /** + * Emitted whenever a custom emoji is created in a guild. + * @event Client#emojiCreate + * @param {GuildEmoji} emoji The emoji that was created + */ + this.client.emit(Events.GUILD_EMOJI_CREATE, emoji); + return { emoji }; + } +} + +module.exports = GuildEmojiCreateAction; diff --git a/node_modules/discord.js/src/client/actions/GuildEmojiDelete.js b/node_modules/discord.js/src/client/actions/GuildEmojiDelete.js new file mode 100644 index 0000000..42af70c --- /dev/null +++ b/node_modules/discord.js/src/client/actions/GuildEmojiDelete.js @@ -0,0 +1,20 @@ +'use strict'; + +const Action = require('./Action'); +const { Events } = require('../../util/Constants'); + +class GuildEmojiDeleteAction extends Action { + handle(emoji) { + emoji.guild.emojis.cache.delete(emoji.id); + emoji.deleted = true; + /** + * Emitted whenever a custom emoji is deleted in a guild. + * @event Client#emojiDelete + * @param {GuildEmoji} emoji The emoji that was deleted + */ + this.client.emit(Events.GUILD_EMOJI_DELETE, emoji); + return { emoji }; + } +} + +module.exports = GuildEmojiDeleteAction; diff --git a/node_modules/discord.js/src/client/actions/GuildEmojiUpdate.js b/node_modules/discord.js/src/client/actions/GuildEmojiUpdate.js new file mode 100644 index 0000000..9fa59c9 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/GuildEmojiUpdate.js @@ -0,0 +1,20 @@ +'use strict'; + +const Action = require('./Action'); +const { Events } = require('../../util/Constants'); + +class GuildEmojiUpdateAction extends Action { + handle(current, data) { + const old = current._update(data); + /** + * Emitted whenever a custom emoji is updated in a guild. + * @event Client#emojiUpdate + * @param {GuildEmoji} oldEmoji The old emoji + * @param {GuildEmoji} newEmoji The new emoji + */ + this.client.emit(Events.GUILD_EMOJI_UPDATE, old, current); + return { emoji: current }; + } +} + +module.exports = GuildEmojiUpdateAction; diff --git a/node_modules/discord.js/src/client/actions/GuildEmojisUpdate.js b/node_modules/discord.js/src/client/actions/GuildEmojisUpdate.js new file mode 100644 index 0000000..7711933 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/GuildEmojisUpdate.js @@ -0,0 +1,34 @@ +'use strict'; + +const Action = require('./Action'); + +class GuildEmojisUpdateAction extends Action { + handle(data) { + const guild = this.client.guilds.cache.get(data.guild_id); + if (!guild || !guild.emojis) return; + + const deletions = new Map(guild.emojis.cache); + + for (const emoji of data.emojis) { + // Determine type of emoji event + const cachedEmoji = guild.emojis.cache.get(emoji.id); + if (cachedEmoji) { + deletions.delete(emoji.id); + if (!cachedEmoji.equals(emoji)) { + // Emoji updated + this.client.actions.GuildEmojiUpdate.handle(cachedEmoji, emoji); + } + } else { + // Emoji added + this.client.actions.GuildEmojiCreate.handle(guild, emoji); + } + } + + for (const emoji of deletions.values()) { + // Emoji deleted + this.client.actions.GuildEmojiDelete.handle(emoji); + } + } +} + +module.exports = GuildEmojisUpdateAction; diff --git a/node_modules/discord.js/src/client/actions/GuildIntegrationsUpdate.js b/node_modules/discord.js/src/client/actions/GuildIntegrationsUpdate.js new file mode 100644 index 0000000..bbce076 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/GuildIntegrationsUpdate.js @@ -0,0 +1,19 @@ +'use strict'; + +const Action = require('./Action'); +const { Events } = require('../../util/Constants'); + +class GuildIntegrationsUpdate extends Action { + handle(data) { + const client = this.client; + const guild = client.guilds.cache.get(data.guild_id); + /** + * Emitted whenever a guild integration is updated + * @event Client#guildIntegrationsUpdate + * @param {Guild} guild The guild whose integrations were updated + */ + if (guild) client.emit(Events.GUILD_INTEGRATIONS_UPDATE, guild); + } +} + +module.exports = GuildIntegrationsUpdate; diff --git a/node_modules/discord.js/src/client/actions/GuildMemberRemove.js b/node_modules/discord.js/src/client/actions/GuildMemberRemove.js new file mode 100644 index 0000000..f0fe3b3 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/GuildMemberRemove.js @@ -0,0 +1,30 @@ +'use strict'; + +const Action = require('./Action'); +const { Events, Status } = require('../../util/Constants'); + +class GuildMemberRemoveAction extends Action { + handle(data, shard) { + const client = this.client; + const guild = client.guilds.cache.get(data.guild_id); + let member = null; + if (guild) { + member = this.getMember(data, guild); + guild.memberCount--; + if (member) { + member.deleted = true; + guild.members.cache.delete(member.id); + /** + * Emitted whenever a member leaves a guild, or is kicked. + * @event Client#guildMemberRemove + * @param {GuildMember} member The member that has left/been kicked from the guild + */ + if (shard.status === Status.READY) client.emit(Events.GUILD_MEMBER_REMOVE, member); + } + guild.voiceStates.cache.delete(data.user.id); + } + return { guild, member }; + } +} + +module.exports = GuildMemberRemoveAction; diff --git a/node_modules/discord.js/src/client/actions/GuildRoleCreate.js b/node_modules/discord.js/src/client/actions/GuildRoleCreate.js new file mode 100644 index 0000000..d7d5214 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/GuildRoleCreate.js @@ -0,0 +1,25 @@ +'use strict'; + +const Action = require('./Action'); +const { Events } = require('../../util/Constants'); + +class GuildRoleCreate extends Action { + handle(data) { + const client = this.client; + const guild = client.guilds.cache.get(data.guild_id); + let role; + if (guild) { + const already = guild.roles.cache.has(data.role.id); + role = guild.roles.add(data.role); + /** + * Emitted whenever a role is created. + * @event Client#roleCreate + * @param {Role} role The role that was created + */ + if (!already) client.emit(Events.GUILD_ROLE_CREATE, role); + } + return { role }; + } +} + +module.exports = GuildRoleCreate; diff --git a/node_modules/discord.js/src/client/actions/GuildRoleDelete.js b/node_modules/discord.js/src/client/actions/GuildRoleDelete.js new file mode 100644 index 0000000..51754b3 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/GuildRoleDelete.js @@ -0,0 +1,30 @@ +'use strict'; + +const Action = require('./Action'); +const { Events } = require('../../util/Constants'); + +class GuildRoleDeleteAction extends Action { + handle(data) { + const client = this.client; + const guild = client.guilds.cache.get(data.guild_id); + let role; + + if (guild) { + role = guild.roles.cache.get(data.role_id); + if (role) { + guild.roles.cache.delete(data.role_id); + role.deleted = true; + /** + * Emitted whenever a guild role is deleted. + * @event Client#roleDelete + * @param {Role} role The role that was deleted + */ + client.emit(Events.GUILD_ROLE_DELETE, role); + } + } + + return { role }; + } +} + +module.exports = GuildRoleDeleteAction; diff --git a/node_modules/discord.js/src/client/actions/GuildRoleUpdate.js b/node_modules/discord.js/src/client/actions/GuildRoleUpdate.js new file mode 100644 index 0000000..faea120 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/GuildRoleUpdate.js @@ -0,0 +1,39 @@ +'use strict'; + +const Action = require('./Action'); +const { Events } = require('../../util/Constants'); + +class GuildRoleUpdateAction extends Action { + handle(data) { + const client = this.client; + const guild = client.guilds.cache.get(data.guild_id); + + if (guild) { + let old = null; + + const role = guild.roles.cache.get(data.role.id); + if (role) { + old = role._update(data.role); + /** + * Emitted whenever a guild role is updated. + * @event Client#roleUpdate + * @param {Role} oldRole The role before the update + * @param {Role} newRole The role after the update + */ + client.emit(Events.GUILD_ROLE_UPDATE, old, role); + } + + return { + old, + updated: role, + }; + } + + return { + old: null, + updated: null, + }; + } +} + +module.exports = GuildRoleUpdateAction; diff --git a/node_modules/discord.js/src/client/actions/GuildRolesPositionUpdate.js b/node_modules/discord.js/src/client/actions/GuildRolesPositionUpdate.js new file mode 100644 index 0000000..d7abca9 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/GuildRolesPositionUpdate.js @@ -0,0 +1,21 @@ +'use strict'; + +const Action = require('./Action'); + +class GuildRolesPositionUpdate extends Action { + handle(data) { + const client = this.client; + + const guild = client.guilds.cache.get(data.guild_id); + if (guild) { + for (const partialRole of data.roles) { + const role = guild.roles.cache.get(partialRole.id); + if (role) role.rawPosition = partialRole.position; + } + } + + return { guild }; + } +} + +module.exports = GuildRolesPositionUpdate; diff --git a/node_modules/discord.js/src/client/actions/GuildUpdate.js b/node_modules/discord.js/src/client/actions/GuildUpdate.js new file mode 100644 index 0000000..78a8afb --- /dev/null +++ b/node_modules/discord.js/src/client/actions/GuildUpdate.js @@ -0,0 +1,33 @@ +'use strict'; + +const Action = require('./Action'); +const { Events } = require('../../util/Constants'); + +class GuildUpdateAction extends Action { + handle(data) { + const client = this.client; + + const guild = client.guilds.cache.get(data.id); + if (guild) { + const old = guild._update(data); + /** + * Emitted whenever a guild is updated - e.g. name change. + * @event Client#guildUpdate + * @param {Guild} oldGuild The guild before the update + * @param {Guild} newGuild The guild after the update + */ + client.emit(Events.GUILD_UPDATE, old, guild); + return { + old, + updated: guild, + }; + } + + return { + old: null, + updated: null, + }; + } +} + +module.exports = GuildUpdateAction; diff --git a/node_modules/discord.js/src/client/actions/InviteCreate.js b/node_modules/discord.js/src/client/actions/InviteCreate.js new file mode 100644 index 0000000..6381331 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/InviteCreate.js @@ -0,0 +1,28 @@ +'use strict'; + +const Action = require('./Action'); +const Invite = require('../../structures/Invite'); +const { Events } = require('../../util/Constants'); + +class InviteCreateAction extends Action { + handle(data) { + const client = this.client; + const channel = client.channels.cache.get(data.channel_id); + const guild = client.guilds.cache.get(data.guild_id); + if (!channel && !guild) return false; + + const inviteData = Object.assign(data, { channel, guild }); + const invite = new Invite(client, inviteData); + /** + * Emitted when an invite is created. + * <info> This event only triggers if the client has `MANAGE_GUILD` permissions for the guild, + * or `MANAGE_CHANNEL` permissions for the channel.</info> + * @event Client#inviteCreate + * @param {Invite} invite The invite that was created + */ + client.emit(Events.INVITE_CREATE, invite); + return { invite }; + } +} + +module.exports = InviteCreateAction; diff --git a/node_modules/discord.js/src/client/actions/InviteDelete.js b/node_modules/discord.js/src/client/actions/InviteDelete.js new file mode 100644 index 0000000..92692c3 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/InviteDelete.js @@ -0,0 +1,29 @@ +'use strict'; + +const Action = require('./Action'); +const Invite = require('../../structures/Invite'); +const { Events } = require('../../util/Constants'); + +class InviteDeleteAction extends Action { + handle(data) { + const client = this.client; + const channel = client.channels.cache.get(data.channel_id); + const guild = client.guilds.cache.get(data.guild_id); + if (!channel && !guild) return false; + + const inviteData = Object.assign(data, { channel, guild }); + const invite = new Invite(client, inviteData); + + /** + * Emitted when an invite is deleted. + * <info> This event only triggers if the client has `MANAGE_GUILD` permissions for the guild, + * or `MANAGE_CHANNEL` permissions for the channel.</info> + * @event Client#inviteDelete + * @param {Invite} invite The invite that was deleted + */ + client.emit(Events.INVITE_DELETE, invite); + return { invite }; + } +} + +module.exports = InviteDeleteAction; diff --git a/node_modules/discord.js/src/client/actions/MessageCreate.js b/node_modules/discord.js/src/client/actions/MessageCreate.js new file mode 100644 index 0000000..7138707 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/MessageCreate.js @@ -0,0 +1,39 @@ +'use strict'; + +const Action = require('./Action'); +const { Events } = require('../../util/Constants'); + +class MessageCreateAction extends Action { + handle(data) { + const client = this.client; + const channel = client.channels.cache.get(data.channel_id); + if (channel) { + const existing = channel.messages.cache.get(data.id); + if (existing) return { message: existing }; + const message = channel.messages.add(data); + const user = message.author; + let member = message.member; + channel.lastMessageID = data.id; + if (user) { + user.lastMessageID = data.id; + user.lastMessageChannelID = channel.id; + } + if (member) { + member.lastMessageID = data.id; + member.lastMessageChannelID = channel.id; + } + + /** + * Emitted whenever a message is created. + * @event Client#message + * @param {Message} message The created message + */ + client.emit(Events.MESSAGE_CREATE, message); + return { message }; + } + + return {}; + } +} + +module.exports = MessageCreateAction; diff --git a/node_modules/discord.js/src/client/actions/MessageDelete.js b/node_modules/discord.js/src/client/actions/MessageDelete.js new file mode 100644 index 0000000..0210625 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/MessageDelete.js @@ -0,0 +1,29 @@ +'use strict'; + +const Action = require('./Action'); +const { Events } = require('../../util/Constants'); + +class MessageDeleteAction extends Action { + handle(data) { + const client = this.client; + const channel = this.getChannel(data); + let message; + if (channel) { + message = this.getMessage(data, channel); + if (message) { + channel.messages.cache.delete(message.id); + message.deleted = true; + /** + * Emitted whenever a message is deleted. + * @event Client#messageDelete + * @param {Message} message The deleted message + */ + client.emit(Events.MESSAGE_DELETE, message); + } + } + + return { message }; + } +} + +module.exports = MessageDeleteAction; diff --git a/node_modules/discord.js/src/client/actions/MessageDeleteBulk.js b/node_modules/discord.js/src/client/actions/MessageDeleteBulk.js new file mode 100644 index 0000000..8686b1d --- /dev/null +++ b/node_modules/discord.js/src/client/actions/MessageDeleteBulk.js @@ -0,0 +1,43 @@ +'use strict'; + +const Action = require('./Action'); +const Collection = require('../../util/Collection'); +const { Events } = require('../../util/Constants'); + +class MessageDeleteBulkAction extends Action { + handle(data) { + const client = this.client; + const channel = client.channels.cache.get(data.channel_id); + + if (channel) { + const ids = data.ids; + const messages = new Collection(); + for (const id of ids) { + const message = this.getMessage( + { + id, + guild_id: data.guild_id, + }, + channel, + false, + ); + if (message) { + message.deleted = true; + messages.set(message.id, message); + channel.messages.cache.delete(id); + } + } + + /** + * Emitted whenever messages are deleted in bulk. + * @event Client#messageDeleteBulk + * @param {Collection<Snowflake, Message>} messages The deleted messages, mapped by their ID + */ + if (messages.size > 0) client.emit(Events.MESSAGE_BULK_DELETE, messages); + return { messages }; + } + return {}; + } +} + +module.exports = MessageDeleteBulkAction; diff --git a/node_modules/discord.js/src/client/actions/MessageReactionAdd.js b/node_modules/discord.js/src/client/actions/MessageReactionAdd.js new file mode 100644 index 0000000..22287db --- /dev/null +++ b/node_modules/discord.js/src/client/actions/MessageReactionAdd.js @@ -0,0 +1,50 @@ +'use strict'; + +const Action = require('./Action'); +const { Events } = require('../../util/Constants'); +const { PartialTypes } = require('../../util/Constants'); + +/* +{ user_id: 'id', + message_id: 'id', + emoji: { name: '�', id: null }, + channel_id: 'id' } } +*/ + +class MessageReactionAdd extends Action { + handle(data) { + if (!data.emoji) return false; + + const user = this.getUser(data); + if (!user) return false; + + // Verify channel + const channel = this.getChannel(data); + if (!channel || channel.type === 'voice') return false; + + // Verify message + const message = this.getMessage(data, channel); + if (!message) return false; + + // Verify reaction + if (message.partial && !this.client.options.partials.includes(PartialTypes.REACTION)) return false; + const reaction = message.reactions.add({ + emoji: data.emoji, + count: message.partial ? null : 0, + me: user.id === this.client.user.id, + }); + if (!reaction) return false; + reaction._add(user); + /** + * Emitted whenever a reaction is added to a cached message. + * @event Client#messageReactionAdd + * @param {MessageReaction} messageReaction The reaction object + * @param {User} user The user that applied the guild or reaction emoji + */ + this.client.emit(Events.MESSAGE_REACTION_ADD, reaction, user); + + return { message, reaction, user }; + } +} + +module.exports = MessageReactionAdd; diff --git a/node_modules/discord.js/src/client/actions/MessageReactionRemove.js b/node_modules/discord.js/src/client/actions/MessageReactionRemove.js new file mode 100644 index 0000000..a40aa75 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/MessageReactionRemove.js @@ -0,0 +1,44 @@ +'use strict'; + +const Action = require('./Action'); +const { Events } = require('../../util/Constants'); + +/* +{ user_id: 'id', + message_id: 'id', + emoji: { name: '�', id: null }, + channel_id: 'id' } } +*/ + +class MessageReactionRemove extends Action { + handle(data) { + if (!data.emoji) return false; + + const user = this.getUser(data); + if (!user) return false; + + // Verify channel + const channel = this.getChannel(data); + if (!channel || channel.type === 'voice') return false; + + // Verify message + const message = this.getMessage(data, channel); + if (!message) return false; + + // Verify reaction + const reaction = this.getReaction(data, message, user); + if (!reaction) return false; + reaction._remove(user); + /** + * Emitted whenever a reaction is removed from a cached message. + * @event Client#messageReactionRemove + * @param {MessageReaction} messageReaction The reaction object + * @param {User} user The user whose emoji or reaction emoji was removed + */ + this.client.emit(Events.MESSAGE_REACTION_REMOVE, reaction, user); + + return { message, reaction, user }; + } +} + +module.exports = MessageReactionRemove; diff --git a/node_modules/discord.js/src/client/actions/MessageReactionRemoveAll.js b/node_modules/discord.js/src/client/actions/MessageReactionRemoveAll.js new file mode 100644 index 0000000..14b79bf --- /dev/null +++ b/node_modules/discord.js/src/client/actions/MessageReactionRemoveAll.js @@ -0,0 +1,29 @@ +'use strict'; + +const Action = require('./Action'); +const { Events } = require('../../util/Constants'); + +class MessageReactionRemoveAll extends Action { + handle(data) { + // Verify channel + const channel = this.getChannel(data); + if (!channel || channel.type === 'voice') return false; + + // Verify message + const message = this.getMessage(data, channel); + if (!message) return false; + + message.reactions.cache.clear(); + this.client.emit(Events.MESSAGE_REACTION_REMOVE_ALL, message); + + return { message }; + } +} + +/** + * Emitted whenever all reactions are removed from a cached message. + * @event Client#messageReactionRemoveAll + * @param {Message} message The message the reactions were removed from + */ + +module.exports = MessageReactionRemoveAll; diff --git a/node_modules/discord.js/src/client/actions/MessageReactionRemoveEmoji.js b/node_modules/discord.js/src/client/actions/MessageReactionRemoveEmoji.js new file mode 100644 index 0000000..143702c --- /dev/null +++ b/node_modules/discord.js/src/client/actions/MessageReactionRemoveEmoji.js @@ -0,0 +1,28 @@ +'use strict'; + +const Action = require('./Action'); +const { Events } = require('../../util/Constants'); + +class MessageReactionRemoveEmoji extends Action { + handle(data) { + const channel = this.getChannel(data); + if (!channel || channel.type === 'voice') return false; + + const message = this.getMessage(data, channel); + if (!message) return false; + + const reaction = this.getReaction(data, message); + if (!reaction) return false; + if (!message.partial) message.reactions.cache.delete(reaction.emoji.id || reaction.emoji.name); + + /** + * Emitted when a bot removes an emoji reaction from a cached message. + * @event Client#messageReactionRemoveEmoji + * @param {MessageReaction} reaction The reaction that was removed + */ + this.client.emit(Events.MESSAGE_REACTION_REMOVE_EMOJI, reaction); + return { reaction }; + } +} + +module.exports = MessageReactionRemoveEmoji; diff --git a/node_modules/discord.js/src/client/actions/MessageUpdate.js b/node_modules/discord.js/src/client/actions/MessageUpdate.js new file mode 100644 index 0000000..07e2aac --- /dev/null +++ b/node_modules/discord.js/src/client/actions/MessageUpdate.js @@ -0,0 +1,24 @@ +'use strict'; + +const Action = require('./Action'); + +class MessageUpdateAction extends Action { + handle(data) { + const channel = this.getChannel(data); + if (channel) { + const { id, channel_id, guild_id, author, timestamp, type } = data; + const message = this.getMessage({ id, channel_id, guild_id, author, timestamp, type }, channel); + if (message) { + message.patch(data); + return { + old: message._edits[0], + updated: message, + }; + } + } + + return {}; + } +} + +module.exports = MessageUpdateAction; diff --git a/node_modules/discord.js/src/client/actions/PresenceUpdate.js b/node_modules/discord.js/src/client/actions/PresenceUpdate.js new file mode 100644 index 0000000..f74fbeb --- /dev/null +++ b/node_modules/discord.js/src/client/actions/PresenceUpdate.js @@ -0,0 +1,44 @@ +'use strict'; + +const Action = require('./Action'); +const { Events } = require('../../util/Constants'); + +class PresenceUpdateAction extends Action { + handle(data) { + let user = this.client.users.cache.get(data.user.id); + if (!user && data.user.username) user = this.client.users.add(data.user); + if (!user) return; + + if (data.user && data.user.username) { + if (!user.equals(data.user)) this.client.actions.UserUpdate.handle(data.user); + } + + const guild = this.client.guilds.cache.get(data.guild_id); + if (!guild) return; + + let oldPresence = guild.presences.cache.get(user.id); + if (oldPresence) oldPresence = oldPresence._clone(); + let member = guild.members.cache.get(user.id); + if (!member && data.status !== 'offline') { + member = guild.members.add({ + user, + roles: data.roles, + deaf: false, + mute: false, + }); + this.client.emit(Events.GUILD_MEMBER_AVAILABLE, member); + } + guild.presences.add(Object.assign(data, { guild })); + if (member && this.client.listenerCount(Events.PRESENCE_UPDATE)) { + /** + * Emitted whenever a guild member's presence (e.g. status, activity) is changed. + * @event Client#presenceUpdate + * @param {?Presence} oldPresence The presence before the update, if one at all + * @param {Presence} newPresence The presence after the update + */ + this.client.emit(Events.PRESENCE_UPDATE, oldPresence, member.presence); + } + } +} + +module.exports = PresenceUpdateAction; diff --git a/node_modules/discord.js/src/client/actions/UserUpdate.js b/node_modules/discord.js/src/client/actions/UserUpdate.js new file mode 100644 index 0000000..7279ca7 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/UserUpdate.js @@ -0,0 +1,34 @@ +'use strict'; + +const Action = require('./Action'); +const { Events } = require('../../util/Constants'); + +class UserUpdateAction extends Action { + handle(data) { + const client = this.client; + + const newUser = client.users.cache.get(data.id); + const oldUser = newUser._update(data); + + if (!oldUser.equals(newUser)) { + /** + * Emitted whenever a user's details (e.g. username) are changed. + * @event Client#userUpdate + * @param {User} oldUser The user before the update + * @param {User} newUser The user after the update + */ + client.emit(Events.USER_UPDATE, oldUser, newUser); + return { + old: oldUser, + updated: newUser, + }; + } + + return { + old: null, + updated: null, + }; + } +} + +module.exports = UserUpdateAction; diff --git a/node_modules/discord.js/src/client/actions/VoiceStateUpdate.js b/node_modules/discord.js/src/client/actions/VoiceStateUpdate.js new file mode 100644 index 0000000..6f2ee9d --- /dev/null +++ b/node_modules/discord.js/src/client/actions/VoiceStateUpdate.js @@ -0,0 +1,44 @@ +'use strict'; + +const Action = require('./Action'); +const VoiceState = require('../../structures/VoiceState'); +const { Events } = require('../../util/Constants'); + +class VoiceStateUpdate extends Action { + handle(data) { + const client = this.client; + const guild = client.guilds.cache.get(data.guild_id); + if (guild) { + // Update the state + const oldState = guild.voiceStates.cache.has(data.user_id) + ? guild.voiceStates.cache.get(data.user_id)._clone() + : new VoiceState(guild, { user_id: data.user_id }); + + const newState = guild.voiceStates.add(data); + + // Get the member + let member = guild.members.cache.get(data.user_id); + if (member && data.member) { + member._patch(data.member); + } else if (data.member && data.member.user && data.member.joined_at) { + member = guild.members.add(data.member); + } + + // Emit event + if (member && member.user.id === client.user.id) { + client.emit('debug', `[VOICE] received voice state update: ${JSON.stringify(data)}`); + client.voice.onVoiceStateUpdate(data); + } + + /** + * Emitted whenever a member changes voice state - e.g. joins/leaves a channel, mutes/unmutes. + * @event Client#voiceStateUpdate + * @param {VoiceState} oldState The voice state before the update + * @param {VoiceState} newState The voice state after the update + */ + client.emit(Events.VOICE_STATE_UPDATE, oldState, newState); + } + } +} + +module.exports = VoiceStateUpdate; diff --git a/node_modules/discord.js/src/client/actions/WebhooksUpdate.js b/node_modules/discord.js/src/client/actions/WebhooksUpdate.js new file mode 100644 index 0000000..fdf9c56 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/WebhooksUpdate.js @@ -0,0 +1,19 @@ +'use strict'; + +const Action = require('./Action'); +const { Events } = require('../../util/Constants'); + +class WebhooksUpdate extends Action { + handle(data) { + const client = this.client; + const channel = client.channels.cache.get(data.channel_id); + /** + * Emitted whenever a guild text channel has its webhooks changed. + * @event Client#webhookUpdate + * @param {TextChannel} channel The channel that had a webhook update + */ + if (channel) client.emit(Events.WEBHOOKS_UPDATE, channel); + } +} + +module.exports = WebhooksUpdate; diff --git a/node_modules/discord.js/src/client/voice/ClientVoiceManager.js b/node_modules/discord.js/src/client/voice/ClientVoiceManager.js new file mode 100644 index 0000000..1eab808 --- /dev/null +++ b/node_modules/discord.js/src/client/voice/ClientVoiceManager.js @@ -0,0 +1,110 @@ +'use strict'; + +const VoiceBroadcast = require('./VoiceBroadcast'); +const VoiceConnection = require('./VoiceConnection'); +const { Error } = require('../../errors'); +const Collection = require('../../util/Collection'); + +/** + * Manages voice connections for the client + */ +class ClientVoiceManager { + constructor(client) { + /** + * The client that instantiated this voice manager + * @type {Client} + * @readonly + * @name ClientVoiceManager#client + */ + Object.defineProperty(this, 'client', { value: client }); + + /** + * A collection mapping connection IDs to the Connection objects + * @type {Collection<Snowflake, VoiceConnection>} + */ + this.connections = new Collection(); + + /** + * Active voice broadcasts that have been created + * @type {VoiceBroadcast[]} + */ + this.broadcasts = []; + } + + /** + * Creates a voice broadcast. + * @returns {VoiceBroadcast} + */ + createBroadcast() { + const broadcast = new VoiceBroadcast(this.client); + this.broadcasts.push(broadcast); + return broadcast; + } + + onVoiceServer({ guild_id, token, endpoint }) { + this.client.emit('debug', `[VOICE] voiceServer guild: ${guild_id} token: ${token} endpoint: ${endpoint}`); + const connection = this.connections.get(guild_id); + if (connection) connection.setTokenAndEndpoint(token, endpoint); + } + + onVoiceStateUpdate({ guild_id, session_id, channel_id }) { + const connection = this.connections.get(guild_id); + this.client.emit('debug', `[VOICE] connection? ${!!connection}, ${guild_id} ${session_id} ${channel_id}`); + if (!connection) return; + if (!channel_id) { + connection._disconnect(); + this.connections.delete(guild_id); + return; + } + connection.channel = this.client.channels.cache.get(channel_id); + connection.setSessionID(session_id); + } + + /** + * Sets up a request to join a voice channel. + * @param {VoiceChannel} channel The voice channel to join + * @returns {Promise<VoiceConnection>} + * @private + */ + joinChannel(channel) { + return new Promise((resolve, reject) => { + if (!channel.joinable) { + throw new Error('VOICE_JOIN_CHANNEL', channel.full); + } + + let connection = this.connections.get(channel.guild.id); + + if (connection) { + if (connection.channel.id !== channel.id) { + this.connections.get(channel.guild.id).updateChannel(channel); + } + resolve(connection); + return; + } else { + connection = new VoiceConnection(this, channel); + connection.on('debug', msg => + this.client.emit('debug', `[VOICE (${channel.guild.id}:${connection.status})]: ${msg}`), + ); + connection.authenticate(); + this.connections.set(channel.guild.id, connection); + } + + connection.once('failed', reason => { + this.connections.delete(channel.guild.id); + reject(reason); + }); + + connection.on('error', reject); + + connection.once('authenticated', () => { + connection.once('ready', () => { + resolve(connection); + connection.removeListener('error', reject); + }); + connection.once('disconnect', () => this.connections.delete(channel.guild.id)); + }); + }); + } +} + +module.exports = ClientVoiceManager; diff --git a/node_modules/discord.js/src/client/voice/VoiceBroadcast.js b/node_modules/discord.js/src/client/voice/VoiceBroadcast.js new file mode 100644 index 0000000..5755b52 --- /dev/null +++ b/node_modules/discord.js/src/client/voice/VoiceBroadcast.js @@ -0,0 +1,111 @@ +'use strict'; + +const EventEmitter = require('events'); +const BroadcastAudioPlayer = require('./player/BroadcastAudioPlayer'); +const PlayInterface = require('./util/PlayInterface'); +const { Events } = require('../../util/Constants'); + +/** + * A voice broadcast can be played across multiple voice connections for improved shared-stream efficiency. + * + * Example usage: + * ```js + * const broadcast = client.voice.createBroadcast(); + * broadcast.play('./music.mp3'); + * // Play "music.mp3" in all voice connections that the client is in + * for (const connection of client.voice.connections.values()) { + * connection.play(broadcast); + * } + * ``` + * @implements {PlayInterface} + * @extends {EventEmitter} + */ +class VoiceBroadcast extends EventEmitter { + constructor(client) { + super(); + /** + * The client that created the broadcast + * @type {Client} + */ + this.client = client; + /** + * The subscribed StreamDispatchers of this broadcast + * @type {StreamDispatcher[]} + */ + this.subscribers = []; + this.player = new BroadcastAudioPlayer(this); + } + + /** + * The current master dispatcher, if any. This dispatcher controls all that is played by subscribed dispatchers. + * @type {?BroadcastDispatcher} + * @readonly + */ + get dispatcher() { + return this.player.dispatcher; + } + + /** + * Play an audio resource. + * @param {ReadableStream|string} resource The resource to play. + * @param {StreamOptions} [options] The options to play. + * @example + * // Play a local audio file + * broadcast.play('/home/hydrabolt/audio.mp3', { volume: 0.5 }); + * @example + * // Play a ReadableStream + * broadcast.play(ytdl('https://www.youtube.com/watch?v=ZlAU_w7-Xp8', { filter: 'audioonly' })); + * @example + * // Using different protocols: https://ffmpeg.org/ffmpeg-protocols.html + * broadcast.play('http://www.sample-videos.com/audio/mp3/wave.mp3'); + * @returns {BroadcastDispatcher} + */ + play() { + return null; + } + + /** + * Ends the broadcast, unsubscribing all subscribed channels and deleting the broadcast + */ + end() { + for (const dispatcher of this.subscribers) this.delete(dispatcher); + const index = this.client.voice.broadcasts.indexOf(this); + if (index !== -1) this.client.voice.broadcasts.splice(index, 1); + } + + add(dispatcher) { + const index = this.subscribers.indexOf(dispatcher); + if (index === -1) { + this.subscribers.push(dispatcher); + /** + * Emitted whenever a stream dispatcher subscribes to the broadcast. + * @event VoiceBroadcast#subscribe + * @param {StreamDispatcher} subscriber The subscribed dispatcher + */ + this.emit(Events.VOICE_BROADCAST_SUBSCRIBE, dispatcher); + return true; + } else { + return false; + } + } + + delete(dispatcher) { + const index = this.subscribers.indexOf(dispatcher); + if (index !== -1) { + this.subscribers.splice(index, 1); + dispatcher.destroy(); + /** + * Emitted whenever a stream dispatcher unsubscribes to the broadcast. + * @event VoiceBroadcast#unsubscribe + * @param {StreamDispatcher} dispatcher The unsubscribed dispatcher + */ + this.emit(Events.VOICE_BROADCAST_UNSUBSCRIBE, dispatcher); + return true; + } + return false; + } +} + +PlayInterface.applyToClass(VoiceBroadcast); + +module.exports = VoiceBroadcast; diff --git a/node_modules/discord.js/src/client/voice/VoiceConnection.js b/node_modules/discord.js/src/client/voice/VoiceConnection.js new file mode 100644 index 0000000..5f0e995 --- /dev/null +++ b/node_modules/discord.js/src/client/voice/VoiceConnection.js @@ -0,0 +1,523 @@ +'use strict'; + +const EventEmitter = require('events'); +const VoiceUDP = require('./networking/VoiceUDPClient'); +const VoiceWebSocket = require('./networking/VoiceWebSocket'); +const AudioPlayer = require('./player/AudioPlayer'); +const VoiceReceiver = require('./receiver/Receiver'); +const PlayInterface = require('./util/PlayInterface'); +const Silence = require('./util/Silence'); +const { Error } = require('../../errors'); +const { OPCodes, VoiceOPCodes, VoiceStatus, Events } = require('../../util/Constants'); +const Speaking = require('../../util/Speaking'); +const Util = require('../../util/Util'); + +// Workaround for Discord now requiring silence to be sent before being able to receive audio +class SingleSilence extends Silence { + _read() { + super._read(); + this.push(null); + } +} + +const SUPPORTED_MODES = ['xsalsa20_poly1305_lite', 'xsalsa20_poly1305_suffix', 'xsalsa20_poly1305']; + +/** + * Represents a connection to a guild's voice server. + * ```js + * // Obtained using: + * voiceChannel.join() + * .then(connection => { + * + * }); + * ``` + * @extends {EventEmitter} + * @implements {PlayInterface} + */ +class VoiceConnection extends EventEmitter { + constructor(voiceManager, channel) { + super(); + + /** + * The voice manager that instantiated this connection + * @type {ClientVoiceManager} + */ + this.voiceManager = voiceManager; + + /** + * The voice channel this connection is currently serving + * @type {VoiceChannel} + */ + this.channel = channel; + + /** + * The current status of the voice connection + * @type {VoiceStatus} + */ + this.status = VoiceStatus.AUTHENTICATING; + + /** + * Our current speaking state + * @type {Readonly<Speaking>} + */ + this.speaking = new Speaking().freeze(); + + /** + * The authentication data needed to connect to the voice server + * @type {Object} + * @private + */ + this.authentication = {}; + + /** + * The audio player for this voice connection + * @type {AudioPlayer} + */ + this.player = new AudioPlayer(this); + + this.player.on('debug', m => { + /** + * Debug info from the connection. + * @event VoiceConnection#debug + * @param {string} message The debug message + */ + this.emit('debug', `audio player - ${m}`); + }); + + this.player.on('error', e => { + /** + * Warning info from the connection. + * @event VoiceConnection#warn + * @param {string|Error} warning The warning + */ + this.emit('warn', e); + }); + + this.once('closing', () => this.player.destroy()); + + /** + * Map SSRC values to user IDs + * @type {Map<number, Snowflake>} + * @private + */ + this.ssrcMap = new Map(); + + /** + * Tracks which users are talking + * @type {Map<Snowflake, Readonly<Speaking>>} + * @private + */ + this._speaking = new Map(); + + /** + * Object that wraps contains the `ws` and `udp` sockets of this voice connection + * @type {Object} + * @private + */ + this.sockets = {}; + + /** + * The voice receiver of this connection + * @type {VoiceReceiver} + */ + this.receiver = new VoiceReceiver(this); + } + + /** + * The client that instantiated this connection + * @type {Client} + * @readonly + */ + get client() { + return this.voiceManager.client; + } + + /** + * The current stream dispatcher (if any) + * @type {?StreamDispatcher} + * @readonly + */ + get dispatcher() { + return this.player.dispatcher; + } + + /** + * Sets whether the voice connection should display as "speaking", "soundshare" or "none". + * @param {BitFieldResolvable} value The new speaking state + * @private + */ + setSpeaking(value) { + if (this.speaking.equals(value)) return; + if (this.status !== VoiceStatus.CONNECTED) return; + this.speaking = new Speaking(value).freeze(); + this.sockets.ws + .sendPacket({ + op: VoiceOPCodes.SPEAKING, + d: { + speaking: this.speaking.bitfield, + delay: 0, + ssrc: this.authentication.ssrc, + }, + }) + .catch(e => { + this.emit('debug', e); + }); + } + + /** + * The voice state of this connection + * @type {VoiceState} + */ + get voice() { + return this.channel.guild.voice; + } + + /** + * Sends a request to the main gateway to join a voice channel. + * @param {Object} [options] The options to provide + * @returns {Promise<Shard>} + * @private + */ + sendVoiceStateUpdate(options = {}) { + options = Util.mergeDefault( + { + guild_id: this.channel.guild.id, + channel_id: this.channel.id, + self_mute: this.voice ? this.voice.selfMute : false, + self_deaf: this.voice ? this.voice.selfDeaf : false, + }, + options, + ); + + this.emit('debug', `Sending voice state update: ${JSON.stringify(options)}`); + + return this.channel.guild.shard.send( + { + op: OPCodes.VOICE_STATE_UPDATE, + d: options, + }, + true, + ); + } + + /** + * Set the token and endpoint required to connect to the voice servers. + * @param {string} token The voice token + * @param {string} endpoint The voice endpoint + * @private + * @returns {void} + */ + setTokenAndEndpoint(token, endpoint) { + this.emit('debug', `Token "${token}" and endpoint "${endpoint}"`); + if (!endpoint) { + // Signifies awaiting endpoint stage + return; + } + + if (!token) { + this.authenticateFailed('VOICE_TOKEN_ABSENT'); + return; + } + + endpoint = endpoint.match(/([^:]*)/)[0]; + this.emit('debug', `Endpoint resolved as ${endpoint}`); + + if (!endpoint) { + this.authenticateFailed('VOICE_INVALID_ENDPOINT'); + return; + } + + if (this.status === VoiceStatus.AUTHENTICATING) { + this.authentication.token = token; + this.authentication.endpoint = endpoint; + this.checkAuthenticated(); + } else if (token !== this.authentication.token || endpoint !== this.authentication.endpoint) { + this.reconnect(token, endpoint); + } + } + + /** + * Sets the Session ID for the connection. + * @param {string} sessionID The voice session ID + * @private + */ + setSessionID(sessionID) { + this.emit('debug', `Setting sessionID ${sessionID} (stored as "${this.authentication.sessionID}")`); + if (!sessionID) { + this.authenticateFailed('VOICE_SESSION_ABSENT'); + return; + } + + if (this.status === VoiceStatus.AUTHENTICATING) { + this.authentication.sessionID = sessionID; + this.checkAuthenticated(); + } else if (sessionID !== this.authentication.sessionID) { + this.authentication.sessionID = sessionID; + /** + * Emitted when a new session ID is received. + * @event VoiceConnection#newSession + * @private + */ + this.emit('newSession', sessionID); + } + } + + /** + * Checks whether the voice connection is authenticated. + * @private + */ + checkAuthenticated() { + const { token, endpoint, sessionID } = this.authentication; + this.emit('debug', `Authenticated with sessionID ${sessionID}`); + if (token && endpoint && sessionID) { + this.status = VoiceStatus.CONNECTING; + /** + * Emitted when we successfully initiate a voice connection. + * @event VoiceConnection#authenticated + */ + this.emit('authenticated'); + this.connect(); + } + } + + /** + * Invoked when we fail to initiate a voice connection. + * @param {string} reason The reason for failure + * @private + */ + authenticateFailed(reason) { + this.client.clearTimeout(this.connectTimeout); + this.emit('debug', `Authenticate failed - ${reason}`); + if (this.status === VoiceStatus.AUTHENTICATING) { + /** + * Emitted when we fail to initiate a voice connection. + * @event VoiceConnection#failed + * @param {Error} error The encountered error + */ + this.emit('failed', new Error(reason)); + } else { + /** + * Emitted whenever the connection encounters an error. + * @event VoiceConnection#error + * @param {Error} error The encountered error + */ + this.emit('error', new Error(reason)); + } + this.status = VoiceStatus.DISCONNECTED; + } + + /** + * Move to a different voice channel in the same guild. + * @param {VoiceChannel} channel The channel to move to + * @private + */ + updateChannel(channel) { + this.channel = channel; + this.sendVoiceStateUpdate(); + } + + /** + * Attempts to authenticate to the voice server. + * @private + */ + authenticate() { + this.sendVoiceStateUpdate(); + this.connectTimeout = this.client.setTimeout(() => this.authenticateFailed('VOICE_CONNECTION_TIMEOUT'), 15000); + } + + /** + * Attempts to reconnect to the voice server (typically after a region change). + * @param {string} token The voice token + * @param {string} endpoint The voice endpoint + * @private + */ + reconnect(token, endpoint) { + this.authentication.token = token; + this.authentication.endpoint = endpoint; + this.speaking = new Speaking().freeze(); + this.status = VoiceStatus.RECONNECTING; + this.emit('debug', `Reconnecting to ${endpoint}`); + /** + * Emitted when the voice connection is reconnecting (typically after a region change). + * @event VoiceConnection#reconnecting + */ + this.emit('reconnecting'); + this.connect(); + } + + /** + * Disconnects the voice connection, causing a disconnect and closing event to be emitted. + */ + disconnect() { + this.emit('closing'); + this.emit('debug', 'disconnect() triggered'); + this.client.clearTimeout(this.connectTimeout); + const conn = this.voiceManager.connections.get(this.channel.guild.id); + if (conn === this) this.voiceManager.connections.delete(this.channel.guild.id); + this.sendVoiceStateUpdate({ + channel_id: null, + }); + this._disconnect(); + } + + /** + * Internally disconnects (doesn't send disconnect packet). + * @private + */ + _disconnect() { + this.cleanup(); + this.status = VoiceStatus.DISCONNECTED; + /** + * Emitted when the voice connection disconnects. + * @event VoiceConnection#disconnect + */ + this.emit('disconnect'); + } + + /** + * Cleans up after disconnect. + * @private + */ + cleanup() { + this.player.destroy(); + this.speaking = new Speaking().freeze(); + const { ws, udp } = this.sockets; + + this.emit('debug', 'Connection clean up'); + + if (ws) { + ws.removeAllListeners('error'); + ws.removeAllListeners('ready'); + ws.removeAllListeners('sessionDescription'); + ws.removeAllListeners('speaking'); + ws.shutdown(); + } + + if (udp) udp.removeAllListeners('error'); + + this.sockets.ws = null; + this.sockets.udp = null; + } + + /** + * Connect the voice connection. + * @private + */ + connect() { + this.emit('debug', `Connect triggered`); + if (this.status !== VoiceStatus.RECONNECTING) { + if (this.sockets.ws) throw new Error('WS_CONNECTION_EXISTS'); + if (this.sockets.udp) throw new Error('UDP_CONNECTION_EXISTS'); + } + + if (this.sockets.ws) this.sockets.ws.shutdown(); + if (this.sockets.udp) this.sockets.udp.shutdown(); + + this.sockets.ws = new VoiceWebSocket(this); + this.sockets.udp = new VoiceUDP(this); + + const { ws, udp } = this.sockets; + + ws.on('debug', msg => this.emit('debug', msg)); + udp.on('debug', msg => this.emit('debug', msg)); + ws.on('error', err => this.emit('error', err)); + udp.on('error', err => this.emit('error', err)); + ws.on('ready', this.onReady.bind(this)); + ws.on('sessionDescription', this.onSessionDescription.bind(this)); + ws.on('startSpeaking', this.onStartSpeaking.bind(this)); + + this.sockets.ws.connect(); + } + + /** + * Invoked when the voice websocket is ready. + * @param {Object} data The received data + * @private + */ + onReady(data) { + Object.assign(this.authentication, data); + for (let mode of data.modes) { + if (SUPPORTED_MODES.includes(mode)) { + this.authentication.mode = mode; + this.emit('debug', `Selecting the ${mode} mode`); + break; + } + } + this.sockets.udp.createUDPSocket(data.ip); + } + + /** + * Invoked when a session description is received. + * @param {Object} data The received data + * @private + */ + onSessionDescription(data) { + Object.assign(this.authentication, data); + this.status = VoiceStatus.CONNECTED; + const ready = () => { + this.client.clearTimeout(this.connectTimeout); + this.emit('debug', `Ready with authentication details: ${JSON.stringify(this.authentication)}`); + /** + * Emitted once the connection is ready, when a promise to join a voice channel resolves, + * the connection will already be ready. + * @event VoiceConnection#ready + */ + this.emit('ready'); + }; + if (this.dispatcher) { + ready(); + } else { + // This serves to provide support for voice receive, sending audio is required to receive it. + const dispatcher = this.play(new SingleSilence(), { type: 'opus', volume: false }); + dispatcher.once('finish', ready); + } + } + + onStartSpeaking({ user_id, ssrc, speaking }) { + this.ssrcMap.set(+ssrc, { userID: user_id, speaking: speaking }); + } + + /** + * Invoked when a speaking event is received. + * @param {Object} data The received data + * @private + */ + onSpeaking({ user_id, speaking }) { + speaking = new Speaking(speaking).freeze(); + const guild = this.channel.guild; + const user = this.client.users.cache.get(user_id); + const old = this._speaking.get(user_id); + this._speaking.set(user_id, speaking); + /** + * Emitted whenever a user changes speaking state. + * @event VoiceConnection#speaking + * @param {User} user The user that has changed speaking state + * @param {Readonly<Speaking>} speaking The speaking state of the user + */ + if (this.status === VoiceStatus.CONNECTED) { + this.emit('speaking', user, speaking); + if (!speaking.has(Speaking.FLAGS.SPEAKING)) { + this.receiver.packets._stoppedSpeaking(user_id); + } + } + + if (guild && user && !speaking.equals(old)) { + const member = guild.member(user); + if (member) { + /** + * Emitted once a guild member changes speaking state. + * @event Client#guildMemberSpeaking + * @param {GuildMember} member The member that started/stopped speaking + * @param {Readonly<Speaking>} speaking The speaking state of the member + */ + this.client.emit(Events.GUILD_MEMBER_SPEAKING, member, speaking); + } + } + } + + play() {} // eslint-disable-line no-empty-function +} + +PlayInterface.applyToClass(VoiceConnection); + +module.exports = VoiceConnection; diff --git a/node_modules/discord.js/src/client/voice/dispatcher/BroadcastDispatcher.js b/node_modules/discord.js/src/client/voice/dispatcher/BroadcastDispatcher.js new file mode 100644 index 0000000..ae8d412 --- /dev/null +++ b/node_modules/discord.js/src/client/voice/dispatcher/BroadcastDispatcher.js @@ -0,0 +1,46 @@ +'use strict'; + +const StreamDispatcher = require('./StreamDispatcher'); + +/** + * The class that sends voice packet data to the voice connection. + * @implements {VolumeInterface} + * @extends {StreamDispatcher} + */ +class BroadcastDispatcher extends StreamDispatcher { + constructor(player, options, streams) { + super(player, options, streams); + this.broadcast = player.broadcast; + } + + _write(chunk, enc, done) { + if (!this.startTime) this.startTime = Date.now(); + for (const dispatcher of this.broadcast.subscribers) { + dispatcher._write(chunk, enc); + } + this._step(done); + } + + _destroy(err, cb) { + if (this.player.dispatcher === this) this.player.dispatcher = null; + const { streams } = this; + if (streams.opus) streams.opus.unpipe(this); + if (streams.ffmpeg) streams.ffmpeg.destroy(); + super._destroy(err, cb); + } + + /** + * Set the bitrate of the current Opus encoder if using a compatible Opus stream. + * @param {number} value New bitrate, in kbps + * If set to 'auto', 48kbps will be used + * @returns {boolean} true if the bitrate has been successfully changed. + */ + setBitrate(value) { + if (!value || !this.streams.opus || !this.streams.opus.setBitrate) return false; + const bitrate = value === 'auto' ? 48 : value; + this.streams.opus.setBitrate(bitrate * 1000); + return true; + } +} + +module.exports = BroadcastDispatcher; diff --git a/node_modules/discord.js/src/client/voice/dispatcher/StreamDispatcher.js b/node_modules/discord.js/src/client/voice/dispatcher/StreamDispatcher.js new file mode 100644 index 0000000..62c46d3 --- /dev/null +++ b/node_modules/discord.js/src/client/voice/dispatcher/StreamDispatcher.js @@ -0,0 +1,354 @@ +'use strict'; + +const { Writable } = require('stream'); +const secretbox = require('../util/Secretbox'); +const Silence = require('../util/Silence'); +const VolumeInterface = require('../util/VolumeInterface'); + +const FRAME_LENGTH = 20; +const CHANNELS = 2; +const TIMESTAMP_INC = (48000 / 100) * CHANNELS; + +const MAX_NONCE_SIZE = 2 ** 32 - 1; +const nonce = Buffer.alloc(24); + +/** + * @external WritableStream + * @see {@link https://nodejs.org/api/stream.html#stream_class_stream_writable} + */ + +/** + * The class that sends voice packet data to the voice connection. + * ```js + * // Obtained using: + * voiceChannel.join().then(connection => { + * // You can play a file or a stream here: + * const dispatcher = connection.play('/home/hydrabolt/audio.mp3'); + * }); + * ``` + * @implements {VolumeInterface} + * @extends {WritableStream} + */ +class StreamDispatcher extends Writable { + constructor(player, { seek = 0, volume = 1, fec, plp, bitrate = 96, highWaterMark = 12 } = {}, streams) { + const streamOptions = { seek, volume, fec, plp, bitrate, highWaterMark }; + super(streamOptions); + /** + * The Audio Player that controls this dispatcher + * @type {AudioPlayer} + */ + this.player = player; + this.streamOptions = streamOptions; + this.streams = streams; + this.streams.silence = new Silence(); + + this._nonce = 0; + this._nonceBuffer = Buffer.alloc(24); + + /** + * The time that the stream was paused at (null if not paused) + * @type {?number} + */ + this.pausedSince = null; + this._writeCallback = null; + + /** + * The broadcast controlling this dispatcher, if any + * @type {?VoiceBroadcast} + */ + this.broadcast = this.streams.broadcast; + + this._pausedTime = 0; + this._silentPausedTime = 0; + this.count = 0; + + this.on('finish', () => { + this._cleanup(); + this._setSpeaking(0); + }); + + this.setVolume(volume); + this.setBitrate(bitrate); + if (typeof fec !== 'undefined') this.setFEC(fec); + if (typeof plp !== 'undefined') this.setPLP(plp); + + const streamError = (type, err) => { + /** + * Emitted when the dispatcher encounters an error. + * @event StreamDispatcher#error + */ + if (type && err) { + err.message = `${type} stream: ${err.message}`; + this.emit(this.player.dispatcher === this ? 'error' : 'debug', err); + } + this.destroy(); + }; + + this.on('error', () => streamError()); + if (this.streams.input) this.streams.input.on('error', err => streamError('input', err)); + if (this.streams.ffmpeg) this.streams.ffmpeg.on('error', err => streamError('ffmpeg', err)); + if (this.streams.opus) this.streams.opus.on('error', err => streamError('opus', err)); + if (this.streams.volume) this.streams.volume.on('error', err => streamError('volume', err)); + } + + get _sdata() { + return this.player.streamingData; + } + + _write(chunk, enc, done) { + if (!this.startTime) { + /** + * Emitted once the stream has started to play. + * @event StreamDispatcher#start + */ + this.emit('start'); + this.startTime = Date.now(); + } + this._playChunk(chunk); + this._step(done); + } + + _destroy(err, cb) { + this._cleanup(); + super._destroy(err, cb); + } + + _cleanup() { + if (this.player.dispatcher === this) this.player.dispatcher = null; + const { streams } = this; + if (streams.broadcast) streams.broadcast.delete(this); + if (streams.opus) streams.opus.destroy(); + if (streams.ffmpeg) streams.ffmpeg.destroy(); + } + + /** + * Pauses playback + * @param {boolean} [silence=false] Whether to play silence while paused to prevent audio glitches + */ + pause(silence = false) { + if (this.paused) return; + if (this.streams.opus) this.streams.opus.unpipe(this); + if (silence) { + this.streams.silence.pipe(this); + this._silence = true; + } else { + this._setSpeaking(0); + } + this.pausedSince = Date.now(); + } + + /** + * Whether or not playback is paused + * @type {boolean} + * @readonly + */ + get paused() { + return Boolean(this.pausedSince); + } + + /** + * Total time that this dispatcher has been paused in milliseconds + * @type {number} + * @readonly + */ + get pausedTime() { + return this._silentPausedTime + this._pausedTime + (this.paused ? Date.now() - this.pausedSince : 0); + } + + /** + * Resumes playback + */ + resume() { + if (!this.pausedSince) return; + this.streams.silence.unpipe(this); + if (this.streams.opus) this.streams.opus.pipe(this); + if (this._silence) { + this._silentPausedTime += Date.now() - this.pausedSince; + this._silence = false; + } else { + this._pausedTime += Date.now() - this.pausedSince; + } + this.pausedSince = null; + if (typeof this._writeCallback === 'function') this._writeCallback(); + } + + /** + * The time (in milliseconds) that the dispatcher has actually been playing audio for + * @type {number} + * @readonly + */ + get streamTime() { + return this.count * FRAME_LENGTH; + } + + /** + * The time (in milliseconds) that the dispatcher has been playing audio for, taking into account skips and pauses + * @type {number} + * @readonly + */ + get totalStreamTime() { + return Date.now() - this.startTime; + } + + /** + * Set the bitrate of the current Opus encoder if using a compatible Opus stream. + * @param {number} value New bitrate, in kbps + * If set to 'auto', the voice channel's bitrate will be used + * @returns {boolean} true if the bitrate has been successfully changed. + */ + setBitrate(value) { + if (!value || !this.bitrateEditable) return false; + const bitrate = value === 'auto' ? this.player.voiceConnection.channel.bitrate : value; + this.streams.opus.setBitrate(bitrate * 1000); + return true; + } + + /** + * Sets the expected packet loss percentage if using a compatible Opus stream. + * @param {number} value between 0 and 1 + * @returns {boolean} Returns true if it was successfully set. + */ + setPLP(value) { + if (!this.bitrateEditable) return false; + this.streams.opus.setPLP(value); + return true; + } + + /** + * Enables or disables forward error correction if using a compatible Opus stream. + * @param {boolean} enabled true to enable + * @returns {boolean} Returns true if it was successfully set. + */ + setFEC(enabled) { + if (!this.bitrateEditable) return false; + this.streams.opus.setFEC(enabled); + return true; + } + + _step(done) { + this._writeCallback = () => { + this._writeCallback = null; + done(); + }; + if (!this.streams.broadcast) { + const next = FRAME_LENGTH + this.count * FRAME_LENGTH - (Date.now() - this.startTime - this._pausedTime); + setTimeout(() => { + if ((!this.pausedSince || this._silence) && this._writeCallback) this._writeCallback(); + }, next); + } + this._sdata.sequence++; + this._sdata.timestamp += TIMESTAMP_INC; + if (this._sdata.sequence >= 2 ** 16) this._sdata.sequence = 0; + if (this._sdata.timestamp >= 2 ** 32) this._sdata.timestamp = 0; + this.count++; + } + + _final(callback) { + this._writeCallback = null; + callback(); + } + + _playChunk(chunk) { + if (this.player.dispatcher !== this || !this.player.voiceConnection.authentication.secret_key) return; + this._sendPacket(this._createPacket(this._sdata.sequence, this._sdata.timestamp, chunk)); + } + + _encrypt(buffer) { + const { secret_key, mode } = this.player.voiceConnection.authentication; + if (mode === 'xsalsa20_poly1305_lite') { + this._nonce++; + if (this._nonce > MAX_NONCE_SIZE) this._nonce = 0; + this._nonceBuffer.writeUInt32BE(this._nonce, 0); + return [secretbox.methods.close(buffer, this._nonceBuffer, secret_key), this._nonceBuffer.slice(0, 4)]; + } else if (mode === 'xsalsa20_poly1305_suffix') { + const random = secretbox.methods.random(24); + return [secretbox.methods.close(buffer, random, secret_key), random]; + } else { + return [secretbox.methods.close(buffer, nonce, secret_key)]; + } + } + + _createPacket(sequence, timestamp, buffer) { + const packetBuffer = Buffer.alloc(12); + packetBuffer[0] = 0x80; + packetBuffer[1] = 0x78; + + packetBuffer.writeUIntBE(sequence, 2, 2); + packetBuffer.writeUIntBE(timestamp, 4, 4); + packetBuffer.writeUIntBE(this.player.voiceConnection.authentication.ssrc, 8, 4); + + packetBuffer.copy(nonce, 0, 0, 12); + return Buffer.concat([packetBuffer, ...this._encrypt(buffer)]); + } + + _sendPacket(packet) { + /** + * Emitted whenever the dispatcher has debug information. + * @event StreamDispatcher#debug + * @param {string} info The debug info + */ + this._setSpeaking(1); + if (!this.player.voiceConnection.sockets.udp) { + this.emit('debug', 'Failed to send a packet - no UDP socket'); + return; + } + this.player.voiceConnection.sockets.udp.send(packet).catch(e => { + this._setSpeaking(0); + this.emit('debug', `Failed to send a packet - ${e}`); + }); + } + + _setSpeaking(value) { + if (typeof this.player.voiceConnection !== 'undefined') { + this.player.voiceConnection.setSpeaking(value); + } + /** + * Emitted when the dispatcher starts/stops speaking. + * @event StreamDispatcher#speaking + * @param {boolean} value Whether or not the dispatcher is speaking + */ + this.emit('speaking', value); + } + + get volumeEditable() { + return Boolean(this.streams.volume); + } + + /** + * Whether or not the Opus bitrate of this stream is editable + * @type {boolean} + * @readonly + */ + get bitrateEditable() { + return this.streams.opus && this.streams.opus.setBitrate; + } + + // Volume + get volume() { + return this.streams.volume ? this.streams.volume.volume : 1; + } + + setVolume(value) { + if (!this.streams.volume) return false; + /** + * Emitted when the volume of this dispatcher changes. + * @event StreamDispatcher#volumeChange + * @param {number} oldVolume The old volume of this dispatcher + * @param {number} newVolume The new volume of this dispatcher + */ + this.emit('volumeChange', this.volume, value); + this.streams.volume.setVolume(value); + return true; + } + + // Volume stubs for docs + /* eslint-disable no-empty-function*/ + get volumeDecibels() {} + get volumeLogarithmic() {} + setVolumeDecibels() {} + setVolumeLogarithmic() {} +} + +VolumeInterface.applyToClass(StreamDispatcher); + +module.exports = StreamDispatcher; diff --git a/node_modules/discord.js/src/client/voice/networking/VoiceUDPClient.js b/node_modules/discord.js/src/client/voice/networking/VoiceUDPClient.js new file mode 100644 index 0000000..b86428a --- /dev/null +++ b/node_modules/discord.js/src/client/voice/networking/VoiceUDPClient.js @@ -0,0 +1,154 @@ +'use strict'; + +const udp = require('dgram'); +const EventEmitter = require('events'); +const { Error } = require('../../../errors'); +const { VoiceOPCodes } = require('../../../util/Constants'); + +/** + * Represents a UDP client for a Voice Connection. + * @extends {EventEmitter} + * @private + */ +class VoiceConnectionUDPClient extends EventEmitter { + constructor(voiceConnection) { + super(); + + /** + * The voice connection that this UDP client serves + * @type {VoiceConnection} + */ + this.voiceConnection = voiceConnection; + + /** + * The UDP socket + * @type {?Socket} + */ + this.socket = null; + + /** + * The address of the Discord voice server + * @type {?string} + */ + this.discordAddress = null; + + /** + * The local IP address + * @type {?string} + */ + this.localAddress = null; + + /** + * The local port + * @type {?string} + */ + this.localPort = null; + + this.voiceConnection.on('closing', this.shutdown.bind(this)); + } + + shutdown() { + this.emit('debug', `[UDP] shutdown requested`); + if (this.socket) { + this.socket.removeAllListeners('message'); + try { + this.socket.close(); + } finally { + this.socket = null; + } + } + } + + /** + * The port of the Discord voice server + * @type {number} + * @readonly + */ + get discordPort() { + return this.voiceConnection.authentication.port; + } + + /** + * Send a packet to the UDP client. + * @param {Object} packet The packet to send + * @returns {Promise<Object>} + */ + send(packet) { + return new Promise((resolve, reject) => { + if (!this.socket) throw new Error('UDP_SEND_FAIL'); + if (!this.discordAddress || !this.discordPort) throw new Error('UDP_ADDRESS_MALFORMED'); + this.socket.send(packet, 0, packet.length, this.discordPort, this.discordAddress, error => { + if (error) { + this.emit('debug', `[UDP] >> ERROR: ${error}`); + reject(error); + } else { + resolve(packet); + } + }); + }); + } + + async createUDPSocket(address) { + this.discordAddress = address; + const socket = (this.socket = udp.createSocket('udp4')); + socket.on('error', e => { + this.emit('debug', `[UDP] Error: ${e}`); + this.emit('error', e); + }); + socket.on('close', () => { + this.emit('debug', '[UDP] socket closed'); + }); + this.emit('debug', `[UDP] created socket`); + socket.once('message', message => { + this.emit('debug', `[UDP] message: [${[...message]}] (${message})`); + // Stop if the sockets have been deleted because the connection has been closed already + if (!this.voiceConnection.sockets.ws) return; + + const packet = parseLocalPacket(message); + if (packet.error) { + this.emit('debug', `[UDP] ERROR: ${packet.error}`); + this.emit('error', packet.error); + return; + } + + this.localAddress = packet.address; + this.localPort = packet.port; + + this.voiceConnection.sockets.ws.sendPacket({ + op: VoiceOPCodes.SELECT_PROTOCOL, + d: { + protocol: 'udp', + data: { + address: packet.address, + port: packet.port, + mode: this.voiceConnection.authentication.mode, + }, + }, + }); + + this.emit('debug', `[UDP] << ${JSON.stringify(packet)}`); + + socket.on('message', buffer => this.voiceConnection.receiver.packets.push(buffer)); + }); + + const blankMessage = Buffer.alloc(70); + blankMessage.writeUIntBE(this.voiceConnection.authentication.ssrc, 0, 4); + this.emit('debug', `Sending IP discovery packet: [${[...blankMessage]}]`); + await this.send(blankMessage); + this.emit('debug', `Successfully sent IP discovery packet`); + } +} + +function parseLocalPacket(message) { + try { + const packet = Buffer.from(message); + let address = ''; + for (let i = 4; i < packet.indexOf(0, i); i++) address += String.fromCharCode(packet[i]); + const port = parseInt(packet.readUIntLE(packet.length - 2, 2).toString(10), 10); + return { address, port }; + } catch (error) { + return { error }; + } +} + +module.exports = VoiceConnectionUDPClient; diff --git a/node_modules/discord.js/src/client/voice/networking/VoiceWebSocket.js b/node_modules/discord.js/src/client/voice/networking/VoiceWebSocket.js new file mode 100644 index 0000000..efc97af --- /dev/null +++ b/node_modules/discord.js/src/client/voice/networking/VoiceWebSocket.js @@ -0,0 +1,264 @@ +'use strict'; + +const EventEmitter = require('events'); +const WebSocket = require('../../../WebSocket'); +const { Error } = require('../../../errors'); +const { OPCodes, VoiceOPCodes } = require('../../../util/Constants'); + +/** + * Represents a Voice Connection's WebSocket. + * @extends {EventEmitter} + * @private + */ +class VoiceWebSocket extends EventEmitter { + constructor(connection) { + super(); + /** + * The Voice Connection that this WebSocket serves + * @type {VoiceConnection} + */ + this.connection = connection; + + /** + * How many connection attempts have been made + * @type {number} + */ + this.attempts = 0; + + this.dead = false; + this.connection.on('closing', this.shutdown.bind(this)); + } + + /** + * The client of this voice WebSocket + * @type {Client} + * @readonly + */ + get client() { + return this.connection.client; + } + + shutdown() { + this.emit('debug', `[WS] shutdown requested`); + this.dead = true; + this.reset(); + } + + /** + * Resets the current WebSocket. + */ + reset() { + this.emit('debug', `[WS] reset requested`); + if (this.ws) { + if (this.ws.readyState !== WebSocket.CLOSED) this.ws.close(); + this.ws = null; + } + this.clearHeartbeat(); + } + + /** + * Starts connecting to the Voice WebSocket Server. + */ + connect() { + this.emit('debug', `[WS] connect requested`); + if (this.dead) return; + if (this.ws) this.reset(); + if (this.attempts >= 5) { + this.emit('debug', new Error('VOICE_CONNECTION_ATTEMPTS_EXCEEDED', this.attempts)); + return; + } + + this.attempts++; + + /** + * The actual WebSocket used to connect to the Voice WebSocket Server. + * @type {WebSocket} + */ + this.ws = WebSocket.create(`wss://${this.connection.authentication.endpoint}/`, { v: 4 }); + this.emit('debug', `[WS] connecting, ${this.attempts} attempts, ${this.ws.url}`); + this.ws.onopen = this.onOpen.bind(this); + this.ws.onmessage = this.onMessage.bind(this); + this.ws.onclose = this.onClose.bind(this); + this.ws.onerror = this.onError.bind(this); + } + + /** + * Sends data to the WebSocket if it is open. + * @param {string} data The data to send to the WebSocket + * @returns {Promise<string>} + */ + send(data) { + this.emit('debug', `[WS] >> ${data}`); + return new Promise((resolve, reject) => { + if (!this.ws || this.ws.readyState !== WebSocket.OPEN) throw new Error('WS_NOT_OPEN', data); + this.ws.send(data, null, error => { + if (error) reject(error); + else resolve(data); + }); + }); + } + + /** + * JSON.stringify's a packet and then sends it to the WebSocket Server. + * @param {Object} packet The packet to send + * @returns {Promise<string>} + */ + sendPacket(packet) { + try { + packet = JSON.stringify(packet); + } catch (error) { + return Promise.reject(error); + } + return this.send(packet); + } + + /** + * Called whenever the WebSocket opens. + */ + onOpen() { + this.emit('debug', `[WS] opened at gateway ${this.connection.authentication.endpoint}`); + this.sendPacket({ + op: OPCodes.DISPATCH, + d: { + server_id: this.connection.channel.guild.id, + user_id: this.client.user.id, + token: this.connection.authentication.token, + session_id: this.connection.authentication.sessionID, + }, + }).catch(() => { + this.emit('error', new Error('VOICE_JOIN_SOCKET_CLOSED')); + }); + } + + /** + * Called whenever a message is received from the WebSocket. + * @param {MessageEvent} event The message event that was received + * @returns {void} + */ + onMessage(event) { + try { + return this.onPacket(WebSocket.unpack(event.data, 'json')); + } catch (error) { + return this.onError(error); + } + } + + /** + * Called whenever the connection to the WebSocket server is lost. + */ + onClose() { + this.emit('debug', `[WS] closed`); + if (!this.dead) this.client.setTimeout(this.connect.bind(this), this.attempts * 1000); + } + + /** + * Called whenever an error occurs with the WebSocket. + * @param {Error} error The error that occurred + */ + onError(error) { + this.emit('debug', `[WS] Error: ${error}`); + this.emit('error', error); + } + + /** + * Called whenever a valid packet is received from the WebSocket. + * @param {Object} packet The received packet + */ + onPacket(packet) { + this.emit('debug', `[WS] << ${JSON.stringify(packet)}`); + switch (packet.op) { + case VoiceOPCodes.HELLO: + this.setHeartbeat(packet.d.heartbeat_interval); + break; + case VoiceOPCodes.READY: + /** + * Emitted once the voice WebSocket receives the ready packet. + * @param {Object} packet The received packet + * @event VoiceWebSocket#ready + */ + this.emit('ready', packet.d); + break; + /* eslint-disable no-case-declarations */ + case VoiceOPCodes.SESSION_DESCRIPTION: + packet.d.secret_key = new Uint8Array(packet.d.secret_key); + /** + * Emitted once the Voice Websocket receives a description of this voice session. + * @param {Object} packet The received packet + * @event VoiceWebSocket#sessionDescription + */ + this.emit('sessionDescription', packet.d); + break; + case VoiceOPCodes.CLIENT_CONNECT: + this.connection.ssrcMap.set(+packet.d.audio_ssrc, packet.d.user_id); + break; + case VoiceOPCodes.CLIENT_DISCONNECT: + const streamInfo = this.connection.receiver && this.connection.receiver.packets.streams.get(packet.d.user_id); + if (streamInfo) { + this.connection.receiver.packets.streams.delete(packet.d.user_id); + streamInfo.stream.push(null); + } + break; + case VoiceOPCodes.SPEAKING: + /** + * Emitted whenever a speaking packet is received. + * @param {Object} data + * @event VoiceWebSocket#startSpeaking + */ + this.emit('startSpeaking', packet.d); + break; + default: + /** + * Emitted when an unhandled packet is received. + * @param {Object} packet + * @event VoiceWebSocket#unknownPacket + */ + this.emit('unknownPacket', packet); + break; + } + } + + /** + * Sets an interval at which to send a heartbeat packet to the WebSocket. + * @param {number} interval The interval at which to send a heartbeat packet + */ + setHeartbeat(interval) { + if (!interval || isNaN(interval)) { + this.onError(new Error('VOICE_INVALID_HEARTBEAT')); + return; + } + if (this.heartbeatInterval) { + /** + * Emitted whenever the voice WebSocket encounters a non-fatal error. + * @param {string} warn The warning + * @event VoiceWebSocket#warn + */ + this.emit('warn', 'A voice heartbeat interval is being overwritten'); + this.client.clearInterval(this.heartbeatInterval); + } + this.heartbeatInterval = this.client.setInterval(this.sendHeartbeat.bind(this), interval); + } + + /** + * Clears a heartbeat interval, if one exists. + */ + clearHeartbeat() { + if (!this.heartbeatInterval) { + this.emit('warn', 'Tried to clear a heartbeat interval that does not exist'); + return; + } + this.client.clearInterval(this.heartbeatInterval); + this.heartbeatInterval = null; + } + + /** + * Sends a heartbeat packet. + */ + sendHeartbeat() { + this.sendPacket({ op: VoiceOPCodes.HEARTBEAT, d: Math.floor(Math.random() * 10e10) }).catch(() => { + this.emit('warn', 'Tried to send heartbeat, but connection is not open'); + this.clearHeartbeat(); + }); + } +} + +module.exports = VoiceWebSocket; diff --git a/node_modules/discord.js/src/client/voice/player/AudioPlayer.js b/node_modules/discord.js/src/client/voice/player/AudioPlayer.js new file mode 100644 index 0000000..6f719a7 --- /dev/null +++ b/node_modules/discord.js/src/client/voice/player/AudioPlayer.js @@ -0,0 +1,27 @@ +'use strict'; + +const BasePlayer = require('./BasePlayer'); + +/** + * An Audio Player for a Voice Connection. + * @private + * @extends {BasePlayer} + */ +class AudioPlayer extends BasePlayer { + constructor(voiceConnection) { + super(); + /** + * The voice connection that the player serves + * @type {VoiceConnection} + */ + this.voiceConnection = voiceConnection; + } + + playBroadcast(broadcast, options) { + const dispatcher = this.createDispatcher(options, { broadcast }); + broadcast.add(dispatcher); + return dispatcher; + } +} + +module.exports = AudioPlayer; diff --git a/node_modules/discord.js/src/client/voice/player/BasePlayer.js b/node_modules/discord.js/src/client/voice/player/BasePlayer.js new file mode 100644 index 0000000..b968f82 --- /dev/null +++ b/node_modules/discord.js/src/client/voice/player/BasePlayer.js @@ -0,0 +1,92 @@ +'use strict'; + +const EventEmitter = require('events'); +const { Readable: ReadableStream } = require('stream'); +const prism = require('prism-media'); +const StreamDispatcher = require('../dispatcher/StreamDispatcher'); + +const FFMPEG_ARGUMENTS = ['-analyzeduration', '0', '-loglevel', '0', '-f', 's16le', '-ar', '48000', '-ac', '2']; + +/** + * An Audio Player for a Voice Connection. + * @private + * @extends {EventEmitter} + */ +class BasePlayer extends EventEmitter { + constructor() { + super(); + + this.dispatcher = null; + + this.streamingData = { + channels: 2, + sequence: 0, + timestamp: 0, + }; + } + + destroy() { + this.destroyDispatcher(); + } + + destroyDispatcher() { + if (this.dispatcher) { + this.dispatcher.destroy(); + this.dispatcher = null; + } + } + + playUnknown(input, options) { + this.destroyDispatcher(); + + const isStream = input instanceof ReadableStream; + + const args = isStream ? FFMPEG_ARGUMENTS.slice() : ['-i', input, ...FFMPEG_ARGUMENTS]; + if (options.seek) args.unshift('-ss', String(options.seek)); + + const ffmpeg = new prism.FFmpeg({ args }); + const streams = { ffmpeg }; + if (isStream) { + streams.input = input; + input.pipe(ffmpeg); + } + return this.playPCMStream(ffmpeg, options, streams); + } + + playPCMStream(stream, options, streams = {}) { + this.destroyDispatcher(); + const opus = (streams.opus = new prism.opus.Encoder({ channels: 2, rate: 48000, frameSize: 960 })); + if (options && options.volume === false) { + stream.pipe(opus); + return this.playOpusStream(opus, options, streams); + } + streams.volume = new prism.VolumeTransformer({ type: 's16le', volume: options ? options.volume : 1 }); + stream.pipe(streams.volume).pipe(opus); + return this.playOpusStream(opus, options, streams); + } + + playOpusStream(stream, options, streams = {}) { + this.destroyDispatcher(); + streams.opus = stream; + if (options.volume !== false && !streams.input) { + streams.input = stream; + const decoder = new prism.opus.Decoder({ channels: 2, rate: 48000, frameSize: 960 }); + streams.volume = new prism.VolumeTransformer({ type: 's16le', volume: options ? options.volume : 1 }); + streams.opus = stream + .pipe(decoder) + .pipe(streams.volume) + .pipe(new prism.opus.Encoder({ channels: 2, rate: 48000, frameSize: 960 })); + } + const dispatcher = this.createDispatcher(options, streams); + streams.opus.pipe(dispatcher); + return dispatcher; + } + + createDispatcher(options, streams, broadcast) { + this.destroyDispatcher(); + const dispatcher = (this.dispatcher = new StreamDispatcher(this, options, streams, broadcast)); + return dispatcher; + } +} + +module.exports = BasePlayer; diff --git a/node_modules/discord.js/src/client/voice/player/BroadcastAudioPlayer.js b/node_modules/discord.js/src/client/voice/player/BroadcastAudioPlayer.js new file mode 100644 index 0000000..05197a4 --- /dev/null +++ b/node_modules/discord.js/src/client/voice/player/BroadcastAudioPlayer.js @@ -0,0 +1,28 @@ +'use strict'; + +const BasePlayer = require('./BasePlayer'); +const BroadcastDispatcher = require('../dispatcher/BroadcastDispatcher'); + +/** + * An Audio Player for a Voice Connection. + * @private + * @extends {BasePlayer} + */ +class AudioPlayer extends BasePlayer { + constructor(broadcast) { + super(); + /** + * The broadcast that the player serves + * @type {VoiceBroadcast} + */ + this.broadcast = broadcast; + } + + createDispatcher(options, streams) { + this.destroyDispatcher(); + const dispatcher = (this.dispatcher = new BroadcastDispatcher(this, options, streams)); + return dispatcher; + } +} + +module.exports = AudioPlayer; diff --git a/node_modules/discord.js/src/client/voice/receiver/PacketHandler.js b/node_modules/discord.js/src/client/voice/receiver/PacketHandler.js new file mode 100644 index 0000000..c441c5e --- /dev/null +++ b/node_modules/discord.js/src/client/voice/receiver/PacketHandler.js @@ -0,0 +1,116 @@ +'use strict'; + +const EventEmitter = require('events'); +const secretbox = require('../util/Secretbox'); + +// The delay between packets when a user is considered to have stopped speaking +// https://github.com/discordjs/discord.js/issues/3524#issuecomment-540373200 +const DISCORD_SPEAKING_DELAY = 250; + +class Readable extends require('stream').Readable { + _read() {} // eslint-disable-line no-empty-function +} + +class PacketHandler extends EventEmitter { + constructor(receiver) { + super(); + this.nonce = Buffer.alloc(24); + this.receiver = receiver; + this.streams = new Map(); + this.speakingTimeouts = new Map(); + } + + get connection() { + return this.receiver.connection; + } + + _stoppedSpeaking(userID) { + const streamInfo = this.streams.get(userID); + if (streamInfo && streamInfo.end === 'silence') { + this.streams.delete(userID); + streamInfo.stream.push(null); + } + } + + makeStream(user, end) { + if (this.streams.has(user)) return this.streams.get(user).stream; + const stream = new Readable(); + stream.on('end', () => this.streams.delete(user)); + this.streams.set(user, { stream, end }); + return stream; + } + + parseBuffer(buffer) { + const { secret_key, mode } = this.receiver.connection.authentication; + + // Choose correct nonce depending on encryption + let end; + if (mode === 'xsalsa20_poly1305_lite') { + buffer.copy(this.nonce, 0, buffer.length - 4); + end = buffer.length - 4; + } else if (mode === 'xsalsa20_poly1305_suffix') { + buffer.copy(this.nonce, 0, buffer.length - 24); + end = buffer.length - 24; + } else { + buffer.copy(this.nonce, 0, 0, 12); + } + + // Open packet + let packet = secretbox.methods.open(buffer.slice(12, end), this.nonce, secret_key); + if (!packet) return new Error('Failed to decrypt voice packet'); + packet = Buffer.from(packet); + + // Strip RTP Header Extensions (one-byte only) + if (packet[0] === 0xbe && packet[1] === 0xde && packet.length > 4) { + const headerExtensionLength = packet.readUInt16BE(2); + let offset = 4; + for (let i = 0; i < headerExtensionLength; i++) { + const byte = packet[offset]; + offset++; + if (byte === 0) continue; + offset += 1 + (0b1111 & (byte >> 4)); + } + // Skip over undocumented Discord byte + offset++; + + packet = packet.slice(offset); + } + + return packet; + } + + push(buffer) { + const ssrc = buffer.readUInt32BE(8); + const userStat = this.connection.ssrcMap.get(ssrc); + if (!userStat) return; + + let speakingTimeout = this.speakingTimeouts.get(ssrc); + if (typeof speakingTimeout === 'undefined') { + this.connection.onSpeaking({ user_id: userStat.userID, ssrc: ssrc, speaking: userStat.speaking }); + speakingTimeout = this.receiver.connection.client.setTimeout(() => { + try { + this.connection.onSpeaking({ user_id: userStat.userID, ssrc: ssrc, speaking: 0 }); + this.receiver.connection.client.clearTimeout(speakingTimeout); + this.speakingTimeouts.delete(ssrc); + } catch { + // Connection already closed, ignore + } + }, DISCORD_SPEAKING_DELAY); + this.speakingTimeouts.set(ssrc, speakingTimeout); + } else { + speakingTimeout.refresh(); + } + + let stream = this.streams.get(userStat.userID); + if (!stream) return; + stream = stream.stream; + const opusPacket = this.parseBuffer(buffer); + if (opusPacket instanceof Error) { + this.emit('error', opusPacket); + return; + } + stream.push(opusPacket); + } +} + +module.exports = PacketHandler; diff --git a/node_modules/discord.js/src/client/voice/receiver/Receiver.js b/node_modules/discord.js/src/client/voice/receiver/Receiver.js new file mode 100644 index 0000000..605d992 --- /dev/null +++ b/node_modules/discord.js/src/client/voice/receiver/Receiver.js @@ -0,0 +1,58 @@ +'use strict'; + +const EventEmitter = require('events'); +const prism = require('prism-media'); +const PacketHandler = require('./PacketHandler'); +const { Error } = require('../../../errors'); + +/** + * Receives audio packets from a voice connection. + * @example + * const receiver = connection.createReceiver(); + * // opusStream is a ReadableStream - that means you could play it back to a voice channel if you wanted to! + * const opusStream = receiver.createStream(user); + */ +class VoiceReceiver extends EventEmitter { + constructor(connection) { + super(); + this.connection = connection; + this.packets = new PacketHandler(this); + /** + * Emitted whenever there is a warning + * @event VoiceReceiver#debug + * @param {Error|string} error The error or message to debug + */ + this.packets.on('error', err => this.emit('debug', err)); + } + + /** + * Options passed to `VoiceReceiver#createStream`. + * @typedef {Object} ReceiveStreamOptions + * @property {string} [mode='opus'] The mode for audio output. This defaults to opus, meaning discord.js won't decode + * the packets for you. You can set this to 'pcm' so that the stream's output will be 16-bit little-endian stereo + * audio + * @property {string} [end='silence'] When the stream should be destroyed. If `silence`, this will be when the user + * stops talking. Otherwise, if `manual`, this should be handled by you. + */ + + /** + * Creates a new audio receiving stream. If a stream already exists for a user, then that stream will be returned + * rather than generating a new one. + * @param {UserResolvable} user The user to start listening to. + * @param {ReceiveStreamOptions} options Options. + * @returns {ReadableStream} + */ + createStream(user, { mode = 'opus', end = 'silence' } = {}) { + user = this.connection.client.users.resolve(user); + if (!user) throw new Error('VOICE_USER_MISSING'); + const stream = this.packets.makeStream(user.id, end); + if (mode === 'pcm') { + const decoder = new prism.opus.Decoder({ channels: 2, rate: 48000, frameSize: 960 }); + stream.pipe(decoder); + return decoder; + } + return stream; + } +} + +module.exports = VoiceReceiver; diff --git a/node_modules/discord.js/src/client/voice/util/PlayInterface.js b/node_modules/discord.js/src/client/voice/util/PlayInterface.js new file mode 100644 index 0000000..9478ee8 --- /dev/null +++ b/node_modules/discord.js/src/client/voice/util/PlayInterface.js @@ -0,0 +1,94 @@ +'use strict'; + +const { Readable } = require('stream'); +const prism = require('prism-media'); +const { Error } = require('../../../errors'); + +/** + * Options that can be passed to stream-playing methods: + * @typedef {Object} StreamOptions + * @property {StreamType} [type='unknown'] The type of stream. + * @property {number} [seek=0] The time to seek to, will be ignored when playing `ogg/opus` or `webm/opus` streams + * @property {number|boolean} [volume=1] The volume to play at. Set this to false to disable volume transforms for + * this stream to improve performance. + * @property {number} [plp] Expected packet loss percentage + * @property {boolean} [fec] Enabled forward error correction + * @property {number|string} [bitrate=96] The bitrate (quality) of the audio in kbps. + * If set to 'auto', the voice channel's bitrate will be used + * @property {number} [highWaterMark=12] The maximum number of opus packets to make and store before they are + * actually needed. See https://nodejs.org/en/docs/guides/backpressuring-in-streams/. Setting this value to + * 1 means that changes in volume will be more instant. + */ + +/** + * An option passed as part of `StreamOptions` specifying the type of the stream. + * * `unknown`: The default type, streams/input will be passed through to ffmpeg before encoding. + * Will play most streams. + * * `converted`: Play a stream of 16bit signed stereo PCM data, skipping ffmpeg. + * * `opus`: Play a stream of opus packets, skipping ffmpeg. You lose the ability to alter volume. + * * `ogg/opus`: Play an ogg file with the opus encoding, skipping ffmpeg. You lose the ability to alter volume. + * * `webm/opus`: Play a webm file with opus audio, skipping ffmpeg. You lose the ability to alter volume. + * @typedef {string} StreamType + */ + +/** + * An interface class to allow you to play audio over VoiceConnections and VoiceBroadcasts. + */ +class PlayInterface { + constructor(player) { + this.player = player; + } + + /** + * Play an audio resource. + * @param {VoiceBroadcast|ReadableStream|string} resource The resource to play. + * @param {StreamOptions} [options] The options to play. + * @example + * // Play a local audio file + * connection.play('/home/hydrabolt/audio.mp3', { volume: 0.5 }); + * @example + * // Play a ReadableStream + * connection.play(ytdl('https://www.youtube.com/watch?v=ZlAU_w7-Xp8', { quality: 'highestaudio' })); + * @example + * // Play a voice broadcast + * const broadcast = client.voice.createBroadcast(); + * broadcast.play('/home/hydrabolt/audio.mp3'); + * connection.play(broadcast); + * @example + * // Using different protocols: https://ffmpeg.org/ffmpeg-protocols.html + * connection.play('http://www.sample-videos.com/audio/mp3/wave.mp3'); + * @returns {StreamDispatcher} + */ + play(resource, options = {}) { + 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); + } + if (resource instanceof Readable || typeof resource === 'string') { + const type = options.type || 'unknown'; + if (type === 'unknown') { + return this.player.playUnknown(resource, options); + } else if (type === 'converted') { + return this.player.playPCMStream(resource, options); + } else if (type === 'opus') { + return this.player.playOpusStream(resource, options); + } else if (type === 'ogg/opus') { + if (!(resource instanceof Readable)) throw new Error('VOICE_PRISM_DEMUXERS_NEED_STREAM'); + return this.player.playOpusStream(resource.pipe(new prism.opus.OggDemuxer()), options); + } else if (type === 'webm/opus') { + if (!(resource instanceof Readable)) throw new Error('VOICE_PRISM_DEMUXERS_NEED_STREAM'); + return this.player.playOpusStream(resource.pipe(new prism.opus.WebmDemuxer()), options); + } + } + throw new Error('VOICE_PLAY_INTERFACE_BAD_TYPE'); + } + + static applyToClass(structure) { + for (const prop of ['play']) { + Object.defineProperty(structure.prototype, prop, Object.getOwnPropertyDescriptor(PlayInterface.prototype, prop)); + } + } +} + +module.exports = PlayInterface; diff --git a/node_modules/discord.js/src/client/voice/util/Secretbox.js b/node_modules/discord.js/src/client/voice/util/Secretbox.js new file mode 100644 index 0000000..c16a435 --- /dev/null +++ b/node_modules/discord.js/src/client/voice/util/Secretbox.js @@ -0,0 +1,32 @@ +'use strict'; + +const libs = { + sodium: sodium => ({ + open: sodium.api.crypto_secretbox_open_easy, + close: sodium.api.crypto_secretbox_easy, + random: n => sodium.randombytes_buf(n), + }), + 'libsodium-wrappers': sodium => ({ + open: sodium.crypto_secretbox_open_easy, + close: sodium.crypto_secretbox_easy, + random: n => sodium.randombytes_buf(n), + }), + tweetnacl: tweetnacl => ({ + open: tweetnacl.secretbox.open, + close: tweetnacl.secretbox, + random: n => tweetnacl.randomBytes(n), + }), +}; + +exports.methods = {}; + +(async () => { + for (const libName of Object.keys(libs)) { + try { + const lib = require(libName); + if (libName === 'libsodium-wrappers' && lib.ready) await lib.ready; // eslint-disable-line no-await-in-loop + exports.methods = libs[libName](lib); + break; + } catch {} // eslint-disable-line no-empty + } +})(); diff --git a/node_modules/discord.js/src/client/voice/util/Silence.js b/node_modules/discord.js/src/client/voice/util/Silence.js new file mode 100644 index 0000000..9bea3d0 --- /dev/null +++ b/node_modules/discord.js/src/client/voice/util/Silence.js @@ -0,0 +1,13 @@ +'use strict'; + +const { Readable } = require('stream'); + +const SILENCE_FRAME = Buffer.from([0xf8, 0xff, 0xfe]); + +class Silence extends Readable { + _read() { + this.push(SILENCE_FRAME); + } +} + +module.exports = Silence; diff --git a/node_modules/discord.js/src/client/voice/util/VolumeInterface.js b/node_modules/discord.js/src/client/voice/util/VolumeInterface.js new file mode 100644 index 0000000..0dca04f --- /dev/null +++ b/node_modules/discord.js/src/client/voice/util/VolumeInterface.js @@ -0,0 +1,103 @@ +'use strict'; + +const EventEmitter = require('events'); + +/** + * An interface class for volume transformation. + * @extends {EventEmitter} + */ +class VolumeInterface extends EventEmitter { + constructor({ volume = 1 } = {}) { + super(); + this.setVolume(volume); + } + + /** + * Whether or not the volume of this stream is editable + * @type {boolean} + * @readonly + */ + get volumeEditable() { + return true; + } + + /** + * The current volume of the stream + * @type {number} + * @readonly + */ + get volume() { + return this._volume; + } + + /** + * The current volume of the stream in decibels + * @type {number} + * @readonly + */ + get volumeDecibels() { + return Math.log10(this.volume) * 20; + } + + /** + * The current volume of the stream from a logarithmic scale + * @type {number} + * @readonly + */ + get volumeLogarithmic() { + return Math.pow(this.volume, 1 / 1.660964); + } + + applyVolume(buffer, volume) { + volume = volume || this._volume; + if (volume === 1) return buffer; + + const out = Buffer.alloc(buffer.length); + for (let i = 0; i < buffer.length; i += 2) { + if (i >= buffer.length - 1) break; + const uint = Math.min(32767, Math.max(-32767, Math.floor(volume * buffer.readInt16LE(i)))); + out.writeInt16LE(uint, i); + } + + return out; + } + + /** + * Sets the volume relative to the input stream - i.e. 1 is normal, 0.5 is half, 2 is double. + * @param {number} volume The volume that you want to set + */ + setVolume(volume) { + /** + * Emitted when the volume of this interface changes. + * @event VolumeInterface#volumeChange + * @param {number} oldVolume The old volume of this interface + * @param {number} newVolume The new volume of this interface + */ + this.emit('volumeChange', this._volume, volume); + this._volume = volume; + } + + /** + * Sets the volume in decibels. + * @param {number} db The decibels + */ + setVolumeDecibels(db) { + this.setVolume(Math.pow(10, db / 20)); + } + + /** + * Sets the volume so that a perceived value of 0.5 is half the perceived volume etc. + * @param {number} value The value for the volume + */ + setVolumeLogarithmic(value) { + this.setVolume(Math.pow(value, 1.660964)); + } +} + +const props = ['volumeDecibels', 'volumeLogarithmic', 'setVolumeDecibels', 'setVolumeLogarithmic']; + +exports.applyToClass = function applyToClass(structure) { + for (const prop of props) { + Object.defineProperty(structure.prototype, prop, Object.getOwnPropertyDescriptor(VolumeInterface.prototype, prop)); + } +}; diff --git a/node_modules/discord.js/src/client/websocket/WebSocketManager.js b/node_modules/discord.js/src/client/websocket/WebSocketManager.js new file mode 100644 index 0000000..1801106 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/WebSocketManager.js @@ -0,0 +1,439 @@ +'use strict'; + +const EventEmitter = require('events'); +const WebSocketShard = require('./WebSocketShard'); +const PacketHandlers = require('./handlers'); +const { Error: DJSError } = require('../../errors'); +const Collection = require('../../util/Collection'); +const { Events, ShardEvents, Status, WSCodes, WSEvents } = require('../../util/Constants'); +const Util = require('../../util/Util'); + +const BeforeReadyWhitelist = [ + WSEvents.READY, + WSEvents.RESUMED, + WSEvents.GUILD_CREATE, + WSEvents.GUILD_DELETE, + WSEvents.GUILD_MEMBERS_CHUNK, + WSEvents.GUILD_MEMBER_ADD, + WSEvents.GUILD_MEMBER_REMOVE, +]; + +const UNRECOVERABLE_CLOSE_CODES = Object.keys(WSCodes) + .slice(1) + .map(Number); +const UNRESUMABLE_CLOSE_CODES = [1000, 4006, 4007]; + +/** + * The WebSocket manager for this client. + * <info>This class forwards raw dispatch events, + * read more about it here {@link https://discordapp.com/developers/docs/topics/gateway}</info> + * @extends EventEmitter + */ +class WebSocketManager extends EventEmitter { + constructor(client) { + super(); + + /** + * The client that instantiated this WebSocketManager + * @type {Client} + * @readonly + * @name WebSocketManager#client + */ + Object.defineProperty(this, 'client', { value: client }); + + /** + * The gateway this manager uses + * @type {?string} + */ + this.gateway = undefined; + + /** + * The amount of shards this manager handles + * @private + * @type {number} + */ + this.totalShards = this.client.options.shards.length; + + /** + * A collection of all shards this manager handles + * @type {Collection<number, WebSocketShard>} + */ + this.shards = new Collection(); + + /** + * An array of shards to be connected or that need to reconnect + * @type {Set<WebSocketShard>} + * @private + * @name WebSocketManager#shardQueue + */ + Object.defineProperty(this, 'shardQueue', { value: new Set(), writable: true }); + + /** + * An array of queued events before this WebSocketManager became ready + * @type {object[]} + * @private + * @name WebSocketManager#packetQueue + */ + Object.defineProperty(this, 'packetQueue', { value: [] }); + + /** + * The current status of this WebSocketManager + * @type {number} + */ + this.status = Status.IDLE; + + /** + * If this manager was destroyed. It will prevent shards from reconnecting + * @type {boolean} + * @private + */ + this.destroyed = false; + + /** + * If this manager is currently reconnecting one or multiple shards + * @type {boolean} + * @private + */ + this.reconnecting = false; + + /** + * The current session limit of the client + * @private + * @type {?Object} + * @prop {number} total Total number of identifies available + * @prop {number} remaining Number of identifies remaining + * @prop {number} reset_after Number of milliseconds after which the limit resets + */ + this.sessionStartLimit = undefined; + } + + /** + * The average ping of all WebSocketShards + * @type {number} + * @readonly + */ + get ping() { + const sum = this.shards.reduce((a, b) => a + b.ping, 0); + return sum / this.shards.size; + } + + /** + * Emits a debug message. + * @param {string} message The debug message + * @param {?WebSocketShard} [shard] The shard that emitted this message, if any + * @private + */ + debug(message, shard) { + this.client.emit(Events.DEBUG, `[WS => ${shard ? `Shard ${shard.id}` : 'Manager'}] ${message}`); + } + + /** + * Connects this manager to the gateway. + * @private + */ + async connect() { + const invalidToken = new DJSError(WSCodes[4004]); + const { + url: gatewayURL, + shards: recommendedShards, + session_start_limit: sessionStartLimit, + } = await this.client.api.gateway.bot.get().catch(error => { + throw error.httpStatus === 401 ? invalidToken : error; + }); + + this.sessionStartLimit = sessionStartLimit; + + const { total, remaining, reset_after } = sessionStartLimit; + + this.debug(`Fetched Gateway Information + URL: ${gatewayURL} + Recommended Shards: ${recommendedShards}`); + + this.debug(`Session Limit Information + Total: ${total} + Remaining: ${remaining}`); + + this.gateway = `${gatewayURL}/`; + + let { shards } = this.client.options; + + if (shards === 'auto') { + this.debug(`Using the recommended shard count provided by Discord: ${recommendedShards}`); + this.totalShards = this.client.options.shardCount = recommendedShards; + shards = this.client.options.shards = Array.from({ length: recommendedShards }, (_, i) => i); + } + + this.totalShards = shards.length; + this.debug(`Spawning shards: ${shards.join(', ')}`); + this.shardQueue = new Set(shards.map(id => new WebSocketShard(this, id))); + + await this._handleSessionLimit(remaining, reset_after); + + return this.createShards(); + } + + /** + * Handles the creation of a shard. + * @returns {Promise<boolean>} + * @private + */ + async createShards() { + // If we don't have any shards to handle, return + if (!this.shardQueue.size) return false; + + const [shard] = this.shardQueue; + + this.shardQueue.delete(shard); + + if (!shard.eventsAttached) { + shard.on(ShardEvents.ALL_READY, unavailableGuilds => { + /** + * Emitted when a shard turns ready. + * @event Client#shardReady + * @param {number} id The shard ID that turned ready + * @param {?Set<string>} unavailableGuilds Set of unavailable guild IDs, if any + */ + this.client.emit(Events.SHARD_READY, shard.id, unavailableGuilds); + + if (!this.shardQueue.size) this.reconnecting = false; + this.checkShardsReady(); + }); + + shard.on(ShardEvents.CLOSE, event => { + if (event.code === 1000 ? this.destroyed : UNRECOVERABLE_CLOSE_CODES.includes(event.code)) { + /** + * Emitted when a shard's WebSocket disconnects and will no longer reconnect. + * @event Client#shardDisconnect + * @param {CloseEvent} event The WebSocket close event + * @param {number} id The shard ID that disconnected + */ + this.client.emit(Events.SHARD_DISCONNECT, event, shard.id); + this.debug(WSCodes[event.code], shard); + return; + } + + if (UNRESUMABLE_CLOSE_CODES.includes(event.code)) { + // These event codes cannot be resumed + shard.sessionID = undefined; + } + + /** + * Emitted when a shard is attempting to reconnect or re-identify. + * @event Client#shardReconnecting + * @param {number} id The shard ID that is attempting to reconnect + */ + this.client.emit(Events.SHARD_RECONNECTING, shard.id); + + this.shardQueue.add(shard); + + if (shard.sessionID) { + this.debug(`Session ID is present, attempting an immediate reconnect...`, shard); + this.reconnect(true); + } else { + shard.destroy({ reset: true, emit: false, log: false }); + this.reconnect(); + } + }); + + shard.on(ShardEvents.INVALID_SESSION, () => { + this.client.emit(Events.SHARD_RECONNECTING, shard.id); + }); + + shard.on(ShardEvents.DESTROYED, () => { + this.debug('Shard was destroyed but no WebSocket connection was present! Reconnecting...', shard); + + this.client.emit(Events.SHARD_RECONNECTING, shard.id); + + this.shardQueue.add(shard); + this.reconnect(); + }); + + shard.eventsAttached = true; + } + + this.shards.set(shard.id, shard); + + try { + await shard.connect(); + } catch (error) { + if (error && error.code && UNRECOVERABLE_CLOSE_CODES.includes(error.code)) { + throw new DJSError(WSCodes[error.code]); + // Undefined if session is invalid, error event for regular closes + } else if (!error || error.code) { + this.debug('Failed to connect to the gateway, requeueing...', shard); + this.shardQueue.add(shard); + } else { + throw error; + } + } + // If we have more shards, add a 5s delay + if (this.shardQueue.size) { + this.debug(`Shard Queue Size: ${this.shardQueue.size}; continuing in 5 seconds...`); + await Util.delayFor(5000); + await this._handleSessionLimit(); + return this.createShards(); + } + + return true; + } + + /** + * Handles reconnects for this manager. + * @param {boolean} [skipLimit=false] IF this reconnect should skip checking the session limit + * @private + * @returns {Promise<boolean>} + */ + async reconnect(skipLimit = false) { + if (this.reconnecting || this.status !== Status.READY) return false; + this.reconnecting = true; + try { + if (!skipLimit) await this._handleSessionLimit(); + await this.createShards(); + } catch (error) { + this.debug(`Couldn't reconnect or fetch information about the gateway. ${error}`); + if (error.httpStatus !== 401) { + this.debug(`Possible network error occurred. Retrying in 5s...`); + await Util.delayFor(5000); + this.reconnecting = false; + return this.reconnect(); + } + // If we get an error at this point, it means we cannot reconnect anymore + if (this.client.listenerCount(Events.INVALIDATED)) { + /** + * Emitted when the client's session becomes invalidated. + * You are expected to handle closing the process gracefully and preventing a boot loop + * if you are listening to this event. + * @event Client#invalidated + */ + this.client.emit(Events.INVALIDATED); + // Destroy just the shards. This means you have to handle the cleanup yourself + this.destroy(); + } else { + this.client.destroy(); + } + } finally { + this.reconnecting = false; + } + return true; + } + + /** + * Broadcasts a packet to every shard this manager handles. + * @param {Object} packet The packet to send + * @private + */ + broadcast(packet) { + for (const shard of this.shards.values()) shard.send(packet); + } + + /** + * Destroys this manager and all its shards. + * @private + */ + destroy() { + if (this.destroyed) return; + this.debug(`Manager was destroyed. Called by:\n${new Error('MANAGER_DESTROYED').stack}`); + this.destroyed = true; + this.shardQueue.clear(); + for (const shard of this.shards.values()) shard.destroy({ closeCode: 1000, reset: true, emit: false, log: false }); + } + + /** + * Handles the timeout required if we cannot identify anymore. + * @param {number} [remaining] The amount of remaining identify sessions that can be done today + * @param {number} [resetAfter] The amount of time in which the identify counter resets + * @private + */ + async _handleSessionLimit(remaining, resetAfter) { + if (typeof remaining === 'undefined' && typeof resetAfter === 'undefined') { + const { session_start_limit } = await this.client.api.gateway.bot.get(); + this.sessionStartLimit = session_start_limit; + remaining = session_start_limit.remaining; + resetAfter = session_start_limit.reset_after; + this.debug(`Session Limit Information + Total: ${session_start_limit.total} + Remaining: ${remaining}`); + } + if (!remaining) { + this.debug(`Exceeded identify threshold. Will attempt a connection in ${resetAfter}ms`); + await Util.delayFor(resetAfter); + } + } + + /** + * Processes a packet and queues it if this WebSocketManager is not ready. + * @param {Object} [packet] The packet to be handled + * @param {WebSocketShard} [shard] The shard that will handle this packet + * @returns {boolean} + * @private + */ + handlePacket(packet, shard) { + if (packet && this.status !== Status.READY) { + if (!BeforeReadyWhitelist.includes(packet.t)) { + this.packetQueue.push({ packet, shard }); + return false; + } + } + + if (this.packetQueue.length) { + const item = this.packetQueue.shift(); + this.client.setImmediate(() => { + this.handlePacket(item.packet, item.shard); + }); + } + + if (packet && PacketHandlers[packet.t]) { + PacketHandlers[packet.t](this.client, packet, shard); + } + + return true; + } + + /** + * Checks whether the client is ready to be marked as ready. + * @private + */ + async checkShardsReady() { + if (this.status === Status.READY) return; + if (this.shards.size !== this.totalShards || this.shards.some(s => s.status !== Status.READY)) { + return; + } + + this.status = Status.NEARLY; + + if (this.client.options.fetchAllMembers) { + try { + const promises = this.client.guilds.cache.map(guild => { + if (guild.available) return guild.members.fetch(); + // Return empty promise if guild is unavailable + return Promise.resolve(); + }); + await Promise.all(promises); + } catch (err) { + this.debug(`Failed to fetch all members before ready! ${err}\n${err.stack}`); + } + } + + this.triggerClientReady(); + } + + /** + * Causes the client to be marked as ready and emits the ready event. + * @private + */ + triggerClientReady() { + this.status = Status.READY; + + this.client.readyAt = new Date(); + + /** + * Emitted when the client becomes ready to start working. + * @event Client#ready + */ + this.client.emit(Events.CLIENT_READY); + + this.handlePacket(); + } +} + +module.exports = WebSocketManager; diff --git a/node_modules/discord.js/src/client/websocket/WebSocketShard.js b/node_modules/discord.js/src/client/websocket/WebSocketShard.js new file mode 100644 index 0000000..9f80f45 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/WebSocketShard.js @@ -0,0 +1,763 @@ +'use strict'; + +const EventEmitter = require('events'); +const WebSocket = require('../../WebSocket'); +const { browser, Status, Events, ShardEvents, OPCodes, WSEvents } = require('../../util/Constants'); + +const STATUS_KEYS = Object.keys(Status); +const CONNECTION_STATE = Object.keys(WebSocket.WebSocket); + +let zlib; + +if (!browser) { + try { + zlib = require('zlib-sync'); + } catch {} // eslint-disable-line no-empty +} + +/** + * Represents a Shard's WebSocket connection + */ +class WebSocketShard extends EventEmitter { + constructor(manager, id) { + super(); + + /** + * The WebSocketManager of the shard + * @type {WebSocketManager} + */ + this.manager = manager; + + /** + * The ID of the shard + * @type {number} + */ + this.id = id; + + /** + * The current status of the shard + * @type {Status} + */ + this.status = Status.IDLE; + + /** + * The current sequence of the shard + * @type {number} + * @private + */ + this.sequence = -1; + + /** + * The sequence of the shard after close + * @type {number} + * @private + */ + this.closeSequence = 0; + + /** + * The current session ID of the shard + * @type {string} + * @private + */ + this.sessionID = undefined; + + /** + * The previous heartbeat ping of the shard + * @type {number} + */ + this.ping = -1; + + /** + * The last time a ping was sent (a timestamp) + * @type {number} + * @private + */ + this.lastPingTimestamp = -1; + + /** + * If we received a heartbeat ack back. Used to identify zombie connections + * @type {boolean} + * @private + */ + this.lastHeartbeatAcked = true; + + /** + * Contains the rate limit queue and metadata + * @type {Object} + * @private + */ + Object.defineProperty(this, 'ratelimit', { + value: { + queue: [], + total: 120, + remaining: 120, + time: 60e3, + timer: null, + }, + }); + + /** + * The WebSocket connection for the current shard + * @type {?WebSocket} + * @private + */ + Object.defineProperty(this, 'connection', { value: null, writable: true }); + + /** + * @external Inflate + * @see {@link https://www.npmjs.com/package/zlib-sync} + */ + + /** + * The compression to use + * @type {?Inflate} + * @private + */ + Object.defineProperty(this, 'inflate', { value: null, writable: true }); + + /** + * The HELLO timeout + * @type {?NodeJS.Timer} + * @private + */ + Object.defineProperty(this, 'helloTimeout', { value: undefined, writable: true }); + + /** + * If the manager attached its event handlers on the shard + * @type {boolean} + * @private + */ + Object.defineProperty(this, 'eventsAttached', { value: false, writable: true }); + + /** + * A set of guild IDs this shard expects to receive + * @type {?Set<string>} + * @private + */ + Object.defineProperty(this, 'expectedGuilds', { value: undefined, writable: true }); + + /** + * The ready timeout + * @type {?NodeJS.Timer} + * @private + */ + Object.defineProperty(this, 'readyTimeout', { value: undefined, writable: true }); + + /** + * Time when the WebSocket connection was opened + * @type {number} + * @private + */ + Object.defineProperty(this, 'connectedAt', { value: 0, writable: true }); + } + + /** + * Emits a debug event. + * @param {string} message The debug message + * @private + */ + debug(message) { + this.manager.debug(message, this); + } + + /** + * Connects the shard to the gateway. + * @private + * @returns {Promise<void>} A promise that will resolve if the shard turns ready successfully, + * or reject if we couldn't connect + */ + connect() { + const { gateway, client } = this.manager; + + if (this.connection && this.connection.readyState === WebSocket.OPEN && this.status === Status.READY) { + return Promise.resolve(); + } + + return new Promise((resolve, reject) => { + const cleanup = () => { + this.removeListener(ShardEvents.CLOSE, onClose); + this.removeListener(ShardEvents.READY, onReady); + this.removeListener(ShardEvents.RESUMED, onResumed); + this.removeListener(ShardEvents.INVALID_SESSION, onInvalidOrDestroyed); + this.removeListener(ShardEvents.DESTROYED, onInvalidOrDestroyed); + }; + + const onReady = () => { + cleanup(); + resolve(); + }; + + const onResumed = () => { + cleanup(); + resolve(); + }; + + const onClose = event => { + cleanup(); + reject(event); + }; + + const onInvalidOrDestroyed = () => { + cleanup(); + // eslint-disable-next-line prefer-promise-reject-errors + reject(); + }; + + this.once(ShardEvents.READY, onReady); + this.once(ShardEvents.RESUMED, onResumed); + this.once(ShardEvents.CLOSE, onClose); + this.once(ShardEvents.INVALID_SESSION, onInvalidOrDestroyed); + this.once(ShardEvents.DESTROYED, onInvalidOrDestroyed); + + if (this.connection && this.connection.readyState === WebSocket.OPEN) { + this.debug('An open connection was found, attempting an immediate identify.'); + this.identify(); + return; + } + + if (this.connection) { + this.debug(`A connection object was found. Cleaning up before continuing. + State: ${CONNECTION_STATE[this.connection.readyState]}`); + this.destroy({ emit: false }); + } + + const wsQuery = { v: client.options.ws.version }; + + if (zlib) { + this.inflate = new zlib.Inflate({ + chunkSize: 65535, + flush: zlib.Z_SYNC_FLUSH, + to: WebSocket.encoding === 'json' ? 'string' : '', + }); + wsQuery.compress = 'zlib-stream'; + } + + this.debug( + `[CONNECT] + Gateway : ${gateway} + Version : ${client.options.ws.version} + Encoding : ${WebSocket.encoding} + Compression: ${zlib ? 'zlib-stream' : 'none'}`, + ); + + this.status = this.status === Status.DISCONNECTED ? Status.RECONNECTING : Status.CONNECTING; + this.setHelloTimeout(); + + this.connectedAt = Date.now(); + + const ws = (this.connection = WebSocket.create(gateway, wsQuery)); + ws.onopen = this.onOpen.bind(this); + ws.onmessage = this.onMessage.bind(this); + ws.onerror = this.onError.bind(this); + ws.onclose = this.onClose.bind(this); + }); + } + + /** + * Called whenever a connection is opened to the gateway. + * @private + */ + onOpen() { + this.debug(`[CONNECTED] ${this.connection.url} in ${Date.now() - this.connectedAt}ms`); + this.status = Status.NEARLY; + } + + /** + * Called whenever a message is received. + * @param {MessageEvent} event Event received + * @private + */ + onMessage({ data }) { + let raw; + if (data instanceof ArrayBuffer) data = new Uint8Array(data); + if (zlib) { + const l = data.length; + const flush = + l >= 4 && data[l - 4] === 0x00 && data[l - 3] === 0x00 && data[l - 2] === 0xff && data[l - 1] === 0xff; + + this.inflate.push(data, flush && zlib.Z_SYNC_FLUSH); + if (!flush) return; + raw = this.inflate.result; + } else { + raw = data; + } + let packet; + try { + packet = WebSocket.unpack(raw); + this.manager.client.emit(Events.RAW, packet, this.id); + if (packet.op === OPCodes.DISPATCH) this.manager.emit(packet.t, packet.d, this.id); + } catch (err) { + this.manager.client.emit(Events.SHARD_ERROR, err, this.id); + return; + } + this.onPacket(packet); + } + + /** + * Called whenever an error occurs with the WebSocket. + * @param {ErrorEvent} event The error that occurred + * @private + */ + onError(event) { + const error = event && event.error ? event.error : event; + if (!error) return; + + /** + * Emitted whenever a shard's WebSocket encounters a connection error. + * @event Client#shardError + * @param {Error} error The encountered error + * @param {number} shardID The shard that encountered this error + */ + this.manager.client.emit(Events.SHARD_ERROR, error, this.id); + } + + /** + * @external CloseEvent + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent} + */ + + /** + * @external ErrorEvent + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/ErrorEvent} + */ + + /** + * @external MessageEvent + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent} + */ + + /** + * Called whenever a connection to the gateway is closed. + * @param {CloseEvent} event Close event that was received + * @private + */ + onClose(event) { + if (this.sequence !== -1) this.closeSequence = this.sequence; + this.sequence = -1; + + this.debug(`[CLOSE] + Event Code: ${event.code} + Clean : ${event.wasClean} + Reason : ${event.reason || 'No reason received'}`); + + this.setHeartbeatTimer(-1); + this.setHelloTimeout(-1); + // If we still have a connection object, clean up its listeners + if (this.connection) this._cleanupConnection(); + + this.status = Status.DISCONNECTED; + + /** + * Emitted when a shard's WebSocket closes. + * @private + * @event WebSocketShard#close + * @param {CloseEvent} event The received event + */ + this.emit(ShardEvents.CLOSE, event); + } + + /** + * Called whenever a packet is received. + * @param {Object} packet The received packet + * @private + */ + onPacket(packet) { + if (!packet) { + this.debug(`Received broken packet: '${packet}'.`); + return; + } + + switch (packet.t) { + case WSEvents.READY: + /** + * Emitted when the shard receives the READY payload and is now waiting for guilds + * @event WebSocketShard#ready + */ + this.emit(ShardEvents.READY); + + this.sessionID = packet.d.session_id; + this.expectedGuilds = new Set(packet.d.guilds.map(d => d.id)); + this.status = Status.WAITING_FOR_GUILDS; + this.debug(`[READY] Session ${this.sessionID}.`); + this.lastHeartbeatAcked = true; + this.sendHeartbeat('ReadyHeartbeat'); + break; + case WSEvents.RESUMED: { + /** + * Emitted when the shard resumes successfully + * @event WebSocketShard#resumed + */ + this.emit(ShardEvents.RESUMED); + + this.status = Status.READY; + const replayed = packet.s - this.closeSequence; + this.debug(`[RESUMED] Session ${this.sessionID} | Replayed ${replayed} events.`); + this.lastHeartbeatAcked = true; + this.sendHeartbeat('ResumeHeartbeat'); + break; + } + } + + if (packet.s > this.sequence) this.sequence = packet.s; + + switch (packet.op) { + case OPCodes.HELLO: + this.setHelloTimeout(-1); + this.setHeartbeatTimer(packet.d.heartbeat_interval); + this.identify(); + break; + case OPCodes.RECONNECT: + this.debug('[RECONNECT] Discord asked us to reconnect'); + this.destroy({ closeCode: 4000 }); + break; + case OPCodes.INVALID_SESSION: + this.debug(`[INVALID SESSION] Resumable: ${packet.d}.`); + // If we can resume the session, do so immediately + if (packet.d) { + this.identifyResume(); + return; + } + // Reset the sequence + this.sequence = -1; + // Reset the session ID as it's invalid + this.sessionID = undefined; + // Set the status to reconnecting + this.status = Status.RECONNECTING; + // Finally, emit the INVALID_SESSION event + this.emit(ShardEvents.INVALID_SESSION); + break; + case OPCodes.HEARTBEAT_ACK: + this.ackHeartbeat(); + break; + case OPCodes.HEARTBEAT: + this.sendHeartbeat('HeartbeatRequest', true); + break; + default: + this.manager.handlePacket(packet, this); + if (this.status === Status.WAITING_FOR_GUILDS && packet.t === WSEvents.GUILD_CREATE) { + this.expectedGuilds.delete(packet.d.id); + this.checkReady(); + } + } + } + + /** + * Checks if the shard can be marked as ready + * @private + */ + checkReady() { + // Step 0. Clear the ready timeout, if it exists + if (this.readyTimeout) { + this.manager.client.clearTimeout(this.readyTimeout); + this.readyTimeout = undefined; + } + // Step 1. If we don't have any other guilds pending, we are ready + if (!this.expectedGuilds.size) { + this.debug('Shard received all its guilds. Marking as fully ready.'); + this.status = Status.READY; + + /** + * Emitted when the shard is fully ready. + * This event is emitted if: + * * all guilds were received by this shard + * * the ready timeout expired, and some guilds are unavailable + * @event WebSocketShard#allReady + * @param {?Set<string>} unavailableGuilds Set of unavailable guilds, if any + */ + this.emit(ShardEvents.ALL_READY); + return; + } + // Step 2. Create a 15s timeout that will mark the shard as ready if there are still unavailable guilds + this.readyTimeout = this.manager.client.setTimeout(() => { + this.debug(`Shard did not receive any more guild packets in 15 seconds. + Unavailable guild count: ${this.expectedGuilds.size}`); + + this.readyTimeout = undefined; + + this.status = Status.READY; + + this.emit(ShardEvents.ALL_READY, this.expectedGuilds); + }, 15000); + } + + /** + * Sets the HELLO packet timeout. + * @param {number} [time] If set to -1, it will clear the hello timeout timeout + * @private + */ + setHelloTimeout(time) { + if (time === -1) { + if (this.helloTimeout) { + this.debug('Clearing the HELLO timeout.'); + this.manager.client.clearTimeout(this.helloTimeout); + this.helloTimeout = undefined; + } + return; + } + this.debug('Setting a HELLO timeout for 20s.'); + this.helloTimeout = this.manager.client.setTimeout(() => { + this.debug('Did not receive HELLO in time. Destroying and connecting again.'); + this.destroy({ reset: true, closeCode: 4009 }); + }, 20000); + } + + /** + * Sets the heartbeat timer for this shard. + * @param {number} time If -1, clears the interval, any other number sets an interval + * @private + */ + setHeartbeatTimer(time) { + if (time === -1) { + if (this.heartbeatInterval) { + this.debug('Clearing the heartbeat interval.'); + this.manager.client.clearInterval(this.heartbeatInterval); + this.heartbeatInterval = undefined; + } + return; + } + this.debug(`Setting a heartbeat interval for ${time}ms.`); + // Sanity checks + if (this.heartbeatInterval) this.manager.client.clearInterval(this.heartbeatInterval); + this.heartbeatInterval = this.manager.client.setInterval(() => this.sendHeartbeat(), time); + } + + /** + * Sends a heartbeat to the WebSocket. + * If this shard didn't receive a heartbeat last time, it will destroy it and reconnect + * @param {string} [tag='HeartbeatTimer'] What caused this heartbeat to be sent + * @param {boolean} [ignoreHeartbeatAck] If we should send the heartbeat forcefully. + * @private + */ + sendHeartbeat( + tag = 'HeartbeatTimer', + ignoreHeartbeatAck = [Status.WAITING_FOR_GUILDS, Status.IDENTIFYING, Status.RESUMING].includes(this.status), + ) { + if (ignoreHeartbeatAck && !this.lastHeartbeatAcked) { + this.debug(`[${tag}] Didn't process heartbeat ack yet but we are still connected. Sending one now.`); + } else if (!this.lastHeartbeatAcked) { + this.debug( + `[${tag}] Didn't receive a heartbeat ack last time, assuming zombie connection. Destroying and reconnecting. + Status : ${STATUS_KEYS[this.status]} + Sequence : ${this.sequence} + Connection State: ${this.connection ? CONNECTION_STATE[this.connection.readyState] : 'No Connection??'}`, + ); + + this.destroy({ closeCode: 4009, reset: true }); + return; + } + + this.debug(`[${tag}] Sending a heartbeat.`); + this.lastHeartbeatAcked = false; + this.lastPingTimestamp = Date.now(); + this.send({ op: OPCodes.HEARTBEAT, d: this.sequence }, true); + } + + /** + * Acknowledges a heartbeat. + * @private + */ + ackHeartbeat() { + this.lastHeartbeatAcked = true; + const latency = Date.now() - this.lastPingTimestamp; + this.debug(`Heartbeat acknowledged, latency of ${latency}ms.`); + this.ping = latency; + } + + /** + * Identifies the client on the connection. + * @private + * @returns {void} + */ + identify() { + return this.sessionID ? this.identifyResume() : this.identifyNew(); + } + + /** + * Identifies as a new connection on the gateway. + * @private + */ + identifyNew() { + const { client } = this.manager; + if (!client.token) { + this.debug('[IDENTIFY] No token available to identify a new session.'); + return; + } + + this.status = Status.IDENTIFYING; + + // Clone the identify payload and assign the token and shard info + const d = { + ...client.options.ws, + token: client.token, + shard: [this.id, Number(client.options.shardCount)], + }; + + this.debug(`[IDENTIFY] Shard ${this.id}/${client.options.shardCount}`); + this.send({ op: OPCodes.IDENTIFY, d }, true); + } + + /** + * Resumes a session on the gateway. + * @private + */ + identifyResume() { + if (!this.sessionID) { + this.debug('[RESUME] No session ID was present; identifying as a new session.'); + this.identifyNew(); + return; + } + + this.status = Status.RESUMING; + + this.debug(`[RESUME] Session ${this.sessionID}, sequence ${this.closeSequence}`); + + const d = { + token: this.manager.client.token, + session_id: this.sessionID, + seq: this.closeSequence, + }; + + this.send({ op: OPCodes.RESUME, d }, true); + } + + /** + * Adds a packet to the queue to be sent to the gateway. + * <warn>If you use this method, make sure you understand that you need to provide + * a full [Payload](https://discordapp.com/developers/docs/topics/gateway#commands-and-events-gateway-commands). + * Do not use this method if you don't know what you're doing.</warn> + * @param {Object} data The full packet to send + * @param {boolean} [important=false] If this packet should be added first in queue + */ + send(data, important = false) { + this.ratelimit.queue[important ? 'unshift' : 'push'](data); + this.processQueue(); + } + + /** + * Sends data, bypassing the queue. + * @param {Object} data Packet to send + * @returns {void} + * @private + */ + _send(data) { + if (!this.connection || this.connection.readyState !== WebSocket.OPEN) { + this.debug(`Tried to send packet '${JSON.stringify(data)}' but no WebSocket is available!`); + this.destroy({ close: 4000 }); + return; + } + + this.connection.send(WebSocket.pack(data), err => { + if (err) this.manager.client.emit(Events.SHARD_ERROR, err, this.id); + }); + } + + /** + * Processes the current WebSocket queue. + * @returns {void} + * @private + */ + processQueue() { + if (this.ratelimit.remaining === 0) return; + if (this.ratelimit.queue.length === 0) return; + if (this.ratelimit.remaining === this.ratelimit.total) { + this.ratelimit.timer = this.manager.client.setTimeout(() => { + this.ratelimit.remaining = this.ratelimit.total; + this.processQueue(); + }, this.ratelimit.time); + } + while (this.ratelimit.remaining > 0) { + const item = this.ratelimit.queue.shift(); + if (!item) return; + this._send(item); + this.ratelimit.remaining--; + } + } + + /** + * Destroys this shard and closes its WebSocket connection. + * @param {Object} [options={ closeCode: 1000, reset: false, emit: true, log: true }] Options for destroying the shard + * @private + */ + destroy({ closeCode = 1000, reset = false, emit = true, log = true } = {}) { + if (log) { + this.debug(`[DESTROY] + Close Code : ${closeCode} + Reset : ${reset} + Emit DESTROYED: ${emit}`); + } + + // Step 0: Remove all timers + this.setHeartbeatTimer(-1); + this.setHelloTimeout(-1); + + // Step 1: Close the WebSocket connection, if any, otherwise, emit DESTROYED + if (this.connection) { + // If the connection is currently opened, we will (hopefully) receive close + if (this.connection.readyState === WebSocket.OPEN) { + this.connection.close(closeCode); + } else { + // Connection is not OPEN + this.debug(`WS State: ${CONNECTION_STATE[this.connection.readyState]}`); + // Remove listeners from the connection + this._cleanupConnection(); + // Attempt to close the connection just in case + try { + this.connection.close(closeCode); + } catch { + // No-op + } + // Emit the destroyed event if needed + if (emit) this._emitDestroyed(); + } + } else if (emit) { + // We requested a destroy, but we had no connection. Emit destroyed + this._emitDestroyed(); + } + + // Step 2: Null the connection object + this.connection = null; + + // Step 3: Set the shard status to DISCONNECTED + this.status = Status.DISCONNECTED; + + // Step 4: Cache the old sequence (use to attempt a resume) + if (this.sequence !== -1) this.closeSequence = this.sequence; + + // Step 5: Reset the sequence and session ID if requested + if (reset) { + this.sequence = -1; + this.sessionID = undefined; + } + + // Step 6: reset the ratelimit data + this.ratelimit.remaining = this.ratelimit.total; + this.ratelimit.queue.length = 0; + if (this.ratelimit.timer) { + this.manager.client.clearTimeout(this.ratelimit.timer); + this.ratelimit.timer = null; + } + } + + /** + * Cleans up the WebSocket connection listeners. + * @private + */ + _cleanupConnection() { + this.connection.onopen = this.connection.onclose = this.connection.onerror = this.connection.onmessage = null; + } + + /** + * Emits the DESTROYED event on the shard + * @private + */ + _emitDestroyed() { + /** + * Emitted when a shard is destroyed, but no WebSocket connection was present. + * @private + * @event WebSocketShard#destroyed + */ + this.emit(ShardEvents.DESTROYED); + } +} + +module.exports = WebSocketShard; diff --git a/node_modules/discord.js/src/client/websocket/handlers/CHANNEL_CREATE.js b/node_modules/discord.js/src/client/websocket/handlers/CHANNEL_CREATE.js new file mode 100644 index 0000000..d6d560d --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/CHANNEL_CREATE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.ChannelCreate.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/CHANNEL_DELETE.js b/node_modules/discord.js/src/client/websocket/handlers/CHANNEL_DELETE.js new file mode 100644 index 0000000..cb9f3d8 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/CHANNEL_DELETE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.ChannelDelete.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/CHANNEL_PINS_UPDATE.js b/node_modules/discord.js/src/client/websocket/handlers/CHANNEL_PINS_UPDATE.js new file mode 100644 index 0000000..13e6f0f --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/CHANNEL_PINS_UPDATE.js @@ -0,0 +1,22 @@ +'use strict'; + +const { Events } = require('../../../util/Constants'); + +module.exports = (client, { d: data }) => { + const channel = client.channels.cache.get(data.channel_id); + const time = new Date(data.last_pin_timestamp); + + if (channel && !Number.isNaN(time.getTime())) { + // Discord sends null for last_pin_timestamp if the last pinned message was removed + channel.lastPinTimestamp = time.getTime() || null; + + /** + * Emitted whenever the pins of a channel are updated. Due to the nature of the WebSocket event, + * not much information can be provided easily here - you need to manually check the pins yourself. + * @event Client#channelPinsUpdate + * @param {DMChannel|TextChannel} channel The channel that the pins update occurred in + * @param {Date} time The time of the pins update + */ + client.emit(Events.CHANNEL_PINS_UPDATE, channel, time); + } +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/CHANNEL_UPDATE.js b/node_modules/discord.js/src/client/websocket/handlers/CHANNEL_UPDATE.js new file mode 100644 index 0000000..d441478 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/CHANNEL_UPDATE.js @@ -0,0 +1,16 @@ +'use strict'; + +const { Events } = require('../../../util/Constants'); + +module.exports = (client, packet) => { + const { old, updated } = client.actions.ChannelUpdate.handle(packet.d); + if (old && updated) { + /** + * Emitted whenever a channel is updated - e.g. name change, topic change, channel type change. + * @event Client#channelUpdate + * @param {DMChannel|GuildChannel} oldChannel The channel before the update + * @param {DMChannel|GuildChannel} newChannel The channel after the update + */ + client.emit(Events.CHANNEL_UPDATE, old, updated); + } +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/GUILD_BAN_ADD.js b/node_modules/discord.js/src/client/websocket/handlers/GUILD_BAN_ADD.js new file mode 100644 index 0000000..5d4a096 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/GUILD_BAN_ADD.js @@ -0,0 +1,16 @@ +'use strict'; + +const { Events } = require('../../../util/Constants'); + +module.exports = (client, { d: data }) => { + const guild = client.guilds.cache.get(data.guild_id); + const user = client.users.add(data.user); + + /** + * Emitted whenever a member is banned from a guild. + * @event Client#guildBanAdd + * @param {Guild} guild The guild that the ban occurred in + * @param {User} user The user that was banned + */ + if (guild && user) client.emit(Events.GUILD_BAN_ADD, guild, user); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/GUILD_BAN_REMOVE.js b/node_modules/discord.js/src/client/websocket/handlers/GUILD_BAN_REMOVE.js new file mode 100644 index 0000000..8389e46 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/GUILD_BAN_REMOVE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.GuildBanRemove.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/GUILD_CREATE.js b/node_modules/discord.js/src/client/websocket/handlers/GUILD_CREATE.js new file mode 100644 index 0000000..6743204 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/GUILD_CREATE.js @@ -0,0 +1,36 @@ +'use strict'; + +const { Events, Status } = require('../../../util/Constants'); + +module.exports = async (client, { d: data }, shard) => { + let guild = client.guilds.cache.get(data.id); + if (guild) { + if (!guild.available && !data.unavailable) { + // A newly available guild + guild._patch(data); + // If the client was ready before and we had unavailable guilds, fetch them + if (client.ws.status === Status.READY && client.options.fetchAllMembers) { + await guild.members + .fetch() + .catch(err => client.emit(Events.DEBUG, `Failed to fetch all members: ${err}\n${err.stack}`)); + } + } + } else { + // A new guild + data.shardID = shard.id; + guild = client.guilds.add(data); + if (client.ws.status === Status.READY) { + /** + * Emitted whenever the client joins a guild. + * @event Client#guildCreate + * @param {Guild} guild The created guild + */ + if (client.options.fetchAllMembers) { + await guild.members + .fetch() + .catch(err => client.emit(Events.DEBUG, `Failed to fetch all members: ${err}\n${err.stack}`)); + } + client.emit(Events.GUILD_CREATE, guild); + } + } +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/GUILD_DELETE.js b/node_modules/discord.js/src/client/websocket/handlers/GUILD_DELETE.js new file mode 100644 index 0000000..27a3256 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/GUILD_DELETE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.GuildDelete.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/GUILD_EMOJIS_UPDATE.js b/node_modules/discord.js/src/client/websocket/handlers/GUILD_EMOJIS_UPDATE.js new file mode 100644 index 0000000..e23b671 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/GUILD_EMOJIS_UPDATE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.GuildEmojisUpdate.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/GUILD_INTEGRATIONS_UPDATE.js b/node_modules/discord.js/src/client/websocket/handlers/GUILD_INTEGRATIONS_UPDATE.js new file mode 100644 index 0000000..e90a72c --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/GUILD_INTEGRATIONS_UPDATE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.GuildIntegrationsUpdate.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/GUILD_MEMBERS_CHUNK.js b/node_modules/discord.js/src/client/websocket/handlers/GUILD_MEMBERS_CHUNK.js new file mode 100644 index 0000000..3ceb622 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/GUILD_MEMBERS_CHUNK.js @@ -0,0 +1,22 @@ +'use strict'; + +const Collection = require('../../../util/Collection'); +const { Events } = require('../../../util/Constants'); + +module.exports = (client, { d: data }) => { + const guild = client.guilds.cache.get(data.guild_id); + if (!guild) return; + const members = new Collection(); + + for (const member of data.members) members.set(member.user.id, guild.members.add(member)); + if (data.presences) { + for (const presence of data.presences) guild.presences.cache.add(Object.assign(presence, { guild })); + } + /** + * Emitted whenever a chunk of guild members is received (all members come from the same guild). + * @event Client#guildMembersChunk + * @param {Collection<Snowflake, GuildMember>} members The members in the chunk + * @param {Guild} guild The guild related to the member chunk + */ + client.emit(Events.GUILD_MEMBERS_CHUNK, members, guild); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/GUILD_MEMBER_ADD.js b/node_modules/discord.js/src/client/websocket/handlers/GUILD_MEMBER_ADD.js new file mode 100644 index 0000000..5128756 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/GUILD_MEMBER_ADD.js @@ -0,0 +1,19 @@ +'use strict'; + +const { Events, Status } = require('../../../util/Constants'); + +module.exports = (client, { d: data }, shard) => { + const guild = client.guilds.cache.get(data.guild_id); + if (guild) { + guild.memberCount++; + const member = guild.members.add(data); + if (shard.status === Status.READY) { + /** + * Emitted whenever a user joins a guild. + * @event Client#guildMemberAdd + * @param {GuildMember} member The member that has joined a guild + */ + client.emit(Events.GUILD_MEMBER_ADD, member); + } + } +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/GUILD_MEMBER_REMOVE.js b/node_modules/discord.js/src/client/websocket/handlers/GUILD_MEMBER_REMOVE.js new file mode 100644 index 0000000..72432af --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/GUILD_MEMBER_REMOVE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet, shard) => { + client.actions.GuildMemberRemove.handle(packet.d, shard); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/GUILD_MEMBER_UPDATE.js b/node_modules/discord.js/src/client/websocket/handlers/GUILD_MEMBER_UPDATE.js new file mode 100644 index 0000000..92c9da6 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/GUILD_MEMBER_UPDATE.js @@ -0,0 +1,22 @@ +'use strict'; + +const { Status, Events } = require('../../../util/Constants'); + +module.exports = (client, { d: data }, shard) => { + const guild = client.guilds.cache.get(data.guild_id); + if (guild) { + const member = guild.members.cache.get(data.user.id); + if (member) { + const old = member._update(data); + if (shard.status === Status.READY) { + /** + * Emitted whenever a guild member changes - i.e. new role, removed role, nickname. + * @event Client#guildMemberUpdate + * @param {GuildMember} oldMember The member before the update + * @param {GuildMember} newMember The member after the update + */ + client.emit(Events.GUILD_MEMBER_UPDATE, old, member); + } + } + } +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/GUILD_ROLE_CREATE.js b/node_modules/discord.js/src/client/websocket/handlers/GUILD_ROLE_CREATE.js new file mode 100644 index 0000000..da9e7bc --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/GUILD_ROLE_CREATE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.GuildRoleCreate.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/GUILD_ROLE_DELETE.js b/node_modules/discord.js/src/client/websocket/handlers/GUILD_ROLE_DELETE.js new file mode 100644 index 0000000..cdc6353 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/GUILD_ROLE_DELETE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.GuildRoleDelete.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/GUILD_ROLE_UPDATE.js b/node_modules/discord.js/src/client/websocket/handlers/GUILD_ROLE_UPDATE.js new file mode 100644 index 0000000..3a9b62e --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/GUILD_ROLE_UPDATE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.GuildRoleUpdate.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/GUILD_UPDATE.js b/node_modules/discord.js/src/client/websocket/handlers/GUILD_UPDATE.js new file mode 100644 index 0000000..fd0012a --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/GUILD_UPDATE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.GuildUpdate.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/INVITE_CREATE.js b/node_modules/discord.js/src/client/websocket/handlers/INVITE_CREATE.js new file mode 100644 index 0000000..50a2e72 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/INVITE_CREATE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.InviteCreate.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/INVITE_DELETE.js b/node_modules/discord.js/src/client/websocket/handlers/INVITE_DELETE.js new file mode 100644 index 0000000..5971852 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/INVITE_DELETE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.InviteDelete.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_CREATE.js b/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_CREATE.js new file mode 100644 index 0000000..c9b79a8 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_CREATE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.MessageCreate.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_DELETE.js b/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_DELETE.js new file mode 100644 index 0000000..85ae2bc --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_DELETE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.MessageDelete.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_DELETE_BULK.js b/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_DELETE_BULK.js new file mode 100644 index 0000000..fbcf80f --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_DELETE_BULK.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.MessageDeleteBulk.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_REACTION_ADD.js b/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_REACTION_ADD.js new file mode 100644 index 0000000..e219b4a --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_REACTION_ADD.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.MessageReactionAdd.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_REACTION_REMOVE.js b/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_REACTION_REMOVE.js new file mode 100644 index 0000000..2980e69 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_REACTION_REMOVE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.MessageReactionRemove.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_REACTION_REMOVE_ALL.js b/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_REACTION_REMOVE_ALL.js new file mode 100644 index 0000000..ead80f7 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_REACTION_REMOVE_ALL.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.MessageReactionRemoveAll.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_REACTION_REMOVE_EMOJI.js b/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_REACTION_REMOVE_EMOJI.js new file mode 100644 index 0000000..579444c --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_REACTION_REMOVE_EMOJI.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.MessageReactionRemoveEmoji.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_UPDATE.js b/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_UPDATE.js new file mode 100644 index 0000000..7428e90 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_UPDATE.js @@ -0,0 +1,16 @@ +'use strict'; + +const { Events } = require('../../../util/Constants'); + +module.exports = (client, packet) => { + const { old, updated } = client.actions.MessageUpdate.handle(packet.d); + if (old && updated) { + /** + * Emitted whenever a message is updated - e.g. embed or content change. + * @event Client#messageUpdate + * @param {Message} oldMessage The message before the update + * @param {Message} newMessage The message after the update + */ + client.emit(Events.MESSAGE_UPDATE, old, updated); + } +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/PRESENCE_UPDATE.js b/node_modules/discord.js/src/client/websocket/handlers/PRESENCE_UPDATE.js new file mode 100644 index 0000000..bde3629 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/PRESENCE_UPDATE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.PresenceUpdate.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/READY.js b/node_modules/discord.js/src/client/websocket/handlers/READY.js new file mode 100644 index 0000000..c38b681 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/READY.js @@ -0,0 +1,21 @@ +'use strict'; + +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); + } + + for (const guild of data.guilds) { + guild.shardID = shard.id; + client.guilds.add(guild); + } + + shard.checkReady(); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/RESUMED.js b/node_modules/discord.js/src/client/websocket/handlers/RESUMED.js new file mode 100644 index 0000000..5e5f403 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/RESUMED.js @@ -0,0 +1,14 @@ +'use strict'; + +const { Events } = require('../../../util/Constants'); + +module.exports = (client, packet, shard) => { + const replayed = shard.sequence - shard.closeSequence; + /** + * Emitted when a shard resumes successfully. + * @event Client#shardResume + * @param {number} id The shard ID that resumed + * @param {number} replayedEvents The amount of replayed events + */ + client.emit(Events.SHARD_RESUME, shard.id, replayed); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/TYPING_START.js b/node_modules/discord.js/src/client/websocket/handlers/TYPING_START.js new file mode 100644 index 0000000..86fb26b --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/TYPING_START.js @@ -0,0 +1,49 @@ +'use strict'; + +const { Events } = require('../../../util/Constants'); + +module.exports = (client, { d: data }) => { + const channel = client.channels.cache.get(data.channel_id); + const user = client.users.cache.get(data.user_id); + const timestamp = new Date(data.timestamp * 1000); + + if (channel && user) { + if (channel.type === 'voice') { + client.emit(Events.WARN, `Discord sent a typing packet to a voice channel ${channel.id}`); + return; + } + + if (channel._typing.has(user.id)) { + const typing = channel._typing.get(user.id); + + typing.lastTimestamp = timestamp; + typing.elapsedTime = Date.now() - typing.since; + client.clearTimeout(typing.timeout); + typing.timeout = tooLate(channel, user); + } else { + const since = new Date(); + const lastTimestamp = new Date(); + channel._typing.set(user.id, { + user, + since, + lastTimestamp, + elapsedTime: Date.now() - since, + timeout: tooLate(channel, user), + }); + + /** + * Emitted whenever a user starts typing in a channel. + * @event Client#typingStart + * @param {Channel} channel The channel the user started typing in + * @param {User} user The user that started typing + */ + client.emit(Events.TYPING_START, channel, user); + } + } +}; + +function tooLate(channel, user) { + return channel.client.setTimeout(() => { + channel._typing.delete(user.id); + }, 10000); +} diff --git a/node_modules/discord.js/src/client/websocket/handlers/USER_UPDATE.js b/node_modules/discord.js/src/client/websocket/handlers/USER_UPDATE.js new file mode 100644 index 0000000..a02bf58 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/USER_UPDATE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.UserUpdate.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/VOICE_SERVER_UPDATE.js b/node_modules/discord.js/src/client/websocket/handlers/VOICE_SERVER_UPDATE.js new file mode 100644 index 0000000..f9cf534 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/VOICE_SERVER_UPDATE.js @@ -0,0 +1,6 @@ +'use strict'; + +module.exports = (client, packet) => { + client.emit('debug', `[VOICE] received voice server: ${JSON.stringify(packet)}`); + client.voice.onVoiceServer(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/VOICE_STATE_UPDATE.js b/node_modules/discord.js/src/client/websocket/handlers/VOICE_STATE_UPDATE.js new file mode 100644 index 0000000..dbff6ea --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/VOICE_STATE_UPDATE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.VoiceStateUpdate.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/WEBHOOKS_UPDATE.js b/node_modules/discord.js/src/client/websocket/handlers/WEBHOOKS_UPDATE.js new file mode 100644 index 0000000..46cacee --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/WEBHOOKS_UPDATE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.WebhooksUpdate.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/index.js b/node_modules/discord.js/src/client/websocket/handlers/index.js new file mode 100644 index 0000000..d69c105 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/index.js @@ -0,0 +1,13 @@ +'use strict'; + +const { WSEvents } = require('../../../util/Constants'); + +const handlers = {}; + +for (const name of Object.keys(WSEvents)) { + try { + handlers[name] = require(`./${name}.js`); + } catch {} // eslint-disable-line no-empty +} + +module.exports = handlers; diff --git a/node_modules/discord.js/src/errors/DJSError.js b/node_modules/discord.js/src/errors/DJSError.js new file mode 100644 index 0000000..157ca66 --- /dev/null +++ b/node_modules/discord.js/src/errors/DJSError.js @@ -0,0 +1,61 @@ +'use strict'; + +// Heavily inspired by node's `internal/errors` module + +const kCode = Symbol('code'); +const messages = new Map(); + +/** + * Extend an error of some sort into a DiscordjsError. + * @param {Error} Base Base error to extend + * @returns {DiscordjsError} + */ +function makeDiscordjsError(Base) { + return class DiscordjsError extends Base { + constructor(key, ...args) { + super(message(key, args)); + this[kCode] = key; + if (Error.captureStackTrace) Error.captureStackTrace(this, DiscordjsError); + } + + get name() { + return `${super.name} [${this[kCode]}]`; + } + + get code() { + return this[kCode]; + } + }; +} + +/** + * Format the message for an error. + * @param {string} key Error key + * @param {Array<*>} args Arguments to pass for util format or as function args + * @returns {string} Formatted string + */ +function message(key, args) { + if (typeof key !== 'string') throw new Error('Error message key must be a string'); + const msg = messages.get(key); + if (!msg) throw new Error(`An invalid error message key was used: ${key}.`); + if (typeof msg === 'function') return msg(...args); + if (args === undefined || args.length === 0) return msg; + args.unshift(msg); + return String(...args); +} + +/** + * Register an error code and message. + * @param {string} sym Unique name for the error + * @param {*} val Value of the error + */ +function register(sym, val) { + messages.set(sym, typeof val === 'function' ? val : String(val)); +} + +module.exports = { + register, + Error: makeDiscordjsError(Error), + TypeError: makeDiscordjsError(TypeError), + RangeError: makeDiscordjsError(RangeError), +}; diff --git a/node_modules/discord.js/src/errors/Messages.js b/node_modules/discord.js/src/errors/Messages.js new file mode 100644 index 0000000..5b4299d --- /dev/null +++ b/node_modules/discord.js/src/errors/Messages.js @@ -0,0 +1,104 @@ +'use strict'; + +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.', + + TOKEN_INVALID: 'An invalid token was provided.', + TOKEN_MISSING: 'Request to use token, but token was unavailable to the client.', + + WS_CLOSE_REQUESTED: 'WebSocket closed due to user request.', + WS_CONNECTION_EXISTS: 'There is already an existing WebSocket connection.', + WS_NOT_OPEN: (data = 'data') => `Websocket not open to send ${data}`, + + BITFIELD_INVALID: 'Invalid bitfield flag or number.', + + 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.`, + SHARDING_PROCESS_EXISTS: id => `Shard ${id} already has an active process.`, + SHARDING_READY_TIMEOUT: id => `Shard ${id}'s Client took too long to become ready.`, + SHARDING_READY_DISCONNECTED: id => `Shard ${id}'s Client disconnected before becoming ready.`, + SHARDING_READY_DIED: id => `Shard ${id}'s process exited before its Client became ready.`, + + COLOR_RANGE: 'Color must be within the range 0 - 16777215 (0xFFFFFF).', + COLOR_CONVERT: 'Unable to convert color to a number.', + + EMBED_FIELD_NAME: 'MessageEmbed field names may not be empty.', + EMBED_FIELD_VALUE: 'MessageEmbed field values may not be empty.', + + FILE_NOT_FOUND: file => `File could not be found: ${file}`, + + USER_NO_DMCHANNEL: 'No DM Channel exists!', + + VOICE_INVALID_HEARTBEAT: 'Tried to set voice heartbeat but no valid interval was specified.', + VOICE_USER_MISSING: "Couldn't resolve the user to create stream.", + VOICE_JOIN_CHANNEL: (full = false) => + `You do not have permission to join this voice channel${full ? '; it is full.' : '.'}`, + VOICE_CONNECTION_TIMEOUT: 'Connection not established within 15 seconds.', + VOICE_TOKEN_ABSENT: 'Token not provided from voice server packet.', + VOICE_SESSION_ABSENT: 'Session ID not supplied.', + VOICE_INVALID_ENDPOINT: 'Invalid endpoint received.', + VOICE_NO_BROWSER: 'Voice connections are not available in browsers.', + VOICE_CONNECTION_ATTEMPTS_EXCEEDED: attempts => `Too many connection attempts (${attempts}).`, + VOICE_JOIN_SOCKET_CLOSED: 'Tried to send join packet, but the WebSocket is not open.', + VOICE_PLAY_INTERFACE_NO_BROADCAST: 'A broadcast cannot be played in this context.', + VOICE_PLAY_INTERFACE_BAD_TYPE: 'Unknown stream type', + VOICE_PRISM_DEMUXERS_NEED_STREAM: 'To play a webm/ogg stream, you need to pass a ReadableStream.', + + VOICE_STATE_UNCACHED_MEMBER: 'The member of this voice state is uncached.', + VOICE_STATE_NOT_OWN: 'You cannot self-deafen/mute on VoiceStates that do not belong to the ClientUser.', + VOICE_STATE_INVALID_TYPE: name => `${name} must be a boolean.`, + + UDP_SEND_FAIL: 'Tried to send a UDP packet, but there is no socket available.', + UDP_ADDRESS_MALFORMED: 'Malformed UDP address or port.', + UDP_CONNECTION_EXISTS: 'There is already an existing UDP connection.', + + REQ_RESOURCE_TYPE: 'The resource must be a string, Buffer or a valid file stream.', + + IMAGE_FORMAT: format => `Invalid image format: ${format}`, + IMAGE_SIZE: size => `Invalid image size: ${size}`, + + MESSAGE_BULK_DELETE_TYPE: 'The messages must be an Array, Collection, or number.', + MESSAGE_NONCE_TYPE: 'Message nonce must fit in an unsigned 64-bit integer.', + + TYPING_COUNT: 'Count must be at least 1', + + SPLIT_MAX_LEN: 'Chunk exceeds the max length and contains no split characters.', + + BAN_RESOLVE_ID: (ban = false) => `Couldn't resolve the user ID to ${ban ? 'ban' : 'unban'}.`, + FETCH_BAN_RESOLVE_ID: "Couldn't resolve the user ID to fetch the ban.", + + PRUNE_DAYS_TYPE: 'Days must be a number', + + GUILD_CHANNEL_RESOLVE: 'Could not resolve channel to a guild channel.', + GUILD_VOICE_CHANNEL_RESOLVE: 'Could not resolve channel to a guild voice channel.', + GUILD_CHANNEL_ORPHAN: 'Could not find a parent to this guild channel.', + GUILD_OWNED: 'Guild is owned by the client.', + GUILD_MEMBERS_TIMEOUT: "Members didn't arrive in time.", + GUILD_UNCACHED_ME: 'The client user as a member of this guild is uncached.', + + INVALID_TYPE: (name, expected, an = false) => `Supplied ${name} is not a${an ? 'n' : ''} ${expected}.`, + + WEBHOOK_MESSAGE: 'The message was not sent by a webhook.', + + EMOJI_TYPE: 'Emoji must be a string or GuildEmoji/ReactionEmoji', + EMOJI_MANAGED: 'Emoji is managed and has no Author.', + MISSING_MANAGE_EMOJIS_PERMISSION: guild => + `Client must have Manage Emoji permission in guild ${guild} to see emoji authors.`, + + REACTION_RESOLVE_USER: "Couldn't resolve the user ID to remove from the reaction.", + + VANITY_URL: 'This guild does not have the VANITY_URL feature enabled.', + + DELETE_GROUP_DM_CHANNEL: "Bots don't have access to Group DM Channels and cannot delete them", + FETCH_GROUP_DM_CHANNEL: "Bots don't have access to Group DM Channels and cannot fetch them", +}; + +for (const [name, message] of Object.entries(Messages)) register(name, message); diff --git a/node_modules/discord.js/src/errors/index.js b/node_modules/discord.js/src/errors/index.js new file mode 100644 index 0000000..c94ddc7 --- /dev/null +++ b/node_modules/discord.js/src/errors/index.js @@ -0,0 +1,4 @@ +'use strict'; + +module.exports = require('./DJSError'); +module.exports.Messages = require('./Messages'); diff --git a/node_modules/discord.js/src/index.js b/node_modules/discord.js/src/index.js new file mode 100644 index 0000000..edce6e8 --- /dev/null +++ b/node_modules/discord.js/src/index.js @@ -0,0 +1,105 @@ +'use strict'; + +const Util = require('./util/Util'); + +module.exports = { + // "Root" classes (starting points) + BaseClient: require('./client/BaseClient'), + Client: require('./client/Client'), + Shard: require('./sharding/Shard'), + ShardClientUtil: require('./sharding/ShardClientUtil'), + ShardingManager: require('./sharding/ShardingManager'), + WebhookClient: require('./client/WebhookClient'), + + // Utilities + ActivityFlags: require('./util/ActivityFlags'), + BitField: require('./util/BitField'), + Collection: require('./util/Collection'), + Constants: require('./util/Constants'), + DataResolver: require('./util/DataResolver'), + 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'), + SnowflakeUtil: require('./util/Snowflake'), + Structures: require('./util/Structures'), + SystemChannelFlags: require('./util/SystemChannelFlags'), + UserFlags: require('./util/UserFlags'), + Util: Util, + version: require('../package.json').version, + + // Managers + ChannelManager: require('./managers/ChannelManager'), + GuildChannelManager: require('./managers/GuildChannelManager'), + GuildEmojiManager: require('./managers/GuildEmojiManager'), + GuildEmojiRoleManager: require('./managers/GuildEmojiRoleManager'), + GuildMemberManager: require('./managers/GuildMemberManager'), + GuildMemberRoleManager: require('./managers/GuildMemberRoleManager'), + GuildManager: require('./managers/GuildManager'), + ReactionUserManager: require('./managers/ReactionUserManager'), + MessageManager: require('./managers/MessageManager'), + PresenceManager: require('./managers/PresenceManager'), + RoleManager: require('./managers/RoleManager'), + UserManager: require('./managers/UserManager'), + + // Shortcuts to Util methods + discordSort: Util.discordSort, + escapeMarkdown: Util.escapeMarkdown, + fetchRecommendedShards: Util.fetchRecommendedShards, + resolveColor: Util.resolveColor, + resolveString: Util.resolveString, + splitMessage: Util.splitMessage, + + // Structures + Base: require('./structures/Base'), + Activity: require('./structures/Presence').Activity, + APIMessage: require('./structures/APIMessage'), + BaseGuildEmoji: require('./structures/BaseGuildEmoji'), + CategoryChannel: require('./structures/CategoryChannel'), + Channel: require('./structures/Channel'), + ClientApplication: require('./structures/ClientApplication'), + get ClientUser() { + // This is a getter so that it properly extends any custom User class + return require('./structures/ClientUser'); + }, + Collector: require('./structures/interfaces/Collector'), + DMChannel: require('./structures/DMChannel'), + Emoji: require('./structures/Emoji'), + Guild: require('./structures/Guild'), + GuildAuditLogs: require('./structures/GuildAuditLogs'), + GuildChannel: require('./structures/GuildChannel'), + GuildEmoji: require('./structures/GuildEmoji'), + GuildMember: require('./structures/GuildMember'), + GuildPreview: require('./structures/GuildPreview'), + Integration: require('./structures/Integration'), + Invite: require('./structures/Invite'), + Message: require('./structures/Message'), + MessageAttachment: require('./structures/MessageAttachment'), + MessageCollector: require('./structures/MessageCollector'), + MessageEmbed: require('./structures/MessageEmbed'), + MessageMentions: require('./structures/MessageMentions'), + MessageReaction: require('./structures/MessageReaction'), + NewsChannel: require('./structures/NewsChannel'), + PermissionOverwrites: require('./structures/PermissionOverwrites'), + Presence: require('./structures/Presence').Presence, + ClientPresence: require('./structures/ClientPresence'), + ReactionCollector: require('./structures/ReactionCollector'), + ReactionEmoji: require('./structures/ReactionEmoji'), + RichPresenceAssets: require('./structures/Presence').RichPresenceAssets, + Role: require('./structures/Role'), + StoreChannel: require('./structures/StoreChannel'), + Team: require('./structures/Team'), + TeamMember: require('./structures/TeamMember'), + TextChannel: require('./structures/TextChannel'), + User: require('./structures/User'), + VoiceChannel: require('./structures/VoiceChannel'), + VoiceRegion: require('./structures/VoiceRegion'), + VoiceState: require('./structures/VoiceState'), + Webhook: require('./structures/Webhook'), + + WebSocket: require('./WebSocket'), +}; diff --git a/node_modules/discord.js/src/managers/BaseManager.js b/node_modules/discord.js/src/managers/BaseManager.js new file mode 100644 index 0000000..c11d191 --- /dev/null +++ b/node_modules/discord.js/src/managers/BaseManager.js @@ -0,0 +1,81 @@ +'use strict'; + +const Collection = require('../util/Collection'); +let Structures; + +/** + * Manages the API methods of a data model and holds its cache. + * @abstract + */ +class BaseManager { + constructor(client, iterable, holds, cacheType = Collection, ...cacheOptions) { + if (!Structures) Structures = require('../util/Structures'); + /** + * The data structure belonging to this manager + * @name BaseManager#holds + * @type {Function} + * @private + * @readonly + */ + Object.defineProperty(this, 'holds', { value: Structures.get(holds.name) || holds }); + + /** + * The client that instantiated this Manager + * @name BaseManager#client + * @type {Client} + * @readonly + */ + Object.defineProperty(this, 'client', { value: client }); + + /** + * The type of Collection of the Manager + * @type {Collection} + */ + this.cacheType = cacheType; + + /** + * Holds the cache for the data model + * @type {Collection} + */ + this.cache = new cacheType(...cacheOptions); + if (iterable) for (const i of iterable) this.add(i); + } + + add(data, cache = true, { id, extras = [] } = {}) { + const existing = this.cache.get(id || data.id); + if (existing && existing._patch && cache) existing._patch(data); + if (existing) return existing; + + const entry = this.holds ? new this.holds(this.client, data, ...extras) : data; + if (cache) this.cache.set(id || entry.id, entry); + return entry; + } + + /** + * Resolves a data entry to a data Object. + * @param {string|Object} idOrInstance The id or instance of something in this Manager + * @returns {?Object} An instance from this Manager + */ + resolve(idOrInstance) { + if (idOrInstance instanceof this.holds) return idOrInstance; + if (typeof idOrInstance === 'string') return this.cache.get(idOrInstance) || null; + return null; + } + + /** + * Resolves a data entry to a instance ID. + * @param {string|Object} idOrInstance The id or instance of something in this Manager + * @returns {?Snowflake} + */ + resolveID(idOrInstance) { + if (idOrInstance instanceof this.holds) return idOrInstance.id; + if (typeof idOrInstance === 'string') return idOrInstance; + return null; + } + + valueOf() { + return this.cache; + } +} + +module.exports = BaseManager; diff --git a/node_modules/discord.js/src/managers/ChannelManager.js b/node_modules/discord.js/src/managers/ChannelManager.js new file mode 100644 index 0000000..99fde10 --- /dev/null +++ b/node_modules/discord.js/src/managers/ChannelManager.js @@ -0,0 +1,93 @@ +'use strict'; + +const BaseManager = require('./BaseManager'); +const Channel = require('../structures/Channel'); +const { Events } = require('../util/Constants'); + +/** + * A manager of channels belonging to a client + * @extends {BaseManager} + */ +class ChannelManager extends BaseManager { + constructor(client, iterable) { + super(client, iterable, Channel); + } + + /** + * The cache of Channels + * @type {Collection<Snowflake, Channel>} + * @name ChannelManager#cache + */ + + add(data, guild, cache = true) { + const existing = this.cache.get(data.id); + if (existing) { + if (existing._patch && cache) existing._patch(data); + if (guild) guild.channels.add(existing); + return existing; + } + + const channel = Channel.create(this.client, data, guild); + + if (!channel) { + this.client.emit(Events.DEBUG, `Failed to find guild, or unknown type for channel ${data.id} ${data.type}`); + return null; + } + + if (cache) this.cache.set(channel.id, channel); + + return channel; + } + + remove(id) { + const channel = this.cache.get(id); + if (channel.guild) channel.guild.channels.cache.delete(id); + this.cache.delete(id); + } + + /** + * Data that can be resolved to give a Channel object. This can be: + * * A Channel object + * * A Snowflake + * @typedef {Channel|Snowflake} ChannelResolvable + */ + + /** + * Resolves a ChannelResolvable to a Channel object. + * @method resolve + * @memberof ChannelManager + * @instance + * @param {ChannelResolvable} channel The channel resolvable to resolve + * @returns {?Channel} + */ + + /** + * Resolves a ChannelResolvable to a channel ID string. + * @method resolveID + * @memberof ChannelManager + * @instance + * @param {ChannelResolvable} channel The channel resolvable to resolve + * @returns {?Snowflake} + */ + + /** + * Obtains a channel from Discord, or the channel cache if it's already available. + * @param {Snowflake} id ID of the channel + * @param {boolean} [cache=true] Whether to cache the new channel object if it isn't already + * @returns {Promise<Channel>} + * @example + * // Fetch a channel by its id + * client.channels.fetch('222109930545610754') + * .then(channel => console.log(channel.name)) + * .catch(console.error); + */ + async fetch(id, cache = true) { + const existing = this.cache.get(id); + if (existing && !existing.partial) return existing; + + const data = await this.client.api.channels(id).get(); + return this.add(data, null, cache); + } +} + +module.exports = ChannelManager; diff --git a/node_modules/discord.js/src/managers/GuildChannelManager.js b/node_modules/discord.js/src/managers/GuildChannelManager.js new file mode 100644 index 0000000..bc72e9a --- /dev/null +++ b/node_modules/discord.js/src/managers/GuildChannelManager.js @@ -0,0 +1,131 @@ +'use strict'; + +const BaseManager = require('./BaseManager'); +const GuildChannel = require('../structures/GuildChannel'); +const PermissionOverwrites = require('../structures/PermissionOverwrites'); +const { ChannelTypes } = require('../util/Constants'); + +/** + * Manages API methods for GuildChannels and stores their cache. + * @extends {BaseManager} + */ +class GuildChannelManager extends BaseManager { + constructor(guild, iterable) { + super(guild.client, iterable, GuildChannel); + + /** + * The guild this Manager belongs to + * @type {Guild} + */ + this.guild = guild; + } + + /** + * The cache of this Manager + * @type {Collection<Snowflake, GuildChannel>} + * @name GuildChannelManager#cache + */ + + add(channel) { + const existing = this.cache.get(channel.id); + if (existing) return existing; + this.cache.set(channel.id, channel); + return channel; + } + + /** + * Data that can be resolved to give a Guild Channel object. This can be: + * * A GuildChannel object + * * A Snowflake + * @typedef {GuildChannel|Snowflake} GuildChannelResolvable + */ + + /** + * Resolves a GuildChannelResolvable to a Channel object. + * @method resolve + * @memberof GuildChannelManager + * @instance + * @param {GuildChannelResolvable} channel The GuildChannel resolvable to resolve + * @returns {?Channel} + */ + + /** + * Resolves a GuildChannelResolvable to a channel ID string. + * @method resolveID + * @memberof GuildChannelManager + * @instance + * @param {GuildChannelResolvable} channel The GuildChannel resolvable to resolve + * @returns {?Snowflake} + */ + + /** + * Creates a new channel in the guild. + * @param {string} name The name of the new channel + * @param {Object} [options] Options + * @param {string} [options.type='text'] The type of the new channel, either `text`, `voice`, or `category` + * @param {string} [options.topic] The topic for the new channel + * @param {boolean} [options.nsfw] Whether the new channel is nsfw + * @param {number} [options.bitrate] Bitrate of the new channel in bits (only voice) + * @param {number} [options.userLimit] Maximum amount of users allowed in the new channel (only voice) + * @param {ChannelResolvable} [options.parent] Parent of the new channel + * @param {OverwriteResolvable[]|Collection<Snowflake, OverwriteResolvable>} [options.permissionOverwrites] + * Permission overwrites of the new channel + * @param {number} [options.position] Position of the new channel + * @param {number} [options.rateLimitPerUser] The ratelimit per user for the channel + * @param {string} [options.reason] Reason for creating the channel + * @returns {Promise<GuildChannel>} + * @example + * // Create a new text channel + * guild.channels.create('new-general', { reason: 'Needed a cool new channel' }) + * .then(console.log) + * .catch(console.error); + * @example + * // Create a new channel with permission overwrites + * guild.channels.create('new-voice', { + * type: 'voice', + * permissionOverwrites: [ + * { + * id: message.author.id, + * deny: ['VIEW_CHANNEL'], + * }, + * ], + * }) + */ + async create(name, options = {}) { + let { + type, + topic, + nsfw, + bitrate, + userLimit, + parent, + permissionOverwrites, + position, + rateLimitPerUser, + reason, + } = options; + if (parent) parent = this.client.channels.resolveID(parent); + if (permissionOverwrites) { + permissionOverwrites = permissionOverwrites.map(o => PermissionOverwrites.resolve(o, this.guild)); + } + + const data = await this.client.api.guilds(this.guild.id).channels.post({ + data: { + name, + topic, + type: type ? ChannelTypes[type.toUpperCase()] : ChannelTypes.TEXT, + nsfw, + bitrate, + user_limit: userLimit, + parent_id: parent, + position, + permission_overwrites: permissionOverwrites, + rate_limit_per_user: rateLimitPerUser, + }, + reason, + }); + return this.client.actions.ChannelCreate.handle(data).channel; + } +} + +module.exports = GuildChannelManager; diff --git a/node_modules/discord.js/src/managers/GuildEmojiManager.js b/node_modules/discord.js/src/managers/GuildEmojiManager.js new file mode 100644 index 0000000..c13ad13 --- /dev/null +++ b/node_modules/discord.js/src/managers/GuildEmojiManager.js @@ -0,0 +1,129 @@ +'use strict'; + +const BaseManager = require('./BaseManager'); +const { TypeError } = require('../errors'); +const GuildEmoji = require('../structures/GuildEmoji'); +const ReactionEmoji = require('../structures/ReactionEmoji'); +const Collection = require('../util/Collection'); +const DataResolver = require('../util/DataResolver'); + +/** + * Manages API methods for GuildEmojis and stores their cache. + * @extends {BaseManager} + */ +class GuildEmojiManager extends BaseManager { + constructor(guild, iterable) { + super(guild.client, iterable, GuildEmoji); + /** + * The guild this manager belongs to + * @type {Guild} + */ + this.guild = guild; + } + + /** + * The cache of GuildEmojis + * @type {Collection<Snowflake, GuildEmoji>} + * @name GuildEmojiManager#cache + */ + + add(data, cache) { + return super.add(data, cache, { extras: [this.guild] }); + } + + /** + * Creates a new custom emoji in the guild. + * @param {BufferResolvable|Base64Resolvable} attachment The image for the emoji + * @param {string} name The name for the emoji + * @param {Object} [options] Options + * @param {Collection<Snowflake, Role>|RoleResolvable[]} [options.roles] Roles to limit the emoji to + * @param {string} [options.reason] Reason for creating the emoji + * @returns {Promise<Emoji>} The created emoji + * @example + * // Create a new emoji from a url + * guild.emojis.create('https://i.imgur.com/w3duR07.png', 'rip') + * .then(emoji => console.log(`Created new emoji with name ${emoji.name}!`)) + * .catch(console.error); + * @example + * // Create a new emoji from a file on your computer + * guild.emojis.create('./memes/banana.png', 'banana') + * .then(emoji => console.log(`Created new emoji with name ${emoji.name}!`)) + * .catch(console.error); + */ + async create(attachment, name, { roles, reason } = {}) { + attachment = await DataResolver.resolveImage(attachment); + if (!attachment) throw new TypeError('REQ_RESOURCE_TYPE'); + + const data = { image: attachment, name }; + if (roles) { + data.roles = []; + for (let role of roles instanceof Collection ? roles.values() : roles) { + role = this.guild.roles.resolve(role); + if (!role) { + return Promise.reject( + new TypeError('INVALID_TYPE', 'options.roles', 'Array or Collection of Roles or Snowflakes', true), + ); + } + data.roles.push(role.id); + } + } + + return this.client.api + .guilds(this.guild.id) + .emojis.post({ data, reason }) + .then(emoji => this.client.actions.GuildEmojiCreate.handle(this.guild, emoji).emoji); + } + + /** + * Data that can be resolved into an GuildEmoji object. This can be: + * * A custom emoji ID + * * A GuildEmoji object + * * A ReactionEmoji object + * @typedef {Snowflake|GuildEmoji|ReactionEmoji} EmojiResolvable + */ + + /** + * Resolves an EmojiResolvable to an Emoji object. + * @param {EmojiResolvable} emoji The Emoji resolvable to identify + * @returns {?GuildEmoji} + */ + resolve(emoji) { + if (emoji instanceof ReactionEmoji) return super.resolve(emoji.id); + return super.resolve(emoji); + } + + /** + * Resolves an EmojiResolvable to an Emoji ID string. + * @param {EmojiResolvable} emoji The Emoji resolvable to identify + * @returns {?Snowflake} + */ + resolveID(emoji) { + if (emoji instanceof ReactionEmoji) return emoji.id; + return super.resolveID(emoji); + } + + /** + * Data that can be resolved to give an emoji identifier. This can be: + * * The unicode representation of an emoji + * * An EmojiResolvable + * @typedef {string|EmojiResolvable} EmojiIdentifierResolvable + */ + + /** + * Resolves an EmojiResolvable to an emoji identifier. + * @param {EmojiIdentifierResolvable} emoji The emoji resolvable to resolve + * @returns {?string} + */ + resolveIdentifier(emoji) { + const emojiResolvable = this.resolve(emoji); + if (emojiResolvable) return emojiResolvable.identifier; + if (emoji instanceof ReactionEmoji) return emoji.identifier; + if (typeof emoji === 'string') { + if (!emoji.includes('%')) return encodeURIComponent(emoji); + else return emoji; + } + return null; + } +} + +module.exports = GuildEmojiManager; diff --git a/node_modules/discord.js/src/managers/GuildEmojiRoleManager.js b/node_modules/discord.js/src/managers/GuildEmojiRoleManager.js new file mode 100644 index 0000000..038571f --- /dev/null +++ b/node_modules/discord.js/src/managers/GuildEmojiRoleManager.js @@ -0,0 +1,119 @@ +'use strict'; + +const { TypeError } = require('../errors'); +const Collection = require('../util/Collection'); + +/** + * Manages API methods for roles belonging to emojis and stores their cache. + */ +class GuildEmojiRoleManager { + constructor(emoji) { + /** + * The emoji belonging to this manager + * @type {GuildEmoji} + */ + this.emoji = emoji; + /** + * The guild belonging to this manager + * @type {Guild} + */ + this.guild = emoji.guild; + /** + * The client belonging to this manager + * @type {Client} + * @readonly + */ + Object.defineProperty(this, 'client', { value: emoji.client }); + } + + /** + * The filtered collection of roles of the guild emoji + * @type {Collection<Snowflake, Role>} + * @private + * @readonly + */ + get _roles() { + return this.guild.roles.cache.filter(role => this.emoji._roles.includes(role.id)); + } + + /** + * The cache of roles belonging to this emoji + * @type {Collection<Snowflake, Role>} + * @readonly + */ + get cache() { + return this._roles; + } + + /** + * Adds a role (or multiple roles) to the list of roles that can use this emoji. + * @param {RoleResolvable|RoleResolvable[]|Collection<Snowflake, Role>} roleOrRoles The role or roles to add + * @returns {Promise<GuildEmoji>} + */ + add(roleOrRoles) { + if (roleOrRoles instanceof Collection) return this.add(roleOrRoles.keyArray()); + if (!Array.isArray(roleOrRoles)) return this.add([roleOrRoles]); + roleOrRoles = roleOrRoles.map(r => this.guild.roles.resolve(r)); + + if (roleOrRoles.includes(null)) { + return Promise.reject(new TypeError('INVALID_TYPE', 'roles', 'Array or Collection of Roles or Snowflakes', true)); + } + + const newRoles = [...new Set(roleOrRoles.concat(...this._roles.values()))]; + return this.set(newRoles); + } + + /** + * Removes a role (or multiple roles) from the list of roles that can use this emoji. + * @param {RoleResolvable|RoleResolvable[]|Collection<Snowflake, Role>} roleOrRoles The role or roles to remove + * @returns {Promise<GuildEmoji>} + */ + remove(roleOrRoles) { + if (roleOrRoles instanceof Collection) return this.remove(roleOrRoles.keyArray()); + if (!Array.isArray(roleOrRoles)) return this.remove([roleOrRoles]); + roleOrRoles = roleOrRoles.map(r => this.guild.roles.resolveID(r)); + + if (roleOrRoles.includes(null)) { + return Promise.reject(new TypeError('INVALID_TYPE', 'roles', 'Array or Collection of Roles or Snowflakes', true)); + } + + const newRoles = this._roles.keyArray().filter(role => !roleOrRoles.includes(role)); + return this.set(newRoles); + } + + /** + * Sets the role(s) that can use this emoji. + * @param {Collection<Snowflake, Role>|RoleResolvable[]} roles The roles or role IDs to apply + * @returns {Promise<GuildEmoji>} + * @example + * // Set the emoji's roles to a single role + * guildEmoji.roles.set(['391156570408615936']) + * .then(console.log) + * .catch(console.error); + * @example + * // Remove all roles from an emoji + * guildEmoji.roles.set([]) + * .then(console.log) + * .catch(console.error); + */ + set(roles) { + return this.emoji.edit({ roles }); + } + + clone() { + const clone = new this.constructor(this.emoji); + clone._patch(this._roles.keyArray().slice()); + return clone; + } + + /** + * Patches the roles for this manager's cache + * @param {Snowflake[]} roles The new roles + * @private + */ + _patch(roles) { + this.emoji._roles = roles; + } +} + +module.exports = GuildEmojiRoleManager; diff --git a/node_modules/discord.js/src/managers/GuildManager.js b/node_modules/discord.js/src/managers/GuildManager.js new file mode 100644 index 0000000..42cbcd4 --- /dev/null +++ b/node_modules/discord.js/src/managers/GuildManager.js @@ -0,0 +1,216 @@ +'use strict'; + +const BaseManager = require('./BaseManager'); +const Guild = require('../structures/Guild'); +const GuildChannel = require('../structures/GuildChannel'); +const GuildEmoji = require('../structures/GuildEmoji'); +const GuildMember = require('../structures/GuildMember'); +const Invite = require('../structures/Invite'); +const Role = require('../structures/Role'); +const { + Events, + VerificationLevels, + DefaultMessageNotifications, + ExplicitContentFilterLevels, +} = require('../util/Constants'); +const DataResolver = require('../util/DataResolver'); +const Permissions = require('../util/Permissions'); +const { resolveColor } = require('../util/Util'); + +/** + * Manages API methods for Guilds and stores their cache. + * @extends {BaseManager} + */ +class GuildManager extends BaseManager { + constructor(client, iterable) { + super(client, iterable, Guild); + } + + /** + * The cache of this Manager + * @type {Collection<Snowflake, Guild>} + * @name GuildManager#cache + */ + + /** + * Data that resolves to give a Guild object. This can be: + * * A Guild object + * * A GuildChannel object + * * A GuildEmoji object + * * A Role object + * * A Snowflake + * * An Invite object + * @typedef {Guild|GuildChannel|GuildMember|GuildEmoji|Role|Snowflake|Invite} GuildResolvable + */ + + /** + * Partial data for a Role. + * @typedef {Object} PartialRoleData + * @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 + * @property {boolean} [hoist] Whether or not the role should be hoisted + * @property {number} [position] The position of the role + * @property {PermissionResolvable|number} [permissions] The permissions of the role + * @property {boolean} [mentionable] Whether or not the role should be mentionable + */ + + /** + * Partial overwrite data. + * @typedef {Object} PartialOverwriteData + * @property {number|Snowflake} id The Role or User ID for this overwrite + * @property {string} [type] The type of this overwrite + * @property {PermissionResolvable} [allow] The permissions to allow + * @property {PermissionResolvable} [deny] The permissions to deny + */ + + /** + * Partial data for a Channel. + * @typedef {Object} PartialChannelData + * @property {number} [id] The ID for this channel, used to set its parent, + * this is a placeholder and will be replaced by the API after consumption + * @property {number} [parentID] The parent ID for this channel + * @property {string} [type] The type of the channel + * @property {string} name The name of the channel + * @property {string} [topic] The topic of the text channel + * @property {boolean} [nsfw] Whether the channel is NSFW + * @property {number} [bitrate] The bitrate of the voice channel + * @property {number} [userLimit] The user limit of the channel + * @property {PartialOverwriteData} [permissionOverwrites] + * Overwrites of the channel + * @property {number} [rateLimitPerUser] The rate limit per user of the channel in seconds + */ + + /** + * Resolves a GuildResolvable to a Guild object. + * @method resolve + * @memberof GuildManager + * @instance + * @param {GuildResolvable} guild The guild resolvable to identify + * @returns {?Guild} + */ + resolve(guild) { + if ( + guild instanceof GuildChannel || + guild instanceof GuildMember || + guild instanceof GuildEmoji || + guild instanceof Role || + (guild instanceof Invite && guild.guild) + ) { + return super.resolve(guild.guild); + } + return super.resolve(guild); + } + + /** + * Resolves a GuildResolvable to a Guild ID string. + * @method resolveID + * @memberof GuildManager + * @instance + * @param {GuildResolvable} guild The guild resolvable to identify + * @returns {?Snowflake} + */ + resolveID(guild) { + if ( + guild instanceof GuildChannel || + guild instanceof GuildMember || + guild instanceof GuildEmoji || + guild instanceof Role || + (guild instanceof Invite && guild.guild) + ) { + return super.resolveID(guild.guild.id); + } + return super.resolveID(guild); + } + + /** + * Creates a guild. + * <warn>This is only available to bots in fewer than 10 guilds.</warn> + * @param {string} name The name of the guild + * @param {Object} [options] Options for the creating + * @param {PartialChannelData[]} [options.channels] The channels for this guild + * @param {DefaultMessageNotifications} [options.defaultMessageNotifications] The default message notifications + * for the guild + * @param {ExplicitContentFilterLevel} [options.explicitContentFilter] The explicit content filter level for the guild + * @param {BufferResolvable|Base64Resolvable} [options.icon=null] The icon for the guild + * @param {string} [options.region] The region for the server, defaults to the closest one available + * @param {PartialRoleData[]} [options.roles] The roles for this guild, + * the first element of this array is used to change properties of the guild's everyone role. + * @param {VerificationLevel} [options.verificationLevel] The verification level for the guild + * @returns {Promise<Guild>} The guild that was created + */ + async create( + name, + { + channels = [], + defaultMessageNotifications, + explicitContentFilter, + icon = null, + region, + roles = [], + verificationLevel, + } = {}, + ) { + icon = await DataResolver.resolveImage(icon); + if (typeof verificationLevel !== 'undefined' && typeof verificationLevel !== 'number') { + verificationLevel = VerificationLevels.indexOf(verificationLevel); + } + if (typeof defaultMessageNotifications !== 'undefined' && typeof defaultMessageNotifications !== 'number') { + defaultMessageNotifications = DefaultMessageNotifications.indexOf(defaultMessageNotifications); + } + if (typeof explicitContentFilter !== 'undefined' && typeof explicitContentFilter !== 'number') { + explicitContentFilter = ExplicitContentFilterLevels.indexOf(explicitContentFilter); + } + for (const channel of channels) { + channel.parent_id = channel.parentID; + delete channel.parentID; + if (!channel.permissionOverwrites) continue; + for (const overwrite of channel.permissionOverwrites) { + if (overwrite.allow) overwrite.allow = Permissions.resolve(overwrite.allow); + if (overwrite.deny) overwrite.deny = Permissions.resolve(overwrite.deny); + } + channel.permission_overwrites = channel.permissionOverwrites; + delete channel.permissionOverwrites; + } + for (const role of roles) { + if (role.color) role.color = resolveColor(role.color); + if (role.permissions) role.permissions = Permissions.resolve(role.permissions); + } + return new Promise((resolve, reject) => + this.client.api.guilds + .post({ + data: { + name, + region, + icon, + verification_level: verificationLevel, + default_message_notifications: defaultMessageNotifications, + explicit_content_filter: explicitContentFilter, + channels, + roles, + }, + }) + .then(data => { + if (this.client.guilds.cache.has(data.id)) return resolve(this.client.guilds.cache.get(data.id)); + + const handleGuild = guild => { + if (guild.id === data.id) { + this.client.removeListener(Events.GUILD_CREATE, handleGuild); + this.client.clearTimeout(timeout); + resolve(guild); + } + }; + this.client.on(Events.GUILD_CREATE, handleGuild); + + const timeout = this.client.setTimeout(() => { + this.client.removeListener(Events.GUILD_CREATE, handleGuild); + resolve(this.client.guilds.add(data)); + }, 10000); + return undefined; + }, reject), + ); + } +} + +module.exports = GuildManager; diff --git a/node_modules/discord.js/src/managers/GuildMemberManager.js b/node_modules/discord.js/src/managers/GuildMemberManager.js new file mode 100644 index 0000000..e1387e8 --- /dev/null +++ b/node_modules/discord.js/src/managers/GuildMemberManager.js @@ -0,0 +1,272 @@ +'use strict'; + +const BaseManager = require('./BaseManager'); +const { Error, TypeError } = require('../errors'); +const GuildMember = require('../structures/GuildMember'); +const Collection = require('../util/Collection'); +const { Events, OPCodes } = require('../util/Constants'); + +/** + * Manages API methods for GuildMembers and stores their cache. + * @extends {BaseManager} + */ +class GuildMemberManager extends BaseManager { + constructor(guild, iterable) { + super(guild.client, iterable, GuildMember); + /** + * The guild this manager belongs to + * @type {Guild} + */ + this.guild = guild; + } + + /** + * The cache of this Manager + * @type {Collection<Snowflake, GuildMember>} + * @name GuildMemberManager#cache + */ + + add(data, cache = true) { + return super.add(data, cache, { id: data.user.id, extras: [this.guild] }); + } + + /** + * Data that resolves to give a GuildMember object. This can be: + * * A GuildMember object + * * A User resolvable + * @typedef {GuildMember|UserResolvable} GuildMemberResolvable + */ + + /** + * Resolves a GuildMemberResolvable to a GuildMember object. + * @param {GuildMemberResolvable} member The user that is part of the guild + * @returns {?GuildMember} + */ + resolve(member) { + const memberResolvable = super.resolve(member); + if (memberResolvable) return memberResolvable; + const userResolvable = this.client.users.resolveID(member); + if (userResolvable) return super.resolve(userResolvable); + return null; + } + + /** + * Resolves a GuildMemberResolvable to a member ID string. + * @param {GuildMemberResolvable} member The user that is part of the guild + * @returns {?Snowflake} + */ + resolveID(member) { + const memberResolvable = super.resolveID(member); + if (memberResolvable) return memberResolvable; + const userResolvable = this.client.users.resolveID(member); + return this.cache.has(userResolvable) ? userResolvable : null; + } + + /** + * Options used to fetch a single member from a guild. + * @typedef {Object} FetchMemberOptions + * @property {UserResolvable} user The user to fetch + * @property {boolean} [cache=true] Whether or not to cache the fetched member + */ + + /** + * Options used to fetch multiple members from a guild. + * @typedef {Object} FetchMembersOptions + * @property {UserResolvable|UserResolvable[]} user The user(s) to fetch + * @property {?string} query Limit fetch to members with similar usernames + * @property {number} [limit=0] Maximum number of members to request + * @property {boolean} [withPresences=false] Whether or not to include the presences + * @property {number} [time=120e3] Timeout for receipt of members + */ + + /** + * Fetches member(s) from Discord, even if they're offline. + * @param {UserResolvable|FetchMemberOptions|FetchMembersOptions} [options] If a UserResolvable, the user to fetch. + * If undefined, fetches all members. + * If a query, it limits the results to users with similar usernames. + * @returns {Promise<GuildMember>|Promise<Collection<Snowflake, GuildMember>>} + * @example + * // Fetch all members from a guild + * guild.members.fetch() + * .then(console.log) + * .catch(console.error); + * @example + * // Fetch a single member + * guild.members.fetch('66564597481480192') + * .then(console.log) + * .catch(console.error); + * @example + * // Fetch a single member without caching + * guild.members.fetch({ user, cache: false }) + * .then(console.log) + * .catch(console.error); + * @example + * // Fetch by an array of users including their presences + * guild.members.fetch({ user: ['66564597481480192', '191615925336670208'], withPresences: true }) + * .then(console.log) + * .catch(console.error); + * @example + * // Fetch by query + * guild.members.fetch({ query: 'hydra', limit: 1 }) + * .then(console.log) + * .catch(console.error); + */ + fetch(options) { + if (!options) return this._fetchMany(); + const user = this.client.users.resolveID(options); + if (user) return this._fetchSingle({ user, cache: true }); + if (options.user) { + if (Array.isArray(options.user)) { + options.user = options.user.map(u => this.client.users.resolveID(u)); + return this._fetchMany(options); + } else { + options.user = this.client.users.resolveID(options.user); + } + if (!options.limit && !options.withPresences) return this._fetchSingle(options); + } + return this._fetchMany(options); + } + + /** + * Prunes members from the guild based on how long they have been inactive. + * <info>It's recommended to set options.count to `false` for large guilds.</info> + * @param {Object} [options] Prune options + * @param {number} [options.days=7] Number of days of inactivity required to kick + * @param {boolean} [options.dry=false] Get number of users that will be kicked, without actually kicking them + * @param {boolean} [options.count=true] Whether or not to return the number of users that have been kicked. + * @param {string} [options.reason] Reason for this prune + * @returns {Promise<number|null>} The number of members that were/will be kicked + * @example + * // See how many members will be pruned + * guild.members.prune({ dry: true }) + * .then(pruned => console.log(`This will prune ${pruned} people!`)) + * .catch(console.error); + * @example + * // Actually prune the members + * guild.members.prune({ days: 1, reason: 'too many people!' }) + * .then(pruned => console.log(`I just pruned ${pruned} people!`)) + * .catch(console.error); + */ + prune({ days = 7, dry = false, count = true, reason } = {}) { + if (typeof days !== 'number') throw new TypeError('PRUNE_DAYS_TYPE'); + return this.client.api + .guilds(this.guild.id) + .prune[dry ? 'get' : 'post']({ + query: { + days, + compute_prune_count: count, + }, + reason, + }) + .then(data => data.pruned); + } + + /** + * Bans a user from the guild. + * @param {UserResolvable} user The user to ban + * @param {Object} [options] Options for the ban + * @param {number} [options.days=0] Number of days of messages to delete + * @param {string} [options.reason] Reason for banning + * @returns {Promise<GuildMember|User|Snowflake>} Result object will be resolved as specifically as possible. + * If the GuildMember cannot be resolved, the User will instead be attempted to be resolved. If that also cannot + * be resolved, the user ID will be the result. + * @example + * // Ban a user by ID (or with a user/guild member object) + * guild.members.ban('84484653687267328') + * .then(user => console.log(`Banned ${user.username || user.id || user} from ${guild.name}`)) + * .catch(console.error); + */ + ban(user, options = { days: 0 }) { + if (options.days) options['delete-message-days'] = options.days; + const id = this.client.users.resolveID(user); + if (!id) return Promise.reject(new Error('BAN_RESOLVE_ID', true)); + return this.client.api + .guilds(this.guild.id) + .bans[id].put({ query: options }) + .then(() => { + if (user instanceof GuildMember) return user; + const _user = this.client.users.resolve(id); + if (_user) { + const member = this.resolve(_user); + return member || _user; + } + return id; + }); + } + + /** + * Unbans a user from the guild. + * @param {UserResolvable} user The user to unban + * @param {string} [reason] Reason for unbanning user + * @returns {Promise<User>} + * @example + * // Unban a user by ID (or with a user/guild member object) + * guild.members.unban('84484653687267328') + * .then(user => console.log(`Unbanned ${user.username} from ${guild.name}`)) + * .catch(console.error); + */ + unban(user, reason) { + const id = this.client.users.resolveID(user); + if (!id) return Promise.reject(new Error('BAN_RESOLVE_ID')); + return this.client.api + .guilds(this.guild.id) + .bans[id].delete({ reason }) + .then(() => this.client.users.resolve(user)); + } + + _fetchSingle({ user, cache }) { + const existing = this.cache.get(user); + if (existing && !existing.partial) return Promise.resolve(existing); + return this.client.api + .guilds(this.guild.id) + .members(user) + .get() + .then(data => this.add(data, cache)); + } + + _fetchMany({ limit = 0, withPresences: presences = false, user: user_ids, query, time = 120e3 } = {}) { + return new Promise((resolve, reject) => { + if (this.guild.memberCount === this.cache.size && !query && !limit && !presences && !user_ids) { + resolve(this.cache); + return; + } + if (!query && !user_ids) query = ''; + this.guild.shard.send({ + op: OPCodes.REQUEST_GUILD_MEMBERS, + d: { + guild_id: this.guild.id, + presences, + user_ids, + query, + limit, + }, + }); + const fetchedMembers = new Collection(); + const option = query || limit || presences || user_ids; + const handler = (members, guild) => { + if (guild.id !== this.guild.id) return; + timeout.refresh(); + for (const member of members.values()) { + if (option) fetchedMembers.set(member.id, member); + } + if ( + this.guild.memberCount <= this.cache.size || + (option && members.size < 1000) || + (limit && fetchedMembers.size >= limit) + ) { + this.guild.client.removeListener(Events.GUILD_MEMBERS_CHUNK, handler); + let fetched = option ? fetchedMembers : this.cache; + if (user_ids && !Array.isArray(user_ids) && fetched.size) fetched = fetched.first(); + resolve(fetched); + } + }; + const timeout = this.guild.client.setTimeout(() => { + this.guild.client.removeListener(Events.GUILD_MEMBERS_CHUNK, handler); + reject(new Error('GUILD_MEMBERS_TIMEOUT')); + }, time); + this.guild.client.on(Events.GUILD_MEMBERS_CHUNK, handler); + }); + } +} + +module.exports = GuildMemberManager; diff --git a/node_modules/discord.js/src/managers/GuildMemberRoleManager.js b/node_modules/discord.js/src/managers/GuildMemberRoleManager.js new file mode 100644 index 0000000..f4f530d --- /dev/null +++ b/node_modules/discord.js/src/managers/GuildMemberRoleManager.js @@ -0,0 +1,166 @@ +'use strict'; + +const { TypeError } = require('../errors'); +const Collection = require('../util/Collection'); + +/** + * Manages API methods for roles of a GuildMember and stores their cache. + */ +class GuildMemberRoleManager { + constructor(member) { + /** + * The GuildMember this manager belongs to + * @type {GuildMember} + */ + this.member = member; + /** + * The Guild this manager belongs to + * @type {Guild} + */ + this.guild = member.guild; + Object.defineProperty(this, 'client', { value: member.client }); + } + + /** + * The filtered collection of roles of the member + * @type {Collection<Snowflake, Role>} + * @private + * @readonly + */ + get _roles() { + const everyone = this.guild.roles.everyone; + return this.guild.roles.cache.filter(role => this.member._roles.includes(role.id)).set(everyone.id, everyone); + } + + /** + * The roles of this member + * @type {Collection<Snowflake, Role>} + * @readonly + */ + get cache() { + return this._roles; + } + + /** + * The role of the member used to hoist them in a separate category in the users list + * @type {?Role} + * @readonly + */ + get hoist() { + const hoistedRoles = this._roles.filter(role => role.hoist); + if (!hoistedRoles.size) return null; + return hoistedRoles.reduce((prev, role) => (!prev || role.comparePositionTo(prev) > 0 ? role : prev)); + } + + /** + * The role of the member used to set their color + * @type {?Role} + * @readonly + */ + get color() { + const coloredRoles = this._roles.filter(role => role.color); + if (!coloredRoles.size) return null; + return coloredRoles.reduce((prev, role) => (!prev || role.comparePositionTo(prev) > 0 ? role : prev)); + } + + /** + * The role of the member with the highest position + * @type {Role} + * @readonly + */ + get highest() { + return this._roles.reduce((prev, role) => (role.comparePositionTo(prev) > 0 ? role : prev), this._roles.first()); + } + + /** + * Adds a role (or multiple roles) to the member. + * @param {RoleResolvable|RoleResolvable[]|Collection<Snowflake, Role>} roleOrRoles The role or roles to add + * @param {string} [reason] Reason for adding the role(s) + * @returns {Promise<GuildMember>} + */ + async add(roleOrRoles, reason) { + if (roleOrRoles instanceof Collection || Array.isArray(roleOrRoles)) { + roleOrRoles = roleOrRoles.map(r => this.guild.roles.resolve(r)); + if (roleOrRoles.includes(null)) { + throw new TypeError('INVALID_TYPE', 'roles', 'Array or Collection of Roles or Snowflakes', true); + } + + const newRoles = [...new Set(roleOrRoles.concat(...this._roles.values()))]; + return this.set(newRoles, reason); + } else { + roleOrRoles = this.guild.roles.resolve(roleOrRoles); + if (roleOrRoles === null) { + throw new TypeError( + 'INVALID_TYPE', + 'roles', + 'Role, Snowflake or Array or Collection of Roles or Snowflakes', + true, + ); + } + + await this.client.api.guilds[this.guild.id].members[this.member.id].roles[roleOrRoles.id].put({ reason }); + + const clone = this.member._clone(); + clone._roles = [...this._roles.keys(), roleOrRoles.id]; + return clone; + } + } + + /** + * Removes a role (or multiple roles) from the member. + * @param {RoleResolvable|RoleResolvable[]|Collection<Snowflake, Role>} roleOrRoles The role or roles to remove + * @param {string} [reason] Reason for removing the role(s) + * @returns {Promise<GuildMember>} + */ + async remove(roleOrRoles, reason) { + if (roleOrRoles instanceof Collection || Array.isArray(roleOrRoles)) { + roleOrRoles = roleOrRoles.map(r => this.guild.roles.resolve(r)); + if (roleOrRoles.includes(null)) { + throw new TypeError('INVALID_TYPE', 'roles', 'Array or Collection of Roles or Snowflakes', true); + } + + const newRoles = this._roles.filter(role => !roleOrRoles.includes(role)); + return this.set(newRoles, reason); + } else { + roleOrRoles = this.guild.roles.resolve(roleOrRoles); + if (roleOrRoles === null) { + throw new TypeError('INVALID_TYPE', 'roles', 'Array or Collection of Roles or Snowflakes', true); + } + + await this.client.api.guilds[this.guild.id].members[this.member.id].roles[roleOrRoles.id].delete({ reason }); + + const clone = this.member._clone(); + const newRoles = this._roles.filter(role => role.id !== roleOrRoles.id); + clone._roles = [...newRoles.keys()]; + return clone; + } + } + + /** + * Sets the roles applied to the member. + * @param {Collection<Snowflake, Role>|RoleResolvable[]} roles The roles or role IDs to apply + * @param {string} [reason] Reason for applying the roles + * @returns {Promise<GuildMember>} + * @example + * // Set the member's roles to a single role + * guildMember.roles.set(['391156570408615936']) + * .then(console.log) + * .catch(console.error); + * @example + * // Remove all the roles from a member + * guildMember.roles.set([]) + * .then(member => console.log(`Member roles is now of ${member.roles.cache.size} size`)) + * .catch(console.error); + */ + set(roles, reason) { + return this.member.edit({ roles }, reason); + } + + clone() { + const clone = new this.constructor(this.member); + clone.member._roles = [...this._roles.keyArray()]; + return clone; + } +} + +module.exports = GuildMemberRoleManager; diff --git a/node_modules/discord.js/src/managers/MessageManager.js b/node_modules/discord.js/src/managers/MessageManager.js new file mode 100644 index 0000000..6d51bcf --- /dev/null +++ b/node_modules/discord.js/src/managers/MessageManager.js @@ -0,0 +1,145 @@ +'use strict'; + +const BaseManager = require('./BaseManager'); +const Message = require('../structures/Message'); +const Collection = require('../util/Collection'); +const LimitedCollection = require('../util/LimitedCollection'); + +/** + * Manages API methods for Messages and holds their cache. + * @extends {BaseManager} + */ +class MessageManager extends BaseManager { + constructor(channel, iterable) { + super(channel.client, iterable, Message, LimitedCollection, channel.client.options.messageCacheMaxSize); + /** + * The channel that the messages belong to + * @type {TextBasedChannel} + */ + this.channel = channel; + } + + /** + * The cache of Messages + * @type {Collection<Snowflake, Message>} + * @name MessageManager#cache + */ + + add(data, cache) { + return super.add(data, cache, { extras: [this.channel] }); + } + + /** + * The parameters to pass in when requesting previous messages from a channel. `around`, `before` and + * `after` are mutually exclusive. All the parameters are optional. + * @typedef {Object} ChannelLogsQueryOptions + * @property {number} [limit=50] Number of messages to acquire + * @property {Snowflake} [before] ID of a message to get the messages that were posted before it + * @property {Snowflake} [after] ID of a message to get the messages that were posted after it + * @property {Snowflake} [around] ID of a message to get the messages that were posted around it + */ + + /** + * Gets a message, or messages, from this channel. + * <info>The returned Collection does not contain reaction users of the messages if they were not cached. + * Those need to be fetched separately in such a case.</info> + * @param {Snowflake|ChannelLogsQueryOptions} [message] The ID of the message to fetch, or query parameters. + * @param {boolean} [cache=true] Whether to cache the message(s) + * @returns {Promise<Message>|Promise<Collection<Snowflake, Message>>} + * @example + * // Get message + * channel.messages.fetch('99539446449315840') + * .then(message => console.log(message.content)) + * .catch(console.error); + * @example + * // Get messages + * channel.messages.fetch({ limit: 10 }) + * .then(messages => console.log(`Received ${messages.size} messages`)) + * .catch(console.error); + * @example + * // Get messages and filter by user ID + * channel.messages.fetch() + * .then(messages => console.log(`${messages.filter(m => m.author.id === '84484653687267328').size} messages`)) + * .catch(console.error); + */ + fetch(message, cache = true) { + return typeof message === 'string' ? this._fetchId(message, cache) : this._fetchMany(message, cache); + } + + /** + * Fetches the pinned messages of this channel and returns a collection of them. + * <info>The returned Collection does not contain any reaction data of the messages. + * Those need to be fetched separately.</info> + * @param {boolean} [cache=true] Whether to cache the message(s) + * @returns {Promise<Collection<Snowflake, Message>>} + * @example + * // Get pinned messages + * channel.fetchPinned() + * .then(messages => console.log(`Received ${messages.size} messages`)) + * .catch(console.error); + */ + fetchPinned(cache = true) { + return this.client.api.channels[this.channel.id].pins.get().then(data => { + const messages = new Collection(); + for (const message of data) messages.set(message.id, this.add(message, cache)); + return messages; + }); + } + + /** + * Data that can be resolved to a Message object. This can be: + * * A Message + * * A Snowflake + * @typedef {Message|Snowflake} MessageResolvable + */ + + /** + * Resolves a MessageResolvable to a Message object. + * @method resolve + * @memberof MessageManager + * @instance + * @param {MessageResolvable} message The message resolvable to resolve + * @returns {?Message} + */ + + /** + * Resolves a MessageResolvable to a Message ID string. + * @method resolveID + * @memberof MessageManager + * @instance + * @param {MessageResolvable} message The message resolvable to resolve + * @returns {?Snowflake} + */ + + /** + * Deletes a message, even if it's not cached. + * @param {MessageResolvable} message The message to delete + * @param {string} [reason] Reason for deleting this message, if it does not belong to the client user + * @returns {Promise<void>} + */ + async delete(message, reason) { + message = this.resolveID(message); + if (message) { + await this.client.api + .channels(this.channel.id) + .messages(message) + .delete({ reason }); + } + } + + async _fetchId(messageID, cache) { + const existing = this.cache.get(messageID); + if (existing && !existing.partial) return existing; + const data = await this.client.api.channels[this.channel.id].messages[messageID].get(); + return this.add(data, cache); + } + + async _fetchMany(options = {}, cache) { + const data = await this.client.api.channels[this.channel.id].messages.get({ query: options }); + const messages = new Collection(); + for (const message of data) messages.set(message.id, this.add(message, cache)); + return messages; + } +} + +module.exports = MessageManager; diff --git a/node_modules/discord.js/src/managers/PresenceManager.js b/node_modules/discord.js/src/managers/PresenceManager.js new file mode 100644 index 0000000..c02fe04 --- /dev/null +++ b/node_modules/discord.js/src/managers/PresenceManager.js @@ -0,0 +1,59 @@ +'use strict'; + +const BaseManager = require('./BaseManager'); +const { Presence } = require('../structures/Presence'); + +/** + * Manages API methods for Presences and holds their cache. + * @extends {BaseManager} + */ +class PresenceManager extends BaseManager { + constructor(client, iterable) { + super(client, iterable, Presence); + } + + /** + * The cache of Presences + * @type {Collection<Snowflake, Presence>} + * @name PresenceManager#cache + */ + + add(data, cache) { + const existing = this.cache.get(data.user.id); + return existing ? existing.patch(data) : super.add(data, cache, { id: data.user.id }); + } + + /** + * Data that can be resolved to a Presence object. This can be: + * * A Presence + * * A UserResolvable + * * A Snowflake + * @typedef {Presence|UserResolvable|Snowflake} PresenceResolvable + */ + + /** + * Resolves a PresenceResolvable to a Presence object. + * @param {PresenceResolvable} presence The presence resolvable to resolve + * @returns {?Presence} + */ + resolve(presence) { + const presenceResolvable = super.resolve(presence); + if (presenceResolvable) return presenceResolvable; + const UserResolvable = this.client.users.resolveID(presence); + return super.resolve(UserResolvable) || null; + } + + /** + * Resolves a PresenceResolvable to a Presence ID string. + * @param {PresenceResolvable} presence The presence resolvable to resolve + * @returns {?Snowflake} + */ + resolveID(presence) { + const presenceResolvable = super.resolveID(presence); + if (presenceResolvable) return presenceResolvable; + const userResolvable = this.client.users.resolveID(presence); + return this.cache.has(userResolvable) ? userResolvable : null; + } +} + +module.exports = PresenceManager; diff --git a/node_modules/discord.js/src/managers/ReactionManager.js b/node_modules/discord.js/src/managers/ReactionManager.js new file mode 100644 index 0000000..b46d187 --- /dev/null +++ b/node_modules/discord.js/src/managers/ReactionManager.js @@ -0,0 +1,69 @@ +'use strict'; + +const BaseManager = require('./BaseManager'); +const MessageReaction = require('../structures/MessageReaction'); + +/** + * Manages API methods for reactions and holds their cache. + * @extends {BaseManager} + */ +class ReactionManager extends BaseManager { + constructor(message, iterable) { + super(message.client, iterable, MessageReaction); + + /** + * The message that this manager belongs to + * @type {Message} + */ + this.message = message; + } + + add(data, cache) { + return super.add(data, cache, { id: data.emoji.id || data.emoji.name, extras: [this.message] }); + } + + /** + * The reaction cache of this manager + * @type {Collection<Snowflake, MessageReaction>} + * @name ReactionManager#cache + */ + + /** + * Data that can be resolved to a MessageReaction object. This can be: + * * A MessageReaction + * * A Snowflake + * @typedef {MessageReaction|Snowflake} MessageReactionResolvable + */ + + /** + * Resolves a MessageReactionResolvable to a MessageReaction object. + * @method resolve + * @memberof ReactionManager + * @instance + * @param {MessageReactionResolvable} reaction The MessageReaction to resolve + * @returns {?MessageReaction} + */ + + /** + * Resolves a MessageReactionResolvable to a MessageReaction ID string. + * @method resolveID + * @memberof ReactionManager + * @instance + * @param {MessageReactionResolvable} reaction The MessageReaction to resolve + * @returns {?Snowflake} + */ + + /** + * Removes all reactions from a message. + * @returns {Promise<Message>} + */ + removeAll() { + return this.client.api + .channels(this.message.channel.id) + .messages(this.message.id) + .reactions.delete() + .then(() => this.message); + } +} + +module.exports = ReactionManager; diff --git a/node_modules/discord.js/src/managers/ReactionUserManager.js b/node_modules/discord.js/src/managers/ReactionUserManager.js new file mode 100644 index 0000000..1a6ce14 --- /dev/null +++ b/node_modules/discord.js/src/managers/ReactionUserManager.js @@ -0,0 +1,66 @@ +'use strict'; + +const BaseManager = require('./BaseManager'); +const { Error } = require('../errors'); +const Collection = require('../util/Collection'); + +/** + * Manages API methods for users who reacted to a reaction and stores their cache. + * @extends {BaseManager} + */ +class ReactionUserManager extends BaseManager { + constructor(client, iterable, reaction) { + super(client, iterable, { name: 'User' }); + /** + * The reaction that this manager belongs to + * @type {MessageReaction} + */ + this.reaction = reaction; + } + + /** + * The cache of this manager + * @type {Collection<Snowflake, User>} + * @name ReactionUserManager#cache + */ + + /** + * Fetches all the users that gave this reaction. Resolves with a collection of users, mapped by their IDs. + * @param {Object} [options] Options for fetching the users + * @param {number} [options.limit=100] The maximum amount of users to fetch, defaults to 100 + * @param {Snowflake} [options.before] Limit fetching users to those with an id lower than the supplied id + * @param {Snowflake} [options.after] Limit fetching users to those with an id greater than the supplied id + * @returns {Promise<Collection<Snowflake, User>>} + */ + async fetch({ limit = 100, after, before } = {}) { + const message = this.reaction.message; + const data = await this.client.api.channels[message.channel.id].messages[message.id].reactions[ + this.reaction.emoji.identifier + ].get({ query: { limit, before, after } }); + const users = new Collection(); + for (const rawUser of data) { + const user = this.client.users.add(rawUser); + this.cache.set(user.id, user); + users.set(user.id, user); + } + return users; + } + + /** + * Removes a user from this reaction. + * @param {UserResolvable} [user=this.reaction.message.client.user] The user to remove the reaction of + * @returns {Promise<MessageReaction>} + */ + remove(user = this.reaction.message.client.user) { + const message = this.reaction.message; + const userID = message.client.users.resolveID(user); + if (!userID) return Promise.reject(new Error('REACTION_RESOLVE_USER')); + return message.client.api.channels[message.channel.id].messages[message.id].reactions[ + this.reaction.emoji.identifier + ][userID === message.client.user.id ? '@me' : userID] + .delete() + .then(() => this.reaction); + } +} + +module.exports = ReactionUserManager; diff --git a/node_modules/discord.js/src/managers/RoleManager.js b/node_modules/discord.js/src/managers/RoleManager.js new file mode 100644 index 0000000..7c5bf51 --- /dev/null +++ b/node_modules/discord.js/src/managers/RoleManager.js @@ -0,0 +1,145 @@ +'use strict'; + +const BaseManager = require('./BaseManager'); +const Role = require('../structures/Role'); +const Permissions = require('../util/Permissions'); +const { resolveColor } = require('../util/Util'); + +/** + * Manages API methods for roles and stores their cache. + * @extends {BaseManager} + */ +class RoleManager extends BaseManager { + constructor(guild, iterable) { + super(guild.client, iterable, Role); + /** + * The guild belonging to this manager + * @type {Guild} + */ + this.guild = guild; + } + + /** + * The role cache of this manager + * @type {Collection<Snowflake, Role>} + * @name RoleManager#cache + */ + + add(data, cache) { + return super.add(data, cache, { extras: [this.guild] }); + } + + /** + * Obtains one or more roles from Discord, or the role cache if they're already available. + * @param {Snowflake} [id] ID or IDs of the role(s) + * @param {boolean} [cache=true] Whether to cache the new roles objects if it weren't already + * @returns {Promise<Role|RoleManager>} + * @example + * // Fetch all roles from the guild + * message.guild.roles.fetch() + * .then(roles => console.log(`There are ${roles.cache.size} roles.`)) + * .catch(console.error); + * @example + * // Fetch a single role + * message.guild.roles.fetch('222078108977594368') + * .then(role => console.log(`The role color is: ${role.color}`)) + * .catch(console.error); + */ + async fetch(id, cache = true) { + if (id) { + const existing = this.cache.get(id); + if (existing) return existing; + } + + // We cannot fetch a single role, as of this commit's date, Discord API throws with 405 + const roles = await this.client.api.guilds(this.guild.id).roles.get(); + for (const role of roles) this.add(role, cache); + return id ? this.cache.get(id) || null : this; + } + + /** + * Data that can be resolved to a Role object. This can be: + * * A Role + * * A Snowflake + * @typedef {Role|Snowflake} RoleResolvable + */ + + /** + * Resolves a RoleResolvable to a Role object. + * @method resolve + * @memberof RoleManager + * @instance + * @param {RoleResolvable} role The role resolvable to resolve + * @returns {?Role} + */ + + /** + * Resolves a RoleResolvable to a role ID string. + * @method resolveID + * @memberof RoleManager + * @instance + * @param {RoleResolvable} role The role resolvable to resolve + * @returns {?Snowflake} + */ + + /** + * Creates a new role in the guild with given information. + * <warn>The position will silently reset to 1 if an invalid one is provided, or none.</warn> + * @param {Object} [options] Options + * @param {RoleData} [options.data] The data to create the role with + * @param {string} [options.reason] Reason for creating this role + * @returns {Promise<Role>} + * @example + * // Create a new role + * guild.roles.create() + * .then(console.log) + * .catch(console.error); + * @example + * // Create a new role with data and a reason + * guild.roles.create({ + * data: { + * name: 'Super Cool People', + * color: 'BLUE', + * }, + * reason: 'we needed a role for Super Cool People', + * }) + * .then(console.log) + * .catch(console.error); + */ + create({ data = {}, reason } = {}) { + if (data.color) data.color = resolveColor(data.color); + if (data.permissions) data.permissions = Permissions.resolve(data.permissions); + + return this.guild.client.api + .guilds(this.guild.id) + .roles.post({ data, reason }) + .then(r => { + const { role } = this.client.actions.GuildRoleCreate.handle({ + guild_id: this.guild.id, + role: r, + }); + if (data.position) return role.setPosition(data.position, reason); + return role; + }); + } + + /** + * The `@everyone` role of the guild + * @type {Role} + * @readonly + */ + get everyone() { + return this.cache.get(this.guild.id); + } + + /** + * The role with the highest position in the cache + * @type {Role} + * @readonly + */ + get highest() { + return this.cache.reduce((prev, role) => (role.comparePositionTo(prev) > 0 ? role : prev), this.cache.first()); + } +} + +module.exports = RoleManager; diff --git a/node_modules/discord.js/src/managers/UserManager.js b/node_modules/discord.js/src/managers/UserManager.js new file mode 100644 index 0000000..ebd7fdf --- /dev/null +++ b/node_modules/discord.js/src/managers/UserManager.js @@ -0,0 +1,68 @@ +'use strict'; + +const BaseManager = require('./BaseManager'); +const GuildMember = require('../structures/GuildMember'); +const Message = require('../structures/Message'); +const User = require('../structures/User'); + +/** + * Manages API methods for users and stores their cache. + * @extends {BaseManager} + */ +class UserManager extends BaseManager { + constructor(client, iterable) { + super(client, iterable, User); + } + + /** + * The cache of this manager + * @type {Collection<Snowflake, User>} + * @name UserManager#cache + */ + + /** + * Data that resolves to give a User object. This can be: + * * A User object + * * A Snowflake + * * A Message object (resolves to the message author) + * * A GuildMember object + * @typedef {User|Snowflake|Message|GuildMember} UserResolvable + */ + + /** + * Resolves a UserResolvable to a User object. + * @param {UserResolvable} user The UserResolvable to identify + * @returns {?User} + */ + resolve(user) { + if (user instanceof GuildMember) return user.user; + if (user instanceof Message) return user.author; + return super.resolve(user); + } + + /** + * Resolves a UserResolvable to a user ID string. + * @param {UserResolvable} user The UserResolvable to identify + * @returns {?Snowflake} + */ + resolveID(user) { + if (user instanceof GuildMember) return user.user.id; + if (user instanceof Message) return user.author.id; + return super.resolveID(user); + } + + /** + * Obtains a user from Discord, or the user cache if it's already available. + * @param {Snowflake} id ID of the user + * @param {boolean} [cache=true] Whether to cache the new user object if it isn't already + * @returns {Promise<User>} + */ + async fetch(id, cache = true) { + const existing = this.cache.get(id); + if (existing && !existing.partial) return existing; + const data = await this.client.api.users(id).get(); + return this.add(data, cache); + } +} + +module.exports = UserManager; diff --git a/node_modules/discord.js/src/managers/VoiceStateManager.js b/node_modules/discord.js/src/managers/VoiceStateManager.js new file mode 100644 index 0000000..4a26b30 --- /dev/null +++ b/node_modules/discord.js/src/managers/VoiceStateManager.js @@ -0,0 +1,36 @@ +'use strict'; + +const BaseManager = require('./BaseManager'); +const VoiceState = require('../structures/VoiceState'); + +/** + * Manages API methods for VoiceStates and stores their cache. + * @extends {BaseManager} + */ +class VoiceStateManager extends BaseManager { + constructor(guild, iterable) { + super(guild.client, iterable, VoiceState); + /** + * The guild this manager belongs to + * @type {Guild} + */ + this.guild = guild; + } + + /** + * The cache of this manager + * @type {Collection<Snowflake, VoiceState>} + * @name VoiceStateManager#cache + */ + + add(data, cache = true) { + const existing = this.cache.get(data.user_id); + if (existing) return existing._patch(data); + + const entry = new VoiceState(this.guild, data); + if (cache) this.cache.set(data.user_id, entry); + return entry; + } +} + +module.exports = VoiceStateManager; diff --git a/node_modules/discord.js/src/rest/APIRequest.js b/node_modules/discord.js/src/rest/APIRequest.js new file mode 100644 index 0000000..36ad0d7 --- /dev/null +++ b/node_modules/discord.js/src/rest/APIRequest.js @@ -0,0 +1,65 @@ +'use strict'; + +const https = require('https'); +const FormData = require('@discordjs/form-data'); +const AbortController = require('abort-controller'); +const fetch = require('node-fetch'); +const { browser, UserAgent } = require('../util/Constants'); + +if (https.Agent) var agent = new https.Agent({ keepAlive: true }); + +class APIRequest { + constructor(rest, method, path, options) { + this.rest = rest; + this.client = rest.client; + this.method = method; + this.route = options.route; + this.options = options; + + let queryString = ''; + if (options.query) { + // Filter out undefined query options + const query = Object.entries(options.query).filter(([, value]) => value !== null && typeof value !== 'undefined'); + queryString = new URLSearchParams(query).toString(); + } + this.path = `${path}${queryString && `?${queryString}`}`; + } + + make() { + const API = + this.options.versioned === false + ? this.client.options.http.api + : `${this.client.options.http.api}/v${this.client.options.http.version}`; + const url = API + this.path; + let headers = {}; + + if (this.options.auth !== false) headers.Authorization = this.rest.getAuth(); + if (this.options.reason) headers['X-Audit-Log-Reason'] = encodeURIComponent(this.options.reason); + if (!browser) headers['User-Agent'] = UserAgent; + if (this.options.headers) headers = Object.assign(headers, this.options.headers); + + let body; + if (this.options.files && this.options.files.length) { + body = new FormData(); + for (const file of this.options.files) if (file && file.file) body.append(file.name, file.file, file.name); + if (typeof this.options.data !== 'undefined') body.append('payload_json', JSON.stringify(this.options.data)); + if (!browser) headers = Object.assign(headers, body.getHeaders()); + // eslint-disable-next-line eqeqeq + } else if (this.options.data != null) { + body = JSON.stringify(this.options.data); + headers['Content-Type'] = 'application/json'; + } + + const controller = new AbortController(); + const timeout = this.client.setTimeout(() => controller.abort(), this.client.options.restRequestTimeout); + return fetch(url, { + method: this.method, + headers, + agent, + body, + signal: controller.signal, + }).finally(() => this.client.clearTimeout(timeout)); + } +} + +module.exports = APIRequest; diff --git a/node_modules/discord.js/src/rest/APIRouter.js b/node_modules/discord.js/src/rest/APIRouter.js new file mode 100644 index 0000000..e85c739 --- /dev/null +++ b/node_modules/discord.js/src/rest/APIRouter.js @@ -0,0 +1,53 @@ +'use strict'; + +const noop = () => {}; // eslint-disable-line no-empty-function +const methods = ['get', 'post', 'delete', 'patch', 'put']; +const reflectors = [ + 'toString', + 'valueOf', + 'inspect', + 'constructor', + Symbol.toPrimitive, + Symbol.for('nodejs.util.inspect.custom'), +]; + +function buildRoute(manager) { + const route = ['']; + const handler = { + get(target, name) { + if (reflectors.includes(name)) return () => route.join('/'); + if (methods.includes(name)) { + const routeBucket = []; + for (let i = 0; i < route.length; i++) { + // Reactions routes and sub-routes all share the same bucket + if (route[i - 1] === 'reactions') break; + // Literal IDs should only be taken account if they are the Major ID (the Channel/Guild ID) + if (/\d{16,19}/g.test(route[i]) && !/channels|guilds/.test(route[i - 1])) routeBucket.push(':id'); + // All other parts of the route should be considered as part of the bucket identifier + else routeBucket.push(route[i]); + } + return options => + manager.request( + name, + route.join('/'), + Object.assign( + { + versioned: manager.versioned, + route: routeBucket.join('/'), + }, + options, + ), + ); + } + route.push(name); + return new Proxy(noop, handler); + }, + apply(target, _, args) { + route.push(...args.filter(x => x != null)); // eslint-disable-line eqeqeq + return new Proxy(noop, handler); + }, + }; + return new Proxy(noop, handler); +} + +module.exports = buildRoute; diff --git a/node_modules/discord.js/src/rest/DiscordAPIError.js b/node_modules/discord.js/src/rest/DiscordAPIError.js new file mode 100644 index 0000000..aebe513 --- /dev/null +++ b/node_modules/discord.js/src/rest/DiscordAPIError.js @@ -0,0 +1,68 @@ +'use strict'; + +/** + * Represents an error from the Discord API. + * @extends Error + */ +class DiscordAPIError extends Error { + constructor(path, error, method, status) { + super(); + const flattened = this.constructor.flattenErrors(error.errors || error).join('\n'); + this.name = 'DiscordAPIError'; + this.message = error.message && flattened ? `${error.message}\n${flattened}` : error.message || flattened; + + /** + * The HTTP method used for the request + * @type {string} + */ + this.method = method; + + /** + * The path of the request relative to the HTTP endpoint + * @type {string} + */ + this.path = path; + + /** + * HTTP error code returned by Discord + * @type {number} + */ + this.code = error.code; + + /** + * The HTTP status code + * @type {number} + */ + this.httpStatus = status; + } + + /** + * Flattens an errors object returned from the API into an array. + * @param {Object} obj Discord errors object + * @param {string} [key] Used internally to determine key names of nested fields + * @returns {string[]} + * @private + */ + static flattenErrors(obj, key = '') { + let messages = []; + + for (const [k, v] of Object.entries(obj)) { + if (k === 'message') continue; + const newKey = key ? (isNaN(k) ? `${key}.${k}` : `${key}[${k}]`) : k; + + if (v._errors) { + messages.push(`${newKey}: ${v._errors.map(e => e.message).join(' ')}`); + } else if (v.code || v.message) { + messages.push(`${v.code ? `${v.code}: ` : ''}${v.message}`.trim()); + } else if (typeof v === 'string') { + messages.push(v); + } else { + messages = messages.concat(this.flattenErrors(v, newKey)); + } + } + + return messages; + } +} + +module.exports = DiscordAPIError; diff --git a/node_modules/discord.js/src/rest/HTTPError.js b/node_modules/discord.js/src/rest/HTTPError.js new file mode 100644 index 0000000..2911467 --- /dev/null +++ b/node_modules/discord.js/src/rest/HTTPError.js @@ -0,0 +1,37 @@ +'use strict'; + +/** + * Represents a HTTP error from a request. + * @extends Error + */ +class HTTPError extends Error { + constructor(message, name, code, method, path) { + super(message); + + /** + * The name of the error + * @type {string} + */ + this.name = name; + + /** + * HTTP error code returned from the request + * @type {number} + */ + this.code = code || 500; + + /** + * The HTTP method used for the request + * @type {string} + */ + this.method = method; + + /** + * The path of the request relative to the HTTP endpoint + * @type {string} + */ + this.path = path; + } +} + +module.exports = HTTPError; diff --git a/node_modules/discord.js/src/rest/RESTManager.js b/node_modules/discord.js/src/rest/RESTManager.js new file mode 100644 index 0000000..b02ae81 --- /dev/null +++ b/node_modules/discord.js/src/rest/RESTManager.js @@ -0,0 +1,68 @@ +'use strict'; + +const APIRequest = require('./APIRequest'); +const routeBuilder = require('./APIRouter'); +const RequestHandler = require('./RequestHandler'); +const { Error } = require('../errors'); +const Collection = require('../util/Collection'); +const { Endpoints } = require('../util/Constants'); + +class RESTManager { + constructor(client, tokenPrefix = 'Bot') { + this.client = client; + this.handlers = new Collection(); + this.tokenPrefix = tokenPrefix; + this.versioned = true; + this.globalTimeout = null; + if (client.options.restSweepInterval > 0) { + client.setInterval(() => { + this.handlers.sweep(handler => handler._inactive); + }, client.options.restSweepInterval * 1000); + } + } + + get api() { + return routeBuilder(this); + } + + getAuth() { + const token = this.client.token || this.client.accessToken; + if (token) return `${this.tokenPrefix} ${token}`; + throw new Error('TOKEN_MISSING'); + } + + get cdn() { + return Endpoints.CDN(this.client.options.http.cdn); + } + + push(handler, apiRequest) { + return new Promise((resolve, reject) => { + handler + .push({ + request: apiRequest, + resolve, + reject, + retries: 0, + }) + .catch(reject); + }); + } + + request(method, url, options = {}) { + const apiRequest = new APIRequest(this, method, url, options); + let handler = this.handlers.get(apiRequest.route); + + if (!handler) { + handler = new RequestHandler(this); + this.handlers.set(apiRequest.route, handler); + } + + return this.push(handler, apiRequest); + } + + set endpoint(endpoint) { + this.client.options.http.api = endpoint; + } +} + +module.exports = RESTManager; diff --git a/node_modules/discord.js/src/rest/RequestHandler.js b/node_modules/discord.js/src/rest/RequestHandler.js new file mode 100644 index 0000000..27879f0 --- /dev/null +++ b/node_modules/discord.js/src/rest/RequestHandler.js @@ -0,0 +1,180 @@ +'use strict'; + +const DiscordAPIError = require('./DiscordAPIError'); +const HTTPError = require('./HTTPError'); +const { + Events: { RATE_LIMIT }, + browser, +} = require('../util/Constants'); +const Util = require('../util/Util'); + +function parseResponse(res) { + if (res.headers.get('content-type').startsWith('application/json')) return res.json(); + if (browser) return res.blob(); + return res.buffer(); +} + +function getAPIOffset(serverDate) { + return new Date(serverDate).getTime() - Date.now(); +} + +function calculateReset(reset, serverDate) { + return new Date(Number(reset) * 1000).getTime() - getAPIOffset(serverDate); +} + +class RequestHandler { + constructor(manager) { + this.manager = manager; + this.busy = false; + this.queue = []; + this.reset = -1; + this.remaining = -1; + this.limit = -1; + this.retryAfter = -1; + } + + push(request) { + if (this.busy) { + this.queue.push(request); + return this.run(); + } else { + return this.execute(request); + } + } + + run() { + if (this.queue.length === 0) return Promise.resolve(); + return this.execute(this.queue.shift()); + } + + get limited() { + return Boolean(this.manager.globalTimeout) || (this.remaining <= 0 && Date.now() < this.reset); + } + + get _inactive() { + return this.queue.length === 0 && !this.limited && this.busy !== true; + } + + async execute(item) { + // Insert item back to the beginning if currently busy + if (this.busy) { + this.queue.unshift(item); + return null; + } + + this.busy = true; + const { reject, request, resolve } = item; + + // After calculations and requests have been done, pre-emptively stop further requests + if (this.limited) { + const timeout = this.reset + this.manager.client.options.restTimeOffset - Date.now(); + + if (this.manager.client.listenerCount(RATE_LIMIT)) { + /** + * Emitted when the client hits a rate limit while making a request + * @event Client#rateLimit + * @param {Object} rateLimitInfo Object containing the rate limit info + * @param {number} rateLimitInfo.timeout Timeout in ms + * @param {number} rateLimitInfo.limit Number of requests that can be made to this endpoint + * @param {string} rateLimitInfo.method HTTP method used for request that triggered this event + * @param {string} rateLimitInfo.path Path used for request that triggered this event + * @param {string} rateLimitInfo.route Route used for request that triggered this event + */ + this.manager.client.emit(RATE_LIMIT, { + timeout, + limit: this.limit, + method: request.method, + path: request.path, + route: request.route, + }); + } + + if (this.manager.globalTimeout) { + await this.manager.globalTimeout; + } else { + // Wait for the timeout to expire in order to avoid an actual 429 + await Util.delayFor(timeout); + } + } + + // Perform the request + let res; + try { + res = await request.make(); + } catch (error) { + // NodeFetch error expected for all "operational" errors, such as 500 status code + this.busy = false; + return reject(new HTTPError(error.message, error.constructor.name, error.status, request.method, request.path)); + } + + if (res && res.headers) { + const serverDate = res.headers.get('date'); + const limit = res.headers.get('x-ratelimit-limit'); + const remaining = res.headers.get('x-ratelimit-remaining'); + const reset = res.headers.get('x-ratelimit-reset'); + const retryAfter = res.headers.get('retry-after'); + + this.limit = limit ? Number(limit) : Infinity; + this.remaining = remaining ? Number(remaining) : 1; + this.reset = reset ? calculateReset(reset, serverDate) : Date.now(); + this.retryAfter = retryAfter ? Number(retryAfter) : -1; + + // https://github.com/discordapp/discord-api-docs/issues/182 + if (item.request.route.includes('reactions')) { + this.reset = new Date(serverDate).getTime() - getAPIOffset(serverDate) + 250; + } + + // Handle global ratelimit + if (res.headers.get('x-ratelimit-global')) { + // Set the manager's global timeout as the promise for other requests to "wait" + this.manager.globalTimeout = Util.delayFor(this.retryAfter); + + // Wait for the global timeout to resolve before continuing + await this.manager.globalTimeout; + + // Clean up global timeout + this.manager.globalTimeout = null; + } + } + + // Finished handling headers, safe to unlock manager + this.busy = false; + + if (res.ok) { + const success = await parseResponse(res); + // Nothing wrong with the request, proceed with the next one + resolve(success); + return this.run(); + } else if (res.status === 429) { + // A ratelimit was hit - this should never happen + this.queue.unshift(item); + this.manager.client.emit('debug', `429 hit on route ${item.request.route}`); + await Util.delayFor(this.retryAfter); + return this.run(); + } else if (res.status >= 500 && res.status < 600) { + // Retry the specified number of times for possible serverside issues + if (item.retries === this.manager.client.options.retryLimit) { + return reject( + new HTTPError(res.statusText, res.constructor.name, res.status, item.request.method, request.path), + ); + } else { + item.retries++; + this.queue.unshift(item); + return this.run(); + } + } else { + // Handle possible malformed requests + try { + const data = await parseResponse(res); + if (res.status >= 400 && res.status < 500) { + return reject(new DiscordAPIError(request.path, data, request.method, res.status)); + } + return null; + } catch (err) { + return reject(new HTTPError(err.message, err.constructor.name, err.status, request.method, request.path)); + } + } + } +} + +module.exports = RequestHandler; diff --git a/node_modules/discord.js/src/sharding/Shard.js b/node_modules/discord.js/src/sharding/Shard.js new file mode 100644 index 0000000..3ef8b9b --- /dev/null +++ b/node_modules/discord.js/src/sharding/Shard.js @@ -0,0 +1,383 @@ +'use strict'; + +const EventEmitter = require('events'); +const path = require('path'); +const { Error } = require('../errors'); +const Util = require('../util/Util'); +let childProcess = null; +let Worker = null; + +/** + * A self-contained shard created by the {@link ShardingManager}. Each one has a {@link ChildProcess} that contains + * an instance of the bot and its {@link Client}. When its child process/worker exits for any reason, the shard will + * spawn a new one to replace it as necessary. + * @extends EventEmitter + */ +class Shard extends EventEmitter { + /** + * @param {ShardingManager} manager Manager that is creating this shard + * @param {number} id ID of this shard + */ + constructor(manager, id) { + super(); + + if (manager.mode === 'process') childProcess = require('child_process'); + else if (manager.mode === 'worker') Worker = require('worker_threads').Worker; + + /** + * Manager that created the shard + * @type {ShardingManager} + */ + this.manager = manager; + + /** + * ID of the shard in the manager + * @type {number} + */ + this.id = id; + + /** + * Arguments for the shard's process (only when {@link ShardingManager#mode} is `process`) + * @type {string[]} + */ + this.args = manager.shardArgs || []; + + /** + * Arguments for the shard's process executable (only when {@link ShardingManager#mode} is `process`) + * @type {?string[]} + */ + this.execArgv = manager.execArgv; + + /** + * Environment variables for the shard's process, or workerData for the shard's worker + * @type {Object} + */ + this.env = Object.assign({}, process.env, { + SHARDING_MANAGER: true, + SHARDS: this.id, + SHARD_COUNT: this.manager.totalShards, + DISCORD_TOKEN: this.manager.token, + }); + + /** + * Whether the shard's {@link Client} is ready + * @type {boolean} + */ + this.ready = false; + + /** + * Process of the shard (if {@link ShardingManager#mode} is `process`) + * @type {?ChildProcess} + */ + this.process = null; + + /** + * Worker of the shard (if {@link ShardingManager#mode} is `worker`) + * @type {?Worker} + */ + this.worker = null; + + /** + * Ongoing promises for calls to {@link Shard#eval}, mapped by the `script` they were called with + * @type {Map<string, Promise>} + * @private + */ + this._evals = new Map(); + + /** + * Ongoing promises for calls to {@link Shard#fetchClientValue}, mapped by the `prop` they were called with + * @type {Map<string, Promise>} + * @private + */ + this._fetches = new Map(); + + /** + * Listener function for the {@link ChildProcess}' `exit` event + * @type {Function} + * @private + */ + this._exitListener = this._handleExit.bind(this, undefined); + } + + /** + * Forks a child process or creates a worker thread for the shard. + * <warn>You should not need to call this manually.</warn> + * @param {number} [spawnTimeout=30000] The amount in milliseconds to wait until the {@link Client} has become ready + * before resolving. (-1 or Infinity for no wait) + * @returns {Promise<ChildProcess>} + */ + async spawn(spawnTimeout = 30000) { + if (this.process) throw new Error('SHARDING_PROCESS_EXISTS', this.id); + if (this.worker) throw new Error('SHARDING_WORKER_EXISTS', this.id); + + if (this.manager.mode === 'process') { + this.process = childProcess + .fork(path.resolve(this.manager.file), this.args, { + env: this.env, + execArgv: this.execArgv, + }) + .on('message', this._handleMessage.bind(this)) + .on('exit', this._exitListener); + } else if (this.manager.mode === 'worker') { + this.worker = new Worker(path.resolve(this.manager.file), { workerData: this.env }) + .on('message', this._handleMessage.bind(this)) + .on('exit', this._exitListener); + } + + /** + * Emitted upon the creation of the shard's child process/worker. + * @event Shard#spawn + * @param {ChildProcess|Worker} process Child process/worker that was created + */ + this.emit('spawn', this.process || this.worker); + + if (spawnTimeout === -1 || spawnTimeout === Infinity) return this.process || this.worker; + await new Promise((resolve, reject) => { + const cleanup = () => { + clearTimeout(spawnTimeoutTimer); + this.off('ready', onReady); + this.off('disconnect', onDisconnect); + this.off('death', onDeath); + }; + + const onReady = () => { + cleanup(); + resolve(); + }; + + const onDisconnect = () => { + cleanup(); + reject(new Error('SHARDING_READY_DISCONNECTED', this.id)); + }; + + const onDeath = () => { + cleanup(); + reject(new Error('SHARDING_READY_DIED', this.id)); + }; + + const onTimeout = () => { + cleanup(); + reject(new Error('SHARDING_READY_TIMEOUT', this.id)); + }; + + const spawnTimeoutTimer = setTimeout(onTimeout, spawnTimeout); + this.once('ready', onReady); + this.once('disconnect', onDisconnect); + this.once('death', onDeath); + }); + return this.process || this.worker; + } + + /** + * Immediately kills the shard's process/worker and does not restart it. + */ + kill() { + if (this.process) { + this.process.removeListener('exit', this._exitListener); + this.process.kill(); + } else { + this.worker.removeListener('exit', this._exitListener); + this.worker.terminate(); + } + + this._handleExit(false); + } + + /** + * Kills and restarts the shard's process/worker. + * @param {number} [delay=500] How long to wait between killing the process/worker and restarting it (in milliseconds) + * @param {number} [spawnTimeout=30000] The amount in milliseconds to wait until the {@link Client} has become ready + * before resolving. (-1 or Infinity for no wait) + * @returns {Promise<ChildProcess>} + */ + async respawn(delay = 500, spawnTimeout) { + this.kill(); + if (delay > 0) await Util.delayFor(delay); + return this.spawn(spawnTimeout); + } + + /** + * Sends a message to the shard's process/worker. + * @param {*} message Message to send to the shard + * @returns {Promise<Shard>} + */ + send(message) { + return new Promise((resolve, reject) => { + if (this.process) { + this.process.send(message, err => { + if (err) reject(err); + else resolve(this); + }); + } else { + this.worker.postMessage(message); + resolve(this); + } + }); + } + + /** + * Fetches a client property value of the shard. + * @param {string} prop Name of the client property to get, using periods for nesting + * @returns {Promise<*>} + * @example + * shard.fetchClientValue('guilds.cache.size') + * .then(count => console.log(`${count} guilds in shard ${shard.id}`)) + * .catch(console.error); + */ + fetchClientValue(prop) { + if (this._fetches.has(prop)) return this._fetches.get(prop); + + const promise = new Promise((resolve, reject) => { + const child = this.process || this.worker; + + const listener = message => { + if (!message || message._fetchProp !== prop) return; + child.removeListener('message', listener); + this._fetches.delete(prop); + resolve(message._result); + }; + child.on('message', listener); + + this.send({ _fetchProp: prop }).catch(err => { + child.removeListener('message', listener); + this._fetches.delete(prop); + reject(err); + }); + }); + + this._fetches.set(prop, promise); + return promise; + } + + /** + * Evaluates a script or function on the shard, in the context of the {@link Client}. + * @param {string|Function} script JavaScript to run on the shard + * @returns {Promise<*>} Result of the script execution + */ + eval(script) { + if (this._evals.has(script)) return this._evals.get(script); + + const promise = new Promise((resolve, reject) => { + const child = this.process || this.worker; + + const listener = message => { + if (!message || message._eval !== script) return; + child.removeListener('message', listener); + this._evals.delete(script); + if (!message._error) resolve(message._result); + else reject(Util.makeError(message._error)); + }; + child.on('message', listener); + + const _eval = typeof script === 'function' ? `(${script})(this)` : script; + this.send({ _eval }).catch(err => { + child.removeListener('message', listener); + this._evals.delete(script); + reject(err); + }); + }); + + this._evals.set(script, promise); + return promise; + } + + /** + * Handles a message received from the child process/worker. + * @param {*} message Message received + * @private + */ + _handleMessage(message) { + if (message) { + // Shard is ready + if (message._ready) { + this.ready = true; + /** + * Emitted upon the shard's {@link Client#ready} event. + * @event Shard#ready + */ + this.emit('ready'); + return; + } + + // Shard has disconnected + if (message._disconnect) { + this.ready = false; + /** + * Emitted upon the shard's {@link Client#disconnect} event. + * @event Shard#disconnect + */ + this.emit('disconnect'); + return; + } + + // Shard is attempting to reconnect + if (message._reconnecting) { + this.ready = false; + /** + * Emitted upon the shard's {@link Client#reconnecting} event. + * @event Shard#reconnecting + */ + this.emit('reconnecting'); + return; + } + + // Shard is requesting a property fetch + if (message._sFetchProp) { + this.manager.fetchClientValues(message._sFetchProp).then( + results => this.send({ _sFetchProp: message._sFetchProp, _result: results }), + err => this.send({ _sFetchProp: message._sFetchProp, _error: Util.makePlainError(err) }), + ); + return; + } + + // Shard is requesting an eval broadcast + if (message._sEval) { + this.manager.broadcastEval(message._sEval).then( + results => this.send({ _sEval: message._sEval, _result: results }), + err => this.send({ _sEval: message._sEval, _error: Util.makePlainError(err) }), + ); + return; + } + + // Shard is requesting a respawn of all shards + if (message._sRespawnAll) { + const { shardDelay, respawnDelay, spawnTimeout } = message._sRespawnAll; + this.manager.respawnAll(shardDelay, respawnDelay, spawnTimeout).catch(() => { + // Do nothing + }); + return; + } + } + + /** + * Emitted upon receiving a message from the child process/worker. + * @event Shard#message + * @param {*} message Message that was received + */ + this.emit('message', message); + } + + /** + * Handles the shard's process/worker exiting. + * @param {boolean} [respawn=this.manager.respawn] Whether to spawn the shard again + * @private + */ + _handleExit(respawn = this.manager.respawn) { + /** + * Emitted upon the shard's child process/worker exiting. + * @event Shard#death + * @param {ChildProcess|Worker} process Child process/worker that exited + */ + this.emit('death', this.process || this.worker); + + this.ready = false; + this.process = null; + this.worker = null; + this._evals.clear(); + this._fetches.clear(); + + if (respawn) this.spawn().catch(err => this.emit('error', err)); + } +} + +module.exports = Shard; diff --git a/node_modules/discord.js/src/sharding/ShardClientUtil.js b/node_modules/discord.js/src/sharding/ShardClientUtil.js new file mode 100644 index 0000000..79345d8 --- /dev/null +++ b/node_modules/discord.js/src/sharding/ShardClientUtil.js @@ -0,0 +1,229 @@ +'use strict'; + +const { Events } = require('../util/Constants'); +const Util = require('../util/Util'); + +/** + * Helper class for sharded clients spawned as a child process/worker, such as from a {@link ShardingManager}. + * Utilises IPC to send and receive data to/from the master process and other shards. + */ +class ShardClientUtil { + /** + * @param {Client} client Client of the current shard + * @param {ShardingManagerMode} mode Mode the shard was spawned with + */ + constructor(client, mode) { + /** + * Client for the shard + * @type {Client} + */ + this.client = client; + + /** + * Mode the shard was spawned with + * @type {ShardingManagerMode} + */ + this.mode = mode; + + /** + * Message port for the master process (only when {@link ShardClientUtil#mode} is `worker`) + * @type {?MessagePort} + */ + this.parentPort = null; + + if (mode === 'process') { + process.on('message', this._handleMessage.bind(this)); + client.on('ready', () => { + process.send({ _ready: true }); + }); + client.on('disconnect', () => { + process.send({ _disconnect: true }); + }); + client.on('reconnecting', () => { + process.send({ _reconnecting: true }); + }); + } else if (mode === 'worker') { + this.parentPort = require('worker_threads').parentPort; + this.parentPort.on('message', this._handleMessage.bind(this)); + client.on('ready', () => { + this.parentPort.postMessage({ _ready: true }); + }); + client.on('disconnect', () => { + this.parentPort.postMessage({ _disconnect: true }); + }); + client.on('reconnecting', () => { + this.parentPort.postMessage({ _reconnecting: true }); + }); + } + } + + /** + * Array of shard IDs of this client + * @type {number[]} + * @readonly + */ + get ids() { + return this.client.options.shards; + } + + /** + * Total number of shards + * @type {number} + * @readonly + */ + get count() { + return this.client.options.shardCount; + } + + /** + * Sends a message to the master process. + * @param {*} message Message to send + * @returns {Promise<void>} + * @emits Shard#message + */ + send(message) { + return new Promise((resolve, reject) => { + if (this.mode === 'process') { + process.send(message, err => { + if (err) reject(err); + else resolve(); + }); + } else if (this.mode === 'worker') { + this.parentPort.postMessage(message); + resolve(); + } + }); + } + + /** + * Fetches a client property value of each shard. + * @param {string} prop Name of the client property to get, using periods for nesting + * @returns {Promise<Array<*>>} + * @example + * client.shard.fetchClientValues('guilds.cache.size') + * .then(results => console.log(`${results.reduce((prev, val) => prev + val, 0)} total guilds`)) + * .catch(console.error); + * @see {@link ShardingManager#fetchClientValues} + */ + fetchClientValues(prop) { + return new Promise((resolve, reject) => { + const parent = this.parentPort || process; + + const listener = message => { + if (!message || message._sFetchProp !== prop) return; + parent.removeListener('message', listener); + if (!message._error) resolve(message._result); + else reject(Util.makeError(message._error)); + }; + parent.on('message', listener); + + this.send({ _sFetchProp: prop }).catch(err => { + parent.removeListener('message', listener); + reject(err); + }); + }); + } + + /** + * Evaluates a script or function on all shards, in the context of the {@link Clients}. + * @param {string|Function} script JavaScript to run on each shard + * @returns {Promise<Array<*>>} Results of the script execution + * @example + * client.shard.broadcastEval('this.guilds.cache.size') + * .then(results => console.log(`${results.reduce((prev, val) => prev + val, 0)} total guilds`)) + * .catch(console.error); + * @see {@link ShardingManager#broadcastEval} + */ + broadcastEval(script) { + return new Promise((resolve, reject) => { + const parent = this.parentPort || process; + script = typeof script === 'function' ? `(${script})(this)` : script; + + const listener = message => { + if (!message || message._sEval !== script) return; + parent.removeListener('message', listener); + if (!message._error) resolve(message._result); + else reject(Util.makeError(message._error)); + }; + parent.on('message', listener); + + this.send({ _sEval: script }).catch(err => { + parent.removeListener('message', listener); + reject(err); + }); + }); + } + + /** + * Requests a respawn of all shards. + * @param {number} [shardDelay=5000] How long to wait between shards (in milliseconds) + * @param {number} [respawnDelay=500] How long to wait between killing a shard's process/worker and restarting it + * (in milliseconds) + * @param {number} [spawnTimeout=30000] The amount in milliseconds to wait for a shard to become ready before + * continuing to another. (-1 or Infinity for no wait) + * @returns {Promise<void>} Resolves upon the message being sent + * @see {@link ShardingManager#respawnAll} + */ + respawnAll(shardDelay = 5000, respawnDelay = 500, spawnTimeout = 30000) { + return this.send({ _sRespawnAll: { shardDelay, respawnDelay, spawnTimeout } }); + } + + /** + * Handles an IPC message. + * @param {*} message Message received + * @private + */ + async _handleMessage(message) { + if (!message) return; + if (message._fetchProp) { + const props = message._fetchProp.split('.'); + let value = this.client; + for (const prop of props) value = value[prop]; + this._respond('fetchProp', { _fetchProp: message._fetchProp, _result: value }); + } else if (message._eval) { + try { + this._respond('eval', { _eval: message._eval, _result: await this.client._eval(message._eval) }); + } catch (err) { + this._respond('eval', { _eval: message._eval, _error: Util.makePlainError(err) }); + } + } + } + + /** + * Sends a message to the master process, emitting an error from the client upon failure. + * @param {string} type Type of response to send + * @param {*} message Message to send + * @private + */ + _respond(type, message) { + this.send(message).catch(err => { + err.message = `Error when sending ${type} response to master process: ${err.message}`; + /** + * Emitted when the client encounters an error. + * @event Client#error + * @param {Error} error The error encountered + */ + this.client.emit(Events.ERROR, err); + }); + } + + /** + * Creates/gets the singleton of this class. + * @param {Client} client The client to use + * @param {ShardingManagerMode} mode Mode the shard was spawned with + * @returns {ShardClientUtil} + */ + static singleton(client, mode) { + if (!this._singleton) { + this._singleton = new this(client, mode); + } else { + client.emit( + Events.WARN, + 'Multiple clients created in child process/worker; only the first will handle sharding helpers.', + ); + } + return this._singleton; + } +} + +module.exports = ShardClientUtil; diff --git a/node_modules/discord.js/src/sharding/ShardingManager.js b/node_modules/discord.js/src/sharding/ShardingManager.js new file mode 100644 index 0000000..0b9fdd9 --- /dev/null +++ b/node_modules/discord.js/src/sharding/ShardingManager.js @@ -0,0 +1,274 @@ +'use strict'; + +const EventEmitter = require('events'); +const fs = require('fs'); +const path = require('path'); +const Shard = require('./Shard'); +const { Error, TypeError, RangeError } = require('../errors'); +const Collection = require('../util/Collection'); +const Util = require('../util/Util'); + +/** + * This is a utility class that makes multi-process sharding of a bot an easy and painless experience. + * It works by spawning a self-contained {@link ChildProcess} or {@link Worker} for each individual shard, each + * containing its own instance of your bot's {@link Client}. They all have a line of communication with the master + * process, and there are several useful methods that utilise it in order to simplify tasks that are normally difficult + * with sharding. It can spawn a specific number of shards or the amount that Discord suggests for the bot, and takes a + * path to your main bot script to launch for each one. + * @extends {EventEmitter} + */ +class ShardingManager extends EventEmitter { + /** + * The mode to spawn shards with for a {@link ShardingManager}: either "process" to use child processes, or + * "worker" to use workers. The "worker" mode relies on the experimental + * [Worker threads](https://nodejs.org/api/worker_threads.html) functionality that is present in Node v10.5.0 or + * newer. Node must be started with the `--experimental-worker` flag to expose it. + * @typedef {Object} ShardingManagerMode + */ + + /** + * @param {string} file Path to your shard script file + * @param {Object} [options] Options for the sharding manager + * @param {string|number} [options.totalShards='auto'] Number of total shards of all shard managers or "auto" + * @param {string|number[]} [options.shardList='auto'] List of shards to spawn or "auto" + * @param {ShardingManagerMode} [options.mode='process'] Which mode to use for shards + * @param {boolean} [options.respawn=true] Whether shards should automatically respawn upon exiting + * @param {string[]} [options.shardArgs=[]] Arguments to pass to the shard script when spawning + * (only available when using the `process` mode) + * @param {string[]} [options.execArgv=[]] Arguments to pass to the shard script executable when spawning + * (only available when using the `process` mode) + * @param {string} [options.token] Token to use for automatic shard count and passing to shards + */ + constructor(file, options = {}) { + super(); + options = Util.mergeDefault( + { + totalShards: 'auto', + mode: 'process', + respawn: true, + shardArgs: [], + execArgv: [], + token: process.env.DISCORD_TOKEN, + }, + options, + ); + + /** + * Path to the shard script file + * @type {string} + */ + this.file = file; + if (!file) throw new Error('CLIENT_INVALID_OPTION', 'File', 'specified.'); + if (!path.isAbsolute(file)) this.file = path.resolve(process.cwd(), file); + const stats = fs.statSync(this.file); + if (!stats.isFile()) throw new Error('CLIENT_INVALID_OPTION', 'File', 'a file'); + + /** + * List of shards this sharding manager spawns + * @type {string|number[]} + */ + this.shardList = options.shardList || 'auto'; + if (this.shardList !== 'auto') { + if (!Array.isArray(this.shardList)) { + throw new TypeError('CLIENT_INVALID_OPTION', 'shardList', 'an array.'); + } + this.shardList = [...new Set(this.shardList)]; + if (this.shardList.length < 1) throw new RangeError('CLIENT_INVALID_OPTION', 'shardList', 'at least 1 ID.'); + if ( + this.shardList.some( + shardID => typeof shardID !== 'number' || isNaN(shardID) || !Number.isInteger(shardID) || shardID < 0, + ) + ) { + throw new TypeError('CLIENT_INVALID_OPTION', 'shardList', 'an array of positive integers.'); + } + } + + /** + * Amount of shards that all sharding managers spawn in total + * @type {number} + */ + this.totalShards = options.totalShards || 'auto'; + if (this.totalShards !== 'auto') { + if (typeof this.totalShards !== 'number' || isNaN(this.totalShards)) { + throw new TypeError('CLIENT_INVALID_OPTION', 'Amount of shards', 'a number.'); + } + if (this.totalShards < 1) throw new RangeError('CLIENT_INVALID_OPTION', 'Amount of shards', 'at least 1.'); + if (!Number.isInteger(this.totalShards)) { + throw new RangeError('CLIENT_INVALID_OPTION', 'Amount of shards', 'an integer.'); + } + } + + /** + * Mode for shards to spawn with + * @type {ShardingManagerMode} + */ + this.mode = options.mode; + if (this.mode !== 'process' && this.mode !== 'worker') { + throw new RangeError('CLIENT_INVALID_OPTION', 'Sharding mode', '"process" or "worker"'); + } + + /** + * Whether shards should automatically respawn upon exiting + * @type {boolean} + */ + this.respawn = options.respawn; + + /** + * An array of arguments to pass to shards (only when {@link ShardingManager#mode} is `process`) + * @type {string[]} + */ + this.shardArgs = options.shardArgs; + + /** + * An array of arguments to pass to the executable (only when {@link ShardingManager#mode} is `process`) + * @type {string[]} + */ + this.execArgv = options.execArgv; + + /** + * Token to use for obtaining the automatic shard count, and passing to shards + * @type {?string} + */ + this.token = options.token ? options.token.replace(/^Bot\s*/i, '') : null; + + /** + * A collection of shards that this manager has spawned + * @type {Collection<number, Shard>} + */ + this.shards = new Collection(); + + process.env.SHARDING_MANAGER = true; + process.env.SHARDING_MANAGER_MODE = this.mode; + process.env.DISCORD_TOKEN = this.token; + } + + /** + * Creates a single shard. + * <warn>Using this method is usually not necessary if you use the spawn method.</warn> + * @param {number} [id=this.shards.size] ID of the shard to create + * <info>This is usually not necessary to manually specify.</info> + * @returns {Shard} Note that the created shard needs to be explicitly spawned using its spawn method. + */ + createShard(id = this.shards.size) { + const shard = new Shard(this, id); + this.shards.set(id, shard); + /** + * Emitted upon creating a shard. + * @event ShardingManager#shardCreate + * @param {Shard} shard Shard that was created + */ + this.emit('shardCreate', shard); + return shard; + } + + /** + * Spawns multiple shards. + * @param {number|string} [amount=this.totalShards] Number of shards to spawn + * @param {number} [delay=5500] How long to wait in between spawning each shard (in milliseconds) + * @param {number} [spawnTimeout=30000] The amount in milliseconds to wait until the {@link Client} has become ready + * before resolving. (-1 or Infinity for no wait) + * @returns {Promise<Collection<number, Shard>>} + */ + async spawn(amount = this.totalShards, delay = 5500, spawnTimeout) { + // Obtain/verify the number of shards to spawn + if (amount === 'auto') { + amount = await Util.fetchRecommendedShards(this.token); + } else { + if (typeof amount !== 'number' || isNaN(amount)) { + throw new TypeError('CLIENT_INVALID_OPTION', 'Amount of shards', 'a number.'); + } + if (amount < 1) throw new RangeError('CLIENT_INVALID_OPTION', 'Amount of shards', 'at least 1.'); + if (!Number.isInteger(amount)) { + throw new TypeError('CLIENT_INVALID_OPTION', 'Amount of shards', 'an integer.'); + } + } + + // Make sure this many shards haven't already been spawned + if (this.shards.size >= amount) throw new Error('SHARDING_ALREADY_SPAWNED', this.shards.size); + if (this.shardList === 'auto' || this.totalShards === 'auto' || this.totalShards !== amount) { + this.shardList = [...Array(amount).keys()]; + } + if (this.totalShards === 'auto' || this.totalShards !== amount) { + this.totalShards = amount; + } + + if (this.shardList.some(shardID => shardID >= amount)) { + throw new RangeError( + 'CLIENT_INVALID_OPTION', + 'Amount of shards', + 'bigger than the highest shardID in the shardList option.', + ); + } + + // Spawn the shards + for (const shardID of this.shardList) { + const promises = []; + const shard = this.createShard(shardID); + promises.push(shard.spawn(spawnTimeout)); + if (delay > 0 && this.shards.size !== this.shardList.length) promises.push(Util.delayFor(delay)); + await Promise.all(promises); // eslint-disable-line no-await-in-loop + } + + return this.shards; + } + + /** + * Sends a message to all shards. + * @param {*} message Message to be sent to the shards + * @returns {Promise<Shard[]>} + */ + broadcast(message) { + const promises = []; + for (const shard of this.shards.values()) promises.push(shard.send(message)); + return Promise.all(promises); + } + + /** + * Evaluates a script on all shards, in the context of the {@link Client}s. + * @param {string} script JavaScript to run on each shard + * @returns {Promise<Array<*>>} Results of the script execution + */ + broadcastEval(script) { + const promises = []; + for (const shard of this.shards.values()) promises.push(shard.eval(script)); + return Promise.all(promises); + } + + /** + * Fetches a client property value of each shard. + * @param {string} prop Name of the client property to get, using periods for nesting + * @returns {Promise<Array<*>>} + * @example + * manager.fetchClientValues('guilds.cache.size') + * .then(results => console.log(`${results.reduce((prev, val) => prev + val, 0)} total guilds`)) + * .catch(console.error); + */ + fetchClientValues(prop) { + if (this.shards.size === 0) return Promise.reject(new Error('SHARDING_NO_SHARDS')); + if (this.shards.size !== this.shardList.length) return Promise.reject(new Error('SHARDING_IN_PROCESS')); + const promises = []; + for (const shard of this.shards.values()) promises.push(shard.fetchClientValue(prop)); + return Promise.all(promises); + } + + /** + * Kills all running shards and respawns them. + * @param {number} [shardDelay=5000] How long to wait between shards (in milliseconds) + * @param {number} [respawnDelay=500] How long to wait between killing a shard's process and restarting it + * (in milliseconds) + * @param {number} [spawnTimeout=30000] The amount in milliseconds to wait for a shard to become ready before + * continuing to another. (-1 or Infinity for no wait) + * @returns {Promise<Collection<string, Shard>>} + */ + async respawnAll(shardDelay = 5000, respawnDelay = 500, spawnTimeout) { + let s = 0; + for (const shard of this.shards.values()) { + const promises = [shard.respawn(respawnDelay, spawnTimeout)]; + if (++s < this.shards.size && shardDelay > 0) promises.push(Util.delayFor(shardDelay)); + await Promise.all(promises); // eslint-disable-line no-await-in-loop + } + return this.shards; + } +} + +module.exports = ShardingManager; diff --git a/node_modules/discord.js/src/structures/APIMessage.js b/node_modules/discord.js/src/structures/APIMessage.js new file mode 100644 index 0000000..86dab74 --- /dev/null +++ b/node_modules/discord.js/src/structures/APIMessage.js @@ -0,0 +1,379 @@ +'use strict'; + +const MessageAttachment = require('./MessageAttachment'); +const MessageEmbed = require('./MessageEmbed'); +const { RangeError } = require('../errors'); +const { browser } = require('../util/Constants'); +const DataResolver = require('../util/DataResolver'); +const MessageFlags = require('../util/MessageFlags'); +const Util = require('../util/Util'); + +/** + * Represents a message to be sent to the API. + */ +class APIMessage { + /** + * @param {MessageTarget} target - The target for this message to be sent to + * @param {MessageOptions|WebhookMessageOptions} options - Options passed in from send + */ + constructor(target, options) { + /** + * The target for this message to be sent to + * @type {MessageTarget} + */ + this.target = target; + + /** + * Options passed in from send + * @type {MessageOptions|WebhookMessageOptions} + */ + this.options = options; + + /** + * Data sendable to the API + * @type {?Object} + */ + this.data = null; + + /** + * Files sendable to the API + * @type {?Object[]} + */ + this.files = null; + } + + /** + * Whether or not the target is a webhook + * @type {boolean} + * @readonly + */ + get isWebhook() { + const Webhook = require('./Webhook'); + const WebhookClient = require('../client/WebhookClient'); + return this.target instanceof Webhook || this.target instanceof WebhookClient; + } + + /** + * Whether or not the target is a user + * @type {boolean} + * @readonly + */ + get isUser() { + const User = require('./User'); + const GuildMember = require('./GuildMember'); + return this.target instanceof User || this.target instanceof GuildMember; + } + + /** + * Whether or not the target is a message + * @type {boolean} + * @readonly + */ + get isMessage() { + const Message = require('./Message'); + return this.target instanceof Message; + } + + /** + * Makes the content of this message. + * @returns {?(string|string[])} + */ + makeContent() { + const GuildMember = require('./GuildMember'); + + let content; + if (this.options.content === null) { + content = ''; + } else if (typeof this.options.content !== 'undefined') { + content = Util.resolveString(this.options.content); + } + + const disableMentions = + typeof this.options.disableMentions === 'undefined' + ? this.target.client.options.disableMentions + : this.options.disableMentions; + if (disableMentions === 'all') { + content = Util.removeMentions(content || ''); + } else if (disableMentions === 'everyone') { + content = (content || '').replace(/@([^<>@ ]*)/gmsu, (match, target) => { + if (target.match(/^[&!]?\d+$/)) { + return `@${target}`; + } else { + return `@\u200b${target}`; + } + }); + } + + const isSplit = typeof this.options.split !== 'undefined' && this.options.split !== false; + const isCode = typeof this.options.code !== 'undefined' && this.options.code !== false; + const splitOptions = isSplit ? { ...this.options.split } : undefined; + + let mentionPart = ''; + if (this.options.reply && !this.isUser && this.target.type !== 'dm') { + const id = this.target.client.users.resolveID(this.options.reply); + mentionPart = `<@${this.options.reply instanceof GuildMember && this.options.reply.nickname ? '!' : ''}${id}>, `; + if (isSplit) { + splitOptions.prepend = `${mentionPart}${splitOptions.prepend || ''}`; + } + } + + if (content || mentionPart) { + if (isCode) { + const codeName = typeof this.options.code === 'string' ? this.options.code : ''; + content = `${mentionPart}\`\`\`${codeName}\n${Util.cleanCodeBlockContent(content || '')}\n\`\`\``; + if (isSplit) { + splitOptions.prepend = `${splitOptions.prepend || ''}\`\`\`${codeName}\n`; + splitOptions.append = `\n\`\`\`${splitOptions.append || ''}`; + } + } else if (mentionPart) { + content = `${mentionPart}${content || ''}`; + } + + if (isSplit) { + content = Util.splitMessage(content || '', splitOptions); + } + } + + return content; + } + + /** + * Resolves data. + * @returns {APIMessage} + */ + resolveData() { + if (this.data) return this; + + const content = this.makeContent(); + const tts = Boolean(this.options.tts); + + let nonce; + if (typeof this.options.nonce !== 'undefined') { + nonce = parseInt(this.options.nonce); + if (isNaN(nonce) || nonce < 0) throw new RangeError('MESSAGE_NONCE_TYPE'); + } + + const embedLikes = []; + if (this.isWebhook) { + if (this.options.embeds) { + embedLikes.push(...this.options.embeds); + } + } else if (this.options.embed) { + embedLikes.push(this.options.embed); + } + const embeds = embedLikes.map(e => new MessageEmbed(e).toJSON()); + + let username; + let avatarURL; + if (this.isWebhook) { + username = this.options.username || this.target.name; + if (this.options.avatarURL) avatarURL = this.options.avatarURL; + } + + let flags; + if (this.isMessage) { + // eslint-disable-next-line eqeqeq + flags = this.options.flags != null ? new MessageFlags(this.options.flags).bitfield : this.target.flags.bitfield; + } + + const allowedMentions = + typeof this.options.allowedMentions === 'undefined' + ? this.target.client.options.allowedMentions + : this.options.allowedMentions; + + this.data = { + content, + tts, + nonce, + embed: this.options.embed === null ? null : embeds[0], + embeds, + username, + avatar_url: avatarURL, + allowed_mentions: allowedMentions, + flags, + }; + return this; + } + + /** + * Resolves files. + * @returns {Promise<APIMessage>} + */ + async resolveFiles() { + if (this.files) return this; + + const embedLikes = []; + if (this.isWebhook) { + if (this.options.embeds) { + embedLikes.push(...this.options.embeds); + } + } else if (this.options.embed) { + embedLikes.push(this.options.embed); + } + + const fileLikes = []; + if (this.options.files) { + fileLikes.push(...this.options.files); + } + for (const embed of embedLikes) { + if (embed.files) { + fileLikes.push(...embed.files); + } + } + + this.files = await Promise.all(fileLikes.map(f => this.constructor.resolveFile(f))); + return this; + } + + /** + * Converts this APIMessage into an array of APIMessages for each split content + * @returns {APIMessage[]} + */ + split() { + if (!this.data) this.resolveData(); + + if (!Array.isArray(this.data.content)) return [this]; + + const apiMessages = []; + + for (let i = 0; i < this.data.content.length; i++) { + let data; + let opt; + + if (i === this.data.content.length - 1) { + data = { ...this.data, content: this.data.content[i] }; + opt = { ...this.options, content: this.data.content[i] }; + } else { + data = { content: this.data.content[i], tts: this.data.tts }; + opt = { content: this.data.content[i], tts: this.data.tts }; + } + + const apiMessage = new APIMessage(this.target, opt); + apiMessage.data = data; + apiMessages.push(apiMessage); + } + + return apiMessages; + } + + /** + * Resolves a single file into an object sendable to the API. + * @param {BufferResolvable|Stream|FileOptions|MessageAttachment} fileLike Something that could be resolved to a file + * @returns {Object} + */ + static async resolveFile(fileLike) { + let attachment; + let name; + + const findName = thing => { + if (typeof thing === 'string') { + return Util.basename(thing); + } + + if (thing.path) { + return Util.basename(thing.path); + } + + return 'file.jpg'; + }; + + const ownAttachment = + typeof fileLike === 'string' || + fileLike instanceof (browser ? ArrayBuffer : Buffer) || + typeof fileLike.pipe === 'function'; + if (ownAttachment) { + attachment = fileLike; + name = findName(attachment); + } else { + attachment = fileLike.attachment; + name = fileLike.name || findName(attachment); + } + + const resource = await DataResolver.resolveFile(attachment); + return { attachment, name, file: resource }; + } + + /** + * Partitions embeds and attachments. + * @param {Array<MessageEmbed|MessageAttachment>} items Items to partition + * @returns {Array<MessageEmbed[], MessageAttachment[]>} + */ + static partitionMessageAdditions(items) { + const embeds = []; + const files = []; + for (const item of items) { + if (item instanceof MessageEmbed) { + embeds.push(item); + } else if (item instanceof MessageAttachment) { + files.push(item); + } + } + + return [embeds, files]; + } + + /** + * Transforms the user-level arguments into a final options object. Passing a transformed options object alone into + * this method will keep it the same, allowing for the reuse of the final options object. + * @param {StringResolvable} [content] Content to send + * @param {MessageOptions|WebhookMessageOptions|MessageAdditions} [options={}] Options to use + * @param {MessageOptions|WebhookMessageOptions} [extra={}] Extra options to add onto transformed options + * @param {boolean} [isWebhook=false] Whether or not to use WebhookMessageOptions as the result + * @returns {MessageOptions|WebhookMessageOptions} + */ + static transformOptions(content, options, extra = {}, isWebhook = false) { + if (!options && typeof content === 'object' && !Array.isArray(content)) { + options = content; + content = undefined; + } + + if (!options) { + options = {}; + } else if (options instanceof MessageEmbed) { + return isWebhook ? { content, embeds: [options], ...extra } : { content, embed: options, ...extra }; + } else if (options instanceof MessageAttachment) { + return { content, files: [options], ...extra }; + } + + if (Array.isArray(options)) { + const [embeds, files] = this.partitionMessageAdditions(options); + return isWebhook ? { content, embeds, files, ...extra } : { content, embed: embeds[0], files, ...extra }; + } else if (Array.isArray(content)) { + const [embeds, files] = this.partitionMessageAdditions(content); + if (embeds.length || files.length) { + return isWebhook ? { embeds, files, ...extra } : { embed: embeds[0], files, ...extra }; + } + } + + return { content, ...options, ...extra }; + } + + /** + * Creates an `APIMessage` from user-level arguments. + * @param {MessageTarget} target Target to send to + * @param {StringResolvable} [content] Content to send + * @param {MessageOptions|WebhookMessageOptions|MessageAdditions} [options={}] Options to use + * @param {MessageOptions|WebhookMessageOptions} [extra={}] - Extra options to add onto transformed options + * @returns {MessageOptions|WebhookMessageOptions} + */ + static create(target, content, options, extra = {}) { + const Webhook = require('./Webhook'); + const WebhookClient = require('../client/WebhookClient'); + + const isWebhook = target instanceof Webhook || target instanceof WebhookClient; + const transformed = this.transformOptions(content, options, extra, isWebhook); + return new this(target, transformed); + } +} + +module.exports = APIMessage; + +/** + * A target for a message. + * @typedef {TextChannel|DMChannel|User|GuildMember|Webhook|WebhookClient} MessageTarget + */ + +/** + * Additional items that can be sent with a message. + * @typedef {MessageEmbed|MessageAttachment|Array<MessageEmbed|MessageAttachment>} MessageAdditions + */ diff --git a/node_modules/discord.js/src/structures/Base.js b/node_modules/discord.js/src/structures/Base.js new file mode 100644 index 0000000..65c1fa5 --- /dev/null +++ b/node_modules/discord.js/src/structures/Base.js @@ -0,0 +1,42 @@ +'use strict'; + +const Util = require('../util/Util'); + +/** + * Represents a data model that is identifiable by a Snowflake (i.e. Discord API data models). + */ +class Base { + constructor(client) { + /** + * The client that instantiated this + * @name Base#client + * @type {Client} + * @readonly + */ + Object.defineProperty(this, 'client', { value: client }); + } + + _clone() { + return Object.assign(Object.create(this), this); + } + + _patch(data) { + return data; + } + + _update(data) { + const clone = this._clone(); + this._patch(data); + return clone; + } + + toJSON(...props) { + return Util.flatten(this, ...props); + } + + valueOf() { + return this.id; + } +} + +module.exports = Base; diff --git a/node_modules/discord.js/src/structures/BaseGuildEmoji.js b/node_modules/discord.js/src/structures/BaseGuildEmoji.js new file mode 100644 index 0000000..d366527 --- /dev/null +++ b/node_modules/discord.js/src/structures/BaseGuildEmoji.js @@ -0,0 +1,58 @@ +'use strict'; + +const Emoji = require('./Emoji'); + +/** + * Parent class for {@link GuildEmoji} and {@link GuildPreviewEmoji}. + * @extends {Emoji} + */ +class BaseGuildEmoji extends Emoji { + constructor(client, data, guild) { + super(client, data); + + /** + * The guild this emoji is a part of + * @type {Guild|GuildPreview} + */ + this.guild = guild; + + /** + * Array of role ids this emoji is active for + * @name BaseGuildEmoji#_roles + * @type {Snowflake[]} + * @private + */ + Object.defineProperty(this, '_roles', { value: [], writable: true }); + + this._patch(data); + } + + _patch(data) { + if (data.name) this.name = data.name; + + /** + * Whether or not this emoji requires colons surrounding it + * @type {boolean} + * @name GuildEmoji#requiresColons + */ + if (typeof data.require_colons !== 'undefined') this.requiresColons = data.require_colons; + + /** + * Whether this emoji is managed by an external service + * @type {boolean} + * @name GuildEmoji#managed + */ + if (typeof data.managed !== 'undefined') this.managed = data.managed; + + /** + * Whether this emoji is available + * @type {boolean} + * @name GuildEmoji#available + */ + if (typeof data.available !== 'undefined') this.available = data.available; + + if (data.roles) this._roles = data.roles; + } +} + +module.exports = BaseGuildEmoji; diff --git a/node_modules/discord.js/src/structures/CategoryChannel.js b/node_modules/discord.js/src/structures/CategoryChannel.js new file mode 100644 index 0000000..4ac9fbb --- /dev/null +++ b/node_modules/discord.js/src/structures/CategoryChannel.js @@ -0,0 +1,33 @@ +'use strict'; + +const GuildChannel = require('./GuildChannel'); + +/** + * Represents a guild category channel on Discord. + * @extends {GuildChannel} + */ +class CategoryChannel extends GuildChannel { + /** + * Channels that are a part of this category + * @type {?Collection<Snowflake, GuildChannel>} + * @readonly + */ + get children() { + return this.guild.channels.cache.filter(c => c.parentID === this.id); + } + + /** + * Sets the category parent of this channel. + * <warn>It is not currently possible to set the parent of a CategoryChannel.</warn> + * @method setParent + * @memberof CategoryChannel + * @instance + * @param {?GuildChannel|Snowflake} channel Parent channel + * @param {Object} [options={}] Options to pass + * @param {boolean} [options.lockPermissions=true] Lock the permissions to what the parent's permissions are + * @param {string} [options.reason] Reason for modifying the parent of this channel + * @returns {Promise<GuildChannel>} + */ +} + +module.exports = CategoryChannel; diff --git a/node_modules/discord.js/src/structures/Channel.js b/node_modules/discord.js/src/structures/Channel.js new file mode 100644 index 0000000..1dc7ee0 --- /dev/null +++ b/node_modules/discord.js/src/structures/Channel.js @@ -0,0 +1,151 @@ +'use strict'; + +const Base = require('./Base'); +const { ChannelTypes } = require('../util/Constants'); +const Snowflake = require('../util/Snowflake'); + +/** + * Represents any channel on Discord. + * @extends {Base} + */ +class Channel extends Base { + constructor(client, data) { + super(client); + + const type = Object.keys(ChannelTypes)[data.type]; + /** + * The type of the channel, either: + * * `dm` - a DM channel + * * `text` - a guild text channel + * * `voice` - a guild voice channel + * * `category` - a guild category channel + * * `news` - a guild news channel + * * `store` - a guild store channel + * * `unknown` - a generic channel of unknown type, could be Channel or GuildChannel + * @type {string} + */ + this.type = type ? type.toLowerCase() : 'unknown'; + + /** + * Whether the channel has been deleted + * @type {boolean} + */ + this.deleted = false; + + if (data) this._patch(data); + } + + _patch(data) { + /** + * The unique ID of the channel + * @type {Snowflake} + */ + this.id = data.id; + } + + /** + * The timestamp the channel was created at + * @type {number} + * @readonly + */ + get createdTimestamp() { + return Snowflake.deconstruct(this.id).timestamp; + } + + /** + * The time the channel was created at + * @type {Date} + * @readonly + */ + get createdAt() { + return new Date(this.createdTimestamp); + } + + /** + * When concatenated with a string, this automatically returns the channel's mention instead of the Channel object. + * @returns {string} + * @example + * // Logs: Hello from <#123456789012345678>! + * console.log(`Hello from ${channel}!`); + */ + toString() { + return `<#${this.id}>`; + } + + /** + * Deletes this channel. + * @returns {Promise<Channel>} + * @example + * // Delete the channel + * channel.delete() + * .then(console.log) + * .catch(console.error); + */ + delete() { + return this.client.api + .channels(this.id) + .delete() + .then(() => this); + } + + /** + * Fetches this channel. + * @returns {Promise<Channel>} + */ + fetch() { + return this.client.channels.fetch(this.id, true); + } + + static create(client, data, guild) { + const Structures = require('../util/Structures'); + let channel; + if (!data.guild_id && !guild) { + if ((data.recipients && data.type !== ChannelTypes.GROUP) || data.type === ChannelTypes.DM) { + const DMChannel = Structures.get('DMChannel'); + channel = new DMChannel(client, data); + } else if (data.type === ChannelTypes.GROUP) { + const PartialGroupDMChannel = require('./PartialGroupDMChannel'); + channel = new PartialGroupDMChannel(client, data); + } + } else { + guild = guild || client.guilds.cache.get(data.guild_id); + if (guild) { + switch (data.type) { + case ChannelTypes.TEXT: { + const TextChannel = Structures.get('TextChannel'); + channel = new TextChannel(guild, data); + break; + } + case ChannelTypes.VOICE: { + const VoiceChannel = Structures.get('VoiceChannel'); + channel = new VoiceChannel(guild, data); + break; + } + case ChannelTypes.CATEGORY: { + const CategoryChannel = Structures.get('CategoryChannel'); + channel = new CategoryChannel(guild, data); + break; + } + case ChannelTypes.NEWS: { + const NewsChannel = Structures.get('NewsChannel'); + channel = new NewsChannel(guild, data); + break; + } + case ChannelTypes.STORE: { + const StoreChannel = Structures.get('StoreChannel'); + channel = new StoreChannel(guild, data); + break; + } + } + if (channel) guild.channels.cache.set(channel.id, channel); + } + } + return channel; + } + + toJSON(...props) { + return super.toJSON({ createdTimestamp: true }, ...props); + } +} + +module.exports = Channel; diff --git a/node_modules/discord.js/src/structures/ClientApplication.js b/node_modules/discord.js/src/structures/ClientApplication.js new file mode 100644 index 0000000..753d90d --- /dev/null +++ b/node_modules/discord.js/src/structures/ClientApplication.js @@ -0,0 +1,156 @@ +'use strict'; + +const Base = require('./Base'); +const Team = require('./Team'); +const { ClientApplicationAssetTypes, Endpoints } = require('../util/Constants'); +const Snowflake = require('../util/Snowflake'); + +const AssetTypes = Object.keys(ClientApplicationAssetTypes); + +/** + * Represents a Client OAuth2 Application. + * @extends {Base} + */ +class ClientApplication extends Base { + constructor(client, data) { + super(client); + this._patch(data); + } + + _patch(data) { + /** + * The ID of the app + * @type {Snowflake} + */ + 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; + + /** + * 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; + } + + /** + * The timestamp the app was created at + * @type {number} + * @readonly + */ + get createdTimestamp() { + return Snowflake.deconstruct(this.id).timestamp; + } + + /** + * The time the app was created at + * @type {Date} + * @readonly + */ + get createdAt() { + return new Date(this.createdTimestamp); + } + + /** + * A link to the application's icon. + * @param {ImageURLOptions} [options={}] Options for the Image URL + * @returns {?string} URL to the icon + */ + iconURL({ format, size } = {}) { + if (!this.icon) return null; + return this.client.rest.cdn.AppIcon(this.id, this.icon, { format, size }); + } + + /** + * A link to this application's cover image. + * @param {ImageURLOptions} [options={}] Options for the Image URL + * @returns {?string} URL to the cover image + */ + coverImage({ format, size } = {}) { + if (!this.cover) return null; + return Endpoints.CDN(this.client.options.http.cdn).AppIcon(this.id, this.cover, { format, size }); + } + + /** + * Asset data. + * @typedef {Object} ClientAsset + * @property {Snowflake} id The asset ID + * @property {string} name The asset name + * @property {string} type The asset type + */ + + /** + * Gets the clients rich presence assets. + * @returns {Promise<Array<ClientAsset>>} + */ + fetchAssets() { + return this.client.api.oauth2 + .applications(this.id) + .assets.get() + .then(assets => + assets.map(a => ({ + id: a.id, + name: a.name, + type: AssetTypes[a.type - 1], + })), + ); + } + + /** + * When concatenated with a string, this automatically returns the application's name instead of the + * ClientApplication object. + * @returns {string} + * @example + * // Logs: Application name: My App + * console.log(`Application name: ${application}`); + */ + toString() { + return this.name; + } + + toJSON() { + return super.toJSON({ createdTimestamp: true }); + } +} + +module.exports = ClientApplication; diff --git a/node_modules/discord.js/src/structures/ClientPresence.js b/node_modules/discord.js/src/structures/ClientPresence.js new file mode 100644 index 0000000..a39ba00 --- /dev/null +++ b/node_modules/discord.js/src/structures/ClientPresence.js @@ -0,0 +1,87 @@ +'use strict'; + +const { Presence } = require('./Presence'); +const { TypeError } = require('../errors'); +const Collection = require('../util/Collection'); +const { ActivityTypes, OPCodes } = require('../util/Constants'); + +class ClientPresence extends Presence { + /** + * @param {Client} client The instantiating client + * @param {Object} [data={}] The data for the client presence + */ + constructor(client, data = {}) { + super(client, Object.assign(data, { status: 'online', user: { id: null } })); + } + + async set(presence) { + const packet = await this._parse(presence); + this.patch(packet); + if (typeof presence.shardID === 'undefined') { + this.client.ws.broadcast({ op: OPCodes.STATUS_UPDATE, d: packet }); + } else if (Array.isArray(presence.shardID)) { + for (const shardID of presence.shardID) { + this.client.ws.shards.get(shardID).send({ op: OPCodes.STATUS_UPDATE, d: packet }); + } + } else { + this.client.ws.shards.get(presence.shardID).send({ op: OPCodes.STATUS_UPDATE, d: packet }); + } + return this; + } + + async _parse({ status, since, afk, activity }) { + const applicationID = activity && (activity.application ? activity.application.id || activity.application : null); + let assets = new Collection(); + if (activity) { + if (typeof activity.name !== 'string') throw new TypeError('INVALID_TYPE', 'name', 'string'); + if (!activity.type) activity.type = 0; + if (activity.assets && applicationID) { + try { + const a = await this.client.api.oauth2.applications(applicationID).assets.get(); + for (const asset of a) assets.set(asset.name, asset.id); + } catch {} // eslint-disable-line no-empty + } + } + + const packet = { + afk: afk != null ? afk : false, // eslint-disable-line eqeqeq + since: since != null ? since : null, // eslint-disable-line eqeqeq + status: status || this.status, + game: activity + ? { + type: activity.type, + name: activity.name, + url: activity.url, + details: activity.details || undefined, + state: activity.state || undefined, + assets: activity.assets + ? { + large_text: activity.assets.largeText || undefined, + small_text: activity.assets.smallText || undefined, + large_image: assets.get(activity.assets.largeImage) || activity.assets.largeImage, + small_image: assets.get(activity.assets.smallImage) || activity.assets.smallImage, + } + : undefined, + timestamps: activity.timestamps || undefined, + party: activity.party || undefined, + application_id: applicationID || undefined, + secrets: activity.secrets || undefined, + instance: activity.instance || undefined, + } + : null, + }; + + if ((status || afk || since) && !activity) { + packet.game = this.activities[0] || null; + } + + if (packet.game) { + packet.game.type = + typeof packet.game.type === 'number' ? packet.game.type : ActivityTypes.indexOf(packet.game.type); + } + + return packet; + } +} + +module.exports = ClientPresence; diff --git a/node_modules/discord.js/src/structures/ClientUser.js b/node_modules/discord.js/src/structures/ClientUser.js new file mode 100644 index 0000000..84ada54 --- /dev/null +++ b/node_modules/discord.js/src/structures/ClientUser.js @@ -0,0 +1,180 @@ +'use strict'; + +const DataResolver = require('../util/DataResolver'); +const Structures = require('../util/Structures'); + +/** + * Represents the logged in client's Discord user. + * @extends {User} + */ +class ClientUser extends Structures.get('User') { + constructor(client, data) { + super(client, data); + this._typing = new Map(); + } + + _patch(data) { + super._patch(data); + + if ('verified' in data) { + /** + * Whether or not this account has been verified + * @type {boolean} + */ + this.verified = data.verified; + } + + if ('mfa_enabled' in data) { + /** + * If the bot's {@link ClientApplication#owner Owner} has MFA enabled on their account + * @type {?boolean} + */ + this.mfaEnabled = typeof data.mfa_enabled === 'boolean' ? data.mfa_enabled : null; + } else if (typeof this.mfaEnabled === 'undefined') { + this.mfaEnabled = null; + } + + if (data.token) this.client.token = data.token; + } + + /** + * ClientUser's presence + * @type {Presence} + * @readonly + */ + get presence() { + return this.client.presence; + } + + edit(data) { + return this.client.api + .users('@me') + .patch({ data }) + .then(newData => { + this.client.token = newData.token; + const { updated } = this.client.actions.UserUpdate.handle(newData); + if (updated) return updated; + return this; + }); + } + + /** + * Sets the username of the logged in client. + * <info>Changing usernames in Discord is heavily rate limited, with only 2 requests + * every hour. Use this sparingly!</info> + * @param {string} username The new username + * @returns {Promise<ClientUser>} + * @example + * // Set username + * client.user.setUsername('discordjs') + * .then(user => console.log(`My new username is ${user.username}`)) + * .catch(console.error); + */ + setUsername(username) { + return this.edit({ username }); + } + + /** + * Sets the avatar of the logged in client. + * @param {BufferResolvable|Base64Resolvable} avatar The new avatar + * @returns {Promise<ClientUser>} + * @example + * // Set avatar + * client.user.setAvatar('./avatar.png') + * .then(user => console.log(`New avatar set!`)) + * .catch(console.error); + */ + async setAvatar(avatar) { + return this.edit({ avatar: await DataResolver.resolveImage(avatar) }); + } + + /** + * Data resembling a raw Discord presence. + * @typedef {Object} PresenceData + * @property {PresenceStatusData} [status] Status of the user + * @property {boolean} [afk] Whether the user is AFK + * @property {Object} [activity] Activity the user is playing + * @property {Object|string} [activity.application] An application object or application id + * @property {string} [activity.application.id] The id of the application + * @property {string} [activity.name] Name of the activity + * @property {ActivityType|number} [activity.type] Type of the activity + * @property {string} [activity.url] Stream url + * @property {?number|number[]} [shardID] Shard Id(s) to have the activity set on + */ + + /** + * Sets the full presence of the client user. + * @param {PresenceData} data Data for the presence + * @returns {Promise<Presence>} + * @example + * // Set the client user's presence + * client.user.setPresence({ activity: { name: 'with discord.js' }, status: 'idle' }) + * .then(console.log) + * .catch(console.error); + */ + setPresence(data) { + return this.client.presence.set(data); + } + + /** + * A user's status. Must be one of: + * * `online` + * * `idle` + * * `invisible` + * * `dnd` (do not disturb) + * @typedef {string} PresenceStatusData + */ + + /** + * Sets the status of the client user. + * @param {PresenceStatusData} status Status to change to + * @param {?number|number[]} [shardID] Shard ID(s) to have the activity set on + * @returns {Promise<Presence>} + * @example + * // Set the client user's status + * client.user.setStatus('idle') + * .then(console.log) + * .catch(console.error); + */ + setStatus(status, shardID) { + return this.setPresence({ status, shardID }); + } + + /** + * Options for setting an activity + * @typedef ActivityOptions + * @type {Object} + * @property {string} [url] Twitch stream URL + * @property {ActivityType|number} [type] Type of the activity + * @property {?number|number[]} [shardID] Shard Id(s) to have the activity set on + */ + + /** + * Sets the activity the client user is playing. + * @param {string|ActivityOptions} [name] Activity being played, or options for setting the activity + * @param {ActivityOptions} [options] Options for setting the activity + * @returns {Promise<Presence>} + * @example + * // Set the client user's activity + * client.user.setActivity('discord.js', { type: 'WATCHING' }) + * .then(presence => console.log(`Activity set to ${presence.activities[0].name}`)) + * .catch(console.error); + */ + setActivity(name, options = {}) { + if (!name) return this.setPresence({ activity: null, shardID: options.shardID }); + + const activity = Object.assign({}, options, typeof name === 'object' ? name : { name }); + return this.setPresence({ activity, shardID: activity.shardID }); + } + + /** + * Sets/removes the AFK flag for the client user. + * @param {boolean} afk Whether or not the user is AFK + * @returns {Promise<Presence>} + */ + setAFK(afk) { + return this.setPresence({ afk }); + } +} + +module.exports = ClientUser; diff --git a/node_modules/discord.js/src/structures/DMChannel.js b/node_modules/discord.js/src/structures/DMChannel.js new file mode 100644 index 0000000..e661bc4 --- /dev/null +++ b/node_modules/discord.js/src/structures/DMChannel.js @@ -0,0 +1,98 @@ +'use strict'; + +const Channel = require('./Channel'); +const TextBasedChannel = require('./interfaces/TextBasedChannel'); +const MessageManager = require('../managers/MessageManager'); + +/** + * Represents a direct message channel between two users. + * @extends {Channel} + * @implements {TextBasedChannel} + */ +class DMChannel extends Channel { + /** + * @param {Client} client The instantiating client + * @param {Object} data The data for the DM channel + */ + constructor(client, data) { + super(client, data); + // Override the channel type so partials have a known type + this.type = 'dm'; + /** + * A manager of the messages belonging to this channel + * @type {MessageManager} + */ + this.messages = new MessageManager(this); + this._typing = new Map(); + } + + _patch(data) { + super._patch(data); + + if (data.recipients) { + /** + * The recipient on the other end of the DM + * @type {User} + */ + this.recipient = this.client.users.add(data.recipients[0]); + } + + /** + * The ID of the last message in the channel, if one was sent + * @type {?Snowflake} + */ + this.lastMessageID = data.last_message_id; + + /** + * The timestamp when the last pinned message was pinned, if there was one + * @type {?number} + */ + this.lastPinTimestamp = data.last_pin_timestamp ? new Date(data.last_pin_timestamp).getTime() : null; + } + + /** + * Whether this DMChannel is a partial + * @type {boolean} + * @readonly + */ + get partial() { + return typeof this.lastMessageID === 'undefined'; + } + + /** + * Fetch this DMChannel. + * @returns {Promise<DMChannel>} + */ + fetch() { + return this.recipient.createDM(); + } + + /** + * When concatenated with a string, this automatically returns the recipient's mention instead of the + * DMChannel object. + * @returns {string} + * @example + * // Logs: Hello from <@123456789012345678>! + * console.log(`Hello from ${channel}!`); + */ + toString() { + return this.recipient.toString(); + } + + // These are here only for documentation purposes - they are implemented by TextBasedChannel + /* eslint-disable no-empty-function */ + get lastMessage() {} + get lastPinAt() {} + send() {} + startTyping() {} + stopTyping() {} + get typing() {} + get typingCount() {} + createMessageCollector() {} + awaitMessages() {} + // Doesn't work on DM channels; bulkDelete() {} +} + +TextBasedChannel.applyToClass(DMChannel, true, ['bulkDelete']); + +module.exports = DMChannel; diff --git a/node_modules/discord.js/src/structures/Emoji.js b/node_modules/discord.js/src/structures/Emoji.js new file mode 100644 index 0000000..0214ea8 --- /dev/null +++ b/node_modules/discord.js/src/structures/Emoji.js @@ -0,0 +1,104 @@ +'use strict'; + +const Base = require('./Base'); +const Snowflake = require('../util/Snowflake'); + +/** + * Represents an emoji, see {@link GuildEmoji} and {@link ReactionEmoji}. + * @extends {Base} + */ +class Emoji extends Base { + constructor(client, emoji) { + super(client); + /** + * Whether this emoji is animated + * @type {boolean} + */ + this.animated = emoji.animated; + + /** + * The name of this emoji + * @type {string} + */ + this.name = emoji.name; + + /** + * The ID of this emoji + * @type {?Snowflake} + */ + this.id = emoji.id; + + /** + * Whether this emoji has been deleted + * @type {boolean} + */ + this.deleted = false; + } + + /** + * The identifier of this emoji, used for message reactions + * @type {string} + * @readonly + */ + get identifier() { + if (this.id) return `${this.animated ? 'a:' : ''}${this.name}:${this.id}`; + return encodeURIComponent(this.name); + } + + /** + * The URL to the emoji file if its a custom emoji + * @type {?string} + * @readonly + */ + get url() { + if (!this.id) return null; + return this.client.rest.cdn.Emoji(this.id, this.animated ? 'gif' : 'png'); + } + + /** + * The timestamp the emoji was created at, or null if unicode + * @type {?number} + * @readonly + */ + get createdTimestamp() { + if (!this.id) return null; + return Snowflake.deconstruct(this.id).timestamp; + } + + /** + * The time the emoji was created at, or null if unicode + * @type {?Date} + * @readonly + */ + get createdAt() { + if (!this.id) return null; + return new Date(this.createdTimestamp); + } + + /** + * When concatenated with a string, this automatically returns the text required to form a graphical emoji on Discord + * instead of the Emoji object. + * @returns {string} + * @example + * // Send a custom emoji from a guild: + * const emoji = guild.emojis.cache.first(); + * msg.reply(`Hello! ${emoji}`); + * @example + * // Send the emoji used in a reaction to the channel the reaction is part of + * reaction.message.channel.send(`The emoji used was: ${reaction.emoji}`); + */ + toString() { + return this.id ? `<${this.animated ? 'a' : ''}:${this.name}:${this.id}>` : this.name; + } + + toJSON() { + return super.toJSON({ + guild: 'guildID', + createdTimestamp: true, + url: true, + identifier: true, + }); + } +} + +module.exports = Emoji; diff --git a/node_modules/discord.js/src/structures/Guild.js b/node_modules/discord.js/src/structures/Guild.js new file mode 100644 index 0000000..e2bc8c4 --- /dev/null +++ b/node_modules/discord.js/src/structures/Guild.js @@ -0,0 +1,1349 @@ +'use strict'; + +const Base = require('./Base'); +const GuildAuditLogs = require('./GuildAuditLogs'); +const GuildPreview = require('./GuildPreview'); +const Integration = require('./Integration'); +const Invite = require('./Invite'); +const VoiceRegion = require('./VoiceRegion'); +const Webhook = require('./Webhook'); +const GuildChannelManager = require('../managers/GuildChannelManager'); +const GuildEmojiManager = require('../managers/GuildEmojiManager'); +const GuildMemberManager = require('../managers/GuildMemberManager'); +const PresenceManager = require('../managers/PresenceManager'); +const RoleManager = require('../managers/RoleManager'); +const VoiceStateManager = require('../managers/VoiceStateManager'); +const Collection = require('../util/Collection'); +const { + ChannelTypes, + DefaultMessageNotifications, + PartialTypes, + VerificationLevels, + ExplicitContentFilterLevels, +} = require('../util/Constants'); +const DataResolver = require('../util/DataResolver'); +const Snowflake = require('../util/Snowflake'); +const SystemChannelFlags = require('../util/SystemChannelFlags'); +const Util = require('../util/Util'); + +/** + * Represents a guild (or a server) on Discord. + * <info>It's recommended to see if a guild is available before performing operations or reading data from it. You can + * check this with `guild.available`.</info> + * @extends {Base} + */ +class Guild extends Base { + /** + * @param {Client} client The instantiating client + * @param {Object} data The data for the guild + */ + constructor(client, data) { + super(client); + + /** + * A manager of the members belonging to this guild + * @type {GuildMemberManager} + */ + this.members = new GuildMemberManager(this); + + /** + * A manager of the channels belonging to this guild + * @type {GuildChannelManager} + */ + this.channels = new GuildChannelManager(this); + + /** + * A manager of the roles belonging to this guild + * @type {RoleManager} + */ + this.roles = new RoleManager(this); + + /** + * A manager of the presences belonging to this guild + * @type {PresenceManager} + */ + this.presences = new PresenceManager(this.client); + + /** + * A manager of the voice states of this guild + * @type {VoiceStateManager} + */ + this.voiceStates = new VoiceStateManager(this); + + /** + * Whether the bot has been removed from the guild + * @type {boolean} + */ + this.deleted = false; + + if (!data) return; + if (data.unavailable) { + /** + * Whether the guild is available to access. If it is not available, it indicates a server outage + * @type {boolean} + */ + this.available = false; + + /** + * The Unique ID of the guild, useful for comparisons + * @type {Snowflake} + */ + this.id = data.id; + } else { + this._patch(data); + if (!data.channels) this.available = false; + } + + /** + * The id of the shard this Guild belongs to. + * @type {number} + */ + this.shardID = data.shardID; + } + + /** + * The Shard this Guild belongs to. + * @type {WebSocketShard} + * @readonly + */ + get shard() { + return this.client.ws.shards.get(this.shardID); + } + + /** + * Sets up the guild. + * @param {*} data The raw data of the guild + * @private + */ + _patch(data) { + /** + * The name of the guild + * @type {string} + */ + this.name = data.name; + + /** + * The hash of the guild icon + * @type {?string} + */ + this.icon = data.icon; + + /** + * The hash of the guild splash image (VIP only) + * @type {?string} + */ + this.splash = data.splash; + + /** + * The region the guild is located in + * @type {string} + */ + this.region = data.region; + + /** + * The full amount of members in this guild + * @type {number} + */ + this.memberCount = data.member_count || this.memberCount; + + /** + * Whether the guild is "large" (has more than 250 members) + * @type {boolean} + */ + this.large = Boolean('large' in data ? data.large : this.large); + + /** + * An array of enabled guild features, here are the possible values: + * * ANIMATED_ICON + * * BANNER + * * COMMERCE + * * DISCOVERABLE + * * FEATURABLE + * * INVITE_SPLASH + * * NEWS + * * PARTNERED + * * PUBLIC + * * PUBLIC_DISABLED + * * VANITY_URL + * * VERIFIED + * * VIP_REGIONS + * * WELCOME_SCREEN_ENABLED + * @typedef {string} Features + */ + + /** + * An array of guild features partnered guilds have enabled + * @type {Features[]} + */ + this.features = data.features; + + /** + * The ID of the application that created this guild (if applicable) + * @type {?Snowflake} + */ + this.applicationID = data.application_id; + + /** + * The time in seconds before a user is counted as "away from keyboard" + * @type {?number} + */ + this.afkTimeout = data.afk_timeout; + + /** + * The ID of the voice channel where AFK members are moved + * @type {?Snowflake} + */ + this.afkChannelID = data.afk_channel_id; + + /** + * The ID of the system channel + * @type {?Snowflake} + */ + this.systemChannelID = data.system_channel_id; + + /** + * Whether embedded images are enabled on this guild + * @type {boolean} + */ + this.embedEnabled = data.embed_enabled; + + /** + * The type of premium tier: + * * 0: NONE + * * 1: TIER_1 + * * 2: TIER_2 + * * 3: TIER_3 + * @typedef {number} PremiumTier + */ + + /** + * The premium tier on this guild + * @type {PremiumTier} + */ + this.premiumTier = data.premium_tier; + + /** + * The total number of users currently boosting this server + * @type {?number} + * @name Guild#premiumSubscriptionCount + */ + if (typeof data.premium_subscription_count !== 'undefined') { + this.premiumSubscriptionCount = data.premium_subscription_count; + } + + /** + * Whether widget images are enabled on this guild + * @type {?boolean} + * @name Guild#widgetEnabled + */ + if (typeof data.widget_enabled !== 'undefined') this.widgetEnabled = data.widget_enabled; + + /** + * The widget channel ID, if enabled + * @type {?string} + * @name Guild#widgetChannelID + */ + if (typeof data.widget_channel_id !== 'undefined') this.widgetChannelID = data.widget_channel_id; + + /** + * The embed channel ID, if enabled + * @type {?string} + * @name Guild#embedChannelID + */ + if (typeof data.embed_channel_id !== 'undefined') this.embedChannelID = data.embed_channel_id; + + /** + * The verification level of the guild + * @type {VerificationLevel} + */ + this.verificationLevel = VerificationLevels[data.verification_level]; + + /** + * The explicit content filter level of the guild + * @type {ExplicitContentFilterLevel} + */ + this.explicitContentFilter = ExplicitContentFilterLevels[data.explicit_content_filter]; + + /** + * The required MFA level for the guild + * @type {number} + */ + this.mfaLevel = data.mfa_level; + + /** + * The timestamp the client user joined the guild at + * @type {number} + */ + this.joinedTimestamp = data.joined_at ? new Date(data.joined_at).getTime() : this.joinedTimestamp; + + /** + * The value set for the guild's default message notifications + * @type {DefaultMessageNotifications|number} + */ + this.defaultMessageNotifications = + DefaultMessageNotifications[data.default_message_notifications] || data.default_message_notifications; + + /** + * The value set for the guild's system channel flags + * @type {Readonly<SystemChannelFlags>} + */ + this.systemChannelFlags = new SystemChannelFlags(data.system_channel_flags).freeze(); + + /** + * The maximum amount of members the guild can have + * <info>You will need to fetch the guild using {@link Guild#fetch} if you want to receive this parameter</info> + * @type {?number} + * @name Guild#maximumMembers + */ + if (typeof data.max_members !== 'undefined') this.maximumMembers = data.max_members || 250000; + + /** + * The maximum amount of presences the guild can have + * <info>You will need to fetch the guild using {@link Guild#fetch} if you want to receive this parameter</info> + * @type {?number} + * @name Guild#maximumPresences + */ + if (typeof data.max_presences !== 'undefined') this.maximumPresences = data.max_presences || 25000; + + /** + * The vanity URL code of the guild, if any + * @type {?string} + */ + this.vanityURLCode = data.vanity_url_code; + + /** + * The description of the guild, if any + * @type {?string} + */ + this.description = data.description; + + /** + * The hash of the guild banner + * @type {?string} + */ + this.banner = data.banner; + + this.id = data.id; + this.available = !data.unavailable; + this.features = data.features || this.features || []; + + /** + * The ID of the rules channel for the guild + * <info>This is only available on guilds with the `PUBLIC` feature</info> + * @type {?Snowflake} + */ + this.rulesChannelID = data.rules_channel_id; + + /** + * The ID of the public updates channel for the guild + * <info>This is only available on guilds with the `PUBLIC` feature</info> + * @type {?Snowflake} + */ + this.publicUpdatesChannelID = data.public_updates_channel_id; + + if (data.channels) { + this.channels.cache.clear(); + for (const rawChannel of data.channels) { + this.client.channels.add(rawChannel, this); + } + } + + if (data.roles) { + this.roles.cache.clear(); + for (const role of data.roles) this.roles.add(role); + } + + if (data.members) { + this.members.cache.clear(); + for (const guildUser of data.members) this.members.add(guildUser); + } + + if (data.owner_id) { + /** + * The user ID of this guild's owner + * @type {Snowflake} + */ + this.ownerID = data.owner_id; + } + + if (data.presences) { + for (const presence of data.presences) { + this.presences.add(Object.assign(presence, { guild: this })); + } + } + + if (data.voice_states) { + this.voiceStates.cache.clear(); + for (const voiceState of data.voice_states) { + this.voiceStates.add(voiceState); + } + } + + if (!this.emojis) { + /** + * A manager of the emojis belonging to this guild + * @type {GuildEmojiManager} + */ + this.emojis = new GuildEmojiManager(this); + if (data.emojis) for (const emoji of data.emojis) this.emojis.add(emoji); + } else if (data.emojis) { + this.client.actions.GuildEmojisUpdate.handle({ + guild_id: this.id, + emojis: data.emojis, + }); + } + } + + /** + * The URL to this guild's banner. + * @param {ImageURLOptions} [options={}] Options for the Image URL + * @returns {?string} + */ + bannerURL({ format, size } = {}) { + if (!this.banner) return null; + return this.client.rest.cdn.Banner(this.id, this.banner, format, size); + } + + /** + * The timestamp the guild was created at + * @type {number} + * @readonly + */ + get createdTimestamp() { + return Snowflake.deconstruct(this.id).timestamp; + } + + /** + * The time the guild was created at + * @type {Date} + * @readonly + */ + get createdAt() { + return new Date(this.createdTimestamp); + } + + /** + * The time the client user joined the guild + * @type {Date} + * @readonly + */ + get joinedAt() { + return new Date(this.joinedTimestamp); + } + + /** + * If this guild is partnered + * @type {boolean} + * @readonly + */ + get partnered() { + return this.features.includes('PARTNERED'); + } + + /** + * If this guild is verified + * @type {boolean} + * @readonly + */ + get verified() { + return this.features.includes('VERIFIED'); + } + + /** + * The URL to this guild's icon. + * @param {ImageURLOptions} [options={}] Options for the Image URL + * @returns {?string} + */ + iconURL({ format, size, dynamic } = {}) { + if (!this.icon) return null; + return this.client.rest.cdn.Icon(this.id, this.icon, format, size, dynamic); + } + + /** + * The acronym that shows up in place of a guild icon. + * @type {string} + * @readonly + */ + get nameAcronym() { + return this.name.replace(/\w+/g, name => name[0]).replace(/\s/g, ''); + } + + /** + * The URL to this guild's splash. + * @param {ImageURLOptions} [options={}] Options for the Image URL + * @returns {?string} + */ + splashURL({ format, size } = {}) { + if (!this.splash) return null; + return this.client.rest.cdn.Splash(this.id, this.splash, format, size); + } + + /** + * The owner of the guild + * @type {?GuildMember} + * @readonly + */ + get owner() { + return ( + this.members.cache.get(this.ownerID) || + (this.client.options.partials.includes(PartialTypes.GUILD_MEMBER) + ? this.members.add({ user: { id: this.ownerID } }, true) + : null) + ); + } + + /** + * AFK voice channel for this guild + * @type {?VoiceChannel} + * @readonly + */ + get afkChannel() { + return this.client.channels.cache.get(this.afkChannelID) || null; + } + + /** + * System channel for this guild + * @type {?TextChannel} + * @readonly + */ + get systemChannel() { + return this.client.channels.cache.get(this.systemChannelID) || null; + } + + /** + * Widget channel for this guild + * @type {?TextChannel} + * @readonly + */ + get widgetChannel() { + return this.client.channels.cache.get(this.widgetChannelID) || null; + } + + /** + * Embed channel for this guild + * @type {?TextChannel} + * @readonly + */ + get embedChannel() { + return this.client.channels.cache.get(this.embedChannelID) || null; + } + + /** + * Rules channel for this guild + * <info>This is only available on guilds with the `PUBLIC` feature</info> + * @type {?TextChannel} + * @readonly + */ + get rulesChannel() { + return this.client.channels.cache.get(this.rulesChannelID) || null; + } + + /** + * Public updates channel for this guild + * <info>This is only available on guilds with the `PUBLIC` feature</info> + * @type {?TextChannel} + * @readonly + */ + get publicUpdatesChannel() { + return this.client.channels.cache.get(this.publicUpdatesChannelID) || null; + } + + /** + * The client user as a GuildMember of this guild + * @type {?GuildMember} + * @readonly + */ + get me() { + return ( + this.members.cache.get(this.client.user.id) || + (this.client.options.partials.includes(PartialTypes.GUILD_MEMBER) + ? this.members.add({ user: { id: this.client.user.id } }, true) + : null) + ); + } + + /** + * The voice state for the client user of this guild, if any + * @type {?VoiceState} + * @readonly + */ + get voice() { + return this.voiceStates.cache.get(this.client.user.id); + } + + /** + * Returns the GuildMember form of a User object, if the user is present in the guild. + * @param {UserResolvable} user The user that you want to obtain the GuildMember of + * @returns {?GuildMember} + * @example + * // Get the guild member of a user + * const member = guild.member(message.author); + */ + member(user) { + return this.members.resolve(user); + } + + /** + * Fetches this guild. + * @returns {Promise<Guild>} + */ + fetch() { + return this.client.api + .guilds(this.id) + .get() + .then(data => { + this._patch(data); + return this; + }); + } + + /** + * An object containing information about a guild member's ban. + * @typedef {Object} BanInfo + * @property {User} user User that was banned + * @property {?string} reason Reason the user was banned + */ + + /** + * Fetches information on a banned user from this guild. + * @param {UserResolvable} user The User to fetch the ban info of + * @returns {Promise<BanInfo>} + */ + fetchBan(user) { + const id = this.client.users.resolveID(user); + if (!id) throw new Error('FETCH_BAN_RESOLVE_ID'); + return this.client.api + .guilds(this.id) + .bans(id) + .get() + .then(ban => ({ + reason: ban.reason, + user: this.client.users.add(ban.user), + })); + } + + /** + * Fetches a collection of banned users in this guild. + * @returns {Promise<Collection<Snowflake, BanInfo>>} + */ + fetchBans() { + return this.client.api + .guilds(this.id) + .bans.get() + .then(bans => + bans.reduce((collection, ban) => { + collection.set(ban.user.id, { + reason: ban.reason, + user: this.client.users.add(ban.user), + }); + return collection; + }, new Collection()), + ); + } + + /** + * Fetches a collection of integrations to this guild. + * Resolves with a collection mapping integrations by their ids. + * @returns {Promise<Collection<string, Integration>>} + * @example + * // Fetch integrations + * guild.fetchIntegrations() + * .then(integrations => console.log(`Fetched ${integrations.size} integrations`)) + * .catch(console.error); + */ + fetchIntegrations() { + return this.client.api + .guilds(this.id) + .integrations.get() + .then(data => + data.reduce( + (collection, integration) => collection.set(integration.id, new Integration(this.client, integration, this)), + new Collection(), + ), + ); + } + + /** + * The data for creating an integration. + * @typedef {Object} IntegrationData + * @property {string} id The integration id + * @property {string} type The integration type + */ + + /** + * Creates an integration by attaching an integration object + * @param {IntegrationData} data The data for the integration + * @param {string} reason Reason for creating the integration + * @returns {Promise<Guild>} + */ + createIntegration(data, reason) { + return this.client.api + .guilds(this.id) + .integrations.post({ data, reason }) + .then(() => this); + } + + /** + * Fetches a collection of invites to this guild. + * Resolves with a collection mapping invites by their codes. + * @returns {Promise<Collection<string, Invite>>} + * @example + * // Fetch invites + * guild.fetchInvites() + * .then(invites => console.log(`Fetched ${invites.size} invites`)) + * .catch(console.error); + * @example + * // Fetch invite creator by their id + * guild.fetchInvites() + * .then(invites => console.log(invites.find(invite => invite.inviter.id === '84484653687267328'))) + * .catch(console.error); + */ + fetchInvites() { + return this.client.api + .guilds(this.id) + .invites.get() + .then(inviteItems => { + const invites = new Collection(); + for (const inviteItem of inviteItems) { + const invite = new Invite(this.client, inviteItem); + invites.set(invite.code, invite); + } + return invites; + }); + } + + /** + * Obtains a guild preview for this guild from Discord, only available for public guilds. + * @returns {Promise<GuildPreview>} + */ + fetchPreview() { + return this.client.api + .guilds(this.id) + .preview.get() + .then(data => new GuildPreview(this.client, data)); + } + + /** + * Fetches the vanity url invite code to this guild. + * Resolves with a string matching the vanity url invite code, not the full url. + * @returns {Promise<string>} + * @example + * // Fetch invites + * guild.fetchVanityCode() + * .then(code => { + * console.log(`Vanity URL: https://discord.gg/${code}`); + * }) + * .catch(console.error); + */ + fetchVanityCode() { + if (!this.features.includes('VANITY_URL')) { + return Promise.reject(new Error('VANITY_URL')); + } + return this.client.api + .guilds(this.id, 'vanity-url') + .get() + .then(res => res.code); + } + + /** + * Fetches all webhooks for the guild. + * @returns {Promise<Collection<Snowflake, Webhook>>} + * @example + * // Fetch webhooks + * guild.fetchWebhooks() + * .then(webhooks => console.log(`Fetched ${webhooks.size} webhooks`)) + * .catch(console.error); + */ + fetchWebhooks() { + return this.client.api + .guilds(this.id) + .webhooks.get() + .then(data => { + const hooks = new Collection(); + for (const hook of data) hooks.set(hook.id, new Webhook(this.client, hook)); + return hooks; + }); + } + + /** + * Fetches available voice regions. + * @returns {Promise<Collection<string, VoiceRegion>>} + */ + fetchVoiceRegions() { + return this.client.api + .guilds(this.id) + .regions.get() + .then(res => { + const regions = new Collection(); + for (const region of res) regions.set(region.id, new VoiceRegion(region)); + return regions; + }); + } + + /** + * The Guild Embed object + * @typedef {Object} GuildEmbedData + * @property {boolean} enabled Whether the embed is enabled + * @property {?GuildChannel} channel The embed channel + */ + + /** + * Fetches the guild embed. + * @returns {Promise<GuildEmbedData>} + * @example + * // Fetches the guild embed + * guild.fetchEmbed() + * .then(embed => console.log(`The embed is ${embed.enabled ? 'enabled' : 'disabled'}`)) + * .catch(console.error); + */ + fetchEmbed() { + return this.client.api + .guilds(this.id) + .embed.get() + .then(data => ({ + enabled: data.enabled, + channel: data.channel_id ? this.channels.cache.get(data.channel_id) : null, + })); + } + + /** + * Fetches audit logs for this guild. + * @param {Object} [options={}] Options for fetching audit logs + * @param {Snowflake|GuildAuditLogsEntry} [options.before] Limit to entries from before specified entry + * @param {number} [options.limit] Limit number of entries + * @param {UserResolvable} [options.user] Only show entries involving this user + * @param {AuditLogAction|number} [options.type] Only show entries involving this action type + * @returns {Promise<GuildAuditLogs>} + * @example + * // Output audit log entries + * guild.fetchAuditLogs() + * .then(audit => console.log(audit.entries.first())) + * .catch(console.error); + */ + fetchAuditLogs(options = {}) { + if (options.before && options.before instanceof GuildAuditLogs.Entry) options.before = options.before.id; + if (typeof options.type === 'string') options.type = GuildAuditLogs.Actions[options.type]; + + return this.client.api + .guilds(this.id) + ['audit-logs'].get({ + query: { + before: options.before, + limit: options.limit, + user_id: this.client.users.resolveID(options.user), + action_type: options.type, + }, + }) + .then(data => GuildAuditLogs.build(this, data)); + } + + /** + * Adds a user to the guild using OAuth2. Requires the `CREATE_INSTANT_INVITE` permission. + * @param {UserResolvable} user User to add to the guild + * @param {Object} options Options for the addition + * @param {string} options.accessToken An OAuth2 access token for the user with the `guilds.join` scope granted to the + * bot's application + * @param {string} [options.nick] Nickname to give the member (requires `MANAGE_NICKNAMES`) + * @param {Collection<Snowflake, Role>|RoleResolvable[]} [options.roles] Roles to add to the member + * (requires `MANAGE_ROLES`) + * @param {boolean} [options.mute] Whether the member should be muted (requires `MUTE_MEMBERS`) + * @param {boolean} [options.deaf] Whether the member should be deafened (requires `DEAFEN_MEMBERS`) + * @returns {Promise<GuildMember>} + */ + addMember(user, options) { + user = this.client.users.resolveID(user); + if (!user) return Promise.reject(new TypeError('INVALID_TYPE', 'user', 'UserResolvable')); + if (this.members.cache.has(user)) return Promise.resolve(this.members.cache.get(user)); + options.access_token = options.accessToken; + if (options.roles) { + const roles = []; + for (let role of options.roles instanceof Collection ? options.roles.values() : options.roles) { + role = this.roles.resolve(role); + if (!role) { + return Promise.reject( + new TypeError('INVALID_TYPE', 'options.roles', 'Array or Collection of Roles or Snowflakes', true), + ); + } + roles.push(role.id); + } + options.roles = roles; + } + return this.client.api + .guilds(this.id) + .members(user) + .put({ data: options }) + .then(data => this.members.add(data)); + } + + /** + * The data for editing a guild. + * @typedef {Object} GuildEditData + * @property {string} [name] The name of the guild + * @property {string} [region] The region of the guild + * @property {VerificationLevel|number} [verificationLevel] The verification level of the guild + * @property {ExplicitContentFilterLevel|number} [explicitContentFilter] The level of the explicit content filter + * @property {ChannelResolvable} [afkChannel] The AFK channel of the guild + * @property {ChannelResolvable} [systemChannel] The system channel of the guild + * @property {number} [afkTimeout] The AFK timeout of the guild + * @property {Base64Resolvable} [icon] The icon of the guild + * @property {GuildMemberResolvable} [owner] The owner of the guild + * @property {Base64Resolvable} [splash] The splash screen of the guild + * @property {Base64Resolvable} [banner] The banner of the guild + * @property {DefaultMessageNotifications|number} [defaultMessageNotifications] The default message notifications + * @property {SystemChannelFlagsResolvable} [systemChannelFlags] The system channel flags of the guild + */ + + /** + * Updates the guild with new information - e.g. a new name. + * @param {GuildEditData} data The data to update the guild with + * @param {string} [reason] Reason for editing this guild + * @returns {Promise<Guild>} + * @example + * // Set the guild name and region + * guild.edit({ + * name: 'Discord Guild', + * region: 'london', + * }) + * .then(updated => console.log(`New guild name ${updated} in region ${updated.region}`)) + * .catch(console.error); + */ + edit(data, reason) { + const _data = {}; + if (data.name) _data.name = data.name; + if (data.region) _data.region = data.region; + if (typeof data.verificationLevel !== 'undefined') { + _data.verification_level = + typeof data.verificationLevel === 'number' + ? Number(data.verificationLevel) + : VerificationLevels.indexOf(data.verificationLevel); + } + if (typeof data.afkChannel !== 'undefined') { + _data.afk_channel_id = this.client.channels.resolveID(data.afkChannel); + } + if (typeof data.systemChannel !== 'undefined') { + _data.system_channel_id = this.client.channels.resolveID(data.systemChannel); + } + if (data.afkTimeout) _data.afk_timeout = Number(data.afkTimeout); + if (typeof data.icon !== 'undefined') _data.icon = data.icon; + if (data.owner) _data.owner_id = this.client.users.resolveID(data.owner); + if (data.splash) _data.splash = data.splash; + if (data.banner) _data.banner = data.banner; + if (typeof data.explicitContentFilter !== 'undefined') { + _data.explicit_content_filter = + typeof data.explicitContentFilter === 'number' + ? data.explicitContentFilter + : ExplicitContentFilterLevels.indexOf(data.explicitContentFilter); + } + if (typeof data.defaultMessageNotifications !== 'undefined') { + _data.default_message_notifications = + typeof data.defaultMessageNotifications === 'string' + ? DefaultMessageNotifications.indexOf(data.defaultMessageNotifications) + : data.defaultMessageNotifications; + } + if (typeof data.systemChannelFlags !== 'undefined') { + _data.system_channel_flags = SystemChannelFlags.resolve(data.systemChannelFlags); + } + return this.client.api + .guilds(this.id) + .patch({ data: _data, reason }) + .then(newData => this.client.actions.GuildUpdate.handle(newData).updated); + } + + /** + * Edits the level of the explicit content filter. + * @param {ExplicitContentFilterLevel|number} explicitContentFilter The new level of the explicit content filter + * @param {string} [reason] Reason for changing the level of the guild's explicit content filter + * @returns {Promise<Guild>} + */ + setExplicitContentFilter(explicitContentFilter, reason) { + return this.edit({ explicitContentFilter }, reason); + } + + /* eslint-disable max-len */ + /** + * Edits the setting of the default message notifications of the guild. + * @param {DefaultMessageNotifications|number} defaultMessageNotifications The new setting for the default message notifications + * @param {string} [reason] Reason for changing the setting of the default message notifications + * @returns {Promise<Guild>} + */ + setDefaultMessageNotifications(defaultMessageNotifications, reason) { + return this.edit({ defaultMessageNotifications }, reason); + } + /* eslint-enable max-len */ + + /** + * Edits the flags of the default message notifications of the guild. + * @param {SystemChannelFlagsResolvable} systemChannelFlags The new flags for the default message notifications + * @param {string} [reason] Reason for changing the flags of the default message notifications + * @returns {Promise<Guild>} + */ + setSystemChannelFlags(systemChannelFlags, reason) { + return this.edit({ systemChannelFlags }, reason); + } + + /** + * Edits the name of the guild. + * @param {string} name The new name of the guild + * @param {string} [reason] Reason for changing the guild's name + * @returns {Promise<Guild>} + * @example + * // Edit the guild name + * guild.setName('Discord Guild') + * .then(updated => console.log(`Updated guild name to ${guild}`)) + * .catch(console.error); + */ + setName(name, reason) { + return this.edit({ name }, reason); + } + + /** + * Edits the region of the guild. + * @param {string} region The new region of the guild + * @param {string} [reason] Reason for changing the guild's region + * @returns {Promise<Guild>} + * @example + * // Edit the guild region + * guild.setRegion('london') + * .then(updated => console.log(`Updated guild region to ${updated.region}`)) + * .catch(console.error); + */ + setRegion(region, reason) { + return this.edit({ region }, reason); + } + + /** + * Edits the verification level of the guild. + * @param {VerificationLevel|number} verificationLevel The new verification level of the guild + * @param {string} [reason] Reason for changing the guild's verification level + * @returns {Promise<Guild>} + * @example + * // Edit the guild verification level + * guild.setVerificationLevel(1) + * .then(updated => console.log(`Updated guild verification level to ${guild.verificationLevel}`)) + * .catch(console.error); + */ + setVerificationLevel(verificationLevel, reason) { + return this.edit({ verificationLevel }, reason); + } + + /** + * Edits the AFK channel of the guild. + * @param {ChannelResolvable} afkChannel The new AFK channel + * @param {string} [reason] Reason for changing the guild's AFK channel + * @returns {Promise<Guild>} + * @example + * // Edit the guild AFK channel + * guild.setAFKChannel(channel) + * .then(updated => console.log(`Updated guild AFK channel to ${guild.afkChannel.name}`)) + * .catch(console.error); + */ + setAFKChannel(afkChannel, reason) { + return this.edit({ afkChannel }, reason); + } + + /** + * Edits the system channel of the guild. + * @param {ChannelResolvable} systemChannel The new system channel + * @param {string} [reason] Reason for changing the guild's system channel + * @returns {Promise<Guild>} + * @example + * // Edit the guild system channel + * guild.setSystemChannel(channel) + * .then(updated => console.log(`Updated guild system channel to ${guild.systemChannel.name}`)) + * .catch(console.error); + */ + setSystemChannel(systemChannel, reason) { + return this.edit({ systemChannel }, reason); + } + + /** + * Edits the AFK timeout of the guild. + * @param {number} afkTimeout The time in seconds that a user must be idle to be considered AFK + * @param {string} [reason] Reason for changing the guild's AFK timeout + * @returns {Promise<Guild>} + * @example + * // Edit the guild AFK channel + * guild.setAFKTimeout(60) + * .then(updated => console.log(`Updated guild AFK timeout to ${guild.afkTimeout}`)) + * .catch(console.error); + */ + setAFKTimeout(afkTimeout, reason) { + return this.edit({ afkTimeout }, reason); + } + + /** + * Sets a new guild icon. + * @param {Base64Resolvable|BufferResolvable} icon The new icon of the guild + * @param {string} [reason] Reason for changing the guild's icon + * @returns {Promise<Guild>} + * @example + * // Edit the guild icon + * guild.setIcon('./icon.png') + * .then(updated => console.log('Updated the guild icon')) + * .catch(console.error); + */ + async setIcon(icon, reason) { + return this.edit({ icon: await DataResolver.resolveImage(icon), reason }); + } + + /** + * Sets a new owner of the guild. + * @param {GuildMemberResolvable} owner The new owner of the guild + * @param {string} [reason] Reason for setting the new owner + * @returns {Promise<Guild>} + * @example + * // Edit the guild owner + * guild.setOwner(guild.members.cache.first()) + * .then(updated => console.log(`Updated the guild owner to ${updated.owner.displayName}`)) + * .catch(console.error); + */ + setOwner(owner, reason) { + return this.edit({ owner }, reason); + } + + /** + * Sets a new guild splash screen. + * @param {Base64Resolvable|BufferResolvable} splash The new splash screen of the guild + * @param {string} [reason] Reason for changing the guild's splash screen + * @returns {Promise<Guild>} + * @example + * // Edit the guild splash + * guild.setSplash('./splash.png') + * .then(updated => console.log('Updated the guild splash')) + * .catch(console.error); + */ + async setSplash(splash, reason) { + return this.edit({ splash: await DataResolver.resolveImage(splash), reason }); + } + + /** + * Sets a new guild banner. + * @param {Base64Resolvable|BufferResolvable} banner The new banner of the guild + * @param {string} [reason] Reason for changing the guild's banner + * @returns {Promise<Guild>} + * @example + * guild.setBanner('./banner.png') + * .then(updated => console.log('Updated the guild banner')) + * .catch(console.error); + */ + async setBanner(banner, reason) { + return this.edit({ banner: await DataResolver.resolveImage(banner), reason }); + } + + /** + * The data needed for updating a channel's position. + * @typedef {Object} ChannelPosition + * @property {ChannelResolvable} channel Channel to update + * @property {number} position New position for the channel + */ + + /** + * Batch-updates the guild's channels' positions. + * @param {ChannelPosition[]} channelPositions Channel positions to update + * @returns {Promise<Guild>} + * @example + * guild.setChannelPositions([{ channel: channelID, position: newChannelIndex }]) + * .then(guild => console.log(`Updated channel positions for ${guild}`)) + * .catch(console.error); + */ + setChannelPositions(channelPositions) { + const updatedChannels = channelPositions.map(r => ({ + id: this.client.channels.resolveID(r.channel), + position: r.position, + })); + + return this.client.api + .guilds(this.id) + .channels.patch({ data: updatedChannels }) + .then( + () => + this.client.actions.GuildChannelsPositionUpdate.handle({ + guild_id: this.id, + channels: updatedChannels, + }).guild, + ); + } + + /** + * The data needed for updating a guild role's position + * @typedef {Object} GuildRolePosition + * @property {RoleResolveable} role The ID of the role + * @property {number} position The position to update + */ + + /** + * Batch-updates the guild's role positions + * @param {GuildRolePosition[]} rolePositions Role positions to update + * @returns {Promise<Guild>} + * @example + * guild.setRolePositions([{ role: roleID, position: updatedRoleIndex }]) + * .then(guild => console.log(`Role permissions updated for ${guild}`)) + * .catch(console.error); + */ + setRolePositions(rolePositions) { + // Make sure rolePositions are prepared for API + rolePositions = rolePositions.map(o => ({ + id: this.roles.resolveID(o.role), + position: o.position, + })); + + // Call the API to update role positions + return this.client.api + .guilds(this.id) + .roles.patch({ + data: rolePositions, + }) + .then( + () => + this.client.actions.GuildRolesPositionUpdate.handle({ + guild_id: this.id, + roles: rolePositions, + }).guild, + ); + } + + /** + * Edits the guild's embed. + * @param {GuildEmbedData} embed The embed for the guild + * @param {string} [reason] Reason for changing the guild's embed + * @returns {Promise<Guild>} + */ + setEmbed(embed, reason) { + return this.client.api + .guilds(this.id) + .embed.patch({ + data: { + enabled: embed.enabled, + channel_id: this.channels.resolveID(embed.channel), + }, + reason, + }) + .then(() => this); + } + + /** + * Leaves the guild. + * @returns {Promise<Guild>} + * @example + * // Leave a guild + * guild.leave() + * .then(g => console.log(`Left the guild ${g}`)) + * .catch(console.error); + */ + leave() { + if (this.ownerID === this.client.user.id) return Promise.reject(new Error('GUILD_OWNED')); + return this.client.api + .users('@me') + .guilds(this.id) + .delete() + .then(() => this.client.actions.GuildDelete.handle({ id: this.id }).guild); + } + + /** + * Deletes the guild. + * @returns {Promise<Guild>} + * @example + * // Delete a guild + * guild.delete() + * .then(g => console.log(`Deleted the guild ${g}`)) + * .catch(console.error); + */ + delete() { + return this.client.api + .guilds(this.id) + .delete() + .then(() => this.client.actions.GuildDelete.handle({ id: this.id }).guild); + } + + /** + * Whether this guild equals another guild. It compares all properties, so for most operations + * it is advisable to just compare `guild.id === guild2.id` as it is much faster and is often + * what most users need. + * @param {Guild} guild The guild to compare with + * @returns {boolean} + */ + equals(guild) { + let equal = + guild && + guild instanceof this.constructor && + this.id === guild.id && + this.available === guild.available && + this.splash === guild.splash && + this.region === guild.region && + this.name === guild.name && + this.memberCount === guild.memberCount && + this.large === guild.large && + this.icon === guild.icon && + this.ownerID === guild.ownerID && + this.verificationLevel === guild.verificationLevel && + this.embedEnabled === guild.embedEnabled && + (this.features === guild.features || + (this.features.length === guild.features.length && + this.features.every((feat, i) => feat === guild.features[i]))); + + if (equal) { + if (this.embedChannel) { + if (!guild.embedChannel || this.embedChannel.id !== guild.embedChannel.id) equal = false; + } else if (guild.embedChannel) { + equal = false; + } + } + + return equal; + } + + /** + * When concatenated with a string, this automatically returns the guild's name instead of the Guild object. + * @returns {string} + * @example + * // Logs: Hello from My Guild! + * console.log(`Hello from ${guild}!`); + */ + toString() { + return this.name; + } + + toJSON() { + const json = super.toJSON({ + available: false, + createdTimestamp: true, + nameAcronym: true, + presences: false, + voiceStates: false, + }); + json.iconURL = this.iconURL(); + json.splashURL = this.splashURL(); + json.bannerURL = this.bannerURL(); + return json; + } + + /** + * Creates a collection of this guild's roles, sorted by their position and IDs. + * @returns {Collection<Role>} + * @private + */ + _sortedRoles() { + return Util.discordSort(this.roles.cache); + } + + /** + * Creates a collection of this guild's or a specific category's channels, sorted by their position and IDs. + * @param {GuildChannel} [channel] Category to get the channels of + * @returns {Collection<GuildChannel>} + * @private + */ + _sortedChannels(channel) { + const category = channel.type === ChannelTypes.CATEGORY; + return Util.discordSort( + this.channels.cache.filter( + c => + (['text', 'news', 'store'].includes(channel.type) + ? ['text', 'news', 'store'].includes(c.type) + : c.type === channel.type) && + (category || c.parent === channel.parent), + ), + ); + } +} + +module.exports = Guild; diff --git a/node_modules/discord.js/src/structures/GuildAuditLogs.js b/node_modules/discord.js/src/structures/GuildAuditLogs.js new file mode 100644 index 0000000..b3522b0 --- /dev/null +++ b/node_modules/discord.js/src/structures/GuildAuditLogs.js @@ -0,0 +1,509 @@ +'use strict'; + +const Integration = require('./Integration'); +const Webhook = require('./Webhook'); +const Collection = require('../util/Collection'); +const { PartialTypes } = require('../util/Constants'); +const Snowflake = require('../util/Snowflake'); +const Util = require('../util/Util'); + +/** + * The target type of an entry, e.g. `GUILD`. Here are the available types: + * * GUILD + * * CHANNEL + * * USER + * * ROLE + * * INVITE + * * WEBHOOK + * * EMOJI + * * MESSAGE + * * INTEGRATION + * @typedef {string} AuditLogTargetType + */ + +/** + * Key mirror of all available audit log targets. + * @name GuildAuditLogs.Targets + * @type {AuditLogTargetType} + */ +const Targets = { + ALL: 'ALL', + GUILD: 'GUILD', + CHANNEL: 'CHANNEL', + USER: 'USER', + ROLE: 'ROLE', + INVITE: 'INVITE', + WEBHOOK: 'WEBHOOK', + EMOJI: 'EMOJI', + MESSAGE: 'MESSAGE', + INTEGRATION: 'INTEGRATION', + UNKNOWN: 'UNKNOWN', +}; + +/** + * The action of an entry. Here are the available actions: + * * ALL: null + * * GUILD_UPDATE: 1 + * * CHANNEL_CREATE: 10 + * * CHANNEL_UPDATE: 11 + * * CHANNEL_DELETE: 12 + * * CHANNEL_OVERWRITE_CREATE: 13 + * * CHANNEL_OVERWRITE_UPDATE: 14 + * * CHANNEL_OVERWRITE_DELETE: 15 + * * MEMBER_KICK: 20 + * * MEMBER_PRUNE: 21 + * * MEMBER_BAN_ADD: 22 + * * MEMBER_BAN_REMOVE: 23 + * * MEMBER_UPDATE: 24 + * * MEMBER_ROLE_UPDATE: 25 + * * MEMBER_MOVE: 26 + * * MEMBER_DISCONNECT: 27 + * * BOT_ADD: 28, + * * ROLE_CREATE: 30 + * * ROLE_UPDATE: 31 + * * ROLE_DELETE: 32 + * * INVITE_CREATE: 40 + * * INVITE_UPDATE: 41 + * * INVITE_DELETE: 42 + * * WEBHOOK_CREATE: 50 + * * WEBHOOK_UPDATE: 51 + * * WEBHOOK_DELETE: 52 + * * EMOJI_CREATE: 60 + * * EMOJI_UPDATE: 61 + * * EMOJI_DELETE: 62 + * * MESSAGE_DELETE: 72 + * * MESSAGE_BULK_DELETE: 73 + * * MESSAGE_PIN: 74 + * * MESSAGE_UNPIN: 75 + * * INTEGRATION_CREATE: 80 + * * INTEGRATION_UPDATE: 81 + * * INTEGRATION_DELETE: 82 + * @typedef {?number|string} AuditLogAction + */ + +/** + * All available actions keyed under their names to their numeric values. + * @name GuildAuditLogs.Actions + * @type {AuditLogAction} + */ +const Actions = { + ALL: null, + GUILD_UPDATE: 1, + CHANNEL_CREATE: 10, + CHANNEL_UPDATE: 11, + CHANNEL_DELETE: 12, + CHANNEL_OVERWRITE_CREATE: 13, + CHANNEL_OVERWRITE_UPDATE: 14, + CHANNEL_OVERWRITE_DELETE: 15, + MEMBER_KICK: 20, + MEMBER_PRUNE: 21, + MEMBER_BAN_ADD: 22, + MEMBER_BAN_REMOVE: 23, + MEMBER_UPDATE: 24, + MEMBER_ROLE_UPDATE: 25, + MEMBER_MOVE: 26, + MEMBER_DISCONNECT: 27, + BOT_ADD: 28, + ROLE_CREATE: 30, + ROLE_UPDATE: 31, + ROLE_DELETE: 32, + INVITE_CREATE: 40, + INVITE_UPDATE: 41, + INVITE_DELETE: 42, + WEBHOOK_CREATE: 50, + WEBHOOK_UPDATE: 51, + WEBHOOK_DELETE: 52, + EMOJI_CREATE: 60, + EMOJI_UPDATE: 61, + EMOJI_DELETE: 62, + MESSAGE_DELETE: 72, + MESSAGE_BULK_DELETE: 73, + MESSAGE_PIN: 74, + MESSAGE_UNPIN: 75, + INTEGRATION_CREATE: 80, + INTEGRATION_UPDATE: 81, + INTEGRATION_DELETE: 82, +}; + +/** + * Audit logs entries are held in this class. + */ +class GuildAuditLogs { + constructor(guild, data) { + if (data.users) for (const user of data.users) guild.client.users.add(user); + /** + * Cached webhooks + * @type {Collection<Snowflake, Webhook>} + * @private + */ + this.webhooks = new Collection(); + if (data.webhooks) { + for (const hook of data.webhooks) { + this.webhooks.set(hook.id, new Webhook(guild.client, hook)); + } + } + + /** + * Cached integrations + * @type {Collection<Snowflake, Integration>} + * @private + */ + this.integrations = new Collection(); + if (data.integrations) { + for (const integration of data.integrations) { + this.integrations.set(integration.id, new Integration(guild.client, integration, guild)); + } + } + + /** + * The entries for this guild's audit logs + * @type {Collection<Snowflake, GuildAuditLogsEntry>} + */ + this.entries = new Collection(); + for (const item of data.audit_log_entries) { + const entry = new GuildAuditLogsEntry(this, guild, item); + this.entries.set(entry.id, entry); + } + } + + /** + * Handles possible promises for entry targets. + * @returns {Promise<GuildAuditLogs>} + */ + static build(...args) { + const logs = new GuildAuditLogs(...args); + return Promise.all(logs.entries.map(e => e.target)).then(() => logs); + } + + /** + * The target of an entry. It can be one of: + * * A guild + * * A user + * * A role + * * An emoji + * * An invite + * * A webhook + * * An integration + * * An object with an id key if target was deleted + * * An object where the keys represent either the new value or the old value + * @typedef {?Object|Guild|User|Role|GuildEmoji|Invite|Webhook|Integration} AuditLogEntryTarget + */ + + /** + * Finds the target type from the entry action. + * @param {AuditLogAction} target The action target + * @returns {AuditLogTargetType} + */ + static targetType(target) { + if (target < 10) return Targets.GUILD; + if (target < 20) return Targets.CHANNEL; + if (target < 30) return Targets.USER; + if (target < 40) return Targets.ROLE; + if (target < 50) return Targets.INVITE; + if (target < 60) return Targets.WEBHOOK; + if (target < 70) return Targets.EMOJI; + if (target < 80) return Targets.MESSAGE; + if (target < 90) return Targets.INTEGRATION; + return Targets.UNKNOWN; + } + + /** + * The action type of an entry, e.g. `CREATE`. Here are the available types: + * * CREATE + * * DELETE + * * UPDATE + * * ALL + * @typedef {string} AuditLogActionType + */ + + /** + * Finds the action type from the entry action. + * @param {AuditLogAction} action The action target + * @returns {AuditLogActionType} + */ + static actionType(action) { + if ( + [ + Actions.CHANNEL_CREATE, + Actions.CHANNEL_OVERWRITE_CREATE, + Actions.MEMBER_BAN_REMOVE, + Actions.BOT_ADD, + Actions.ROLE_CREATE, + Actions.INVITE_CREATE, + Actions.WEBHOOK_CREATE, + Actions.EMOJI_CREATE, + Actions.MESSAGE_PIN, + Actions.INTEGRATION_CREATE, + ].includes(action) + ) { + return 'CREATE'; + } + + if ( + [ + Actions.CHANNEL_DELETE, + Actions.CHANNEL_OVERWRITE_DELETE, + Actions.MEMBER_KICK, + Actions.MEMBER_PRUNE, + Actions.MEMBER_BAN_ADD, + Actions.MEMBER_DISCONNECT, + Actions.ROLE_DELETE, + Actions.INVITE_DELETE, + Actions.WEBHOOK_DELETE, + Actions.EMOJI_DELETE, + Actions.MESSAGE_DELETE, + Actions.MESSAGE_BULK_DELETE, + Actions.MESSAGE_UNPIN, + Actions.INTEGRATION_DELETE, + ].includes(action) + ) { + return 'DELETE'; + } + + if ( + [ + Actions.GUILD_UPDATE, + Actions.CHANNEL_UPDATE, + Actions.CHANNEL_OVERWRITE_UPDATE, + Actions.MEMBER_UPDATE, + Actions.MEMBER_ROLE_UPDATE, + Actions.MEMBER_MOVE, + Actions.ROLE_UPDATE, + Actions.INVITE_UPDATE, + Actions.WEBHOOK_UPDATE, + Actions.EMOJI_UPDATE, + Actions.INTEGRATION_UPDATE, + ].includes(action) + ) { + return 'UPDATE'; + } + + return 'ALL'; + } + + toJSON() { + return Util.flatten(this); + } +} + +/** + * Audit logs entry. + */ +class GuildAuditLogsEntry { + constructor(logs, guild, data) { + const targetType = GuildAuditLogs.targetType(data.action_type); + /** + * The target type of this entry + * @type {AuditLogTargetType} + */ + this.targetType = targetType; + + /** + * The action type of this entry + * @type {AuditLogActionType} + */ + this.actionType = GuildAuditLogs.actionType(data.action_type); + + /** + * Specific action type of this entry in its string presentation + * @type {AuditLogAction} + */ + this.action = Object.keys(Actions).find(k => Actions[k] === data.action_type); + + /** + * The reason of this entry + * @type {?string} + */ + this.reason = data.reason || null; + + /** + * The user that executed this entry + * @type {User} + */ + this.executor = guild.client.options.partials.includes(PartialTypes.USER) + ? guild.client.users.add({ id: data.user_id }) + : guild.client.users.cache.get(data.user_id); + + /** + * An entry in the audit log representing a specific change. + * @typedef {object} AuditLogChange + * @property {string} key The property that was changed, e.g. `nick` for nickname changes + * @property {*} [old] The old value of the change, e.g. for nicknames, the old nickname + * @property {*} [new] The new value of the change, e.g. for nicknames, the new nickname + */ + + /** + * Specific property changes + * @type {AuditLogChange[]} + */ + this.changes = data.changes ? data.changes.map(c => ({ key: c.key, old: c.old_value, new: c.new_value })) : null; + + /** + * The ID of this entry + * @type {Snowflake} + */ + this.id = data.id; + + /** + * Any extra data from the entry + * @type {?Object|Role|GuildMember} + */ + this.extra = null; + switch (data.action_type) { + case Actions.MEMBER_PRUNE: + this.extra = { + removed: Number(data.options.members_removed), + days: Number(data.options.delete_member_days), + }; + break; + + case Actions.MEMBER_MOVE: + case Actions.MESSAGE_DELETE: + case Actions.MESSAGE_BULK_DELETE: + this.extra = { + channel: guild.channels.cache.get(data.options.channel_id) || { id: data.options.channel_id }, + count: Number(data.options.count), + }; + break; + + case Actions.MESSAGE_PIN: + case Actions.MESSAGE_UNPIN: + this.extra = { + channel: guild.client.channels.cache.get(data.options.channel_id) || { id: data.options.channel_id }, + messageID: data.options.message_id, + }; + break; + + case Actions.MEMBER_DISCONNECT: + this.extra = { + count: Number(data.options.count), + }; + break; + + case Actions.CHANNEL_OVERWRITE_CREATE: + case Actions.CHANNEL_OVERWRITE_UPDATE: + case Actions.CHANNEL_OVERWRITE_DELETE: + switch (data.options.type) { + case 'member': + this.extra = guild.members.cache.get(data.options.id) || { id: data.options.id, type: 'member' }; + break; + + case 'role': + this.extra = guild.roles.cache.get(data.options.id) || { + id: data.options.id, + name: data.options.role_name, + type: 'role', + }; + break; + + default: + break; + } + break; + + default: + break; + } + + /** + * The target of this entry + * @type {?AuditLogEntryTarget} + */ + this.target = null; + if (targetType === Targets.UNKNOWN) { + this.target = this.changes.reduce((o, c) => { + o[c.key] = c.new || c.old; + return o; + }, {}); + this.target.id = data.target_id; + // MEMBER_DISCONNECT and similar types do not provide a target_id. + } else if (targetType === Targets.USER && data.target_id) { + this.target = guild.client.options.partials.includes(PartialTypes.USER) + ? guild.client.users.add({ id: data.target_id }) + : guild.client.users.cache.get(data.target_id); + } else if (targetType === Targets.GUILD) { + this.target = guild.client.guilds.cache.get(data.target_id); + } else if (targetType === Targets.WEBHOOK) { + this.target = + logs.webhooks.get(data.target_id) || + new Webhook( + guild.client, + this.changes.reduce( + (o, c) => { + o[c.key] = c.new || c.old; + return o; + }, + { + id: data.target_id, + guild_id: guild.id, + }, + ), + ); + } else if (targetType === Targets.INVITE) { + this.target = guild.members.fetch(guild.client.user.id).then(me => { + if (me.permissions.has('MANAGE_GUILD')) { + const change = this.changes.find(c => c.key === 'code'); + return guild.fetchInvites().then(invites => { + this.target = invites.find(i => i.code === (change.new || change.old)); + }); + } else { + this.target = this.changes.reduce((o, c) => { + o[c.key] = c.new || c.old; + return o; + }, {}); + return this.target; + } + }); + } else if (targetType === Targets.MESSAGE) { + // Discord sends a channel id for the MESSAGE_BULK_DELETE action type. + this.target = + data.action_type === Actions.MESSAGE_BULK_DELETE + ? guild.channels.cache.get(data.target_id) || { id: data.target_id } + : guild.client.users.cache.get(data.target_id); + } else if (targetType === Targets.INTEGRATION) { + this.target = + logs.integrations.get(data.target_id) || + new Integration( + guild.client, + this.changes.reduce( + (o, c) => { + o[c.key] = c.new || c.old; + return o; + }, + { id: data.target_id }, + ), + guild, + ); + } else if (data.target_id) { + this.target = guild[`${targetType.toLowerCase()}s`].cache.get(data.target_id) || { id: data.target_id }; + } + } + + /** + * The timestamp this entry was created at + * @type {number} + * @readonly + */ + get createdTimestamp() { + return Snowflake.deconstruct(this.id).timestamp; + } + + /** + * The time this entry was created at + * @type {Date} + * @readonly + */ + get createdAt() { + return new Date(this.createdTimestamp); + } + + toJSON() { + return Util.flatten(this, { createdTimestamp: true }); + } +} + +GuildAuditLogs.Actions = Actions; +GuildAuditLogs.Targets = Targets; +GuildAuditLogs.Entry = GuildAuditLogsEntry; + +module.exports = GuildAuditLogs; diff --git a/node_modules/discord.js/src/structures/GuildChannel.js b/node_modules/discord.js/src/structures/GuildChannel.js new file mode 100644 index 0000000..2e80eca --- /dev/null +++ b/node_modules/discord.js/src/structures/GuildChannel.js @@ -0,0 +1,609 @@ +'use strict'; + +const Channel = require('./Channel'); +const Invite = require('./Invite'); +const PermissionOverwrites = require('./PermissionOverwrites'); +const Role = require('./Role'); +const { Error, TypeError } = require('../errors'); +const Collection = require('../util/Collection'); +const Permissions = require('../util/Permissions'); +const Util = require('../util/Util'); + +/** + * Represents a guild channel from any of the following: + * - {@link TextChannel} + * - {@link VoiceChannel} + * - {@link CategoryChannel} + * - {@link NewsChannel} + * - {@link StoreChannel} + * @extends {Channel} + */ +class GuildChannel extends Channel { + /** + * @param {Guild} guild The guild the guild channel is part of + * @param {Object} data The data for the guild channel + */ + constructor(guild, data) { + super(guild.client, data); + + /** + * The guild the channel is in + * @type {Guild} + */ + this.guild = guild; + } + + _patch(data) { + super._patch(data); + + /** + * The name of the guild channel + * @type {string} + */ + this.name = data.name; + + /** + * The raw position of the channel from discord + * @type {number} + */ + this.rawPosition = data.position; + + /** + * The ID of the category parent of this channel + * @type {?Snowflake} + */ + this.parentID = data.parent_id; + + /** + * A map of permission overwrites in this channel for roles and users + * @type {Collection<Snowflake, PermissionOverwrites>} + */ + this.permissionOverwrites = new Collection(); + if (data.permission_overwrites) { + for (const overwrite of data.permission_overwrites) { + this.permissionOverwrites.set(overwrite.id, new PermissionOverwrites(this, overwrite)); + } + } + } + + /** + * The category parent of this channel + * @type {?CategoryChannel} + * @readonly + */ + get parent() { + return this.guild.channels.cache.get(this.parentID) || null; + } + + /** + * If the permissionOverwrites match the parent channel, null if no parent + * @type {?boolean} + * @readonly + */ + get permissionsLocked() { + if (!this.parent) return null; + if (this.permissionOverwrites.size !== this.parent.permissionOverwrites.size) return false; + return this.permissionOverwrites.every((value, key) => { + const testVal = this.parent.permissionOverwrites.get(key); + return ( + testVal !== undefined && + testVal.deny.bitfield === value.deny.bitfield && + testVal.allow.bitfield === value.allow.bitfield + ); + }); + } + + /** + * The position of the channel + * @type {number} + * @readonly + */ + get position() { + const sorted = this.guild._sortedChannels(this); + return sorted.array().indexOf(sorted.get(this.id)); + } + + /** + * Gets the overall set of permissions for a member or role in this channel, taking into account channel overwrites. + * @param {GuildMemberResolvable|RoleResolvable} memberOrRole The member or role to obtain the overall permissions for + * @returns {?Readonly<Permissions>} + */ + permissionsFor(memberOrRole) { + const member = this.guild.members.resolve(memberOrRole); + if (member) return this.memberPermissions(member); + const role = this.guild.roles.resolve(memberOrRole); + if (role) return this.rolePermissions(role); + return null; + } + + overwritesFor(member, verified = false, roles = null) { + if (!verified) member = this.guild.members.resolve(member); + if (!member) return []; + + roles = roles || member.roles.cache; + const roleOverwrites = []; + let memberOverwrites; + let everyoneOverwrites; + + for (const overwrite of this.permissionOverwrites.values()) { + if (overwrite.id === this.guild.id) { + everyoneOverwrites = overwrite; + } else if (roles.has(overwrite.id)) { + roleOverwrites.push(overwrite); + } else if (overwrite.id === member.id) { + memberOverwrites = overwrite; + } + } + + return { + everyone: everyoneOverwrites, + roles: roleOverwrites, + member: memberOverwrites, + }; + } + + /** + * Gets the overall set of permissions for a member in this channel, taking into account channel overwrites. + * @param {GuildMember} member The member to obtain the overall permissions for + * @returns {Readonly<Permissions>} + * @private + */ + memberPermissions(member) { + if (member.id === this.guild.ownerID) return new Permissions(Permissions.ALL).freeze(); + + const roles = member.roles.cache; + const permissions = new Permissions(roles.map(role => role.permissions)); + + if (permissions.has(Permissions.FLAGS.ADMINISTRATOR)) return new Permissions(Permissions.ALL).freeze(); + + const overwrites = this.overwritesFor(member, true, roles); + + return permissions + .remove(overwrites.everyone ? overwrites.everyone.deny : 0) + .add(overwrites.everyone ? overwrites.everyone.allow : 0) + .remove(overwrites.roles.length > 0 ? overwrites.roles.map(role => role.deny) : 0) + .add(overwrites.roles.length > 0 ? overwrites.roles.map(role => role.allow) : 0) + .remove(overwrites.member ? overwrites.member.deny : 0) + .add(overwrites.member ? overwrites.member.allow : 0) + .freeze(); + } + + /** + * Gets the overall set of permissions for a role in this channel, taking into account channel overwrites. + * @param {Role} role The role to obtain the overall permissions for + * @returns {Readonly<Permissions>} + * @private + */ + rolePermissions(role) { + if (role.permissions.has(Permissions.FLAGS.ADMINISTRATOR)) return new Permissions(Permissions.ALL).freeze(); + + const everyoneOverwrites = this.permissionOverwrites.get(this.guild.id); + const roleOverwrites = this.permissionOverwrites.get(role.id); + + return role.permissions + .remove(everyoneOverwrites ? everyoneOverwrites.deny : 0) + .add(everyoneOverwrites ? everyoneOverwrites.allow : 0) + .remove(roleOverwrites ? roleOverwrites.deny : 0) + .add(roleOverwrites ? roleOverwrites.allow : 0) + .freeze(); + } + + /** + * Replaces the permission overwrites in this channel. + * @param {OverwriteResolvable[]|Collection<Snowflake, OverwriteResolvable>} overwrites + * Permission overwrites the channel gets updated with + * @param {string} [reason] Reason for updating the channel overwrites + * @returns {Promise<GuildChannel>} + * @example + * channel.overwritePermissions([ + * { + * id: message.author.id, + * deny: ['VIEW_CHANNEL'], + * }, + * ], 'Needed to change permissions'); + */ + overwritePermissions(overwrites, reason) { + if (!Array.isArray(overwrites) && !(overwrites instanceof Collection)) { + return Promise.reject( + new TypeError('INVALID_TYPE', 'overwrites', 'Array or Collection of Permission Overwrites', true), + ); + } + return this.edit({ permissionOverwrites: overwrites, reason }).then(() => this); + } + + /** + * Updates Overwrites for a user or role in this channel. (creates if non-existent) + * @param {RoleResolvable|UserResolvable} userOrRole The user or role to update + * @param {PermissionOverwriteOptions} options The options for the update + * @param {string} [reason] Reason for creating/editing this overwrite + * @returns {Promise<GuildChannel>} + * @example + * // Update or Create permission overwrites for a message author + * message.channel.updateOverwrite(message.author, { + * SEND_MESSAGES: false + * }) + * .then(channel => console.log(channel.permissionOverwrites.get(message.author.id))) + * .catch(console.error); + */ + updateOverwrite(userOrRole, options, reason) { + userOrRole = this.guild.roles.resolve(userOrRole) || this.client.users.resolve(userOrRole); + if (!userOrRole) return Promise.reject(new TypeError('INVALID_TYPE', 'parameter', 'User nor a Role', true)); + + const existing = this.permissionOverwrites.get(userOrRole.id); + if (existing) return existing.update(options, reason).then(() => this); + return this.createOverwrite(userOrRole, options, reason); + } + + /** + * Overwrites the permissions for a user or role in this channel. (replaces if existent) + * @param {RoleResolvable|UserResolvable} userOrRole The user or role to update + * @param {PermissionOverwriteOptions} options The options for the update + * @param {string} [reason] Reason for creating/editing this overwrite + * @returns {Promise<GuildChannel>} + * @example + * // Create or Replace permissions overwrites for a message author + * message.channel.createOverwrite(message.author, { + * SEND_MESSAGES: false + * }) + * .then(channel => console.log(channel.permissionOverwrites.get(message.author.id))) + * .catch(console.error); + */ + createOverwrite(userOrRole, options, reason) { + userOrRole = this.guild.roles.resolve(userOrRole) || this.client.users.resolve(userOrRole); + if (!userOrRole) return Promise.reject(new TypeError('INVALID_TYPE', 'parameter', 'User nor a Role', true)); + + const type = userOrRole instanceof Role ? 'role' : 'member'; + const { allow, deny } = PermissionOverwrites.resolveOverwriteOptions(options); + + return this.client.api + .channels(this.id) + .permissions[userOrRole.id].put({ + data: { id: userOrRole.id, type, allow: allow.bitfield, deny: deny.bitfield }, + reason, + }) + .then(() => this); + } + + /** + * Locks in the permission overwrites from the parent channel. + * @returns {Promise<GuildChannel>} + */ + lockPermissions() { + if (!this.parent) return Promise.reject(new Error('GUILD_CHANNEL_ORPHAN')); + const permissionOverwrites = this.parent.permissionOverwrites.map(overwrite => overwrite.toJSON()); + return this.edit({ permissionOverwrites }); + } + + /** + * A collection of members that can see this channel, mapped by their ID + * @type {Collection<Snowflake, GuildMember>} + * @readonly + */ + get members() { + const members = new Collection(); + for (const member of this.guild.members.cache.values()) { + if (this.permissionsFor(member).has('VIEW_CHANNEL', false)) { + members.set(member.id, member); + } + } + return members; + } + + /** + * The data for a guild channel. + * @typedef {Object} ChannelData + * @property {string} [name] The name of the channel + * @property {number} [position] The position of the channel + * @property {string} [topic] The topic of the text channel + * @property {boolean} [nsfw] Whether the channel is NSFW + * @property {number} [bitrate] The bitrate of the voice channel + * @property {number} [userLimit] The user limit of the voice channel + * @property {Snowflake} [parentID] The parent ID of the channel + * @property {boolean} [lockPermissions] + * Lock the permissions of the channel to what the parent's permissions are + * @property {OverwriteResolvable[]|Collection<Snowflake, OverwriteResolvable>} [permissionOverwrites] + * Permission overwrites for the channel + * @property {number} [rateLimitPerUser] The ratelimit per user for the channel in seconds + */ + + /** + * Edits the channel. + * @param {ChannelData} data The new data for the channel + * @param {string} [reason] Reason for editing this channel + * @returns {Promise<GuildChannel>} + * @example + * // Edit a channel + * channel.edit({ name: 'new-channel' }) + * .then(console.log) + * .catch(console.error); + */ + async edit(data, reason) { + if (typeof data.position !== 'undefined') { + await Util.setPosition( + this, + data.position, + false, + this.guild._sortedChannels(this), + this.client.api.guilds(this.guild.id).channels, + reason, + ).then(updatedChannels => { + this.client.actions.GuildChannelsPositionUpdate.handle({ + guild_id: this.guild.id, + channels: updatedChannels, + }); + }); + } + + const permission_overwrites = + data.permissionOverwrites && data.permissionOverwrites.map(o => PermissionOverwrites.resolve(o, this.guild)); + + const newData = await this.client.api.channels(this.id).patch({ + data: { + name: (data.name || this.name).trim(), + topic: data.topic, + nsfw: data.nsfw, + bitrate: data.bitrate || this.bitrate, + user_limit: typeof data.userLimit !== 'undefined' ? data.userLimit : this.userLimit, + parent_id: data.parentID, + lock_permissions: data.lockPermissions, + rate_limit_per_user: data.rateLimitPerUser, + permission_overwrites, + }, + reason, + }); + + const clone = this._clone(); + clone._patch(newData); + return clone; + } + + /** + * Sets a new name for the guild channel. + * @param {string} name The new name for the guild channel + * @param {string} [reason] Reason for changing the guild channel's name + * @returns {Promise<GuildChannel>} + * @example + * // Set a new channel name + * channel.setName('not_general') + * .then(newChannel => console.log(`Channel's new name is ${newChannel.name}`)) + * .catch(console.error); + */ + setName(name, reason) { + return this.edit({ name }, reason); + } + + /** + * Sets the category parent of this channel. + * @param {?CategoryChannel|Snowflake} channel Parent channel + * @param {Object} [options={}] Options to pass + * @param {boolean} [options.lockPermissions=true] Lock the permissions to what the parent's permissions are + * @param {string} [options.reason] Reason for modifying the parent of this channel + * @returns {Promise<GuildChannel>} + * @example + * // Add a parent to a channel + * message.channel.setParent('355908108431917066', { lockPermissions: false }) + * .then(channel => console.log(`New parent of ${message.channel.name}: ${channel.name}`)) + * .catch(console.error); + */ + setParent(channel, { lockPermissions = true, reason } = {}) { + return this.edit( + { + // eslint-disable-next-line no-prototype-builtins + parentID: channel !== null ? (channel.hasOwnProperty('id') ? channel.id : channel) : null, + lockPermissions, + }, + reason, + ); + } + + /** + * Sets a new topic for the guild channel. + * @param {string} topic The new topic for the guild channel + * @param {string} [reason] Reason for changing the guild channel's topic + * @returns {Promise<GuildChannel>} + * @example + * // Set a new channel topic + * channel.setTopic('needs more rate limiting') + * .then(newChannel => console.log(`Channel's new topic is ${newChannel.topic}`)) + * .catch(console.error); + */ + setTopic(topic, reason) { + return this.edit({ topic }, reason); + } + + /** + * Sets a new position for the guild channel. + * @param {number} position The new position for the guild channel + * @param {Object} [options] Options for setting position + * @param {boolean} [options.relative=false] Change the position relative to its current value + * @param {string} [options.reason] Reason for changing the position + * @returns {Promise<GuildChannel>} + * @example + * // Set a new channel position + * channel.setPosition(2) + * .then(newChannel => console.log(`Channel's new position is ${newChannel.position}`)) + * .catch(console.error); + */ + setPosition(position, { relative, reason } = {}) { + return Util.setPosition( + this, + position, + relative, + this.guild._sortedChannels(this), + this.client.api.guilds(this.guild.id).channels, + reason, + ).then(updatedChannels => { + this.client.actions.GuildChannelsPositionUpdate.handle({ + guild_id: this.guild.id, + channels: updatedChannels, + }); + return this; + }); + } + + /** + * Creates an invite to this guild channel. + * @param {Object} [options={}] Options for the invite + * @param {boolean} [options.temporary=false] Whether members that joined via the invite should be automatically + * kicked after 24 hours if they have not yet received a role + * @param {number} [options.maxAge=86400] How long the invite should last (in seconds, 0 for forever) + * @param {number} [options.maxUses=0] Maximum number of uses + * @param {boolean} [options.unique=false] Create a unique invite, or use an existing one with similar settings + * @param {string} [options.reason] Reason for creating this + * @returns {Promise<Invite>} + * @example + * // Create an invite to a channel + * channel.createInvite() + * .then(invite => console.log(`Created an invite with a code of ${invite.code}`)) + * .catch(console.error); + */ + createInvite({ temporary = false, maxAge = 86400, maxUses = 0, unique, reason } = {}) { + return this.client.api + .channels(this.id) + .invites.post({ + data: { + temporary, + max_age: maxAge, + max_uses: maxUses, + unique, + }, + reason, + }) + .then(invite => new Invite(this.client, invite)); + } + + /** + * Fetches a collection of invites to this guild channel. + * Resolves with a collection mapping invites by their codes. + * @returns {Promise<Collection<string, Invite>>} + */ + async fetchInvites() { + const inviteItems = await this.client.api.channels(this.id).invites.get(); + const invites = new Collection(); + for (const inviteItem of inviteItems) { + const invite = new Invite(this.client, inviteItem); + invites.set(invite.code, invite); + } + return invites; + } + + /* eslint-disable max-len */ + /** + * Clones this channel. + * @param {Object} [options] The options + * @param {string} [options.name=this.name] Name of the new channel + * @param {OverwriteResolvable[]|Collection<Snowflake, OverwriteResolvable>} [options.permissionOverwrites=this.permissionOverwrites] + * Permission overwrites of the new channel + * @param {string} [options.type=this.type] Type of the new channel + * @param {string} [options.topic=this.topic] Topic of the new channel (only text) + * @param {boolean} [options.nsfw=this.nsfw] Whether the new channel is nsfw (only text) + * @param {number} [options.bitrate=this.bitrate] Bitrate of the new channel in bits (only voice) + * @param {number} [options.userLimit=this.userLimit] Maximum amount of users allowed in the new channel (only voice) + * @param {number} [options.rateLimitPerUser=ThisType.rateLimitPerUser] Ratelimit per user for the new channel (only text) + * @param {ChannelResolvable} [options.parent=this.parent] Parent of the new channel + * @param {string} [options.reason] Reason for cloning this channel + * @returns {Promise<GuildChannel>} + */ + clone(options = {}) { + Util.mergeDefault( + { + name: this.name, + permissionOverwrites: this.permissionOverwrites, + topic: this.topic, + type: this.type, + nsfw: this.nsfw, + parent: this.parent, + bitrate: this.bitrate, + userLimit: this.userLimit, + rateLimitPerUser: this.rateLimitPerUser, + reason: null, + }, + options, + ); + return this.guild.channels.create(options.name, options); + } + /* eslint-enable max-len */ + + /** + * Checks if this channel has the same type, topic, position, name, overwrites and ID as another channel. + * In most cases, a simple `channel.id === channel2.id` will do, and is much faster too. + * @param {GuildChannel} channel Channel to compare with + * @returns {boolean} + */ + equals(channel) { + let equal = + channel && + this.id === channel.id && + this.type === channel.type && + this.topic === channel.topic && + this.position === channel.position && + this.name === channel.name; + + if (equal) { + if (this.permissionOverwrites && channel.permissionOverwrites) { + equal = this.permissionOverwrites.equals(channel.permissionOverwrites); + } else { + equal = !this.permissionOverwrites && !channel.permissionOverwrites; + } + } + + return equal; + } + + /** + * Whether the channel is deletable by the client user + * @type {boolean} + * @readonly + */ + get deletable() { + return this.permissionsFor(this.client.user).has(Permissions.FLAGS.MANAGE_CHANNELS, false); + } + + /** + * Whether the channel is manageable by the client user + * @type {boolean} + * @readonly + */ + get manageable() { + if (this.client.user.id === this.guild.ownerID) return true; + if (this.type === 'voice') { + if (!this.permissionsFor(this.client.user).has(Permissions.FLAGS.CONNECT, false)) { + return false; + } + } else if (!this.viewable) { + return false; + } + return this.permissionsFor(this.client.user).has(Permissions.FLAGS.MANAGE_CHANNELS, false); + } + + /** + * Whether the channel is viewable by the client user + * @type {boolean} + * @readonly + */ + get viewable() { + if (this.client.user.id === this.guild.ownerID) return true; + const permissions = this.permissionsFor(this.client.user); + if (!permissions) return false; + return permissions.has(Permissions.FLAGS.VIEW_CHANNEL, false); + } + + /** + * Deletes this channel. + * @param {string} [reason] Reason for deleting this channel + * @returns {Promise<GuildChannel>} + * @example + * // Delete the channel + * channel.delete('making room for new channels') + * .then(console.log) + * .catch(console.error); + */ + delete(reason) { + return this.client.api + .channels(this.id) + .delete({ reason }) + .then(() => this); + } +} + +module.exports = GuildChannel; diff --git a/node_modules/discord.js/src/structures/GuildEmoji.js b/node_modules/discord.js/src/structures/GuildEmoji.js new file mode 100644 index 0000000..d4189e9 --- /dev/null +++ b/node_modules/discord.js/src/structures/GuildEmoji.js @@ -0,0 +1,159 @@ +'use strict'; + +const BaseGuildEmoji = require('./BaseGuildEmoji'); +const { Error } = require('../errors'); +const GuildEmojiRoleManager = require('../managers/GuildEmojiRoleManager'); +const Permissions = require('../util/Permissions'); + +/** + * Represents a custom emoji. + * @extends {BaseGuildEmoji} + */ +class GuildEmoji extends BaseGuildEmoji { + /** + * @name GuildEmoji + * @kind constructor + * @memberof GuildEmoji + * @param {Client} client The instantiating client + * @param {Object} data The data for the guild emoji + * @param {Guild} guild The guild the guild emoji is part of + */ + + /** + * The guild this emoji is part of + * @type {Guild} + * @name GuildEmoji#guild + */ + + _clone() { + const clone = super._clone(); + clone._roles = this._roles.slice(); + return clone; + } + + /** + * Whether the emoji is deletable by the client user + * @type {boolean} + * @readonly + */ + get deletable() { + if (!this.guild.me) throw new Error('GUILD_UNCACHED_ME'); + return !this.managed && this.guild.me.hasPermission(Permissions.FLAGS.MANAGE_EMOJIS); + } + + /** + * A manager for roles this emoji is active for. + * @type {GuildEmojiRoleManager} + * @readonly + */ + get roles() { + return new GuildEmojiRoleManager(this); + } + + /** + * Fetches the author for this emoji + * @returns {Promise<User>} + */ + fetchAuthor() { + if (this.managed) { + return Promise.reject(new Error('EMOJI_MANAGED')); + } else { + if (!this.guild.me) return Promise.reject(new Error('GUILD_UNCACHED_ME')); + if (!this.guild.me.permissions.has(Permissions.FLAGS.MANAGE_EMOJIS)) { + return Promise.reject(new Error('MISSING_MANAGE_EMOJIS_PERMISSION', this.guild)); + } + } + return this.client.api + .guilds(this.guild.id) + .emojis(this.id) + .get() + .then(emoji => this.client.users.add(emoji.user)); + } + + /** + * Data for editing an emoji. + * @typedef {Object} GuildEmojiEditData + * @property {string} [name] The name of the emoji + * @property {Collection<Snowflake, Role>|RoleResolvable[]} [roles] Roles to restrict emoji to + */ + + /** + * Edits the emoji. + * @param {GuildEmojiEditData} data The new data for the emoji + * @param {string} [reason] Reason for editing this emoji + * @returns {Promise<GuildEmoji>} + * @example + * // Edit an emoji + * emoji.edit({ name: 'newemoji' }) + * .then(e => console.log(`Edited emoji ${e}`)) + * .catch(console.error); + */ + edit(data, reason) { + const roles = data.roles ? data.roles.map(r => r.id || r) : undefined; + return this.client.api + .guilds(this.guild.id) + .emojis(this.id) + .patch({ + data: { + name: data.name, + roles, + }, + reason, + }) + .then(newData => { + const clone = this._clone(); + clone._patch(newData); + return clone; + }); + } + + /** + * Sets the name of the emoji. + * @param {string} name The new name for the emoji + * @param {string} [reason] Reason for changing the emoji's name + * @returns {Promise<GuildEmoji>} + */ + setName(name, reason) { + return this.edit({ name }, reason); + } + + /** + * Deletes the emoji. + * @param {string} [reason] Reason for deleting the emoji + * @returns {Promise<GuildEmoji>} + */ + delete(reason) { + return this.client.api + .guilds(this.guild.id) + .emojis(this.id) + .delete({ reason }) + .then(() => this); + } + + /** + * Whether this emoji is the same as another one. + * @param {GuildEmoji|Object} other The emoji to compare it to + * @returns {boolean} Whether the emoji is equal to the given emoji or not + */ + equals(other) { + if (other instanceof GuildEmoji) { + return ( + other.id === this.id && + other.name === this.name && + other.managed === this.managed && + other.requiresColons === this.requiresColons && + other.roles.cache.size === this.roles.cache.size && + other.roles.cache.every(role => this.roles.cache.has(role.id)) + ); + } else { + return ( + other.id === this.id && + other.name === this.name && + other.roles.length === this.roles.cache.size && + other.roles.every(role => this.roles.cache.has(role)) + ); + } + } +} + +module.exports = GuildEmoji; diff --git a/node_modules/discord.js/src/structures/GuildMember.js b/node_modules/discord.js/src/structures/GuildMember.js new file mode 100644 index 0000000..f949517 --- /dev/null +++ b/node_modules/discord.js/src/structures/GuildMember.js @@ -0,0 +1,410 @@ +'use strict'; + +const Base = require('./Base'); +const { Presence } = require('./Presence'); +const Role = require('./Role'); +const VoiceState = require('./VoiceState'); +const TextBasedChannel = require('./interfaces/TextBasedChannel'); +const { Error } = require('../errors'); +const GuildMemberRoleManager = require('../managers/GuildMemberRoleManager'); +const Permissions = require('../util/Permissions'); + +/** + * Represents a member of a guild on Discord. + * @implements {TextBasedChannel} + * @extends {Base} + */ +class GuildMember extends Base { + /** + * @param {Client} client The instantiating client + * @param {Object} data The data for the guild member + * @param {Guild} guild The guild the member is part of + */ + constructor(client, data, guild) { + super(client); + + /** + * The guild that this member is part of + * @type {Guild} + */ + this.guild = guild; + + /** + * The user that this guild member instance represents + * @type {User} + * @name GuildMember#user + */ + if (data.user) this.user = client.users.add(data.user, true); + + /** + * The timestamp the member joined the guild at + * @type {?number} + */ + this.joinedTimestamp = null; + + /** + * The ID of the last message sent by the member in their guild, if one was sent + * @type {?Snowflake} + */ + this.lastMessageID = null; + + /** + * The ID of the channel for the last message sent by the member in their guild, if one was sent + * @type {?Snowflake} + */ + this.lastMessageChannelID = null; + + /** + * The timestamp of when the member used their Nitro boost on the guild, if it was used + * @type {?number} + */ + this.premiumSinceTimestamp = null; + + /** + * Whether the member has been removed from the guild + * @type {boolean} + */ + this.deleted = false; + + this._roles = []; + if (data) this._patch(data); + } + + _patch(data) { + /** + * The nickname of this member, if they have one + * @type {?string} + * @name GuildMember#nickname + */ + if (typeof data.nick !== 'undefined') this.nickname = data.nick; + + if (data.joined_at) this.joinedTimestamp = new Date(data.joined_at).getTime(); + if (data.premium_since) this.premiumSinceTimestamp = new Date(data.premium_since).getTime(); + + if (data.user) this.user = this.guild.client.users.add(data.user); + if (data.roles) this._roles = data.roles; + } + + _clone() { + const clone = super._clone(); + clone._roles = this._roles.slice(); + return clone; + } + + /** + * Whether this GuildMember is a partial + * @type {boolean} + * @readonly + */ + get partial() { + return !this.joinedTimestamp; + } + + /** + * A manager for the roles belonging to this member + * @type {GuildMemberRoleManager} + * @readonly + */ + get roles() { + return new GuildMemberRoleManager(this); + } + + /** + * The Message object of the last message sent by the member in their guild, if one was sent + * @type {?Message} + * @readonly + */ + get lastMessage() { + const channel = this.guild.channels.cache.get(this.lastMessageChannelID); + return (channel && channel.messages.cache.get(this.lastMessageID)) || null; + } + + /** + * The voice state of this member + * @type {VoiceState} + * @readonly + */ + get voice() { + return this.guild.voiceStates.cache.get(this.id) || new VoiceState(this.guild, { user_id: this.id }); + } + + /** + * The time this member joined the guild + * @type {?Date} + * @readonly + */ + get joinedAt() { + return this.joinedTimestamp ? new Date(this.joinedTimestamp) : null; + } + + /** + * The time of when the member used their Nitro boost on the guild, if it was used + * @type {?Date} + * @readonly + */ + get premiumSince() { + return this.premiumSinceTimestamp ? new Date(this.premiumSinceTimestamp) : null; + } + + /** + * The presence of this guild member + * @type {Presence} + * @readonly + */ + get presence() { + return ( + this.guild.presences.cache.get(this.id) || + new Presence(this.client, { + user: { + id: this.id, + }, + guild: this.guild, + }) + ); + } + + /** + * The displayed color of this member in base 10 + * @type {number} + * @readonly + */ + get displayColor() { + const role = this.roles.color; + return (role && role.color) || 0; + } + + /** + * The displayed color of this member in hexadecimal + * @type {string} + * @readonly + */ + get displayHexColor() { + const role = this.roles.color; + return (role && role.hexColor) || '#000000'; + } + + /** + * The ID of this member + * @type {Snowflake} + * @readonly + */ + get id() { + return this.user.id; + } + + /** + * The nickname of this member, or their username if they don't have one + * @type {string} + * @readonly + */ + get displayName() { + return this.nickname || this.user.username; + } + + /** + * The overall set of permissions for this member, taking only roles into account + * @type {Readonly<Permissions>} + * @readonly + */ + get permissions() { + if (this.user.id === this.guild.ownerID) return new Permissions(Permissions.ALL).freeze(); + return new Permissions(this.roles.cache.map(role => role.permissions)).freeze(); + } + + /** + * 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 + */ + get manageable() { + if (this.user.id === this.guild.ownerID) return false; + if (this.user.id === this.client.user.id) return false; + if (this.client.user.id === this.guild.ownerID) return true; + if (!this.guild.me) throw new Error('GUILD_UNCACHED_ME'); + return this.guild.me.roles.highest.comparePositionTo(this.roles.highest) > 0; + } + + /** + * Whether this member is kickable by the client user + * @type {boolean} + * @readonly + */ + get kickable() { + return this.manageable && this.guild.me.permissions.has(Permissions.FLAGS.KICK_MEMBERS); + } + + /** + * Whether this member is bannable by the client user + * @type {boolean} + * @readonly + */ + get bannable() { + return this.manageable && this.guild.me.permissions.has(Permissions.FLAGS.BAN_MEMBERS); + } + + /** + * Returns `channel.permissionsFor(guildMember)`. Returns permissions for a member in a guild channel, + * taking into account roles and permission overwrites. + * @param {ChannelResolvable} channel The guild channel to use as context + * @returns {Readonly<Permissions>} + */ + permissionsIn(channel) { + channel = this.guild.channels.resolve(channel); + if (!channel) throw new Error('GUILD_CHANNEL_RESOLVE'); + return channel.memberPermissions(this); + } + + /** + * Checks if any of this member's roles have a permission. + * @param {PermissionResolvable} permission Permission(s) to check for + * @param {Object} [options] Options + * @param {boolean} [options.checkAdmin=true] Whether to allow the administrator permission to override + * @param {boolean} [options.checkOwner=true] Whether to allow being the guild's owner to override + * @returns {boolean} + */ + hasPermission(permission, { checkAdmin = true, checkOwner = true } = {}) { + if (checkOwner && this.user.id === this.guild.ownerID) return true; + return this.roles.cache.some(r => r.permissions.has(permission, checkAdmin)); + } + + /** + * The data for editing a guild member. + * @typedef {Object} GuildMemberEditData + * @property {string} [nick] The nickname to set for the member + * @property {Collection<Snowflake, Role>|RoleResolvable[]} [roles] The roles or role IDs to apply + * @property {boolean} [mute] Whether or not the member should be muted + * @property {boolean} [deaf] Whether or not the member should be deafened + * @property {ChannelResolvable|null} [channel] Channel to move member to (if they are connected to voice), or `null` + * if you want to kick them from voice + */ + + /** + * Edits this member. + * @param {GuildMemberEditData} data The data to edit the member with + * @param {string} [reason] Reason for editing this user + * @returns {Promise<GuildMember>} + */ + async edit(data, reason) { + if (data.channel) { + data.channel = this.guild.channels.resolve(data.channel); + if (!data.channel || data.channel.type !== 'voice') { + throw new Error('GUILD_VOICE_CHANNEL_RESOLVE'); + } + data.channel_id = data.channel.id; + data.channel = undefined; + } else if (data.channel === null) { + data.channel_id = null; + data.channel = undefined; + } + if (data.roles) data.roles = data.roles.map(role => (role instanceof Role ? role.id : role)); + let endpoint = this.client.api.guilds(this.guild.id); + if (this.user.id === this.client.user.id) { + const keys = Object.keys(data); + if (keys.length === 1 && keys[0] === 'nick') endpoint = endpoint.members('@me').nick; + else endpoint = endpoint.members(this.id); + } else { + endpoint = endpoint.members(this.id); + } + await endpoint.patch({ data, reason }); + + const clone = this._clone(); + data.user = this.user; + clone._patch(data); + return clone; + } + + /** + * Sets the nickname for this member. + * @param {string} nick The nickname for the guild member + * @param {string} [reason] Reason for setting the nickname + * @returns {Promise<GuildMember>} + */ + setNickname(nick, reason) { + return this.edit({ nick }, reason); + } + + /** + * Creates a DM channel between the client and this member. + * @returns {Promise<DMChannel>} + */ + createDM() { + return this.user.createDM(); + } + + /** + * Deletes any DMs with this member. + * @returns {Promise<DMChannel>} + */ + deleteDM() { + return this.user.deleteDM(); + } + + /** + * Kicks this member from the guild. + * @param {string} [reason] Reason for kicking user + * @returns {Promise<GuildMember>} + */ + kick(reason) { + return this.client.api + .guilds(this.guild.id) + .members(this.user.id) + .delete({ reason }) + .then(() => this); + } + + /** + * Bans this guild member. + * @param {Object} [options] Options for the ban + * @param {number} [options.days=0] Number of days of messages to delete + * @param {string} [options.reason] Reason for banning + * @returns {Promise<GuildMember>} + * @example + * // ban a guild member + * guildMember.ban({ days: 7, reason: 'They deserved it' }) + * .then(console.log) + * .catch(console.error); + */ + ban(options) { + return this.guild.members.ban(this, options); + } + + /** + * Fetches this GuildMember. + * @returns {Promise<GuildMember>} + */ + fetch() { + return this.guild.members.fetch(this.id, true); + } + + /** + * When concatenated with a string, this automatically returns the user's mention instead of the GuildMember object. + * @returns {string} + * @example + * // Logs: Hello from <@123456789012345678>! + * console.log(`Hello from ${member}!`); + */ + toString() { + return `<@${this.nickname ? '!' : ''}${this.user.id}>`; + } + + toJSON() { + return super.toJSON({ + guild: 'guildID', + user: 'userID', + displayName: true, + speaking: false, + lastMessage: false, + lastMessageID: false, + roles: true, + }); + } + + // These are here only for documentation purposes - they are implemented by TextBasedChannel + /* eslint-disable no-empty-function */ + send() {} +} + +TextBasedChannel.applyToClass(GuildMember); + +module.exports = GuildMember; diff --git a/node_modules/discord.js/src/structures/GuildPreview.js b/node_modules/discord.js/src/structures/GuildPreview.js new file mode 100644 index 0000000..681ff60 --- /dev/null +++ b/node_modules/discord.js/src/structures/GuildPreview.js @@ -0,0 +1,157 @@ +'use strict'; + +const Base = require('./Base'); +const GuildPreviewEmoji = require('./GuildPreviewEmoji'); +const Collection = require('../util/Collection'); + +/** + * Represents the data about the guild any bot can preview, connected to the specified public guild. + * @extends {Base} + */ +class GuildPreview extends Base { + constructor(client, data) { + super(client); + + if (!data) return; + + this._patch(data); + } + + /** + * Builds the public guild with the provided data. + * @param {*} data The raw data of the public guild + * @private + */ + _patch(data) { + /** + * The id of this public guild + * @type {string} + */ + this.id = data.id; + + /** + * The name of this public guild + * @type {string} + */ + this.name = data.name; + + /** + * The icon of this public guild + * @type {?string} + */ + this.icon = data.icon; + + /** + * The splash icon of this public guild + * @type {?string} + */ + this.splash = data.splash; + + /** + * The discovery splash icon of this public guild + * @type {?string} + */ + this.discoverySplash = data.discovery_splash; + + /** + * An array of enabled guild features + * @type {Features[]} + */ + this.features = data.features; + + /** + * The approximate count of members in this public guild + * @type {number} + */ + this.approximateMemberCount = data.approximate_member_count; + + /** + * The approximate count of online members in this public guild + * @type {number} + */ + this.approximatePresenceCount = data.approximate_presence_count; + + /** + * The description for this public guild + * @type {?string} + */ + this.description = data.description; + + if (!this.emojis) { + /** + * Collection of emojis belonging to this public guild + * @type {Collection<Snowflake, GuildPreviewEmoji>} + */ + this.emojis = new Collection(); + } else { + this.emojis.clear(); + } + for (const emoji of data.emojis) { + this.emojis.set(emoji.id, new GuildPreviewEmoji(this.client, emoji, this)); + } + } + + /** + * The URL to this public guild's splash. + * @param {ImageURLOptions} [options={}] Options for the Image URL + * @returns {?string} + */ + splashURL({ format, size } = {}) { + if (!this.splash) return null; + return this.client.rest.cdn.Splash(this.id, this.splash, format, size); + } + + /** + * The URL to this public guild's discovery splash. + * @param {ImageURLOptions} [options={}] Options for the Image URL + * @returns {?string} + */ + discoverySplashURL({ format, size } = {}) { + if (!this.discoverySplash) return null; + return this.client.rest.cdn.DiscoverySplash(this.id, this.discoverySplash, format, size); + } + + /** + * The URL to this public guild's icon. + * @param {ImageURLOptions} [options={}] Options for the Image URL + * @returns {?string} + */ + iconURL({ format, size, dynamic } = {}) { + if (!this.icon) return null; + return this.client.rest.cdn.Icon(this.id, this.icon, format, size, dynamic); + } + + /** + * Fetches this public guild. + * @returns {Promise<GuildPreview>} + */ + fetch() { + return this.client.api + .guilds(this.id) + .preview.get() + .then(data => { + this._patch(data); + return this; + }); + } + + /** + * When concatenated with a string, this automatically returns the guild's name instead of the Guild object. + * @returns {string} + * @example + * // Logs: Hello from My Guild! + * console.log(`Hello from ${previewGuild}!`); + */ + toString() { + return this.name; + } + + toJSON() { + const json = super.toJSON(); + json.iconURL = this.iconURL(); + json.splashURL = this.splashURL(); + return json; + } +} + +module.exports = GuildPreview; diff --git a/node_modules/discord.js/src/structures/GuildPreviewEmoji.js b/node_modules/discord.js/src/structures/GuildPreviewEmoji.js new file mode 100644 index 0000000..4c70903 --- /dev/null +++ b/node_modules/discord.js/src/structures/GuildPreviewEmoji.js @@ -0,0 +1,26 @@ +'use strict'; + +const BaseGuildEmoji = require('./BaseGuildEmoji'); + +/** + * Represents an instance of an emoji belonging to a public guild obtained through Discord's preview endpoint. + * @extends {BaseGuildEmoji} + */ +class GuildPreviewEmoji extends BaseGuildEmoji { + /** + * The public guild this emoji is part of + * @type {GuildPreview} + * @name GuildPreviewEmoji#guild + */ + + /** + * Set of roles this emoji is active for + * @type {Set<Snowflake>} + * @readonly + */ + get roles() { + return new Set(this._roles); + } +} + +module.exports = GuildPreviewEmoji; diff --git a/node_modules/discord.js/src/structures/Integration.js b/node_modules/discord.js/src/structures/Integration.js new file mode 100644 index 0000000..49a0227 --- /dev/null +++ b/node_modules/discord.js/src/structures/Integration.js @@ -0,0 +1,167 @@ +'use strict'; + +const Base = require('./Base'); + +/** + * The information account for an integration + * @typedef {Object} IntegrationAccount + * @property {string} id The id of the account + * @property {string} name The name of the account + */ + +/** + * Represents a guild integration. + */ +class Integration extends Base { + constructor(client, data, guild) { + super(client); + + /** + * The guild this integration belongs to + * @type {Guild} + */ + this.guild = guild; + + /** + * The integration id + * @type {Snowflake} + */ + this.id = data.id; + + /** + * The integration name + * @type {string} + */ + this.name = data.name; + + /** + * The integration type (twitch, youtube, etc) + * @type {string} + */ + this.type = data.type; + + /** + * Whether this integration is enabled + * @type {boolean} + */ + this.enabled = data.enabled; + + /** + * Whether this integration is syncing + * @type {boolean} + */ + this.syncing = data.syncing; + + /** + * The role that this integration uses for subscribers + * @type {Role} + */ + this.role = this.guild.roles.cache.get(data.role_id); + + /** + * The user for this integration + * @type {User} + */ + this.user = this.client.users.add(data.user); + + /** + * The account integration information + * @type {IntegrationAccount} + */ + this.account = data.account; + + /** + * The last time this integration was last synced + * @type {number} + */ + this.syncedAt = data.synced_at; + this._patch(data); + } + + _patch(data) { + /** + * The behavior of expiring subscribers + * @type {number} + */ + this.expireBehavior = data.expire_behavior; + + /** + * The grace period before expiring subscribers + * @type {number} + */ + this.expireGracePeriod = data.expire_grace_period; + } + + /** + * Sync this integration + * @returns {Promise<Integration>} + */ + sync() { + this.syncing = true; + return this.client.api + .guilds(this.guild.id) + .integrations(this.id) + .post() + .then(() => { + this.syncing = false; + this.syncedAt = Date.now(); + return this; + }); + } + + /** + * The data for editing an integration. + * @typedef {Object} IntegrationEditData + * @property {number} [expireBehavior] The new behaviour of expiring subscribers + * @property {number} [expireGracePeriod] The new grace period before expiring subscribers + */ + + /** + * Edits this integration. + * @param {IntegrationEditData} data The data to edit this integration with + * @param {string} reason Reason for editing this integration + * @returns {Promise<Integration>} + */ + edit(data, reason) { + if ('expireBehavior' in data) { + data.expire_behavior = data.expireBehavior; + data.expireBehavior = null; + } + if ('expireGracePeriod' in data) { + data.expire_grace_period = data.expireGracePeriod; + data.expireGracePeriod = null; + } + // The option enable_emoticons is only available for Twitch at this moment + return this.client.api + .guilds(this.guild.id) + .integrations(this.id) + .patch({ data, reason }) + .then(() => { + this._patch(data); + return this; + }); + } + + /** + * Deletes this integration. + * @returns {Promise<Integration>} + * @param {string} [reason] Reason for deleting this integration + */ + delete(reason) { + return this.client.api + .guilds(this.guild.id) + .integrations(this.id) + .delete({ reason }) + .then(() => this); + } + + toJSON() { + return super.toJSON({ + role: 'roleID', + guild: 'guildID', + user: 'userID', + }); + } +} + +module.exports = Integration; diff --git a/node_modules/discord.js/src/structures/Invite.js b/node_modules/discord.js/src/structures/Invite.js new file mode 100644 index 0000000..6833266 --- /dev/null +++ b/node_modules/discord.js/src/structures/Invite.js @@ -0,0 +1,194 @@ +'use strict'; + +const Base = require('./Base'); +const { Endpoints } = require('../util/Constants'); +const Permissions = require('../util/Permissions'); + +/** + * Represents an invitation to a guild channel. + * <warn>The only guaranteed properties are `code`, `channel`, and `url`. Other properties can be missing.</warn> + * @extends {Base} + */ +class Invite extends Base { + constructor(client, data) { + super(client); + this._patch(data); + } + + _patch(data) { + /** + * The guild the invite is for + * @type {?Guild} + */ + this.guild = data.guild ? this.client.guilds.add(data.guild, false) : null; + + /** + * The code for this invite + * @type {string} + */ + this.code = data.code; + + /** + * The approximate number of online members of the guild this invite is for + * @type {?number} + */ + this.presenceCount = 'approximate_presence_count' in data ? data.approximate_presence_count : null; + + /** + * The approximate total number of members of the guild this invite is for + * @type {?number} + */ + this.memberCount = 'approximate_member_count' in data ? data.approximate_member_count : null; + + /** + * Whether or not this invite is temporary + * @type {?boolean} + */ + this.temporary = 'temporary' in data ? data.temporary : null; + + /** + * The maximum age of the invite, in seconds, 0 if never expires + * @type {?number} + */ + this.maxAge = 'max_age' in data ? data.max_age : null; + + /** + * How many times this invite has been used + * @type {?number} + */ + this.uses = 'uses' in data ? data.uses : null; + + /** + * The maximum uses of this invite + * @type {?number} + */ + this.maxUses = 'max_uses' in data ? data.max_uses : null; + + /** + * The user who created this invite + * @type {?User} + */ + this.inviter = data.inviter ? this.client.users.add(data.inviter) : null; + + /** + * The target user for this invite + * @type {?User} + */ + this.targetUser = data.target_user ? this.client.users.add(data.target_user) : null; + + /** + * The type of the target user: + * * 1: STREAM + * @typedef {number} TargetUser + */ + + /** + * The target user type + * @type {?TargetUser} + */ + this.targetUserType = typeof data.target_user_type === 'number' ? data.target_user_type : null; + + /** + * The channel the invite is for + * @type {Channel} + */ + this.channel = this.client.channels.add(data.channel, this.guild, false); + + /** + * The timestamp the invite was created at + * @type {?number} + */ + this.createdTimestamp = 'created_at' in data ? new Date(data.created_at).getTime() : null; + } + + /** + * The time the invite was created at + * @type {?Date} + * @readonly + */ + get createdAt() { + return this.createdTimestamp ? new Date(this.createdTimestamp) : null; + } + + /** + * Whether the invite is deletable by the client user + * @type {boolean} + * @readonly + */ + get deletable() { + const guild = this.guild; + if (!guild || !this.client.guilds.cache.has(guild.id)) return false; + if (!guild.me) throw new Error('GUILD_UNCACHED_ME'); + return ( + this.channel.permissionsFor(this.client.user).has(Permissions.FLAGS.MANAGE_CHANNELS, false) || + guild.me.permissions.has(Permissions.FLAGS.MANAGE_GUILD) + ); + } + + /** + * The timestamp the invite will expire at + * @type {?number} + * @readonly + */ + get expiresTimestamp() { + return this.createdTimestamp && this.maxAge ? this.createdTimestamp + this.maxAge * 1000 : null; + } + + /** + * The time the invite will expire at + * @type {?Date} + * @readonly + */ + get expiresAt() { + const { expiresTimestamp } = this; + return expiresTimestamp ? new Date(expiresTimestamp) : null; + } + + /** + * The URL to the invite + * @type {string} + * @readonly + */ + get url() { + return Endpoints.invite(this.client.options.http.invite, this.code); + } + + /** + * Deletes this invite. + * @param {string} [reason] Reason for deleting this invite + * @returns {Promise<Invite>} + */ + delete(reason) { + return this.client.api.invites[this.code].delete({ reason }).then(() => this); + } + + /** + * When concatenated with a string, this automatically concatenates the invite's URL instead of the object. + * @returns {string} + * @example + * // Logs: Invite: https://discord.gg/A1b2C3 + * console.log(`Invite: ${invite}`); + */ + toString() { + return this.url; + } + + toJSON() { + return super.toJSON({ + url: true, + expiresTimestamp: true, + presenceCount: false, + memberCount: false, + uses: false, + channel: 'channelID', + inviter: 'inviterID', + guild: 'guildID', + }); + } + + valueOf() { + return this.code; + } +} + +module.exports = Invite; diff --git a/node_modules/discord.js/src/structures/Message.js b/node_modules/discord.js/src/structures/Message.js new file mode 100644 index 0000000..06ed12a --- /dev/null +++ b/node_modules/discord.js/src/structures/Message.js @@ -0,0 +1,623 @@ +'use strict'; + +const APIMessage = require('./APIMessage'); +const Base = require('./Base'); +const ClientApplication = require('./ClientApplication'); +const MessageAttachment = require('./MessageAttachment'); +const Embed = require('./MessageEmbed'); +const Mentions = require('./MessageMentions'); +const ReactionCollector = require('./ReactionCollector'); +const { Error, TypeError } = require('../errors'); +const ReactionManager = require('../managers/ReactionManager'); +const Collection = require('../util/Collection'); +const { MessageTypes } = require('../util/Constants'); +const MessageFlags = require('../util/MessageFlags'); +const Permissions = require('../util/Permissions'); +const Util = require('../util/Util'); + +/** + * Represents a message on Discord. + * @extends {Base} + */ +class Message extends Base { + /** + * @param {Client} client The instantiating client + * @param {Object} data The data for the message + * @param {TextChannel|DMChannel} channel The channel the message was sent in + */ + constructor(client, data, channel) { + super(client); + + /** + * The channel that the message was sent in + * @type {TextChannel|DMChannel} + */ + this.channel = channel; + + /** + * Whether this message has been deleted + * @type {boolean} + */ + this.deleted = false; + + if (data) this._patch(data); + } + + _patch(data) { + /** + * The ID of the message + * @type {Snowflake} + */ + this.id = data.id; + + /** + * The type of the message + * @type {MessageType} + */ + this.type = MessageTypes[data.type]; + + /** + * The content of the message + * @type {string} + */ + this.content = data.content; + + /** + * The author of the message + * @type {?User} + */ + this.author = data.author ? this.client.users.add(data.author, !data.webhook_id) : null; + + /** + * Whether or not this message is pinned + * @type {boolean} + */ + this.pinned = data.pinned; + + /** + * Whether or not the message was Text-To-Speech + * @type {boolean} + */ + this.tts = data.tts; + + /** + * A random number or string used for checking message delivery + * <warn>This is only received after the message was sent successfully, and + * lost if re-fetched</warn> + * @type {?string} + */ + this.nonce = data.nonce; + + /** + * Whether or not this message was sent by Discord, not actually a user (e.g. pin notifications) + * @type {boolean} + */ + this.system = data.type !== 0; + + /** + * A list of embeds in the message - e.g. YouTube Player + * @type {MessageEmbed[]} + */ + this.embeds = (data.embeds || []).map(e => new Embed(e, true)); + + /** + * A collection of attachments in the message - e.g. Pictures - mapped by their ID + * @type {Collection<Snowflake, MessageAttachment>} + */ + this.attachments = new Collection(); + if (data.attachments) { + for (const attachment of data.attachments) { + this.attachments.set(attachment.id, new MessageAttachment(attachment.url, attachment.filename, attachment)); + } + } + + /** + * The timestamp the message was sent at + * @type {number} + */ + this.createdTimestamp = new Date(data.timestamp).getTime(); + + /** + * The timestamp the message was last edited at (if applicable) + * @type {?number} + */ + this.editedTimestamp = data.edited_timestamp ? new Date(data.edited_timestamp).getTime() : null; + + /** + * A manager of the reactions belonging to this message + * @type {ReactionManager} + */ + this.reactions = new ReactionManager(this); + if (data.reactions && data.reactions.length > 0) { + for (const reaction of data.reactions) { + this.reactions.add(reaction); + } + } + + /** + * All valid mentions that the message contains + * @type {MessageMentions} + */ + this.mentions = new Mentions(this, data.mentions, data.mention_roles, data.mention_everyone, data.mention_channels); + + /** + * ID of the webhook that sent the message, if applicable + * @type {?Snowflake} + */ + this.webhookID = data.webhook_id || null; + + /** + * Supplemental application information for group activities + * @type {?ClientApplication} + */ + this.application = data.application ? new ClientApplication(this.client, data.application) : null; + + /** + * Group activity + * @type {?MessageActivity} + */ + this.activity = data.activity + ? { + partyID: data.activity.party_id, + type: data.activity.type, + } + : null; + + /** + * The previous versions of the message, sorted with the most recent first + * @type {Message[]} + * @private + */ + this._edits = []; + + if (this.member && data.member) { + this.member._patch(data.member); + } else if (data.member && this.guild && this.author) { + this.guild.members.add(Object.assign(data.member, { user: this.author })); + } + + /** + * Flags that are applied to the message + * @type {Readonly<MessageFlags>} + */ + this.flags = new MessageFlags(data.flags).freeze(); + + /** + * Reference data sent in a crossposted message. + * @typedef {Object} MessageReference + * @property {string} channelID ID of the channel the message was crossposted from + * @property {?string} guildID ID of the guild the message was crossposted from + * @property {?string} messageID ID of the message that was crossposted + */ + + /** + * Message reference data + * @type {?MessageReference} + */ + this.reference = data.message_reference + ? { + channelID: data.message_reference.channel_id, + guildID: data.message_reference.guild_id, + messageID: data.message_reference.message_id, + } + : null; + } + + /** + * Whether or not this message is a partial + * @type {boolean} + * @readonly + */ + get partial() { + return typeof this.content !== 'string' || !this.author; + } + + /** + * Updates the message. + * @param {Object} data Raw Discord message update data + * @private + */ + patch(data) { + const clone = this._clone(); + this._edits.unshift(clone); + + if ('edited_timestamp' in data) this.editedTimestamp = new Date(data.edited_timestamp).getTime(); + if ('content' in data) this.content = data.content; + if ('pinned' in data) this.pinned = data.pinned; + if ('tts' in data) this.tts = data.tts; + if ('embeds' in data) this.embeds = data.embeds.map(e => new Embed(e, true)); + else this.embeds = this.embeds.slice(); + + if ('attachments' in data) { + this.attachments = new Collection(); + for (const attachment of data.attachments) { + this.attachments.set(attachment.id, new MessageAttachment(attachment.url, attachment.filename, attachment)); + } + } else { + this.attachments = new Collection(this.attachments); + } + + this.mentions = new Mentions( + this, + 'mentions' in data ? data.mentions : this.mentions.users, + 'mention_roles' in data ? data.mention_roles : this.mentions.roles, + 'mention_everyone' in data ? data.mention_everyone : this.mentions.everyone, + 'mention_channels' in data ? data.mention_channels : this.mentions.crosspostedChannels, + ); + + this.flags = new MessageFlags('flags' in data ? data.flags : 0).freeze(); + } + + /** + * Represents the author of the message as a guild member. + * Only available if the message comes from a guild where the author is still a member + * @type {?GuildMember} + * @readonly + */ + get member() { + return this.guild ? this.guild.member(this.author) || null : null; + } + + /** + * The time the message was sent at + * @type {Date} + * @readonly + */ + get createdAt() { + return new Date(this.createdTimestamp); + } + + /** + * The time the message was last edited at (if applicable) + * @type {?Date} + * @readonly + */ + get editedAt() { + return this.editedTimestamp ? new Date(this.editedTimestamp) : null; + } + + /** + * The guild the message was sent in (if in a guild channel) + * @type {?Guild} + * @readonly + */ + get guild() { + return this.channel.guild || null; + } + + /** + * The url to jump to this message + * @type {string} + * @readonly + */ + get url() { + return `https://discordapp.com/channels/${this.guild ? this.guild.id : '@me'}/${this.channel.id}/${this.id}`; + } + + /** + * The message contents with all mentions replaced by the equivalent text. + * If mentions cannot be resolved to a name, the relevant mention in the message content will not be converted. + * @type {string} + * @readonly + */ + get cleanContent() { + // eslint-disable-next-line eqeqeq + return this.content != null ? Util.cleanContent(this.content, this) : null; + } + + /** + * Creates a reaction collector. + * @param {CollectorFilter} filter The filter to apply + * @param {ReactionCollectorOptions} [options={}] Options to send to the collector + * @returns {ReactionCollector} + * @example + * // Create a reaction collector + * const filter = (reaction, user) => reaction.emoji.name === '👌' && user.id === 'someID'; + * const collector = message.createReactionCollector(filter, { time: 15000 }); + * collector.on('collect', r => console.log(`Collected ${r.emoji.name}`)); + * collector.on('end', collected => console.log(`Collected ${collected.size} items`)); + */ + createReactionCollector(filter, options = {}) { + return new ReactionCollector(this, filter, options); + } + + /** + * An object containing the same properties as CollectorOptions, but a few more: + * @typedef {ReactionCollectorOptions} AwaitReactionsOptions + * @property {string[]} [errors] Stop/end reasons that cause the promise to reject + */ + + /** + * Similar to createReactionCollector but in promise form. + * Resolves with a collection of reactions that pass the specified filter. + * @param {CollectorFilter} filter The filter function to use + * @param {AwaitReactionsOptions} [options={}] Optional options to pass to the internal collector + * @returns {Promise<Collection<string, MessageReaction>>} + * @example + * // Create a reaction collector + * const filter = (reaction, user) => reaction.emoji.name === '👌' && user.id === 'someID' + * message.awaitReactions(filter, { time: 15000 }) + * .then(collected => console.log(`Collected ${collected.size} reactions`)) + * .catch(console.error); + */ + awaitReactions(filter, options = {}) { + return new Promise((resolve, reject) => { + const collector = this.createReactionCollector(filter, options); + collector.once('end', (reactions, reason) => { + if (options.errors && options.errors.includes(reason)) reject(reactions); + else resolve(reactions); + }); + }); + } + + /** + * An array of cached versions of the message, including the current version + * Sorted from latest (first) to oldest (last) + * @type {Message[]} + * @readonly + */ + get edits() { + const copy = this._edits.slice(); + copy.unshift(this); + return copy; + } + + /** + * Whether the message is editable by the client user + * @type {boolean} + * @readonly + */ + get editable() { + return this.author.id === this.client.user.id; + } + + /** + * Whether the message is deletable by the client user + * @type {boolean} + * @readonly + */ + get deletable() { + return ( + !this.deleted && + (this.author.id === this.client.user.id || + (this.guild && this.channel.permissionsFor(this.client.user).has(Permissions.FLAGS.MANAGE_MESSAGES, false))) + ); + } + + /** + * Whether the message is pinnable by the client user + * @type {boolean} + * @readonly + */ + get pinnable() { + return ( + this.type === 'DEFAULT' && + (!this.guild || this.channel.permissionsFor(this.client.user).has(Permissions.FLAGS.MANAGE_MESSAGES, false)) + ); + } + + /** + * Options that can be passed into editMessage. + * @typedef {Object} MessageEditOptions + * @property {string} [content] Content to be edited + * @property {Object} [embed] An embed to be added/edited + * @property {string|boolean} [code] Language for optional codeblock formatting to apply + * @property {MessageMentionOptions} [allowedMentions] Which mentions should be parsed from the message content + */ + + /** + * Edits the content of the message. + * @param {StringResolvable|APIMessage} [content] The new content for the message + * @param {MessageEditOptions|MessageEmbed} [options] The options to provide + * @returns {Promise<Message>} + * @example + * // Update the content of a message + * message.edit('This is my new content!') + * .then(msg => console.log(`Updated the content of a message to ${msg.content}`)) + * .catch(console.error); + */ + edit(content, options) { + const { data } = + content instanceof APIMessage ? content.resolveData() : APIMessage.create(this, content, options).resolveData(); + return this.client.api.channels[this.channel.id].messages[this.id].patch({ data }).then(d => { + const clone = this._clone(); + clone._patch(d); + return clone; + }); + } + + /** + * Pins this message to the channel's pinned messages. + * @returns {Promise<Message>} + */ + pin() { + return this.client.api + .channels(this.channel.id) + .pins(this.id) + .put() + .then(() => this); + } + + /** + * Unpins this message from the channel's pinned messages. + * @returns {Promise<Message>} + */ + unpin() { + return this.client.api + .channels(this.channel.id) + .pins(this.id) + .delete() + .then(() => this); + } + + /** + * Adds a reaction to the message. + * @param {EmojiIdentifierResolvable} emoji The emoji to react with + * @returns {Promise<MessageReaction>} + * @example + * // React to a message with a unicode emoji + * message.react('🤔') + * .then(console.log) + * .catch(console.error); + * @example + * // React to a message with a custom emoji + * message.react(message.guild.emojis.cache.get('123456789012345678')) + * .then(console.log) + * .catch(console.error); + */ + react(emoji) { + emoji = this.client.emojis.resolveIdentifier(emoji); + if (!emoji) throw new TypeError('EMOJI_TYPE'); + + return this.client.api + .channels(this.channel.id) + .messages(this.id) + .reactions(emoji, '@me') + .put() + .then( + () => + this.client.actions.MessageReactionAdd.handle({ + user: this.client.user, + channel: this.channel, + message: this, + emoji: Util.parseEmoji(emoji), + }).reaction, + ); + } + + /** + * Deletes the message. + * @param {Object} [options] Options + * @param {number} [options.timeout=0] How long to wait to delete the message in milliseconds + * @param {string} [options.reason] Reason for deleting this message, if it does not belong to the client user + * @returns {Promise<Message>} + * @example + * // Delete a message + * message.delete() + * .then(msg => console.log(`Deleted message from ${msg.author.username}`)) + * .catch(console.error); + */ + delete(options = {}) { + if (typeof options !== 'object') throw new TypeError('INVALID_TYPE', 'options', 'object', true); + const { timeout = 0, reason } = options; + if (timeout <= 0) { + return this.channel.messages.delete(this.id, reason).then(() => this); + } else { + return new Promise(resolve => { + this.client.setTimeout(() => { + resolve(this.delete({ reason })); + }, timeout); + }); + } + } + + /** + * Replies to the message. + * @param {StringResolvable|APIMessage} [content=''] The content for the message + * @param {MessageOptions|MessageAdditions} [options={}] The options to provide + * @returns {Promise<Message|Message[]>} + * @example + * // Reply to a message + * message.reply('Hey, I\'m a reply!') + * .then(() => console.log(`Sent a reply to ${message.author.username}`)) + * .catch(console.error); + */ + reply(content, options) { + return this.channel.send( + content instanceof APIMessage + ? content + : APIMessage.transformOptions(content, options, { reply: this.member || this.author }), + ); + } + + /** + * Fetch this message. + * @returns {Promise<Message>} + */ + fetch() { + return this.channel.messages.fetch(this.id, true); + } + + /** + * Fetches the webhook used to create this message. + * @returns {Promise<?Webhook>} + */ + fetchWebhook() { + if (!this.webhookID) return Promise.reject(new Error('WEBHOOK_MESSAGE')); + return this.client.fetchWebhook(this.webhookID); + } + + /** + * Suppresses or unsuppresses embeds on a message + * @param {boolean} [suppress=true] If the embeds should be suppressed or not + * @returns {Promise<Message>} + */ + suppressEmbeds(suppress = true) { + const flags = new MessageFlags(this.flags.bitfield); + + if (suppress) { + flags.add(MessageFlags.FLAGS.SUPPRESS_EMBEDS); + } else { + flags.remove(MessageFlags.FLAGS.SUPPRESS_EMBEDS); + } + + return this.edit({ flags }); + } + + /** + * Used mainly internally. Whether two messages are identical in properties. If you want to compare messages + * without checking all the properties, use `message.id === message2.id`, which is much more efficient. This + * method allows you to see if there are differences in content, embeds, attachments, nonce and tts properties. + * @param {Message} message The message to compare it to + * @param {Object} rawData Raw data passed through the WebSocket about this message + * @returns {boolean} + */ + equals(message, rawData) { + if (!message) return false; + const embedUpdate = !message.author && !message.attachments; + if (embedUpdate) return this.id === message.id && this.embeds.length === message.embeds.length; + + let equal = + this.id === message.id && + this.author.id === message.author.id && + this.content === message.content && + this.tts === message.tts && + this.nonce === message.nonce && + this.embeds.length === message.embeds.length && + this.attachments.length === message.attachments.length; + + if (equal && rawData) { + equal = + this.mentions.everyone === message.mentions.everyone && + this.createdTimestamp === new Date(rawData.timestamp).getTime() && + this.editedTimestamp === new Date(rawData.edited_timestamp).getTime(); + } + + return equal; + } + + /** + * When concatenated with a string, this automatically concatenates the message's content instead of the object. + * @returns {string} + * @example + * // Logs: Message: This is a message! + * console.log(`Message: ${message}`); + */ + toString() { + return this.content; + } + + toJSON() { + return super.toJSON({ + channel: 'channelID', + author: 'authorID', + application: 'applicationID', + guild: 'guildID', + cleanContent: true, + member: false, + reactions: false, + }); + } +} + +module.exports = Message; diff --git a/node_modules/discord.js/src/structures/MessageAttachment.js b/node_modules/discord.js/src/structures/MessageAttachment.js new file mode 100644 index 0000000..f5fb723 --- /dev/null +++ b/node_modules/discord.js/src/structures/MessageAttachment.js @@ -0,0 +1,98 @@ +'use strict'; + +const Util = require('../util/Util'); + +/** + * Represents an attachment in a message. + */ +class MessageAttachment { + /** + * @param {BufferResolvable|Stream} attachment The file + * @param {string} [name=null] The name of the file, if any + * @param {Object} [data] Extra data + */ + constructor(attachment, name = null, data) { + this.attachment = attachment; + /** + * The name of this attachment + * @type {?string} + */ + this.name = name; + if (data) this._patch(data); + } + + /** + * Sets the file of this attachment. + * @param {BufferResolvable|Stream} attachment The file + * @param {string} [name=null] The name of the file, if any + * @returns {MessageAttachment} This attachment + */ + setFile(attachment, name = null) { + this.attachment = attachment; + this.name = name; + return this; + } + + /** + * Sets the name of this attachment. + * @param {string} name The name of the file + * @returns {MessageAttachment} This attachment + */ + setName(name) { + this.name = name; + return this; + } + + _patch(data) { + /** + * The ID of this attachment + * @type {Snowflake} + */ + this.id = data.id; + + /** + * The size of this attachment in bytes + * @type {number} + */ + this.size = data.size; + + /** + * The URL to this attachment + * @type {string} + */ + this.url = data.url; + + /** + * The Proxy URL to this attachment + * @type {string} + */ + this.proxyURL = data.proxy_url; + + /** + * The height of this attachment (if an image or video) + * @type {?number} + */ + this.height = typeof data.height !== 'undefined' ? data.height : null; + + /** + * The width of this attachment (if an image or video) + * @type {?number} + */ + this.width = typeof data.width !== 'undefined' ? data.width : null; + } + + /** + * Whether or not this attachment has been marked as a spoiler + * @type {boolean} + * @readonly + */ + get spoiler() { + return Util.basename(this.url).startsWith('SPOILER_'); + } + + toJSON() { + return Util.flatten(this); + } +} + +module.exports = MessageAttachment; diff --git a/node_modules/discord.js/src/structures/MessageCollector.js b/node_modules/discord.js/src/structures/MessageCollector.js new file mode 100644 index 0000000..f8f3d5a --- /dev/null +++ b/node_modules/discord.js/src/structures/MessageCollector.js @@ -0,0 +1,129 @@ +'use strict'; + +const Collector = require('./interfaces/Collector'); +const { Events } = require('../util/Constants'); + +/** + * @typedef {CollectorOptions} MessageCollectorOptions + * @property {number} max The maximum amount of messages to collect + * @property {number} maxProcessed The maximum amount of messages to process + */ + +/** + * Collects messages on a channel. + * Will automatically stop if the channel (`'channelDelete'`) or guild (`'guildDelete'`) are deleted. + * @extends {Collector} + */ +class MessageCollector extends Collector { + /** + * @param {TextChannel|DMChannel} channel The channel + * @param {CollectorFilter} filter The filter to be applied to this collector + * @param {MessageCollectorOptions} options The options to be applied to this collector + * @emits MessageCollector#message + */ + constructor(channel, filter, options = {}) { + super(channel.client, filter, options); + + /** + * The channel + * @type {TextBasedChannel} + */ + this.channel = channel; + + /** + * Total number of messages that were received in the channel during message collection + * @type {number} + */ + this.received = 0; + + const bulkDeleteListener = messages => { + for (const message of messages.values()) this.handleDispose(message); + }; + this._handleChannelDeletion = this._handleChannelDeletion.bind(this); + this._handleGuildDeletion = this._handleGuildDeletion.bind(this); + + if (this.client.getMaxListeners() !== 0) this.client.setMaxListeners(this.client.getMaxListeners() + 1); + this.client.on(Events.MESSAGE_CREATE, this.handleCollect); + this.client.on(Events.MESSAGE_DELETE, this.handleDispose); + this.client.on(Events.MESSAGE_BULK_DELETE, bulkDeleteListener); + this.client.on(Events.CHANNEL_DELETE, this._handleChannelDeletion); + this.client.on(Events.GUILD_DELETE, this._handleGuildDeletion); + + this.once('end', () => { + this.client.removeListener(Events.MESSAGE_CREATE, this.handleCollect); + this.client.removeListener(Events.MESSAGE_DELETE, this.handleDispose); + this.client.removeListener(Events.MESSAGE_BULK_DELETE, bulkDeleteListener); + this.client.removeListener(Events.CHANNEL_DELETE, this._handleChannelDeletion); + this.client.removeListener(Events.GUILD_DELETE, this._handleGuildDeletion); + if (this.client.getMaxListeners() !== 0) this.client.setMaxListeners(this.client.getMaxListeners() - 1); + }); + } + + /** + * Handles a message for possible collection. + * @param {Message} message The message that could be collected + * @returns {?Snowflake} + * @private + */ + collect(message) { + /** + * Emitted whenever a message is collected. + * @event MessageCollector#collect + * @param {Message} message The message that was collected + */ + if (message.channel.id !== this.channel.id) return null; + this.received++; + return message.id; + } + + /** + * Handles a message for possible disposal. + * @param {Message} message The message that could be disposed of + * @returns {?Snowflake} + */ + dispose(message) { + /** + * Emitted whenever a message is disposed of. + * @event MessageCollector#dispose + * @param {Message} message The message that was disposed of + */ + return message.channel.id === this.channel.id ? message.id : null; + } + + /** + * Checks after un/collection to see if the collector is done. + * @returns {?string} + * @private + */ + endReason() { + if (this.options.max && this.collected.size >= this.options.max) return 'limit'; + if (this.options.maxProcessed && this.received === this.options.maxProcessed) return 'processedLimit'; + return null; + } + + /** + * Handles checking if the channel has been deleted, and if so, stops the collector with the reason 'channelDelete'. + * @private + * @param {GuildChannel} channel The channel that was deleted + * @returns {void} + */ + _handleChannelDeletion(channel) { + if (channel.id === this.channel.id) { + this.stop('channelDelete'); + } + } + + /** + * Handles checking if the guild has been deleted, and if so, stops the collector with the reason 'guildDelete'. + * @private + * @param {Guild} guild The guild that was deleted + * @returns {void} + */ + _handleGuildDeletion(guild) { + if (this.channel.guild && guild.id === this.channel.guild.id) { + this.stop('guildDelete'); + } + } +} + +module.exports = MessageCollector; diff --git a/node_modules/discord.js/src/structures/MessageEmbed.js b/node_modules/discord.js/src/structures/MessageEmbed.js new file mode 100644 index 0000000..2b993a3 --- /dev/null +++ b/node_modules/discord.js/src/structures/MessageEmbed.js @@ -0,0 +1,454 @@ +'use strict'; + +const { RangeError } = require('../errors'); +const Util = require('../util/Util'); + +/** + * Represents an embed in a message (image/video preview, rich embed, etc.) + */ +class MessageEmbed { + /** + * @name MessageEmbed + * @kind constructor + * @memberof MessageEmbed + * @param {MessageEmbed|Object} [data={}] MessageEmbed to clone or raw embed data + */ + + constructor(data = {}, skipValidation = false) { + this.setup(data, skipValidation); + } + + setup(data, skipValidation) { + /** + * The type of this embed, either: + * * `rich` - a rich embed + * * `image` - an image embed + * * `video` - a video embed + * * `gifv` - a gifv embed + * * `article` - an article embed + * * `link` - a link embed + * @type {string} + */ + this.type = data.type; + + /** + * The title of this embed + * @type {?string} + */ + this.title = data.title; + + /** + * The description of this embed + * @type {?string} + */ + this.description = data.description; + + /** + * The URL of this embed + * @type {?string} + */ + this.url = data.url; + + /** + * The color of this embed + * @type {?number} + */ + this.color = Util.resolveColor(data.color); + + /** + * The timestamp of this embed + * @type {?number} + */ + this.timestamp = data.timestamp ? new Date(data.timestamp).getTime() : null; + + /** + * @typedef {Object} EmbedField + * @property {string} name The name of this field + * @property {string} value The value of this field + * @property {boolean} inline If this field will be displayed inline + */ + + /** + * The fields of this embed + * @type {EmbedField[]} + */ + this.fields = []; + if (data.fields) { + this.fields = skipValidation ? data.fields.map(Util.cloneObject) : this.constructor.normalizeFields(data.fields); + } + + /** + * @typedef {Object} MessageEmbedThumbnail + * @property {string} url URL for this thumbnail + * @property {string} proxyURL ProxyURL for this thumbnail + * @property {number} height Height of this thumbnail + * @property {number} width Width of this thumbnail + */ + + /** + * The thumbnail of this embed (if there is one) + * @type {?MessageEmbedThumbnail} + */ + this.thumbnail = data.thumbnail + ? { + url: data.thumbnail.url, + proxyURL: data.thumbnail.proxyURL || data.thumbnail.proxy_url, + height: data.thumbnail.height, + width: data.thumbnail.width, + } + : null; + + /** + * @typedef {Object} MessageEmbedImage + * @property {string} url URL for this image + * @property {string} proxyURL ProxyURL for this image + * @property {number} height Height of this image + * @property {number} width Width of this image + */ + + /** + * The image of this embed, if there is one + * @type {?MessageEmbedImage} + */ + this.image = data.image + ? { + url: data.image.url, + proxyURL: data.image.proxyURL || data.image.proxy_url, + height: data.image.height, + width: data.image.width, + } + : null; + + /** + * @typedef {Object} MessageEmbedVideo + * @property {string} url URL of this video + * @property {string} proxyURL ProxyURL for this video + * @property {number} height Height of this video + * @property {number} width Width of this video + */ + + /** + * The video of this embed (if there is one) + * @type {?MessageEmbedVideo} + * @readonly + */ + this.video = data.video + ? { + url: data.video.url, + proxyURL: data.video.proxyURL || data.video.proxy_url, + height: data.video.height, + width: data.video.width, + } + : null; + + /** + * @typedef {Object} MessageEmbedAuthor + * @property {string} name The name of this author + * @property {string} url URL of this author + * @property {string} iconURL URL of the icon for this author + * @property {string} proxyIconURL Proxied URL of the icon for this author + */ + + /** + * The author of this embed (if there is one) + * @type {?MessageEmbedAuthor} + */ + this.author = data.author + ? { + name: data.author.name, + url: data.author.url, + iconURL: data.author.iconURL || data.author.icon_url, + proxyIconURL: data.author.proxyIconURL || data.author.proxy_icon_url, + } + : null; + + /** + * @typedef {Object} MessageEmbedProvider + * @property {string} name The name of this provider + * @property {string} url URL of this provider + */ + + /** + * The provider of this embed (if there is one) + * @type {?MessageEmbedProvider} + */ + this.provider = data.provider + ? { + name: data.provider.name, + url: data.provider.name, + } + : null; + + /** + * @typedef {Object} MessageEmbedFooter + * @property {string} text The text of this footer + * @property {string} iconURL URL of the icon for this footer + * @property {string} proxyIconURL Proxied URL of the icon for this footer + */ + + /** + * The footer of this embed + * @type {?MessageEmbedFooter} + */ + this.footer = data.footer + ? { + text: data.footer.text, + iconURL: data.footer.iconURL || data.footer.icon_url, + proxyIconURL: data.footer.proxyIconURL || data.footer.proxy_icon_url, + } + : null; + + /** + * The files of this embed + * @type {Array<FileOptions|string|MessageAttachment>} + */ + this.files = data.files || []; + } + + /** + * The date displayed on this embed + * @type {?Date} + * @readonly + */ + get createdAt() { + return this.timestamp ? new Date(this.timestamp) : null; + } + + /** + * The hexadecimal version of the embed color, with a leading hash + * @type {?string} + * @readonly + */ + get hexColor() { + return this.color ? `#${this.color.toString(16).padStart(6, '0')}` : null; + } + + /** + * The accumulated length for the embed title, description, fields and footer text + * @type {number} + * @readonly + */ + get length() { + return ( + (this.title ? this.title.length : 0) + + (this.description ? this.description.length : 0) + + (this.fields.length >= 1 + ? this.fields.reduce((prev, curr) => prev + curr.name.length + curr.value.length, 0) + : 0) + + (this.footer ? this.footer.text.length : 0) + ); + } + + /** + * Adds a field to the embed (max 25). + * @param {StringResolvable} name The name of this field + * @param {StringResolvable} value The value of this field + * @param {boolean} [inline=false] If this field will be displayed inline + * @returns {MessageEmbed} + */ + addField(name, value, inline) { + return this.addFields({ name, value, inline }); + } + + /** + * Adds fields to the embed (max 25). + * @param {...EmbedFieldData|EmbedFieldData[]} fields The fields to add + * @returns {MessageEmbed} + */ + addFields(...fields) { + this.fields.push(...this.constructor.normalizeFields(fields)); + return this; + } + + /** + * Removes, replaces, and inserts fields in the embed (max 25). + * @param {number} index The index to start at + * @param {number} deleteCount The number of fields to remove + * @param {...EmbedFieldData|EmbedFieldData[]} [fields] The replacing field objects + * @returns {MessageEmbed} + */ + spliceFields(index, deleteCount, ...fields) { + this.fields.splice(index, deleteCount, ...this.constructor.normalizeFields(...fields)); + return this; + } + + /** + * Sets the file to upload alongside the embed. This file can be accessed via `attachment://fileName.extension` when + * setting an embed image or author/footer icons. Multiple files can be attached. + * @param {Array<FileOptions|string|MessageAttachment>} files Files to attach + * @returns {MessageEmbed} + */ + attachFiles(files) { + this.files = this.files.concat(files); + return this; + } + + /** + * Sets the author of this embed. + * @param {StringResolvable} name The name of the author + * @param {string} [iconURL] The icon URL of the author + * @param {string} [url] The URL of the author + * @returns {MessageEmbed} + */ + setAuthor(name, iconURL, url) { + this.author = { name: Util.resolveString(name), iconURL, url }; + return this; + } + + /** + * Sets the color of this embed. + * @param {ColorResolvable} color The color of the embed + * @returns {MessageEmbed} + */ + setColor(color) { + this.color = Util.resolveColor(color); + return this; + } + + /** + * Sets the description of this embed. + * @param {StringResolvable} description The description + * @returns {MessageEmbed} + */ + setDescription(description) { + description = Util.resolveString(description); + this.description = description; + return this; + } + + /** + * Sets the footer of this embed. + * @param {StringResolvable} text The text of the footer + * @param {string} [iconURL] The icon URL of the footer + * @returns {MessageEmbed} + */ + setFooter(text, iconURL) { + text = Util.resolveString(text); + this.footer = { text, iconURL }; + return this; + } + + /** + * Sets the image of this embed. + * @param {string} url The URL of the image + * @returns {MessageEmbed} + */ + setImage(url) { + this.image = { url }; + return this; + } + + /** + * Sets the thumbnail of this embed. + * @param {string} url The URL of the thumbnail + * @returns {MessageEmbed} + */ + setThumbnail(url) { + this.thumbnail = { url }; + return this; + } + + /** + * Sets the timestamp of this embed. + * @param {Date|number} [timestamp=Date.now()] The timestamp or date + * @returns {MessageEmbed} + */ + setTimestamp(timestamp = Date.now()) { + if (timestamp instanceof Date) timestamp = timestamp.getTime(); + this.timestamp = timestamp; + return this; + } + + /** + * Sets the title of this embed. + * @param {StringResolvable} title The title + * @returns {MessageEmbed} + */ + setTitle(title) { + title = Util.resolveString(title); + this.title = title; + return this; + } + + /** + * Sets the URL of this embed. + * @param {string} url The URL + * @returns {MessageEmbed} + */ + setURL(url) { + this.url = url; + return this; + } + + /** + * Transforms the embed to a plain object. + * @returns {Object} The raw data of this embed + */ + toJSON() { + return { + title: this.title, + type: 'rich', + description: this.description, + url: this.url, + timestamp: this.timestamp ? new Date(this.timestamp) : null, + color: this.color, + fields: this.fields, + thumbnail: this.thumbnail, + image: this.image, + author: this.author + ? { + name: this.author.name, + url: this.author.url, + icon_url: this.author.iconURL, + } + : null, + footer: this.footer + ? { + text: this.footer.text, + icon_url: this.footer.iconURL, + } + : null, + }; + } + + /** + * Normalizes field input and resolves strings. + * @param {StringResolvable} name The name of the field + * @param {StringResolvable} value The value of the field + * @param {boolean} [inline=false] Set the field to display inline + * @returns {EmbedField} + */ + static normalizeField(name, value, inline = false) { + name = Util.resolveString(name); + if (!name) throw new RangeError('EMBED_FIELD_NAME'); + value = Util.resolveString(value); + if (!value) throw new RangeError('EMBED_FIELD_VALUE'); + return { name, value, inline }; + } + + /** + * @typedef {Object} EmbedFieldData + * @property {StringResolvable} name The name of this field + * @property {StringResolvable} value The value of this field + * @property {boolean} [inline] If this field will be displayed inline + */ + + /** + * Normalizes field input and resolves strings. + * @param {...EmbedFieldData|EmbedFieldData[]} fields Fields to normalize + * @returns {EmbedField[]} + */ + static normalizeFields(...fields) { + return fields + .flat(2) + .map(field => + this.normalizeField( + field && field.name, + field && field.value, + field && typeof field.inline === 'boolean' ? field.inline : false, + ), + ); + } +} + +module.exports = MessageEmbed; diff --git a/node_modules/discord.js/src/structures/MessageMentions.js b/node_modules/discord.js/src/structures/MessageMentions.js new file mode 100644 index 0000000..cbdd1c7 --- /dev/null +++ b/node_modules/discord.js/src/structures/MessageMentions.js @@ -0,0 +1,221 @@ +'use strict'; + +const Collection = require('../util/Collection'); +const { ChannelTypes } = require('../util/Constants'); +const Util = require('../util/Util'); + +/** + * Keeps track of mentions in a {@link Message}. + */ +class MessageMentions { + constructor(message, users, roles, everyone, crosspostedChannels) { + /** + * The client the message is from + * @type {Client} + * @readonly + */ + Object.defineProperty(this, 'client', { value: message.client }); + + /** + * The guild the message is in + * @type {?Guild} + * @readonly + */ + Object.defineProperty(this, 'guild', { value: message.guild }); + + /** + * The initial message content + * @type {string} + * @readonly + * @private + */ + Object.defineProperty(this, '_content', { value: message.content }); + + /** + * Whether `@everyone` or `@here` were mentioned + * @type {boolean} + */ + this.everyone = Boolean(everyone); + + if (users) { + if (users instanceof Collection) { + /** + * Any users that were mentioned + * <info>Order as received from the API, not as they appear in the message content</info> + * @type {Collection<Snowflake, User>} + */ + this.users = new Collection(users); + } else { + this.users = new Collection(); + for (const mention of users) { + if (mention.member && message.guild) { + message.guild.members.add(Object.assign(mention.member, { user: mention })); + } + const user = message.client.users.add(mention); + this.users.set(user.id, user); + } + } + } else { + this.users = new Collection(); + } + + if (roles) { + if (roles instanceof Collection) { + /** + * Any roles that were mentioned + * <info>Order as received from the API, not as they appear in the message content</info> + * @type {Collection<Snowflake, Role>} + */ + this.roles = new Collection(roles); + } else { + this.roles = new Collection(); + for (const mention of roles) { + const role = message.channel.guild.roles.cache.get(mention); + if (role) this.roles.set(role.id, role); + } + } + } else { + this.roles = new Collection(); + } + + /** + * Cached members for {@link MessageMention#members} + * @type {?Collection<Snowflake, GuildMember>} + * @private + */ + this._members = null; + + /** + * Cached channels for {@link MessageMention#channels} + * @type {?Collection<Snowflake, GuildChannel>} + * @private + */ + this._channels = null; + + /** + * Crossposted channel data. + * @typedef {Object} CrosspostedChannel + * @property {string} channelID ID of the mentioned channel + * @property {string} guildID ID of the guild that has the channel + * @property {string} type Type of the channel + * @property {string} name The name of the channel + */ + + if (crosspostedChannels) { + if (crosspostedChannels instanceof Collection) { + /** + * A collection of crossposted channels + * <info>Order as received from the API, not as they appear in the message content</info> + * @type {Collection<Snowflake, CrosspostedChannel>} + */ + this.crosspostedChannels = new Collection(crosspostedChannels); + } else { + this.crosspostedChannels = new Collection(); + const channelTypes = Object.keys(ChannelTypes); + for (const d of crosspostedChannels) { + const type = channelTypes[d.type]; + this.crosspostedChannels.set(d.id, { + channelID: d.id, + guildID: d.guild_id, + type: type ? type.toLowerCase() : 'unknown', + name: d.name, + }); + } + } + } else { + this.crosspostedChannels = new Collection(); + } + } + + /** + * Any members that were mentioned (only in {@link TextChannel}s) + * <info>Order as received from the API, not as they appear in the message content</info> + * @type {?Collection<Snowflake, GuildMember>} + * @readonly + */ + get members() { + if (this._members) return this._members; + if (!this.guild) return null; + this._members = new Collection(); + this.users.forEach(user => { + const member = this.guild.member(user); + if (member) this._members.set(member.user.id, member); + }); + return this._members; + } + + /** + * Any channels that were mentioned + * <info>Order as they appear first in the message content</info> + * @type {Collection<Snowflake, GuildChannel>} + * @readonly + */ + get channels() { + if (this._channels) return this._channels; + this._channels = new Collection(); + let matches; + while ((matches = this.constructor.CHANNELS_PATTERN.exec(this._content)) !== null) { + const chan = this.client.channels.cache.get(matches[1]); + if (chan) this._channels.set(chan.id, chan); + } + return this._channels; + } + + /** + * Checks if a user, guild member, role, or channel is mentioned. + * Takes into account user mentions, role mentions, and @everyone/@here mentions. + * @param {UserResolvable|GuildMember|Role|GuildChannel} data User/GuildMember/Role/Channel to check + * @param {Object} [options] Options + * @param {boolean} [options.ignoreDirect=false] - Whether to ignore direct mentions to the item + * @param {boolean} [options.ignoreRoles=false] - Whether to ignore role mentions to a guild member + * @param {boolean} [options.ignoreEveryone=false] - Whether to ignore everyone/here mentions + * @returns {boolean} + */ + has(data, { ignoreDirect = false, ignoreRoles = false, ignoreEveryone = false } = {}) { + if (!ignoreEveryone && this.everyone) return true; + const GuildMember = require('./GuildMember'); + if (!ignoreRoles && data instanceof GuildMember) { + for (const role of this.roles.values()) if (data.roles.cache.has(role.id)) return true; + } + + if (!ignoreDirect) { + const id = data.id || data; + return this.users.has(id) || this.channels.has(id) || this.roles.has(id); + } + + return false; + } + + toJSON() { + return Util.flatten(this, { + members: true, + channels: true, + }); + } +} + +/** + * Regular expression that globally matches `@everyone` and `@here` + * @type {RegExp} + */ +MessageMentions.EVERYONE_PATTERN = /@(everyone|here)/g; + +/** + * Regular expression that globally matches user mentions like `<@81440962496172032>` + * @type {RegExp} + */ +MessageMentions.USERS_PATTERN = /<@!?(\d{17,19})>/g; + +/** + * Regular expression that globally matches role mentions like `<@&297577916114403338>` + * @type {RegExp} + */ +MessageMentions.ROLES_PATTERN = /<@&(\d{17,19})>/g; + +/** + * Regular expression that globally matches channel mentions like `<#222079895583457280>` + * @type {RegExp} + */ +MessageMentions.CHANNELS_PATTERN = /<#(\d{17,19})>/g; + +module.exports = MessageMentions; diff --git a/node_modules/discord.js/src/structures/MessageReaction.js b/node_modules/discord.js/src/structures/MessageReaction.js new file mode 100644 index 0000000..771626e --- /dev/null +++ b/node_modules/discord.js/src/structures/MessageReaction.js @@ -0,0 +1,135 @@ +'use strict'; + +const GuildEmoji = require('./GuildEmoji'); +const ReactionEmoji = require('./ReactionEmoji'); +const ReactionUserManager = require('../managers/ReactionUserManager'); +const Util = require('../util/Util'); + +/** + * Represents a reaction to a message. + */ +class MessageReaction { + /** + * @param {Client} client The instantiating client + * @param {Object} data The data for the message reaction + * @param {Message} message The message the reaction refers to + */ + constructor(client, data, message) { + /** + * The client that instantiated this message reaction + * @name MessageReaction#client + * @type {Client} + * @readonly + */ + Object.defineProperty(this, 'client', { value: client }); + /** + * The message that this reaction refers to + * @type {Message} + */ + this.message = message; + + /** + * Whether the client has given this reaction + * @type {boolean} + */ + this.me = data.me; + + /** + * A manager of the users that have given this reaction + * @type {ReactionUserManager} + */ + this.users = new ReactionUserManager(client, undefined, this); + + this._emoji = new ReactionEmoji(this, data.emoji); + + this._patch(data); + } + + _patch(data) { + /** + * The number of people that have given the same reaction + * @type {?number} + * @name MessageReaction#count + */ + // eslint-disable-next-line eqeqeq + if (this.count == undefined) this.count = data.count; + } + + /** + * Removes all users from this reaction. + * @returns {Promise<MessageReaction>} + */ + async remove() { + await this.client.api + .channels(this.message.channel.id) + .messages(this.message.id) + .reactions(this._emoji.identifier) + .delete(); + return this; + } + + /** + * The emoji of this reaction, either an GuildEmoji object for known custom emojis, or a ReactionEmoji + * object which has fewer properties. Whatever the prototype of the emoji, it will still have + * `name`, `id`, `identifier` and `toString()` + * @type {GuildEmoji|ReactionEmoji} + * @readonly + */ + get emoji() { + if (this._emoji instanceof GuildEmoji) return this._emoji; + // Check to see if the emoji has become known to the client + if (this._emoji.id) { + const emojis = this.message.client.emojis.cache; + if (emojis.has(this._emoji.id)) { + const emoji = emojis.get(this._emoji.id); + this._emoji = emoji; + return emoji; + } + } + return this._emoji; + } + + /** + * Whether or not this reaction is a partial + * @type {boolean} + * @readonly + */ + get partial() { + return this.count === null; + } + + /** + * Fetch this reaction. + * @returns {Promise<MessageReaction>} + */ + async fetch() { + const message = await this.message.fetch(); + const existing = message.reactions.cache.get(this.emoji.id || this.emoji.name); + // The reaction won't get set when it has been completely removed + this._patch(existing || { count: 0 }); + return this; + } + + toJSON() { + return Util.flatten(this, { emoji: 'emojiID', message: 'messageID' }); + } + + _add(user) { + if (this.partial) return; + this.users.cache.set(user.id, user); + if (!this.me || user.id !== this.message.client.user.id || this.count === 0) this.count++; + if (!this.me) this.me = user.id === this.message.client.user.id; + } + + _remove(user) { + if (this.partial) return; + this.users.cache.delete(user.id); + if (!this.me || user.id !== this.message.client.user.id) this.count--; + if (user.id === this.message.client.user.id) this.me = false; + if (this.count <= 0 && this.users.cache.size === 0) { + this.message.reactions.cache.delete(this.emoji.id || this.emoji.name); + } + } +} + +module.exports = MessageReaction; diff --git a/node_modules/discord.js/src/structures/NewsChannel.js b/node_modules/discord.js/src/structures/NewsChannel.js new file mode 100644 index 0000000..76727fc --- /dev/null +++ b/node_modules/discord.js/src/structures/NewsChannel.js @@ -0,0 +1,18 @@ +'use strict'; + +const TextChannel = require('./TextChannel'); + +/** + * Represents a guild news channel on Discord. + * @extends {TextChannel} + */ +class NewsChannel extends TextChannel { + _patch(data) { + super._patch(data); + + // News channels don't have a rate limit per user, remove it + this.rateLimitPerUser = undefined; + } +} + +module.exports = NewsChannel; diff --git a/node_modules/discord.js/src/structures/PartialGroupDMChannel.js b/node_modules/discord.js/src/structures/PartialGroupDMChannel.js new file mode 100644 index 0000000..e398f23 --- /dev/null +++ b/node_modules/discord.js/src/structures/PartialGroupDMChannel.js @@ -0,0 +1,46 @@ +'use strict'; + +const Channel = require('./Channel'); +const { Error } = require('../errors'); + +/** + * Represents a Partial Group DM Channel on Discord. + * @extends {Channel} + */ +class PartialGroupDMChannel extends Channel { + constructor(client, data) { + super(client, data); + + /** + * The name of this Group DM Channel + * @type {string} + */ + this.name = data.name; + + /** + * The hash of the channel icon + * @type {?string} + */ + this.icon = data.icon; + } + + /** + * The URL to this channel's icon. + * @param {ImageURLOptions} [options={}] Options for the Image URL + * @returns {?string} + */ + iconURL({ format, size } = {}) { + if (!this.icon) return null; + return this.client.rest.cdn.GDMIcon(this.id, this.icon, format, size); + } + + delete() { + return Promise.reject(new Error('DELETE_GROUP_DM_CHANNEL')); + } + + fetch() { + return Promise.reject(new Error('FETCH_GROUP_DM_CHANNEL')); + } +} + +module.exports = PartialGroupDMChannel; diff --git a/node_modules/discord.js/src/structures/PermissionOverwrites.js b/node_modules/discord.js/src/structures/PermissionOverwrites.js new file mode 100644 index 0000000..ea7a45e --- /dev/null +++ b/node_modules/discord.js/src/structures/PermissionOverwrites.js @@ -0,0 +1,189 @@ +'use strict'; + +const Role = require('./Role'); +const { TypeError } = require('../errors'); +const Permissions = require('../util/Permissions'); +const Util = require('../util/Util'); + +/** + * Represents a permission overwrite for a role or member in a guild channel. + */ +class PermissionOverwrites { + constructor(guildChannel, data) { + /** + * The GuildChannel this overwrite is for + * @name PermissionOverwrites#channel + * @type {GuildChannel} + * @readonly + */ + Object.defineProperty(this, 'channel', { value: guildChannel }); + + if (data) this._patch(data); + } + + _patch(data) { + /** + * The ID of this overwrite, either a user ID or a role ID + * @type {Snowflake} + */ + this.id = data.id; + + /** + * The type of a permission overwrite. It can be one of: + * * member + * * role + * @typedef {string} OverwriteType + */ + + /** + * The type of this overwrite + * @type {OverwriteType} + */ + this.type = data.type; + + /** + * The permissions that are denied for the user or role. + * @type {Readonly<Permissions>} + */ + this.deny = new Permissions(data.deny).freeze(); + + /** + * The permissions that are allowed for the user or role. + * @type {Readonly<Permissions>} + */ + this.allow = new Permissions(data.allow).freeze(); + } + + /** + * Updates this permissionOverwrites. + * @param {PermissionOverwriteOptions} options The options for the update + * @param {string} [reason] Reason for creating/editing this overwrite + * @returns {Promise<PermissionOverwrites>} + * @example + * // Update permission overwrites + * permissionOverwrites.update({ + * SEND_MESSAGES: false + * }) + * .then(channel => console.log(channel.permissionOverwrites.get(message.author.id))) + * .catch(console.error); + */ + update(options, reason) { + const { allow, deny } = this.constructor.resolveOverwriteOptions(options, this); + + return this.channel.client.api + .channels(this.channel.id) + .permissions[this.id].put({ + data: { id: this.id, type: this.type, allow: allow.bitfield, deny: deny.bitfield }, + reason, + }) + .then(() => this); + } + + /** + * Deletes this Permission Overwrite. + * @param {string} [reason] Reason for deleting this overwrite + * @returns {Promise<PermissionOverwrites>} + */ + delete(reason) { + return this.channel.client.api.channels[this.channel.id].permissions[this.id].delete({ reason }).then(() => this); + } + + toJSON() { + return Util.flatten(this); + } + + /** + * An object mapping permission flags to `true` (enabled), `null` (unset) or `false` (disabled). + * ```js + * { + * 'SEND_MESSAGES': true, + * 'EMBED_LINKS': null, + * 'ATTACH_FILES': false, + * } + * ``` + * @typedef {Object} PermissionOverwriteOptions + */ + + /** + * @typedef {object} ResolvedOverwriteOptions + * @property {Permissions} allow The allowed permissions + * @property {Permissions} deny The denied permissions + */ + + /** + * Resolves bitfield permissions overwrites from an object. + * @param {PermissionOverwriteOptions} options The options for the update + * @param {Object} initialPermissions The initial permissions + * @param {PermissionResolvable} initialPermissions.allow Initial allowed permissions + * @param {PermissionResolvable} initialPermissions.deny Initial denied permissions + * @returns {ResolvedOverwriteOptions} + */ + static resolveOverwriteOptions(options, { allow, deny } = {}) { + allow = new Permissions(allow); + deny = new Permissions(deny); + + for (const [perm, value] of Object.entries(options)) { + if (value === true) { + allow.add(Permissions.FLAGS[perm]); + deny.remove(Permissions.FLAGS[perm]); + } else if (value === false) { + allow.remove(Permissions.FLAGS[perm]); + deny.add(Permissions.FLAGS[perm]); + } else if (value === null) { + allow.remove(Permissions.FLAGS[perm]); + deny.remove(Permissions.FLAGS[perm]); + } + } + + return { allow, deny }; + } + + /** + * The raw data for a permission overwrite + * @typedef {Object} RawOverwriteData + * @property {Snowflake} id The id of the overwrite + * @property {number} allow The permissions to allow + * @property {number} deny The permissions to deny + * @property {OverwriteType} type The type of this OverwriteData + */ + + /** + * Data that can be resolved into {@link RawOverwriteData} + * @typedef {PermissionOverwrites|OverwriteData} OverwriteResolvable + */ + + /** + * Data that can be used for a permission overwrite + * @typedef {Object} OverwriteData + * @property {GuildMemberResolvable|RoleResolvable} id Member or role this overwrite is for + * @property {PermissionResolvable} [allow] The permissions to allow + * @property {PermissionResolvable} [deny] The permissions to deny + * @property {OverwriteType} [type] The type of this OverwriteData + */ + + /** + * Resolves an overwrite into {@link RawOverwriteData}. + * @param {OverwriteResolvable} overwrite The overwrite-like data to resolve + * @param {Guild} guild The guild to resolve from + * @returns {RawOverwriteData} + */ + static resolve(overwrite, guild) { + if (overwrite instanceof this) return overwrite.toJSON(); + if (typeof overwrite.id === 'string' && ['role', 'member'].includes(overwrite.type)) { + return { ...overwrite, allow: Permissions.resolve(overwrite.allow), deny: Permissions.resolve(overwrite.deny) }; + } + + const userOrRole = guild.roles.resolve(overwrite.id) || guild.client.users.resolve(overwrite.id); + if (!userOrRole) throw new TypeError('INVALID_TYPE', 'parameter', 'User nor a Role', true); + const type = userOrRole instanceof Role ? 'role' : 'member'; + + return { + id: userOrRole.id, + type, + allow: Permissions.resolve(overwrite.allow), + deny: Permissions.resolve(overwrite.deny), + }; + } +} + +module.exports = PermissionOverwrites; diff --git a/node_modules/discord.js/src/structures/Presence.js b/node_modules/discord.js/src/structures/Presence.js new file mode 100644 index 0000000..ac07a54 --- /dev/null +++ b/node_modules/discord.js/src/structures/Presence.js @@ -0,0 +1,336 @@ +'use strict'; + +const Emoji = require('./Emoji'); +const ActivityFlags = require('../util/ActivityFlags'); +const { ActivityTypes } = require('../util/Constants'); +const Util = require('../util/Util'); + +/** + * Activity sent in a message. + * @typedef {Object} MessageActivity + * @property {string} [partyID] Id of the party represented in activity + * @property {number} [type] Type of activity sent + */ + +/** + * The status of this presence: + * * **`online`** - user is online + * * **`idle`** - user is AFK + * * **`offline`** - user is offline or invisible + * * **`dnd`** - user is in Do Not Disturb + * @typedef {string} PresenceStatus + */ + +/** + * The status of this presence: + * * **`online`** - user is online + * * **`idle`** - user is AFK + * * **`dnd`** - user is in Do Not Disturb + * @typedef {string} ClientPresenceStatus + */ + +/** + * Represents a user's presence. + */ +class Presence { + /** + * @param {Client} client The instantiating client + * @param {Object} [data={}] The data for the presence + */ + constructor(client, data = {}) { + /** + * The client that instantiated this + * @name Presence#client + * @type {Client} + * @readonly + */ + Object.defineProperty(this, 'client', { value: client }); + /** + * The user ID of this presence + * @type {Snowflake} + */ + this.userID = data.user.id; + + /** + * The guild of this presence + * @type {?Guild} + */ + this.guild = data.guild || null; + + this.patch(data); + } + + /** + * The user of this presence + * @type {?User} + * @readonly + */ + get user() { + return this.client.users.cache.get(this.userID) || null; + } + + /** + * The member of this presence + * @type {?GuildMember} + * @readonly + */ + get member() { + return this.guild.members.cache.get(this.userID) || null; + } + + patch(data) { + /** + * The status of this presence + * @type {PresenceStatus} + */ + this.status = data.status || this.status || 'offline'; + + if (data.activities) { + /** + * The activities of this presence + * @type {Activity[]} + */ + this.activities = data.activities.map(activity => new Activity(this, activity)); + } else if (data.activity || data.game) { + this.activities = [new Activity(this, data.game || data.activity)]; + } else { + this.activities = []; + } + + /** + * The devices this presence is on + * @type {?Object} + * @property {?ClientPresenceStatus} web The current presence in the web application + * @property {?ClientPresenceStatus} mobile The current presence in the mobile application + * @property {?ClientPresenceStatus} desktop The current presence in the desktop application + */ + this.clientStatus = data.client_status || null; + + return this; + } + + _clone() { + const clone = Object.assign(Object.create(this), this); + if (this.activities) clone.activities = this.activities.map(activity => activity._clone()); + return clone; + } + + /** + * Whether this presence is equal to another. + * @param {Presence} presence The presence to compare with + * @returns {boolean} + */ + equals(presence) { + return ( + this === presence || + (presence && + this.status === presence.status && + this.activities.length === presence.activities.length && + this.activities.every((activity, index) => activity.equals(presence.activities[index])) && + this.clientStatus.web === presence.clientStatus.web && + this.clientStatus.mobile === presence.clientStatus.mobile && + this.clientStatus.desktop === presence.clientStatus.desktop) + ); + } + + toJSON() { + return Util.flatten(this); + } +} + +/** + * Represents an activity that is part of a user's presence. + */ +class Activity { + constructor(presence, data) { + Object.defineProperty(this, 'presence', { value: presence }); + + /** + * The name of the activity being played + * @type {string} + */ + this.name = data.name; + + /** + * The type of the activity status + * @type {ActivityType} + */ + this.type = ActivityTypes[data.type]; + + /** + * If the activity is being streamed, a link to the stream + * @type {?string} + */ + this.url = data.url || null; + + /** + * Details about the activity + * @type {?string} + */ + this.details = data.details || null; + + /** + * State of the activity + * @type {?string} + */ + this.state = data.state || null; + + /** + * Application ID associated with this activity + * @type {?Snowflake} + */ + this.applicationID = data.application_id || null; + + /** + * Timestamps for the activity + * @type {?Object} + * @prop {?Date} start When the activity started + * @prop {?Date} end When the activity will end + */ + this.timestamps = data.timestamps + ? { + start: data.timestamps.start ? new Date(Number(data.timestamps.start)) : null, + end: data.timestamps.end ? new Date(Number(data.timestamps.end)) : null, + } + : null; + + /** + * Party of the activity + * @type {?Object} + * @prop {?string} id ID of the party + * @prop {number[]} size Size of the party as `[current, max]` + */ + this.party = data.party || null; + + /** + * Assets for rich presence + * @type {?RichPresenceAssets} + */ + this.assets = data.assets ? new RichPresenceAssets(this, data.assets) : null; + + this.syncID = data.sync_id; + + /** + * Flags that describe the activity + * @type {Readonly<ActivityFlags>} + */ + this.flags = new ActivityFlags(data.flags).freeze(); + + /** + * Emoji for a custom activity + * @type {?Emoji} + */ + this.emoji = data.emoji ? new Emoji(presence.client, data.emoji) : null; + + /** + * Creation date of the activity + * @type {number} + */ + this.createdTimestamp = new Date(data.created_at).getTime(); + } + + /** + * Whether this activity is equal to another activity. + * @param {Activity} activity The activity to compare with + * @returns {boolean} + */ + equals(activity) { + return ( + this === activity || + (activity && this.name === activity.name && this.type === activity.type && this.url === activity.url) + ); + } + + /** + * The time the activity was created at + * @type {Date} + * @readonly + */ + get createdAt() { + return new Date(this.createdTimestamp); + } + + /** + * When concatenated with a string, this automatically returns the activities' name instead of the Activity object. + * @returns {string} + */ + toString() { + return this.name; + } + + _clone() { + return Object.assign(Object.create(this), this); + } +} + +/** + * Assets for a rich presence + */ +class RichPresenceAssets { + constructor(activity, assets) { + Object.defineProperty(this, 'activity', { value: activity }); + + /** + * Hover text for the large image + * @type {?string} + */ + this.largeText = assets.large_text || null; + + /** + * Hover text for the small image + * @type {?string} + */ + this.smallText = assets.small_text || null; + + /** + * ID of the large image asset + * @type {?Snowflake} + */ + this.largeImage = assets.large_image || null; + + /** + * ID of the small image asset + * @type {?Snowflake} + */ + this.smallImage = assets.small_image || null; + } + + /** + * Gets the URL of the small image asset + * @param {Object} [options] Options for the image url + * @param {string} [options.format] Format of the image + * @param {number} [options.size] Size of the image + * @returns {?string} The small image URL + */ + smallImageURL({ format, size } = {}) { + if (!this.smallImage) return null; + return this.activity.presence.client.rest.cdn.AppAsset(this.activity.applicationID, this.smallImage, { + format, + size, + }); + } + + /** + * Gets the URL of the large image asset + * @param {Object} [options] Options for the image url + * @param {string} [options.format] Format of the image + * @param {number} [options.size] Size of the image + * @returns {?string} The large image URL + */ + largeImageURL({ format, size } = {}) { + if (!this.largeImage) return null; + if (/^spotify:/.test(this.largeImage)) { + return `https://i.scdn.co/image/${this.largeImage.slice(8)}`; + } else if (/^twitch:/.test(this.largeImage)) { + return `https://static-cdn.jtvnw.net/previews-ttv/live_user_${this.largeImage.slice(7)}.png`; + } + return this.activity.presence.client.rest.cdn.AppAsset(this.activity.applicationID, this.largeImage, { + format, + size, + }); + } +} + +exports.Presence = Presence; +exports.Activity = Activity; +exports.RichPresenceAssets = RichPresenceAssets; diff --git a/node_modules/discord.js/src/structures/ReactionCollector.js b/node_modules/discord.js/src/structures/ReactionCollector.js new file mode 100644 index 0000000..6da9d17 --- /dev/null +++ b/node_modules/discord.js/src/structures/ReactionCollector.js @@ -0,0 +1,190 @@ +'use strict'; + +const Collector = require('./interfaces/Collector'); +const Collection = require('../util/Collection'); +const { Events } = require('../util/Constants'); + +/** + * @typedef {CollectorOptions} ReactionCollectorOptions + * @property {number} max The maximum total amount of reactions to collect + * @property {number} maxEmojis The maximum number of emojis to collect + * @property {number} maxUsers The maximum number of users to react + */ + +/** + * Collects reactions on messages. + * Will automatically stop if the message (`'messageDelete'`), + * channel (`'channelDelete'`), or guild (`'guildDelete'`) are deleted. + * @extends {Collector} + */ +class ReactionCollector extends Collector { + /** + * @param {Message} message The message upon which to collect reactions + * @param {CollectorFilter} filter The filter to apply to this collector + * @param {ReactionCollectorOptions} [options={}] The options to apply to this collector + */ + constructor(message, filter, options = {}) { + super(message.client, filter, options); + + /** + * The message upon which to collect reactions + * @type {Message} + */ + this.message = message; + + /** + * The users which have reacted to this message + * @type {Collection} + */ + this.users = new Collection(); + + /** + * The total number of reactions collected + * @type {number} + */ + this.total = 0; + + this.empty = this.empty.bind(this); + this._handleChannelDeletion = this._handleChannelDeletion.bind(this); + this._handleGuildDeletion = this._handleGuildDeletion.bind(this); + this._handleMessageDeletion = this._handleMessageDeletion.bind(this); + + if (this.client.getMaxListeners() !== 0) this.client.setMaxListeners(this.client.getMaxListeners() + 1); + this.client.on(Events.MESSAGE_REACTION_ADD, this.handleCollect); + this.client.on(Events.MESSAGE_REACTION_REMOVE, this.handleDispose); + this.client.on(Events.MESSAGE_REACTION_REMOVE_ALL, this.empty); + this.client.on(Events.MESSAGE_DELETE, this._handleMessageDeletion); + this.client.on(Events.CHANNEL_DELETE, this._handleChannelDeletion); + this.client.on(Events.GUILD_DELETE, this._handleGuildDeletion); + + this.once('end', () => { + this.client.removeListener(Events.MESSAGE_REACTION_ADD, this.handleCollect); + this.client.removeListener(Events.MESSAGE_REACTION_REMOVE, this.handleDispose); + this.client.removeListener(Events.MESSAGE_REACTION_REMOVE_ALL, this.empty); + this.client.removeListener(Events.MESSAGE_DELETE, this._handleMessageDeletion); + this.client.removeListener(Events.CHANNEL_DELETE, this._handleChannelDeletion); + this.client.removeListener(Events.GUILD_DELETE, this._handleGuildDeletion); + if (this.client.getMaxListeners() !== 0) this.client.setMaxListeners(this.client.getMaxListeners() - 1); + }); + + this.on('collect', (reaction, user) => { + this.total++; + this.users.set(user.id, user); + }); + + this.on('remove', (reaction, user) => { + this.total--; + if (!this.collected.some(r => r.users.cache.has(user.id))) this.users.delete(user.id); + }); + } + + /** + * Handles an incoming reaction for possible collection. + * @param {MessageReaction} reaction The reaction to possibly collect + * @returns {?Snowflake|string} + * @private + */ + collect(reaction) { + /** + * Emitted whenever a reaction is collected. + * @event ReactionCollector#collect + * @param {MessageReaction} reaction The reaction that was collected + * @param {User} user The user that added the reaction + */ + if (reaction.message.id !== this.message.id) return null; + return ReactionCollector.key(reaction); + } + + /** + * Handles a reaction deletion for possible disposal. + * @param {MessageReaction} reaction The reaction to possibly dispose of + * @param {User} user The user that removed the reaction + * @returns {?Snowflake|string} + */ + dispose(reaction, user) { + /** + * Emitted whenever a reaction is disposed of. + * @event ReactionCollector#dispose + * @param {MessageReaction} reaction The reaction that was disposed of + * @param {User} user The user that removed the reaction + */ + if (reaction.message.id !== this.message.id) return null; + + /** + * Emitted whenever a reaction is removed from a message. Will emit on all reaction removals, + * as opposed to {@link Collector#dispose} which will only be emitted when the entire reaction + * is removed. + * @event ReactionCollector#remove + * @param {MessageReaction} reaction The reaction that was removed + * @param {User} user The user that removed the reaction + */ + if (this.collected.has(ReactionCollector.key(reaction)) && this.users.has(user.id)) { + this.emit('remove', reaction, user); + } + return reaction.count ? null : ReactionCollector.key(reaction); + } + + /** + * Empties this reaction collector. + */ + empty() { + this.total = 0; + this.collected.clear(); + this.users.clear(); + this.checkEnd(); + } + + endReason() { + if (this.options.max && this.total >= this.options.max) return 'limit'; + if (this.options.maxEmojis && this.collected.size >= this.options.maxEmojis) return 'emojiLimit'; + if (this.options.maxUsers && this.users.size >= this.options.maxUsers) return 'userLimit'; + return null; + } + + /** + * Handles checking if the message has been deleted, and if so, stops the collector with the reason 'messageDelete'. + * @private + * @param {Message} message The message that was deleted + * @returns {void} + */ + _handleMessageDeletion(message) { + if (message.id === this.message.id) { + this.stop('messageDelete'); + } + } + + /** + * Handles checking if the channel has been deleted, and if so, stops the collector with the reason 'channelDelete'. + * @private + * @param {GuildChannel} channel The channel that was deleted + * @returns {void} + */ + _handleChannelDeletion(channel) { + if (channel.id === this.message.channel.id) { + this.stop('channelDelete'); + } + } + + /** + * Handles checking if the guild has been deleted, and if so, stops the collector with the reason 'guildDelete'. + * @private + * @param {Guild} guild The guild that was deleted + * @returns {void} + */ + _handleGuildDeletion(guild) { + if (this.message.guild && guild.id === this.message.guild.id) { + this.stop('guildDelete'); + } + } + + /** + * Gets the collector key for a reaction. + * @param {MessageReaction} reaction The message reaction to get the key for + * @returns {Snowflake|string} + */ + static key(reaction) { + return reaction.emoji.id || reaction.emoji.name; + } +} + +module.exports = ReactionCollector; diff --git a/node_modules/discord.js/src/structures/ReactionEmoji.js b/node_modules/discord.js/src/structures/ReactionEmoji.js new file mode 100644 index 0000000..5c4bc13 --- /dev/null +++ b/node_modules/discord.js/src/structures/ReactionEmoji.js @@ -0,0 +1,31 @@ +'use strict'; + +const Emoji = require('./Emoji'); +const Util = require('../util/Util'); + +/** + * Represents a limited emoji set used for both custom and unicode emojis. Custom emojis + * will use this class opposed to the Emoji class when the client doesn't know enough + * information about them. + * @extends {Emoji} + */ +class ReactionEmoji extends Emoji { + constructor(reaction, emoji) { + super(reaction.message.client, emoji); + /** + * The message reaction this emoji refers to + * @type {MessageReaction} + */ + this.reaction = reaction; + } + + toJSON() { + return Util.flatten(this, { identifier: true }); + } + + valueOf() { + return this.id; + } +} + +module.exports = ReactionEmoji; diff --git a/node_modules/discord.js/src/structures/Role.js b/node_modules/discord.js/src/structures/Role.js new file mode 100644 index 0000000..bba198e --- /dev/null +++ b/node_modules/discord.js/src/structures/Role.js @@ -0,0 +1,403 @@ +'use strict'; + +const Base = require('./Base'); +const { Error, TypeError } = require('../errors'); +const Permissions = require('../util/Permissions'); +const Snowflake = require('../util/Snowflake'); +const Util = require('../util/Util'); + +/** + * Represents a role on Discord. + * @extends {Base} + */ +class Role extends Base { + /** + * @param {Client} client The instantiating client + * @param {Object} data The data for the role + * @param {Guild} guild The guild the role is part of + */ + constructor(client, data, guild) { + super(client); + + /** + * The guild that the role belongs to + * @type {Guild} + */ + this.guild = guild; + + if (data) this._patch(data); + } + + _patch(data) { + /** + * The ID of the role (unique to the guild it is part of) + * @type {Snowflake} + */ + this.id = data.id; + + /** + * The name of the role + * @type {string} + */ + this.name = data.name; + + /** + * The base 10 color of the role + * @type {number} + */ + this.color = data.color; + + /** + * If true, users that are part of this role will appear in a separate category in the users list + * @type {boolean} + */ + this.hoist = data.hoist; + + /** + * The raw position of the role from the API + * @type {number} + */ + this.rawPosition = data.position; + + /** + * The permissions of the role + * @type {Readonly<Permissions>} + */ + this.permissions = new Permissions(data.permissions).freeze(); + + /** + * Whether or not the role is managed by an external service + * @type {boolean} + */ + this.managed = data.managed; + + /** + * Whether or not the role can be mentioned by anyone + * @type {boolean} + */ + this.mentionable = data.mentionable; + + /** + * Whether the role has been deleted + * @type {boolean} + */ + this.deleted = false; + } + + /** + * The timestamp the role was created at + * @type {number} + * @readonly + */ + get createdTimestamp() { + return Snowflake.deconstruct(this.id).timestamp; + } + + /** + * The time the role was created at + * @type {Date} + * @readonly + */ + get createdAt() { + return new Date(this.createdTimestamp); + } + + /** + * The hexadecimal version of the role color, with a leading hashtag + * @type {string} + * @readonly + */ + get hexColor() { + return `#${this.color.toString(16).padStart(6, '0')}`; + } + + /** + * The cached guild members that have this role + * @type {Collection<Snowflake, GuildMember>} + * @readonly + */ + get members() { + return this.guild.members.cache.filter(m => m.roles.cache.has(this.id)); + } + + /** + * Whether the role is editable by the client user + * @type {boolean} + * @readonly + */ + get editable() { + if (this.managed) return false; + const clientMember = this.guild.member(this.client.user); + if (!clientMember.permissions.has(Permissions.FLAGS.MANAGE_ROLES)) return false; + return clientMember.roles.highest.comparePositionTo(this) > 0; + } + + /** + * The position of the role in the role manager + * @type {number} + * @readonly + */ + get position() { + const sorted = this.guild._sortedRoles(); + return sorted.array().indexOf(sorted.get(this.id)); + } + + /** + * Compares this role's position to another role's. + * @param {RoleResolvable} role Role to compare to this one + * @returns {number} Negative number if this role's position is lower (other role's is higher), + * positive number if this one is higher (other's is lower), 0 if equal + */ + comparePositionTo(role) { + role = this.guild.roles.resolve(role); + if (!role) throw new TypeError('INVALID_TYPE', 'role', 'Role nor a Snowflake'); + return this.constructor.comparePositions(this, role); + } + + /** + * The data for a role. + * @typedef {Object} RoleData + * @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 + * @property {boolean} [hoist] Whether or not the role should be hoisted + * @property {number} [position] The position of the role + * @property {PermissionResolvable} [permissions] The permissions of the role + * @property {boolean} [mentionable] Whether or not the role should be mentionable + */ + + /** + * Edits the role. + * @param {RoleData} data The new data for the role + * @param {string} [reason] Reason for editing this role + * @returns {Promise<Role>} + * @example + * // Edit a role + * role.edit({ name: 'new role' }) + * .then(updated => console.log(`Edited role ${updated.name} name to ${updated.name}`)) + * .catch(console.error); + */ + async edit(data, reason) { + if (typeof data.permissions !== 'undefined') data.permissions = Permissions.resolve(data.permissions); + else data.permissions = this.permissions.bitfield; + if (typeof data.position !== 'undefined') { + await Util.setPosition( + this, + data.position, + false, + this.guild._sortedRoles(), + this.client.api.guilds(this.guild.id).roles, + reason, + ).then(updatedRoles => { + this.client.actions.GuildRolesPositionUpdate.handle({ + guild_id: this.guild.id, + roles: updatedRoles, + }); + }); + } + return this.client.api.guilds[this.guild.id].roles[this.id] + .patch({ + data: { + name: data.name || this.name, + color: data.color !== null ? Util.resolveColor(data.color || this.color) : null, + hoist: typeof data.hoist !== 'undefined' ? data.hoist : this.hoist, + permissions: data.permissions, + mentionable: typeof data.mentionable !== 'undefined' ? data.mentionable : this.mentionable, + }, + reason, + }) + .then(role => { + const clone = this._clone(); + clone._patch(role); + return clone; + }); + } + + /** + * Returns `channel.permissionsFor(role)`. Returns permissions for a role in a guild channel, + * taking into account permission overwrites. + * @param {ChannelResolvable} channel The guild channel to use as context + * @returns {Readonly<Permissions>} + */ + permissionsIn(channel) { + channel = this.guild.channels.resolve(channel); + if (!channel) throw new Error('GUILD_CHANNEL_RESOLVE'); + return channel.rolePermissions(this); + } + + /** + * Sets a new name for the role. + * @param {string} name The new name of the role + * @param {string} [reason] Reason for changing the role's name + * @returns {Promise<Role>} + * @example + * // Set the name of the role + * role.setName('new role') + * .then(updated => console.log(`Edited name of role ${role.name} to ${updated.name}`)) + * .catch(console.error); + */ + setName(name, reason) { + return this.edit({ name }, reason); + } + + /** + * Sets a new color for the role. + * @param {ColorResolvable} color The color of the role + * @param {string} [reason] Reason for changing the role's color + * @returns {Promise<Role>} + * @example + * // Set the color of a role + * role.setColor('#FF0000') + * .then(updated => console.log(`Set color of role to ${updated.color}`)) + * .catch(console.error); + */ + setColor(color, reason) { + return this.edit({ color }, reason); + } + + /** + * Sets whether or not the role should be hoisted. + * @param {boolean} hoist Whether or not to hoist the role + * @param {string} [reason] Reason for setting whether or not the role should be hoisted + * @returns {Promise<Role>} + * @example + * // Set the hoist of the role + * role.setHoist(true) + * .then(r => console.log(`Role hoisted: ${r.hoist}`)) + * .catch(console.error); + */ + setHoist(hoist, reason) { + return this.edit({ hoist }, reason); + } + + /** + * Sets the permissions of the role. + * @param {PermissionResolvable} permissions The permissions of the role + * @param {string} [reason] Reason for changing the role's permissions + * @returns {Promise<Role>} + * @example + * // Set the permissions of the role + * role.setPermissions(['KICK_MEMBERS', 'BAN_MEMBERS']) + * .then(updated => console.log(`Updated permissions to ${updated.permissions.bitfield}`)) + * .catch(console.error); + * @example + * // Remove all permissions from a role + * role.setPermissions(0) + * .then(updated => console.log(`Updated permissions to ${updated.permissions.bitfield}`)) + * .catch(console.error); + */ + setPermissions(permissions, reason) { + return this.edit({ permissions }, reason); + } + + /** + * Sets whether this role is mentionable. + * @param {boolean} mentionable Whether this role should be mentionable + * @param {string} [reason] Reason for setting whether or not this role should be mentionable + * @returns {Promise<Role>} + * @example + * // Make the role mentionable + * role.setMentionable(true) + * .then(updated => console.log(`Role updated ${updated.name}`)) + * .catch(console.error); + */ + setMentionable(mentionable, reason) { + return this.edit({ mentionable }, reason); + } + + /** + * Sets the position of the role. + * @param {number} position The position of the role + * @param {Object} [options] Options for setting position + * @param {boolean} [options.relative=false] Change the position relative to its current value + * @param {string} [options.reason] Reason for changing the position + * @returns {Promise<Role>} + * @example + * // Set the position of the role + * role.setPosition(1) + * .then(updated => console.log(`Role position: ${updated.position}`)) + * .catch(console.error); + */ + setPosition(position, { relative, reason } = {}) { + return Util.setPosition( + this, + position, + relative, + this.guild._sortedRoles(), + this.client.api.guilds(this.guild.id).roles, + reason, + ).then(updatedRoles => { + this.client.actions.GuildRolesPositionUpdate.handle({ + guild_id: this.guild.id, + roles: updatedRoles, + }); + return this; + }); + } + + /** + * Deletes the role. + * @param {string} [reason] Reason for deleting this role + * @returns {Promise<Role>} + * @example + * // Delete a role + * role.delete('The role needed to go') + * .then(deleted => console.log(`Deleted role ${deleted.name}`)) + * .catch(console.error); + */ + delete(reason) { + return this.client.api.guilds[this.guild.id].roles[this.id].delete({ reason }).then(() => { + this.client.actions.GuildRoleDelete.handle({ guild_id: this.guild.id, role_id: this.id }); + return this; + }); + } + + /** + * Whether this role equals another role. It compares all properties, so for most operations + * it is advisable to just compare `role.id === role2.id` as it is much faster and is often + * what most users need. + * @param {Role} role Role to compare with + * @returns {boolean} + */ + equals(role) { + return ( + role && + this.id === role.id && + this.name === role.name && + this.color === role.color && + this.hoist === role.hoist && + this.position === role.position && + this.permissions.bitfield === role.permissions.bitfield && + this.managed === role.managed + ); + } + + /** + * When concatenated with a string, this automatically returns the role's mention instead of the Role object. + * @returns {string} + * @example + * // Logs: Role: <@&123456789012345678> + * console.log(`Role: ${role}`); + */ + toString() { + if (this.id === this.guild.id) return '@everyone'; + return `<@&${this.id}>`; + } + + toJSON() { + return super.toJSON({ createdTimestamp: true }); + } + + /** + * Compares the positions of two roles. + * @param {Role} role1 First role to compare + * @param {Role} role2 Second role to compare + * @returns {number} Negative number if the first role's position is lower (second role's is higher), + * positive number if the first's is higher (second's is lower), 0 if equal + */ + static comparePositions(role1, role2) { + if (role1.position === role2.position) return role2.id - role1.id; + return role1.position - role2.position; + } +} + +module.exports = Role; diff --git a/node_modules/discord.js/src/structures/StoreChannel.js b/node_modules/discord.js/src/structures/StoreChannel.js new file mode 100644 index 0000000..87cb040 --- /dev/null +++ b/node_modules/discord.js/src/structures/StoreChannel.js @@ -0,0 +1,22 @@ +'use strict'; + +const GuildChannel = require('./GuildChannel'); + +/** + * Represents a guild store channel on Discord. + * @extends {GuildChannel} + */ +class StoreChannel extends GuildChannel { + _patch(data) { + super._patch(data); + + /** + * If the guild considers this channel NSFW + * @type {boolean} + * @readonly + */ + this.nsfw = data.nsfw; + } +} + +module.exports = StoreChannel; diff --git a/node_modules/discord.js/src/structures/Team.js b/node_modules/discord.js/src/structures/Team.js new file mode 100644 index 0000000..a28c5a2 --- /dev/null +++ b/node_modules/discord.js/src/structures/Team.js @@ -0,0 +1,109 @@ +'use strict'; + +const Base = require('./Base'); +const TeamMember = require('./TeamMember'); +const Collection = require('../util/Collection'); +const Snowflake = require('../util/Snowflake'); + +/** + * Represents a Client OAuth2 Application Team. + * @extends {Base} + */ +class Team extends Base { + constructor(client, data) { + super(client); + this._patch(data); + } + + _patch(data) { + /** + * The ID of the Team + * @type {Snowflake} + */ + this.id = data.id; + + /** + * The name of the Team + * @type {string} + */ + this.name = data.name; + + /** + * The Team's icon hash + * @type {?string} + */ + this.icon = data.icon || null; + + /** + * The Team's owner id + * @type {?string} + */ + this.ownerID = data.owner_user_id || null; + + /** + * The Team's members + * @type {Collection<Snowflake, TeamMember>} + */ + this.members = new Collection(); + + for (const memberData of data.members) { + const member = new TeamMember(this, memberData); + this.members.set(member.id, member); + } + } + + /** + * The owner of this team + * @type {?TeamMember} + * @readonly + */ + get owner() { + return this.members.get(this.ownerID) || null; + } + + /** + * The timestamp the team was created at + * @type {number} + * @readonly + */ + get createdTimestamp() { + return Snowflake.deconstruct(this.id).timestamp; + } + + /** + * The time the team was created at + * @type {Date} + * @readonly + */ + get createdAt() { + return new Date(this.createdTimestamp); + } + + /** + * A link to the teams's icon. + * @param {ImageURLOptions} [options={}] Options for the Image URL + * @returns {?string} URL to the icon + */ + iconURL({ format, size } = {}) { + if (!this.icon) return null; + return this.client.rest.cdn.TeamIcon(this.id, this.icon, { format, size }); + } + + /** + * When concatenated with a string, this automatically returns the Team's name instead of the + * Team object. + * @returns {string} + * @example + * // Logs: Team name: My Team + * console.log(`Team name: ${team}`); + */ + toString() { + return this.name; + } + + toJSON() { + return super.toJSON({ createdTimestamp: true }); + } +} + +module.exports = Team; diff --git a/node_modules/discord.js/src/structures/TeamMember.js b/node_modules/discord.js/src/structures/TeamMember.js new file mode 100644 index 0000000..ba7ecd2 --- /dev/null +++ b/node_modules/discord.js/src/structures/TeamMember.js @@ -0,0 +1,65 @@ +'use strict'; + +const Base = require('./Base'); +const { MembershipStates } = require('../util/Constants'); + +/** + * Represents a Client OAuth2 Application Team Member. + * @extends {Base} + */ +class TeamMember extends Base { + constructor(team, data) { + super(team.client); + + /** + * The Team this member is part of + * @type {Team} + */ + this.team = team; + + this._patch(data); + } + + _patch(data) { + /** + * The permissions this Team Member has with regard to the team + * @type {string[]} + */ + this.permissions = data.permissions; + + /** + * The permissions this Team Member has with regard to the team + * @type {MembershipStates} + */ + this.membershipState = MembershipStates[data.membership_state]; + + /** + * The user for this Team Member + * @type {User} + */ + this.user = this.client.users.add(data.user); + } + + /** + * The ID of the Team Member + * @type {Snowflake} + * @readonly + */ + get id() { + return this.user.id; + } + + /** + * When concatenated with a string, this automatically returns the team members's mention instead of the + * TeamMember object. + * @returns {string} + * @example + * // Logs: Team Member's mention: <@123456789012345678> + * console.log(`Team Member's mention: ${teamMember}`); + */ + toString() { + return this.user.toString(); + } +} + +module.exports = TeamMember; diff --git a/node_modules/discord.js/src/structures/TextChannel.js b/node_modules/discord.js/src/structures/TextChannel.js new file mode 100644 index 0000000..65ec13d --- /dev/null +++ b/node_modules/discord.js/src/structures/TextChannel.js @@ -0,0 +1,151 @@ +'use strict'; + +const GuildChannel = require('./GuildChannel'); +const Webhook = require('./Webhook'); +const TextBasedChannel = require('./interfaces/TextBasedChannel'); +const MessageManager = require('../managers/MessageManager'); +const Collection = require('../util/Collection'); +const DataResolver = require('../util/DataResolver'); + +/** + * Represents a guild text channel on Discord. + * @extends {GuildChannel} + * @implements {TextBasedChannel} + */ +class TextChannel extends GuildChannel { + /** + * @param {Guild} guild The guild the text channel is part of + * @param {Object} data The data for the text channel + */ + constructor(guild, data) { + super(guild, data); + /** + * A manager of the messages sent to this channel + * @type {MessageManager} + */ + this.messages = new MessageManager(this); + this._typing = new Map(); + } + + _patch(data) { + super._patch(data); + + /** + * The topic of the text channel + * @type {?string} + */ + this.topic = data.topic; + + /** + * If the guild considers this channel NSFW + * @type {boolean} + * @readonly + */ + this.nsfw = data.nsfw; + + /** + * The ID of the last message sent in this channel, if one was sent + * @type {?Snowflake} + */ + this.lastMessageID = data.last_message_id; + + /** + * The ratelimit per user for this channel in seconds + * @type {number} + */ + this.rateLimitPerUser = data.rate_limit_per_user || 0; + + /** + * The timestamp when the last pinned message was pinned, if there was one + * @type {?number} + */ + this.lastPinTimestamp = data.last_pin_timestamp ? new Date(data.last_pin_timestamp).getTime() : null; + + if (data.messages) for (const message of data.messages) this.messages.add(message); + } + + /** + * Sets the rate limit per user for this channel. + * @param {number} rateLimitPerUser The new ratelimit in seconds + * @param {string} [reason] Reason for changing the channel's ratelimits + * @returns {Promise<TextChannel>} + */ + setRateLimitPerUser(rateLimitPerUser, reason) { + return this.edit({ rateLimitPerUser }, reason); + } + + /** + * Sets whether this channel is flagged as NSFW. + * @param {boolean} nsfw Whether the channel should be considered NSFW + * @param {string} [reason] Reason for changing the channel's NSFW flag + * @returns {Promise<TextChannel>} + */ + setNSFW(nsfw, reason) { + return this.edit({ nsfw }, reason); + } + + /** + * Fetches all webhooks for the channel. + * @returns {Promise<Collection<Snowflake, Webhook>>} + * @example + * // Fetch webhooks + * channel.fetchWebhooks() + * .then(hooks => console.log(`This channel has ${hooks.size} hooks`)) + * .catch(console.error); + */ + fetchWebhooks() { + return this.client.api.channels[this.id].webhooks.get().then(data => { + const hooks = new Collection(); + for (const hook of data) hooks.set(hook.id, new Webhook(this.client, hook)); + return hooks; + }); + } + + /** + * Creates a webhook for the channel. + * @param {string} name The name of the webhook + * @param {Object} [options] Options for creating the webhook + * @param {BufferResolvable|Base64Resolvable} [options.avatar] Avatar for the webhook + * @param {string} [options.reason] Reason for creating the webhook + * @returns {Promise<Webhook>} webhook The created webhook + * @example + * // Create a webhook for the current channel + * channel.createWebhook('Snek', { + * avatar: 'https://i.imgur.com/mI8XcpG.jpg', + * reason: 'Needed a cool new Webhook' + * }) + * .then(console.log) + * .catch(console.error) + */ + async createWebhook(name, { avatar, reason } = {}) { + if (typeof avatar === 'string' && !avatar.startsWith('data:')) { + avatar = await DataResolver.resolveImage(avatar); + } + return this.client.api.channels[this.id].webhooks + .post({ + data: { + name, + avatar, + }, + reason, + }) + .then(data => new Webhook(this.client, data)); + } + + // These are here only for documentation purposes - they are implemented by TextBasedChannel + /* eslint-disable no-empty-function */ + get lastMessage() {} + get lastPinAt() {} + send() {} + startTyping() {} + stopTyping() {} + get typing() {} + get typingCount() {} + createMessageCollector() {} + awaitMessages() {} + bulkDelete() {} +} + +TextBasedChannel.applyToClass(TextChannel, true); + +module.exports = TextChannel; diff --git a/node_modules/discord.js/src/structures/User.js b/node_modules/discord.js/src/structures/User.js new file mode 100644 index 0000000..b2a6500 --- /dev/null +++ b/node_modules/discord.js/src/structures/User.js @@ -0,0 +1,318 @@ +'use strict'; + +const Base = require('./Base'); +const { Presence } = require('./Presence'); +const TextBasedChannel = require('./interfaces/TextBasedChannel'); +const { Error } = require('../errors'); +const Snowflake = require('../util/Snowflake'); +const UserFlags = require('../util/UserFlags'); + +/** + * Represents a user on Discord. + * @implements {TextBasedChannel} + * @extends {Base} + */ +class User extends Base { + /** + * @param {Client} client The instantiating client + * @param {Object} data The data for the user + */ + constructor(client, data) { + super(client); + + /** + * The ID of the user + * @type {Snowflake} + */ + this.id = data.id; + + /** + * Whether or not the user is a bot + * @type {boolean} + * @name User#bot + */ + this.bot = Boolean(data.bot); + + this._patch(data); + } + + _patch(data) { + /** + * The username of the user + * @type {?string} + * @name User#username + */ + if (data.username) this.username = data.username; + + /** + * A discriminator based on username for the user + * @type {?string} + * @name User#discriminator + */ + if (data.discriminator) this.discriminator = data.discriminator; + + /** + * The ID of the user's avatar + * @type {?string} + * @name User#avatar + */ + if (typeof data.avatar !== 'undefined') this.avatar = data.avatar; + + if (typeof data.bot !== 'undefined') this.bot = Boolean(data.bot); + + /** + * Whether the user is an Official Discord System user (part of the urgent message system) + * @type {?boolean} + * @name User#system + */ + if (typeof data.system !== 'undefined') this.system = Boolean(data.system); + + /** + * The locale of the user's client (ISO 639-1) + * @type {?string} + * @name User#locale + */ + if (data.locale) this.locale = data.locale; + + /** + * The flags for this user + * @type {?UserFlags} + */ + if (typeof data.public_flags !== 'undefined') this.flags = new UserFlags(data.public_flags); + + /** + * The ID of the last message sent by the user, if one was sent + * @type {?Snowflake} + */ + this.lastMessageID = null; + + /** + * The ID of the channel for the last message sent by the user, if one was sent + * @type {?Snowflake} + */ + this.lastMessageChannelID = null; + } + + /** + * Whether this User is a partial + * @type {boolean} + * @readonly + */ + get partial() { + return typeof this.username !== 'string'; + } + + /** + * The timestamp the user was created at + * @type {number} + * @readonly + */ + get createdTimestamp() { + return Snowflake.deconstruct(this.id).timestamp; + } + + /** + * The time the user was created at + * @type {Date} + * @readonly + */ + get createdAt() { + return new Date(this.createdTimestamp); + } + + /** + * The Message object of the last message sent by the user, if one was sent + * @type {?Message} + * @readonly + */ + get lastMessage() { + const channel = this.client.channels.cache.get(this.lastMessageChannelID); + return (channel && channel.messages.cache.get(this.lastMessageID)) || null; + } + + /** + * The presence of this user + * @type {Presence} + * @readonly + */ + get presence() { + for (const guild of this.client.guilds.cache.values()) { + if (guild.presences.cache.has(this.id)) return guild.presences.cache.get(this.id); + } + return new Presence(this.client, { user: { id: this.id } }); + } + + /** + * A link to the user's avatar. + * @param {ImageURLOptions} [options={}] Options for the Image URL + * @returns {?string} + */ + avatarURL({ format, size, dynamic } = {}) { + if (!this.avatar) return null; + return this.client.rest.cdn.Avatar(this.id, this.avatar, format, size, dynamic); + } + + /** + * A link to the user's default avatar + * @type {string} + * @readonly + */ + get defaultAvatarURL() { + return this.client.rest.cdn.DefaultAvatar(this.discriminator % 5); + } + + /** + * A link to the user's avatar if they have one. + * Otherwise a link to their default avatar will be returned. + * @param {ImageURLOptions} [options={}] Options for the Image URL + * @returns {string} + */ + displayAvatarURL(options) { + return this.avatarURL(options) || this.defaultAvatarURL; + } + + /** + * The Discord "tag" (e.g. `hydrabolt#0001`) for this user + * @type {?string} + * @readonly + */ + get tag() { + return typeof this.username === 'string' ? `${this.username}#${this.discriminator}` : null; + } + + /** + * Checks whether the user is typing in a channel. + * @param {ChannelResolvable} channel The channel to check in + * @returns {boolean} + */ + typingIn(channel) { + channel = this.client.channels.resolve(channel); + return channel._typing.has(this.id); + } + + /** + * Gets the time that the user started typing. + * @param {ChannelResolvable} channel The channel to get the time in + * @returns {?Date} + */ + typingSinceIn(channel) { + channel = this.client.channels.resolve(channel); + return channel._typing.has(this.id) ? new Date(channel._typing.get(this.id).since) : null; + } + + /** + * Gets the amount of time the user has been typing in a channel for (in milliseconds), or -1 if they're not typing. + * @param {ChannelResolvable} channel The channel to get the time in + * @returns {number} + */ + typingDurationIn(channel) { + channel = this.client.channels.resolve(channel); + return channel._typing.has(this.id) ? channel._typing.get(this.id).elapsedTime : -1; + } + + /** + * The DM between the client's user and this user + * @type {?DMChannel} + * @readonly + */ + get dmChannel() { + return this.client.channels.cache.find(c => c.type === 'dm' && c.recipient.id === this.id) || null; + } + + /** + * Creates a DM channel between the client and the user. + * @returns {Promise<DMChannel>} + */ + async createDM() { + const { dmChannel } = this; + if (dmChannel && !dmChannel.partial) return dmChannel; + const data = await this.client.api.users(this.client.user.id).channels.post({ + data: { + recipient_id: this.id, + }, + }); + return this.client.actions.ChannelCreate.handle(data).channel; + } + + /** + * Deletes a DM channel (if one exists) between the client and the user. Resolves with the channel if successful. + * @returns {Promise<DMChannel>} + */ + async deleteDM() { + const { dmChannel } = this; + if (!dmChannel) throw new Error('USER_NO_DMCHANNEL'); + const data = await this.client.api.channels(dmChannel.id).delete(); + return this.client.actions.ChannelDelete.handle(data).channel; + } + + /** + * Checks if the user is equal to another. It compares ID, username, discriminator, avatar, and bot flags. + * It is recommended to compare equality by using `user.id === user2.id` unless you want to compare all properties. + * @param {User} user User to compare with + * @returns {boolean} + */ + equals(user) { + let equal = + user && + this.id === user.id && + this.username === user.username && + this.discriminator === user.discriminator && + this.avatar === user.avatar; + + return equal; + } + + /** + * Fetches this user's flags. + * @returns {Promise<UserFlags>} + */ + async fetchFlags() { + if (this.flags) return this.flags; + const data = await this.client.api.users(this.id).get(); + this._patch(data); + return this.flags; + } + + /** + * Fetches this user. + * @returns {Promise<User>} + */ + fetch() { + return this.client.users.fetch(this.id, true); + } + + /** + * When concatenated with a string, this automatically returns the user's mention instead of the User object. + * @returns {string} + * @example + * // Logs: Hello from <@123456789012345678>! + * console.log(`Hello from ${user}!`); + */ + toString() { + return `<@${this.id}>`; + } + + toJSON(...props) { + const json = super.toJSON( + { + createdTimestamp: true, + defaultAvatarURL: true, + tag: true, + lastMessage: false, + lastMessageID: false, + }, + ...props, + ); + json.avatarURL = this.avatarURL(); + json.displayAvatarURL = this.displayAvatarURL(); + return json; + } + + // These are here only for documentation purposes - they are implemented by TextBasedChannel + /* eslint-disable no-empty-function */ + send() {} +} + +TextBasedChannel.applyToClass(User); + +module.exports = User; diff --git a/node_modules/discord.js/src/structures/VoiceChannel.js b/node_modules/discord.js/src/structures/VoiceChannel.js new file mode 100644 index 0000000..6fb5ab7 --- /dev/null +++ b/node_modules/discord.js/src/structures/VoiceChannel.js @@ -0,0 +1,151 @@ +'use strict'; + +const GuildChannel = require('./GuildChannel'); +const { Error } = require('../errors'); +const Collection = require('../util/Collection'); +const { browser } = require('../util/Constants'); +const Permissions = require('../util/Permissions'); + +/** + * Represents a guild voice channel on Discord. + * @extends {GuildChannel} + */ +class VoiceChannel extends GuildChannel { + _patch(data) { + super._patch(data); + /** + * The bitrate of this voice channel + * @type {number} + */ + this.bitrate = data.bitrate; + + /** + * The maximum amount of users allowed in this channel - 0 means unlimited. + * @type {number} + */ + this.userLimit = data.user_limit; + } + + /** + * The members in this voice channel + * @type {Collection<Snowflake, GuildMember>} + * @name VoiceChannel#members + * @readonly + */ + get members() { + const coll = new Collection(); + for (const state of this.guild.voiceStates.cache.values()) { + if (state.channelID === this.id && state.member) { + coll.set(state.id, state.member); + } + } + return coll; + } + + /** + * Checks if the voice channel is full + * @type {boolean} + * @readonly + */ + get full() { + return this.userLimit > 0 && this.members.size >= this.userLimit; + } + + /** + * Whether the channel is deletable by the client user + * @type {boolean} + * @readonly + */ + get deletable() { + return super.deletable && this.permissionsFor(this.client.user).has(Permissions.FLAGS.CONNECT, false); + } + + /** + * Whether the channel is editable by the client user + * @type {boolean} + * @readonly + */ + get editable() { + return this.manageable && this.permissionsFor(this.client.user).has(Permissions.FLAGS.CONNECT, false); + } + + /** + * Whether the channel is joinable by the client user + * @type {boolean} + * @readonly + */ + get joinable() { + if (browser) return false; + if (!this.viewable) return false; + if (!this.permissionsFor(this.client.user).has(Permissions.FLAGS.CONNECT, false)) return false; + if (this.full && !this.permissionsFor(this.client.user).has(Permissions.FLAGS.MOVE_MEMBERS, false)) return false; + return true; + } + + /** + * Checks if the client has permission to send audio to the voice channel + * @type {boolean} + * @readonly + */ + get speakable() { + return this.permissionsFor(this.client.user).has(Permissions.FLAGS.SPEAK, false); + } + + /** + * Sets the bitrate of the channel. + * @param {number} bitrate The new bitrate + * @param {string} [reason] Reason for changing the channel's bitrate + * @returns {Promise<VoiceChannel>} + * @example + * // Set the bitrate of a voice channel + * voiceChannel.setBitrate(48000) + * .then(vc => console.log(`Set bitrate to ${vc.bitrate}bps for ${vc.name}`)) + * .catch(console.error); + */ + setBitrate(bitrate, reason) { + return this.edit({ bitrate }, reason); + } + + /** + * Sets the user limit of the channel. + * @param {number} userLimit The new user limit + * @param {string} [reason] Reason for changing the user limit + * @returns {Promise<VoiceChannel>} + * @example + * // Set the user limit of a voice channel + * voiceChannel.setUserLimit(42) + * .then(vc => console.log(`Set user limit to ${vc.userLimit} for ${vc.name}`)) + * .catch(console.error); + */ + setUserLimit(userLimit, reason) { + return this.edit({ userLimit }, reason); + } + + /** + * Attempts to join this voice channel. + * @returns {Promise<VoiceConnection>} + * @example + * // Join a voice channel + * voiceChannel.join() + * .then(connection => console.log('Connected!')) + * .catch(console.error); + */ + join() { + if (browser) return Promise.reject(new Error('VOICE_NO_BROWSER')); + return this.client.voice.joinChannel(this); + } + + /** + * Leaves this voice channel. + * @example + * // Leave a voice channel + * voiceChannel.leave(); + */ + leave() { + if (browser) return; + const connection = this.client.voice.connections.get(this.guild.id); + if (connection && connection.channel.id === this.id) connection.disconnect(); + } +} + +module.exports = VoiceChannel; diff --git a/node_modules/discord.js/src/structures/VoiceRegion.js b/node_modules/discord.js/src/structures/VoiceRegion.js new file mode 100644 index 0000000..9626195 --- /dev/null +++ b/node_modules/discord.js/src/structures/VoiceRegion.js @@ -0,0 +1,52 @@ +'use strict'; + +const Util = require('../util/Util'); + +/** + * Represents a Discord voice region for guilds. + */ +class VoiceRegion { + constructor(data) { + /** + * The ID of the region + * @type {string} + */ + this.id = data.id; + + /** + * Name of the region + * @type {string} + */ + this.name = data.name; + + /** + * Whether the region is VIP-only + * @type {boolean} + */ + this.vip = data.vip; + + /** + * Whether the region is deprecated + * @type {boolean} + */ + this.deprecated = data.deprecated; + + /** + * Whether the region is optimal + * @type {boolean} + */ + this.optimal = data.optimal; + + /** + * Whether the region is custom + * @type {boolean} + */ + this.custom = data.custom; + } + + toJSON() { + return Util.flatten(this); + } +} + +module.exports = VoiceRegion; diff --git a/node_modules/discord.js/src/structures/VoiceState.js b/node_modules/discord.js/src/structures/VoiceState.js new file mode 100644 index 0000000..cc9bcde --- /dev/null +++ b/node_modules/discord.js/src/structures/VoiceState.js @@ -0,0 +1,208 @@ +'use strict'; + +const Base = require('./Base'); +const { Error, TypeError } = require('../errors'); +const { browser } = require('../util/Constants'); + +/** + * Represents the voice state for a Guild Member. + */ +class VoiceState extends Base { + /** + * @param {Guild} guild The guild the voice state is part of + * @param {Object} data The data for the voice state + */ + constructor(guild, data) { + super(guild.client); + /** + * The guild of this voice state + * @type {Guild} + */ + this.guild = guild; + /** + * The ID of the member of this voice state + * @type {Snowflake} + */ + this.id = data.user_id; + this._patch(data); + } + + _patch(data) { + /** + * Whether this member is deafened server-wide + * @type {?boolean} + */ + this.serverDeaf = data.deaf; + /** + * Whether this member is muted server-wide + * @type {?boolean} + */ + this.serverMute = data.mute; + /** + * Whether this member is self-deafened + * @type {?boolean} + */ + this.selfDeaf = data.self_deaf; + /** + * Whether this member is self-muted + * @type {?boolean} + */ + this.selfMute = data.self_mute; + /** + * The session ID of this member's connection + * @type {?string} + */ + this.sessionID = data.session_id; + /** + * Whether this member is streaming using "Go Live" + * @type {boolean} + */ + this.streaming = data.self_stream || false; + /** + * The ID of the voice channel that this member is in + * @type {?Snowflake} + */ + this.channelID = data.channel_id; + return this; + } + + /** + * The member that this voice state belongs to + * @type {?GuildMember} + * @readonly + */ + get member() { + return this.guild.members.cache.get(this.id) || null; + } + + /** + * The channel that the member is connected to + * @type {?VoiceChannel} + * @readonly + */ + get channel() { + return this.guild.channels.cache.get(this.channelID) || null; + } + + /** + * If this is a voice state of the client user, then this will refer to the active VoiceConnection for this guild + * @type {?VoiceConnection} + * @readonly + */ + get connection() { + if (browser || this.id !== this.client.user.id) return null; + return this.client.voice.connections.get(this.guild.id) || null; + } + + /** + * Whether this member is either self-deafened or server-deafened + * @type {?boolean} + * @readonly + */ + get deaf() { + return this.serverDeaf || this.selfDeaf; + } + + /** + * Whether this member is either self-muted or server-muted + * @type {?boolean} + * @readonly + */ + get mute() { + return this.serverMute || this.selfMute; + } + + /** + * Whether this member is currently speaking. A boolean if the information is available (aka + * the bot is connected to any voice channel in the guild), otherwise this is null + * @type {?boolean} + * @readonly + */ + get speaking() { + return this.channel && this.channel.connection ? Boolean(this.channel.connection._speaking.get(this.id)) : null; + } + + /** + * Mutes/unmutes the member of this voice state. + * @param {boolean} mute Whether or not the member should be muted + * @param {string} [reason] Reason for muting or unmuting + * @returns {Promise<GuildMember>} + */ + setMute(mute, reason) { + return this.member ? this.member.edit({ mute }, reason) : Promise.reject(new Error('VOICE_STATE_UNCACHED_MEMBER')); + } + + /** + * Deafens/undeafens the member of this voice state. + * @param {boolean} deaf Whether or not the member should be deafened + * @param {string} [reason] Reason for deafening or undeafening + * @returns {Promise<GuildMember>} + */ + setDeaf(deaf, reason) { + return this.member ? this.member.edit({ deaf }, reason) : Promise.reject(new Error('VOICE_STATE_UNCACHED_MEMBER')); + } + + /** + * Kicks the member from the voice channel. + * @param {string} [reason] Reason for kicking member from the channel + * @returns {Promise<GuildMember>} + */ + kick(reason) { + return this.setChannel(null, reason); + } + + /** + * Moves the member to a different channel, or disconnects them from the one they're in. + * @param {ChannelResolvable|null} [channel] Channel to move the member to, or `null` if you want to disconnect them + * from voice. Requires the `MOVE_MEMBERS` permission. + * @param {string} [reason] Reason for moving member to another channel or disconnecting + * @returns {Promise<GuildMember>} + */ + setChannel(channel, reason) { + return this.member + ? this.member.edit({ channel }, reason) + : Promise.reject(new Error('VOICE_STATE_UNCACHED_MEMBER')); + } + + /** + * Self-mutes/unmutes the bot for this voice state. + * @param {boolean} mute Whether or not the bot should be self-muted + * @returns {Promise<boolean>} true if the voice state was successfully updated, otherwise false + */ + async setSelfMute(mute) { + if (this.id !== this.client.user.id) throw new Error('VOICE_STATE_NOT_OWN'); + if (typeof mute !== 'boolean') throw new TypeError('VOICE_STATE_INVALID_TYPE', 'mute'); + if (!this.connection) return false; + this.selfMute = mute; + await this.connection.sendVoiceStateUpdate(); + return true; + } + + /** + * Self-deafens/undeafens the bot for this voice state. + * @param {boolean} deaf Whether or not the bot should be self-deafened + * @returns {Promise<boolean>} true if the voice state was successfully updated, otherwise false + */ + async setSelfDeaf(deaf) { + if (this.id !== this.client.user.id) return new Error('VOICE_STATE_NOT_OWN'); + if (typeof deaf !== 'boolean') return new TypeError('VOICE_STATE_INVALID_TYPE', 'deaf'); + if (!this.connection) return false; + this.selfDeaf = deaf; + await this.connection.sendVoiceStateUpdate(); + return true; + } + + toJSON() { + return super.toJSON({ + id: true, + serverDeaf: true, + serverMute: true, + selfDeaf: true, + selfMute: true, + sessionID: true, + channelID: 'channel', + }); + } +} + +module.exports = VoiceState; diff --git a/node_modules/discord.js/src/structures/Webhook.js b/node_modules/discord.js/src/structures/Webhook.js new file mode 100644 index 0000000..b9aed9d --- /dev/null +++ b/node_modules/discord.js/src/structures/Webhook.js @@ -0,0 +1,273 @@ +'use strict'; + +const APIMessage = require('./APIMessage'); +const Channel = require('./Channel'); +const { WebhookTypes } = require('../util/Constants'); +const DataResolver = require('../util/DataResolver'); +const Snowflake = require('../util/Snowflake'); + +/** + * Represents a webhook. + */ +class Webhook { + constructor(client, data) { + /** + * The client that instantiated the webhook + * @name Webhook#client + * @type {Client} + * @readonly + */ + Object.defineProperty(this, 'client', { value: client }); + if (data) this._patch(data); + } + + _patch(data) { + /** + * The name of the webhook + * @type {string} + */ + this.name = data.name; + + /** + * The token for the webhook + * @name Webhook#token + * @type {?string} + */ + Object.defineProperty(this, 'token', { value: data.token || null, writable: true, configurable: true }); + + /** + * The avatar for the webhook + * @type {?string} + */ + this.avatar = data.avatar; + + /** + * The ID of the webhook + * @type {Snowflake} + */ + this.id = data.id; + + /** + * The type of the webhook + * @type {WebhookTypes} + */ + this.type = WebhookTypes[data.type]; + + /** + * The guild the webhook belongs to + * @type {Snowflake} + */ + this.guildID = data.guild_id; + + /** + * The channel the webhook belongs to + * @type {Snowflake} + */ + this.channelID = data.channel_id; + + if (data.user) { + /** + * The owner of the webhook + * @type {?User|Object} + */ + this.owner = this.client.users ? this.client.users.cache.get(data.user.id) : data.user; + } else { + this.owner = null; + } + } + + /** + * Options that can be passed into send. + * @typedef {Object} WebhookMessageOptions + * @property {string} [username=this.name] Username override for the message + * @property {string} [avatarURL] Avatar URL override for the message + * @property {boolean} [tts=false] Whether or not the message should be spoken aloud + * @property {string} [nonce=''] The nonce for the message + * @property {Object[]} [embeds] An array of embeds for the message + * @property {MessageMentionOptions} [allowedMentions] Which mentions should be parsed from the message content + * (see [here](https://discordapp.com/developers/docs/resources/channel#embed-object) for more details) + * @property {DisableMentionType} [disableMentions=this.client.options.disableMentions] Whether or not all mentions or + * everyone/here mentions should be sanitized to prevent unexpected mentions + * @property {FileOptions[]|string[]} [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 + * it exceeds the character limit. If an object is provided, these are the options for splitting the message. + */ + + /** + * Sends a message with this webhook. + * @param {StringResolvable|APIMessage} [content=''] The content to send + * @param {WebhookMessageOptions|MessageAdditions} [options={}] The options to provide + * @returns {Promise<Message|Object>} + * @example + * // Send a basic message + * webhook.send('hello!') + * .then(message => console.log(`Sent message: ${message.content}`)) + * .catch(console.error); + * @example + * // Send a remote file + * webhook.send({ + * files: ['https://cdn.discordapp.com/icons/222078108977594368/6e1019b3179d71046e463a75915e7244.png?size=2048'] + * }) + * .then(console.log) + * .catch(console.error); + * @example + * // Send a local file + * webhook.send({ + * files: [{ + * attachment: 'entire/path/to/file.jpg', + * name: 'file.jpg' + * }] + * }) + * .then(console.log) + * .catch(console.error); + * @example + * // Send an embed with a local image inside + * webhook.send('This is an embed', { + * embeds: [{ + * thumbnail: { + * url: 'attachment://file.jpg' + * } + * }], + * files: [{ + * attachment: 'entire/path/to/file.jpg', + * name: 'file.jpg' + * }] + * }) + * .then(console.log) + * .catch(console.error); + */ + async send(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)) { + return Promise.all(apiMessage.split().map(this.send.bind(this))); + } + } + + const { data, files } = await apiMessage.resolveFiles(); + return this.client.api + .webhooks(this.id, this.token) + .post({ + data, + files, + query: { wait: true }, + auth: false, + }) + .then(d => { + const channel = this.client.channels ? this.client.channels.cache.get(d.channel_id) : undefined; + if (!channel) return d; + return channel.messages.add(d, false); + }); + } + + /** + * Sends a raw slack message with this webhook. + * @param {Object} body The raw body to send + * @returns {Promise<boolean>} + * @example + * // Send a slack message + * webhook.sendSlackMessage({ + * 'username': 'Wumpus', + * 'attachments': [{ + * 'pretext': 'this looks pretty cool', + * 'color': '#F0F', + * 'footer_icon': 'http://snek.s3.amazonaws.com/topSnek.png', + * 'footer': 'Powered by sneks', + * 'ts': Date.now() / 1000 + * }] + * }).catch(console.error); + */ + sendSlackMessage(body) { + return this.client.api + .webhooks(this.id, this.token) + .slack.post({ + query: { wait: true }, + auth: false, + data: body, + }) + .then(data => data.toString() === 'ok'); + } + + /** + * Edits the webhook. + * @param {Object} options Options + * @param {string} [options.name=this.name] New name for this webhook + * @param {BufferResolvable} [options.avatar] New avatar for this webhook + * @param {ChannelResolvable} [options.channel] New channel for this webhook + * @param {string} [reason] Reason for editing this webhook + * @returns {Promise<Webhook>} + */ + async edit({ name = this.name, avatar, channel }, reason) { + if (avatar && typeof avatar === 'string' && !avatar.startsWith('data:')) { + avatar = await DataResolver.resolveImage(avatar); + } + if (channel) channel = channel instanceof Channel ? channel.id : channel; + const data = await this.client.api.webhooks(this.id, channel ? undefined : this.token).patch({ + data: { name, avatar, channel_id: channel }, + reason, + }); + + this.name = data.name; + this.avatar = data.avatar; + this.channelID = data.channel_id; + return this; + } + + /** + * Deletes the webhook. + * @param {string} [reason] Reason for deleting this webhook + * @returns {Promise} + */ + delete(reason) { + return this.client.api.webhooks(this.id, this.token).delete({ reason }); + } + /** + * The timestamp the webhook was created at + * @type {number} + * @readonly + */ + get createdTimestamp() { + return Snowflake.deconstruct(this.id).timestamp; + } + + /** + * The time the webhook was created at + * @type {Date} + * @readonly + */ + get createdAt() { + return new Date(this.createdTimestamp); + } + + /** + * The url of this webhook + * @type {string} + * @readonly + */ + get url() { + return this.client.options.http.api + this.client.api.webhooks(this.id, this.token); + } + + /** + * A link to the webhook's avatar. + * @param {ImageURLOptions} [options={}] Options for the Image URL + * @returns {?string} + */ + avatarURL({ format, size } = {}) { + if (!this.avatar) return null; + return this.client.rest.cdn.Avatar(this.id, this.avatar, format, size); + } + + static applyToClass(structure) { + for (const prop of ['send', 'sendSlackMessage', 'edit', 'delete', 'createdTimestamp', 'createdAt', 'url']) { + Object.defineProperty(structure.prototype, prop, Object.getOwnPropertyDescriptor(Webhook.prototype, prop)); + } + } +} + +module.exports = Webhook; diff --git a/node_modules/discord.js/src/structures/interfaces/Collector.js b/node_modules/discord.js/src/structures/interfaces/Collector.js new file mode 100644 index 0000000..21d01ca --- /dev/null +++ b/node_modules/discord.js/src/structures/interfaces/Collector.js @@ -0,0 +1,281 @@ +'use strict'; + +const EventEmitter = require('events'); +const Collection = require('../../util/Collection'); +const Util = require('../../util/Util'); + +/** + * Filter to be applied to the collector. + * @typedef {Function} CollectorFilter + * @param {...*} args Any arguments received by the listener + * @param {Collection} collection The items collected by this collector + * @returns {boolean} + */ + +/** + * Options to be applied to the collector. + * @typedef {Object} CollectorOptions + * @property {number} [time] How long to run the collector for in milliseconds + * @property {number} [idle] How long to stop the collector after inactivity in milliseconds + * @property {boolean} [dispose=false] Whether to dispose data when it's deleted + */ + +/** + * Abstract class for defining a new Collector. + * @abstract + */ +class Collector extends EventEmitter { + constructor(client, filter, options = {}) { + super(); + + /** + * The client that instantiated this Collector + * @name Collector#client + * @type {Client} + * @readonly + */ + Object.defineProperty(this, 'client', { value: client }); + + /** + * The filter applied to this collector + * @type {CollectorFilter} + */ + this.filter = filter; + + /** + * The options of this collector + * @type {CollectorOptions} + */ + this.options = options; + + /** + * The items collected by this collector + * @type {Collection} + */ + this.collected = new Collection(); + + /** + * Whether this collector has finished collecting + * @type {boolean} + */ + this.ended = false; + + /** + * Timeout for cleanup + * @type {?Timeout} + * @private + */ + this._timeout = null; + + /** + * Timeout for cleanup due to inactivity + * @type {?Timeout} + * @private + */ + this._idletimeout = null; + + this.handleCollect = this.handleCollect.bind(this); + this.handleDispose = this.handleDispose.bind(this); + + if (options.time) this._timeout = this.client.setTimeout(() => this.stop('time'), options.time); + if (options.idle) this._idletimeout = this.client.setTimeout(() => this.stop('idle'), options.idle); + } + + /** + * Call this to handle an event as a collectable element. Accepts any event data as parameters. + * @param {...*} args The arguments emitted by the listener + * @emits Collector#collect + */ + handleCollect(...args) { + const collect = this.collect(...args); + + if (collect && this.filter(...args, this.collected)) { + this.collected.set(collect, args[0]); + + /** + * Emitted whenever an element is collected. + * @event Collector#collect + * @param {...*} args The arguments emitted by the listener + */ + this.emit('collect', ...args); + + if (this._idletimeout) { + this.client.clearTimeout(this._idletimeout); + this._idletimeout = this.client.setTimeout(() => this.stop('idle'), this.options.idle); + } + } + this.checkEnd(); + } + + /** + * Call this to remove an element from the collection. Accepts any event data as parameters. + * @param {...*} args The arguments emitted by the listener + * @emits Collector#dispose + */ + handleDispose(...args) { + if (!this.options.dispose) return; + + const dispose = this.dispose(...args); + if (!dispose || !this.filter(...args) || !this.collected.has(dispose)) return; + this.collected.delete(dispose); + + /** + * Emitted whenever an element is disposed of. + * @event Collector#dispose + * @param {...*} args The arguments emitted by the listener + */ + this.emit('dispose', ...args); + this.checkEnd(); + } + + /** + * Returns a promise that resolves with the next collected element; + * rejects with collected elements if the collector finishes without receiving a next element + * @type {Promise} + * @readonly + */ + get next() { + return new Promise((resolve, reject) => { + if (this.ended) { + reject(this.collected); + return; + } + + const cleanup = () => { + this.removeListener('collect', onCollect); + this.removeListener('end', onEnd); + }; + + const onCollect = item => { + cleanup(); + resolve(item); + }; + + const onEnd = () => { + cleanup(); + reject(this.collected); // eslint-disable-line prefer-promise-reject-errors + }; + + this.on('collect', onCollect); + this.on('end', onEnd); + }); + } + + /** + * Stops this collector and emits the `end` event. + * @param {string} [reason='user'] The reason this collector is ending + * @emits Collector#end + */ + stop(reason = 'user') { + if (this.ended) return; + + if (this._timeout) { + this.client.clearTimeout(this._timeout); + this._timeout = null; + } + if (this._idletimeout) { + this.client.clearTimeout(this._idletimeout); + this._idletimeout = null; + } + this.ended = true; + + /** + * Emitted when the collector is finished collecting. + * @event Collector#end + * @param {Collection} collected The elements collected by the collector + * @param {string} reason The reason the collector ended + */ + this.emit('end', this.collected, reason); + } + + /** + * Resets the collectors timeout and idle timer. + * @param {Object} [options] Options + * @param {number} [options.time] How long to run the collector for in milliseconds + * @param {number} [options.idle] How long to stop the collector after inactivity in milliseconds + */ + resetTimer({ time, idle } = {}) { + if (this._timeout) { + this.client.clearTimeout(this._timeout); + this._timeout = this.client.setTimeout(() => this.stop('time'), time || this.options.time); + } + if (this._idletimeout) { + this.client.clearTimeout(this._idletimeout); + this._idletimeout = this.client.setTimeout(() => this.stop('idle'), idle || this.options.idle); + } + } + + /** + * Checks whether the collector should end, and if so, ends it. + */ + checkEnd() { + const reason = this.endReason(); + if (reason) this.stop(reason); + } + + /** + * Allows collectors to be consumed with for-await-of loops + * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of} + */ + async *[Symbol.asyncIterator]() { + const queue = []; + const onCollect = item => queue.push(item); + this.on('collect', onCollect); + + try { + while (queue.length || !this.ended) { + if (queue.length) { + yield queue.shift(); + } else { + // eslint-disable-next-line no-await-in-loop + await new Promise(resolve => { + const tick = () => { + this.removeListener('collect', tick); + this.removeListener('end', tick); + return resolve(); + }; + this.on('collect', tick); + this.on('end', tick); + }); + } + } + } finally { + this.removeListener('collect', onCollect); + } + } + + toJSON() { + return Util.flatten(this); + } + + /* eslint-disable no-empty-function, valid-jsdoc */ + /** + * Handles incoming events from the `handleCollect` function. Returns null if the event should not + * be collected, or returns an object describing the data that should be stored. + * @see Collector#handleCollect + * @param {...*} args Any args the event listener emits + * @returns {?{key, value}} Data to insert into collection, if any + * @abstract + */ + collect() {} + + /** + * Handles incoming events from the `handleDispose`. Returns null if the event should not + * be disposed, or returns the key that should be removed. + * @see Collector#handleDispose + * @param {...*} args Any args the event listener emits + * @returns {?*} Key to remove from the collection, if any + * @abstract + */ + dispose() {} + + /** + * The reason this collector has ended or will end with. + * @returns {?string} Reason to end the collector, if any + * @abstract + */ + endReason() {} + /* eslint-enable no-empty-function, valid-jsdoc */ +} + +module.exports = Collector; diff --git a/node_modules/discord.js/src/structures/interfaces/TextBasedChannel.js b/node_modules/discord.js/src/structures/interfaces/TextBasedChannel.js new file mode 100644 index 0000000..b63c92a --- /dev/null +++ b/node_modules/discord.js/src/structures/interfaces/TextBasedChannel.js @@ -0,0 +1,396 @@ +'use strict'; + +/* eslint-disable import/order */ +const MessageCollector = require('../MessageCollector'); +const APIMessage = require('../APIMessage'); +const Snowflake = require('../../util/Snowflake'); +const Collection = require('../../util/Collection'); +const { RangeError, TypeError } = require('../../errors'); + +/** + * Interface for classes that have text-channel-like features. + * @interface + */ +class TextBasedChannel { + constructor() { + /** + * A manager of the messages sent to this channel + * @type {MessageManager} + */ + this.messages = new MessageManager(this); + + /** + * The ID of the last message in the channel, if one was sent + * @type {?Snowflake} + */ + this.lastMessageID = null; + + /** + * The timestamp when the last pinned message was pinned, if there was one + * @type {?number} + */ + this.lastPinTimestamp = null; + } + + /** + * The Message object of the last message in the channel, if one was sent + * @type {?Message} + * @readonly + */ + get lastMessage() { + return this.messages.cache.get(this.lastMessageID) || null; + } + + /** + * The date when the last pinned message was pinned, if there was one + * @type {?Date} + * @readonly + */ + get lastPinAt() { + return this.lastPinTimestamp ? new Date(this.lastPinTimestamp) : null; + } + + /** + * Options provided when sending or editing a message. + * @typedef {Object} MessageOptions + * @property {boolean} [tts=false] Whether or not the message should be spoken aloud + * @property {string} [nonce=''] The nonce for the message + * @property {string} [content=''] The content for the message + * @property {MessageEmbed|Object} [embed] An embed for the message + * (see [here](https://discordapp.com/developers/docs/resources/channel#embed-object) for more details) + * @property {MessageMentionOptions} [allowedMentions] Which mentions should be parsed from the message content + * @property {DisableMentionType} [disableMentions=this.client.options.disableMentions] Whether or not 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 + * it exceeds the character limit. If an object is provided, these are the options for splitting the message + * @property {UserResolvable} [reply] User to reply to (prefixes the message with a mention, except in DMs) + */ + + /** + * Options provided to control parsing of mentions by Discord + * @typedef {Object} MessageMentionOptions + * @property {MessageMentionTypes[]} [parse] Types of mentions to be parsed + * @property {Snowflake[]} [users] Snowflakes of Users to be parsed as mentions + * @property {Snowflake[]} [roles] Snowflakes of Roles to be parsed as mentions + */ + + /** + * Types of mentions to enable in MessageMentionOptions. + * - `roles` + * - `users` + * - `everyone` + * @typedef {string} MessageMentionTypes + */ + + /** + * The type of mentions to disable. + * - `none` + * - `all` + * - `everyone` + * @typedef {string} DisableMentionType + */ + + /** + * @typedef {Object} FileOptions + * @property {BufferResolvable} attachment File to attach + * @property {string} [name='file.jpg'] Filename of the attachment + */ + + /** + * Options for splitting a message. + * @typedef {Object} SplitOptions + * @property {number} [maxLength=2000] Maximum character length per message piece + * @property {string} [char='\n'] Character to split the message with + * @property {string} [prepend=''] Text to prepend to every piece except the first + * @property {string} [append=''] Text to append to every piece except the last + */ + + /** + * Sends a message to this channel. + * @param {StringResolvable|APIMessage} [content=''] The content to send + * @param {MessageOptions|MessageAdditions} [options={}] The options to provide + * @returns {Promise<Message|Message[]>} + * @example + * // Send a basic message + * channel.send('hello!') + * .then(message => console.log(`Sent message: ${message.content}`)) + * .catch(console.error); + * @example + * // Send a remote file + * channel.send({ + * files: ['https://cdn.discordapp.com/icons/222078108977594368/6e1019b3179d71046e463a75915e7244.png?size=2048'] + * }) + * .then(console.log) + * .catch(console.error); + * @example + * // Send a local file + * channel.send({ + * files: [{ + * attachment: 'entire/path/to/file.jpg', + * name: 'file.jpg' + * }] + * }) + * .then(console.log) + * .catch(console.error); + * @example + * // Send an embed with a local image inside + * channel.send('This is an embed', { + * embed: { + * thumbnail: { + * url: 'attachment://file.jpg' + * } + * }, + * files: [{ + * attachment: 'entire/path/to/file.jpg', + * name: 'file.jpg' + * }] + * }) + * .then(console.log) + * .catch(console.error); + */ + async send(content, options) { + const User = require('../User'); + const GuildMember = require('../GuildMember'); + + if (this instanceof User || this instanceof GuildMember) { + return this.createDM().then(dm => dm.send(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)) { + return Promise.all(apiMessage.split().map(this.send.bind(this))); + } + } + + const { data, files } = await apiMessage.resolveFiles(); + return this.client.api.channels[this.id].messages + .post({ data, files }) + .then(d => this.client.actions.MessageCreate.handle(d).message); + } + + /** + * Starts a typing indicator in the channel. + * @param {number} [count=1] The number of times startTyping should be considered to have been called + * @returns {Promise} Resolves once the bot stops typing gracefully, or rejects when an error occurs + * @example + * // Start typing in a channel, or increase the typing count by one + * channel.startTyping(); + * @example + * // Start typing in a channel with a typing count of five, or set it to five + * channel.startTyping(5); + */ + startTyping(count) { + if (typeof count !== 'undefined' && count < 1) throw new RangeError('TYPING_COUNT'); + if (this.client.user._typing.has(this.id)) { + const entry = this.client.user._typing.get(this.id); + entry.count = count || entry.count + 1; + return entry.promise; + } + + const entry = {}; + entry.promise = new Promise((resolve, reject) => { + const endpoint = this.client.api.channels[this.id].typing; + Object.assign(entry, { + count: count || 1, + interval: this.client.setInterval(() => { + endpoint.post().catch(error => { + this.client.clearInterval(entry.interval); + this.client.user._typing.delete(this.id); + reject(error); + }); + }, 9000), + resolve, + }); + endpoint.post().catch(error => { + this.client.clearInterval(entry.interval); + this.client.user._typing.delete(this.id); + reject(error); + }); + this.client.user._typing.set(this.id, entry); + }); + return entry.promise; + } + + /** + * Stops the typing indicator in the channel. + * The indicator will only stop if this is called as many times as startTyping(). + * <info>It can take a few seconds for the client user to stop typing.</info> + * @param {boolean} [force=false] Whether or not to reset the call count and force the indicator to stop + * @example + * // Reduce the typing count by one and stop typing if it reached 0 + * channel.stopTyping(); + * @example + * // Force typing to fully stop regardless of typing count + * channel.stopTyping(true); + */ + stopTyping(force = false) { + if (this.client.user._typing.has(this.id)) { + const entry = this.client.user._typing.get(this.id); + entry.count--; + if (entry.count <= 0 || force) { + this.client.clearInterval(entry.interval); + this.client.user._typing.delete(this.id); + entry.resolve(); + } + } + } + + /** + * Whether or not the typing indicator is being shown in the channel + * @type {boolean} + * @readonly + */ + get typing() { + return this.client.user._typing.has(this.id); + } + + /** + * Number of times `startTyping` has been called + * @type {number} + * @readonly + */ + get typingCount() { + if (this.client.user._typing.has(this.id)) return this.client.user._typing.get(this.id).count; + return 0; + } + + /** + * Creates a Message Collector. + * @param {CollectorFilter} filter The filter to create the collector with + * @param {MessageCollectorOptions} [options={}] The options to pass to the collector + * @returns {MessageCollector} + * @example + * // Create a message collector + * const filter = m => m.content.includes('discord'); + * const collector = channel.createMessageCollector(filter, { time: 15000 }); + * collector.on('collect', m => console.log(`Collected ${m.content}`)); + * collector.on('end', collected => console.log(`Collected ${collected.size} items`)); + */ + createMessageCollector(filter, options = {}) { + return new MessageCollector(this, filter, options); + } + + /** + * An object containing the same properties as CollectorOptions, but a few more: + * @typedef {MessageCollectorOptions} AwaitMessagesOptions + * @property {string[]} [errors] Stop/end reasons that cause the promise to reject + */ + + /** + * Similar to createMessageCollector but in promise form. + * Resolves with a collection of messages that pass the specified filter. + * @param {CollectorFilter} filter The filter function to use + * @param {AwaitMessagesOptions} [options={}] Optional options to pass to the internal collector + * @returns {Promise<Collection<Snowflake, Message>>} + * @example + * // Await !vote messages + * const filter = m => m.content.startsWith('!vote'); + * // Errors: ['time'] treats ending because of the time limit as an error + * channel.awaitMessages(filter, { max: 4, time: 60000, errors: ['time'] }) + * .then(collected => console.log(collected.size)) + * .catch(collected => console.log(`After a minute, only ${collected.size} out of 4 voted.`)); + */ + awaitMessages(filter, options = {}) { + return new Promise((resolve, reject) => { + const collector = this.createMessageCollector(filter, options); + collector.once('end', (collection, reason) => { + if (options.errors && options.errors.includes(reason)) { + reject(collection); + } else { + resolve(collection); + } + }); + }); + } + + /** + * Bulk deletes given messages that are newer than two weeks. + * @param {Collection<Snowflake, Message>|Message[]|Snowflake[]|number} messages + * Messages or number of messages to delete + * @param {boolean} [filterOld=false] Filter messages to remove those which are older than two weeks automatically + * @returns {Promise<Collection<Snowflake, Message>>} Deleted messages + * @example + * // Bulk delete messages + * channel.bulkDelete(5) + * .then(messages => console.log(`Bulk deleted ${messages.size} messages`)) + * .catch(console.error); + */ + async bulkDelete(messages, filterOld = false) { + if (Array.isArray(messages) || messages instanceof Collection) { + let messageIDs = messages instanceof Collection ? messages.keyArray() : messages.map(m => m.id || m); + if (filterOld) { + messageIDs = messageIDs.filter(id => Date.now() - Snowflake.deconstruct(id).date.getTime() < 1209600000); + } + if (messageIDs.length === 0) return new Collection(); + if (messageIDs.length === 1) { + await this.client.api + .channels(this.id) + .messages(messageIDs[0]) + .delete(); + const message = this.client.actions.MessageDelete.getMessage( + { + message_id: messageIDs[0], + }, + this, + ); + return message ? new Collection([[message.id, message]]) : new Collection(); + } + await this.client.api.channels[this.id].messages['bulk-delete'].post({ data: { messages: messageIDs } }); + return messageIDs.reduce( + (col, id) => + col.set( + id, + this.client.actions.MessageDeleteBulk.getMessage( + { + message_id: id, + }, + this, + ), + ), + new Collection(), + ); + } + if (!isNaN(messages)) { + const msgs = await this.messages.fetch({ limit: messages }); + return this.bulkDelete(msgs, filterOld); + } + throw new TypeError('MESSAGE_BULK_DELETE_TYPE'); + } + + static applyToClass(structure, full = false, ignore = []) { + const props = ['send']; + if (full) { + props.push( + 'lastMessage', + 'lastPinAt', + 'bulkDelete', + 'startTyping', + 'stopTyping', + 'typing', + 'typingCount', + 'createMessageCollector', + 'awaitMessages', + ); + } + for (const prop of props) { + if (ignore.includes(prop)) continue; + Object.defineProperty( + structure.prototype, + prop, + Object.getOwnPropertyDescriptor(TextBasedChannel.prototype, prop), + ); + } + } +} + +module.exports = TextBasedChannel; + +// Fixes Circular +const MessageManager = require('../../managers/MessageManager'); diff --git a/node_modules/discord.js/src/util/ActivityFlags.js b/node_modules/discord.js/src/util/ActivityFlags.js new file mode 100644 index 0000000..40c9e7e --- /dev/null +++ b/node_modules/discord.js/src/util/ActivityFlags.js @@ -0,0 +1,38 @@ +'use strict'; + +const BitField = require('./BitField'); + +/** + * Data structure that makes it easy to interact with an {@link Activity#flags} bitfield. + * @extends {BitField} + */ +class ActivityFlags extends BitField {} + +/** + * @name ActivityFlags + * @kind constructor + * @memberof ActivityFlags + * @param {BitFieldResolvable} [bits=0] Bit(s) to read from + */ + +/** + * Numeric activity flags. All available properties: + * * `INSTANCE` + * * `JOIN` + * * `SPECTATE` + * * `JOIN_REQUEST` + * * `SYNC` + * * `PLAY` + * @type {Object} + * @see {@link https://discordapp.com/developers/docs/topics/gateway#activity-object-activity-flags} + */ +ActivityFlags.FLAGS = { + INSTANCE: 1 << 0, + JOIN: 1 << 1, + SPECTATE: 1 << 2, + JOIN_REQUEST: 1 << 3, + SYNC: 1 << 4, + PLAY: 1 << 5, +}; + +module.exports = ActivityFlags; diff --git a/node_modules/discord.js/src/util/BitField.js b/node_modules/discord.js/src/util/BitField.js new file mode 100644 index 0000000..ec1f8dc --- /dev/null +++ b/node_modules/discord.js/src/util/BitField.js @@ -0,0 +1,162 @@ +'use strict'; + +const { RangeError } = require('../errors'); + +/** + * Data structure that makes it easy to interact with a bitfield. + */ +class BitField { + /** + * @param {BitFieldResolvable} [bits=0] Bit(s) to read from + */ + constructor(bits) { + /** + * Bitfield of the packed bits + * @type {number} + */ + this.bitfield = this.constructor.resolve(bits); + } + + /** + * Checks whether the bitfield has a bit, or any of multiple bits. + * @param {BitFieldResolvable} bit Bit(s) to check for + * @returns {boolean} + */ + any(bit) { + return (this.bitfield & this.constructor.resolve(bit)) !== 0; + } + + /** + * Checks if this bitfield equals another + * @param {BitFieldResolvable} bit Bit(s) to check for + * @returns {boolean} + */ + equals(bit) { + return this.bitfield === this.constructor.resolve(bit); + } + + /** + * Checks whether the bitfield has a bit, or multiple bits. + * @param {BitFieldResolvable} bit Bit(s) to check for + * @returns {boolean} + */ + has(bit) { + if (Array.isArray(bit)) return bit.every(p => this.has(p)); + bit = this.constructor.resolve(bit); + return (this.bitfield & bit) === bit; + } + + /** + * Gets all given bits that are missing from the bitfield. + * @param {BitFieldResolvable} bits Bit(s) to check for + * @param {...*} hasParams Additional parameters for the has method, if any + * @returns {string[]} + */ + missing(bits, ...hasParams) { + if (!Array.isArray(bits)) bits = new this.constructor(bits).toArray(false); + return bits.filter(p => !this.has(p, ...hasParams)); + } + + /** + * Freezes these bits, making them immutable. + * @returns {Readonly<BitField>} These bits + */ + freeze() { + return Object.freeze(this); + } + + /** + * Adds bits to these ones. + * @param {...BitFieldResolvable} [bits] Bits to add + * @returns {BitField} These bits or new BitField if the instance is frozen. + */ + add(...bits) { + let total = 0; + for (const bit of bits) { + total |= this.constructor.resolve(bit); + } + if (Object.isFrozen(this)) return new this.constructor(this.bitfield | total); + this.bitfield |= total; + return this; + } + + /** + * Removes bits from these. + * @param {...BitFieldResolvable} [bits] Bits to remove + * @returns {BitField} These bits or new BitField if the instance is frozen. + */ + remove(...bits) { + let total = 0; + for (const bit of bits) { + total |= this.constructor.resolve(bit); + } + if (Object.isFrozen(this)) return new this.constructor(this.bitfield & ~total); + this.bitfield &= ~total; + return this; + } + + /** + * Gets an object mapping field names to a {@link boolean} indicating whether the + * bit is available. + * @param {...*} hasParams Additional parameters for the has method, if any + * @returns {Object} + */ + serialize(...hasParams) { + const serialized = {}; + for (const [flag, bit] of Object.entries(this.constructor.FLAGS)) serialized[flag] = this.has(bit, ...hasParams); + return serialized; + } + + /** + * Gets an {@link Array} of bitfield names based on the bits available. + * @param {...*} hasParams Additional parameters for the has method, if any + * @returns {string[]} + */ + toArray(...hasParams) { + return Object.keys(this.constructor.FLAGS).filter(bit => this.has(bit, ...hasParams)); + } + + toJSON() { + return this.bitfield; + } + + valueOf() { + return this.bitfield; + } + + *[Symbol.iterator]() { + yield* this.toArray(); + } + + /** + * Data that can be resolved to give a bitfield. This can be: + * * A string (see {@link BitField.FLAGS}) + * * A bit number + * * An instance of BitField + * * An Array of BitFieldResolvable + * @typedef {string|number|BitField|BitFieldResolvable[]} BitFieldResolvable + */ + + /** + * Resolves bitfields to their numeric form. + * @param {BitFieldResolvable} [bit=0] - bit(s) to resolve + * @returns {number} + */ + static resolve(bit = 0) { + if (typeof bit === 'number' && bit >= 0) return bit; + if (bit instanceof BitField) return bit.bitfield; + if (Array.isArray(bit)) return bit.map(p => this.resolve(p)).reduce((prev, p) => prev | p, 0); + if (typeof bit === 'string' && typeof this.FLAGS[bit] !== 'undefined') return this.FLAGS[bit]; + throw new RangeError('BITFIELD_INVALID'); + } +} + +/** + * Numeric bitfield flags. + * <info>Defined in extension classes</info> + * @type {Object} + * @abstract + */ +BitField.FLAGS = {}; + +module.exports = BitField; diff --git a/node_modules/discord.js/src/util/Collection.js b/node_modules/discord.js/src/util/Collection.js new file mode 100644 index 0000000..3219fb8 --- /dev/null +++ b/node_modules/discord.js/src/util/Collection.js @@ -0,0 +1,17 @@ +'use strict'; + +const BaseCollection = require('@discordjs/collection'); +const Util = require('./Util'); + +class Collection extends BaseCollection { + toJSON() { + return this.map(e => (typeof e.toJSON === 'function' ? e.toJSON() : Util.flatten(e))); + } +} + +module.exports = Collection; + +/** + * @external Collection + * @see {@link https://discord.js.org/#/docs/collection/master/class/Collection} + */ diff --git a/node_modules/discord.js/src/util/Constants.js b/node_modules/discord.js/src/util/Constants.js new file mode 100644 index 0000000..74a58ff --- /dev/null +++ b/node_modules/discord.js/src/util/Constants.js @@ -0,0 +1,650 @@ +'use strict'; + +const Package = (exports.Package = require('../../package.json')); +const { Error, RangeError } = require('../errors'); +const browser = (exports.browser = typeof window !== 'undefined'); + +/** + * Options for a client. + * @typedef {Object} ClientOptions + * @property {number|number[]|string} [shards] ID of the shard to run, or an array of shard IDs. If not specified, + * the client will spawn {@link ClientOptions#shardCount} shards. If set to `auto`, it will fetch the + * recommended amount of shards from Discord and spawn that amount + * @property {number} [shardCount=1] The total amount of shards used by all processes of this bot + * (e.g. recommended shard count, shard count of the ShardingManager) + * @property {number} [messageCacheMaxSize=200] Maximum number of messages to cache per channel + * (-1 or Infinity for unlimited - don't do this without message sweeping, otherwise memory usage will climb + * indefinitely) + * @property {number} [messageCacheLifetime=0] How long a message should stay in the cache until it is considered + * sweepable (in seconds, 0 for forever) + * @property {number} [messageSweepInterval=0] How frequently to remove messages from the cache that are older than + * the message cache lifetime (in seconds, 0 for never) + * @property {boolean} [fetchAllMembers=false] Whether to cache all guild members and users upon startup, as well as + * upon joining a guild (should be avoided whenever possible) + * @property {DisableMentionType} [disableMentions='none'] Default value for {@link MessageOptions#disableMentions} + * @property {MessageMentionOptions} [allowedMentions] Default value for {@link MessageOptions#allowedMentions} + * @property {PartialType[]} [partials] Structures allowed to be partial. This means events can be emitted even when + * they're missing all the data for a particular structure. See the "Partials" topic listed in the sidebar for some + * important usage information, as partials require you to put checks in place when handling data. + * @property {number} [restWsBridgeTimeout=5000] Maximum time permitted between REST responses and their + * corresponding websocket events + * @property {number} [restTimeOffset=500] Extra time in millseconds to wait before continuing to make REST + * requests (higher values will reduce rate-limiting errors on bad connections) + * @property {number} [restRequestTimeout=15000] Time to wait before cancelling a REST request, in milliseconds + * @property {number} [restSweepInterval=60] How frequently to delete inactive request buckets, in seconds + * (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 {WebsocketOptions} [ws] Options for the WebSocket + * @property {HTTPOptions} [http] HTTP options + */ +exports.DefaultOptions = { + shardCount: 1, + messageCacheMaxSize: 200, + messageCacheLifetime: 0, + messageSweepInterval: 0, + fetchAllMembers: false, + disableMentions: 'none', + partials: [], + restWsBridgeTimeout: 5000, + restRequestTimeout: 15000, + retryLimit: 1, + restTimeOffset: 500, + restSweepInterval: 60, + presence: {}, + + /** + * 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, + compress: false, + properties: { + $os: browser ? 'browser' : process.platform, + $browser: 'discord.js', + $device: 'discord.js', + }, + version: 6, + }, + + /** + * HTTP options + * @typedef {Object} HTTPOptions + * @property {number} [version=7] API version to use + * @property {string} [api='https://discordapp.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 + */ + http: { + version: 7, + api: 'https://discordapp.com/api', + cdn: 'https://cdn.discordapp.com', + invite: 'https://discord.gg', + }, +}; + +exports.UserAgent = browser + ? null + : `DiscordBot (${Package.homepage.split('#')[0]}, ${Package.version}) Node.js/${process.version}`; + +exports.WSCodes = { + 1000: 'WS_CLOSE_REQUESTED', + 4004: 'TOKEN_INVALID', + 4010: 'SHARDING_INVALID', + 4011: 'SHARDING_REQUIRED', + 4013: 'INVALID_INTENTS', + 4014: 'DISALLOWED_INTENTS', +}; + +const AllowedImageFormats = ['webp', 'png', 'jpg', 'jpeg', 'gif']; + +const AllowedImageSizes = Array.from({ length: 9 }, (e, i) => 2 ** (i + 4)); + +function makeImageUrl(root, { format = 'webp', size } = {}) { + if (format && !AllowedImageFormats.includes(format)) throw new Error('IMAGE_FORMAT', format); + if (size && !AllowedImageSizes.includes(size)) throw new RangeError('IMAGE_SIZE', size); + return `${root}.${format}${size ? `?size=${size}` : ''}`; +} +/** + * Options for Image URLs. + * @typedef {Object} ImageURLOptions + * @property {string} [format] One of `webp`, `png`, `jpg`, `jpeg`, `gif`. If no format is provided, + * defaults to `webp`. + * @property {boolean} [dynamic] If true, the format will dynamically change to `gif` for + * animated avatars; the default is false. + * @property {number} [size] One of `16`, `32`, `64`, `128`, `256`, `512`, `1024`, `2048`, `4096` + */ + +exports.Endpoints = { + CDN(root) { + return { + Emoji: (emojiID, format = 'png') => `${root}/emojis/${emojiID}.${format}`, + Asset: name => `${root}/assets/${name}`, + DefaultAvatar: discriminator => `${root}/embed/avatars/${discriminator}.png`, + Avatar: (userID, hash, format = 'webp', size, dynamic = false) => { + if (dynamic) format = hash.startsWith('a_') ? 'gif' : format; + return makeImageUrl(`${root}/avatars/${userID}/${hash}`, { format, size }); + }, + Banner: (guildID, hash, format = 'webp', size) => + makeImageUrl(`${root}/banners/${guildID}/${hash}`, { format, size }), + Icon: (guildID, hash, format = 'webp', size, dynamic = false) => { + if (dynamic) format = hash.startsWith('a_') ? 'gif' : format; + return makeImageUrl(`${root}/icons/${guildID}/${hash}`, { format, size }); + }, + AppIcon: (clientID, hash, { format = 'webp', size } = {}) => + makeImageUrl(`${root}/app-icons/${clientID}/${hash}`, { size, format }), + AppAsset: (clientID, hash, { format = 'webp', size } = {}) => + makeImageUrl(`${root}/app-assets/${clientID}/${hash}`, { size, format }), + GDMIcon: (channelID, hash, format = 'webp', size) => + makeImageUrl(`${root}/channel-icons/${channelID}/${hash}`, { size, format }), + Splash: (guildID, hash, format = 'webp', size) => + makeImageUrl(`${root}/splashes/${guildID}/${hash}`, { size, format }), + DiscoverySplash: (guildID, hash, format = 'webp', size) => + makeImageUrl(`${root}/discovery-splashes/${guildID}/${hash}`, { size, format }), + TeamIcon: (teamID, hash, { format = 'webp', size } = {}) => + makeImageUrl(`${root}/team-icons/${teamID}/${hash}`, { size, format }), + }; + }, + invite: (root, code) => `${root}/${code}`, + botGateway: '/gateway/bot', +}; + +/** + * The current status of the client. Here are the available statuses: + * * READY: 0 + * * CONNECTING: 1 + * * RECONNECTING: 2 + * * IDLE: 3 + * * NEARLY: 4 + * * DISCONNECTED: 5 + * * WAITING_FOR_GUILDS: 6 + * * IDENTIFYING: 7 + * * RESUMING: 8 + * @typedef {number} Status + */ +exports.Status = { + READY: 0, + CONNECTING: 1, + RECONNECTING: 2, + IDLE: 3, + NEARLY: 4, + DISCONNECTED: 5, + WAITING_FOR_GUILDS: 6, + IDENTIFYING: 7, + RESUMING: 8, +}; + +/** + * The current status of a voice connection. Here are the available statuses: + * * CONNECTED: 0 + * * CONNECTING: 1 + * * AUTHENTICATING: 2 + * * RECONNECTING: 3 + * * DISCONNECTED: 4 + * @typedef {number} VoiceStatus + */ +exports.VoiceStatus = { + CONNECTED: 0, + CONNECTING: 1, + AUTHENTICATING: 2, + RECONNECTING: 3, + DISCONNECTED: 4, +}; + +exports.OPCodes = { + DISPATCH: 0, + HEARTBEAT: 1, + IDENTIFY: 2, + STATUS_UPDATE: 3, + VOICE_STATE_UPDATE: 4, + VOICE_GUILD_PING: 5, + RESUME: 6, + RECONNECT: 7, + REQUEST_GUILD_MEMBERS: 8, + INVALID_SESSION: 9, + HELLO: 10, + HEARTBEAT_ACK: 11, +}; + +exports.VoiceOPCodes = { + IDENTIFY: 0, + SELECT_PROTOCOL: 1, + READY: 2, + HEARTBEAT: 3, + SESSION_DESCRIPTION: 4, + SPEAKING: 5, + HELLO: 8, + CLIENT_CONNECT: 12, + CLIENT_DISCONNECT: 13, +}; + +exports.Events = { + RATE_LIMIT: 'rateLimit', + CLIENT_READY: 'ready', + GUILD_CREATE: 'guildCreate', + GUILD_DELETE: 'guildDelete', + GUILD_UPDATE: 'guildUpdate', + GUILD_UNAVAILABLE: 'guildUnavailable', + GUILD_AVAILABLE: 'guildAvailable', + GUILD_MEMBER_ADD: 'guildMemberAdd', + GUILD_MEMBER_REMOVE: 'guildMemberRemove', + GUILD_MEMBER_UPDATE: 'guildMemberUpdate', + GUILD_MEMBER_AVAILABLE: 'guildMemberAvailable', + GUILD_MEMBER_SPEAKING: 'guildMemberSpeaking', + GUILD_MEMBERS_CHUNK: 'guildMembersChunk', + GUILD_INTEGRATIONS_UPDATE: 'guildIntegrationsUpdate', + GUILD_ROLE_CREATE: 'roleCreate', + GUILD_ROLE_DELETE: 'roleDelete', + INVITE_CREATE: 'inviteCreate', + INVITE_DELETE: 'inviteDelete', + GUILD_ROLE_UPDATE: 'roleUpdate', + GUILD_EMOJI_CREATE: 'emojiCreate', + GUILD_EMOJI_DELETE: 'emojiDelete', + GUILD_EMOJI_UPDATE: 'emojiUpdate', + GUILD_BAN_ADD: 'guildBanAdd', + GUILD_BAN_REMOVE: 'guildBanRemove', + CHANNEL_CREATE: 'channelCreate', + CHANNEL_DELETE: 'channelDelete', + CHANNEL_UPDATE: 'channelUpdate', + CHANNEL_PINS_UPDATE: 'channelPinsUpdate', + MESSAGE_CREATE: 'message', + MESSAGE_DELETE: 'messageDelete', + MESSAGE_UPDATE: 'messageUpdate', + MESSAGE_BULK_DELETE: 'messageDeleteBulk', + MESSAGE_REACTION_ADD: 'messageReactionAdd', + MESSAGE_REACTION_REMOVE: 'messageReactionRemove', + MESSAGE_REACTION_REMOVE_ALL: 'messageReactionRemoveAll', + MESSAGE_REACTION_REMOVE_EMOJI: 'messageReactionRemoveEmoji', + USER_UPDATE: 'userUpdate', + PRESENCE_UPDATE: 'presenceUpdate', + VOICE_SERVER_UPDATE: 'voiceServerUpdate', + VOICE_STATE_UPDATE: 'voiceStateUpdate', + VOICE_BROADCAST_SUBSCRIBE: 'subscribe', + VOICE_BROADCAST_UNSUBSCRIBE: 'unsubscribe', + TYPING_START: 'typingStart', + TYPING_STOP: 'typingStop', + WEBHOOKS_UPDATE: 'webhookUpdate', + ERROR: 'error', + WARN: 'warn', + DEBUG: 'debug', + SHARD_DISCONNECT: 'shardDisconnect', + SHARD_ERROR: 'shardError', + SHARD_RECONNECTING: 'shardReconnecting', + SHARD_READY: 'shardReady', + SHARD_RESUME: 'shardResume', + INVALIDATED: 'invalidated', + RAW: 'raw', +}; + +exports.ShardEvents = { + CLOSE: 'close', + DESTROYED: 'destroyed', + INVALID_SESSION: 'invalidSession', + READY: 'ready', + RESUMED: 'resumed', + ALL_READY: 'allReady', +}; + +/** + * The type of Structure allowed to be a partial: + * * USER + * * CHANNEL (only affects DMChannels) + * * GUILD_MEMBER + * * MESSAGE + * * REACTION + * <warn>Partials require you to put checks in place when handling data, read the Partials topic listed in the + * sidebar for more information.</warn> + * @typedef {string} PartialType + */ +exports.PartialTypes = keyMirror(['USER', 'CHANNEL', 'GUILD_MEMBER', 'MESSAGE', 'REACTION']); + +/** + * The type of a websocket message event, e.g. `MESSAGE_CREATE`. Here are the available events: + * * READY + * * RESUMED + * * GUILD_CREATE + * * GUILD_DELETE + * * GUILD_UPDATE + * * INVITE_CREATE + * * INVITE_DELETE + * * GUILD_MEMBER_ADD + * * GUILD_MEMBER_REMOVE + * * GUILD_MEMBER_UPDATE + * * GUILD_MEMBERS_CHUNK + * * GUILD_INTEGRATIONS_UPDATE + * * GUILD_ROLE_CREATE + * * GUILD_ROLE_DELETE + * * GUILD_ROLE_UPDATE + * * GUILD_BAN_ADD + * * GUILD_BAN_REMOVE + * * GUILD_EMOJIS_UPDATE + * * CHANNEL_CREATE + * * CHANNEL_DELETE + * * CHANNEL_UPDATE + * * CHANNEL_PINS_UPDATE + * * MESSAGE_CREATE + * * MESSAGE_DELETE + * * MESSAGE_UPDATE + * * MESSAGE_DELETE_BULK + * * MESSAGE_REACTION_ADD + * * MESSAGE_REACTION_REMOVE + * * MESSAGE_REACTION_REMOVE_ALL + * * MESSAGE_REACTION_REMOVE_EMOJI + * * USER_UPDATE + * * PRESENCE_UPDATE + * * TYPING_START + * * VOICE_STATE_UPDATE + * * VOICE_SERVER_UPDATE + * * WEBHOOKS_UPDATE + * @typedef {string} WSEventType + */ +exports.WSEvents = keyMirror([ + 'READY', + 'RESUMED', + 'GUILD_CREATE', + 'GUILD_DELETE', + 'GUILD_UPDATE', + 'INVITE_CREATE', + 'INVITE_DELETE', + 'GUILD_MEMBER_ADD', + 'GUILD_MEMBER_REMOVE', + 'GUILD_MEMBER_UPDATE', + 'GUILD_MEMBERS_CHUNK', + 'GUILD_INTEGRATIONS_UPDATE', + 'GUILD_ROLE_CREATE', + 'GUILD_ROLE_DELETE', + 'GUILD_ROLE_UPDATE', + 'GUILD_BAN_ADD', + 'GUILD_BAN_REMOVE', + 'GUILD_EMOJIS_UPDATE', + 'CHANNEL_CREATE', + 'CHANNEL_DELETE', + 'CHANNEL_UPDATE', + 'CHANNEL_PINS_UPDATE', + 'MESSAGE_CREATE', + 'MESSAGE_DELETE', + 'MESSAGE_UPDATE', + 'MESSAGE_DELETE_BULK', + 'MESSAGE_REACTION_ADD', + 'MESSAGE_REACTION_REMOVE', + 'MESSAGE_REACTION_REMOVE_ALL', + 'MESSAGE_REACTION_REMOVE_EMOJI', + 'USER_UPDATE', + 'PRESENCE_UPDATE', + 'TYPING_START', + 'VOICE_STATE_UPDATE', + 'VOICE_SERVER_UPDATE', + 'WEBHOOKS_UPDATE', +]); + +/** + * The type of a message, e.g. `DEFAULT`. Here are the available types: + * * DEFAULT + * * RECIPIENT_ADD + * * RECIPIENT_REMOVE + * * CALL + * * CHANNEL_NAME_CHANGE + * * CHANNEL_ICON_CHANGE + * * PINS_ADD + * * GUILD_MEMBER_JOIN + * * USER_PREMIUM_GUILD_SUBSCRIPTION + * * USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1 + * * USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2 + * * USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3 + * * CHANNEL_FOLLOW_ADD + * * GUILD_DISCOVERY_DISQUALIFIED + * * GUILD_DISCOVERY_REQUALIFIED + * @typedef {string} MessageType + */ +exports.MessageTypes = [ + 'DEFAULT', + 'RECIPIENT_ADD', + 'RECIPIENT_REMOVE', + 'CALL', + 'CHANNEL_NAME_CHANGE', + 'CHANNEL_ICON_CHANGE', + 'PINS_ADD', + 'GUILD_MEMBER_JOIN', + 'USER_PREMIUM_GUILD_SUBSCRIPTION', + 'USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1', + 'USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2', + 'USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3', + 'CHANNEL_FOLLOW_ADD', + // 13 isn't yet documented + null, + 'GUILD_DISCOVERY_DISQUALIFIED', + 'GUILD_DISCOVERY_REQUALIFIED', +]; + +/** + * <info>Bots cannot set a `CUSTOM_STATUS`, it is only for custom statuses received from users</info> + * The type of an activity of a users presence, e.g. `PLAYING`. Here are the available types: + * * PLAYING + * * STREAMING + * * LISTENING + * * WATCHING + * * CUSTOM_STATUS + * @typedef {string} ActivityType + */ +exports.ActivityTypes = ['PLAYING', 'STREAMING', 'LISTENING', 'WATCHING', 'CUSTOM_STATUS']; + +exports.ChannelTypes = { + TEXT: 0, + DM: 1, + VOICE: 2, + GROUP: 3, + CATEGORY: 4, + NEWS: 5, + STORE: 6, +}; + +exports.ClientApplicationAssetTypes = { + SMALL: 1, + BIG: 2, +}; + +exports.Colors = { + DEFAULT: 0x000000, + WHITE: 0xffffff, + AQUA: 0x1abc9c, + GREEN: 0x2ecc71, + BLUE: 0x3498db, + YELLOW: 0xffff00, + PURPLE: 0x9b59b6, + LUMINOUS_VIVID_PINK: 0xe91e63, + GOLD: 0xf1c40f, + ORANGE: 0xe67e22, + RED: 0xe74c3c, + GREY: 0x95a5a6, + NAVY: 0x34495e, + DARK_AQUA: 0x11806a, + DARK_GREEN: 0x1f8b4c, + DARK_BLUE: 0x206694, + DARK_PURPLE: 0x71368a, + DARK_VIVID_PINK: 0xad1457, + DARK_GOLD: 0xc27c0e, + DARK_ORANGE: 0xa84300, + DARK_RED: 0x992d22, + DARK_GREY: 0x979c9f, + DARKER_GREY: 0x7f8c8d, + LIGHT_GREY: 0xbcc0c0, + DARK_NAVY: 0x2c3e50, + BLURPLE: 0x7289da, + GREYPLE: 0x99aab5, + DARK_BUT_NOT_BLACK: 0x2c2f33, + NOT_QUITE_BLACK: 0x23272a, +}; + +/** + * The value set for the explicit content filter levels for a guild: + * * DISABLED + * * MEMBERS_WITHOUT_ROLES + * * ALL_MEMBERS + * @typedef {string} ExplicitContentFilterLevel + */ +exports.ExplicitContentFilterLevels = ['DISABLED', 'MEMBERS_WITHOUT_ROLES', 'ALL_MEMBERS']; + +/** + * The value set for the verification levels for a guild: + * * NONE + * * LOW + * * MEDIUM + * * HIGH + * * VERY_HIGH + * @typedef {string} VerificationLevel + */ +exports.VerificationLevels = ['NONE', 'LOW', 'MEDIUM', 'HIGH', 'VERY_HIGH']; + +/** + * An error encountered while performing an API request. Here are the potential errors: + * * UNKNOWN_ACCOUNT + * * UNKNOWN_APPLICATION + * * UNKNOWN_CHANNEL + * * UNKNOWN_GUILD + * * UNKNOWN_INTEGRATION + * * UNKNOWN_INVITE + * * UNKNOWN_MEMBER + * * UNKNOWN_MESSAGE + * * UNKNOWN_OVERWRITE + * * UNKNOWN_PROVIDER + * * UNKNOWN_ROLE + * * UNKNOWN_TOKEN + * * UNKNOWN_USER + * * UNKNOWN_EMOJI + * * UNKNOWN_WEBHOOK + * * BOT_PROHIBITED_ENDPOINT + * * BOT_ONLY_ENDPOINT + * * MAXIMUM_GUILDS + * * MAXIMUM_FRIENDS + * * MAXIMUM_PINS + * * MAXIMUM_ROLES + * * MAXIMUM_REACTIONS + * * MAXIMUM_CHANNELS + * * MAXIMUM_INVITES + * * UNAUTHORIZED + * * USER_BANNED + * * MISSING_ACCESS + * * INVALID_ACCOUNT_TYPE + * * CANNOT_EXECUTE_ON_DM + * * EMBED_DISABLED + * * CANNOT_EDIT_MESSAGE_BY_OTHER + * * CANNOT_SEND_EMPTY_MESSAGE + * * CANNOT_MESSAGE_USER + * * CANNOT_SEND_MESSAGES_IN_VOICE_CHANNEL + * * CHANNEL_VERIFICATION_LEVEL_TOO_HIGH + * * OAUTH2_APPLICATION_BOT_ABSENT + * * MAXIMUM_OAUTH2_APPLICATIONS + * * INVALID_OAUTH_STATE + * * MISSING_PERMISSIONS + * * INVALID_AUTHENTICATION_TOKEN + * * NOTE_TOO_LONG + * * INVALID_BULK_DELETE_QUANTITY + * * CANNOT_PIN_MESSAGE_IN_OTHER_CHANNEL + * * INVALID_OR_TAKEN_INVITE_CODE + * * CANNOT_EXECUTE_ON_SYSTEM_MESSAGE + * * INVALID_OAUTH_TOKEN + * * BULK_DELETE_MESSAGE_TOO_OLD + * * INVALID_FORM_BODY + * * INVITE_ACCEPTED_TO_GUILD_NOT_CONTAINING_BOT + * * INVALID_API_VERSION + * * REACTION_BLOCKED + * * RESOURCE_OVERLOADED + * @typedef {string} APIError + */ +exports.APIErrors = { + UNKNOWN_ACCOUNT: 10001, + UNKNOWN_APPLICATION: 10002, + UNKNOWN_CHANNEL: 10003, + UNKNOWN_GUILD: 10004, + UNKNOWN_INTEGRATION: 10005, + UNKNOWN_INVITE: 10006, + UNKNOWN_MEMBER: 10007, + UNKNOWN_MESSAGE: 10008, + UNKNOWN_OVERWRITE: 10009, + UNKNOWN_PROVIDER: 10010, + UNKNOWN_ROLE: 10011, + UNKNOWN_TOKEN: 10012, + UNKNOWN_USER: 10013, + UNKNOWN_EMOJI: 10014, + UNKNOWN_WEBHOOK: 10015, + BOT_PROHIBITED_ENDPOINT: 20001, + BOT_ONLY_ENDPOINT: 20002, + MAXIMUM_GUILDS: 30001, + MAXIMUM_FRIENDS: 30002, + MAXIMUM_PINS: 30003, + MAXIMUM_ROLES: 30005, + MAXIMUM_REACTIONS: 30010, + MAXIMUM_CHANNELS: 30013, + MAXIMUM_INVITES: 30016, + UNAUTHORIZED: 40001, + USER_BANNED: 40007, + MISSING_ACCESS: 50001, + INVALID_ACCOUNT_TYPE: 50002, + CANNOT_EXECUTE_ON_DM: 50003, + EMBED_DISABLED: 50004, + CANNOT_EDIT_MESSAGE_BY_OTHER: 50005, + CANNOT_SEND_EMPTY_MESSAGE: 50006, + CANNOT_MESSAGE_USER: 50007, + CANNOT_SEND_MESSAGES_IN_VOICE_CHANNEL: 50008, + CHANNEL_VERIFICATION_LEVEL_TOO_HIGH: 50009, + OAUTH2_APPLICATION_BOT_ABSENT: 50010, + MAXIMUM_OAUTH2_APPLICATIONS: 50011, + INVALID_OAUTH_STATE: 50012, + MISSING_PERMISSIONS: 50013, + INVALID_AUTHENTICATION_TOKEN: 50014, + NOTE_TOO_LONG: 50015, + INVALID_BULK_DELETE_QUANTITY: 50016, + CANNOT_PIN_MESSAGE_IN_OTHER_CHANNEL: 50019, + INVALID_OR_TAKEN_INVITE_CODE: 50020, + CANNOT_EXECUTE_ON_SYSTEM_MESSAGE: 50021, + INVALID_OAUTH_TOKEN: 50025, + BULK_DELETE_MESSAGE_TOO_OLD: 50034, + INVALID_FORM_BODY: 50035, + INVITE_ACCEPTED_TO_GUILD_NOT_CONTAINING_BOT: 50036, + INVALID_API_VERSION: 50041, + REACTION_BLOCKED: 90001, + RESOURCE_OVERLOADED: 130000, +}; + +/** + * The value set for a guild's default message notifications, e.g. `ALL`. Here are the available types: + * * ALL + * * MENTIONS + * @typedef {string} DefaultMessageNotifications + */ +exports.DefaultMessageNotifications = ['ALL', 'MENTIONS']; + +/** + * The value set for a team members's membership state: + * * INVITED + * * ACCEPTED + * @typedef {string} MembershipStates + */ +exports.MembershipStates = [ + // They start at 1 + null, + 'INVITED', + 'ACCEPTED', +]; + +/** + * The value set for a webhook's type: + * * Incoming + * * Channel Follower + * @typedef {string} WebhookTypes + */ +exports.WebhookTypes = [ + // They start at 1 + null, + 'Incoming', + 'Channel Follower', +]; + +function keyMirror(arr) { + let tmp = Object.create(null); + for (const value of arr) tmp[value] = value; + return tmp; +} diff --git a/node_modules/discord.js/src/util/DataResolver.js b/node_modules/discord.js/src/util/DataResolver.js new file mode 100644 index 0000000..71f690c --- /dev/null +++ b/node_modules/discord.js/src/util/DataResolver.js @@ -0,0 +1,127 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const stream = require('stream'); +const fetch = require('node-fetch'); +const { Error: DiscordError, TypeError } = require('../errors'); +const { browser } = require('../util/Constants'); +const Util = require('../util/Util'); + +/** + * The DataResolver identifies different objects and tries to resolve a specific piece of information from them. + * @private + */ +class DataResolver { + constructor() { + throw new Error(`The ${this.constructor.name} class may not be instantiated.`); + } + + /** + * Data that can be resolved to give an invite code. This can be: + * * An invite code + * * An invite URL + * @typedef {string} InviteResolvable + */ + + /** + * Resolves InviteResolvable to an invite code. + * @param {InviteResolvable} data The invite resolvable to resolve + * @returns {string} + */ + static resolveInviteCode(data) { + const inviteRegex = /discord(?:app\.com\/invite|\.gg(?:\/invite)?)\/([\w-]{2,255})/i; + const match = inviteRegex.exec(data); + if (match && match[1]) return match[1]; + return data; + } + + /** + * Resolves a Base64Resolvable, a string, or a BufferResolvable to a Base 64 image. + * @param {BufferResolvable|Base64Resolvable} image The image to be resolved + * @returns {Promise<?string>} + */ + static async resolveImage(image) { + if (!image) return null; + if (typeof image === 'string' && image.startsWith('data:')) { + return image; + } + const file = await this.resolveFileAsBuffer(image); + return DataResolver.resolveBase64(file); + } + + /** + * Data that resolves to give a Base64 string, typically for image uploading. This can be: + * * A Buffer + * * A base64 string + * @typedef {Buffer|string} Base64Resolvable + */ + + /** + * Resolves a Base64Resolvable to a Base 64 image. + * @param {Base64Resolvable} data The base 64 resolvable you want to resolve + * @returns {?string} + */ + static resolveBase64(data) { + if (Buffer.isBuffer(data)) return `data:image/jpg;base64,${data.toString('base64')}`; + return data; + } + + /** + * Data that can be resolved to give a Buffer. This can be: + * * A Buffer + * * The path to a local file + * * A URL + * @typedef {string|Buffer} BufferResolvable + */ + + /** + * @external Stream + * @see {@link https://nodejs.org/api/stream.html} + */ + + /** + * Resolves a BufferResolvable to a Buffer or a Stream. + * @param {BufferResolvable|Stream} resource The buffer or stream resolvable to resolve + * @returns {Promise<Buffer|Stream>} + */ + static async resolveFile(resource) { + if (!browser && Buffer.isBuffer(resource)) return resource; + if (browser && resource instanceof ArrayBuffer) return Util.convertToBuffer(resource); + if (resource instanceof stream.Readable) return resource; + + if (typeof resource === 'string') { + if (/^https?:\/\//.test(resource)) { + const res = await fetch(resource); + return browser ? res.blob() : res.body; + } else if (!browser) { + return new Promise((resolve, reject) => { + const file = path.resolve(resource); + fs.stat(file, (err, stats) => { + if (err) return reject(err); + if (!stats.isFile()) return reject(new DiscordError('FILE_NOT_FOUND', file)); + return resolve(fs.createReadStream(file)); + }); + }); + } + } + + throw new TypeError('REQ_RESOURCE_TYPE'); + } + + /** + * Resolves a BufferResolvable to a Buffer. + * @param {BufferResolvable|Stream} resource The buffer or stream resolvable to resolve + * @returns {Promise<Buffer>} + */ + static async resolveFileAsBuffer(resource) { + const file = await this.resolveFile(resource); + if (Buffer.isBuffer(file)) return file; + + const buffers = []; + for await (const data of file) buffers.push(data); + return Buffer.concat(buffers); + } +} + +module.exports = DataResolver; diff --git a/node_modules/discord.js/src/util/Intents.js b/node_modules/discord.js/src/util/Intents.js new file mode 100644 index 0000000..dc85e55 --- /dev/null +++ b/node_modules/discord.js/src/util/Intents.js @@ -0,0 +1,83 @@ +'use strict'; +const BitField = require('./BitField'); + +/** + * Data structure that makes it easy to calculate intents. + * @extends {BitField} + */ +class Intents extends BitField {} + +/** + * @name Intents + * @kind constructor + * @memberof Intents + * @param {IntentsResolvable} [bits=0] Bit(s) to read from + */ + +/** + * 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/node_modules/discord.js/src/util/LimitedCollection.js b/node_modules/discord.js/src/util/LimitedCollection.js new file mode 100644 index 0000000..caa4978 --- /dev/null +++ b/node_modules/discord.js/src/util/LimitedCollection.js @@ -0,0 +1,34 @@ +'use strict'; + +const Collection = require('./Collection.js'); + +/** + * A Collection which holds a max amount of entries. The first key is deleted if the Collection has + * reached max size. + * @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) { + super(iterable); + /** + * The max size of the Collection. + * @type {number} + */ + this.maxSize = maxSize; + } + + set(key, value) { + if (this.maxSize === 0) return this; + 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/node_modules/discord.js/src/util/MessageFlags.js b/node_modules/discord.js/src/util/MessageFlags.js new file mode 100644 index 0000000..f2bccc9 --- /dev/null +++ b/node_modules/discord.js/src/util/MessageFlags.js @@ -0,0 +1,36 @@ +'use strict'; + +const BitField = require('./BitField'); + +/** + * Data structure that makes it easy to interact with an {@link Message#flags} bitfield. + * @extends {BitField} + */ +class MessageFlags extends BitField {} + +/** + * @name MessageFlags + * @kind constructor + * @memberof MessageFlags + * @param {BitFieldResolvable} [bits=0] Bit(s) to read from + */ + +/** + * Numeric message flags. All available properties: + * * `CROSSPOSTED` + * * `IS_CROSSPOST` + * * `SUPPRESS_EMBEDS` + * * `SOURCE_MESSAGE_DELETED` + * * `URGENT` + * @type {Object} + * @see {@link https://discordapp.com/developers/docs/resources/channel#message-object-message-flags} + */ +MessageFlags.FLAGS = { + CROSSPOSTED: 1 << 0, + IS_CROSSPOST: 1 << 1, + SUPPRESS_EMBEDS: 1 << 2, + SOURCE_MESSAGE_DELETED: 1 << 3, + URGENT: 1 << 4, +}; + +module.exports = MessageFlags; diff --git a/node_modules/discord.js/src/util/Permissions.js b/node_modules/discord.js/src/util/Permissions.js new file mode 100644 index 0000000..6d32527 --- /dev/null +++ b/node_modules/discord.js/src/util/Permissions.js @@ -0,0 +1,131 @@ +'use strict'; + +const BitField = require('./BitField'); + +/** + * Data structure that makes it easy to interact with a permission bitfield. All {@link GuildMember}s have a set of + * permissions in their guild, and each channel in the guild may also have {@link PermissionOverwrites} for the member + * that override their default permissions. + * @extends {BitField} + */ +class Permissions extends BitField { + /** + * @name Permissions + * @kind constructor + * @memberof Permissions + * @param {PermissionResolvable} [bits=0] Bit(s) to read from + */ + + /** + * Data that can be resolved to give a permission number. This can be: + * * A string (see {@link Permissions.FLAGS}) + * * A permission number + * * An instance of Permissions + * * An Array of PermissionResolvable + * @typedef {string|number|Permissions|PermissionResolvable[]} PermissionResolvable + */ + + /** + * Checks whether the bitfield has a permission, or any of multiple permissions. + * @param {PermissionResolvable} permission Permission(s) to check for + * @param {boolean} [checkAdmin=true] Whether to allow the administrator permission to override + * @returns {boolean} + */ + any(permission, checkAdmin = true) { + return (checkAdmin && super.has(this.constructor.FLAGS.ADMINISTRATOR)) || super.any(permission); + } + + /** + * Checks whether the bitfield has a permission, or multiple permissions. + * @param {PermissionResolvable} permission Permission(s) to check for + * @param {boolean} [checkAdmin=true] Whether to allow the administrator permission to override + * @returns {boolean} + */ + has(permission, checkAdmin = true) { + return (checkAdmin && super.has(this.constructor.FLAGS.ADMINISTRATOR)) || super.has(permission); + } +} + +/** + * Numeric permission flags. All available properties: + * * `ADMINISTRATOR` (implicitly has *all* permissions, and bypasses all channel overwrites) + * * `CREATE_INSTANT_INVITE` (create invitations to the guild) + * * `KICK_MEMBERS` + * * `BAN_MEMBERS` + * * `MANAGE_CHANNELS` (edit and reorder channels) + * * `MANAGE_GUILD` (edit the guild information, region, etc.) + * * `ADD_REACTIONS` (add new reactions to messages) + * * `VIEW_AUDIT_LOG` + * * `PRIORITY_SPEAKER` + * * `STREAM` + * * `VIEW_CHANNEL` + * * `SEND_MESSAGES` + * * `SEND_TTS_MESSAGES` + * * `MANAGE_MESSAGES` (delete messages and reactions) + * * `EMBED_LINKS` (links posted will have a preview embedded) + * * `ATTACH_FILES` + * * `READ_MESSAGE_HISTORY` (view messages that were posted prior to opening Discord) + * * `MENTION_EVERYONE` + * * `USE_EXTERNAL_EMOJIS` (use emojis from different guilds) + * * `VIEW_GUILD_INSIGHTS` + * * `CONNECT` (connect to a voice channel) + * * `SPEAK` (speak in a voice channel) + * * `MUTE_MEMBERS` (mute members across all voice channels) + * * `DEAFEN_MEMBERS` (deafen members across all voice channels) + * * `MOVE_MEMBERS` (move members between voice channels) + * * `USE_VAD` (use voice activity detection) + * * `CHANGE_NICKNAME` + * * `MANAGE_NICKNAMES` (change other members' nicknames) + * * `MANAGE_ROLES` + * * `MANAGE_WEBHOOKS` + * * `MANAGE_EMOJIS` + * @type {Object} + * @see {@link https://discordapp.com/developers/docs/topics/permissions} + */ +Permissions.FLAGS = { + CREATE_INSTANT_INVITE: 1 << 0, + KICK_MEMBERS: 1 << 1, + BAN_MEMBERS: 1 << 2, + ADMINISTRATOR: 1 << 3, + MANAGE_CHANNELS: 1 << 4, + MANAGE_GUILD: 1 << 5, + ADD_REACTIONS: 1 << 6, + VIEW_AUDIT_LOG: 1 << 7, + PRIORITY_SPEAKER: 1 << 8, + STREAM: 1 << 9, + VIEW_CHANNEL: 1 << 10, + SEND_MESSAGES: 1 << 11, + SEND_TTS_MESSAGES: 1 << 12, + MANAGE_MESSAGES: 1 << 13, + EMBED_LINKS: 1 << 14, + ATTACH_FILES: 1 << 15, + READ_MESSAGE_HISTORY: 1 << 16, + MENTION_EVERYONE: 1 << 17, + USE_EXTERNAL_EMOJIS: 1 << 18, + VIEW_GUILD_INSIGHTS: 1 << 19, + CONNECT: 1 << 20, + SPEAK: 1 << 21, + MUTE_MEMBERS: 1 << 22, + DEAFEN_MEMBERS: 1 << 23, + MOVE_MEMBERS: 1 << 24, + USE_VAD: 1 << 25, + CHANGE_NICKNAME: 1 << 26, + MANAGE_NICKNAMES: 1 << 27, + MANAGE_ROLES: 1 << 28, + MANAGE_WEBHOOKS: 1 << 29, + MANAGE_EMOJIS: 1 << 30, +}; + +/** + * Bitfield representing every permission combined + * @type {number} + */ +Permissions.ALL = Object.values(Permissions.FLAGS).reduce((all, p) => all | p, 0); + +/** + * Bitfield representing the default permissions for users + * @type {number} + */ +Permissions.DEFAULT = 104324673; + +module.exports = Permissions; diff --git a/node_modules/discord.js/src/util/Snowflake.js b/node_modules/discord.js/src/util/Snowflake.js new file mode 100644 index 0000000..ec71033 --- /dev/null +++ b/node_modules/discord.js/src/util/Snowflake.js @@ -0,0 +1,87 @@ +'use strict'; + +const Util = require('../util/Util'); + +// Discord epoch (2015-01-01T00:00:00.000Z) +const EPOCH = 1420070400000; +let INCREMENT = 0; + +/** + * A container for useful snowflake-related methods. + */ +class SnowflakeUtil { + constructor() { + throw new Error(`The ${this.constructor.name} class may not be instantiated.`); + } + + /** + * A Twitter snowflake, except the epoch is 2015-01-01T00:00:00.000Z + * ``` + * If we have a snowflake '266241948824764416' we can represent it as binary: + * + * 64 22 17 12 0 + * 000000111011000111100001101001000101000000 00001 00000 000000000000 + * number of ms since Discord epoch worker pid increment + * ``` + * @typedef {string} Snowflake + */ + + /** + * Generates a Discord snowflake. + * <info>This hardcodes the worker ID as 1 and the process ID as 0.</info> + * @param {number|Date} [timestamp=Date.now()] Timestamp or date of the snowflake to generate + * @returns {Snowflake} The generated snowflake + */ + static generate(timestamp = Date.now()) { + if (timestamp instanceof Date) timestamp = timestamp.getTime(); + if (typeof timestamp !== 'number' || isNaN(timestamp)) { + throw new TypeError( + `"timestamp" argument must be a number (received ${isNaN(timestamp) ? 'NaN' : typeof timestamp})`, + ); + } + if (INCREMENT >= 4095) INCREMENT = 0; + // eslint-disable-next-line max-len + const BINARY = `${(timestamp - EPOCH).toString(2).padStart(42, '0')}0000100000${(INCREMENT++) + .toString(2) + .padStart(12, '0')}`; + return Util.binaryToID(BINARY); + } + + /** + * A deconstructed snowflake. + * @typedef {Object} DeconstructedSnowflake + * @property {number} timestamp Timestamp the snowflake was created + * @property {Date} date Date the snowflake was created + * @property {number} workerID Worker ID in the snowflake + * @property {number} processID Process ID in the snowflake + * @property {number} increment Increment in the snowflake + * @property {string} binary Binary representation of the snowflake + */ + + /** + * Deconstructs a Discord snowflake. + * @param {Snowflake} snowflake Snowflake to deconstruct + * @returns {DeconstructedSnowflake} Deconstructed snowflake + */ + static deconstruct(snowflake) { + const BINARY = Util.idToBinary(snowflake) + .toString(2) + .padStart(64, '0'); + const res = { + timestamp: parseInt(BINARY.substring(0, 42), 2) + EPOCH, + workerID: parseInt(BINARY.substring(42, 47), 2), + processID: parseInt(BINARY.substring(47, 52), 2), + increment: parseInt(BINARY.substring(52, 64), 2), + binary: BINARY, + }; + Object.defineProperty(res, 'date', { + get: function get() { + return new Date(this.timestamp); + }, + enumerable: true, + }); + return res; + } +} + +module.exports = SnowflakeUtil; diff --git a/node_modules/discord.js/src/util/Speaking.js b/node_modules/discord.js/src/util/Speaking.js new file mode 100644 index 0000000..8f819d1 --- /dev/null +++ b/node_modules/discord.js/src/util/Speaking.js @@ -0,0 +1,33 @@ +'use strict'; + +const BitField = require('./BitField'); + +/** + * Data structure that makes it easy to interact with a {@link VoiceConnection#speaking} + * and {@link guildMemberSpeaking} event bitfields. + * @extends {BitField} + */ +class Speaking extends BitField {} + +/** + * @name Speaking + * @kind constructor + * @memberof Speaking + * @param {BitFieldResolvable} [bits=0] Bit(s) to read from + */ + +/** + * Numeric speaking flags. All available properties: + * * `SPEAKING` + * * `SOUNDSHARE` + * * `PRIORITY_SPEAKING` + * @type {Object} + * @see {@link https://discordapp.com/developers/docs/topics/voice-connections#speaking} + */ +Speaking.FLAGS = { + SPEAKING: 1 << 0, + SOUNDSHARE: 1 << 1, + PRIORITY_SPEAKING: 1 << 2, +}; + +module.exports = Speaking; diff --git a/node_modules/discord.js/src/util/Structures.js b/node_modules/discord.js/src/util/Structures.js new file mode 100644 index 0000000..0c6ab35 --- /dev/null +++ b/node_modules/discord.js/src/util/Structures.js @@ -0,0 +1,112 @@ +'use strict'; + +/** + * An extendable structure: + * * **`GuildEmoji`** + * * **`DMChannel`** + * * **`TextChannel`** + * * **`VoiceChannel`** + * * **`CategoryChannel`** + * * **`NewsChannel`** + * * **`StoreChannel`** + * * **`GuildMember`** + * * **`Guild`** + * * **`Message`** + * * **`MessageReaction`** + * * **`Presence`** + * * **`ClientPresence`** + * * **`VoiceState`** + * * **`Role`** + * * **`User`** + * @typedef {string} ExtendableStructure + */ + +/** + * Allows for the extension of built-in Discord.js structures that are instantiated by {@link BaseManager Managers}. + */ +class Structures { + constructor() { + throw new Error(`The ${this.constructor.name} class may not be instantiated.`); + } + + /** + * Retrieves a structure class. + * @param {string} structure Name of the structure to retrieve + * @returns {Function} + */ + static get(structure) { + if (typeof structure === 'string') return structures[structure]; + throw new TypeError(`"structure" argument must be a string (received ${typeof structure})`); + } + + /** + * Extends a structure. + * <warn> Make sure to extend all structures before instantiating your client. + * Extending after doing so may not work as expected. </warn> + * @param {ExtendableStructure} structure Name of the structure class to extend + * @param {Function} extender Function that takes the base class to extend as its only parameter and returns the + * extended class/prototype + * @returns {Function} Extended class/prototype returned from the extender + * @example + * const { Structures } = require('discord.js'); + * + * Structures.extend('Guild', Guild => { + * class CoolGuild extends Guild { + * constructor(client, data) { + * super(client, data); + * this.cool = true; + * } + * } + * + * return CoolGuild; + * }); + */ + static extend(structure, extender) { + if (!structures[structure]) throw new RangeError(`"${structure}" is not a valid extensible structure.`); + if (typeof extender !== 'function') { + const received = `(received ${typeof extender})`; + throw new TypeError( + `"extender" argument must be a function that returns the extended structure class/prototype ${received}.`, + ); + } + + const extended = extender(structures[structure]); + if (typeof extended !== 'function') { + const received = `(received ${typeof extended})`; + throw new TypeError(`The extender function must return the extended structure class/prototype ${received}.`); + } + + if (!(extended.prototype instanceof structures[structure])) { + const prototype = Object.getPrototypeOf(extended); + const received = `${extended.name || 'unnamed'}${prototype.name ? ` extends ${prototype.name}` : ''}`; + throw new Error( + 'The class/prototype returned from the extender function must extend the existing structure class/prototype' + + ` (received function ${received}; expected extension of ${structures[structure].name}).`, + ); + } + + structures[structure] = extended; + return extended; + } +} + +const structures = { + GuildEmoji: require('../structures/GuildEmoji'), + DMChannel: require('../structures/DMChannel'), + TextChannel: require('../structures/TextChannel'), + VoiceChannel: require('../structures/VoiceChannel'), + CategoryChannel: require('../structures/CategoryChannel'), + NewsChannel: require('../structures/NewsChannel'), + StoreChannel: require('../structures/StoreChannel'), + GuildMember: require('../structures/GuildMember'), + Guild: require('../structures/Guild'), + Message: require('../structures/Message'), + MessageReaction: require('../structures/MessageReaction'), + Presence: require('../structures/Presence').Presence, + ClientPresence: require('../structures/ClientPresence'), + VoiceState: require('../structures/VoiceState'), + Role: require('../structures/Role'), + User: require('../structures/User'), +}; + +module.exports = Structures; diff --git a/node_modules/discord.js/src/util/SystemChannelFlags.js b/node_modules/discord.js/src/util/SystemChannelFlags.js new file mode 100644 index 0000000..4d08b76 --- /dev/null +++ b/node_modules/discord.js/src/util/SystemChannelFlags.js @@ -0,0 +1,40 @@ +'use strict'; + +const BitField = require('./BitField'); + +/** + * Data structure that makes it easy to interact with a {@link Guild#systemChannelFlags} bitfield. + * <info>Note that all event message types are enabled by default, + * and by setting their corresponding flags you are disabling them</info> + * @extends {BitField} + */ +class SystemChannelFlags extends BitField {} + +/** + * @name SystemChannelFlags + * @kind constructor + * @memberof SystemChannelFlags + * @param {SystemChannelFlagsResolvable} [bits=0] Bit(s) to read from + */ + +/** + * Data that can be resolved to give a sytem channel flag bitfield. This can be: + * * A string (see {@link SystemChannelFlags.FLAGS}) + * * A sytem channel flag + * * An instance of SystemChannelFlags + * * An Array of SystemChannelFlagsResolvable + * @typedef {string|number|SystemChannelFlags|SystemChannelFlagsResolvable[]} SystemChannelFlagsResolvable + */ + +/** + * Numeric system channel flags. All available properties: + * * `WELCOME_MESSAGE_DISABLED` + * * `BOOST_MESSAGE_DISABLED` + * @type {Object} + */ +SystemChannelFlags.FLAGS = { + WELCOME_MESSAGE_DISABLED: 1 << 0, + BOOST_MESSAGE_DISABLED: 1 << 1, +}; + +module.exports = SystemChannelFlags; diff --git a/node_modules/discord.js/src/util/UserFlags.js b/node_modules/discord.js/src/util/UserFlags.js new file mode 100644 index 0000000..120cb4f --- /dev/null +++ b/node_modules/discord.js/src/util/UserFlags.js @@ -0,0 +1,51 @@ +'use strict'; +const BitField = require('./BitField'); + +/** + * Data structure that makes it easy to interact with a {@link User#flags} bitfield. + * @extends {BitField} + */ +class UserFlags extends BitField {} + +/** + * @name UserFlags + * @kind constructor + * @memberof UserFlags + * @param {BitFieldResolvable} [bits=0] Bit(s) to read from + */ + +/** + * Numeric user flags. All available properties: + * * `DISCORD_EMPLOYEE` + * * `DISCORD_PARTNER` + * * `HYPESQUAD_EVENTS` + * * `BUGHUNTER_LEVEL_1` + * * `HOUSE_BRAVERY` + * * `HOUSE_BRILLIANCE` + * * `HOUSE_BALANCE` + * * `EARLY_SUPPORTER` + * * `TEAM_USER` + * * `SYSTEM` + * * `BUGHUNTER_LEVEL_2` + * * `VERIFIED_BOT` + * * `VERIFIED_DEVELOPER` + * @type {Object} + * @see {@link https://discordapp.com/developers/docs/resources/user#user-object-user-flags} + */ +UserFlags.FLAGS = { + DISCORD_EMPLOYEE: 1 << 0, + DISCORD_PARTNER: 1 << 1, + HYPESQUAD_EVENTS: 1 << 2, + BUGHUNTER_LEVEL_1: 1 << 3, + HOUSE_BRAVERY: 1 << 6, + HOUSE_BRILLIANCE: 1 << 7, + HOUSE_BALANCE: 1 << 8, + EARLY_SUPPORTER: 1 << 9, + TEAM_USER: 1 << 10, + SYSTEM: 1 << 12, + BUGHUNTER_LEVEL_2: 1 << 14, + VERIFIED_BOT: 1 << 16, + VERIFIED_DEVELOPER: 1 << 17, +}; + +module.exports = UserFlags; diff --git a/node_modules/discord.js/src/util/Util.js b/node_modules/discord.js/src/util/Util.js new file mode 100644 index 0000000..c4fcf48 --- /dev/null +++ b/node_modules/discord.js/src/util/Util.js @@ -0,0 +1,621 @@ +'use strict'; + +const { parse } = require('path'); +const fetch = require('node-fetch'); +const { Colors, DefaultOptions, Endpoints } = require('./Constants'); +const { Error: DiscordError, RangeError, TypeError } = require('../errors'); +const has = (o, k) => Object.prototype.hasOwnProperty.call(o, k); +const isObject = d => typeof d === 'object' && d !== null; + +/** + * Contains various general-purpose utility methods. These functions are also available on the base `Discord` object. + */ +class Util { + constructor() { + throw new Error(`The ${this.constructor.name} class may not be instantiated.`); + } + + /** + * Flatten an object. Any properties that are collections will get converted to an array of keys. + * @param {Object} obj The object to flatten. + * @param {...Object<string, boolean|string>} [props] Specific properties to include/exclude. + * @returns {Object} + */ + static flatten(obj, ...props) { + if (!isObject(obj)) return obj; + + props = Object.assign( + ...Object.keys(obj) + .filter(k => !k.startsWith('_')) + .map(k => ({ [k]: true })), + ...props, + ); + + const out = {}; + + for (let [prop, newProp] of Object.entries(props)) { + if (!newProp) continue; + newProp = newProp === true ? prop : newProp; + + const element = obj[prop]; + const elemIsObj = isObject(element); + const valueOf = elemIsObj && typeof element.valueOf === 'function' ? element.valueOf() : null; + + // If it's a Collection, make the array of keys + if (element instanceof require('./Collection')) out[newProp] = Array.from(element.keys()); + // If the valueOf is a Collection, use its array of keys + else if (valueOf instanceof require('./Collection')) out[newProp] = Array.from(valueOf.keys()); + // If it's an array, flatten each element + else if (Array.isArray(element)) out[newProp] = element.map(e => Util.flatten(e)); + // If it's an object with a primitive `valueOf`, use that value + else if (typeof valueOf !== 'object') out[newProp] = valueOf; + // If it's a primitive + else if (!elemIsObj) out[newProp] = element; + } + + return out; + } + + /** + * Splits a string into multiple chunks at a designated character that do not exceed a specific length. + * @param {StringResolvable} text Content to split + * @param {SplitOptions} [options] Options controlling the behavior of the split + * @returns {string[]} + */ + static splitMessage(text, { maxLength = 2000, char = '\n', prepend = '', append = '' } = {}) { + text = Util.resolveString(text); + if (text.length <= maxLength) return [text]; + const splitText = text.split(char); + if (splitText.some(chunk => chunk.length > maxLength)) throw new RangeError('SPLIT_MAX_LEN'); + const messages = []; + let msg = ''; + for (const chunk of splitText) { + if (msg && (msg + char + chunk + append).length > maxLength) { + messages.push(msg + append); + msg = prepend; + } + msg += (msg && msg !== prepend ? char : '') + chunk; + } + return messages.concat(msg).filter(m => m); + } + + /** + * Escapes any Discord-flavour markdown in a string. + * @param {string} text Content to escape + * @param {Object} [options={}] What types of markdown to escape + * @param {boolean} [options.codeBlock=true] Whether to escape code blocks or not + * @param {boolean} [options.inlineCode=true] Whether to escape inline code or not + * @param {boolean} [options.bold=true] Whether to escape bolds or not + * @param {boolean} [options.italic=true] Whether to escape italics or not + * @param {boolean} [options.underline=true] Whether to escape underlines or not + * @param {boolean} [options.strikethrough=true] Whether to escape strikethroughs or not + * @param {boolean} [options.spoiler=true] Whether to escape spoilers or not + * @param {boolean} [options.codeBlockContent=true] Whether to escape text inside code blocks or not + * @param {boolean} [options.inlineCodeContent=true] Whether to escape text inside inline code or not + * @returns {string} + */ + static escapeMarkdown( + text, + { + codeBlock = true, + inlineCode = true, + bold = true, + italic = true, + underline = true, + strikethrough = true, + spoiler = true, + codeBlockContent = true, + inlineCodeContent = true, + } = {}, + ) { + if (!codeBlockContent) { + return text + .split('```') + .map((subString, index, array) => { + if (index % 2 && index !== array.length - 1) return subString; + return Util.escapeMarkdown(subString, { + inlineCode, + bold, + italic, + underline, + strikethrough, + spoiler, + inlineCodeContent, + }); + }) + .join(codeBlock ? '\\`\\`\\`' : '```'); + } + if (!inlineCodeContent) { + return text + .split(/(?<=^|[^`])`(?=[^`]|$)/g) + .map((subString, index, array) => { + if (index % 2 && index !== array.length - 1) return subString; + return Util.escapeMarkdown(subString, { + codeBlock, + bold, + italic, + underline, + strikethrough, + spoiler, + }); + }) + .join(inlineCode ? '\\`' : '`'); + } + if (inlineCode) text = Util.escapeInlineCode(text); + if (codeBlock) text = Util.escapeCodeBlock(text); + if (italic) text = Util.escapeItalic(text); + if (bold) text = Util.escapeBold(text); + if (underline) text = Util.escapeUnderline(text); + if (strikethrough) text = Util.escapeStrikethrough(text); + if (spoiler) text = Util.escapeSpoiler(text); + return text; + } + + /** + * Escapes code block markdown in a string. + * @param {string} text Content to escape + * @returns {string} + */ + static escapeCodeBlock(text) { + return text.replace(/```/g, '\\`\\`\\`'); + } + + /** + * Escapes inline code markdown in a string. + * @param {string} text Content to escape + * @returns {string} + */ + static escapeInlineCode(text) { + return text.replace(/(?<=^|[^`])`(?=[^`]|$)/g, '\\`'); + } + + /** + * Escapes italic markdown in a string. + * @param {string} text Content to escape + * @returns {string} + */ + static escapeItalic(text) { + let i = 0; + text = text.replace(/(?<=^|[^*])\*([^*]|\*\*|$)/g, (_, match) => { + if (match === '**') return ++i % 2 ? `\\*${match}` : `${match}\\*`; + return `\\*${match}`; + }); + i = 0; + return text.replace(/(?<=^|[^_])_([^_]|__|$)/g, (_, match) => { + if (match === '__') return ++i % 2 ? `\\_${match}` : `${match}\\_`; + return `\\_${match}`; + }); + } + + /** + * Escapes bold markdown in a string. + * @param {string} text Content to escape + * @returns {string} + */ + static escapeBold(text) { + let i = 0; + return text.replace(/\*\*(\*)?/g, (_, match) => { + if (match) return ++i % 2 ? `${match}\\*\\*` : `\\*\\*${match}`; + return '\\*\\*'; + }); + } + + /** + * Escapes underline markdown in a string. + * @param {string} text Content to escape + * @returns {string} + */ + static escapeUnderline(text) { + let i = 0; + return text.replace(/__(_)?/g, (_, match) => { + if (match) return ++i % 2 ? `${match}\\_\\_` : `\\_\\_${match}`; + return '\\_\\_'; + }); + } + + /** + * Escapes strikethrough markdown in a string. + * @param {string} text Content to escape + * @returns {string} + */ + static escapeStrikethrough(text) { + return text.replace(/~~/g, '\\~\\~'); + } + + /** + * Escapes spoiler markdown in a string. + * @param {string} text Content to escape + * @returns {string} + */ + static escapeSpoiler(text) { + return text.replace(/\|\|/g, '\\|\\|'); + } + + /** + * Gets the recommended shard count from Discord. + * @param {string} token Discord auth token + * @param {number} [guildsPerShard=1000] Number of guilds per shard + * @returns {Promise<number>} The recommended number of shards + */ + static fetchRecommendedShards(token, guildsPerShard = 1000) { + if (!token) throw new DiscordError('TOKEN_MISSING'); + return fetch(`${DefaultOptions.http.api}/v${DefaultOptions.http.version}${Endpoints.botGateway}`, { + method: 'GET', + headers: { Authorization: `Bot ${token.replace(/^Bot\s*/i, '')}` }, + }) + .then(res => { + if (res.ok) return res.json(); + throw res; + }) + .then(data => data.shards * (1000 / guildsPerShard)); + } + + /** + * Parses emoji info out of a string. The string must be one of: + * * A UTF-8 emoji (no ID) + * * A URL-encoded UTF-8 emoji (no ID) + * * A Discord custom emoji (`<:name:id>` or `<a:name:id>`) + * @param {string} text Emoji string to parse + * @returns {Object} Object with `animated`, `name`, and `id` properties + * @private + */ + static parseEmoji(text) { + if (text.includes('%')) text = decodeURIComponent(text); + if (!text.includes(':')) return { animated: false, name: text, id: null }; + const m = text.match(/<?(?:(a):)?(\w{2,32}):(\d{17,19})?>?/); + if (!m) return null; + return { animated: Boolean(m[1]), name: m[2], id: m[3] || null }; + } + + /** + * Shallow-copies an object with its class/prototype intact. + * @param {Object} obj Object to clone + * @returns {Object} + * @private + */ + static cloneObject(obj) { + return Object.assign(Object.create(obj), obj); + } + + /** + * Sets default properties on an object that aren't already specified. + * @param {Object} def Default properties + * @param {Object} given Object to assign defaults to + * @returns {Object} + * @private + */ + static mergeDefault(def, given) { + if (!given) return def; + for (const key in def) { + if (!has(given, key) || given[key] === undefined) { + given[key] = def[key]; + } else if (given[key] === Object(given[key])) { + given[key] = Util.mergeDefault(def[key], given[key]); + } + } + + return given; + } + + /** + * Converts an ArrayBuffer or string to a Buffer. + * @param {ArrayBuffer|string} ab ArrayBuffer to convert + * @returns {Buffer} + * @private + */ + static convertToBuffer(ab) { + if (typeof ab === 'string') ab = Util.str2ab(ab); + return Buffer.from(ab); + } + + /** + * Converts a string to an ArrayBuffer. + * @param {string} str String to convert + * @returns {ArrayBuffer} + * @private + */ + static str2ab(str) { + const buffer = new ArrayBuffer(str.length * 2); + const view = new Uint16Array(buffer); + for (var i = 0, strLen = str.length; i < strLen; i++) view[i] = str.charCodeAt(i); + return buffer; + } + + /** + * Makes an Error from a plain info object. + * @param {Object} obj Error info + * @param {string} obj.name Error type + * @param {string} obj.message Message for the error + * @param {string} obj.stack Stack for the error + * @returns {Error} + * @private + */ + static makeError(obj) { + const err = new Error(obj.message); + err.name = obj.name; + err.stack = obj.stack; + return err; + } + + /** + * Makes a plain error info object from an Error. + * @param {Error} err Error to get info from + * @returns {Object} + * @private + */ + static makePlainError(err) { + return { + name: err.name, + message: err.message, + stack: err.stack, + }; + } + + /** + * Moves an element in an array *in place*. + * @param {Array<*>} array Array to modify + * @param {*} element Element to move + * @param {number} newIndex Index or offset to move the element to + * @param {boolean} [offset=false] Move the element by an offset amount rather than to a set index + * @returns {number} + * @private + */ + static moveElementInArray(array, element, newIndex, offset = false) { + const index = array.indexOf(element); + newIndex = (offset ? index : 0) + newIndex; + if (newIndex > -1 && newIndex < array.length) { + const removedElement = array.splice(index, 1)[0]; + array.splice(newIndex, 0, removedElement); + } + return array.indexOf(element); + } + + /** + * Data that can be resolved to give a string. This can be: + * * A string + * * An array (joined with a new line delimiter to give a string) + * * Any value + * @typedef {string|Array|*} StringResolvable + */ + + /** + * Resolves a StringResolvable to a string. + * @param {StringResolvable} data The string resolvable to resolve + * @returns {string} + */ + static resolveString(data) { + if (typeof data === 'string') return data; + if (Array.isArray(data)) return data.join('\n'); + return String(data); + } + + /** + * Can be a number, hex string, an RGB array like: + * ```js + * [255, 0, 255] // purple + * ``` + * or one of the following strings: + * - `DEFAULT` + * - `WHITE` + * - `AQUA` + * - `GREEN` + * - `BLUE` + * - `YELLOW` + * - `PURPLE` + * - `LUMINOUS_VIVID_PINK` + * - `GOLD` + * - `ORANGE` + * - `RED` + * - `GREY` + * - `DARKER_GREY` + * - `NAVY` + * - `DARK_AQUA` + * - `DARK_GREEN` + * - `DARK_BLUE` + * - `DARK_PURPLE` + * - `DARK_VIVID_PINK` + * - `DARK_GOLD` + * - `DARK_ORANGE` + * - `DARK_RED` + * - `DARK_GREY` + * - `LIGHT_GREY` + * - `DARK_NAVY` + * - `RANDOM` + * @typedef {string|number|number[]} ColorResolvable + */ + + /** + * Resolves a ColorResolvable into a color number. + * @param {ColorResolvable} color Color to resolve + * @returns {number} A color + */ + static resolveColor(color) { + if (typeof color === 'string') { + if (color === 'RANDOM') return Math.floor(Math.random() * (0xffffff + 1)); + if (color === 'DEFAULT') return 0; + color = Colors[color] || parseInt(color.replace('#', ''), 16); + } else if (Array.isArray(color)) { + color = (color[0] << 16) + (color[1] << 8) + color[2]; + } + + if (color < 0 || color > 0xffffff) throw new RangeError('COLOR_RANGE'); + else if (color && isNaN(color)) throw new TypeError('COLOR_CONVERT'); + + return color; + } + + /** + * Sorts by Discord's position and ID. + * @param {Collection} collection Collection of objects to sort + * @returns {Collection} + */ + static discordSort(collection) { + return collection.sorted( + (a, b) => + a.rawPosition - b.rawPosition || + parseInt(b.id.slice(0, -10)) - parseInt(a.id.slice(0, -10)) || + parseInt(b.id.slice(10)) - parseInt(a.id.slice(10)), + ); + } + + /** + * Sets the position of a Channel or Role. + * @param {Channel|Role} item Object to set the position of + * @param {number} position New position for the object + * @param {boolean} relative Whether `position` is relative to its current position + * @param {Collection<string, Channel|Role>} sorted A collection of the objects sorted properly + * @param {APIRouter} route Route to call PATCH on + * @param {string} [reason] Reason for the change + * @returns {Promise<Object[]>} Updated item list, with `id` and `position` properties + * @private + */ + static setPosition(item, position, relative, sorted, route, reason) { + let updatedItems = sorted.array(); + Util.moveElementInArray(updatedItems, item, position, relative); + updatedItems = updatedItems.map((r, i) => ({ id: r.id, position: i })); + return route.patch({ data: updatedItems, reason }).then(() => updatedItems); + } + + /** + * Alternative to Node's `path.basename`, removing query string after the extension if it exists. + * @param {string} path Path to get the basename of + * @param {string} [ext] File extension to remove + * @returns {string} Basename of the path + * @private + */ + static basename(path, ext) { + let res = parse(path); + return ext && res.ext.startsWith(ext) ? res.name : res.base.split('?')[0]; + } + + /** + * Transforms a snowflake from a decimal string to a bit string. + * @param {Snowflake} num Snowflake to be transformed + * @returns {string} + * @private + */ + static idToBinary(num) { + let bin = ''; + let high = parseInt(num.slice(0, -10)) || 0; + let low = parseInt(num.slice(-10)); + while (low > 0 || high > 0) { + bin = String(low & 1) + bin; + low = Math.floor(low / 2); + if (high > 0) { + low += 5000000000 * (high % 2); + high = Math.floor(high / 2); + } + } + return bin; + } + + /** + * Transforms a snowflake from a bit string to a decimal string. + * @param {string} num Bit string to be transformed + * @returns {Snowflake} + * @private + */ + static binaryToID(num) { + let dec = ''; + + while (num.length > 50) { + const high = parseInt(num.slice(0, -32), 2); + const low = parseInt((high % 10).toString(2) + num.slice(-32), 2); + + dec = (low % 10).toString() + dec; + num = + Math.floor(high / 10).toString(2) + + Math.floor(low / 10) + .toString(2) + .padStart(32, '0'); + } + + num = parseInt(num, 2); + while (num > 0) { + dec = (num % 10).toString() + dec; + num = Math.floor(num / 10); + } + + return dec; + } + + /** + * Breaks user, role and everyone/here mentions by adding a zero width space after every @ character + * @param {string} str The string to sanitize + * @returns {string} + */ + static removeMentions(str) { + return str.replace(/@/g, '@\u200b'); + } + + /** + * The content to have all mentions replaced by the equivalent text. + * @param {string} str The string to be converted + * @param {Message} message The message object to reference + * @returns {string} + */ + static cleanContent(str, message) { + str = str + .replace(/<@!?[0-9]+>/g, input => { + const id = input.replace(/<|!|>|@/g, ''); + if (message.channel.type === 'dm') { + const user = message.client.users.cache.get(id); + return user ? `@${user.username}` : input; + } + + const member = message.channel.guild.members.cache.get(id); + if (member) { + return `@${member.displayName}`; + } else { + const user = message.client.users.cache.get(id); + return user ? `@${user.username}` : input; + } + }) + .replace(/<#[0-9]+>/g, input => { + const channel = message.client.channels.cache.get(input.replace(/<|#|>/g, '')); + return channel ? `#${channel.name}` : input; + }) + .replace(/<@&[0-9]+>/g, input => { + if (message.channel.type === 'dm') return input; + const role = message.guild.roles.cache.get(input.replace(/<|@|>|&/g, '')); + return role ? `@${role.name}` : input; + }); + if (message.client.options.disableMentions === 'everyone') { + str = str.replace(/@([^<>@ ]*)/gmsu, (match, target) => { + if (target.match(/^[&!]?\d+$/)) { + return `@${target}`; + } else { + return `@\u200b${target}`; + } + }); + } + if (message.client.options.disableMentions === 'all') { + return Util.removeMentions(str); + } else { + return str; + } + } + + /** + * The content to put in a codeblock with all codeblock fences replaced by the equivalent backticks. + * @param {string} text The string to be converted + * @returns {string} + */ + static cleanCodeBlockContent(text) { + return text.replace(/```/g, '`\u200b``'); + } + + /** + * Creates a Promise that resolves after a specified duration. + * @param {number} ms How long to wait before resolving (in milliseconds) + * @returns {Promise<void>} + * @private + */ + static delayFor(ms) { + return new Promise(resolve => { + setTimeout(resolve, ms); + }); + } +} + +module.exports = Util; diff --git a/node_modules/discord.js/typings/index.d.ts b/node_modules/discord.js/typings/index.d.ts new file mode 100644 index 0000000..0470ca6 --- /dev/null +++ b/node_modules/discord.js/typings/index.d.ts @@ -0,0 +1,3080 @@ +declare enum ChannelType { + text = 0, + dm = 1, + voice = 2, + group = 3, + category = 4, + news = 5, + store = 6, + unknown = 7, +} + +declare module 'discord.js' { + import BaseCollection from '@discordjs/collection'; + import { ChildProcess } from 'child_process'; + import { EventEmitter } from 'events'; + import { PathLike } from 'fs'; + import { Readable, Stream, Writable } from 'stream'; + import * as WebSocket from 'ws'; + + export const version: string; + + //#region Classes + + export class Activity { + constructor(presence: Presence, data?: object); + public applicationID: Snowflake | null; + public assets: RichPresenceAssets | null; + public readonly createdAt: Date; + public createdTimestamp: number; + public details: string | null; + public emoji: Emoji | null; + public name: string; + public party: { + id: string | null; + size: [number, number]; + } | null; + public state: string | null; + public timestamps: { + start: Date | null; + end: Date | null; + } | null; + public type: ActivityType; + public url: string | null; + public equals(activity: Activity): boolean; + } + + export class ActivityFlags extends BitField<ActivityFlagsString> { + public static FLAGS: Record<ActivityFlagsString, number>; + public static resolve(bit?: BitFieldResolvable<ActivityFlagsString>): number; + } + + export class APIMessage { + constructor(target: MessageTarget, options: MessageOptions | WebhookMessageOptions); + public data: object | null; + public readonly isUser: boolean; + public readonly isWebhook: boolean; + public files: object[] | null; + public options: MessageOptions | WebhookMessageOptions; + public target: MessageTarget; + + public static create( + target: MessageTarget, + content?: StringResolvable, + options?: MessageOptions | WebhookMessageOptions | MessageAdditions, + extra?: MessageOptions | WebhookMessageOptions, + ): APIMessage; + public static partitionMessageAdditions( + items: (MessageEmbed | MessageAttachment)[], + ): [MessageEmbed[], MessageAttachment[]]; + public static resolveFile(fileLike: BufferResolvable | Stream | FileOptions | MessageAttachment): Promise<object>; + public static transformOptions( + content: StringResolvable, + options: MessageOptions | WebhookMessageOptions | MessageAdditions, + extra?: MessageOptions | WebhookMessageOptions, + isWebhook?: boolean, + ): MessageOptions | WebhookMessageOptions; + + public makeContent(): string | string[] | undefined; + public resolve(): Promise<this>; + public resolveData(): this; + public resolveFiles(): Promise<this>; + public split(): APIMessage[]; + } + + export class Base { + constructor(client: Client); + public readonly client: Client; + public toJSON(...props: { [key: string]: boolean | string }[]): object; + public valueOf(): string; + } + + export class BaseClient extends EventEmitter { + constructor(options?: ClientOptions); + private _timeouts: Set<NodeJS.Timer>; + private _intervals: Set<NodeJS.Timer>; + private _immediates: Set<NodeJS.Immediate>; + private readonly api: object; + private rest: object; + + public options: ClientOptions; + public clearInterval(interval: NodeJS.Timer): void; + public clearTimeout(timeout: NodeJS.Timer): void; + public clearImmediate(timeout: NodeJS.Immediate): void; + public destroy(): void; + public setInterval(fn: (...args: any[]) => void, delay: number, ...args: any[]): NodeJS.Timer; + public setTimeout(fn: (...args: any[]) => void, delay: number, ...args: any[]): NodeJS.Timer; + public setImmediate(fn: (...args: any[]) => void, ...args: any[]): NodeJS.Immediate; + public toJSON(...props: { [key: string]: boolean | string }[]): object; + } + + export class BaseGuildEmoji extends Emoji { + constructor(client: Client, data: object, guild: Guild); + private _roles: string[]; + + public available: boolean; + public readonly createdAt: Date; + public readonly createdTimestamp: number; + public guild: Guild | GuildPreview; + public id: Snowflake; + public managed: boolean; + public requiresColons: boolean; + } + + class BroadcastDispatcher extends VolumeMixin(StreamDispatcher) { + public broadcast: VoiceBroadcast; + } + + export class BitField<S extends string> { + constructor(bits?: BitFieldResolvable<S>); + public bitfield: number; + public add(...bits: BitFieldResolvable<S>[]): BitField<S>; + public any(bit: BitFieldResolvable<S>): boolean; + public equals(bit: BitFieldResolvable<S>): boolean; + public freeze(): Readonly<BitField<S>>; + public has(bit: BitFieldResolvable<S>): boolean; + public missing(bits: BitFieldResolvable<S>, ...hasParam: readonly unknown[]): S[]; + public remove(...bits: BitFieldResolvable<S>[]): BitField<S>; + public serialize(...hasParam: readonly unknown[]): Record<S, boolean>; + public toArray(...hasParam: readonly unknown[]): S[]; + public toJSON(): number; + public valueOf(): number; + public [Symbol.iterator](): IterableIterator<S>; + public static FLAGS: object; + public static resolve(bit?: BitFieldResolvable<any>): number; + } + + export class CategoryChannel extends GuildChannel { + public readonly children: Collection<Snowflake, GuildChannel>; + public type: 'category'; + } + + export class Channel extends Base { + constructor(client: Client, data?: object); + public readonly createdAt: Date; + public readonly createdTimestamp: number; + public deleted: boolean; + public id: Snowflake; + public type: keyof typeof ChannelType; + public delete(reason?: string): Promise<this>; + public fetch(): Promise<this>; + public toString(): string; + } + + export class Client extends BaseClient { + constructor(options?: ClientOptions); + private actions: object; + private _eval(script: string): any; + private _validateOptions(options?: ClientOptions): void; + + public channels: ChannelManager; + public readonly emojis: GuildEmojiManager; + public guilds: GuildManager; + public readyAt: Date | null; + public readonly readyTimestamp: number | null; + public shard: ShardClientUtil | null; + public token: string | null; + public readonly uptime: number | null; + public user: ClientUser | null; + public users: UserManager; + public voice: ClientVoiceManager | null; + public ws: WebSocketManager; + public destroy(): void; + public fetchApplication(): Promise<ClientApplication>; + public fetchGuildPreview(guild: GuildResolvable): Promise<GuildPreview>; + public fetchInvite(invite: InviteResolvable): Promise<Invite>; + public fetchVoiceRegions(): Promise<Collection<string, VoiceRegion>>; + public fetchWebhook(id: Snowflake, token?: string): Promise<Webhook>; + public generateInvite(permissions?: PermissionResolvable): Promise<string>; + public login(token?: string): Promise<string>; + public sweepMessages(lifetime?: number): number; + public toJSON(): object; + + public on<K extends keyof ClientEvents>(event: K, listener: (...args: ClientEvents[K]) => void): this; + + public once<K extends keyof ClientEvents>(event: K, listener: (...args: ClientEvents[K]) => void): this; + + public emit<K extends keyof ClientEvents>(event: K, ...args: ClientEvents[K]): boolean; + } + + export class ClientApplication extends Base { + constructor(client: Client, data: object); + public botPublic: boolean | null; + public botRequireCodeGrant: boolean | null; + public cover: string | null; + public readonly createdAt: Date; + public readonly createdTimestamp: number; + public description: string; + public icon: string; + public id: Snowflake; + public name: string; + public owner: User | Team | null; + public rpcOrigins: string[]; + public coverImage(options?: ImageURLOptions): string; + public fetchAssets(): Promise<ClientApplicationAsset[]>; + public iconURL(options?: ImageURLOptions): string; + public toJSON(): object; + public toString(): string; + } + + export class ClientUser extends User { + public mfaEnabled: boolean; + public verified: boolean; + public setActivity(options?: ActivityOptions): Promise<Presence>; + public setActivity(name: string, options?: ActivityOptions): Promise<Presence>; + public setAFK(afk: boolean): Promise<Presence>; + public setAvatar(avatar: BufferResolvable | Base64Resolvable): Promise<ClientUser>; + public setPresence(data: PresenceData): Promise<Presence>; + public setStatus(status: PresenceStatusData, shardID?: number | number[]): Promise<Presence>; + public setUsername(username: string): Promise<ClientUser>; + } + + export class ClientVoiceManager { + constructor(client: Client); + public readonly client: Client; + public connections: Collection<Snowflake, VoiceConnection>; + public broadcasts: VoiceBroadcast[]; + + private joinChannel(channel: VoiceChannel): Promise<VoiceConnection>; + + public createBroadcast(): VoiceBroadcast; + } + + export abstract class Collector<K, V> extends EventEmitter { + constructor(client: Client, filter: CollectorFilter, options?: CollectorOptions); + private _timeout: NodeJS.Timer | null; + private _idletimeout: NodeJS.Timer | null; + + public readonly client: Client; + public collected: Collection<K, V>; + public ended: boolean; + public filter: CollectorFilter; + public readonly next: Promise<V>; + public options: CollectorOptions; + public checkEnd(): void; + public handleCollect(...args: any[]): void; + public handleDispose(...args: any[]): void; + public stop(reason?: string): void; + public resetTimer(options?: { time?: number; idle?: number }): void; + public [Symbol.asyncIterator](): AsyncIterableIterator<V>; + public toJSON(): object; + + protected listener: (...args: any[]) => void; + public abstract collect(...args: any[]): K; + public abstract dispose(...args: any[]): K; + public abstract endReason(): void; + + public on(event: 'collect' | 'dispose', listener: (...args: any[]) => void): this; + public on(event: 'end', listener: (collected: Collection<K, V>, reason: string) => void): this; + + public once(event: 'collect' | 'dispose', listener: (...args: any[]) => void): this; + public once(event: 'end', listener: (collected: Collection<K, V>, reason: string) => void): this; + } + + type AllowedImageFormat = 'webp' | 'png' | 'jpg' | 'jpeg' | 'gif'; + + export const Constants: { + Package: { + name: string; + version: string; + description: string; + author: string; + license: string; + main: PathLike; + types: PathLike; + homepage: string; + keywords: string[]; + bugs: { url: string }; + repository: { type: string; url: string }; + browser: { [key: string]: boolean }; + scripts: { [key: string]: string }; + engines: { [key: string]: string }; + dependencies: { [key: string]: string }; + peerDependencies: { [key: string]: string }; + devDependencies: { [key: string]: string }; + [key: string]: any; + }; + browser: boolean; + DefaultOptions: ClientOptions; + UserAgent: string | null; + Endpoints: { + botGateway: string; + invite: (root: string, code: string) => string; + CDN: ( + root: string, + ) => { + Asset: (name: string) => string; + DefaultAvatar: (id: string | number) => string; + Emoji: (emojiID: string, format: 'png' | 'gif') => string; + Avatar: (userID: string | number, hash: string, format: 'default' | AllowedImageFormat, size: number) => string; + Banner: (guildID: string | number, hash: string, format: AllowedImageFormat, size: number) => string; + Icon: (userID: string | number, hash: string, format: 'default' | AllowedImageFormat, size: number) => string; + AppIcon: (userID: string | number, hash: string, format: AllowedImageFormat, size: number) => string; + AppAsset: (userID: string | number, hash: string, format: AllowedImageFormat, size: number) => string; + GDMIcon: (userID: string | number, hash: string, format: AllowedImageFormat, size: number) => string; + Splash: (guildID: string | number, hash: string, format: AllowedImageFormat, size: number) => string; + DiscoverySplash: (guildID: string | number, hash: string, format: AllowedImageFormat, size: number) => string; + TeamIcon: (teamID: string | number, hash: string, format: AllowedImageFormat, size: number) => string; + }; + }; + WSCodes: { + 1000: 'WS_CLOSE_REQUESTED'; + 4004: 'TOKEN_INVALID'; + 4010: 'SHARDING_INVALID'; + 4011: 'SHARDING_REQUIRED'; + }; + Events: { + RATE_LIMIT: 'rateLimit'; + CLIENT_READY: 'ready'; + RESUMED: 'resumed'; + GUILD_CREATE: 'guildCreate'; + GUILD_DELETE: 'guildDelete'; + GUILD_UPDATE: 'guildUpdate'; + INVITE_CREATE: 'inviteCreate'; + INVITE_DELETE: 'inviteDelete'; + GUILD_UNAVAILABLE: 'guildUnavailable'; + GUILD_MEMBER_ADD: 'guildMemberAdd'; + GUILD_MEMBER_REMOVE: 'guildMemberRemove'; + GUILD_MEMBER_UPDATE: 'guildMemberUpdate'; + GUILD_MEMBER_AVAILABLE: 'guildMemberAvailable'; + GUILD_MEMBER_SPEAKING: 'guildMemberSpeaking'; + GUILD_MEMBERS_CHUNK: 'guildMembersChunk'; + GUILD_INTEGRATIONS_UPDATE: 'guildIntegrationsUpdate'; + GUILD_ROLE_CREATE: 'roleCreate'; + GUILD_ROLE_DELETE: 'roleDelete'; + GUILD_ROLE_UPDATE: 'roleUpdate'; + GUILD_EMOJI_CREATE: 'emojiCreate'; + GUILD_EMOJI_DELETE: 'emojiDelete'; + GUILD_EMOJI_UPDATE: 'emojiUpdate'; + GUILD_BAN_ADD: 'guildBanAdd'; + GUILD_BAN_REMOVE: 'guildBanRemove'; + CHANNEL_CREATE: 'channelCreate'; + CHANNEL_DELETE: 'channelDelete'; + CHANNEL_UPDATE: 'channelUpdate'; + CHANNEL_PINS_UPDATE: 'channelPinsUpdate'; + MESSAGE_CREATE: 'message'; + MESSAGE_DELETE: 'messageDelete'; + MESSAGE_UPDATE: 'messageUpdate'; + MESSAGE_BULK_DELETE: 'messageDeleteBulk'; + MESSAGE_REACTION_ADD: 'messageReactionAdd'; + MESSAGE_REACTION_REMOVE: 'messageReactionRemove'; + MESSAGE_REACTION_REMOVE_ALL: 'messageReactionRemoveAll'; + USER_UPDATE: 'userUpdate'; + PRESENCE_UPDATE: 'presenceUpdate'; + VOICE_STATE_UPDATE: 'voiceStateUpdate'; + VOICE_BROADCAST_SUBSCRIBE: 'subscribe'; + VOICE_BROADCAST_UNSUBSCRIBE: 'unsubscribe'; + TYPING_START: 'typingStart'; + WEBHOOKS_UPDATE: 'webhookUpdate'; + DISCONNECT: 'disconnect'; + RECONNECTING: 'reconnecting'; + ERROR: 'error'; + WARN: 'warn'; + DEBUG: 'debug'; + SHARD_DISCONNECT: 'shardDisconnect'; + SHARD_ERROR: 'shardError'; + SHARD_RECONNECTING: 'shardReconnecting'; + SHARD_READY: 'shardReady'; + SHARD_RESUME: 'shardResume'; + INVALIDATED: 'invalidated'; + RAW: 'raw'; + }; + ShardEvents: { + CLOSE: 'close'; + DESTROYED: 'destroyed'; + INVALID_SESSION: 'invalidSession'; + READY: 'ready'; + RESUMED: 'resumed'; + }; + PartialTypes: { + [K in PartialTypes]: K; + }; + WSEvents: { + [K in WSEventType]: K; + }; + Colors: { + DEFAULT: 0x000000; + WHITE: 0xffffff; + AQUA: 0x1abc9c; + GREEN: 0x2ecc71; + BLUE: 0x3498db; + YELLOW: 0xffff00; + PURPLE: 0x9b59b6; + LUMINOUS_VIVID_PINK: 0xe91e63; + GOLD: 0xf1c40f; + ORANGE: 0xe67e22; + RED: 0xe74c3c; + GREY: 0x95a5a6; + NAVY: 0x34495e; + DARK_AQUA: 0x11806a; + DARK_GREEN: 0x1f8b4c; + DARK_BLUE: 0x206694; + DARK_PURPLE: 0x71368a; + DARK_VIVID_PINK: 0xad1457; + DARK_GOLD: 0xc27c0e; + DARK_ORANGE: 0xa84300; + DARK_RED: 0x992d22; + DARK_GREY: 0x979c9f; + DARKER_GREY: 0x7f8c8d; + LIGHT_GREY: 0xbcc0c0; + DARK_NAVY: 0x2c3e50; + BLURPLE: 0x7289da; + GREYPLE: 0x99aab5; + DARK_BUT_NOT_BLACK: 0x2c2f33; + NOT_QUITE_BLACK: 0x23272a; + }; + Status: { + READY: 0; + CONNECTING: 1; + RECONNECTING: 2; + IDLE: 3; + NEARLY: 4; + DISCONNECTED: 5; + }; + OPCodes: { + DISPATCH: 0; + HEARTBEAT: 1; + IDENTIFY: 2; + STATUS_UPDATE: 3; + VOICE_STATE_UPDATE: 4; + VOICE_GUILD_PING: 5; + RESUME: 6; + RECONNECT: 7; + REQUEST_GUILD_MEMBERS: 8; + INVALID_SESSION: 9; + HELLO: 10; + HEARTBEAT_ACK: 11; + }; + APIErrors: { + UNKNOWN_ACCOUNT: 10001; + UNKNOWN_APPLICATION: 10002; + UNKNOWN_CHANNEL: 10003; + UNKNOWN_GUILD: 10004; + UNKNOWN_INTEGRATION: 10005; + UNKNOWN_INVITE: 10006; + UNKNOWN_MEMBER: 10007; + UNKNOWN_MESSAGE: 10008; + UNKNOWN_OVERWRITE: 10009; + UNKNOWN_PROVIDER: 10010; + UNKNOWN_ROLE: 10011; + UNKNOWN_TOKEN: 10012; + UNKNOWN_USER: 10013; + UNKNOWN_EMOJI: 10014; + UNKNOWN_WEBHOOK: 10015; + BOT_PROHIBITED_ENDPOINT: 20001; + BOT_ONLY_ENDPOINT: 20002; + MAXIMUM_GUILDS: 30001; + MAXIMUM_FRIENDS: 30002; + MAXIMUM_PINS: 30003; + MAXIMUM_ROLES: 30005; + MAXIMUM_REACTIONS: 30010; + MAXIMUM_CHANNELS: 30013; + MAXIMUM_INVITES: 30016; + UNAUTHORIZED: 40001; + USER_BANNED: 40007; + MISSING_ACCESS: 50001; + INVALID_ACCOUNT_TYPE: 50002; + CANNOT_EXECUTE_ON_DM: 50003; + EMBED_DISABLED: 50004; + CANNOT_EDIT_MESSAGE_BY_OTHER: 50005; + CANNOT_SEND_EMPTY_MESSAGE: 50006; + CANNOT_MESSAGE_USER: 50007; + CANNOT_SEND_MESSAGES_IN_VOICE_CHANNEL: 50008; + CHANNEL_VERIFICATION_LEVEL_TOO_HIGH: 50009; + OAUTH2_APPLICATION_BOT_ABSENT: 50010; + MAXIMUM_OAUTH2_APPLICATIONS: 50011; + INVALID_OAUTH_STATE: 50012; + MISSING_PERMISSIONS: 50013; + INVALID_AUTHENTICATION_TOKEN: 50014; + NOTE_TOO_LONG: 50015; + INVALID_BULK_DELETE_QUANTITY: 50016; + CANNOT_PIN_MESSAGE_IN_OTHER_CHANNEL: 50019; + CANNOT_EXECUTE_ON_SYSTEM_MESSAGE: 50021; + INVALID_OAUTH_TOKEN: 50025; + BULK_DELETE_MESSAGE_TOO_OLD: 50034; + INVALID_FORM_BODY: 50035; + INVITE_ACCEPTED_TO_GUILD_NOT_CONTAINING_BOT: 50036; + INVALID_API_VERSION: 50041; + REACTION_BLOCKED: 90001; + RESOURCE_OVERLOADED: 130000; + }; + VoiceStatus: { + CONNECTED: 0; + CONNECTING: 1; + AUTHENTICATING: 2; + RECONNECTING: 3; + DISCONNECTED: 4; + }; + VoiceOPCodes: { + IDENTIFY: 0; + SELECT_PROTOCOL: 1; + READY: 2; + HEARTBEAT: 3; + SESSION_DESCRIPTION: 4; + SPEAKING: 5; + HELLO: 8; + CLIENT_CONNECT: 12; + CLIENT_DISCONNECT: 13; + }; + ChannelTypes: { + TEXT: 0; + DM: 1; + VOICE: 2; + GROUP: 3; + CATEGORY: 4; + NEWS: 5; + STORE: 6; + }; + ClientApplicationAssetTypes: { + SMALL: 1; + BIG: 2; + }; + MessageTypes: MessageType[]; + ActivityTypes: ActivityType[]; + ExplicitContentFilterLevels: ExplicitContentFilterLevel[]; + DefaultMessageNotifications: DefaultMessageNotifications[]; + VerificationLevels: VerificationLevel[]; + MembershipStates: 'INVITED' | 'ACCEPTED'; + }; + + export class DataResolver { + public static resolveBase64(data: Base64Resolvable): string; + public static resolveFile(resource: BufferResolvable | Stream): Promise<Buffer | Stream>; + public static resolveFileAsBuffer(resource: BufferResolvable | Stream): Promise<Buffer>; + public static resolveImage(resource: BufferResolvable | Base64Resolvable): Promise<string>; + public static resolveInviteCode(data: InviteResolvable): string; + } + + export class DiscordAPIError extends Error { + constructor(path: string, error: object, method: string, httpStatus: number); + private static flattenErrors(obj: object, key: string): string[]; + + public code: number; + public method: string; + public path: string; + public httpStatus: number; + } + + export class DMChannel extends TextBasedChannel(Channel) { + constructor(client: Client, data?: object); + public messages: MessageManager; + public recipient: User; + public readonly partial: false; + public type: 'dm'; + public fetch(): Promise<this>; + } + + export class Emoji extends Base { + constructor(client: Client, emoji: object); + public animated: boolean; + public readonly createdAt: Date | null; + public readonly createdTimestamp: number | null; + public deleted: boolean; + public id: Snowflake | null; + public name: string; + public readonly identifier: string; + public readonly url: string | null; + public toJSON(): object; + public toString(): string; + } + + export class Guild extends Base { + constructor(client: Client, data: object); + private _sortedRoles(): Collection<Snowflake, Role>; + private _sortedChannels(channel: Channel): Collection<Snowflake, GuildChannel>; + private _memberSpeakUpdate(user: Snowflake, speaking: boolean): void; + + public readonly afkChannel: VoiceChannel | null; + public afkChannelID: Snowflake | null; + public afkTimeout: number; + public applicationID: Snowflake | null; + public available: boolean; + public banner: string | null; + public channels: GuildChannelManager; + public readonly createdAt: Date; + public readonly createdTimestamp: number; + public defaultMessageNotifications: DefaultMessageNotifications | number; + public deleted: boolean; + public description: string | null; + public embedChannel: GuildChannel | null; + public embedChannelID: Snowflake | null; + public embedEnabled: boolean; + public emojis: GuildEmojiManager; + public explicitContentFilter: ExplicitContentFilterLevel; + public features: GuildFeatures[]; + public icon: string | null; + public id: Snowflake; + public readonly joinedAt: Date; + public joinedTimestamp: number; + public large: boolean; + public maximumMembers: number | null; + public maximumPresences: number | null; + public readonly me: GuildMember | null; + public memberCount: number; + public members: GuildMemberManager; + public mfaLevel: number; + public name: string; + public readonly nameAcronym: string; + public readonly owner: GuildMember | null; + public ownerID: Snowflake; + public readonly partnered: boolean; + public premiumSubscriptionCount: number | null; + public premiumTier: PremiumTier; + public presences: PresenceManager; + public readonly publicUpdatesChannel: TextChannel | null; + public publicUpdatesChannelID: Snowflake | null; + public region: string; + public roles: RoleManager; + public readonly rulesChannel: TextChannel | null; + public rulesChannelID: Snowflake | null; + public readonly shard: WebSocketShard; + public shardID: number; + public splash: string | null; + public readonly systemChannel: TextChannel | null; + public systemChannelFlags: Readonly<SystemChannelFlags>; + public systemChannelID: Snowflake | null; + public vanityURLCode: string | null; + public verificationLevel: VerificationLevel; + public readonly verified: boolean; + public readonly voice: VoiceState | null; + public readonly voiceStates: VoiceStateManager; + public readonly widgetChannel: TextChannel | null; + public widgetChannelID: Snowflake | null; + public widgetEnabled: boolean | null; + public addMember(user: UserResolvable, options: AddGuildMemberOptions): Promise<GuildMember>; + public bannerURL(options?: ImageURLOptions): string | null; + public createIntegration(data: IntegrationData, reason?: string): Promise<Guild>; + public delete(): Promise<Guild>; + public edit(data: GuildEditData, reason?: string): Promise<Guild>; + public equals(guild: Guild): boolean; + public fetch(): Promise<Guild>; + public fetchAuditLogs(options?: GuildAuditLogsFetchOptions): Promise<GuildAuditLogs>; + public fetchBan(user: UserResolvable): Promise<{ user: User; reason: string }>; + public fetchBans(): Promise<Collection<Snowflake, { user: User; reason: string }>>; + public fetchEmbed(): Promise<GuildEmbedData>; + public fetchIntegrations(): Promise<Collection<string, Integration>>; + public fetchInvites(): Promise<Collection<string, Invite>>; + public fetchPreview(): Promise<GuildPreview>; + public fetchVanityCode(): Promise<string>; + public fetchVoiceRegions(): Promise<Collection<string, VoiceRegion>>; + public fetchWebhooks(): Promise<Collection<Snowflake, Webhook>>; + public iconURL(options?: ImageURLOptions & { dynamic?: boolean }): string | null; + public leave(): Promise<Guild>; + public member(user: UserResolvable): GuildMember | null; + public setAFKChannel(afkChannel: ChannelResolvable | null, reason?: string): Promise<Guild>; + public setAFKTimeout(afkTimeout: number, reason?: string): Promise<Guild>; + public setBanner(banner: Base64Resolvable | null, reason?: string): Promise<Guild>; + public setChannelPositions(channelPositions: ChannelPosition[]): Promise<Guild>; + public setDefaultMessageNotifications( + defaultMessageNotifications: DefaultMessageNotifications | number, + reason?: string, + ): Promise<Guild>; + public setEmbed(embed: GuildEmbedData, reason?: string): Promise<Guild>; + public setExplicitContentFilter(explicitContentFilter: ExplicitContentFilterLevel, reason?: string): Promise<Guild>; + public setIcon(icon: Base64Resolvable | null, reason?: string): Promise<Guild>; + public setName(name: string, reason?: string): Promise<Guild>; + public setOwner(owner: GuildMemberResolvable, reason?: string): Promise<Guild>; + public setRegion(region: string, reason?: string): Promise<Guild>; + public setRolePositions(rolePositions: RolePosition[]): Promise<Guild>; + public setSplash(splash: Base64Resolvable | null, reason?: string): Promise<Guild>; + public setSystemChannel(systemChannel: ChannelResolvable | null, reason?: string): Promise<Guild>; + public setSystemChannelFlags(systemChannelFlags: SystemChannelFlagsResolvable, reason?: string): Promise<Guild>; + public setVerificationLevel(verificationLevel: VerificationLevel, reason?: string): Promise<Guild>; + public splashURL(options?: ImageURLOptions): string | null; + public toJSON(): object; + public toString(): string; + } + + export class GuildAuditLogs { + constructor(guild: Guild, data: object); + private webhooks: Collection<Snowflake, Webhook>; + private integrations: Collection<Snowflake, Integration>; + + public entries: Collection<Snowflake, GuildAuditLogsEntry>; + + public static Actions: GuildAuditLogsActions; + public static Targets: GuildAuditLogsTargets; + public static Entry: typeof GuildAuditLogsEntry; + public static actionType(action: number): GuildAuditLogsActionType; + public static build(...args: any[]): Promise<GuildAuditLogs>; + public static targetType(target: number): GuildAuditLogsTarget; + public toJSON(): object; + } + + class GuildAuditLogsEntry { + constructor(logs: GuildAuditLogs, guild: Guild, data: object); + public action: GuildAuditLogsAction; + public actionType: GuildAuditLogsActionType; + public changes: AuditLogChange[] | null; + public readonly createdAt: Date; + public readonly createdTimestamp: number; + public executor: User; + public extra: object | Role | GuildMember | null; + public id: Snowflake; + public reason: string | null; + public target: Guild | User | Role | GuildEmoji | Invite | Webhook | Integration | null; + public targetType: GuildAuditLogsTarget; + public toJSON(): object; + } + + export class GuildChannel extends Channel { + constructor(guild: Guild, data?: object); + private memberPermissions(member: GuildMember): Readonly<Permissions>; + private rolePermissions(role: Role): Readonly<Permissions>; + + public readonly calculatedPosition: number; + public readonly deletable: boolean; + public guild: Guild; + public readonly manageable: boolean; + public readonly members: Collection<Snowflake, GuildMember>; + public name: string; + public readonly parent: CategoryChannel | null; + public parentID: Snowflake | null; + public permissionOverwrites: Collection<Snowflake, PermissionOverwrites>; + public readonly permissionsLocked: boolean | null; + public readonly position: number; + public rawPosition: number; + public type: Exclude<keyof typeof ChannelType, 'dm' | 'group' | 'unknown'>; + public readonly viewable: boolean; + public clone(options?: GuildChannelCloneOptions): Promise<this>; + public createInvite(options?: InviteOptions): Promise<Invite>; + public createOverwrite( + userOrRole: RoleResolvable | UserResolvable, + options: PermissionOverwriteOption, + reason?: string, + ): Promise<this>; + public edit(data: ChannelData, reason?: string): Promise<this>; + public equals(channel: GuildChannel): boolean; + public fetchInvites(): Promise<Collection<string, Invite>>; + public lockPermissions(): Promise<this>; + public overwritePermissions( + overwrites: OverwriteResolvable[] | Collection<Snowflake, OverwriteResolvable>, + reason?: string, + ): Promise<this>; + public permissionsFor(memberOrRole: GuildMemberResolvable | RoleResolvable): Readonly<Permissions> | null; + public setName(name: string, reason?: string): Promise<this>; + public setParent( + channel: CategoryChannel | Snowflake, + options?: { lockPermissions?: boolean; reason?: string }, + ): Promise<this>; + public setPosition(position: number, options?: { relative?: boolean; reason?: string }): Promise<this>; + public setTopic(topic: string, reason?: string): Promise<this>; + public updateOverwrite( + userOrRole: RoleResolvable | UserResolvable, + options: PermissionOverwriteOption, + reason?: string, + ): Promise<this>; + } + + export class GuildEmoji extends BaseGuildEmoji { + public readonly deletable: boolean; + public guild: Guild; + public readonly roles: GuildEmojiRoleManager; + public readonly url: string; + public delete(reason?: string): Promise<GuildEmoji>; + public edit(data: GuildEmojiEditData, reason?: string): Promise<GuildEmoji>; + public equals(other: GuildEmoji | object): boolean; + public fetchAuthor(): Promise<User>; + public setName(name: string, reason?: string): Promise<GuildEmoji>; + } + + export class GuildMember extends PartialTextBasedChannel(Base) { + constructor(client: Client, data: object, guild: Guild); + public readonly bannable: boolean; + public deleted: boolean; + public readonly displayColor: number; + public readonly displayHexColor: string; + public readonly displayName: string; + public guild: Guild; + public readonly id: Snowflake; + public readonly joinedAt: Date | null; + public joinedTimestamp: number | null; + public readonly kickable: boolean; + public lastMessageChannelID: Snowflake | null; + public readonly manageable: boolean; + public nickname: string | null; + public readonly partial: false; + public readonly permissions: Readonly<Permissions>; + public readonly premiumSince: Date | null; + public premiumSinceTimestamp: number | null; + public readonly presence: Presence; + public readonly roles: GuildMemberRoleManager; + public user: User; + public readonly voice: VoiceState; + public ban(options?: BanOptions): Promise<GuildMember>; + public fetch(): Promise<GuildMember>; + public createDM(): Promise<DMChannel>; + public deleteDM(): Promise<DMChannel>; + public edit(data: GuildMemberEditData, reason?: string): Promise<GuildMember>; + public hasPermission( + permission: PermissionResolvable, + options?: { checkAdmin?: boolean; checkOwner?: boolean }, + ): boolean; + public kick(reason?: string): Promise<GuildMember>; + public permissionsIn(channel: ChannelResolvable): Readonly<Permissions>; + public setNickname(nickname: string, reason?: string): Promise<GuildMember>; + public toJSON(): object; + public toString(): string; + public valueOf(): string; + } + + export class GuildPreview extends Base { + constructor(client: Client, data: object); + public approximateMemberCount: number; + public approximatePresenceCount: number; + public description?: string; + public discoverySplash: string | null; + public emojis: Collection<Snowflake, GuildPreviewEmoji>; + public features: GuildFeatures[]; + public icon: string | null; + public id: string; + public name: string; + public splash: string | null; + public discoverySplashURL(options?: ImageURLOptions): string | null; + public iconURL(options?: ImageURLOptions & { dynamic?: boolean }): string | null; + public splashURL(options?: ImageURLOptions): string | null; + public fetch(): Promise<GuildPreview>; + public toJSON(): object; + public toString(): string; + } + + export class GuildPreviewEmoji extends BaseGuildEmoji { + constructor(client: Client, data: object, guild: GuildPreview); + public guild: GuildPreview; + public readonly roles: Set<Snowflake>; + } + + export class HTTPError extends Error { + constructor(message: string, name: string, code: number, method: string, path: string); + public code: number; + public method: string; + public name: string; + public path: string; + } + + export class Integration extends Base { + constructor(client: Client, data: object, guild: Guild); + public account: IntegrationAccount; + public enabled: boolean; + public expireBehavior: number; + public expireGracePeriod: number; + public guild: Guild; + public id: Snowflake; + public name: string; + public role: Role; + public syncedAt: number; + public syncing: boolean; + public type: string; + public user: User; + public delete(reason?: string): Promise<Integration>; + public edit(data: IntegrationEditData, reason?: string): Promise<Integration>; + public sync(): Promise<Integration>; + } + + export class Intents extends BitField<IntentsString> { + public static FLAGS: Record<IntentsString, number>; + public static PRIVILEGED: number; + public static ALL: number; + public static NON_PRIVILEGED: number; + public static resolve(bit?: BitFieldResolvable<IntentsString>): number; + } + + export class Invite extends Base { + constructor(client: Client, data: object); + public channel: GuildChannel | PartialGroupDMChannel; + public code: string; + public readonly deletable: boolean; + public readonly createdAt: Date | null; + public createdTimestamp: number | null; + public readonly expiresAt: Date | null; + public readonly expiresTimestamp: number | null; + public guild: Guild | null; + public inviter: User | null; + public maxAge: number | null; + public maxUses: number | null; + public memberCount: number; + public presenceCount: number; + public targetUser: User | null; + public targetUserType: TargetUser | null; + public temporary: boolean | null; + public readonly url: string; + public uses: number | null; + public delete(reason?: string): Promise<Invite>; + public toJSON(): object; + public toString(): string; + } + + export class Message extends Base { + constructor(client: Client, data: object, channel: TextChannel | DMChannel); + private _edits: Message[]; + private patch(data: object): void; + + public activity: MessageActivity | null; + public application: ClientApplication | null; + public attachments: Collection<Snowflake, MessageAttachment>; + public author: User; + public channel: TextChannel | DMChannel | NewsChannel; + public readonly cleanContent: string; + public content: string; + public readonly createdAt: Date; + public createdTimestamp: number; + public readonly deletable: boolean; + public deleted: boolean; + public readonly editable: boolean; + public readonly editedAt: Date | null; + public editedTimestamp: number | null; + public readonly edits: Message[]; + public embeds: MessageEmbed[]; + public readonly guild: Guild | null; + public id: Snowflake; + public readonly member: GuildMember | null; + public mentions: MessageMentions; + public nonce: string | null; + public readonly partial: false; + public readonly pinnable: boolean; + public pinned: boolean; + public reactions: ReactionManager; + public system: boolean; + public tts: boolean; + public type: MessageType; + public readonly url: string; + public webhookID: Snowflake | null; + public flags: Readonly<MessageFlags>; + public reference: MessageReference | null; + public awaitReactions( + filter: CollectorFilter, + options?: AwaitReactionsOptions, + ): Promise<Collection<Snowflake, MessageReaction>>; + public createReactionCollector(filter: CollectorFilter, options?: ReactionCollectorOptions): ReactionCollector; + public delete(options?: { timeout?: number; reason?: string }): Promise<Message>; + public edit(content: StringResolvable, options?: MessageEditOptions | MessageEmbed): Promise<Message>; + public edit(options: MessageEditOptions | MessageEmbed | APIMessage): Promise<Message>; + public equals(message: Message, rawData: object): boolean; + public fetchWebhook(): Promise<Webhook>; + public fetch(): Promise<Message>; + public pin(): Promise<Message>; + public react(emoji: EmojiIdentifierResolvable): Promise<MessageReaction>; + public reply( + content?: StringResolvable, + options?: MessageOptions | MessageAdditions | (MessageOptions & { split?: false }) | MessageAdditions, + ): Promise<Message>; + public reply( + content?: StringResolvable, + options?: (MessageOptions & { split: true | SplitOptions }) | MessageAdditions, + ): Promise<Message[]>; + public reply( + options?: + | MessageOptions + | MessageAdditions + | APIMessage + | (MessageOptions & { split?: false }) + | MessageAdditions + | APIMessage, + ): Promise<Message>; + public reply( + options?: (MessageOptions & { split: true | SplitOptions }) | MessageAdditions | APIMessage, + ): Promise<Message[]>; + public suppressEmbeds(suppress?: boolean): Promise<Message>; + public toJSON(): object; + public toString(): string; + public unpin(): Promise<Message>; + } + + export class MessageAttachment { + constructor(attachment: BufferResolvable | Stream, name?: string, data?: object); + + public attachment: BufferResolvable | Stream; + public height: number | null; + public id: Snowflake; + public name?: string; + public proxyURL: string; + public size: number; + public readonly spoiler: boolean; + public url: string; + public width: number | null; + public setFile(attachment: BufferResolvable | Stream, name?: string): this; + public setName(name: string): this; + public toJSON(): object; + } + + export class MessageCollector extends Collector<Snowflake, Message> { + constructor(channel: TextChannel | DMChannel, filter: CollectorFilter, options?: MessageCollectorOptions); + private _handleChannelDeletion(channel: GuildChannel): void; + private _handleGuildDeletion(guild: Guild): void; + + public channel: Channel; + public options: MessageCollectorOptions; + public received: number; + + public collect(message: Message): Snowflake; + public dispose(message: Message): Snowflake; + public endReason(): string; + } + + export class MessageEmbed { + constructor(data?: MessageEmbed | MessageEmbedOptions); + public author: MessageEmbedAuthor | null; + public color?: number; + public readonly createdAt: Date | null; + public description?: string; + public fields: EmbedField[]; + public files: (MessageAttachment | string | FileOptions)[]; + public footer: MessageEmbedFooter | null; + public readonly hexColor: string | null; + public image: MessageEmbedImage | null; + public readonly length: number; + public provider: MessageEmbedProvider | null; + public thumbnail: MessageEmbedThumbnail | null; + public timestamp: number | null; + public title?: string; + public type: string; + public url?: string; + public readonly video: MessageEmbedVideo | null; + public addField(name: StringResolvable, value: StringResolvable, inline?: boolean): this; + public addFields(...fields: EmbedFieldData[] | EmbedFieldData[][]): this; + public attachFiles(file: (MessageAttachment | FileOptions | string)[]): this; + public setAuthor(name: StringResolvable, iconURL?: string, url?: string): this; + public setColor(color: ColorResolvable): this; + public setDescription(description: StringResolvable): this; + public setFooter(text: StringResolvable, iconURL?: string): this; + public setImage(url: string): this; + public setThumbnail(url: string): this; + public setTimestamp(timestamp?: Date | number): this; + public setTitle(title: StringResolvable): this; + public setURL(url: string): this; + public spliceFields(index: number, deleteCount: number, ...fields: EmbedFieldData[] | EmbedFieldData[][]): this; + public toJSON(): object; + + public static normalizeField( + name: StringResolvable, + value: StringResolvable, + inline?: boolean, + ): Required<EmbedFieldData>; + public static normalizeFields(...fields: EmbedFieldData[] | EmbedFieldData[][]): Required<EmbedFieldData>[]; + } + + export class MessageFlags extends BitField<MessageFlagsString> { + public static FLAGS: Record<MessageFlagsString, number>; + public static resolve(bit?: BitFieldResolvable<MessageFlagsString>): number; + } + + export class MessageMentions { + constructor( + message: Message, + users: object[] | Collection<Snowflake, User>, + roles: Snowflake[] | Collection<Snowflake, Role>, + everyone: boolean, + ); + private _channels: Collection<Snowflake, GuildChannel> | null; + private readonly _content: Message; + private _members: Collection<Snowflake, GuildMember> | null; + + public readonly channels: Collection<Snowflake, TextChannel>; + public readonly client: Client; + public everyone: boolean; + public readonly guild: Guild; + public has( + data: User | GuildMember | Role | GuildChannel, + options?: { + ignoreDirect?: boolean; + ignoreRoles?: boolean; + ignoreEveryone?: boolean; + }, + ): boolean; + public readonly members: Collection<Snowflake, GuildMember> | null; + public roles: Collection<Snowflake, Role>; + public users: Collection<Snowflake, User>; + public crosspostedChannels: Collection<Snowflake, CrosspostedChannel>; + public toJSON(): object; + + public static CHANNELS_PATTERN: RegExp; + public static EVERYONE_PATTERN: RegExp; + public static ROLES_PATTERN: RegExp; + public static USERS_PATTERN: RegExp; + } + + export class MessageReaction { + constructor(client: Client, data: object, message: Message); + private _emoji: GuildEmoji | ReactionEmoji; + + public count: number | null; + public readonly emoji: GuildEmoji | ReactionEmoji; + public me: boolean; + public message: Message; + public readonly partial: boolean; + public users: ReactionUserManager; + public remove(): Promise<MessageReaction>; + public fetch(): Promise<MessageReaction>; + public toJSON(): object; + } + + export class NewsChannel extends TextBasedChannel(GuildChannel) { + constructor(guild: Guild, data?: object); + public messages: MessageManager; + public nsfw: boolean; + public topic: string | null; + public type: 'news'; + public createWebhook( + name: string, + options?: { avatar?: BufferResolvable | Base64Resolvable; reason?: string }, + ): Promise<Webhook>; + public setNSFW(nsfw: boolean, reason?: string): Promise<NewsChannel>; + public fetchWebhooks(): Promise<Collection<Snowflake, Webhook>>; + } + + export class PartialGroupDMChannel extends Channel { + constructor(client: Client, data: object); + public name: string; + public icon: string | null; + public iconURL(options?: ImageURLOptions): string | null; + } + + export class PermissionOverwrites { + constructor(guildChannel: GuildChannel, data?: object); + public allow: Readonly<Permissions>; + public readonly channel: GuildChannel; + public deny: Readonly<Permissions>; + public id: Snowflake; + public type: OverwriteType; + public update(options: PermissionOverwriteOption, reason?: string): Promise<PermissionOverwrites>; + public delete(reason?: string): Promise<PermissionOverwrites>; + public toJSON(): object; + public static resolveOverwriteOptions( + options: ResolvedOverwriteOptions, + initialPermissions: { allow?: PermissionResolvable; deny?: PermissionResolvable }, + ): ResolvedOverwriteOptions; + public static resolve(overwrite: OverwriteResolvable, guild: Guild): RawOverwriteData; + } + + export class Permissions extends BitField<PermissionString> { + public any(permission: PermissionResolvable, checkAdmin?: boolean): boolean; + public has(permission: PermissionResolvable, checkAdmin?: boolean): boolean; + public missing(bits: BitFieldResolvable<PermissionString>, checkAdmin?: boolean): PermissionString[]; + public serialize(checkAdmin?: boolean): Record<PermissionString, boolean>; + public toArray(checkAdmin?: boolean): PermissionString[]; + + public static ALL: number; + public static DEFAULT: number; + public static FLAGS: PermissionFlags; + public static resolve(permission?: PermissionResolvable): number; + } + + export class Presence { + constructor(client: Client, data?: object); + public activities: Activity[]; + public clientStatus: ClientPresenceStatusData | null; + public flags: Readonly<ActivityFlags>; + public guild: Guild | null; + public readonly member: GuildMember | null; + public status: PresenceStatus; + public readonly user: User | null; + public userID: Snowflake; + public equals(presence: Presence): boolean; + } + + export class ReactionCollector extends Collector<Snowflake, MessageReaction> { + constructor(message: Message, filter: CollectorFilter, options?: ReactionCollectorOptions); + private _handleChannelDeletion(channel: GuildChannel): void; + private _handleGuildDeletion(guild: Guild): void; + private _handleMessageDeletion(message: Message): void; + + public message: Message; + public options: ReactionCollectorOptions; + public total: number; + public users: Collection<Snowflake, User>; + + public static key(reaction: MessageReaction): Snowflake | string; + + public collect(reaction: MessageReaction): Snowflake | string; + public dispose(reaction: MessageReaction, user: User): Snowflake | string; + public empty(): void; + public endReason(): string | null; + + public on(event: 'collect' | 'dispose' | 'remove', listener: (reaction: MessageReaction, user: User) => void): this; + public on( + event: 'end', + listener: (collected: Collection<Snowflake, MessageReaction>, reason: string) => void, + ): this; + public on(event: string, listener: (...args: any[]) => void): this; + + public once( + event: 'collect' | 'dispose' | 'remove', + listener: (reaction: MessageReaction, user: User) => void, + ): this; + public once( + event: 'end', + listener: (collected: Collection<Snowflake, MessageReaction>, reason: string) => void, + ): this; + public once(event: string, listener: (...args: any[]) => void): this; + } + + export class ReactionEmoji extends Emoji { + constructor(reaction: MessageReaction, emoji: object); + public reaction: MessageReaction; + public toJSON(): object; + } + + export class RichPresenceAssets { + constructor(activity: Activity, assets: object); + public largeImage: Snowflake | null; + public largeText: string | null; + public smallImage: Snowflake | null; + public smallText: string | null; + public largeImageURL(options?: ImageURLOptions): string | null; + public smallImageURL(options?: ImageURLOptions): string | null; + } + + export class Role extends Base { + constructor(client: Client, data: object, guild: Guild); + public color: number; + public readonly createdAt: Date; + public readonly createdTimestamp: number; + public deleted: boolean; + public readonly editable: boolean; + public guild: Guild; + public readonly hexColor: string; + public hoist: boolean; + public id: Snowflake; + public managed: boolean; + public readonly members: Collection<Snowflake, GuildMember>; + public mentionable: boolean; + public name: string; + public permissions: Readonly<Permissions>; + public readonly position: number; + public rawPosition: number; + public comparePositionTo(role: Role): number; + public delete(reason?: string): Promise<Role>; + public edit(data: RoleData, reason?: string): Promise<Role>; + public equals(role: Role): boolean; + public permissionsIn(channel: ChannelResolvable): Readonly<Permissions>; + public setColor(color: ColorResolvable, reason?: string): Promise<Role>; + public setHoist(hoist: boolean, reason?: string): Promise<Role>; + public setMentionable(mentionable: boolean, reason?: string): Promise<Role>; + public setName(name: string, reason?: string): Promise<Role>; + public setPermissions(permissions: PermissionResolvable, reason?: string): Promise<Role>; + public setPosition(position: number, options?: { relative?: boolean; reason?: string }): Promise<Role>; + public toJSON(): object; + public toString(): string; + + public static comparePositions(role1: Role, role2: Role): number; + } + + export class Shard extends EventEmitter { + constructor(manager: ShardingManager, id: number); + private _evals: Map<string, Promise<any>>; + private _exitListener: (...args: any[]) => void; + private _fetches: Map<string, Promise<any>>; + private _handleExit(respawn?: boolean): void; + private _handleMessage(message: any): void; + + public args: string[]; + public execArgv: string[]; + public env: object; + public id: number; + public manager: ShardingManager; + public process: ChildProcess | null; + public ready: boolean; + public worker: any | null; + public eval(script: string): Promise<any>; + public eval<T>(fn: (client: Client) => T): Promise<T[]>; + public fetchClientValue(prop: string): Promise<any>; + public kill(): void; + public respawn(delay?: number, spawnTimeout?: number): Promise<ChildProcess>; + public send(message: any): Promise<Shard>; + public spawn(spawnTimeout?: number): Promise<ChildProcess>; + + public on(event: 'spawn' | 'death', listener: (child: ChildProcess) => void): this; + public on(event: 'disconnect' | 'ready' | 'reconnecting', listener: () => void): this; + public on(event: 'error', listener: (error: Error) => void): this; + public on(event: 'message', listener: (message: any) => void): this; + public on(event: string, listener: (...args: any[]) => void): this; + + public once(event: 'spawn' | 'death', listener: (child: ChildProcess) => void): this; + public once(event: 'disconnect' | 'ready' | 'reconnecting', listener: () => void): this; + public once(event: 'error', listener: (error: Error) => void): this; + public once(event: 'message', listener: (message: any) => void): this; + public once(event: string, listener: (...args: any[]) => void): this; + } + + export class ShardClientUtil { + constructor(client: Client, mode: ShardingManagerMode); + private _handleMessage(message: any): void; + private _respond(type: string, message: any): void; + + public client: Client; + public readonly count: number; + public readonly ids: number[]; + public mode: ShardingManagerMode; + public parentPort: any | null; + public broadcastEval(script: string): Promise<any[]>; + public broadcastEval<T>(fn: (client: Client) => T): Promise<T[]>; + public fetchClientValues(prop: string): Promise<any[]>; + public respawnAll(shardDelay?: number, respawnDelay?: number, spawnTimeout?: number): Promise<void>; + public send(message: any): Promise<void>; + + public static singleton(client: Client, mode: ShardingManagerMode): ShardClientUtil; + } + + export class ShardingManager extends EventEmitter { + constructor( + file: string, + options?: { + totalShards?: number | 'auto'; + shardList?: number[] | 'auto'; + mode?: ShardingManagerMode; + respawn?: boolean; + shardArgs?: string[]; + token?: string; + execArgv?: string[]; + }, + ); + + public file: string; + public respawn: boolean; + public shardArgs: string[]; + public shards: Collection<number, Shard>; + public token: string | null; + public totalShards: number | 'auto'; + public broadcast(message: any): Promise<Shard[]>; + public broadcastEval(script: string): Promise<any[]>; + public createShard(id: number): Shard; + public fetchClientValues(prop: string): Promise<any[]>; + public respawnAll( + shardDelay?: number, + respawnDelay?: number, + spawnTimeout?: number, + ): Promise<Collection<number, Shard>>; + public spawn(amount?: number | 'auto', delay?: number, spawnTimeout?: number): Promise<Collection<number, Shard>>; + + public on(event: 'shardCreate', listener: (shard: Shard) => void): this; + + public once(event: 'shardCreate', listener: (shard: Shard) => void): this; + } + + export class SnowflakeUtil { + public static deconstruct(snowflake: Snowflake): DeconstructedSnowflake; + public static generate(timestamp?: number | Date): Snowflake; + } + + export class Speaking extends BitField<SpeakingString> { + public static FLAGS: Record<SpeakingString, number>; + public static resolve(bit?: BitFieldResolvable<SpeakingString>): number; + } + + export class StoreChannel extends GuildChannel { + constructor(guild: Guild, data?: object); + public nsfw: boolean; + public type: 'store'; + } + + class StreamDispatcher extends VolumeMixin(Writable) { + constructor(player: object, options?: StreamOptions, streams?: object); + public readonly bitrateEditable: boolean; + public broadcast: VoiceBroadcast | null; + public readonly paused: boolean; + public pausedSince: number | null; + public readonly pausedTime: number; + public player: object; + public readonly streamTime: number; + public readonly totalStreamTime: number; + + public pause(silence?: boolean): void; + public resume(): void; + public setBitrate(value: number | 'auto'): boolean; + public setFEC(enabled: boolean): boolean; + public setPLP(value: number): boolean; + + public on(event: 'close' | 'drain' | 'finish' | 'start', listener: () => void): this; + public on(event: 'debug', listener: (info: string) => void): this; + public on(event: 'error', listener: (err: Error) => void): this; + public on(event: 'pipe' | 'unpipe', listener: (src: Readable) => void): this; + public on(event: 'speaking', listener: (speaking: boolean) => void): this; + public on(event: 'volumeChange', listener: (oldVolume: number, newVolume: number) => void): this; + public on(event: string, listener: (...args: any[]) => void): this; + + public once(event: 'close' | 'drain' | 'finish' | 'start', listener: () => void): this; + public once(event: 'debug', listener: (info: string) => void): this; + public once(event: 'error', listener: (err: Error) => void): this; + public once(event: 'pipe' | 'unpipe', listener: (src: Readable) => void): this; + public once(event: 'speaking', listener: (speaking: boolean) => void): this; + public once(event: 'volumeChange', listener: (oldVolume: number, newVolume: number) => void): this; + public once(event: string, listener: (...args: any[]) => void): this; + } + + export class Structures { + public static get<K extends keyof Extendable>(structure: K): Extendable[K]; + public static get(structure: string): (...args: any[]) => void; + public static extend<K extends keyof Extendable, T extends Extendable[K]>( + structure: K, + extender: (baseClass: Extendable[K]) => T, + ): T; + public static extend<T extends (...args: any[]) => void>( + structure: string, + extender: (baseClass: typeof Function) => T, + ): T; + } + + export class SystemChannelFlags extends BitField<SystemChannelFlagsString> { + public static FLAGS: Record<SystemChannelFlagsString, number>; + public static resolve(bit?: BitFieldResolvable<SystemChannelFlagsString>): number; + } + + export class Team extends Base { + constructor(client: Client, data: object); + public id: Snowflake; + public name: string; + public icon: string | null; + public ownerID: Snowflake | null; + public members: Collection<Snowflake, TeamMember>; + + public readonly owner: TeamMember; + public readonly createdAt: Date; + public readonly createdTimestamp: number; + + public iconURL(options?: ImageURLOptions): string; + public toJSON(): object; + public toString(): string; + } + + export class TeamMember extends Base { + constructor(team: Team, data: object); + public team: Team; + public readonly id: Snowflake; + public permissions: string[]; + public membershipState: MembershipStates; + public user: User; + + public toString(): string; + } + + export class TextChannel extends TextBasedChannel(GuildChannel) { + constructor(guild: Guild, data?: object); + public messages: MessageManager; + public nsfw: boolean; + public type: 'text'; + public rateLimitPerUser: number; + public topic: string | null; + public createWebhook( + name: string, + options?: { avatar?: BufferResolvable | Base64Resolvable; reason?: string }, + ): Promise<Webhook>; + public setNSFW(nsfw: boolean, reason?: string): Promise<TextChannel>; + public setRateLimitPerUser(rateLimitPerUser: number, reason?: string): Promise<TextChannel>; + public fetchWebhooks(): Promise<Collection<Snowflake, Webhook>>; + } + + export class User extends PartialTextBasedChannel(Base) { + constructor(client: Client, data: object); + public avatar: string | null; + public bot: boolean; + public readonly createdAt: Date; + public readonly createdTimestamp: number; + public discriminator: string; + public readonly defaultAvatarURL: string; + public readonly dmChannel: DMChannel; + public flags: Readonly<UserFlags>; + public id: Snowflake; + public lastMessageID: Snowflake | null; + public locale: string; + public readonly partial: false; + public readonly presence: Presence; + public system?: boolean; + public readonly tag: string; + public username: string; + public avatarURL(options?: ImageURLOptions & { dynamic?: boolean }): string | null; + public createDM(): Promise<DMChannel>; + public deleteDM(): Promise<DMChannel>; + public displayAvatarURL(options?: ImageURLOptions & { dynamic?: boolean }): string; + public equals(user: User): boolean; + public fetch(): Promise<User>; + public toString(): string; + public typingDurationIn(channel: ChannelResolvable): number; + public typingIn(channel: ChannelResolvable): boolean; + public typingSinceIn(channel: ChannelResolvable): Date; + } + + export class UserFlags extends BitField<UserFlagsString> { + public static FLAGS: Record<UserFlagsString, number>; + public static resolve(bit?: BitFieldResolvable<UserFlagsString>): number; + } + + export class Util { + public static basename(path: string, ext?: string): string; + public static binaryToID(num: string): Snowflake; + public static cleanContent(str: string, message: Message): string; + public static removeMentions(str: string): string; + public static cloneObject(obj: object): object; + public static convertToBuffer(ab: ArrayBuffer | string): Buffer; + public static delayFor(ms: number): Promise<void>; + public static discordSort<K, V extends { rawPosition: number; id: string }>( + collection: Collection<K, V>, + ): Collection<K, V>; + public static escapeMarkdown(text: string, options?: EscapeMarkdownOptions): string; + public static escapeCodeBlock(text: string): string; + public static escapeInlineCode(text: string): string; + public static escapeBold(text: string): string; + public static escapeItalic(text: string): string; + public static escapeUnderline(text: string): string; + public static escapeStrikethrough(text: string): string; + public static escapeSpoiler(text: string): string; + public static cleanCodeBlockContent(text: string): string; + public static fetchRecommendedShards(token: string, guildsPerShard?: number): Promise<number>; + public static flatten(obj: object, ...props: { [key: string]: boolean | string }[]): object; + public static idToBinary(num: Snowflake): string; + public static makeError(obj: { name: string; message: string; stack: string }): Error; + public static makePlainError(err: Error): { name: string; message: string; stack: string }; + public static mergeDefault(def: object, given: object): object; + public static moveElementInArray(array: any[], element: any, newIndex: number, offset?: boolean): number; + public static parseEmoji(text: string): { animated: boolean; name: string; id: string | null } | null; + public static resolveColor(color: ColorResolvable): number; + public static resolveString(data: StringResolvable): string; + public static setPosition<T extends Channel | Role>( + item: T, + position: number, + relative: boolean, + sorted: Collection<Snowflake, T>, + route: object, + reason?: string, + ): Promise<{ id: Snowflake; position: number }[]>; + public static splitMessage(text: StringResolvable, options?: SplitOptions): string[]; + public static str2ab(str: string): ArrayBuffer; + } + + class VoiceBroadcast extends EventEmitter { + constructor(client: Client); + public client: Client; + public subscribers: StreamDispatcher[]; + public readonly dispatcher?: BroadcastDispatcher; + public play(input: string | Readable, options?: StreamOptions): BroadcastDispatcher; + public end(): void; + + public on(event: 'end', listener: () => void): this; + public on(event: 'subscribe' | 'unsubscribe', listener: (dispatcher: StreamDispatcher) => void): this; + public on(event: string, listener: (...args: any[]) => void): this; + + public once(event: 'end', listener: () => void): this; + public once(event: 'subscribe' | 'unsubscribe', listener: (dispatcher: StreamDispatcher) => void): this; + public once(event: string, listener: (...args: any[]) => void): this; + } + + export class VoiceChannel extends GuildChannel { + constructor(guild: Guild, data?: object); + public bitrate: number; + public readonly editable: boolean; + public readonly full: boolean; + public readonly joinable: boolean; + public readonly speakable: boolean; + public type: 'voice'; + public userLimit: number; + public join(): Promise<VoiceConnection>; + public leave(): void; + public setBitrate(bitrate: number, reason?: string): Promise<VoiceChannel>; + public setUserLimit(userLimit: number, reason?: string): Promise<VoiceChannel>; + } + + class VoiceConnection extends EventEmitter { + constructor(voiceManager: ClientVoiceManager, channel: VoiceChannel); + private authentication: object; + private sockets: object; + private ssrcMap: Map<number, boolean>; + private _speaking: Map<Snowflake, Readonly<Speaking>>; + private _disconnect(): void; + private authenticate(): void; + private authenticateFailed(reason: string): void; + private checkAuthenticated(): void; + private cleanup(): void; + private connect(): void; + private onReady(data: object): void; + private onSessionDescription(mode: string, secret: string): void; + private onSpeaking(data: object): void; + private reconnect(token: string, endpoint: string): void; + private sendVoiceStateUpdate(options: object): Promise<Shard>; + private setSessionID(sessionID: string): void; + private setSpeaking(value: BitFieldResolvable<SpeakingString>): void; + private setTokenAndEndpoint(token: string, endpoint: string): void; + private updateChannel(channel: VoiceChannel): void; + + public channel: VoiceChannel; + public readonly client: Client; + public readonly dispatcher: StreamDispatcher; + public player: object; + public receiver: VoiceReceiver; + public speaking: Readonly<Speaking>; + public status: VoiceStatus; + public readonly voice: VoiceState; + public voiceManager: ClientVoiceManager; + public disconnect(): void; + public play(input: VoiceBroadcast | Readable | string, options?: StreamOptions): StreamDispatcher; + + public on(event: 'authenticated' | 'closing' | 'newSession' | 'ready' | 'reconnecting', listener: () => void): this; + public on(event: 'debug', listener: (message: string) => void): this; + public on(event: 'error' | 'failed' | 'disconnect', listener: (error: Error) => void): this; + public on(event: 'speaking', listener: (user: User, speaking: Readonly<Speaking>) => void): this; + public on(event: 'warn', listener: (warning: string | Error) => void): this; + public on(event: string, listener: (...args: any[]) => void): this; + + public once( + event: 'authenticated' | 'closing' | 'newSession' | 'ready' | 'reconnecting', + listener: () => void, + ): this; + public once(event: 'debug', listener: (message: string) => void): this; + public once(event: 'error' | 'failed' | 'disconnect', listener: (error: Error) => void): this; + public once(event: 'speaking', listener: (user: User, speaking: Readonly<Speaking>) => void): this; + public once(event: 'warn', listener: (warning: string | Error) => void): this; + public once(event: string, listener: (...args: any[]) => void): this; + } + + class VoiceReceiver extends EventEmitter { + constructor(connection: VoiceConnection); + public createStream( + user: UserResolvable, + options?: { mode?: 'opus' | 'pcm'; end?: 'silence' | 'manual' }, + ): Readable; + + public on(event: 'debug', listener: (error: Error | string) => void): this; + public on(event: string, listener: (...args: any[]) => void): this; + + public once(event: 'debug', listener: (error: Error | string) => void): this; + public once(event: string, listener: (...args: any[]) => void): this; + } + + export class VoiceRegion { + constructor(data: object); + public custom: boolean; + public deprecated: boolean; + public id: string; + public name: string; + public optimal: boolean; + public vip: boolean; + public toJSON(): object; + } + + export class VoiceState extends Base { + constructor(guild: Guild, data: object); + public readonly channel: VoiceChannel | null; + public channelID?: Snowflake; + public readonly connection: VoiceConnection | null; + public readonly deaf?: boolean; + public guild: Guild; + public id: Snowflake; + public readonly member: GuildMember | null; + public readonly mute?: boolean; + public selfDeaf?: boolean; + public selfMute?: boolean; + public serverDeaf?: boolean; + public serverMute?: boolean; + public sessionID?: string; + public streaming: boolean; + public readonly speaking: boolean | null; + + public setDeaf(deaf: boolean, reason?: string): Promise<GuildMember>; + public setMute(mute: boolean, reason?: string): Promise<GuildMember>; + public kick(reason?: string): Promise<GuildMember>; + public setChannel(channel: ChannelResolvable | null, reason?: string): Promise<GuildMember>; + public setSelfDeaf(deaf: boolean): Promise<boolean>; + public setSelfMute(mute: boolean): Promise<boolean>; + } + + class VolumeInterface extends EventEmitter { + constructor(options?: { volume?: number }); + public readonly volume: number; + public readonly volumeDecibels: number; + public readonly volumeEditable: boolean; + public readonly volumeLogarithmic: number; + public setVolume(volume: number): void; + public setVolumeDecibels(db: number): void; + public setVolumeLogarithmic(value: number): void; + + public on(event: 'volumeChange', listener: (oldVolume: number, newVolume: number) => void): this; + + public once(event: 'volumeChange', listener: (oldVolume: number, newVolume: number) => void): this; + } + + export class Webhook extends WebhookMixin() { + constructor(client: Client, data?: object); + public avatar: string; + public avatarURL(options?: ImageURLOptions): string | null; + public channelID: Snowflake; + public client: Client; + public guildID: Snowflake; + public name: string; + public owner: User | object | null; + public token: string | null; + public type: WebhookTypes; + } + + export class WebhookClient extends WebhookMixin(BaseClient) { + constructor(id: string, token: string, options?: ClientOptions); + public client: this; + public token: string; + } + + export class WebSocketManager extends EventEmitter { + constructor(client: Client); + private totalShards: number | string; + private shardQueue: Set<WebSocketShard>; + private packetQueue: object[]; + private destroyed: boolean; + private reconnecting: boolean; + private sessionStartLimit?: { total: number; remaining: number; reset_after: number }; + + public readonly client: Client; + public gateway?: string; + public shards: Collection<number, WebSocketShard>; + public status: Status; + public readonly ping: number; + + public on(event: WSEventType, listener: (data: any, shardID: number) => void): this; + public once(event: WSEventType, listener: (data: any, shardID: number) => void): this; + + private debug(message: string, shard?: WebSocketShard): void; + private connect(): Promise<void>; + private createShards(): Promise<void>; + private reconnect(): Promise<void>; + private broadcast(packet: object): void; + private destroy(): void; + private _handleSessionLimit(remaining?: number, resetAfter?: number): Promise<void>; + private handlePacket(packet?: object, shard?: WebSocketShard): boolean; + private checkShardsReady(): Promise<void>; + private triggerClientReady(): void; + } + + export class WebSocketShard extends EventEmitter { + constructor(manager: WebSocketManager, id: number); + private sequence: number; + private closeSequence: number; + private sessionID?: string; + private lastPingTimestamp: number; + private lastHeartbeatAcked: boolean; + private ratelimit: { queue: object[]; total: number; remaining: number; time: 60e3; timer: NodeJS.Timeout | null }; + private connection: WebSocket | null; + private helloTimeout: NodeJS.Timeout | undefined; + private eventsAttached: boolean; + private expectedGuilds: Set<Snowflake> | undefined; + private readyTimeout: NodeJS.Timeout | undefined; + + public manager: WebSocketManager; + public id: number; + public status: Status; + public ping: number; + + private debug(message: string): void; + private connect(): Promise<void>; + private onOpen(): void; + private onMessage(event: MessageEvent): void; + private onError(error: ErrorEvent | object): void; + private onClose(event: CloseEvent): void; + private onPacket(packet: object): void; + private checkReady(): void; + private setHelloTimeout(time?: number): void; + private setHeartbeatTimer(time: number): void; + private sendHeartbeat(): void; + private ackHeartbeat(): void; + private identify(): void; + private identifyNew(): void; + private identifyResume(): void; + private _send(data: object): void; + private processQueue(): void; + private destroy(destroyOptions?: { closeCode?: number; reset?: boolean; emit?: boolean; log?: boolean }): void; + private _cleanupConnection(): void; + private _emitDestroyed(): void; + + public send(data: object): void; + public on(event: 'ready' | 'resumed' | 'invalidSession', listener: () => void): this; + public on(event: 'close', listener: (event: CloseEvent) => void): this; + public on(event: 'allReady', listener: (unavailableGuilds?: Set<Snowflake>) => void): this; + public on(event: string, listener: (...args: any[]) => void): this; + + public once(event: 'ready' | 'resumed' | 'invalidSession', listener: () => void): this; + public once(event: 'close', listener: (event: CloseEvent) => void): this; + public once(event: 'allReady', listener: (unavailableGuilds?: Set<Snowflake>) => void): this; + public once(event: string, listener: (...args: any[]) => void): this; + } + + //#endregion + + //#region Collections + + export class Collection<K, V> extends BaseCollection<K, V> { + public flatMap<T>( + fn: (value: V, key: K, collection: this) => Collection<K, T>, + thisArg?: unknown, + ): Collection<K, T>; + public flatMap<T, This>( + fn: (this: This, value: V, key: K, collection: this) => Collection<K, T>, + thisArg: This, + ): Collection<K, T>; + public mapValues<T>(fn: (value: V, key: K, collection: this) => T, thisArg?: unknown): Collection<K, T>; + public mapValues<This, T>( + fn: (this: This, value: V, key: K, collection: this) => T, + thisArg: This, + ): Collection<K, T>; + public toJSON(): object; + } + + //#endregion + + //#region Managers + + export class ChannelManager extends BaseManager<Snowflake, Channel, ChannelResolvable> { + constructor(client: Client, iterable: Iterable<any>); + public fetch(id: Snowflake, cache?: boolean): Promise<Channel>; + } + + export abstract class BaseManager<K, Holds, R> { + constructor(client: Client, iterable: Iterable<any>, holds: Constructable<Holds>, cacheType: Collection<K, Holds>); + public holds: Constructable<Holds>; + public cache: Collection<K, Holds>; + public cacheType: Collection<K, Holds>; + public readonly client: Client; + public add(data: any, cache?: boolean, { id, extras }?: { id: K; extras: any[] }): Holds; + public resolve(resolvable: R): Holds | null; + public resolveID(resolvable: R): K | null; + } + + export class GuildChannelManager extends BaseManager<Snowflake, GuildChannel, GuildChannelResolvable> { + constructor(guild: Guild, iterable?: Iterable<any>); + public guild: Guild; + public create(name: string, options: GuildCreateChannelOptions & { type: 'voice' }): Promise<VoiceChannel>; + public create(name: string, options: GuildCreateChannelOptions & { type: 'category' }): Promise<CategoryChannel>; + public create(name: string, options?: GuildCreateChannelOptions & { type?: 'text' }): Promise<TextChannel>; + public create( + name: string, + options: GuildCreateChannelOptions, + ): Promise<TextChannel | VoiceChannel | CategoryChannel>; + } + + export class GuildEmojiManager extends BaseManager<Snowflake, GuildEmoji, EmojiResolvable> { + constructor(guild: Guild, iterable?: Iterable<any>); + public guild: Guild; + public create( + attachment: BufferResolvable | Base64Resolvable, + name: string, + options?: GuildEmojiCreateOptions, + ): Promise<GuildEmoji>; + public resolveIdentifier(emoji: EmojiIdentifierResolvable): string | null; + } + + export class GuildEmojiRoleManager { + constructor(emoji: GuildEmoji); + public emoji: GuildEmoji; + public guild: Guild; + public cache: Collection<Snowflake, Role>; + public add(roleOrRoles: RoleResolvable | RoleResolvable[] | Collection<Snowflake, Role>): Promise<GuildEmoji>; + public set(roles: RoleResolvable[] | Collection<Snowflake, Role>): Promise<GuildEmoji>; + public remove(roleOrRoles: RoleResolvable | RoleResolvable[] | Collection<Snowflake, Role>): Promise<GuildEmoji>; + } + + export class GuildManager extends BaseManager<Snowflake, Guild, GuildResolvable> { + constructor(client: Client, iterable?: Iterable<any>); + public create( + name: string, + options?: { region?: string; icon: BufferResolvable | Base64Resolvable | null }, + ): Promise<Guild>; + } + + export class GuildMemberManager extends BaseManager<Snowflake, GuildMember, GuildMemberResolvable> { + constructor(guild: Guild, iterable?: Iterable<any>); + public guild: Guild; + public ban(user: UserResolvable, options?: BanOptions): Promise<GuildMember | User | Snowflake>; + public fetch( + options: UserResolvable | FetchMemberOptions | (FetchMembersOptions & { user: UserResolvable }), + ): Promise<GuildMember>; + public fetch(options?: FetchMembersOptions): Promise<Collection<Snowflake, GuildMember>>; + public prune(options: GuildPruneMembersOptions & { dry?: false; count: false }): Promise<null>; + public prune(options?: GuildPruneMembersOptions): Promise<number>; + public unban(user: UserResolvable, reason?: string): Promise<User>; + } + + export class GuildMemberRoleManager extends OverridableManager<Snowflake, Role, RoleResolvable> { + constructor(member: GuildMember); + public readonly hoist: Role | null; + public readonly color: Role | null; + public readonly highest: Role; + public member: GuildMember; + public guild: Guild; + + public add( + roleOrRoles: RoleResolvable | RoleResolvable[] | Collection<Snowflake, Role>, + reason?: string, + ): Promise<GuildMember>; + public set(roles: RoleResolvable[] | Collection<Snowflake, Role>, reason?: string): Promise<GuildMember>; + public remove( + roleOrRoles: RoleResolvable | RoleResolvable[] | Collection<Snowflake, Role>, + reason?: string, + ): Promise<GuildMember>; + } + + export class MessageManager extends BaseManager<Snowflake, Message, MessageResolvable> { + constructor(channel: TextChannel | DMChannel, iterable?: Iterable<any>); + public channel: TextBasedChannelFields; + public cache: Collection<Snowflake, Message>; + public fetch(message: Snowflake, cache?: boolean): Promise<Message>; + public fetch(options?: ChannelLogsQueryOptions, cache?: boolean): Promise<Collection<Snowflake, Message>>; + public fetchPinned(cache?: boolean): Promise<Collection<Snowflake, Message>>; + public delete(message: MessageResolvable, reason?: string): Promise<void>; + } + + // Hacky workaround because changing the signature of an overridden method errors + class OverridableManager<V, K, R = any> extends BaseManager<V, K, R> { + public add(data: any, cache: any): any; + public set(key: any): any; + } + + export class PresenceManager extends BaseManager<Snowflake, Presence, PresenceResolvable> { + constructor(client: Client, iterable?: Iterable<any>); + } + + export class ReactionManager extends BaseManager<Snowflake, MessageReaction, MessageReactionResolvable> { + constructor(message: Message, iterable?: Iterable<any>); + public message: Message; + public removeAll(): Promise<Message>; + } + + export class ReactionUserManager extends BaseManager<Snowflake, User, UserResolvable> { + constructor(client: Client, iterable: Iterable<any> | undefined, reaction: MessageReaction); + public reaction: MessageReaction; + public fetch(options?: { + limit?: number; + after?: Snowflake; + before?: Snowflake; + }): Promise<Collection<Snowflake, User>>; + public remove(user?: UserResolvable): Promise<MessageReaction>; + } + + export class RoleManager extends BaseManager<Snowflake, Role, RoleResolvable> { + constructor(guild: Guild, iterable?: Iterable<any>); + public readonly everyone: Role; + public readonly highest: Role; + public guild: Guild; + + public create(options?: { data?: RoleData; reason?: string }): Promise<Role>; + public fetch(id: Snowflake, cache?: boolean): Promise<Role | null>; + public fetch(id?: Snowflake, cache?: boolean): Promise<this>; + } + + export class UserManager extends BaseManager<Snowflake, User, UserResolvable> { + constructor(client: Client, iterable?: Iterable<any>); + public fetch(id: Snowflake, cache?: boolean): Promise<User>; + } + + export class VoiceStateManager extends BaseManager<Snowflake, VoiceState, typeof VoiceState> { + constructor(guild: Guild, iterable?: Iterable<any>); + public guild: Guild; + } + + //#endregion + + //#region Mixins + + // Model the TextBasedChannel mixin system, allowing application of these fields + // to the classes that use these methods without having to manually add them + // to each of those classes + + type Constructable<T> = new (...args: any[]) => T; + function PartialTextBasedChannel<T>(Base?: Constructable<T>): Constructable<T & PartialTextBasedChannelFields>; + function TextBasedChannel<T>(Base?: Constructable<T>): Constructable<T & TextBasedChannelFields>; + + interface PartialTextBasedChannelFields { + lastMessageID: Snowflake | null; + readonly lastMessage: Message | null; + send( + options: MessageOptions | (MessageOptions & { split?: false }) | MessageAdditions | APIMessage, + ): Promise<Message>; + send( + options: (MessageOptions & { split: true | SplitOptions; content: StringResolvable }) | APIMessage, + ): Promise<Message[]>; + send( + content: StringResolvable, + options?: MessageOptions | (MessageOptions & { split?: false }) | MessageAdditions, + ): Promise<Message>; + send(content: StringResolvable, options?: MessageOptions & { split: true | SplitOptions }): Promise<Message[]>; + } + + interface TextBasedChannelFields extends PartialTextBasedChannelFields { + _typing: Map<string, TypingData>; + lastPinTimestamp: number | null; + readonly lastPinAt: Date; + typing: boolean; + typingCount: number; + awaitMessages(filter: CollectorFilter, options?: AwaitMessagesOptions): Promise<Collection<Snowflake, Message>>; + bulkDelete( + messages: Collection<Snowflake, Message> | Message[] | Snowflake[] | number, + filterOld?: boolean, + ): Promise<Collection<Snowflake, Message>>; + createMessageCollector(filter: CollectorFilter, options?: MessageCollectorOptions): MessageCollector; + startTyping(count?: number): Promise<void>; + stopTyping(force?: boolean): void; + } + + function WebhookMixin<T>(Base?: Constructable<T>): Constructable<T & WebhookFields>; + + function VolumeMixin<T>(base: Constructable<T>): Constructable<T & VolumeInterface>; + + interface WebhookFields { + id: Snowflake; + readonly createdAt: Date; + readonly createdTimestamp: number; + readonly url: string; + delete(reason?: string): Promise<void>; + edit(options: WebhookEditData): Promise<Webhook>; + send( + content?: StringResolvable, + options?: (WebhookMessageOptions & { split?: false }) | MessageAdditions, + ): Promise<Message>; + send( + content?: StringResolvable, + options?: (WebhookMessageOptions & { split: true | SplitOptions }) | MessageAdditions, + ): Promise<Message[]>; + send(options?: (WebhookMessageOptions & { split?: false }) | MessageAdditions | APIMessage): Promise<Message>; + send( + options?: (WebhookMessageOptions & { split: true | SplitOptions }) | MessageAdditions | APIMessage, + ): Promise<Message[]>; + sendSlackMessage(body: object): Promise<boolean>; + } + + //#endregion + + //#region Typedefs + + type ActivityFlagsString = 'INSTANCE' | 'JOIN' | 'SPECTATE' | 'JOIN_REQUEST' | 'SYNC' | 'PLAY'; + + interface ActivityOptions { + name?: string; + url?: string; + type?: ActivityType | number; + shardID?: number | number[]; + } + + type ActivityType = 'PLAYING' | 'STREAMING' | 'LISTENING' | 'WATCHING' | 'CUSTOM_STATUS'; + + interface AddGuildMemberOptions { + accessToken: string; + nick?: string; + roles?: Collection<Snowflake, Role> | RoleResolvable[]; + mute?: boolean; + deaf?: boolean; + } + + interface APIErrror { + UNKNOWN_ACCOUNT: number; + UNKNOWN_APPLICATION: number; + UNKNOWN_CHANNEL: number; + UNKNOWN_GUILD: number; + UNKNOWN_INTEGRATION: number; + UNKNOWN_INVITE: number; + UNKNOWN_MEMBER: number; + UNKNOWN_MESSAGE: number; + UNKNOWN_OVERWRITE: number; + UNKNOWN_PROVIDER: number; + UNKNOWN_ROLE: number; + UNKNOWN_TOKEN: number; + UNKNOWN_USER: number; + UNKNOWN_EMOJI: number; + UNKNOWN_WEBHOOK: number; + BOT_PROHIBITED_ENDPOINT: number; + BOT_ONLY_ENDPOINT: number; + MAXIMUM_GUILDS: number; + MAXIMUM_FRIENDS: number; + MAXIMUM_PINS: number; + MAXIMUM_ROLES: number; + MAXIMUM_REACTIONS: number; + UNAUTHORIZED: number; + MISSING_ACCESS: number; + INVALID_ACCOUNT_TYPE: number; + CANNOT_EXECUTE_ON_DM: number; + EMBED_DISABLED: number; + CANNOT_EDIT_MESSAGE_BY_OTHER: number; + CANNOT_SEND_EMPTY_MESSAGE: number; + CANNOT_MESSAGE_USER: number; + CANNOT_SEND_MESSAGES_IN_VOICE_CHANNEL: number; + CHANNEL_VERIFICATION_LEVEL_TOO_HIGH: number; + OAUTH2_APPLICATION_BOT_ABSENT: number; + MAXIMUM_OAUTH2_APPLICATIONS: number; + INVALID_OAUTH_STATE: number; + MISSING_PERMISSIONS: number; + INVALID_AUTHENTICATION_TOKEN: number; + NOTE_TOO_LONG: number; + INVALID_BULK_DELETE_QUANTITY: number; + CANNOT_PIN_MESSAGE_IN_OTHER_CHANNEL: number; + CANNOT_EXECUTE_ON_SYSTEM_MESSAGE: number; + BULK_DELETE_MESSAGE_TOO_OLD: number; + INVITE_ACCEPTED_TO_GUILD_NOT_CONTAINING_BOT: number; + REACTION_BLOCKED: number; + } + + interface AuditLogChange { + key: string; + old?: any; + new?: any; + } + + interface AwaitMessagesOptions extends MessageCollectorOptions { + errors?: string[]; + } + + interface AwaitReactionsOptions extends ReactionCollectorOptions { + errors?: string[]; + } + + interface BanOptions { + days?: number; + reason?: string; + } + + type Base64Resolvable = Buffer | Base64String; + + type Base64String = string; + + type BitFieldResolvable<T extends string> = + | RecursiveArray<T | number | Readonly<BitField<T>>> + | T + | number + | Readonly<BitField<T>>; + + type BufferResolvable = Buffer | string; + + interface ChannelCreationOverwrites { + allow?: PermissionResolvable | number; + deny?: PermissionResolvable | number; + id: RoleResolvable | UserResolvable; + } + + interface ChannelData { + name?: string; + position?: number; + topic?: string; + nsfw?: boolean; + bitrate?: number; + userLimit?: number; + parentID?: Snowflake; + rateLimitPerUser?: number; + lockPermissions?: boolean; + permissionOverwrites?: OverwriteResolvable[] | Collection<Snowflake, OverwriteResolvable>; + } + + interface ChannelLogsQueryOptions { + limit?: number; + before?: Snowflake; + after?: Snowflake; + around?: Snowflake; + } + + interface ChannelPosition { + channel: ChannelResolvable; + position: number; + } + + type ChannelResolvable = Channel | Snowflake; + + interface ClientApplicationAsset { + name: string; + id: Snowflake; + type: 'BIG' | 'SMALL'; + } + + interface ClientEvents { + channelCreate: [Channel]; + channelDelete: [Channel | PartialDMChannel]; + channelPinsUpdate: [Channel | PartialDMChannel, Date]; + channelUpdate: [Channel, Channel]; + debug: [string]; + warn: [string]; + disconnect: [any, number]; + emojiCreate: [GuildEmoji]; + emojiDelete: [GuildEmoji]; + emojiUpdate: [GuildEmoji, GuildEmoji]; + error: [Error]; + guildBanAdd: [Guild, User | PartialUser]; + guildBanRemove: [Guild, User | PartialUser]; + guildCreate: [Guild]; + guildDelete: [Guild]; + guildUnavailable: [Guild]; + guildIntegrationsUpdate: [Guild]; + guildMemberAdd: [GuildMember | PartialGuildMember]; + guildMemberAvailable: [GuildMember | PartialGuildMember]; + guildMemberRemove: [GuildMember | PartialGuildMember]; + guildMembersChunk: [Collection<Snowflake, GuildMember | PartialGuildMember>, Guild]; + guildMemberSpeaking: [GuildMember | PartialGuildMember, Readonly<Speaking>]; + guildMemberUpdate: [GuildMember | PartialGuildMember, GuildMember | PartialGuildMember]; + guildUpdate: [Guild, Guild]; + inviteCreate: [Invite]; + inviteDelete: [Invite]; + message: [Message]; + messageDelete: [Message | PartialMessage]; + messageReactionRemoveAll: [Message | PartialMessage]; + messageReactionRemoveEmoji: [MessageReaction]; + messageDeleteBulk: [Collection<Snowflake, Message | PartialMessage>]; + messageReactionAdd: [MessageReaction, User | PartialUser]; + messageReactionRemove: [MessageReaction, User | PartialUser]; + messageUpdate: [Message | PartialMessage, Message | PartialMessage]; + presenceUpdate: [Presence | undefined, Presence]; + rateLimit: [RateLimitData]; + ready: []; + invalidated: []; + roleCreate: [Role]; + roleDelete: [Role]; + roleUpdate: [Role, Role]; + typingStart: [Channel | PartialDMChannel, User | PartialUser]; + userUpdate: [User | PartialUser, User | PartialUser]; + voiceStateUpdate: [VoiceState, VoiceState]; + webhookUpdate: [TextChannel]; + shardDisconnect: [CloseEvent, number]; + shardError: [Error, number]; + shardReady: [number]; + shardReconnecting: [number]; + shardResume: [number, number]; + } + + interface ClientOptions { + shards?: number | number[] | 'auto'; + shardCount?: number; + messageCacheMaxSize?: number; + messageCacheLifetime?: number; + messageSweepInterval?: number; + fetchAllMembers?: boolean; + disableMentions?: 'none' | 'all' | 'everyone'; + allowedMentions?: MessageMentionOptions; + partials?: PartialTypes[]; + restWsBridgeTimeout?: number; + restTimeOffset?: number; + restRequestTimeout?: number; + restSweepInterval?: number; + retryLimit?: number; + presence?: PresenceData; + ws?: WebSocketOptions; + http?: HTTPOptions; + } + + type ClientPresenceStatus = 'online' | 'idle' | 'dnd'; + + interface ClientPresenceStatusData { + web?: ClientPresenceStatus; + mobile?: ClientPresenceStatus; + desktop?: ClientPresenceStatus; + } + + interface CloseEvent { + wasClean: boolean; + code: number; + reason: string; + target: WebSocket; + } + + type CollectorFilter = (...args: any[]) => boolean; + + interface CollectorOptions { + time?: number; + idle?: number; + dispose?: boolean; + } + + type ColorResolvable = + | 'DEFAULT' + | 'WHITE' + | 'AQUA' + | 'GREEN' + | 'BLUE' + | 'YELLOW' + | 'PURPLE' + | 'LUMINOUS_VIVID_PINK' + | 'GOLD' + | 'ORANGE' + | 'RED' + | 'GREY' + | 'DARKER_GREY' + | 'NAVY' + | 'DARK_AQUA' + | 'DARK_GREEN' + | 'DARK_BLUE' + | 'DARK_PURPLE' + | 'DARK_VIVID_PINK' + | 'DARK_GOLD' + | 'DARK_ORANGE' + | 'DARK_RED' + | 'DARK_GREY' + | 'LIGHT_GREY' + | 'DARK_NAVY' + | 'RANDOM' + | [number, number, number] + | number + | string; + + interface CrosspostedChannel { + channelID: Snowflake; + guildID: Snowflake; + type: keyof typeof ChannelType; + name: string; + } + + interface DeconstructedSnowflake { + timestamp: number; + readonly date: Date; + workerID: number; + processID: number; + increment: number; + binary: string; + } + + type DefaultMessageNotifications = 'ALL' | 'MENTIONS'; + + interface EmbedField { + name: string; + value: string; + inline: boolean; + } + + interface EmbedFieldData { + name: StringResolvable; + value: StringResolvable; + inline?: boolean; + } + + type EmojiIdentifierResolvable = string | EmojiResolvable; + + type EmojiResolvable = Snowflake | GuildEmoji | ReactionEmoji; + + interface ErrorEvent { + error: any; + message: string; + type: string; + target: WebSocket; + } + + interface EscapeMarkdownOptions { + codeBlock?: boolean; + inlineCode?: boolean; + bold?: boolean; + italic?: boolean; + underline?: boolean; + strikethrough?: boolean; + spoiler?: boolean; + inlineCodeContent?: boolean; + codeBlockContent?: boolean; + } + + type ExplicitContentFilterLevel = 'DISABLED' | 'MEMBERS_WITHOUT_ROLES' | 'ALL_MEMBERS'; + + interface Extendable { + GuildEmoji: typeof GuildEmoji; + DMChannel: typeof DMChannel; + TextChannel: typeof TextChannel; + VoiceChannel: typeof VoiceChannel; + CategoryChannel: typeof CategoryChannel; + NewsChannel: typeof NewsChannel; + StoreChannel: typeof StoreChannel; + GuildMember: typeof GuildMember; + Guild: typeof Guild; + Message: typeof Message; + MessageReaction: typeof MessageReaction; + Presence: typeof Presence; + VoiceState: typeof VoiceState; + Role: typeof Role; + User: typeof User; + } + + interface FetchMemberOptions { + user: UserResolvable; + cache?: boolean; + } + + interface FetchMembersOptions { + user?: UserResolvable | UserResolvable[]; + query?: string; + limit?: number; + withPresences?: boolean; + time?: number; + } + + interface FileOptions { + attachment: BufferResolvable | Stream; + name?: string; + } + + type GuildAuditLogsAction = keyof GuildAuditLogsActions; + + interface GuildAuditLogsActions { + ALL?: null; + GUILD_UPDATE?: number; + CHANNEL_CREATE?: number; + CHANNEL_UPDATE?: number; + CHANNEL_DELETE?: number; + CHANNEL_OVERWRITE_CREATE?: number; + CHANNEL_OVERWRITE_UPDATE?: number; + CHANNEL_OVERWRITE_DELETE?: number; + MEMBER_KICK?: number; + MEMBER_PRUNE?: number; + MEMBER_BAN_ADD?: number; + MEMBER_BAN_REMOVE?: number; + MEMBER_UPDATE?: number; + MEMBER_ROLE_UPDATE?: number; + MEMBER_MOVE?: number; + MEMBER_DISCONNECT?: number; + BOT_ADD?: number; + ROLE_CREATE?: number; + ROLE_UPDATE?: number; + ROLE_DELETE?: number; + INVITE_CREATE?: number; + INVITE_UPDATE?: number; + INVITE_DELETE?: number; + WEBHOOK_CREATE?: number; + WEBHOOK_UPDATE?: number; + WEBHOOK_DELETE?: number; + EMOJI_CREATE?: number; + EMOJI_UPDATE?: number; + EMOJI_DELETE?: number; + MESSAGE_DELETE?: number; + MESSAGE_BULK_DELETE?: number; + MESSAGE_PIN?: number; + MESSAGE_UNPIN?: number; + INTEGRATION_CREATE?: number; + INTEGRATION_UPDATE?: number; + INTEGRATION_DELETE?: number; + } + + type GuildAuditLogsActionType = 'CREATE' | 'DELETE' | 'UPDATE' | 'ALL'; + + interface GuildAuditLogsFetchOptions { + before?: Snowflake | GuildAuditLogsEntry; + limit?: number; + user?: UserResolvable; + type?: GuildAuditLogsAction | number; + } + + type GuildAuditLogsTarget = keyof GuildAuditLogsTargets; + + interface GuildAuditLogsTargets { + ALL?: string; + GUILD?: string; + CHANNEL?: string; + USER?: string; + ROLE?: string; + INVITE?: string; + WEBHOOK?: string; + EMOJI?: string; + MESSAGE?: string; + INTEGRATION?: string; + UNKNOWN?: string; + } + + type GuildChannelResolvable = Snowflake | GuildChannel; + + interface GuildCreateChannelOptions { + permissionOverwrites?: OverwriteResolvable[] | Collection<Snowflake, OverwriteResolvable>; + topic?: string; + type?: Exclude< + keyof typeof ChannelType | ChannelType, + 'dm' | 'group' | 'unknown' | ChannelType.dm | ChannelType.group | ChannelType.unknown + >; + nsfw?: boolean; + parent?: ChannelResolvable; + bitrate?: number; + userLimit?: number; + rateLimitPerUser?: number; + position?: number; + reason?: string; + } + + interface GuildChannelCloneOptions extends GuildCreateChannelOptions { + name?: string; + } + + interface GuildEditData { + name?: string; + region?: string; + verificationLevel?: VerificationLevel; + explicitContentFilter?: ExplicitContentFilterLevel; + defaultMessageNotifications?: DefaultMessageNotifications | number; + afkChannel?: ChannelResolvable; + systemChannel?: ChannelResolvable; + systemChannelFlags?: SystemChannelFlagsResolvable; + afkTimeout?: number; + icon?: Base64Resolvable; + owner?: GuildMemberResolvable; + splash?: Base64Resolvable; + banner?: Base64Resolvable; + } + + interface GuildEmbedData { + enabled: boolean; + channel: GuildChannelResolvable | null; + } + + interface GuildEmojiCreateOptions { + roles?: Collection<Snowflake, Role> | RoleResolvable[]; + reason?: string; + } + + interface GuildEmojiEditData { + name?: string; + roles?: Collection<Snowflake, Role> | RoleResolvable[]; + } + + type GuildFeatures = + | 'ANIMATED_ICON' + | 'BANNER' + | 'COMMERCE' + | 'DISCOVERABLE' + | 'FEATURABLE' + | 'INVITE_SPLASH' + | 'NEWS' + | 'PARTNERED' + | 'PUBLIC' + | 'PUBLIC_DISABLED' + | 'VANITY_URL' + | 'VERIFIED' + | 'VIP_REGIONS' + | 'WELCOME_SCREEN_ENABLED'; + + interface GuildMemberEditData { + nick?: string; + roles?: Collection<Snowflake, Role> | RoleResolvable[]; + mute?: boolean; + deaf?: boolean; + channel?: ChannelResolvable | null; + } + + type GuildMemberResolvable = GuildMember | UserResolvable; + + type GuildResolvable = Guild | GuildChannel | GuildMember | GuildEmoji | Invite | Role | Snowflake; + + interface GuildPruneMembersOptions { + count?: boolean; + days?: number; + dry?: boolean; + reason?: string; + } + + interface HTTPOptions { + api?: string; + version?: number; + host?: string; + cdn?: string; + invite?: string; + } + + type ImageSize = 16 | 32 | 64 | 128 | 256 | 512 | 1024 | 2048 | 4096; + + interface ImageURLOptions { + format?: AllowedImageFormat; + size?: ImageSize; + } + + interface IntegrationData { + id: string; + type: string; + } + + interface IntegrationEditData { + expireBehavior?: number; + expireGracePeriod?: number; + } + + interface IntegrationAccount { + id: string; + 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; + maxUses?: number; + unique?: boolean; + reason?: string; + } + + type InviteResolvable = string; + + type MembershipStates = 'INVITED' | 'ACCEPTED'; + + type MessageAdditions = MessageEmbed | MessageAttachment | (MessageEmbed | MessageAttachment)[]; + + interface MessageActivity { + partyID: string; + type: number; + } + + interface MessageCollectorOptions extends CollectorOptions { + max?: number; + maxProcessed?: number; + } + + interface MessageEditOptions { + content?: string; + embed?: MessageEmbedOptions | null; + code?: string | boolean; + flags?: BitFieldResolvable<MessageFlagsString>; + allowedMentions?: MessageMentionOptions; + } + + interface MessageEmbedAuthor { + name?: string; + url?: string; + iconURL?: string; + proxyIconURL?: string; + } + + interface MessageEmbedFooter { + text?: string; + iconURL?: string; + proxyIconURL?: string; + } + + interface MessageEmbedImage { + url: string; + proxyURL?: string; + height?: number; + width?: number; + } + + interface MessageEmbedOptions { + title?: string; + description?: string; + url?: string; + timestamp?: Date | number; + color?: ColorResolvable; + fields?: EmbedFieldData[]; + files?: (MessageAttachment | string | FileOptions)[]; + author?: Partial<MessageEmbedAuthor> & { icon_url?: string; proxy_icon_url?: string }; + thumbnail?: Partial<MessageEmbedThumbnail> & { proxy_url?: string }; + image?: Partial<MessageEmbedImage> & { proxy_url?: string }; + video?: Partial<MessageEmbedVideo> & { proxy_url?: string }; + footer?: Partial<MessageEmbedFooter> & { icon_url?: string; proxy_icon_url?: string }; + } + + interface MessageEmbedProvider { + name: string; + url: string; + } + + interface MessageEmbedThumbnail { + url: string; + proxyURL?: string; + height?: number; + width?: number; + } + + interface MessageEmbedVideo { + url?: string; + proxyURL?: string; + height?: number; + width?: number; + } + + interface MessageEvent { + data: WebSocket.Data; + type: string; + target: WebSocket; + } + + type MessageFlagsString = 'CROSSPOSTED' | 'IS_CROSSPOST' | 'SUPPRESS_EMBEDS' | 'SOURCE_MESSAGE_DELETED' | 'URGENT'; + + interface MessageMentionOptions { + parse?: MessageMentionTypes[]; + roles?: Snowflake[]; + users?: Snowflake[]; + } + + type MessageMentionTypes = 'roles' | 'users' | 'everyone'; + + interface MessageOptions { + tts?: boolean; + nonce?: string; + content?: string; + embed?: MessageEmbed | MessageEmbedOptions; + disableMentions?: 'none' | 'all' | 'everyone'; + allowedMentions?: MessageMentionOptions; + files?: (FileOptions | BufferResolvable | Stream | MessageAttachment)[]; + code?: string | boolean; + split?: boolean | SplitOptions; + reply?: UserResolvable; + } + + type MessageReactionResolvable = MessageReaction | Snowflake; + + interface MessageReference { + channelID: string; + guildID: string; + messageID: string | null; + } + + type MessageResolvable = Message | Snowflake; + + type MessageTarget = TextChannel | DMChannel | User | GuildMember | Webhook | WebhookClient; + + type MessageType = + | 'DEFAULT' + | 'RECIPIENT_ADD' + | 'RECIPIENT_REMOVE' + | 'CALL' + | 'CHANNEL_NAME_CHANGE' + | 'CHANNEL_ICON_CHANGE' + | 'PINS_ADD' + | 'GUILD_MEMBER_JOIN' + | 'USER_PREMIUM_GUILD_SUBSCRIPTION' + | 'USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1' + | 'USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2' + | 'USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3' + | 'CHANNEL_FOLLOW_ADD' + | 'GUILD_DISCOVERY_DISQUALIFIED' + | 'GUILD_DISCOVERY_REQUALIFIED'; + + interface OverwriteData { + allow?: PermissionResolvable; + deny?: PermissionResolvable; + id: GuildMemberResolvable | RoleResolvable; + type?: OverwriteType; + } + + type OverwriteResolvable = PermissionOverwrites | OverwriteData; + + type OverwriteType = 'member' | 'role'; + + interface PermissionFlags extends Record<PermissionString, number> {} + + interface PermissionObject extends Record<PermissionString, boolean> {} + + interface PermissionOverwriteOption extends Partial<Record<PermissionString, boolean | null>> {} + + type PermissionResolvable = BitFieldResolvable<PermissionString>; + + type PermissionString = + | 'CREATE_INSTANT_INVITE' + | 'KICK_MEMBERS' + | 'BAN_MEMBERS' + | 'ADMINISTRATOR' + | 'MANAGE_CHANNELS' + | 'MANAGE_GUILD' + | 'ADD_REACTIONS' + | 'VIEW_AUDIT_LOG' + | 'PRIORITY_SPEAKER' + | 'STREAM' + | 'VIEW_CHANNEL' + | 'SEND_MESSAGES' + | 'SEND_TTS_MESSAGES' + | 'MANAGE_MESSAGES' + | 'EMBED_LINKS' + | 'ATTACH_FILES' + | 'READ_MESSAGE_HISTORY' + | 'MENTION_EVERYONE' + | 'USE_EXTERNAL_EMOJIS' + | 'VIEW_GUILD_INSIGHTS' + | 'CONNECT' + | 'SPEAK' + | 'MUTE_MEMBERS' + | 'DEAFEN_MEMBERS' + | 'MOVE_MEMBERS' + | 'USE_VAD' + | 'CHANGE_NICKNAME' + | 'MANAGE_NICKNAMES' + | 'MANAGE_ROLES' + | 'MANAGE_WEBHOOKS' + | 'MANAGE_EMOJIS'; + + interface RecursiveArray<T> extends Array<T | RecursiveArray<T>> {} + + interface PermissionOverwriteOptions { + allow: PermissionResolvable; + deny: PermissionResolvable; + id: UserResolvable | RoleResolvable; + } + + type PremiumTier = number; + + interface PresenceData { + status?: PresenceStatusData; + afk?: boolean; + activity?: { + name?: string; + type?: ActivityType | number; + url?: string; + }; + shardID?: number | number[]; + } + + type PresenceResolvable = Presence | UserResolvable | Snowflake; + + type Partialize<T, O extends string> = { + readonly client: Client; + readonly createdAt: Date; + readonly createdTimestamp: number; + deleted: boolean; + id: string; + partial: true; + fetch(): Promise<T>; + } & { + [K in keyof Omit< + T, + 'client' | 'createdAt' | 'createdTimestamp' | 'id' | 'partial' | 'fetch' | O + >]: T[K] extends Function ? T[K] : T[K] | null; // tslint:disable-line:ban-types + }; + + interface PartialDMChannel + extends Partialize< + DMChannel, + 'lastMessage' | 'lastMessageID' | 'messages' | 'recipient' | 'type' | 'typing' | 'typingCount' + > { + lastMessage: null; + lastMessageID: undefined; + messages: MessageManager; + recipient: User | PartialUser; + type: 'dm'; + readonly typing: boolean; + readonly typingCount: number; + } + + interface PartialChannelData { + id?: number; + name: string; + topic?: string; + type?: ChannelType; + parentID?: number; + permissionOverwrites?: { + id: number | Snowflake; + type?: OverwriteType; + allow?: PermissionResolvable; + deny?: PermissionResolvable; + }[]; + } + + interface PartialGuildMember + extends Partialize< + GuildMember, + 'bannable' | 'displayColor' | 'displayHexColor' | 'displayName' | 'guild' | 'kickable' | 'permissions' | 'roles' + > { + readonly bannable: boolean; + readonly displayColor: number; + readonly displayHexColor: string; + readonly displayName: string; + guild: Guild; + joinedAt: null; + joinedTimestamp: null; + readonly kickable: boolean; + readonly permissions: GuildMember['permissions']; + readonly roles: GuildMember['roles']; + } + + interface PartialMessage + extends Partialize< + Message, + 'attachments' | 'channel' | 'deletable' | 'editable' | 'mentions' | 'pinnable' | 'system' | 'url' + > { + attachments: Message['attachments']; + channel: Message['channel']; + readonly deletable: boolean; + readonly editable: boolean; + mentions: Message['mentions']; + readonly pinnable: boolean; + reactions: Message['reactions']; + system: boolean; + readonly url: string; + } + + interface PartialRoleData extends RoleData { + id?: number; + } + + type PartialTypes = 'USER' | 'CHANNEL' | 'GUILD_MEMBER' | 'MESSAGE' | 'REACTION'; + + interface PartialUser extends Partialize<User, 'discriminator' | 'username' | 'tag'> { + discriminator: undefined; + username: undefined; + readonly tag: null; + } + + type PresenceStatus = ClientPresenceStatus | 'offline'; + + type PresenceStatusData = ClientPresenceStatus | 'invisible'; + + interface RateLimitData { + timeout: number; + limit: number; + timeDifference: number; + method: string; + path: string; + route: string; + } + + interface RawOverwriteData { + id: Snowflake; + allow: number; + deny: number; + type: OverwriteType; + } + + interface ReactionCollectorOptions extends CollectorOptions { + max?: number; + maxEmojis?: number; + maxUsers?: number; + } + + interface ResolvedOverwriteOptions { + allow: Permissions; + deny: Permissions; + } + + interface RoleData { + name?: string; + color?: ColorResolvable; + hoist?: boolean; + position?: number; + permissions?: PermissionResolvable; + mentionable?: boolean; + } + + interface RolePosition { + role: RoleResolvable; + position: number; + } + + type RoleResolvable = Role | string; + + type ShardingManagerMode = 'process' | 'worker'; + + type Snowflake = string; + + interface SplitOptions { + maxLength?: number; + char?: string; + prepend?: string; + append?: string; + } + + type Status = number; + + interface StreamOptions { + type?: StreamType; + seek?: number; + volume?: number | boolean; + plp?: number; + fec?: boolean; + bitrate?: number | 'auto'; + highWaterMark?: number; + } + + type SpeakingString = 'SPEAKING' | 'SOUNDSHARE' | 'PRIORITY_SPEAKING'; + + type StreamType = 'unknown' | 'converted' | 'opus' | 'ogg/opus' | 'webm/opus'; + + type StringResolvable = string | string[] | any; + + type SystemChannelFlagsString = 'WELCOME_MESSAGE_DISABLED' | 'BOOST_MESSAGE_DISABLED'; + + type SystemChannelFlagsResolvable = BitFieldResolvable<SystemChannelFlagsString>; + + type TargetUser = number; + + interface TypingData { + user: User | PartialUser; + since: Date; + lastTimestamp: Date; + elapsedTime: number; + timeout: NodeJS.Timeout; + } + + type UserFlagsString = + | 'DISCORD_EMPLOYEE' + | 'DISCORD_PARTNER' + | 'HYPESQUAD_EVENTS' + | 'BUGHUNTER_LEVEL_1' + | 'HOUSE_BRAVERY' + | 'HOUSE_BRILLIANCE' + | 'HOUSE_BALANCE' + | 'EARLY_SUPPORTER' + | 'TEAM_USER' + | 'SYSTEM' + | 'BUGHUNTER_LEVEL_2' + | 'VERIFIED_BOT' + | 'VERIFIED_DEVELOPER'; + + type UserResolvable = User | Snowflake | Message | GuildMember; + + type VerificationLevel = 'NONE' | 'LOW' | 'MEDIUM' | 'HIGH' | 'VERY_HIGH'; + + type VoiceStatus = number; + + interface WebhookEditData { + name?: string; + avatar?: BufferResolvable; + channel?: ChannelResolvable; + reason?: string; + } + + interface WebhookMessageOptions { + username?: string; + avatarURL?: string; + tts?: boolean; + nonce?: string; + embeds?: (MessageEmbed | object)[]; + disableMentions?: 'none' | 'all' | 'everyone'; + allowedMentions?: MessageMentionOptions; + files?: (FileOptions | BufferResolvable | Stream | MessageAttachment)[]; + code?: string | boolean; + split?: boolean | SplitOptions; + } + + type WebhookTypes = 'Incoming' | 'Channel Follower'; + + interface WebSocketOptions { + large_threshold?: number; + compress?: boolean; + intents?: BitFieldResolvable<IntentsString> | number; + } + + type WSEventType = + | 'READY' + | 'RESUMED' + | 'GUILD_CREATE' + | 'GUILD_DELETE' + | 'GUILD_UPDATE' + | 'INVITE_CREATE' + | 'INVITE_DELETE' + | 'GUILD_MEMBER_ADD' + | 'GUILD_MEMBER_REMOVE' + | 'GUILD_MEMBER_UPDATE' + | 'GUILD_MEMBERS_CHUNK' + | 'GUILD_ROLE_CREATE' + | 'GUILD_ROLE_DELETE' + | 'GUILD_ROLE_UPDATE' + | 'GUILD_BAN_ADD' + | 'GUILD_BAN_REMOVE' + | 'GUILD_EMOJIS_UPDATE' + | 'GUILD_INTEGRATIONS_UPDATE' + | 'CHANNEL_CREATE' + | 'CHANNEL_DELETE' + | 'CHANNEL_UPDATE' + | 'CHANNEL_PINS_UPDATE' + | 'MESSAGE_CREATE' + | 'MESSAGE_DELETE' + | 'MESSAGE_UPDATE' + | 'MESSAGE_DELETE_BULK' + | 'MESSAGE_REACTION_ADD' + | 'MESSAGE_REACTION_REMOVE' + | 'MESSAGE_REACTION_REMOVE_ALL' + | 'MESSAGE_REACTION_REMOVE_EMOJI' + | 'USER_UPDATE' + | 'PRESENCE_UPDATE' + | 'TYPING_START' + | 'VOICE_STATE_UPDATE' + | 'VOICE_SERVER_UPDATE' + | 'WEBHOOKS_UPDATE'; + + //#endregion +} diff --git a/node_modules/discord.js/typings/index.ts b/node_modules/discord.js/typings/index.ts new file mode 100644 index 0000000..26708a4 --- /dev/null +++ b/node_modules/discord.js/typings/index.ts @@ -0,0 +1,28 @@ +/// <reference path="index.d.ts" /> + +import { Client } from 'discord.js'; + +const client: Client = new Client(); + +client.on('ready', () => { + console.log(`Client is logged in as ${client.user!.tag} and ready!`); +}); + +client.on('guildCreate', g => { + const channel = g.channels.cache.random(); + if (!channel) return; + + channel.setName('foo').then(updatedChannel => { + console.log(`New channel name: ${updatedChannel.name}`); + }); +}); + +client.on('messageReactionRemoveAll', async message => { + console.log(`messageReactionRemoveAll - id: ${message.id} (${message.id.length})`); + + if (message.partial) message = await message.fetch(); + + console.log(`messageReactionRemoveAll - content: ${message.content}`); +}); + +client.login('absolutely-valid-token'); diff --git a/node_modules/discord.js/webpack/discord.min.js b/node_modules/discord.js/webpack/discord.min.js new file mode 100644 index 0000000..24d1987 --- /dev/null +++ b/node_modules/discord.js/webpack/discord.min.js @@ -0,0 +1 @@ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.Discord=t():e.Discord=t()}(window,(function(){return function(e){var t={};function i(s){if(t[s])return t[s].exports;var n=t[s]={i:s,l:!1,exports:{}};return e[s].call(n.exports,n,n.exports,i),n.l=!0,n.exports}return i.m=e,i.c=t,i.d=function(e,t,s){i.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:s})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,t){if(1&t&&(e=i(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var s=Object.create(null);if(i.r(s),Object.defineProperty(s,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var n in e)i.d(s,n,function(t){return e[t]}.bind(null,n));return s},i.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(t,"a",t),t},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.p="",i(i.s=97)}([function(e,t,i){"use strict";(function(e){const s=t.Package=i(58),{Error:n,RangeError:r}=i(3),o=t.browser="undefined"!=typeof window;t.DefaultOptions={shardCount:1,messageCacheMaxSize:200,messageCacheLifetime:0,messageSweepInterval:0,fetchAllMembers:!1,disableMentions:"none",partials:[],restWsBridgeTimeout:5e3,restRequestTimeout:15e3,retryLimit:1,restTimeOffset:500,restSweepInterval:60,presence:{},ws:{large_threshold:250,compress:!1,properties:{$os:o?"browser":e.platform,$browser:"discord.js",$device:"discord.js"},version:6},http:{version:7,api:"https://discordapp.com/api",cdn:"https://cdn.discordapp.com",invite:"https://discord.gg"}},t.UserAgent=o?null:`DiscordBot (${s.homepage.split("#")[0]}, ${s.version}) Node.js/${e.version}`,t.WSCodes={1e3:"WS_CLOSE_REQUESTED",4004:"TOKEN_INVALID",4010:"SHARDING_INVALID",4011:"SHARDING_REQUIRED",4013:"INVALID_INTENTS",4014:"DISALLOWED_INTENTS"};const a=["webp","png","jpg","jpeg","gif"],c=Array.from({length:9},(e,t)=>2**(t+4));function l(e,{format:t="webp",size:i}={}){if(t&&!a.includes(t))throw new n("IMAGE_FORMAT",t);if(i&&!c.includes(i))throw new r("IMAGE_SIZE",i);return`${e}.${t}${i?`?size=${i}`:""}`}function h(e){let t=Object.create(null);for(const i of e)t[i]=i;return t}t.Endpoints={CDN:e=>({Emoji:(t,i="png")=>`${e}/emojis/${t}.${i}`,Asset:t=>`${e}/assets/${t}`,DefaultAvatar:t=>`${e}/embed/avatars/${t}.png`,Avatar:(t,i,s="webp",n,r=!1)=>(r&&(s=i.startsWith("a_")?"gif":s),l(`${e}/avatars/${t}/${i}`,{format:s,size:n})),Banner:(t,i,s="webp",n)=>l(`${e}/banners/${t}/${i}`,{format:s,size:n}),Icon:(t,i,s="webp",n,r=!1)=>(r&&(s=i.startsWith("a_")?"gif":s),l(`${e}/icons/${t}/${i}`,{format:s,size:n})),AppIcon:(t,i,{format:s="webp",size:n}={})=>l(`${e}/app-icons/${t}/${i}`,{size:n,format:s}),AppAsset:(t,i,{format:s="webp",size:n}={})=>l(`${e}/app-assets/${t}/${i}`,{size:n,format:s}),GDMIcon:(t,i,s="webp",n)=>l(`${e}/channel-icons/${t}/${i}`,{size:n,format:s}),Splash:(t,i,s="webp",n)=>l(`${e}/splashes/${t}/${i}`,{size:n,format:s}),DiscoverySplash:(t,i,s="webp",n)=>l(`${e}/discovery-splashes/${t}/${i}`,{size:n,format:s}),TeamIcon:(t,i,{format:s="webp",size:n}={})=>l(`${e}/team-icons/${t}/${i}`,{size:n,format:s})}),invite:(e,t)=>`${e}/${t}`,botGateway:"/gateway/bot"},t.Status={READY:0,CONNECTING:1,RECONNECTING:2,IDLE:3,NEARLY:4,DISCONNECTED:5,WAITING_FOR_GUILDS:6,IDENTIFYING:7,RESUMING:8},t.VoiceStatus={CONNECTED:0,CONNECTING:1,AUTHENTICATING:2,RECONNECTING:3,DISCONNECTED:4},t.OPCodes={DISPATCH:0,HEARTBEAT:1,IDENTIFY:2,STATUS_UPDATE:3,VOICE_STATE_UPDATE:4,VOICE_GUILD_PING:5,RESUME:6,RECONNECT:7,REQUEST_GUILD_MEMBERS:8,INVALID_SESSION:9,HELLO:10,HEARTBEAT_ACK:11},t.VoiceOPCodes={IDENTIFY:0,SELECT_PROTOCOL:1,READY:2,HEARTBEAT:3,SESSION_DESCRIPTION:4,SPEAKING:5,HELLO:8,CLIENT_CONNECT:12,CLIENT_DISCONNECT:13},t.Events={RATE_LIMIT:"rateLimit",CLIENT_READY:"ready",GUILD_CREATE:"guildCreate",GUILD_DELETE:"guildDelete",GUILD_UPDATE:"guildUpdate",GUILD_UNAVAILABLE:"guildUnavailable",GUILD_AVAILABLE:"guildAvailable",GUILD_MEMBER_ADD:"guildMemberAdd",GUILD_MEMBER_REMOVE:"guildMemberRemove",GUILD_MEMBER_UPDATE:"guildMemberUpdate",GUILD_MEMBER_AVAILABLE:"guildMemberAvailable",GUILD_MEMBER_SPEAKING:"guildMemberSpeaking",GUILD_MEMBERS_CHUNK:"guildMembersChunk",GUILD_INTEGRATIONS_UPDATE:"guildIntegrationsUpdate",GUILD_ROLE_CREATE:"roleCreate",GUILD_ROLE_DELETE:"roleDelete",INVITE_CREATE:"inviteCreate",INVITE_DELETE:"inviteDelete",GUILD_ROLE_UPDATE:"roleUpdate",GUILD_EMOJI_CREATE:"emojiCreate",GUILD_EMOJI_DELETE:"emojiDelete",GUILD_EMOJI_UPDATE:"emojiUpdate",GUILD_BAN_ADD:"guildBanAdd",GUILD_BAN_REMOVE:"guildBanRemove",CHANNEL_CREATE:"channelCreate",CHANNEL_DELETE:"channelDelete",CHANNEL_UPDATE:"channelUpdate",CHANNEL_PINS_UPDATE:"channelPinsUpdate",MESSAGE_CREATE:"message",MESSAGE_DELETE:"messageDelete",MESSAGE_UPDATE:"messageUpdate",MESSAGE_BULK_DELETE:"messageDeleteBulk",MESSAGE_REACTION_ADD:"messageReactionAdd",MESSAGE_REACTION_REMOVE:"messageReactionRemove",MESSAGE_REACTION_REMOVE_ALL:"messageReactionRemoveAll",MESSAGE_REACTION_REMOVE_EMOJI:"messageReactionRemoveEmoji",USER_UPDATE:"userUpdate",PRESENCE_UPDATE:"presenceUpdate",VOICE_SERVER_UPDATE:"voiceServerUpdate",VOICE_STATE_UPDATE:"voiceStateUpdate",VOICE_BROADCAST_SUBSCRIBE:"subscribe",VOICE_BROADCAST_UNSUBSCRIBE:"unsubscribe",TYPING_START:"typingStart",TYPING_STOP:"typingStop",WEBHOOKS_UPDATE:"webhookUpdate",ERROR:"error",WARN:"warn",DEBUG:"debug",SHARD_DISCONNECT:"shardDisconnect",SHARD_ERROR:"shardError",SHARD_RECONNECTING:"shardReconnecting",SHARD_READY:"shardReady",SHARD_RESUME:"shardResume",INVALIDATED:"invalidated",RAW:"raw"},t.ShardEvents={CLOSE:"close",DESTROYED:"destroyed",INVALID_SESSION:"invalidSession",READY:"ready",RESUMED:"resumed",ALL_READY:"allReady"},t.PartialTypes=h(["USER","CHANNEL","GUILD_MEMBER","MESSAGE","REACTION"]),t.WSEvents=h(["READY","RESUMED","GUILD_CREATE","GUILD_DELETE","GUILD_UPDATE","INVITE_CREATE","INVITE_DELETE","GUILD_MEMBER_ADD","GUILD_MEMBER_REMOVE","GUILD_MEMBER_UPDATE","GUILD_MEMBERS_CHUNK","GUILD_INTEGRATIONS_UPDATE","GUILD_ROLE_CREATE","GUILD_ROLE_DELETE","GUILD_ROLE_UPDATE","GUILD_BAN_ADD","GUILD_BAN_REMOVE","GUILD_EMOJIS_UPDATE","CHANNEL_CREATE","CHANNEL_DELETE","CHANNEL_UPDATE","CHANNEL_PINS_UPDATE","MESSAGE_CREATE","MESSAGE_DELETE","MESSAGE_UPDATE","MESSAGE_DELETE_BULK","MESSAGE_REACTION_ADD","MESSAGE_REACTION_REMOVE","MESSAGE_REACTION_REMOVE_ALL","MESSAGE_REACTION_REMOVE_EMOJI","USER_UPDATE","PRESENCE_UPDATE","TYPING_START","VOICE_STATE_UPDATE","VOICE_SERVER_UPDATE","WEBHOOKS_UPDATE"]),t.MessageTypes=["DEFAULT","RECIPIENT_ADD","RECIPIENT_REMOVE","CALL","CHANNEL_NAME_CHANGE","CHANNEL_ICON_CHANGE","PINS_ADD","GUILD_MEMBER_JOIN","USER_PREMIUM_GUILD_SUBSCRIPTION","USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1","USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2","USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3","CHANNEL_FOLLOW_ADD",null,"GUILD_DISCOVERY_DISQUALIFIED","GUILD_DISCOVERY_REQUALIFIED"],t.ActivityTypes=["PLAYING","STREAMING","LISTENING","WATCHING","CUSTOM_STATUS"],t.ChannelTypes={TEXT:0,DM:1,VOICE:2,GROUP:3,CATEGORY:4,NEWS:5,STORE:6},t.ClientApplicationAssetTypes={SMALL:1,BIG:2},t.Colors={DEFAULT:0,WHITE:16777215,AQUA:1752220,GREEN:3066993,BLUE:3447003,YELLOW:16776960,PURPLE:10181046,LUMINOUS_VIVID_PINK:15277667,GOLD:15844367,ORANGE:15105570,RED:15158332,GREY:9807270,NAVY:3426654,DARK_AQUA:1146986,DARK_GREEN:2067276,DARK_BLUE:2123412,DARK_PURPLE:7419530,DARK_VIVID_PINK:11342935,DARK_GOLD:12745742,DARK_ORANGE:11027200,DARK_RED:10038562,DARK_GREY:9936031,DARKER_GREY:8359053,LIGHT_GREY:12370112,DARK_NAVY:2899536,BLURPLE:7506394,GREYPLE:10070709,DARK_BUT_NOT_BLACK:2895667,NOT_QUITE_BLACK:2303786},t.ExplicitContentFilterLevels=["DISABLED","MEMBERS_WITHOUT_ROLES","ALL_MEMBERS"],t.VerificationLevels=["NONE","LOW","MEDIUM","HIGH","VERY_HIGH"],t.APIErrors={UNKNOWN_ACCOUNT:10001,UNKNOWN_APPLICATION:10002,UNKNOWN_CHANNEL:10003,UNKNOWN_GUILD:10004,UNKNOWN_INTEGRATION:10005,UNKNOWN_INVITE:10006,UNKNOWN_MEMBER:10007,UNKNOWN_MESSAGE:10008,UNKNOWN_OVERWRITE:10009,UNKNOWN_PROVIDER:10010,UNKNOWN_ROLE:10011,UNKNOWN_TOKEN:10012,UNKNOWN_USER:10013,UNKNOWN_EMOJI:10014,UNKNOWN_WEBHOOK:10015,BOT_PROHIBITED_ENDPOINT:20001,BOT_ONLY_ENDPOINT:20002,MAXIMUM_GUILDS:30001,MAXIMUM_FRIENDS:30002,MAXIMUM_PINS:30003,MAXIMUM_ROLES:30005,MAXIMUM_REACTIONS:30010,MAXIMUM_CHANNELS:30013,MAXIMUM_INVITES:30016,UNAUTHORIZED:40001,USER_BANNED:40007,MISSING_ACCESS:50001,INVALID_ACCOUNT_TYPE:50002,CANNOT_EXECUTE_ON_DM:50003,EMBED_DISABLED:50004,CANNOT_EDIT_MESSAGE_BY_OTHER:50005,CANNOT_SEND_EMPTY_MESSAGE:50006,CANNOT_MESSAGE_USER:50007,CANNOT_SEND_MESSAGES_IN_VOICE_CHANNEL:50008,CHANNEL_VERIFICATION_LEVEL_TOO_HIGH:50009,OAUTH2_APPLICATION_BOT_ABSENT:50010,MAXIMUM_OAUTH2_APPLICATIONS:50011,INVALID_OAUTH_STATE:50012,MISSING_PERMISSIONS:50013,INVALID_AUTHENTICATION_TOKEN:50014,NOTE_TOO_LONG:50015,INVALID_BULK_DELETE_QUANTITY:50016,CANNOT_PIN_MESSAGE_IN_OTHER_CHANNEL:50019,INVALID_OR_TAKEN_INVITE_CODE:50020,CANNOT_EXECUTE_ON_SYSTEM_MESSAGE:50021,INVALID_OAUTH_TOKEN:50025,BULK_DELETE_MESSAGE_TOO_OLD:50034,INVALID_FORM_BODY:50035,INVITE_ACCEPTED_TO_GUILD_NOT_CONTAINING_BOT:50036,INVALID_API_VERSION:50041,REACTION_BLOCKED:90001,RESOURCE_OVERLOADED:13e4},t.DefaultMessageNotifications=["ALL","MENTIONS"],t.MembershipStates=[null,"INVITED","ACCEPTED"],t.WebhookTypes=[null,"Incoming","Channel Follower"]}).call(this,i(14))},function(e,t,i){"use strict";const{PartialTypes:s}=i(0);e.exports=class GenericAction{constructor(e){this.client=e}handle(e){return e}getPayload(e,t,i,s,n){const r=t.cache.get(i);return!r&&this.client.options.partials.includes(s)?t.add(e,n):r}getChannel(e){const t=e.channel_id||e.id;return e.channel||this.getPayload({id:t,guild_id:e.guild_id,recipients:[e.author||{id:e.user_id}]},this.client.channels,t,s.CHANNEL)}getMessage(e,t,i){const n=e.message_id||e.id;return e.message||this.getPayload({id:n,channel_id:t.id,guild_id:e.guild_id||(t.guild?t.guild.id:null)},t.messages,n,s.MESSAGE,i)}getReaction(e,t,i){const n=e.emoji.id||decodeURIComponent(e.emoji.name);return this.getPayload({emoji:e.emoji,count:t.partial?null:0,me:!!i&&i.id===this.client.user.id},t.reactions,n,s.REACTION)}getMember(e,t){const i=e.user.id;return this.getPayload({user:{id:i}},t.members,i,s.GUILD_MEMBER)}getUser(e){const t=e.user_id;return e.user||this.getPayload({id:t},this.client.users,t,s.USER)}}},function(e,t,i){"use strict";const s=i(99),n=i(4);e.exports=class Collection extends s{toJSON(){return this.map(e=>"function"==typeof e.toJSON?e.toJSON():n.flatten(e))}}},function(e,t,i){"use strict";e.exports=i(59),e.exports.Messages=i(98)},function(e,t,i){"use strict";const{parse:s}=i(35),n=i(36),{Colors:r,DefaultOptions:o,Endpoints:a}=i(0),{Error:c,RangeError:l,TypeError:h}=i(3),u=e=>"object"==typeof e&&null!==e;class Util{constructor(){throw new Error(`The ${this.constructor.name} class may not be instantiated.`)}static flatten(e,...t){if(!u(e))return e;t=Object.assign(...Object.keys(e).filter(e=>!e.startsWith("_")).map(e=>({[e]:!0})),...t);const s={};for(let[n,r]of Object.entries(t)){if(!r)continue;r=!0===r?n:r;const t=e[n],o=u(t),a=o&&"function"==typeof t.valueOf?t.valueOf():null;t instanceof i(2)?s[r]=Array.from(t.keys()):a instanceof i(2)?s[r]=Array.from(a.keys()):Array.isArray(t)?s[r]=t.map(e=>Util.flatten(e)):"object"!=typeof a?s[r]=a:o||(s[r]=t)}return s}static splitMessage(e,{maxLength:t=2e3,char:i="\n",prepend:s="",append:n=""}={}){if((e=Util.resolveString(e)).length<=t)return[e];const r=e.split(i);if(r.some(e=>e.length>t))throw new l("SPLIT_MAX_LEN");const o=[];let a="";for(const e of r)a&&(a+i+e+n).length>t&&(o.push(a+n),a=s),a+=(a&&a!==s?i:"")+e;return o.concat(a).filter(e=>e)}static escapeMarkdown(e,{codeBlock:t=!0,inlineCode:i=!0,bold:s=!0,italic:n=!0,underline:r=!0,strikethrough:o=!0,spoiler:a=!0,codeBlockContent:c=!0,inlineCodeContent:l=!0}={}){return c?l?(i&&(e=Util.escapeInlineCode(e)),t&&(e=Util.escapeCodeBlock(e)),n&&(e=Util.escapeItalic(e)),s&&(e=Util.escapeBold(e)),r&&(e=Util.escapeUnderline(e)),o&&(e=Util.escapeStrikethrough(e)),a&&(e=Util.escapeSpoiler(e)),e):e.split(/(?<=^|[^`])`(?=[^`]|$)/g).map((e,i,c)=>i%2&&i!==c.length-1?e:Util.escapeMarkdown(e,{codeBlock:t,bold:s,italic:n,underline:r,strikethrough:o,spoiler:a})).join(i?"\\`":"`"):e.split("```").map((e,t,c)=>t%2&&t!==c.length-1?e:Util.escapeMarkdown(e,{inlineCode:i,bold:s,italic:n,underline:r,strikethrough:o,spoiler:a,inlineCodeContent:l})).join(t?"\\`\\`\\`":"```")}static escapeCodeBlock(e){return e.replace(/```/g,"\\`\\`\\`")}static escapeInlineCode(e){return e.replace(/(?<=^|[^`])`(?=[^`]|$)/g,"\\`")}static escapeItalic(e){let t=0;return e=e.replace(/(?<=^|[^*])\*([^*]|\*\*|$)/g,(e,i)=>"**"===i?++t%2?`\\*${i}`:`${i}\\*`:`\\*${i}`),t=0,e.replace(/(?<=^|[^_])_([^_]|__|$)/g,(e,i)=>"__"===i?++t%2?`\\_${i}`:`${i}\\_`:`\\_${i}`)}static escapeBold(e){let t=0;return e.replace(/\*\*(\*)?/g,(e,i)=>i?++t%2?`${i}\\*\\*`:`\\*\\*${i}`:"\\*\\*")}static escapeUnderline(e){let t=0;return e.replace(/__(_)?/g,(e,i)=>i?++t%2?`${i}\\_\\_`:`\\_\\_${i}`:"\\_\\_")}static escapeStrikethrough(e){return e.replace(/~~/g,"\\~\\~")}static escapeSpoiler(e){return e.replace(/\|\|/g,"\\|\\|")}static fetchRecommendedShards(e,t=1e3){if(!e)throw new c("TOKEN_MISSING");return n(`${o.http.api}/v${o.http.version}${a.botGateway}`,{method:"GET",headers:{Authorization:`Bot ${e.replace(/^Bot\s*/i,"")}`}}).then(e=>{if(e.ok)return e.json();throw e}).then(e=>e.shards*(1e3/t))}static parseEmoji(e){if(e.includes("%")&&(e=decodeURIComponent(e)),!e.includes(":"))return{animated:!1,name:e,id:null};const t=e.match(/<?(?:(a):)?(\w{2,32}):(\d{17,19})?>?/);return t?{animated:Boolean(t[1]),name:t[2],id:t[3]||null}:null}static cloneObject(e){return Object.assign(Object.create(e),e)}static mergeDefault(e,t){if(!t)return e;for(const n in e)i=t,s=n,Object.prototype.hasOwnProperty.call(i,s)&&void 0!==t[n]?t[n]===Object(t[n])&&(t[n]=Util.mergeDefault(e[n],t[n])):t[n]=e[n];var i,s;return t}static convertToBuffer(e){return"string"==typeof e&&(e=Util.str2ab(e)),Buffer.from(e)}static str2ab(e){const t=new ArrayBuffer(2*e.length),i=new Uint16Array(t);for(var s=0,n=e.length;s<n;s++)i[s]=e.charCodeAt(s);return t}static makeError(e){const t=new Error(e.message);return t.name=e.name,t.stack=e.stack,t}static makePlainError(e){return{name:e.name,message:e.message,stack:e.stack}}static moveElementInArray(e,t,i,s=!1){const n=e.indexOf(t);if((i=(s?n:0)+i)>-1&&i<e.length){const t=e.splice(n,1)[0];e.splice(i,0,t)}return e.indexOf(t)}static resolveString(e){return"string"==typeof e?e:Array.isArray(e)?e.join("\n"):String(e)}static resolveColor(e){if("string"==typeof e){if("RANDOM"===e)return Math.floor(16777216*Math.random());if("DEFAULT"===e)return 0;e=r[e]||parseInt(e.replace("#",""),16)}else Array.isArray(e)&&(e=(e[0]<<16)+(e[1]<<8)+e[2]);if(e<0||e>16777215)throw new l("COLOR_RANGE");if(e&&isNaN(e))throw new h("COLOR_CONVERT");return e}static discordSort(e){return e.sorted((e,t)=>e.rawPosition-t.rawPosition||parseInt(t.id.slice(0,-10))-parseInt(e.id.slice(0,-10))||parseInt(t.id.slice(10))-parseInt(e.id.slice(10)))}static setPosition(e,t,i,s,n,r){let o=s.array();return Util.moveElementInArray(o,e,t,i),o=o.map((e,t)=>({id:e.id,position:t})),n.patch({data:o,reason:r}).then(()=>o)}static basename(e,t){let i=s(e);return t&&i.ext.startsWith(t)?i.name:i.base.split("?")[0]}static idToBinary(e){let t="",i=parseInt(e.slice(0,-10))||0,s=parseInt(e.slice(-10));for(;s>0||i>0;)t=String(1&s)+t,s=Math.floor(s/2),i>0&&(s+=i%2*5e9,i=Math.floor(i/2));return t}static binaryToID(e){let t="";for(;e.length>50;){const i=parseInt(e.slice(0,-32),2),s=parseInt((i%10).toString(2)+e.slice(-32),2);t=(s%10).toString()+t,e=Math.floor(i/10).toString(2)+Math.floor(s/10).toString(2).padStart(32,"0")}for(e=parseInt(e,2);e>0;)t=(e%10).toString()+t,e=Math.floor(e/10);return t}static removeMentions(e){return e.replace(/@/g,"@​")}static cleanContent(e,t){return e=e.replace(/<@!?[0-9]+>/g,e=>{const i=e.replace(/<|!|>|@/g,"");if("dm"===t.channel.type){const s=t.client.users.cache.get(i);return s?`@${s.username}`:e}const s=t.channel.guild.members.cache.get(i);if(s)return`@${s.displayName}`;{const s=t.client.users.cache.get(i);return s?`@${s.username}`:e}}).replace(/<#[0-9]+>/g,e=>{const i=t.client.channels.cache.get(e.replace(/<|#|>/g,""));return i?`#${i.name}`:e}).replace(/<@&[0-9]+>/g,e=>{if("dm"===t.channel.type)return e;const i=t.guild.roles.cache.get(e.replace(/<|@|>|&/g,""));return i?`@${i.name}`:e}),"everyone"===t.client.options.disableMentions&&(e=e.replace(/@([^<>@ ]*)/gmus,(e,t)=>t.match(/^[&!]?\d+$/)?`@${t}`:`@​${t}`)),"all"===t.client.options.disableMentions?Util.removeMentions(e):e}static cleanCodeBlockContent(e){return e.replace(/```/g,"`​``")}static delayFor(e){return new Promise(t=>{setTimeout(t,e)})}}e.exports=Util},function(e,t,i){"use strict";const s=i(4);e.exports=class Base{constructor(e){Object.defineProperty(this,"client",{value:e})}_clone(){return Object.assign(Object.create(this),this)}_patch(e){return e}_update(e){const t=this._clone();return this._patch(e),t}toJSON(...e){return s.flatten(this,...e)}valueOf(){return this.id}}},function(e,t,i){"use strict";const s=i(2);let n;e.exports=class BaseManager{constructor(e,t,r,o=s,...a){if(n||(n=i(23)),Object.defineProperty(this,"holds",{value:n.get(r.name)||r}),Object.defineProperty(this,"client",{value:e}),this.cacheType=o,this.cache=new o(...a),t)for(const e of t)this.add(e)}add(e,t=!0,{id:i,extras:s=[]}={}){const n=this.cache.get(i||e.id);if(n&&n._patch&&t&&n._patch(e),n)return n;const r=this.holds?new this.holds(this.client,e,...s):e;return t&&this.cache.set(i||r.id,r),r}resolve(e){return e instanceof this.holds?e:"string"==typeof e&&this.cache.get(e)||null}resolveID(e){return e instanceof this.holds?e.id:"string"==typeof e?e:null}valueOf(){return this.cache}}},function(e,t,i){"use strict";const s=i(4);let n=0;e.exports=class SnowflakeUtil{constructor(){throw new Error(`The ${this.constructor.name} class may not be instantiated.`)}static generate(e=Date.now()){if(e instanceof Date&&(e=e.getTime()),"number"!=typeof e||isNaN(e))throw new TypeError(`"timestamp" argument must be a number (received ${isNaN(e)?"NaN":typeof e})`);n>=4095&&(n=0);const t=`${(e-14200704e5).toString(2).padStart(42,"0")}0000100000${(n++).toString(2).padStart(12,"0")}`;return s.binaryToID(t)}static deconstruct(e){const t=s.idToBinary(e).toString(2).padStart(64,"0"),i={timestamp:parseInt(t.substring(0,42),2)+14200704e5,workerID:parseInt(t.substring(42,47),2),processID:parseInt(t.substring(47,52),2),increment:parseInt(t.substring(52,64),2),binary:t};return Object.defineProperty(i,"date",{get:function(){return new Date(this.timestamp)},enumerable:!0}),i}}},function(e,t,i){"use strict";const s=i(11);class Permissions extends s{any(e,t=!0){return t&&super.has(this.constructor.FLAGS.ADMINISTRATOR)||super.any(e)}has(e,t=!0){return t&&super.has(this.constructor.FLAGS.ADMINISTRATOR)||super.has(e)}}Permissions.FLAGS={CREATE_INSTANT_INVITE:1,KICK_MEMBERS:2,BAN_MEMBERS:4,ADMINISTRATOR:8,MANAGE_CHANNELS:16,MANAGE_GUILD:32,ADD_REACTIONS:64,VIEW_AUDIT_LOG:128,PRIORITY_SPEAKER:256,STREAM:512,VIEW_CHANNEL:1024,SEND_MESSAGES:2048,SEND_TTS_MESSAGES:4096,MANAGE_MESSAGES:8192,EMBED_LINKS:16384,ATTACH_FILES:32768,READ_MESSAGE_HISTORY:65536,MENTION_EVERYONE:1<<17,USE_EXTERNAL_EMOJIS:1<<18,VIEW_GUILD_INSIGHTS:1<<19,CONNECT:1<<20,SPEAK:1<<21,MUTE_MEMBERS:1<<22,DEAFEN_MEMBERS:1<<23,MOVE_MEMBERS:1<<24,USE_VAD:1<<25,CHANGE_NICKNAME:1<<26,MANAGE_NICKNAMES:1<<27,MANAGE_ROLES:1<<28,MANAGE_WEBHOOKS:1<<29,MANAGE_EMOJIS:1<<30},Permissions.ALL=Object.values(Permissions.FLAGS).reduce((e,t)=>e|t,0),Permissions.DEFAULT=104324673,e.exports=Permissions},function(e,t,i){"use strict";const s=i(35),n=i(35),r=i(119),o=i(36),{Error:a,TypeError:c}=i(3),{browser:l}=i(0),h=i(4);class DataResolver{constructor(){throw new Error(`The ${this.constructor.name} class may not be instantiated.`)}static resolveInviteCode(e){const t=/discord(?:app\.com\/invite|\.gg(?:\/invite)?)\/([\w-]{2,255})/i.exec(e);return t&&t[1]?t[1]:e}static async resolveImage(e){if(!e)return null;if("string"==typeof e&&e.startsWith("data:"))return e;const t=await this.resolveFileAsBuffer(e);return DataResolver.resolveBase64(t)}static resolveBase64(e){return Buffer.isBuffer(e)?`data:image/jpg;base64,${e.toString("base64")}`:e}static async resolveFile(e){if(!l&&Buffer.isBuffer(e))return e;if(l&&e instanceof ArrayBuffer)return h.convertToBuffer(e);if(e instanceof r.Readable)return e;if("string"==typeof e){if(/^https?:\/\//.test(e)){const t=await o(e);return l?t.blob():t.body}if(!l)return new Promise((t,i)=>{const r=n.resolve(e);s.stat(r,(e,n)=>e?i(e):n.isFile()?t(s.createReadStream(r)):i(new a("FILE_NOT_FOUND",r)))})}throw new c("REQ_RESOURCE_TYPE")}static async resolveFileAsBuffer(e){const t=await this.resolveFile(e);if(Buffer.isBuffer(t))return t;const i=[];for await(const e of t)i.push(e);return Buffer.concat(i)}}e.exports=DataResolver},function(e,t,i){"use strict";const s=i(5),{Presence:n}=i(13),r=i(19),o=i(27),a=i(33),{Error:c}=i(3),l=i(72),h=i(8);class GuildMember extends s{constructor(e,t,i){super(e),this.guild=i,t.user&&(this.user=e.users.add(t.user,!0)),this.joinedTimestamp=null,this.lastMessageID=null,this.lastMessageChannelID=null,this.premiumSinceTimestamp=null,this.deleted=!1,this._roles=[],t&&this._patch(t)}_patch(e){void 0!==e.nick&&(this.nickname=e.nick),e.joined_at&&(this.joinedTimestamp=new Date(e.joined_at).getTime()),e.premium_since&&(this.premiumSinceTimestamp=new Date(e.premium_since).getTime()),e.user&&(this.user=this.guild.client.users.add(e.user)),e.roles&&(this._roles=e.roles)}_clone(){const e=super._clone();return e._roles=this._roles.slice(),e}get partial(){return!this.joinedTimestamp}get roles(){return new l(this)}get lastMessage(){const e=this.guild.channels.cache.get(this.lastMessageChannelID);return e&&e.messages.cache.get(this.lastMessageID)||null}get voice(){return this.guild.voiceStates.cache.get(this.id)||new o(this.guild,{user_id:this.id})}get joinedAt(){return this.joinedTimestamp?new Date(this.joinedTimestamp):null}get premiumSince(){return this.premiumSinceTimestamp?new Date(this.premiumSinceTimestamp):null}get presence(){return this.guild.presences.cache.get(this.id)||new n(this.client,{user:{id:this.id},guild:this.guild})}get displayColor(){const e=this.roles.color;return e&&e.color||0}get displayHexColor(){const e=this.roles.color;return e&&e.hexColor||"#000000"}get id(){return this.user.id}get displayName(){return this.nickname||this.user.username}get permissions(){return this.user.id===this.guild.ownerID?new h(h.ALL).freeze():new h(this.roles.cache.map(e=>e.permissions)).freeze()}get manageable(){if(this.user.id===this.guild.ownerID)return!1;if(this.user.id===this.client.user.id)return!1;if(this.client.user.id===this.guild.ownerID)return!0;if(!this.guild.me)throw new c("GUILD_UNCACHED_ME");return this.guild.me.roles.highest.comparePositionTo(this.roles.highest)>0}get kickable(){return this.manageable&&this.guild.me.permissions.has(h.FLAGS.KICK_MEMBERS)}get bannable(){return this.manageable&&this.guild.me.permissions.has(h.FLAGS.BAN_MEMBERS)}permissionsIn(e){if(!(e=this.guild.channels.resolve(e)))throw new c("GUILD_CHANNEL_RESOLVE");return e.memberPermissions(this)}hasPermission(e,{checkAdmin:t=!0,checkOwner:i=!0}={}){return!(!i||this.user.id!==this.guild.ownerID)||this.roles.cache.some(i=>i.permissions.has(e,t))}async edit(e,t){if(e.channel){if(e.channel=this.guild.channels.resolve(e.channel),!e.channel||"voice"!==e.channel.type)throw new c("GUILD_VOICE_CHANNEL_RESOLVE");e.channel_id=e.channel.id,e.channel=void 0}else null===e.channel&&(e.channel_id=null,e.channel=void 0);e.roles&&(e.roles=e.roles.map(e=>e instanceof r?e.id:e));let i=this.client.api.guilds(this.guild.id);if(this.user.id===this.client.user.id){const t=Object.keys(e);i=1===t.length&&"nick"===t[0]?i.members("@me").nick:i.members(this.id)}else i=i.members(this.id);await i.patch({data:e,reason:t});const s=this._clone();return e.user=this.user,s._patch(e),s}setNickname(e,t){return this.edit({nick:e},t)}createDM(){return this.user.createDM()}deleteDM(){return this.user.deleteDM()}kick(e){return this.client.api.guilds(this.guild.id).members(this.user.id).delete({reason:e}).then(()=>this)}ban(e){return this.guild.members.ban(this,e)}fetch(){return this.guild.members.fetch(this.id,!0)}toString(){return`<@${this.nickname?"!":""}${this.user.id}>`}toJSON(){return super.toJSON({guild:"guildID",user:"userID",displayName:!0,speaking:!1,lastMessage:!1,lastMessageID:!1,roles:!0})}send(){}}a.applyToClass(GuildMember),e.exports=GuildMember},function(e,t,i){"use strict";const{RangeError:s}=i(3);class BitField{constructor(e){this.bitfield=this.constructor.resolve(e)}any(e){return 0!=(this.bitfield&this.constructor.resolve(e))}equals(e){return this.bitfield===this.constructor.resolve(e)}has(e){return Array.isArray(e)?e.every(e=>this.has(e)):(e=this.constructor.resolve(e),(this.bitfield&e)===e)}missing(e,...t){return Array.isArray(e)||(e=new this.constructor(e).toArray(!1)),e.filter(e=>!this.has(e,...t))}freeze(){return Object.freeze(this)}add(...e){let t=0;for(const i of e)t|=this.constructor.resolve(i);return Object.isFrozen(this)?new this.constructor(this.bitfield|t):(this.bitfield|=t,this)}remove(...e){let t=0;for(const i of e)t|=this.constructor.resolve(i);return Object.isFrozen(this)?new this.constructor(this.bitfield&~t):(this.bitfield&=~t,this)}serialize(...e){const t={};for(const[i,s]of Object.entries(this.constructor.FLAGS))t[i]=this.has(s,...e);return t}toArray(...e){return Object.keys(this.constructor.FLAGS).filter(t=>this.has(t,...e))}toJSON(){return this.bitfield}valueOf(){return this.bitfield}*[Symbol.iterator](){yield*this.toArray()}static resolve(e=0){if("number"==typeof e&&e>=0)return e;if(e instanceof BitField)return e.bitfield;if(Array.isArray(e))return e.map(e=>this.resolve(e)).reduce((e,t)=>e|t,0);if("string"==typeof e&&void 0!==this.FLAGS[e])return this.FLAGS[e];throw new s("BITFIELD_INVALID")}}BitField.FLAGS={},e.exports=BitField},function(e,t,i){"use strict";const s=i(30),n=i(16),{WebhookTypes:r}=i(0),o=i(9),a=i(7);class Webhook{constructor(e,t){Object.defineProperty(this,"client",{value:e}),t&&this._patch(t)}_patch(e){this.name=e.name,Object.defineProperty(this,"token",{value:e.token||null,writable:!0,configurable:!0}),this.avatar=e.avatar,this.id=e.id,this.type=r[e.type],this.guildID=e.guild_id,this.channelID=e.channel_id,e.user?this.owner=this.client.users?this.client.users.cache.get(e.user.id):e.user:this.owner=null}async send(e,t){let i;if(e instanceof s)i=e.resolveData();else if(i=s.create(this,e,t).resolveData(),Array.isArray(i.data.content))return Promise.all(i.split().map(this.send.bind(this)));const{data:n,files:r}=await i.resolveFiles();return this.client.api.webhooks(this.id,this.token).post({data:n,files:r,query:{wait:!0},auth:!1}).then(e=>{const t=this.client.channels?this.client.channels.cache.get(e.channel_id):void 0;return t?t.messages.add(e,!1):e})}sendSlackMessage(e){return this.client.api.webhooks(this.id,this.token).slack.post({query:{wait:!0},auth:!1,data:e}).then(e=>"ok"===e.toString())}async edit({name:e=this.name,avatar:t,channel:i},s){t&&"string"==typeof t&&!t.startsWith("data:")&&(t=await o.resolveImage(t)),i&&(i=i instanceof n?i.id:i);const r=await this.client.api.webhooks(this.id,i?void 0:this.token).patch({data:{name:e,avatar:t,channel_id:i},reason:s});return this.name=r.name,this.avatar=r.avatar,this.channelID=r.channel_id,this}delete(e){return this.client.api.webhooks(this.id,this.token).delete({reason:e})}get createdTimestamp(){return a.deconstruct(this.id).timestamp}get createdAt(){return new Date(this.createdTimestamp)}get url(){return this.client.options.http.api+this.client.api.webhooks(this.id,this.token)}avatarURL({format:e,size:t}={}){return this.avatar?this.client.rest.cdn.Avatar(this.id,this.avatar,e,t):null}static applyToClass(e){for(const t of["send","sendSlackMessage","edit","delete","createdTimestamp","createdAt","url"])Object.defineProperty(e.prototype,t,Object.getOwnPropertyDescriptor(Webhook.prototype,t))}}e.exports=Webhook},function(e,t,i){"use strict";const s=i(29),n=i(70),{ActivityTypes:r}=i(0),o=i(4);class Activity{constructor(e,t){Object.defineProperty(this,"presence",{value:e}),this.name=t.name,this.type=r[t.type],this.url=t.url||null,this.details=t.details||null,this.state=t.state||null,this.applicationID=t.application_id||null,this.timestamps=t.timestamps?{start:t.timestamps.start?new Date(Number(t.timestamps.start)):null,end:t.timestamps.end?new Date(Number(t.timestamps.end)):null}:null,this.party=t.party||null,this.assets=t.assets?new RichPresenceAssets(this,t.assets):null,this.syncID=t.sync_id,this.flags=new n(t.flags).freeze(),this.emoji=t.emoji?new s(e.client,t.emoji):null,this.createdTimestamp=new Date(t.created_at).getTime()}equals(e){return this===e||e&&this.name===e.name&&this.type===e.type&&this.url===e.url}get createdAt(){return new Date(this.createdTimestamp)}toString(){return this.name}_clone(){return Object.assign(Object.create(this),this)}}class RichPresenceAssets{constructor(e,t){Object.defineProperty(this,"activity",{value:e}),this.largeText=t.large_text||null,this.smallText=t.small_text||null,this.largeImage=t.large_image||null,this.smallImage=t.small_image||null}smallImageURL({format:e,size:t}={}){return this.smallImage?this.activity.presence.client.rest.cdn.AppAsset(this.activity.applicationID,this.smallImage,{format:e,size:t}):null}largeImageURL({format:e,size:t}={}){return this.largeImage?/^spotify:/.test(this.largeImage)?`https://i.scdn.co/image/${this.largeImage.slice(8)}`:/^twitch:/.test(this.largeImage)?`https://static-cdn.jtvnw.net/previews-ttv/live_user_${this.largeImage.slice(7)}.png`:this.activity.presence.client.rest.cdn.AppAsset(this.activity.applicationID,this.largeImage,{format:e,size:t}):null}}t.Presence=class Presence{constructor(e,t={}){Object.defineProperty(this,"client",{value:e}),this.userID=t.user.id,this.guild=t.guild||null,this.patch(t)}get user(){return this.client.users.cache.get(this.userID)||null}get member(){return this.guild.members.cache.get(this.userID)||null}patch(e){return this.status=e.status||this.status||"offline",e.activities?this.activities=e.activities.map(e=>new Activity(this,e)):e.activity||e.game?this.activities=[new Activity(this,e.game||e.activity)]:this.activities=[],this.clientStatus=e.client_status||null,this}_clone(){const e=Object.assign(Object.create(this),this);return this.activities&&(e.activities=this.activities.map(e=>e._clone())),e}equals(e){return this===e||e&&this.status===e.status&&this.activities.length===e.activities.length&&this.activities.every((t,i)=>t.equals(e.activities[i]))&&this.clientStatus.web===e.clientStatus.web&&this.clientStatus.mobile===e.clientStatus.mobile&&this.clientStatus.desktop===e.clientStatus.desktop}toJSON(){return o.flatten(this)}},t.Activity=Activity,t.RichPresenceAssets=RichPresenceAssets},function(e,t){var i,s,n=e.exports={};function r(){throw new Error("setTimeout has not been defined")}function o(){throw new Error("clearTimeout has not been defined")}function a(e){if(i===setTimeout)return setTimeout(e,0);if((i===r||!i)&&setTimeout)return i=setTimeout,setTimeout(e,0);try{return i(e,0)}catch(t){try{return i.call(null,e,0)}catch(t){return i.call(this,e,0)}}}!function(){try{i="function"==typeof setTimeout?setTimeout:r}catch(e){i=r}try{s="function"==typeof clearTimeout?clearTimeout:o}catch(e){s=o}}();var c,l=[],h=!1,u=-1;function d(){h&&c&&(h=!1,c.length?l=c.concat(l):u=-1,l.length&&p())}function p(){if(!h){var e=a(d);h=!0;for(var t=l.length;t;){for(c=l,l=[];++u<t;)c&&c[u].run();u=-1,t=l.length}c=null,h=!1,function(e){if(s===clearTimeout)return clearTimeout(e);if((s===o||!s)&&clearTimeout)return s=clearTimeout,clearTimeout(e);try{s(e)}catch(t){try{return s.call(null,e)}catch(t){return s.call(this,e)}}}(e)}}function f(e,t){this.fun=e,this.array=t}function m(){}n.nextTick=function(e){var t=new Array(arguments.length-1);if(arguments.length>1)for(var i=1;i<arguments.length;i++)t[i-1]=arguments[i];l.push(new f(e,t)),1!==l.length||h||a(p)},f.prototype.run=function(){this.fun.apply(null,this.array)},n.title="browser",n.browser=!0,n.env={},n.argv=[],n.version="",n.versions={},n.on=m,n.addListener=m,n.once=m,n.off=m,n.removeListener=m,n.removeAllListeners=m,n.emit=m,n.prependListener=m,n.prependOnceListener=m,n.listeners=function(e){return[]},n.binding=function(e){throw new Error("process.binding is not supported")},n.cwd=function(){return"/"},n.chdir=function(e){throw new Error("process.chdir is not supported")},n.umask=function(){return 0}},function(e,t,i){"use strict";var s,n="object"==typeof Reflect?Reflect:null,r=n&&"function"==typeof n.apply?n.apply:function(e,t,i){return Function.prototype.apply.call(e,t,i)};s=n&&"function"==typeof n.ownKeys?n.ownKeys:Object.getOwnPropertySymbols?function(e){return Object.getOwnPropertyNames(e).concat(Object.getOwnPropertySymbols(e))}:function(e){return Object.getOwnPropertyNames(e)};var o=Number.isNaN||function(e){return e!=e};function a(){a.init.call(this)}e.exports=a,a.EventEmitter=a,a.prototype._events=void 0,a.prototype._eventsCount=0,a.prototype._maxListeners=void 0;var c=10;function l(e){if("function"!=typeof e)throw new TypeError('The "listener" argument must be of type Function. Received type '+typeof e)}function h(e){return void 0===e._maxListeners?a.defaultMaxListeners:e._maxListeners}function u(e,t,i,s){var n,r,o,a;if(l(i),void 0===(r=e._events)?(r=e._events=Object.create(null),e._eventsCount=0):(void 0!==r.newListener&&(e.emit("newListener",t,i.listener?i.listener:i),r=e._events),o=r[t]),void 0===o)o=r[t]=i,++e._eventsCount;else if("function"==typeof o?o=r[t]=s?[i,o]:[o,i]:s?o.unshift(i):o.push(i),(n=h(e))>0&&o.length>n&&!o.warned){o.warned=!0;var c=new Error("Possible EventEmitter memory leak detected. "+o.length+" "+String(t)+" listeners added. Use emitter.setMaxListeners() to increase limit");c.name="MaxListenersExceededWarning",c.emitter=e,c.type=t,c.count=o.length,a=c,console&&console.warn&&console.warn(a)}return e}function d(){if(!this.fired)return this.target.removeListener(this.type,this.wrapFn),this.fired=!0,0===arguments.length?this.listener.call(this.target):this.listener.apply(this.target,arguments)}function p(e,t,i){var s={fired:!1,wrapFn:void 0,target:e,type:t,listener:i},n=d.bind(s);return n.listener=i,s.wrapFn=n,n}function f(e,t,i){var s=e._events;if(void 0===s)return[];var n=s[t];return void 0===n?[]:"function"==typeof n?i?[n.listener||n]:[n]:i?function(e){for(var t=new Array(e.length),i=0;i<t.length;++i)t[i]=e[i].listener||e[i];return t}(n):g(n,n.length)}function m(e){var t=this._events;if(void 0!==t){var i=t[e];if("function"==typeof i)return 1;if(void 0!==i)return i.length}return 0}function g(e,t){for(var i=new Array(t),s=0;s<t;++s)i[s]=e[s];return i}Object.defineProperty(a,"defaultMaxListeners",{enumerable:!0,get:function(){return c},set:function(e){if("number"!=typeof e||e<0||o(e))throw new RangeError('The value of "defaultMaxListeners" is out of range. It must be a non-negative number. Received '+e+".");c=e}}),a.init=function(){void 0!==this._events&&this._events!==Object.getPrototypeOf(this)._events||(this._events=Object.create(null),this._eventsCount=0),this._maxListeners=this._maxListeners||void 0},a.prototype.setMaxListeners=function(e){if("number"!=typeof e||e<0||o(e))throw new RangeError('The value of "n" is out of range. It must be a non-negative number. Received '+e+".");return this._maxListeners=e,this},a.prototype.getMaxListeners=function(){return h(this)},a.prototype.emit=function(e){for(var t=[],i=1;i<arguments.length;i++)t.push(arguments[i]);var s="error"===e,n=this._events;if(void 0!==n)s=s&&void 0===n.error;else if(!s)return!1;if(s){var o;if(t.length>0&&(o=t[0]),o instanceof Error)throw o;var a=new Error("Unhandled error."+(o?" ("+o.message+")":""));throw a.context=o,a}var c=n[e];if(void 0===c)return!1;if("function"==typeof c)r(c,this,t);else{var l=c.length,h=g(c,l);for(i=0;i<l;++i)r(h[i],this,t)}return!0},a.prototype.addListener=function(e,t){return u(this,e,t,!1)},a.prototype.on=a.prototype.addListener,a.prototype.prependListener=function(e,t){return u(this,e,t,!0)},a.prototype.once=function(e,t){return l(t),this.on(e,p(this,e,t)),this},a.prototype.prependOnceListener=function(e,t){return l(t),this.prependListener(e,p(this,e,t)),this},a.prototype.removeListener=function(e,t){var i,s,n,r,o;if(l(t),void 0===(s=this._events))return this;if(void 0===(i=s[e]))return this;if(i===t||i.listener===t)0==--this._eventsCount?this._events=Object.create(null):(delete s[e],s.removeListener&&this.emit("removeListener",e,i.listener||t));else if("function"!=typeof i){for(n=-1,r=i.length-1;r>=0;r--)if(i[r]===t||i[r].listener===t){o=i[r].listener,n=r;break}if(n<0)return this;0===n?i.shift():function(e,t){for(;t+1<e.length;t++)e[t]=e[t+1];e.pop()}(i,n),1===i.length&&(s[e]=i[0]),void 0!==s.removeListener&&this.emit("removeListener",e,o||t)}return this},a.prototype.off=a.prototype.removeListener,a.prototype.removeAllListeners=function(e){var t,i,s;if(void 0===(i=this._events))return this;if(void 0===i.removeListener)return 0===arguments.length?(this._events=Object.create(null),this._eventsCount=0):void 0!==i[e]&&(0==--this._eventsCount?this._events=Object.create(null):delete i[e]),this;if(0===arguments.length){var n,r=Object.keys(i);for(s=0;s<r.length;++s)"removeListener"!==(n=r[s])&&this.removeAllListeners(n);return this.removeAllListeners("removeListener"),this._events=Object.create(null),this._eventsCount=0,this}if("function"==typeof(t=i[e]))this.removeListener(e,t);else if(void 0!==t)for(s=t.length-1;s>=0;s--)this.removeListener(e,t[s]);return this},a.prototype.listeners=function(e){return f(this,e,!0)},a.prototype.rawListeners=function(e){return f(this,e,!1)},a.listenerCount=function(e,t){return"function"==typeof e.listenerCount?e.listenerCount(t):m.call(e,t)},a.prototype.listenerCount=m,a.prototype.eventNames=function(){return this._eventsCount>0?s(this._events):[]}},function(e,t,i){"use strict";const s=i(5),{ChannelTypes:n}=i(0),r=i(7);e.exports=class Channel extends s{constructor(e,t){super(e);const i=Object.keys(n)[t.type];this.type=i?i.toLowerCase():"unknown",this.deleted=!1,t&&this._patch(t)}_patch(e){this.id=e.id}get createdTimestamp(){return r.deconstruct(this.id).timestamp}get createdAt(){return new Date(this.createdTimestamp)}toString(){return`<#${this.id}>`}delete(){return this.client.api.channels(this.id).delete().then(()=>this)}fetch(){return this.client.channels.fetch(this.id,!0)}static create(e,t,s){const r=i(23);let o;if(t.guild_id||s){if(s=s||e.guilds.cache.get(t.guild_id)){switch(t.type){case n.TEXT:o=new(r.get("TextChannel"))(s,t);break;case n.VOICE:o=new(r.get("VoiceChannel"))(s,t);break;case n.CATEGORY:o=new(r.get("CategoryChannel"))(s,t);break;case n.NEWS:o=new(r.get("NewsChannel"))(s,t);break;case n.STORE:o=new(r.get("StoreChannel"))(s,t);break}o&&s.channels.cache.set(o.id,o)}}else if(t.recipients&&t.type!==n.GROUP||t.type===n.DM){o=new(r.get("DMChannel"))(e,t)}else if(t.type===n.GROUP){o=new(i(136))(e,t)}return o}toJSON(...e){return super.toJSON({createdTimestamp:!0},...e)}}},function(e,t,i){"use strict";const s=i(16),n=i(18),r=i(42),o=i(19),{Error:a,TypeError:c}=i(3),l=i(2),h=i(8),u=i(4);e.exports=class GuildChannel extends s{constructor(e,t){super(e.client,t),this.guild=e}_patch(e){if(super._patch(e),this.name=e.name,this.rawPosition=e.position,this.parentID=e.parent_id,this.permissionOverwrites=new l,e.permission_overwrites)for(const t of e.permission_overwrites)this.permissionOverwrites.set(t.id,new r(this,t))}get parent(){return this.guild.channels.cache.get(this.parentID)||null}get permissionsLocked(){return this.parent?this.permissionOverwrites.size===this.parent.permissionOverwrites.size&&this.permissionOverwrites.every((e,t)=>{const i=this.parent.permissionOverwrites.get(t);return void 0!==i&&i.deny.bitfield===e.deny.bitfield&&i.allow.bitfield===e.allow.bitfield}):null}get position(){const e=this.guild._sortedChannels(this);return e.array().indexOf(e.get(this.id))}permissionsFor(e){const t=this.guild.members.resolve(e);if(t)return this.memberPermissions(t);const i=this.guild.roles.resolve(e);return i?this.rolePermissions(i):null}overwritesFor(e,t=!1,i=null){if(t||(e=this.guild.members.resolve(e)),!e)return[];i=i||e.roles.cache;const s=[];let n,r;for(const t of this.permissionOverwrites.values())t.id===this.guild.id?r=t:i.has(t.id)?s.push(t):t.id===e.id&&(n=t);return{everyone:r,roles:s,member:n}}memberPermissions(e){if(e.id===this.guild.ownerID)return new h(h.ALL).freeze();const t=e.roles.cache,i=new h(t.map(e=>e.permissions));if(i.has(h.FLAGS.ADMINISTRATOR))return new h(h.ALL).freeze();const s=this.overwritesFor(e,!0,t);return i.remove(s.everyone?s.everyone.deny:0).add(s.everyone?s.everyone.allow:0).remove(s.roles.length>0?s.roles.map(e=>e.deny):0).add(s.roles.length>0?s.roles.map(e=>e.allow):0).remove(s.member?s.member.deny:0).add(s.member?s.member.allow:0).freeze()}rolePermissions(e){if(e.permissions.has(h.FLAGS.ADMINISTRATOR))return new h(h.ALL).freeze();const t=this.permissionOverwrites.get(this.guild.id),i=this.permissionOverwrites.get(e.id);return e.permissions.remove(t?t.deny:0).add(t?t.allow:0).remove(i?i.deny:0).add(i?i.allow:0).freeze()}overwritePermissions(e,t){return Array.isArray(e)||e instanceof l?this.edit({permissionOverwrites:e,reason:t}).then(()=>this):Promise.reject(new c("INVALID_TYPE","overwrites","Array or Collection of Permission Overwrites",!0))}updateOverwrite(e,t,i){if(!(e=this.guild.roles.resolve(e)||this.client.users.resolve(e)))return Promise.reject(new c("INVALID_TYPE","parameter","User nor a Role",!0));const s=this.permissionOverwrites.get(e.id);return s?s.update(t,i).then(()=>this):this.createOverwrite(e,t,i)}createOverwrite(e,t,i){if(!(e=this.guild.roles.resolve(e)||this.client.users.resolve(e)))return Promise.reject(new c("INVALID_TYPE","parameter","User nor a Role",!0));const s=e instanceof o?"role":"member",{allow:n,deny:a}=r.resolveOverwriteOptions(t);return this.client.api.channels(this.id).permissions[e.id].put({data:{id:e.id,type:s,allow:n.bitfield,deny:a.bitfield},reason:i}).then(()=>this)}lockPermissions(){if(!this.parent)return Promise.reject(new a("GUILD_CHANNEL_ORPHAN"));const e=this.parent.permissionOverwrites.map(e=>e.toJSON());return this.edit({permissionOverwrites:e})}get members(){const e=new l;for(const t of this.guild.members.cache.values())this.permissionsFor(t).has("VIEW_CHANNEL",!1)&&e.set(t.id,t);return e}async edit(e,t){void 0!==e.position&&await u.setPosition(this,e.position,!1,this.guild._sortedChannels(this),this.client.api.guilds(this.guild.id).channels,t).then(e=>{this.client.actions.GuildChannelsPositionUpdate.handle({guild_id:this.guild.id,channels:e})});const i=e.permissionOverwrites&&e.permissionOverwrites.map(e=>r.resolve(e,this.guild)),s=await this.client.api.channels(this.id).patch({data:{name:(e.name||this.name).trim(),topic:e.topic,nsfw:e.nsfw,bitrate:e.bitrate||this.bitrate,user_limit:void 0!==e.userLimit?e.userLimit:this.userLimit,parent_id:e.parentID,lock_permissions:e.lockPermissions,rate_limit_per_user:e.rateLimitPerUser,permission_overwrites:i},reason:t}),n=this._clone();return n._patch(s),n}setName(e,t){return this.edit({name:e},t)}setParent(e,{lockPermissions:t=!0,reason:i}={}){return this.edit({parentID:null!==e?e.hasOwnProperty("id")?e.id:e:null,lockPermissions:t},i)}setTopic(e,t){return this.edit({topic:e},t)}setPosition(e,{relative:t,reason:i}={}){return u.setPosition(this,e,t,this.guild._sortedChannels(this),this.client.api.guilds(this.guild.id).channels,i).then(e=>(this.client.actions.GuildChannelsPositionUpdate.handle({guild_id:this.guild.id,channels:e}),this))}createInvite({temporary:e=!1,maxAge:t=86400,maxUses:i=0,unique:s,reason:r}={}){return this.client.api.channels(this.id).invites.post({data:{temporary:e,max_age:t,max_uses:i,unique:s},reason:r}).then(e=>new n(this.client,e))}async fetchInvites(){const e=await this.client.api.channels(this.id).invites.get(),t=new l;for(const i of e){const e=new n(this.client,i);t.set(e.code,e)}return t}clone(e={}){return u.mergeDefault({name:this.name,permissionOverwrites:this.permissionOverwrites,topic:this.topic,type:this.type,nsfw:this.nsfw,parent:this.parent,bitrate:this.bitrate,userLimit:this.userLimit,rateLimitPerUser:this.rateLimitPerUser,reason:null},e),this.guild.channels.create(e.name,e)}equals(e){let t=e&&this.id===e.id&&this.type===e.type&&this.topic===e.topic&&this.position===e.position&&this.name===e.name;return t&&(t=this.permissionOverwrites&&e.permissionOverwrites?this.permissionOverwrites.equals(e.permissionOverwrites):!this.permissionOverwrites&&!e.permissionOverwrites),t}get deletable(){return this.permissionsFor(this.client.user).has(h.FLAGS.MANAGE_CHANNELS,!1)}get manageable(){if(this.client.user.id===this.guild.ownerID)return!0;if("voice"===this.type){if(!this.permissionsFor(this.client.user).has(h.FLAGS.CONNECT,!1))return!1}else if(!this.viewable)return!1;return this.permissionsFor(this.client.user).has(h.FLAGS.MANAGE_CHANNELS,!1)}get viewable(){if(this.client.user.id===this.guild.ownerID)return!0;const e=this.permissionsFor(this.client.user);return!!e&&e.has(h.FLAGS.VIEW_CHANNEL,!1)}delete(e){return this.client.api.channels(this.id).delete({reason:e}).then(()=>this)}}},function(e,t,i){"use strict";const s=i(5),{Endpoints:n}=i(0),r=i(8);e.exports=class Invite extends s{constructor(e,t){super(e),this._patch(t)}_patch(e){this.guild=e.guild?this.client.guilds.add(e.guild,!1):null,this.code=e.code,this.presenceCount="approximate_presence_count"in e?e.approximate_presence_count:null,this.memberCount="approximate_member_count"in e?e.approximate_member_count:null,this.temporary="temporary"in e?e.temporary:null,this.maxAge="max_age"in e?e.max_age:null,this.uses="uses"in e?e.uses:null,this.maxUses="max_uses"in e?e.max_uses:null,this.inviter=e.inviter?this.client.users.add(e.inviter):null,this.targetUser=e.target_user?this.client.users.add(e.target_user):null,this.targetUserType="number"==typeof e.target_user_type?e.target_user_type:null,this.channel=this.client.channels.add(e.channel,this.guild,!1),this.createdTimestamp="created_at"in e?new Date(e.created_at).getTime():null}get createdAt(){return this.createdTimestamp?new Date(this.createdTimestamp):null}get deletable(){const e=this.guild;if(!e||!this.client.guilds.cache.has(e.id))return!1;if(!e.me)throw new Error("GUILD_UNCACHED_ME");return this.channel.permissionsFor(this.client.user).has(r.FLAGS.MANAGE_CHANNELS,!1)||e.me.permissions.has(r.FLAGS.MANAGE_GUILD)}get expiresTimestamp(){return this.createdTimestamp&&this.maxAge?this.createdTimestamp+1e3*this.maxAge:null}get expiresAt(){const{expiresTimestamp:e}=this;return e?new Date(e):null}get url(){return n.invite(this.client.options.http.invite,this.code)}delete(e){return this.client.api.invites[this.code].delete({reason:e}).then(()=>this)}toString(){return this.url}toJSON(){return super.toJSON({url:!0,expiresTimestamp:!0,presenceCount:!1,memberCount:!1,uses:!1,channel:"channelID",inviter:"inviterID",guild:"guildID"})}valueOf(){return this.code}}},function(e,t,i){"use strict";const s=i(5),{Error:n,TypeError:r}=i(3),o=i(8),a=i(7),c=i(4);e.exports=class Role extends s{constructor(e,t,i){super(e),this.guild=i,t&&this._patch(t)}_patch(e){this.id=e.id,this.name=e.name,this.color=e.color,this.hoist=e.hoist,this.rawPosition=e.position,this.permissions=new o(e.permissions).freeze(),this.managed=e.managed,this.mentionable=e.mentionable,this.deleted=!1}get createdTimestamp(){return a.deconstruct(this.id).timestamp}get createdAt(){return new Date(this.createdTimestamp)}get hexColor(){return`#${this.color.toString(16).padStart(6,"0")}`}get members(){return this.guild.members.cache.filter(e=>e.roles.cache.has(this.id))}get editable(){if(this.managed)return!1;const e=this.guild.member(this.client.user);return!!e.permissions.has(o.FLAGS.MANAGE_ROLES)&&e.roles.highest.comparePositionTo(this)>0}get position(){const e=this.guild._sortedRoles();return e.array().indexOf(e.get(this.id))}comparePositionTo(e){if(!(e=this.guild.roles.resolve(e)))throw new r("INVALID_TYPE","role","Role nor a Snowflake");return this.constructor.comparePositions(this,e)}async edit(e,t){return void 0!==e.permissions?e.permissions=o.resolve(e.permissions):e.permissions=this.permissions.bitfield,void 0!==e.position&&await c.setPosition(this,e.position,!1,this.guild._sortedRoles(),this.client.api.guilds(this.guild.id).roles,t).then(e=>{this.client.actions.GuildRolesPositionUpdate.handle({guild_id:this.guild.id,roles:e})}),this.client.api.guilds[this.guild.id].roles[this.id].patch({data:{name:e.name||this.name,color:null!==e.color?c.resolveColor(e.color||this.color):null,hoist:void 0!==e.hoist?e.hoist:this.hoist,permissions:e.permissions,mentionable:void 0!==e.mentionable?e.mentionable:this.mentionable},reason:t}).then(e=>{const t=this._clone();return t._patch(e),t})}permissionsIn(e){if(!(e=this.guild.channels.resolve(e)))throw new n("GUILD_CHANNEL_RESOLVE");return e.rolePermissions(this)}setName(e,t){return this.edit({name:e},t)}setColor(e,t){return this.edit({color:e},t)}setHoist(e,t){return this.edit({hoist:e},t)}setPermissions(e,t){return this.edit({permissions:e},t)}setMentionable(e,t){return this.edit({mentionable:e},t)}setPosition(e,{relative:t,reason:i}={}){return c.setPosition(this,e,t,this.guild._sortedRoles(),this.client.api.guilds(this.guild.id).roles,i).then(e=>(this.client.actions.GuildRolesPositionUpdate.handle({guild_id:this.guild.id,roles:e}),this))}delete(e){return this.client.api.guilds[this.guild.id].roles[this.id].delete({reason:e}).then(()=>(this.client.actions.GuildRoleDelete.handle({guild_id:this.guild.id,role_id:this.id}),this))}equals(e){return e&&this.id===e.id&&this.name===e.name&&this.color===e.color&&this.hoist===e.hoist&&this.position===e.position&&this.permissions.bitfield===e.permissions.bitfield&&this.managed===e.managed}toString(){return this.id===this.guild.id?"@everyone":`<@&${this.id}>`}toJSON(){return super.toJSON({createdTimestamp:!0})}static comparePositions(e,t){return e.position===t.position?t.id-e.id:e.position-t.position}}},function(e,t,i){"use strict";var s=i(31),n=Object.keys||function(e){var t=[];for(var i in e)t.push(i);return t};e.exports=u;var r=Object.create(i(25));r.inherits=i(22);var o=i(64),a=i(46);r.inherits(u,o);for(var c=n(a.prototype),l=0;l<c.length;l++){var h=c[l];u.prototype[h]||(u.prototype[h]=a.prototype[h])}function u(e){if(!(this instanceof u))return new u(e);o.call(this,e),a.call(this,e),e&&!1===e.readable&&(this.readable=!1),e&&!1===e.writable&&(this.writable=!1),this.allowHalfOpen=!0,e&&!1===e.allowHalfOpen&&(this.allowHalfOpen=!1),this.once("end",d)}function d(){this.allowHalfOpen||this._writableState.ended||s.nextTick(p,this)}function p(e){e.end()}Object.defineProperty(u.prototype,"writableHighWaterMark",{enumerable:!1,get:function(){return this._writableState.highWaterMark}}),Object.defineProperty(u.prototype,"destroyed",{get:function(){return void 0!==this._readableState&&void 0!==this._writableState&&(this._readableState.destroyed&&this._writableState.destroyed)},set:function(e){void 0!==this._readableState&&void 0!==this._writableState&&(this._readableState.destroyed=e,this._writableState.destroyed=e)}}),u.prototype._destroy=function(e,t){this.push(null),this.end(),s.nextTick(t,e)}},function(e,t){var i;i=function(){return this}();try{i=i||new Function("return this")()}catch(e){"object"==typeof window&&(i=window)}e.exports=i},function(e,t){"function"==typeof Object.create?e.exports=function(e,t){t&&(e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}))}:e.exports=function(e,t){if(t){e.super_=t;var i=function(){};i.prototype=t.prototype,e.prototype=new i,e.prototype.constructor=e}}},function(e,t,i){"use strict";const s={GuildEmoji:i(24),DMChannel:i(39),TextChannel:i(41),VoiceChannel:i(79),CategoryChannel:i(80),NewsChannel:i(81),StoreChannel:i(82),GuildMember:i(10),Guild:i(53),Message:i(28),MessageReaction:i(51),Presence:i(13).Presence,ClientPresence:i(89),VoiceState:i(27),Role:i(19),User:i(26)};e.exports=class Structures{constructor(){throw new Error(`The ${this.constructor.name} class may not be instantiated.`)}static get(e){if("string"==typeof e)return s[e];throw new TypeError(`"structure" argument must be a string (received ${typeof e})`)}static extend(e,t){if(!s[e])throw new RangeError(`"${e}" is not a valid extensible structure.`);if("function"!=typeof t){throw new TypeError(`"extender" argument must be a function that returns the extended structure class/prototype ${`(received ${typeof t})`}.`)}const i=t(s[e]);if("function"!=typeof i){throw new TypeError(`The extender function must return the extended structure class/prototype ${`(received ${typeof i})`}.`)}if(!(i.prototype instanceof s[e])){const t=Object.getPrototypeOf(i),n=`${i.name||"unnamed"}${t.name?` extends ${t.name}`:""}`;throw new Error("The class/prototype returned from the extender function must extend the existing structure class/prototype"+` (received function ${n}; expected extension of ${s[e].name}).`)}return s[e]=i,i}}},function(e,t,i){"use strict";const s=i(40),{Error:n}=i(3),r=i(63),o=i(8);class GuildEmoji extends s{_clone(){const e=super._clone();return e._roles=this._roles.slice(),e}get deletable(){if(!this.guild.me)throw new n("GUILD_UNCACHED_ME");return!this.managed&&this.guild.me.hasPermission(o.FLAGS.MANAGE_EMOJIS)}get roles(){return new r(this)}fetchAuthor(){return this.managed?Promise.reject(new n("EMOJI_MANAGED")):this.guild.me?this.guild.me.permissions.has(o.FLAGS.MANAGE_EMOJIS)?this.client.api.guilds(this.guild.id).emojis(this.id).get().then(e=>this.client.users.add(e.user)):Promise.reject(new n("MISSING_MANAGE_EMOJIS_PERMISSION",this.guild)):Promise.reject(new n("GUILD_UNCACHED_ME"))}edit(e,t){const i=e.roles?e.roles.map(e=>e.id||e):void 0;return this.client.api.guilds(this.guild.id).emojis(this.id).patch({data:{name:e.name,roles:i},reason:t}).then(e=>{const t=this._clone();return t._patch(e),t})}setName(e,t){return this.edit({name:e},t)}delete(e){return this.client.api.guilds(this.guild.id).emojis(this.id).delete({reason:e}).then(()=>this)}equals(e){return e instanceof GuildEmoji?e.id===this.id&&e.name===this.name&&e.managed===this.managed&&e.requiresColons===this.requiresColons&&e.roles.cache.size===this.roles.cache.size&&e.roles.cache.every(e=>this.roles.cache.has(e.id)):e.id===this.id&&e.name===this.name&&e.roles.length===this.roles.cache.size&&e.roles.every(e=>this.roles.cache.has(e))}}e.exports=GuildEmoji},function(e,t){function i(e){return Object.prototype.toString.call(e)}t.isArray=function(e){return Array.isArray?Array.isArray(e):"[object Array]"===i(e)},t.isBoolean=function(e){return"boolean"==typeof e},t.isNull=function(e){return null===e},t.isNullOrUndefined=function(e){return null==e},t.isNumber=function(e){return"number"==typeof e},t.isString=function(e){return"string"==typeof e},t.isSymbol=function(e){return"symbol"==typeof e},t.isUndefined=function(e){return void 0===e},t.isRegExp=function(e){return"[object RegExp]"===i(e)},t.isObject=function(e){return"object"==typeof e&&null!==e},t.isDate=function(e){return"[object Date]"===i(e)},t.isError=function(e){return"[object Error]"===i(e)||e instanceof Error},t.isFunction=function(e){return"function"==typeof e},t.isPrimitive=function(e){return null===e||"boolean"==typeof e||"number"==typeof e||"string"==typeof e||"symbol"==typeof e||void 0===e},t.isBuffer=Buffer.isBuffer},function(e,t,i){"use strict";const s=i(5),{Presence:n}=i(13),r=i(33),{Error:o}=i(3),a=i(7),c=i(78);class User extends s{constructor(e,t){super(e),this.id=t.id,this.bot=Boolean(t.bot),this._patch(t)}_patch(e){e.username&&(this.username=e.username),e.discriminator&&(this.discriminator=e.discriminator),void 0!==e.avatar&&(this.avatar=e.avatar),void 0!==e.bot&&(this.bot=Boolean(e.bot)),void 0!==e.system&&(this.system=Boolean(e.system)),e.locale&&(this.locale=e.locale),void 0!==e.public_flags&&(this.flags=new c(e.public_flags)),this.lastMessageID=null,this.lastMessageChannelID=null}get partial(){return"string"!=typeof this.username}get createdTimestamp(){return a.deconstruct(this.id).timestamp}get createdAt(){return new Date(this.createdTimestamp)}get lastMessage(){const e=this.client.channels.cache.get(this.lastMessageChannelID);return e&&e.messages.cache.get(this.lastMessageID)||null}get presence(){for(const e of this.client.guilds.cache.values())if(e.presences.cache.has(this.id))return e.presences.cache.get(this.id);return new n(this.client,{user:{id:this.id}})}avatarURL({format:e,size:t,dynamic:i}={}){return this.avatar?this.client.rest.cdn.Avatar(this.id,this.avatar,e,t,i):null}get defaultAvatarURL(){return this.client.rest.cdn.DefaultAvatar(this.discriminator%5)}displayAvatarURL(e){return this.avatarURL(e)||this.defaultAvatarURL}get tag(){return"string"==typeof this.username?`${this.username}#${this.discriminator}`:null}typingIn(e){return(e=this.client.channels.resolve(e))._typing.has(this.id)}typingSinceIn(e){return(e=this.client.channels.resolve(e))._typing.has(this.id)?new Date(e._typing.get(this.id).since):null}typingDurationIn(e){return(e=this.client.channels.resolve(e))._typing.has(this.id)?e._typing.get(this.id).elapsedTime:-1}get dmChannel(){return this.client.channels.cache.find(e=>"dm"===e.type&&e.recipient.id===this.id)||null}async createDM(){const{dmChannel:e}=this;if(e&&!e.partial)return e;const t=await this.client.api.users(this.client.user.id).channels.post({data:{recipient_id:this.id}});return this.client.actions.ChannelCreate.handle(t).channel}async deleteDM(){const{dmChannel:e}=this;if(!e)throw new o("USER_NO_DMCHANNEL");const t=await this.client.api.channels(e.id).delete();return this.client.actions.ChannelDelete.handle(t).channel}equals(e){return e&&this.id===e.id&&this.username===e.username&&this.discriminator===e.discriminator&&this.avatar===e.avatar}async fetchFlags(){if(this.flags)return this.flags;const e=await this.client.api.users(this.id).get();return this._patch(e),this.flags}fetch(){return this.client.users.fetch(this.id,!0)}toString(){return`<@${this.id}>`}toJSON(...e){const t=super.toJSON({createdTimestamp:!0,defaultAvatarURL:!0,tag:!0,lastMessage:!1,lastMessageID:!1},...e);return t.avatarURL=this.avatarURL(),t.displayAvatarURL=this.displayAvatarURL(),t}send(){}}r.applyToClass(User),e.exports=User},function(e,t,i){"use strict";const s=i(5),{Error:n,TypeError:r}=i(3),{browser:o}=i(0);e.exports=class VoiceState extends s{constructor(e,t){super(e.client),this.guild=e,this.id=t.user_id,this._patch(t)}_patch(e){return this.serverDeaf=e.deaf,this.serverMute=e.mute,this.selfDeaf=e.self_deaf,this.selfMute=e.self_mute,this.sessionID=e.session_id,this.streaming=e.self_stream||!1,this.channelID=e.channel_id,this}get member(){return this.guild.members.cache.get(this.id)||null}get channel(){return this.guild.channels.cache.get(this.channelID)||null}get connection(){return o||this.id!==this.client.user.id?null:this.client.voice.connections.get(this.guild.id)||null}get deaf(){return this.serverDeaf||this.selfDeaf}get mute(){return this.serverMute||this.selfMute}get speaking(){return this.channel&&this.channel.connection?Boolean(this.channel.connection._speaking.get(this.id)):null}setMute(e,t){return this.member?this.member.edit({mute:e},t):Promise.reject(new n("VOICE_STATE_UNCACHED_MEMBER"))}setDeaf(e,t){return this.member?this.member.edit({deaf:e},t):Promise.reject(new n("VOICE_STATE_UNCACHED_MEMBER"))}kick(e){return this.setChannel(null,e)}setChannel(e,t){return this.member?this.member.edit({channel:e},t):Promise.reject(new n("VOICE_STATE_UNCACHED_MEMBER"))}async setSelfMute(e){if(this.id!==this.client.user.id)throw new n("VOICE_STATE_NOT_OWN");if("boolean"!=typeof e)throw new r("VOICE_STATE_INVALID_TYPE","mute");return!!this.connection&&(this.selfMute=e,await this.connection.sendVoiceStateUpdate(),!0)}async setSelfDeaf(e){return this.id!==this.client.user.id?new n("VOICE_STATE_NOT_OWN"):"boolean"!=typeof e?new r("VOICE_STATE_INVALID_TYPE","deaf"):!!this.connection&&(this.selfDeaf=e,await this.connection.sendVoiceStateUpdate(),!0)}toJSON(){return super.toJSON({id:!0,serverDeaf:!0,serverMute:!0,selfDeaf:!0,selfMute:!0,sessionID:!0,channelID:"channel"})}}},function(e,t,i){"use strict";const s=i(30),n=i(5),r=i(50),o=i(43),a=i(44),c=i(75),l=i(76),{Error:h,TypeError:u}=i(3),d=i(132),p=i(2),{MessageTypes:f}=i(0),m=i(47),g=i(8),E=i(4);e.exports=class Message extends n{constructor(e,t,i){super(e),this.channel=i,this.deleted=!1,t&&this._patch(t)}_patch(e){if(this.id=e.id,this.type=f[e.type],this.content=e.content,this.author=e.author?this.client.users.add(e.author,!e.webhook_id):null,this.pinned=e.pinned,this.tts=e.tts,this.nonce=e.nonce,this.system=0!==e.type,this.embeds=(e.embeds||[]).map(e=>new a(e,!0)),this.attachments=new p,e.attachments)for(const t of e.attachments)this.attachments.set(t.id,new o(t.url,t.filename,t));if(this.createdTimestamp=new Date(e.timestamp).getTime(),this.editedTimestamp=e.edited_timestamp?new Date(e.edited_timestamp).getTime():null,this.reactions=new d(this),e.reactions&&e.reactions.length>0)for(const t of e.reactions)this.reactions.add(t);this.mentions=new c(this,e.mentions,e.mention_roles,e.mention_everyone,e.mention_channels),this.webhookID=e.webhook_id||null,this.application=e.application?new r(this.client,e.application):null,this.activity=e.activity?{partyID:e.activity.party_id,type:e.activity.type}:null,this._edits=[],this.member&&e.member?this.member._patch(e.member):e.member&&this.guild&&this.author&&this.guild.members.add(Object.assign(e.member,{user:this.author})),this.flags=new m(e.flags).freeze(),this.reference=e.message_reference?{channelID:e.message_reference.channel_id,guildID:e.message_reference.guild_id,messageID:e.message_reference.message_id}:null}get partial(){return"string"!=typeof this.content||!this.author}patch(e){const t=this._clone();if(this._edits.unshift(t),"edited_timestamp"in e&&(this.editedTimestamp=new Date(e.edited_timestamp).getTime()),"content"in e&&(this.content=e.content),"pinned"in e&&(this.pinned=e.pinned),"tts"in e&&(this.tts=e.tts),this.embeds="embeds"in e?e.embeds.map(e=>new a(e,!0)):this.embeds.slice(),"attachments"in e){this.attachments=new p;for(const t of e.attachments)this.attachments.set(t.id,new o(t.url,t.filename,t))}else this.attachments=new p(this.attachments);this.mentions=new c(this,"mentions"in e?e.mentions:this.mentions.users,"mention_roles"in e?e.mention_roles:this.mentions.roles,"mention_everyone"in e?e.mention_everyone:this.mentions.everyone,"mention_channels"in e?e.mention_channels:this.mentions.crosspostedChannels),this.flags=new m("flags"in e?e.flags:0).freeze()}get member(){return this.guild&&this.guild.member(this.author)||null}get createdAt(){return new Date(this.createdTimestamp)}get editedAt(){return this.editedTimestamp?new Date(this.editedTimestamp):null}get guild(){return this.channel.guild||null}get url(){return`https://discordapp.com/channels/${this.guild?this.guild.id:"@me"}/${this.channel.id}/${this.id}`}get cleanContent(){return null!=this.content?E.cleanContent(this.content,this):null}createReactionCollector(e,t={}){return new l(this,e,t)}awaitReactions(e,t={}){return new Promise((i,s)=>{this.createReactionCollector(e,t).once("end",(e,n)=>{t.errors&&t.errors.includes(n)?s(e):i(e)})})}get edits(){const e=this._edits.slice();return e.unshift(this),e}get editable(){return this.author.id===this.client.user.id}get deletable(){return!this.deleted&&(this.author.id===this.client.user.id||this.guild&&this.channel.permissionsFor(this.client.user).has(g.FLAGS.MANAGE_MESSAGES,!1))}get pinnable(){return"DEFAULT"===this.type&&(!this.guild||this.channel.permissionsFor(this.client.user).has(g.FLAGS.MANAGE_MESSAGES,!1))}edit(e,t){const{data:i}=e instanceof s?e.resolveData():s.create(this,e,t).resolveData();return this.client.api.channels[this.channel.id].messages[this.id].patch({data:i}).then(e=>{const t=this._clone();return t._patch(e),t})}pin(){return this.client.api.channels(this.channel.id).pins(this.id).put().then(()=>this)}unpin(){return this.client.api.channels(this.channel.id).pins(this.id).delete().then(()=>this)}react(e){if(!(e=this.client.emojis.resolveIdentifier(e)))throw new u("EMOJI_TYPE");return this.client.api.channels(this.channel.id).messages(this.id).reactions(e,"@me").put().then(()=>this.client.actions.MessageReactionAdd.handle({user:this.client.user,channel:this.channel,message:this,emoji:E.parseEmoji(e)}).reaction)}delete(e={}){if("object"!=typeof e)throw new u("INVALID_TYPE","options","object",!0);const{timeout:t=0,reason:i}=e;return t<=0?this.channel.messages.delete(this.id,i).then(()=>this):new Promise(e=>{this.client.setTimeout(()=>{e(this.delete({reason:i}))},t)})}reply(e,t){return this.channel.send(e instanceof s?e:s.transformOptions(e,t,{reply:this.member||this.author}))}fetch(){return this.channel.messages.fetch(this.id,!0)}fetchWebhook(){return this.webhookID?this.client.fetchWebhook(this.webhookID):Promise.reject(new h("WEBHOOK_MESSAGE"))}suppressEmbeds(e=!0){const t=new m(this.flags.bitfield);return e?t.add(m.FLAGS.SUPPRESS_EMBEDS):t.remove(m.FLAGS.SUPPRESS_EMBEDS),this.edit({flags:t})}equals(e,t){if(!e)return!1;if(!e.author&&!e.attachments)return this.id===e.id&&this.embeds.length===e.embeds.length;let i=this.id===e.id&&this.author.id===e.author.id&&this.content===e.content&&this.tts===e.tts&&this.nonce===e.nonce&&this.embeds.length===e.embeds.length&&this.attachments.length===e.attachments.length;return i&&t&&(i=this.mentions.everyone===e.mentions.everyone&&this.createdTimestamp===new Date(t.timestamp).getTime()&&this.editedTimestamp===new Date(t.edited_timestamp).getTime()),i}toString(){return this.content}toJSON(){return super.toJSON({channel:"channelID",author:"authorID",application:"applicationID",guild:"guildID",cleanContent:!0,member:!1,reactions:!1})}}},function(e,t,i){"use strict";const s=i(5),n=i(7);e.exports=class Emoji extends s{constructor(e,t){super(e),this.animated=t.animated,this.name=t.name,this.id=t.id,this.deleted=!1}get identifier(){return this.id?`${this.animated?"a:":""}${this.name}:${this.id}`:encodeURIComponent(this.name)}get url(){return this.id?this.client.rest.cdn.Emoji(this.id,this.animated?"gif":"png"):null}get createdTimestamp(){return this.id?n.deconstruct(this.id).timestamp:null}get createdAt(){return this.id?new Date(this.createdTimestamp):null}toString(){return this.id?`<${this.animated?"a":""}:${this.name}:${this.id}>`:this.name}toJSON(){return super.toJSON({guild:"guildID",createdTimestamp:!0,url:!0,identifier:!0})}}},function(e,t,i){"use strict";const s=i(43),n=i(44),{RangeError:r}=i(3),{browser:o}=i(0),a=i(9),c=i(47),l=i(4);class APIMessage{constructor(e,t){this.target=e,this.options=t,this.data=null,this.files=null}get isWebhook(){const e=i(12),t=i(48);return this.target instanceof e||this.target instanceof t}get isUser(){const e=i(26),t=i(10);return this.target instanceof e||this.target instanceof t}get isMessage(){const e=i(28);return this.target instanceof e}makeContent(){const e=i(10);let t;null===this.options.content?t="":void 0!==this.options.content&&(t=l.resolveString(this.options.content));const s=void 0===this.options.disableMentions?this.target.client.options.disableMentions:this.options.disableMentions;"all"===s?t=l.removeMentions(t||""):"everyone"===s&&(t=(t||"").replace(/@([^<>@ ]*)/gmus,(e,t)=>t.match(/^[&!]?\d+$/)?`@${t}`:`@​${t}`));const n=void 0!==this.options.split&&!1!==this.options.split,r=void 0!==this.options.code&&!1!==this.options.code,o=n?{...this.options.split}:void 0;let a="";if(this.options.reply&&!this.isUser&&"dm"!==this.target.type){const t=this.target.client.users.resolveID(this.options.reply);a=`<@${this.options.reply instanceof e&&this.options.reply.nickname?"!":""}${t}>, `,n&&(o.prepend=`${a}${o.prepend||""}`)}if(t||a){if(r){const e="string"==typeof this.options.code?this.options.code:"";t=`${a}\`\`\`${e}\n${l.cleanCodeBlockContent(t||"")}\n\`\`\``,n&&(o.prepend=`${o.prepend||""}\`\`\`${e}\n`,o.append=`\n\`\`\`${o.append||""}`)}else a&&(t=`${a}${t||""}`);n&&(t=l.splitMessage(t||"",o))}return t}resolveData(){if(this.data)return this;const e=this.makeContent(),t=Boolean(this.options.tts);let i;if(void 0!==this.options.nonce&&(i=parseInt(this.options.nonce),isNaN(i)||i<0))throw new r("MESSAGE_NONCE_TYPE");const s=[];this.isWebhook?this.options.embeds&&s.push(...this.options.embeds):this.options.embed&&s.push(this.options.embed);const o=s.map(e=>new n(e).toJSON());let a,l,h;this.isWebhook&&(a=this.options.username||this.target.name,this.options.avatarURL&&(l=this.options.avatarURL)),this.isMessage&&(h=null!=this.options.flags?new c(this.options.flags).bitfield:this.target.flags.bitfield);const u=void 0===this.options.allowedMentions?this.target.client.options.allowedMentions:this.options.allowedMentions;return this.data={content:e,tts:t,nonce:i,embed:null===this.options.embed?null:o[0],embeds:o,username:a,avatar_url:l,allowed_mentions:u,flags:h},this}async resolveFiles(){if(this.files)return this;const e=[];this.isWebhook?this.options.embeds&&e.push(...this.options.embeds):this.options.embed&&e.push(this.options.embed);const t=[];this.options.files&&t.push(...this.options.files);for(const i of e)i.files&&t.push(...i.files);return this.files=await Promise.all(t.map(e=>this.constructor.resolveFile(e))),this}split(){if(this.data||this.resolveData(),!Array.isArray(this.data.content))return[this];const e=[];for(let t=0;t<this.data.content.length;t++){let i,s;t===this.data.content.length-1?(i={...this.data,content:this.data.content[t]},s={...this.options,content:this.data.content[t]}):(i={content:this.data.content[t],tts:this.data.tts},s={content:this.data.content[t],tts:this.data.tts});const n=new APIMessage(this.target,s);n.data=i,e.push(n)}return e}static async resolveFile(e){let t,i;const s=e=>"string"==typeof e?l.basename(e):e.path?l.basename(e.path):"file.jpg";return"string"==typeof e||e instanceof(o?ArrayBuffer:Buffer)||"function"==typeof e.pipe?(t=e,i=s(t)):(t=e.attachment,i=e.name||s(t)),{attachment:t,name:i,file:await a.resolveFile(t)}}static partitionMessageAdditions(e){const t=[],i=[];for(const r of e)r instanceof n?t.push(r):r instanceof s&&i.push(r);return[t,i]}static transformOptions(e,t,i={},r=!1){if(t||"object"!=typeof e||Array.isArray(e)||(t=e,e=void 0),t){if(t instanceof n)return r?{content:e,embeds:[t],...i}:{content:e,embed:t,...i};if(t instanceof s)return{content:e,files:[t],...i}}else t={};if(Array.isArray(t)){const[s,n]=this.partitionMessageAdditions(t);return r?{content:e,embeds:s,files:n,...i}:{content:e,embed:s[0],files:n,...i}}if(Array.isArray(e)){const[t,s]=this.partitionMessageAdditions(e);if(t.length||s.length)return r?{embeds:t,files:s,...i}:{embed:t[0],files:s,...i}}return{content:e,...t,...i}}static create(e,t,s,n={}){const r=i(12),o=i(48),a=e instanceof r||e instanceof o;return new this(e,this.transformOptions(t,s,n,a))}}e.exports=APIMessage},function(e,t,i){"use strict";(function(t){void 0===t||!t.version||0===t.version.indexOf("v0.")||0===t.version.indexOf("v1.")&&0!==t.version.indexOf("v1.8.")?e.exports={nextTick:function(e,i,s,n){if("function"!=typeof e)throw new TypeError('"callback" argument must be a function');var r,o,a=arguments.length;switch(a){case 0:case 1:return t.nextTick(e);case 2:return t.nextTick((function(){e.call(null,i)}));case 3:return t.nextTick((function(){e.call(null,i,s)}));case 4:return t.nextTick((function(){e.call(null,i,s,n)}));default:for(r=new Array(a-1),o=0;o<r.length;)r[o++]=arguments[o];return t.nextTick((function(){e.apply(null,r)}))}}}:e.exports=t}).call(this,i(14))},function(e,t,i){var s=i(120),n=s.Buffer;function r(e,t){for(var i in e)t[i]=e[i]}function o(e,t,i){return n(e,t,i)}n.from&&n.alloc&&n.allocUnsafe&&n.allocUnsafeSlow?e.exports=s:(r(s,t),t.Buffer=o),r(n,o),o.from=function(e,t,i){if("number"==typeof e)throw new TypeError("Argument must not be a number");return n(e,t,i)},o.alloc=function(e,t,i){if("number"!=typeof e)throw new TypeError("Argument must be a number");var s=n(e);return void 0!==t?"string"==typeof i?s.fill(t,i):s.fill(t):s.fill(0),s},o.allocUnsafe=function(e){if("number"!=typeof e)throw new TypeError("Argument must be a number");return n(e)},o.allocUnsafeSlow=function(e){if("number"!=typeof e)throw new TypeError("Argument must be a number");return s.SlowBuffer(e)}},function(e,t,i){"use strict";const s=i(71),n=i(30),r=i(7),o=i(2),{RangeError:a,TypeError:c}=i(3);class TextBasedChannel{constructor(){this.messages=new l(this),this.lastMessageID=null,this.lastPinTimestamp=null}get lastMessage(){return this.messages.cache.get(this.lastMessageID)||null}get lastPinAt(){return this.lastPinTimestamp?new Date(this.lastPinTimestamp):null}async send(e,t){const s=i(26),r=i(10);if(this instanceof s||this instanceof r)return this.createDM().then(i=>i.send(e,t));let o;if(e instanceof n)o=e.resolveData();else if(o=n.create(this,e,t).resolveData(),Array.isArray(o.data.content))return Promise.all(o.split().map(this.send.bind(this)));const{data:a,files:c}=await o.resolveFiles();return this.client.api.channels[this.id].messages.post({data:a,files:c}).then(e=>this.client.actions.MessageCreate.handle(e).message)}startTyping(e){if(void 0!==e&&e<1)throw new a("TYPING_COUNT");if(this.client.user._typing.has(this.id)){const t=this.client.user._typing.get(this.id);return t.count=e||t.count+1,t.promise}const t={};return t.promise=new Promise((i,s)=>{const n=this.client.api.channels[this.id].typing;Object.assign(t,{count:e||1,interval:this.client.setInterval(()=>{n.post().catch(e=>{this.client.clearInterval(t.interval),this.client.user._typing.delete(this.id),s(e)})},9e3),resolve:i}),n.post().catch(e=>{this.client.clearInterval(t.interval),this.client.user._typing.delete(this.id),s(e)}),this.client.user._typing.set(this.id,t)}),t.promise}stopTyping(e=!1){if(this.client.user._typing.has(this.id)){const t=this.client.user._typing.get(this.id);t.count--,(t.count<=0||e)&&(this.client.clearInterval(t.interval),this.client.user._typing.delete(this.id),t.resolve())}}get typing(){return this.client.user._typing.has(this.id)}get typingCount(){return this.client.user._typing.has(this.id)?this.client.user._typing.get(this.id).count:0}createMessageCollector(e,t={}){return new s(this,e,t)}awaitMessages(e,t={}){return new Promise((i,s)=>{this.createMessageCollector(e,t).once("end",(e,n)=>{t.errors&&t.errors.includes(n)?s(e):i(e)})})}async bulkDelete(e,t=!1){if(Array.isArray(e)||e instanceof o){let i=e instanceof o?e.keyArray():e.map(e=>e.id||e);if(t&&(i=i.filter(e=>Date.now()-r.deconstruct(e).date.getTime()<12096e5)),0===i.length)return new o;if(1===i.length){await this.client.api.channels(this.id).messages(i[0]).delete();const e=this.client.actions.MessageDelete.getMessage({message_id:i[0]},this);return e?new o([[e.id,e]]):new o}return await this.client.api.channels[this.id].messages["bulk-delete"].post({data:{messages:i}}),i.reduce((e,t)=>e.set(t,this.client.actions.MessageDeleteBulk.getMessage({message_id:t},this)),new o)}if(!isNaN(e)){const i=await this.messages.fetch({limit:e});return this.bulkDelete(i,t)}throw new c("MESSAGE_BULK_DELETE_TYPE")}static applyToClass(e,t=!1,i=[]){const s=["send"];t&&s.push("lastMessage","lastPinAt","bulkDelete","startTyping","stopTyping","typing","typingCount","createMessageCollector","awaitMessages");for(const t of s)i.includes(t)||Object.defineProperty(e.prototype,t,Object.getOwnPropertyDescriptor(TextBasedChannel.prototype,t))}}e.exports=TextBasedChannel;const l=i(34)},function(e,t,i){"use strict";const s=i(6),n=i(28),r=i(2),o=i(133);e.exports=class MessageManager extends s{constructor(e,t){super(e.client,t,n,o,e.client.options.messageCacheMaxSize),this.channel=e}add(e,t){return super.add(e,t,{extras:[this.channel]})}fetch(e,t=!0){return"string"==typeof e?this._fetchId(e,t):this._fetchMany(e,t)}fetchPinned(e=!0){return this.client.api.channels[this.channel.id].pins.get().then(t=>{const i=new r;for(const s of t)i.set(s.id,this.add(s,e));return i})}async delete(e,t){(e=this.resolveID(e))&&await this.client.api.channels(this.channel.id).messages(e).delete({reason:t})}async _fetchId(e,t){const i=this.cache.get(e);if(i&&!i.partial)return i;const s=await this.client.api.channels[this.channel.id].messages[e].get();return this.add(s,t)}async _fetchMany(e={},t){const i=await this.client.api.channels[this.channel.id].messages.get({query:e}),s=new r;for(const e of i)s.set(e.id,this.add(e,t));return s}}},function(e,t){},function(e,t,i){"use strict";var s=function(){if("undefined"!=typeof self)return self;if("undefined"!=typeof window)return window;if(void 0!==s)return s;throw new Error("unable to locate global object")}();e.exports=t=s.fetch,t.default=s.fetch.bind(s),t.Headers=s.Headers,t.Request=s.Request,t.Response=s.Response},function(e,t,i){"use strict";(function(t,s){i(60);const n=i(15),r=i(100),{DefaultOptions:o}=i(0),a=i(4);e.exports=class BaseClient extends n{constructor(e={}){super(),this._timeouts=new Set,this._intervals=new Set,this._immediates=new Set,this.options=a.mergeDefault(o,e),this.rest=new r(this,e._tokenType)}get api(){return this.rest.api}destroy(){for(const e of this._timeouts)this.clearTimeout(e);for(const e of this._intervals)this.clearInterval(e);for(const e of this._immediates)this.clearImmediate(e);this._timeouts.clear(),this._intervals.clear(),this._immediates.clear()}setTimeout(e,t,...i){const s=setTimeout(()=>{e(...i),this._timeouts.delete(s)},t);return this._timeouts.add(s),s}clearTimeout(e){clearTimeout(e),this._timeouts.delete(e)}setInterval(e,t,...i){const s=setInterval(e,t,...i);return this._intervals.add(s),s}clearInterval(e){clearInterval(e),this._intervals.delete(e)}setImmediate(e,...i){const s=t(e,...i);return this._immediates.add(s),s}clearImmediate(e){s(e),this._immediates.delete(e)}toJSON(...e){return a.flatten(this,{domain:!1},...e)}}}).call(this,i(38).setImmediate,i(38).clearImmediate)},function(e,t,i){(function(e){var s=void 0!==e&&e||"undefined"!=typeof self&&self||window,n=Function.prototype.apply;function r(e,t){this._id=e,this._clearFn=t}t.setTimeout=function(){return new r(n.call(setTimeout,s,arguments),clearTimeout)},t.setInterval=function(){return new r(n.call(setInterval,s,arguments),clearInterval)},t.clearTimeout=t.clearInterval=function(e){e&&e.close()},r.prototype.unref=r.prototype.ref=function(){},r.prototype.close=function(){this._clearFn.call(s,this._id)},t.enroll=function(e,t){clearTimeout(e._idleTimeoutId),e._idleTimeout=t},t.unenroll=function(e){clearTimeout(e._idleTimeoutId),e._idleTimeout=-1},t._unrefActive=t.active=function(e){clearTimeout(e._idleTimeoutId);var t=e._idleTimeout;t>=0&&(e._idleTimeoutId=setTimeout((function(){e._onTimeout&&e._onTimeout()}),t))},i(60),t.setImmediate="undefined"!=typeof self&&self.setImmediate||void 0!==e&&e.setImmediate||this&&this.setImmediate,t.clearImmediate="undefined"!=typeof self&&self.clearImmediate||void 0!==e&&e.clearImmediate||this&&this.clearImmediate}).call(this,i(21))},function(e,t,i){"use strict";const s=i(16),n=i(33),r=i(34);class DMChannel extends s{constructor(e,t){super(e,t),this.type="dm",this.messages=new r(this),this._typing=new Map}_patch(e){super._patch(e),e.recipients&&(this.recipient=this.client.users.add(e.recipients[0])),this.lastMessageID=e.last_message_id,this.lastPinTimestamp=e.last_pin_timestamp?new Date(e.last_pin_timestamp).getTime():null}get partial(){return void 0===this.lastMessageID}fetch(){return this.recipient.createDM()}toString(){return this.recipient.toString()}get lastMessage(){}get lastPinAt(){}send(){}startTyping(){}stopTyping(){}get typing(){}get typingCount(){}createMessageCollector(){}awaitMessages(){}}n.applyToClass(DMChannel,!0,["bulkDelete"]),e.exports=DMChannel},function(e,t,i){"use strict";const s=i(29);e.exports=class BaseGuildEmoji extends s{constructor(e,t,i){super(e,t),this.guild=i,Object.defineProperty(this,"_roles",{value:[],writable:!0}),this._patch(t)}_patch(e){e.name&&(this.name=e.name),void 0!==e.require_colons&&(this.requiresColons=e.require_colons),void 0!==e.managed&&(this.managed=e.managed),void 0!==e.available&&(this.available=e.available),e.roles&&(this._roles=e.roles)}}},function(e,t,i){"use strict";const s=i(17),n=i(12),r=i(33),o=i(34),a=i(2),c=i(9);class TextChannel extends s{constructor(e,t){super(e,t),this.messages=new o(this),this._typing=new Map}_patch(e){if(super._patch(e),this.topic=e.topic,this.nsfw=e.nsfw,this.lastMessageID=e.last_message_id,this.rateLimitPerUser=e.rate_limit_per_user||0,this.lastPinTimestamp=e.last_pin_timestamp?new Date(e.last_pin_timestamp).getTime():null,e.messages)for(const t of e.messages)this.messages.add(t)}setRateLimitPerUser(e,t){return this.edit({rateLimitPerUser:e},t)}setNSFW(e,t){return this.edit({nsfw:e},t)}fetchWebhooks(){return this.client.api.channels[this.id].webhooks.get().then(e=>{const t=new a;for(const i of e)t.set(i.id,new n(this.client,i));return t})}async createWebhook(e,{avatar:t,reason:i}={}){return"string"!=typeof t||t.startsWith("data:")||(t=await c.resolveImage(t)),this.client.api.channels[this.id].webhooks.post({data:{name:e,avatar:t},reason:i}).then(e=>new n(this.client,e))}get lastMessage(){}get lastPinAt(){}send(){}startTyping(){}stopTyping(){}get typing(){}get typingCount(){}createMessageCollector(){}awaitMessages(){}bulkDelete(){}}r.applyToClass(TextChannel,!0),e.exports=TextChannel},function(e,t,i){"use strict";const s=i(19),{TypeError:n}=i(3),r=i(8),o=i(4);e.exports=class PermissionOverwrites{constructor(e,t){Object.defineProperty(this,"channel",{value:e}),t&&this._patch(t)}_patch(e){this.id=e.id,this.type=e.type,this.deny=new r(e.deny).freeze(),this.allow=new r(e.allow).freeze()}update(e,t){const{allow:i,deny:s}=this.constructor.resolveOverwriteOptions(e,this);return this.channel.client.api.channels(this.channel.id).permissions[this.id].put({data:{id:this.id,type:this.type,allow:i.bitfield,deny:s.bitfield},reason:t}).then(()=>this)}delete(e){return this.channel.client.api.channels[this.channel.id].permissions[this.id].delete({reason:e}).then(()=>this)}toJSON(){return o.flatten(this)}static resolveOverwriteOptions(e,{allow:t,deny:i}={}){t=new r(t),i=new r(i);for(const[s,n]of Object.entries(e))!0===n?(t.add(r.FLAGS[s]),i.remove(r.FLAGS[s])):!1===n?(t.remove(r.FLAGS[s]),i.add(r.FLAGS[s])):null===n&&(t.remove(r.FLAGS[s]),i.remove(r.FLAGS[s]));return{allow:t,deny:i}}static resolve(e,t){if(e instanceof this)return e.toJSON();if("string"==typeof e.id&&["role","member"].includes(e.type))return{...e,allow:r.resolve(e.allow),deny:r.resolve(e.deny)};const i=t.roles.resolve(e.id)||t.client.users.resolve(e.id);if(!i)throw new n("INVALID_TYPE","parameter","User nor a Role",!0);const o=i instanceof s?"role":"member";return{id:i.id,type:o,allow:r.resolve(e.allow),deny:r.resolve(e.deny)}}}},function(e,t,i){"use strict";const s=i(4);e.exports=class MessageAttachment{constructor(e,t=null,i){this.attachment=e,this.name=t,i&&this._patch(i)}setFile(e,t=null){return this.attachment=e,this.name=t,this}setName(e){return this.name=e,this}_patch(e){this.id=e.id,this.size=e.size,this.url=e.url,this.proxyURL=e.proxy_url,this.height=void 0!==e.height?e.height:null,this.width=void 0!==e.width?e.width:null}get spoiler(){return s.basename(this.url).startsWith("SPOILER_")}toJSON(){return s.flatten(this)}}},function(e,t,i){"use strict";const{RangeError:s}=i(3),n=i(4);e.exports=class MessageEmbed{constructor(e={},t=!1){this.setup(e,t)}setup(e,t){this.type=e.type,this.title=e.title,this.description=e.description,this.url=e.url,this.color=n.resolveColor(e.color),this.timestamp=e.timestamp?new Date(e.timestamp).getTime():null,this.fields=[],e.fields&&(this.fields=t?e.fields.map(n.cloneObject):this.constructor.normalizeFields(e.fields)),this.thumbnail=e.thumbnail?{url:e.thumbnail.url,proxyURL:e.thumbnail.proxyURL||e.thumbnail.proxy_url,height:e.thumbnail.height,width:e.thumbnail.width}:null,this.image=e.image?{url:e.image.url,proxyURL:e.image.proxyURL||e.image.proxy_url,height:e.image.height,width:e.image.width}:null,this.video=e.video?{url:e.video.url,proxyURL:e.video.proxyURL||e.video.proxy_url,height:e.video.height,width:e.video.width}:null,this.author=e.author?{name:e.author.name,url:e.author.url,iconURL:e.author.iconURL||e.author.icon_url,proxyIconURL:e.author.proxyIconURL||e.author.proxy_icon_url}:null,this.provider=e.provider?{name:e.provider.name,url:e.provider.name}:null,this.footer=e.footer?{text:e.footer.text,iconURL:e.footer.iconURL||e.footer.icon_url,proxyIconURL:e.footer.proxyIconURL||e.footer.proxy_icon_url}:null,this.files=e.files||[]}get createdAt(){return this.timestamp?new Date(this.timestamp):null}get hexColor(){return this.color?`#${this.color.toString(16).padStart(6,"0")}`:null}get length(){return(this.title?this.title.length:0)+(this.description?this.description.length:0)+(this.fields.length>=1?this.fields.reduce((e,t)=>e+t.name.length+t.value.length,0):0)+(this.footer?this.footer.text.length:0)}addField(e,t,i){return this.addFields({name:e,value:t,inline:i})}addFields(...e){return this.fields.push(...this.constructor.normalizeFields(e)),this}spliceFields(e,t,...i){return this.fields.splice(e,t,...this.constructor.normalizeFields(...i)),this}attachFiles(e){return this.files=this.files.concat(e),this}setAuthor(e,t,i){return this.author={name:n.resolveString(e),iconURL:t,url:i},this}setColor(e){return this.color=n.resolveColor(e),this}setDescription(e){return e=n.resolveString(e),this.description=e,this}setFooter(e,t){return e=n.resolveString(e),this.footer={text:e,iconURL:t},this}setImage(e){return this.image={url:e},this}setThumbnail(e){return this.thumbnail={url:e},this}setTimestamp(e=Date.now()){return e instanceof Date&&(e=e.getTime()),this.timestamp=e,this}setTitle(e){return e=n.resolveString(e),this.title=e,this}setURL(e){return this.url=e,this}toJSON(){return{title:this.title,type:"rich",description:this.description,url:this.url,timestamp:this.timestamp?new Date(this.timestamp):null,color:this.color,fields:this.fields,thumbnail:this.thumbnail,image:this.image,author:this.author?{name:this.author.name,url:this.author.url,icon_url:this.author.iconURL}:null,footer:this.footer?{text:this.footer.text,icon_url:this.footer.iconURL}:null}}static normalizeField(e,t,i=!1){if(!(e=n.resolveString(e)))throw new s("EMBED_FIELD_NAME");if(!(t=n.resolveString(t)))throw new s("EMBED_FIELD_VALUE");return{name:e,value:t,inline:i}}static normalizeFields(...e){return e.flat(2).map(e=>this.normalizeField(e&&e.name,e&&e.value,!(!e||"boolean"!=typeof e.inline)&&e.inline))}}},function(e,t,i){(t=e.exports=i(64)).Stream=t,t.Readable=t,t.Writable=i(46),t.Duplex=i(20),t.Transform=i(69),t.PassThrough=i(127)},function(e,t,i){"use strict";(function(t,s,n){var r=i(31);function o(e){var t=this;this.next=null,this.entry=null,this.finish=function(){!function(e,t,i){var s=e.entry;e.entry=null;for(;s;){var n=s.callback;t.pendingcb--,n(i),s=s.next}t.corkedRequestsFree?t.corkedRequestsFree.next=e:t.corkedRequestsFree=e}(t,e)}}e.exports=_;var a,c=!t.browser&&["v0.10","v0.9."].indexOf(t.version.slice(0,5))>-1?s:r.nextTick;_.WritableState=E;var l=Object.create(i(25));l.inherits=i(22);var h={deprecate:i(126)},u=i(66),d=i(32).Buffer,p=n.Uint8Array||function(){};var f,m=i(67);function g(){}function E(e,t){a=a||i(20),e=e||{};var s=t instanceof a;this.objectMode=!!e.objectMode,s&&(this.objectMode=this.objectMode||!!e.writableObjectMode);var n=e.highWaterMark,l=e.writableHighWaterMark,h=this.objectMode?16:16384;this.highWaterMark=n||0===n?n:s&&(l||0===l)?l:h,this.highWaterMark=Math.floor(this.highWaterMark),this.finalCalled=!1,this.needDrain=!1,this.ending=!1,this.ended=!1,this.finished=!1,this.destroyed=!1;var u=!1===e.decodeStrings;this.decodeStrings=!u,this.defaultEncoding=e.defaultEncoding||"utf8",this.length=0,this.writing=!1,this.corked=0,this.sync=!0,this.bufferProcessing=!1,this.onwrite=function(e){!function(e,t){var i=e._writableState,s=i.sync,n=i.writecb;if(function(e){e.writing=!1,e.writecb=null,e.length-=e.writelen,e.writelen=0}(i),t)!function(e,t,i,s,n){--t.pendingcb,i?(r.nextTick(n,s),r.nextTick(S,e,t),e._writableState.errorEmitted=!0,e.emit("error",s)):(n(s),e._writableState.errorEmitted=!0,e.emit("error",s),S(e,t))}(e,i,s,t,n);else{var o=w(i);o||i.corked||i.bufferProcessing||!i.bufferedRequest||v(e,i),s?c(b,e,i,o,n):b(e,i,o,n)}}(t,e)},this.writecb=null,this.writelen=0,this.bufferedRequest=null,this.lastBufferedRequest=null,this.pendingcb=0,this.prefinished=!1,this.errorEmitted=!1,this.bufferedRequestCount=0,this.corkedRequestsFree=new o(this)}function _(e){if(a=a||i(20),!(f.call(_,this)||this instanceof a))return new _(e);this._writableState=new E(e,this),this.writable=!0,e&&("function"==typeof e.write&&(this._write=e.write),"function"==typeof e.writev&&(this._writev=e.writev),"function"==typeof e.destroy&&(this._destroy=e.destroy),"function"==typeof e.final&&(this._final=e.final)),u.call(this)}function y(e,t,i,s,n,r,o){t.writelen=s,t.writecb=o,t.writing=!0,t.sync=!0,i?e._writev(n,t.onwrite):e._write(n,r,t.onwrite),t.sync=!1}function b(e,t,i,s){i||function(e,t){0===t.length&&t.needDrain&&(t.needDrain=!1,e.emit("drain"))}(e,t),t.pendingcb--,s(),S(e,t)}function v(e,t){t.bufferProcessing=!0;var i=t.bufferedRequest;if(e._writev&&i&&i.next){var s=t.bufferedRequestCount,n=new Array(s),r=t.corkedRequestsFree;r.entry=i;for(var a=0,c=!0;i;)n[a]=i,i.isBuf||(c=!1),i=i.next,a+=1;n.allBuffers=c,y(e,t,!0,t.length,n,"",r.finish),t.pendingcb++,t.lastBufferedRequest=null,r.next?(t.corkedRequestsFree=r.next,r.next=null):t.corkedRequestsFree=new o(t),t.bufferedRequestCount=0}else{for(;i;){var l=i.chunk,h=i.encoding,u=i.callback;if(y(e,t,!1,t.objectMode?1:l.length,l,h,u),i=i.next,t.bufferedRequestCount--,t.writing)break}null===i&&(t.lastBufferedRequest=null)}t.bufferedRequest=i,t.bufferProcessing=!1}function w(e){return e.ending&&0===e.length&&null===e.bufferedRequest&&!e.finished&&!e.writing}function A(e,t){e._final((function(i){t.pendingcb--,i&&e.emit("error",i),t.prefinished=!0,e.emit("prefinish"),S(e,t)}))}function S(e,t){var i=w(t);return i&&(!function(e,t){t.prefinished||t.finalCalled||("function"==typeof e._final?(t.pendingcb++,t.finalCalled=!0,r.nextTick(A,e,t)):(t.prefinished=!0,e.emit("prefinish")))}(e,t),0===t.pendingcb&&(t.finished=!0,e.emit("finish"))),i}l.inherits(_,u),E.prototype.getBuffer=function(){for(var e=this.bufferedRequest,t=[];e;)t.push(e),e=e.next;return t},function(){try{Object.defineProperty(E.prototype,"buffer",{get:h.deprecate((function(){return this.getBuffer()}),"_writableState.buffer is deprecated. Use _writableState.getBuffer instead.","DEP0003")})}catch(e){}}(),"function"==typeof Symbol&&Symbol.hasInstance&&"function"==typeof Function.prototype[Symbol.hasInstance]?(f=Function.prototype[Symbol.hasInstance],Object.defineProperty(_,Symbol.hasInstance,{value:function(e){return!!f.call(this,e)||this===_&&(e&&e._writableState instanceof E)}})):f=function(e){return e instanceof this},_.prototype.pipe=function(){this.emit("error",new Error("Cannot pipe, not readable"))},_.prototype.write=function(e,t,i){var s,n=this._writableState,o=!1,a=!n.objectMode&&(s=e,d.isBuffer(s)||s instanceof p);return a&&!d.isBuffer(e)&&(e=function(e){return d.from(e)}(e)),"function"==typeof t&&(i=t,t=null),a?t="buffer":t||(t=n.defaultEncoding),"function"!=typeof i&&(i=g),n.ended?function(e,t){var i=new Error("write after end");e.emit("error",i),r.nextTick(t,i)}(this,i):(a||function(e,t,i,s){var n=!0,o=!1;return null===i?o=new TypeError("May not write null values to stream"):"string"==typeof i||void 0===i||t.objectMode||(o=new TypeError("Invalid non-string/buffer chunk")),o&&(e.emit("error",o),r.nextTick(s,o),n=!1),n}(this,n,e,i))&&(n.pendingcb++,o=function(e,t,i,s,n,r){if(!i){var o=function(e,t,i){e.objectMode||!1===e.decodeStrings||"string"!=typeof t||(t=d.from(t,i));return t}(t,s,n);s!==o&&(i=!0,n="buffer",s=o)}var a=t.objectMode?1:s.length;t.length+=a;var c=t.length<t.highWaterMark;c||(t.needDrain=!0);if(t.writing||t.corked){var l=t.lastBufferedRequest;t.lastBufferedRequest={chunk:s,encoding:n,isBuf:i,callback:r,next:null},l?l.next=t.lastBufferedRequest:t.bufferedRequest=t.lastBufferedRequest,t.bufferedRequestCount+=1}else y(e,t,!1,a,s,n,r);return c}(this,n,a,e,t,i)),o},_.prototype.cork=function(){this._writableState.corked++},_.prototype.uncork=function(){var e=this._writableState;e.corked&&(e.corked--,e.writing||e.corked||e.finished||e.bufferProcessing||!e.bufferedRequest||v(this,e))},_.prototype.setDefaultEncoding=function(e){if("string"==typeof e&&(e=e.toLowerCase()),!(["hex","utf8","utf-8","ascii","binary","base64","ucs2","ucs-2","utf16le","utf-16le","raw"].indexOf((e+"").toLowerCase())>-1))throw new TypeError("Unknown encoding: "+e);return this._writableState.defaultEncoding=e,this},Object.defineProperty(_.prototype,"writableHighWaterMark",{enumerable:!1,get:function(){return this._writableState.highWaterMark}}),_.prototype._write=function(e,t,i){i(new Error("_write() is not implemented"))},_.prototype._writev=null,_.prototype.end=function(e,t,i){var s=this._writableState;"function"==typeof e?(i=e,e=null,t=null):"function"==typeof t&&(i=t,t=null),null!=e&&this.write(e,t),s.corked&&(s.corked=1,this.uncork()),s.ending||s.finished||function(e,t,i){t.ending=!0,S(e,t),i&&(t.finished?r.nextTick(i):e.once("finish",i));t.ended=!0,e.writable=!1}(this,s,i)},Object.defineProperty(_.prototype,"destroyed",{get:function(){return void 0!==this._writableState&&this._writableState.destroyed},set:function(e){this._writableState&&(this._writableState.destroyed=e)}}),_.prototype.destroy=m.destroy,_.prototype._undestroy=m.undestroy,_.prototype._destroy=function(e,t){this.end(),t(e)}}).call(this,i(14),i(38).setImmediate,i(21))},function(e,t,i){"use strict";const s=i(11);class MessageFlags extends s{}MessageFlags.FLAGS={CROSSPOSTED:1,IS_CROSSPOST:2,SUPPRESS_EMBEDS:4,SOURCE_MESSAGE_DELETED:8,URGENT:16},e.exports=MessageFlags},function(e,t,i){"use strict";const s=i(37);class WebhookClient extends s{constructor(e,t,i){super(i),Object.defineProperty(this,"client",{value:this}),this.id=e,Object.defineProperty(this,"token",{value:t,writable:!0,configurable:!0})}}i(12).applyToClass(WebhookClient),e.exports=WebhookClient},function(e,t,i){"use strict";const s=i(15),n=i(2),r=i(4);e.exports=class Collector extends s{constructor(e,t,i={}){super(),Object.defineProperty(this,"client",{value:e}),this.filter=t,this.options=i,this.collected=new n,this.ended=!1,this._timeout=null,this._idletimeout=null,this.handleCollect=this.handleCollect.bind(this),this.handleDispose=this.handleDispose.bind(this),i.time&&(this._timeout=this.client.setTimeout(()=>this.stop("time"),i.time)),i.idle&&(this._idletimeout=this.client.setTimeout(()=>this.stop("idle"),i.idle))}handleCollect(...e){const t=this.collect(...e);t&&this.filter(...e,this.collected)&&(this.collected.set(t,e[0]),this.emit("collect",...e),this._idletimeout&&(this.client.clearTimeout(this._idletimeout),this._idletimeout=this.client.setTimeout(()=>this.stop("idle"),this.options.idle))),this.checkEnd()}handleDispose(...e){if(!this.options.dispose)return;const t=this.dispose(...e);t&&this.filter(...e)&&this.collected.has(t)&&(this.collected.delete(t),this.emit("dispose",...e),this.checkEnd())}get next(){return new Promise((e,t)=>{if(this.ended)return void t(this.collected);const i=()=>{this.removeListener("collect",s),this.removeListener("end",n)},s=t=>{i(),e(t)},n=()=>{i(),t(this.collected)};this.on("collect",s),this.on("end",n)})}stop(e="user"){this.ended||(this._timeout&&(this.client.clearTimeout(this._timeout),this._timeout=null),this._idletimeout&&(this.client.clearTimeout(this._idletimeout),this._idletimeout=null),this.ended=!0,this.emit("end",this.collected,e))}resetTimer({time:e,idle:t}={}){this._timeout&&(this.client.clearTimeout(this._timeout),this._timeout=this.client.setTimeout(()=>this.stop("time"),e||this.options.time)),this._idletimeout&&(this.client.clearTimeout(this._idletimeout),this._idletimeout=this.client.setTimeout(()=>this.stop("idle"),t||this.options.idle))}checkEnd(){const e=this.endReason();e&&this.stop(e)}async*[Symbol.asyncIterator](){const e=[],t=t=>e.push(t);this.on("collect",t);try{for(;e.length||!this.ended;)e.length?yield e.shift():await new Promise(e=>{const t=()=>(this.removeListener("collect",t),this.removeListener("end",t),e());this.on("collect",t),this.on("end",t)})}finally{this.removeListener("collect",t)}}toJSON(){return r.flatten(this)}collect(){}dispose(){}endReason(){}}},function(e,t,i){"use strict";const s=i(5),n=i(73),{ClientApplicationAssetTypes:r,Endpoints:o}=i(0),a=i(7),c=Object.keys(r);e.exports=class ClientApplication extends s{constructor(e,t){super(e),this._patch(t)}_patch(e){this.id=e.id,this.name=e.name,this.description=e.description,this.icon=e.icon,this.cover=e.cover_image||null,this.rpcOrigins=e.rpc_origins||[],this.botRequireCodeGrant=void 0!==e.bot_require_code_grant?e.bot_require_code_grant:null,this.botPublic=void 0!==e.bot_public?e.bot_public:null,this.owner=e.team?new n(this.client,e.team):e.owner?this.client.users.add(e.owner):null}get createdTimestamp(){return a.deconstruct(this.id).timestamp}get createdAt(){return new Date(this.createdTimestamp)}iconURL({format:e,size:t}={}){return this.icon?this.client.rest.cdn.AppIcon(this.id,this.icon,{format:e,size:t}):null}coverImage({format:e,size:t}={}){return this.cover?o.CDN(this.client.options.http.cdn).AppIcon(this.id,this.cover,{format:e,size:t}):null}fetchAssets(){return this.client.api.oauth2.applications(this.id).assets.get().then(e=>e.map(e=>({id:e.id,name:e.name,type:c[e.type-1]})))}toString(){return this.name}toJSON(){return super.toJSON({createdTimestamp:!0})}}},function(e,t,i){"use strict";const s=i(24),n=i(52),r=i(77),o=i(4);e.exports=class MessageReaction{constructor(e,t,i){Object.defineProperty(this,"client",{value:e}),this.message=i,this.me=t.me,this.users=new r(e,void 0,this),this._emoji=new n(this,t.emoji),this._patch(t)}_patch(e){null==this.count&&(this.count=e.count)}async remove(){return await this.client.api.channels(this.message.channel.id).messages(this.message.id).reactions(this._emoji.identifier).delete(),this}get emoji(){if(this._emoji instanceof s)return this._emoji;if(this._emoji.id){const e=this.message.client.emojis.cache;if(e.has(this._emoji.id)){const t=e.get(this._emoji.id);return this._emoji=t,t}}return this._emoji}get partial(){return null===this.count}async fetch(){const e=(await this.message.fetch()).reactions.cache.get(this.emoji.id||this.emoji.name);return this._patch(e||{count:0}),this}toJSON(){return o.flatten(this,{emoji:"emojiID",message:"messageID"})}_add(e){this.partial||(this.users.cache.set(e.id,e),this.me&&e.id===this.message.client.user.id&&0!==this.count||this.count++,this.me||(this.me=e.id===this.message.client.user.id))}_remove(e){this.partial||(this.users.cache.delete(e.id),this.me&&e.id===this.message.client.user.id||this.count--,e.id===this.message.client.user.id&&(this.me=!1),this.count<=0&&0===this.users.cache.size&&this.message.reactions.cache.delete(this.emoji.id||this.emoji.name))}}},function(e,t,i){"use strict";const s=i(29),n=i(4);e.exports=class ReactionEmoji extends s{constructor(e,t){super(e.message.client,t),this.reaction=e}toJSON(){return n.flatten(this,{identifier:!0})}valueOf(){return this.id}}},function(e,t,i){"use strict";const s=i(5),n=i(83),r=i(55),o=i(54),a=i(18),c=i(56),l=i(12),h=i(84),u=i(57),d=i(85),p=i(86),f=i(87),m=i(135),g=i(2),{ChannelTypes:E,DefaultMessageNotifications:_,PartialTypes:y,VerificationLevels:b,ExplicitContentFilterLevels:v}=i(0),w=i(9),A=i(7),S=i(88),I=i(4);e.exports=class Guild extends s{constructor(e,t){super(e),this.members=new d(this),this.channels=new h(this),this.roles=new f(this),this.presences=new p(this.client),this.voiceStates=new m(this),this.deleted=!1,t&&(t.unavailable?(this.available=!1,this.id=t.id):(this._patch(t),t.channels||(this.available=!1)),this.shardID=t.shardID)}get shard(){return this.client.ws.shards.get(this.shardID)}_patch(e){if(this.name=e.name,this.icon=e.icon,this.splash=e.splash,this.region=e.region,this.memberCount=e.member_count||this.memberCount,this.large=Boolean("large"in e?e.large:this.large),this.features=e.features,this.applicationID=e.application_id,this.afkTimeout=e.afk_timeout,this.afkChannelID=e.afk_channel_id,this.systemChannelID=e.system_channel_id,this.embedEnabled=e.embed_enabled,this.premiumTier=e.premium_tier,void 0!==e.premium_subscription_count&&(this.premiumSubscriptionCount=e.premium_subscription_count),void 0!==e.widget_enabled&&(this.widgetEnabled=e.widget_enabled),void 0!==e.widget_channel_id&&(this.widgetChannelID=e.widget_channel_id),void 0!==e.embed_channel_id&&(this.embedChannelID=e.embed_channel_id),this.verificationLevel=b[e.verification_level],this.explicitContentFilter=v[e.explicit_content_filter],this.mfaLevel=e.mfa_level,this.joinedTimestamp=e.joined_at?new Date(e.joined_at).getTime():this.joinedTimestamp,this.defaultMessageNotifications=_[e.default_message_notifications]||e.default_message_notifications,this.systemChannelFlags=new S(e.system_channel_flags).freeze(),void 0!==e.max_members&&(this.maximumMembers=e.max_members||25e4),void 0!==e.max_presences&&(this.maximumPresences=e.max_presences||25e3),this.vanityURLCode=e.vanity_url_code,this.description=e.description,this.banner=e.banner,this.id=e.id,this.available=!e.unavailable,this.features=e.features||this.features||[],this.rulesChannelID=e.rules_channel_id,this.publicUpdatesChannelID=e.public_updates_channel_id,e.channels){this.channels.cache.clear();for(const t of e.channels)this.client.channels.add(t,this)}if(e.roles){this.roles.cache.clear();for(const t of e.roles)this.roles.add(t)}if(e.members){this.members.cache.clear();for(const t of e.members)this.members.add(t)}if(e.owner_id&&(this.ownerID=e.owner_id),e.presences)for(const t of e.presences)this.presences.add(Object.assign(t,{guild:this}));if(e.voice_states){this.voiceStates.cache.clear();for(const t of e.voice_states)this.voiceStates.add(t)}if(this.emojis)e.emojis&&this.client.actions.GuildEmojisUpdate.handle({guild_id:this.id,emojis:e.emojis});else if(this.emojis=new u(this),e.emojis)for(const t of e.emojis)this.emojis.add(t)}bannerURL({format:e,size:t}={}){return this.banner?this.client.rest.cdn.Banner(this.id,this.banner,e,t):null}get createdTimestamp(){return A.deconstruct(this.id).timestamp}get createdAt(){return new Date(this.createdTimestamp)}get joinedAt(){return new Date(this.joinedTimestamp)}get partnered(){return this.features.includes("PARTNERED")}get verified(){return this.features.includes("VERIFIED")}iconURL({format:e,size:t,dynamic:i}={}){return this.icon?this.client.rest.cdn.Icon(this.id,this.icon,e,t,i):null}get nameAcronym(){return this.name.replace(/\w+/g,e=>e[0]).replace(/\s/g,"")}splashURL({format:e,size:t}={}){return this.splash?this.client.rest.cdn.Splash(this.id,this.splash,e,t):null}get owner(){return this.members.cache.get(this.ownerID)||(this.client.options.partials.includes(y.GUILD_MEMBER)?this.members.add({user:{id:this.ownerID}},!0):null)}get afkChannel(){return this.client.channels.cache.get(this.afkChannelID)||null}get systemChannel(){return this.client.channels.cache.get(this.systemChannelID)||null}get widgetChannel(){return this.client.channels.cache.get(this.widgetChannelID)||null}get embedChannel(){return this.client.channels.cache.get(this.embedChannelID)||null}get rulesChannel(){return this.client.channels.cache.get(this.rulesChannelID)||null}get publicUpdatesChannel(){return this.client.channels.cache.get(this.publicUpdatesChannelID)||null}get me(){return this.members.cache.get(this.client.user.id)||(this.client.options.partials.includes(y.GUILD_MEMBER)?this.members.add({user:{id:this.client.user.id}},!0):null)}get voice(){return this.voiceStates.cache.get(this.client.user.id)}member(e){return this.members.resolve(e)}fetch(){return this.client.api.guilds(this.id).get().then(e=>(this._patch(e),this))}fetchBan(e){const t=this.client.users.resolveID(e);if(!t)throw new Error("FETCH_BAN_RESOLVE_ID");return this.client.api.guilds(this.id).bans(t).get().then(e=>({reason:e.reason,user:this.client.users.add(e.user)}))}fetchBans(){return this.client.api.guilds(this.id).bans.get().then(e=>e.reduce((e,t)=>(e.set(t.user.id,{reason:t.reason,user:this.client.users.add(t.user)}),e),new g))}fetchIntegrations(){return this.client.api.guilds(this.id).integrations.get().then(e=>e.reduce((e,t)=>e.set(t.id,new o(this.client,t,this)),new g))}createIntegration(e,t){return this.client.api.guilds(this.id).integrations.post({data:e,reason:t}).then(()=>this)}fetchInvites(){return this.client.api.guilds(this.id).invites.get().then(e=>{const t=new g;for(const i of e){const e=new a(this.client,i);t.set(e.code,e)}return t})}fetchPreview(){return this.client.api.guilds(this.id).preview.get().then(e=>new r(this.client,e))}fetchVanityCode(){return this.features.includes("VANITY_URL")?this.client.api.guilds(this.id,"vanity-url").get().then(e=>e.code):Promise.reject(new Error("VANITY_URL"))}fetchWebhooks(){return this.client.api.guilds(this.id).webhooks.get().then(e=>{const t=new g;for(const i of e)t.set(i.id,new l(this.client,i));return t})}fetchVoiceRegions(){return this.client.api.guilds(this.id).regions.get().then(e=>{const t=new g;for(const i of e)t.set(i.id,new c(i));return t})}fetchEmbed(){return this.client.api.guilds(this.id).embed.get().then(e=>({enabled:e.enabled,channel:e.channel_id?this.channels.cache.get(e.channel_id):null}))}fetchAuditLogs(e={}){return e.before&&e.before instanceof n.Entry&&(e.before=e.before.id),"string"==typeof e.type&&(e.type=n.Actions[e.type]),this.client.api.guilds(this.id)["audit-logs"].get({query:{before:e.before,limit:e.limit,user_id:this.client.users.resolveID(e.user),action_type:e.type}}).then(e=>n.build(this,e))}addMember(e,t){if(!(e=this.client.users.resolveID(e)))return Promise.reject(new TypeError("INVALID_TYPE","user","UserResolvable"));if(this.members.cache.has(e))return Promise.resolve(this.members.cache.get(e));if(t.access_token=t.accessToken,t.roles){const e=[];for(let i of t.roles instanceof g?t.roles.values():t.roles){if(i=this.roles.resolve(i),!i)return Promise.reject(new TypeError("INVALID_TYPE","options.roles","Array or Collection of Roles or Snowflakes",!0));e.push(i.id)}t.roles=e}return this.client.api.guilds(this.id).members(e).put({data:t}).then(e=>this.members.add(e))}edit(e,t){const i={};return e.name&&(i.name=e.name),e.region&&(i.region=e.region),void 0!==e.verificationLevel&&(i.verification_level="number"==typeof e.verificationLevel?Number(e.verificationLevel):b.indexOf(e.verificationLevel)),void 0!==e.afkChannel&&(i.afk_channel_id=this.client.channels.resolveID(e.afkChannel)),void 0!==e.systemChannel&&(i.system_channel_id=this.client.channels.resolveID(e.systemChannel)),e.afkTimeout&&(i.afk_timeout=Number(e.afkTimeout)),void 0!==e.icon&&(i.icon=e.icon),e.owner&&(i.owner_id=this.client.users.resolveID(e.owner)),e.splash&&(i.splash=e.splash),e.banner&&(i.banner=e.banner),void 0!==e.explicitContentFilter&&(i.explicit_content_filter="number"==typeof e.explicitContentFilter?e.explicitContentFilter:v.indexOf(e.explicitContentFilter)),void 0!==e.defaultMessageNotifications&&(i.default_message_notifications="string"==typeof e.defaultMessageNotifications?_.indexOf(e.defaultMessageNotifications):e.defaultMessageNotifications),void 0!==e.systemChannelFlags&&(i.system_channel_flags=S.resolve(e.systemChannelFlags)),this.client.api.guilds(this.id).patch({data:i,reason:t}).then(e=>this.client.actions.GuildUpdate.handle(e).updated)}setExplicitContentFilter(e,t){return this.edit({explicitContentFilter:e},t)}setDefaultMessageNotifications(e,t){return this.edit({defaultMessageNotifications:e},t)}setSystemChannelFlags(e,t){return this.edit({systemChannelFlags:e},t)}setName(e,t){return this.edit({name:e},t)}setRegion(e,t){return this.edit({region:e},t)}setVerificationLevel(e,t){return this.edit({verificationLevel:e},t)}setAFKChannel(e,t){return this.edit({afkChannel:e},t)}setSystemChannel(e,t){return this.edit({systemChannel:e},t)}setAFKTimeout(e,t){return this.edit({afkTimeout:e},t)}async setIcon(e,t){return this.edit({icon:await w.resolveImage(e),reason:t})}setOwner(e,t){return this.edit({owner:e},t)}async setSplash(e,t){return this.edit({splash:await w.resolveImage(e),reason:t})}async setBanner(e,t){return this.edit({banner:await w.resolveImage(e),reason:t})}setChannelPositions(e){const t=e.map(e=>({id:this.client.channels.resolveID(e.channel),position:e.position}));return this.client.api.guilds(this.id).channels.patch({data:t}).then(()=>this.client.actions.GuildChannelsPositionUpdate.handle({guild_id:this.id,channels:t}).guild)}setRolePositions(e){return e=e.map(e=>({id:this.roles.resolveID(e.role),position:e.position})),this.client.api.guilds(this.id).roles.patch({data:e}).then(()=>this.client.actions.GuildRolesPositionUpdate.handle({guild_id:this.id,roles:e}).guild)}setEmbed(e,t){return this.client.api.guilds(this.id).embed.patch({data:{enabled:e.enabled,channel_id:this.channels.resolveID(e.channel)},reason:t}).then(()=>this)}leave(){return this.ownerID===this.client.user.id?Promise.reject(new Error("GUILD_OWNED")):this.client.api.users("@me").guilds(this.id).delete().then(()=>this.client.actions.GuildDelete.handle({id:this.id}).guild)}delete(){return this.client.api.guilds(this.id).delete().then(()=>this.client.actions.GuildDelete.handle({id:this.id}).guild)}equals(e){let t=e&&e instanceof this.constructor&&this.id===e.id&&this.available===e.available&&this.splash===e.splash&&this.region===e.region&&this.name===e.name&&this.memberCount===e.memberCount&&this.large===e.large&&this.icon===e.icon&&this.ownerID===e.ownerID&&this.verificationLevel===e.verificationLevel&&this.embedEnabled===e.embedEnabled&&(this.features===e.features||this.features.length===e.features.length&&this.features.every((t,i)=>t===e.features[i]));return t&&(this.embedChannel?e.embedChannel&&this.embedChannel.id===e.embedChannel.id||(t=!1):e.embedChannel&&(t=!1)),t}toString(){return this.name}toJSON(){const e=super.toJSON({available:!1,createdTimestamp:!0,nameAcronym:!0,presences:!1,voiceStates:!1});return e.iconURL=this.iconURL(),e.splashURL=this.splashURL(),e.bannerURL=this.bannerURL(),e}_sortedRoles(){return I.discordSort(this.roles.cache)}_sortedChannels(e){const t=e.type===E.CATEGORY;return I.discordSort(this.channels.cache.filter(i=>(["text","news","store"].includes(e.type)?["text","news","store"].includes(i.type):i.type===e.type)&&(t||i.parent===e.parent)))}}},function(e,t,i){"use strict";const s=i(5);e.exports=class Integration extends s{constructor(e,t,i){super(e),this.guild=i,this.id=t.id,this.name=t.name,this.type=t.type,this.enabled=t.enabled,this.syncing=t.syncing,this.role=this.guild.roles.cache.get(t.role_id),this.user=this.client.users.add(t.user),this.account=t.account,this.syncedAt=t.synced_at,this._patch(t)}_patch(e){this.expireBehavior=e.expire_behavior,this.expireGracePeriod=e.expire_grace_period}sync(){return this.syncing=!0,this.client.api.guilds(this.guild.id).integrations(this.id).post().then(()=>(this.syncing=!1,this.syncedAt=Date.now(),this))}edit(e,t){return"expireBehavior"in e&&(e.expire_behavior=e.expireBehavior,e.expireBehavior=null),"expireGracePeriod"in e&&(e.expire_grace_period=e.expireGracePeriod,e.expireGracePeriod=null),this.client.api.guilds(this.guild.id).integrations(this.id).patch({data:e,reason:t}).then(()=>(this._patch(e),this))}delete(e){return this.client.api.guilds(this.guild.id).integrations(this.id).delete({reason:e}).then(()=>this)}toJSON(){return super.toJSON({role:"roleID",guild:"guildID",user:"userID"})}}},function(e,t,i){"use strict";const s=i(5),n=i(134),r=i(2);e.exports=class GuildPreview extends s{constructor(e,t){super(e),t&&this._patch(t)}_patch(e){this.id=e.id,this.name=e.name,this.icon=e.icon,this.splash=e.splash,this.discoverySplash=e.discovery_splash,this.features=e.features,this.approximateMemberCount=e.approximate_member_count,this.approximatePresenceCount=e.approximate_presence_count,this.description=e.description,this.emojis?this.emojis.clear():this.emojis=new r;for(const t of e.emojis)this.emojis.set(t.id,new n(this.client,t,this))}splashURL({format:e,size:t}={}){return this.splash?this.client.rest.cdn.Splash(this.id,this.splash,e,t):null}discoverySplashURL({format:e,size:t}={}){return this.discoverySplash?this.client.rest.cdn.DiscoverySplash(this.id,this.discoverySplash,e,t):null}iconURL({format:e,size:t,dynamic:i}={}){return this.icon?this.client.rest.cdn.Icon(this.id,this.icon,e,t,i):null}fetch(){return this.client.api.guilds(this.id).preview.get().then(e=>(this._patch(e),this))}toString(){return this.name}toJSON(){const e=super.toJSON();return e.iconURL=this.iconURL(),e.splashURL=this.splashURL(),e}}},function(e,t,i){"use strict";const s=i(4);e.exports=class VoiceRegion{constructor(e){this.id=e.id,this.name=e.name,this.vip=e.vip,this.deprecated=e.deprecated,this.optimal=e.optimal,this.custom=e.custom}toJSON(){return s.flatten(this)}}},function(e,t,i){"use strict";const s=i(6),{TypeError:n}=i(3),r=i(24),o=i(52),a=i(2),c=i(9);e.exports=class GuildEmojiManager extends s{constructor(e,t){super(e.client,t,r),this.guild=e}add(e,t){return super.add(e,t,{extras:[this.guild]})}async create(e,t,{roles:i,reason:s}={}){if(!(e=await c.resolveImage(e)))throw new n("REQ_RESOURCE_TYPE");const r={image:e,name:t};if(i){r.roles=[];for(let e of i instanceof a?i.values():i){if(e=this.guild.roles.resolve(e),!e)return Promise.reject(new n("INVALID_TYPE","options.roles","Array or Collection of Roles or Snowflakes",!0));r.roles.push(e.id)}}return this.client.api.guilds(this.guild.id).emojis.post({data:r,reason:s}).then(e=>this.client.actions.GuildEmojiCreate.handle(this.guild,e).emoji)}resolve(e){return e instanceof o?super.resolve(e.id):super.resolve(e)}resolveID(e){return e instanceof o?e.id:super.resolveID(e)}resolveIdentifier(e){const t=this.resolve(e);return t?t.identifier:e instanceof o?e.identifier:"string"==typeof e?e.includes("%")?e:encodeURIComponent(e):null}}},function(e,t){e.exports={version:"12.2.0",homepage:"https://github.com/discordjs/discord.js#readme"}},function(e,t,i){"use strict";const s=Symbol("code"),n=new Map;function r(e){return class DiscordjsError extends e{constructor(e,...t){super(function(e,t){if("string"!=typeof e)throw new Error("Error message key must be a string");const i=n.get(e);if(!i)throw new Error(`An invalid error message key was used: ${e}.`);return"function"==typeof i?i(...t):void 0===t||0===t.length?i:(t.unshift(i),String(...t))}(e,t)),this[s]=e,Error.captureStackTrace&&Error.captureStackTrace(this,DiscordjsError)}get name(){return`${super.name} [${this[s]}]`}get code(){return this[s]}}}e.exports={register:function(e,t){n.set(e,"function"==typeof t?t:String(t))},Error:r(Error),TypeError:r(TypeError),RangeError:r(RangeError)}},function(e,t,i){(function(e,t){!function(e,i){"use strict";if(!e.setImmediate){var s,n,r,o,a,c=1,l={},h=!1,u=e.document,d=Object.getPrototypeOf&&Object.getPrototypeOf(e);d=d&&d.setTimeout?d:e,"[object process]"==={}.toString.call(e.process)?s=function(e){t.nextTick((function(){f(e)}))}:!function(){if(e.postMessage&&!e.importScripts){var t=!0,i=e.onmessage;return e.onmessage=function(){t=!1},e.postMessage("","*"),e.onmessage=i,t}}()?e.MessageChannel?((r=new MessageChannel).port1.onmessage=function(e){f(e.data)},s=function(e){r.port2.postMessage(e)}):u&&"onreadystatechange"in u.createElement("script")?(n=u.documentElement,s=function(e){var t=u.createElement("script");t.onreadystatechange=function(){f(e),t.onreadystatechange=null,n.removeChild(t),t=null},n.appendChild(t)}):s=function(e){setTimeout(f,0,e)}:(o="setImmediate$"+Math.random()+"$",a=function(t){t.source===e&&"string"==typeof t.data&&0===t.data.indexOf(o)&&f(+t.data.slice(o.length))},e.addEventListener?e.addEventListener("message",a,!1):e.attachEvent("onmessage",a),s=function(t){e.postMessage(o+t,"*")}),d.setImmediate=function(e){"function"!=typeof e&&(e=new Function(""+e));for(var t=new Array(arguments.length-1),i=0;i<t.length;i++)t[i]=arguments[i+1];var n={callback:e,args:t};return l[c]=n,s(c),c++},d.clearImmediate=p}function p(e){delete l[e]}function f(e){if(h)setTimeout(f,0,e);else{var t=l[e];if(t){h=!0;try{!function(e){var t=e.callback,i=e.args;switch(i.length){case 0:t();break;case 1:t(i[0]);break;case 2:t(i[0],i[1]);break;case 3:t(i[0],i[1],i[2]);break;default:t.apply(void 0,i)}}(t)}finally{p(e),h=!1}}}}}("undefined"==typeof self?void 0===e?this:e:self)}).call(this,i(21),i(14))},function(e,t,i){"use strict";class DiscordAPIError extends Error{constructor(e,t,i,s){super();const n=this.constructor.flattenErrors(t.errors||t).join("\n");this.name="DiscordAPIError",this.message=t.message&&n?`${t.message}\n${n}`:t.message||n,this.method=i,this.path=e,this.code=t.code,this.httpStatus=s}static flattenErrors(e,t=""){let i=[];for(const[s,n]of Object.entries(e)){if("message"===s)continue;const e=t?isNaN(s)?`${t}.${s}`:`${t}[${s}]`:s;n._errors?i.push(`${e}: ${n._errors.map(e=>e.message).join(" ")}`):n.code||n.message?i.push(`${n.code?`${n.code}: `:""}${n.message}`.trim()):"string"==typeof n?i.push(n):i=i.concat(this.flattenErrors(n,e))}return i}}e.exports=DiscordAPIError},function(e,t,i){"use strict";class HTTPError extends Error{constructor(e,t,i,s,n){super(e),this.name=t,this.code=i||500,this.method=s,this.path=n}}e.exports=HTTPError},function(e,t,i){"use strict";const{TypeError:s}=i(3),n=i(2);e.exports=class GuildEmojiRoleManager{constructor(e){this.emoji=e,this.guild=e.guild,Object.defineProperty(this,"client",{value:e.client})}get _roles(){return this.guild.roles.cache.filter(e=>this.emoji._roles.includes(e.id))}get cache(){return this._roles}add(e){if(e instanceof n)return this.add(e.keyArray());if(!Array.isArray(e))return this.add([e]);if((e=e.map(e=>this.guild.roles.resolve(e))).includes(null))return Promise.reject(new s("INVALID_TYPE","roles","Array or Collection of Roles or Snowflakes",!0));const t=[...new Set(e.concat(...this._roles.values()))];return this.set(t)}remove(e){if(e instanceof n)return this.remove(e.keyArray());if(!Array.isArray(e))return this.remove([e]);if((e=e.map(e=>this.guild.roles.resolveID(e))).includes(null))return Promise.reject(new s("INVALID_TYPE","roles","Array or Collection of Roles or Snowflakes",!0));const t=this._roles.keyArray().filter(t=>!e.includes(t));return this.set(t)}set(e){return this.emoji.edit({roles:e})}clone(){const e=new this.constructor(this.emoji);return e._patch(this._roles.keyArray().slice()),e}_patch(e){this.emoji._roles=e}}},function(e,t,i){"use strict";(function(t,s){var n=i(31);e.exports=y;var r,o=i(65);y.ReadableState=_;i(15).EventEmitter;var a=function(e,t){return e.listeners(t).length},c=i(66),l=i(32).Buffer,h=t.Uint8Array||function(){};var u=Object.create(i(25));u.inherits=i(22);var d=i(123),p=void 0;p=d&&d.debuglog?d.debuglog("stream"):function(){};var f,m=i(124),g=i(67);u.inherits(y,c);var E=["error","close","destroy","pause","resume"];function _(e,t){e=e||{};var s=t instanceof(r=r||i(20));this.objectMode=!!e.objectMode,s&&(this.objectMode=this.objectMode||!!e.readableObjectMode);var n=e.highWaterMark,o=e.readableHighWaterMark,a=this.objectMode?16:16384;this.highWaterMark=n||0===n?n:s&&(o||0===o)?o:a,this.highWaterMark=Math.floor(this.highWaterMark),this.buffer=new m,this.length=0,this.pipes=null,this.pipesCount=0,this.flowing=null,this.ended=!1,this.endEmitted=!1,this.reading=!1,this.sync=!0,this.needReadable=!1,this.emittedReadable=!1,this.readableListening=!1,this.resumeScheduled=!1,this.destroyed=!1,this.defaultEncoding=e.defaultEncoding||"utf8",this.awaitDrain=0,this.readingMore=!1,this.decoder=null,this.encoding=null,e.encoding&&(f||(f=i(68).StringDecoder),this.decoder=new f(e.encoding),this.encoding=e.encoding)}function y(e){if(r=r||i(20),!(this instanceof y))return new y(e);this._readableState=new _(e,this),this.readable=!0,e&&("function"==typeof e.read&&(this._read=e.read),"function"==typeof e.destroy&&(this._destroy=e.destroy)),c.call(this)}function b(e,t,i,s,n){var r,o=e._readableState;null===t?(o.reading=!1,function(e,t){if(t.ended)return;if(t.decoder){var i=t.decoder.end();i&&i.length&&(t.buffer.push(i),t.length+=t.objectMode?1:i.length)}t.ended=!0,A(e)}(e,o)):(n||(r=function(e,t){var i;s=t,l.isBuffer(s)||s instanceof h||"string"==typeof t||void 0===t||e.objectMode||(i=new TypeError("Invalid non-string/buffer chunk"));var s;return i}(o,t)),r?e.emit("error",r):o.objectMode||t&&t.length>0?("string"==typeof t||o.objectMode||Object.getPrototypeOf(t)===l.prototype||(t=function(e){return l.from(e)}(t)),s?o.endEmitted?e.emit("error",new Error("stream.unshift() after end event")):v(e,o,t,!0):o.ended?e.emit("error",new Error("stream.push() after EOF")):(o.reading=!1,o.decoder&&!i?(t=o.decoder.write(t),o.objectMode||0!==t.length?v(e,o,t,!1):I(e,o)):v(e,o,t,!1))):s||(o.reading=!1));return function(e){return!e.ended&&(e.needReadable||e.length<e.highWaterMark||0===e.length)}(o)}function v(e,t,i,s){t.flowing&&0===t.length&&!t.sync?(e.emit("data",i),e.read(0)):(t.length+=t.objectMode?1:i.length,s?t.buffer.unshift(i):t.buffer.push(i),t.needReadable&&A(e)),I(e,t)}Object.defineProperty(y.prototype,"destroyed",{get:function(){return void 0!==this._readableState&&this._readableState.destroyed},set:function(e){this._readableState&&(this._readableState.destroyed=e)}}),y.prototype.destroy=g.destroy,y.prototype._undestroy=g.undestroy,y.prototype._destroy=function(e,t){this.push(null),t(e)},y.prototype.push=function(e,t){var i,s=this._readableState;return s.objectMode?i=!0:"string"==typeof e&&((t=t||s.defaultEncoding)!==s.encoding&&(e=l.from(e,t),t=""),i=!0),b(this,e,t,!1,i)},y.prototype.unshift=function(e){return b(this,e,null,!0,!1)},y.prototype.isPaused=function(){return!1===this._readableState.flowing},y.prototype.setEncoding=function(e){return f||(f=i(68).StringDecoder),this._readableState.decoder=new f(e),this._readableState.encoding=e,this};function w(e,t){return e<=0||0===t.length&&t.ended?0:t.objectMode?1:e!=e?t.flowing&&t.length?t.buffer.head.data.length:t.length:(e>t.highWaterMark&&(t.highWaterMark=function(e){return e>=8388608?e=8388608:(e--,e|=e>>>1,e|=e>>>2,e|=e>>>4,e|=e>>>8,e|=e>>>16,e++),e}(e)),e<=t.length?e:t.ended?t.length:(t.needReadable=!0,0))}function A(e){var t=e._readableState;t.needReadable=!1,t.emittedReadable||(p("emitReadable",t.flowing),t.emittedReadable=!0,t.sync?n.nextTick(S,e):S(e))}function S(e){p("emit readable"),e.emit("readable"),R(e)}function I(e,t){t.readingMore||(t.readingMore=!0,n.nextTick(T,e,t))}function T(e,t){for(var i=t.length;!t.reading&&!t.flowing&&!t.ended&&t.length<t.highWaterMark&&(p("maybeReadMore read 0"),e.read(0),i!==t.length);)i=t.length;t.readingMore=!1}function D(e){p("readable nexttick read 0"),e.read(0)}function N(e,t){t.reading||(p("resume read 0"),e.read(0)),t.resumeScheduled=!1,t.awaitDrain=0,e.emit("resume"),R(e),t.flowing&&!t.reading&&e.read(0)}function R(e){var t=e._readableState;for(p("flow",t.flowing);t.flowing&&null!==e.read(););}function O(e,t){return 0===t.length?null:(t.objectMode?i=t.buffer.shift():!e||e>=t.length?(i=t.decoder?t.buffer.join(""):1===t.buffer.length?t.buffer.head.data:t.buffer.concat(t.length),t.buffer.clear()):i=function(e,t,i){var s;e<t.head.data.length?(s=t.head.data.slice(0,e),t.head.data=t.head.data.slice(e)):s=e===t.head.data.length?t.shift():i?function(e,t){var i=t.head,s=1,n=i.data;e-=n.length;for(;i=i.next;){var r=i.data,o=e>r.length?r.length:e;if(o===r.length?n+=r:n+=r.slice(0,e),0===(e-=o)){o===r.length?(++s,i.next?t.head=i.next:t.head=t.tail=null):(t.head=i,i.data=r.slice(o));break}++s}return t.length-=s,n}(e,t):function(e,t){var i=l.allocUnsafe(e),s=t.head,n=1;s.data.copy(i),e-=s.data.length;for(;s=s.next;){var r=s.data,o=e>r.length?r.length:e;if(r.copy(i,i.length-e,0,o),0===(e-=o)){o===r.length?(++n,s.next?t.head=s.next:t.head=t.tail=null):(t.head=s,s.data=r.slice(o));break}++n}return t.length-=n,i}(e,t);return s}(e,t.buffer,t.decoder),i);var i}function C(e){var t=e._readableState;if(t.length>0)throw new Error('"endReadable()" called on non-empty stream');t.endEmitted||(t.ended=!0,n.nextTick(L,t,e))}function L(e,t){e.endEmitted||0!==e.length||(e.endEmitted=!0,t.readable=!1,t.emit("end"))}function M(e,t){for(var i=0,s=e.length;i<s;i++)if(e[i]===t)return i;return-1}y.prototype.read=function(e){p("read",e),e=parseInt(e,10);var t=this._readableState,i=e;if(0!==e&&(t.emittedReadable=!1),0===e&&t.needReadable&&(t.length>=t.highWaterMark||t.ended))return p("read: emitReadable",t.length,t.ended),0===t.length&&t.ended?C(this):A(this),null;if(0===(e=w(e,t))&&t.ended)return 0===t.length&&C(this),null;var s,n=t.needReadable;return p("need readable",n),(0===t.length||t.length-e<t.highWaterMark)&&p("length less than watermark",n=!0),t.ended||t.reading?p("reading or ended",n=!1):n&&(p("do read"),t.reading=!0,t.sync=!0,0===t.length&&(t.needReadable=!0),this._read(t.highWaterMark),t.sync=!1,t.reading||(e=w(i,t))),null===(s=e>0?O(e,t):null)?(t.needReadable=!0,e=0):t.length-=e,0===t.length&&(t.ended||(t.needReadable=!0),i!==e&&t.ended&&C(this)),null!==s&&this.emit("data",s),s},y.prototype._read=function(e){this.emit("error",new Error("_read() is not implemented"))},y.prototype.pipe=function(e,t){var i=this,r=this._readableState;switch(r.pipesCount){case 0:r.pipes=e;break;case 1:r.pipes=[r.pipes,e];break;default:r.pipes.push(e)}r.pipesCount+=1,p("pipe count=%d opts=%j",r.pipesCount,t);var c=(!t||!1!==t.end)&&e!==s.stdout&&e!==s.stderr?h:y;function l(t,s){p("onunpipe"),t===i&&s&&!1===s.hasUnpiped&&(s.hasUnpiped=!0,p("cleanup"),e.removeListener("close",E),e.removeListener("finish",_),e.removeListener("drain",u),e.removeListener("error",g),e.removeListener("unpipe",l),i.removeListener("end",h),i.removeListener("end",y),i.removeListener("data",m),d=!0,!r.awaitDrain||e._writableState&&!e._writableState.needDrain||u())}function h(){p("onend"),e.end()}r.endEmitted?n.nextTick(c):i.once("end",c),e.on("unpipe",l);var u=function(e){return function(){var t=e._readableState;p("pipeOnDrain",t.awaitDrain),t.awaitDrain&&t.awaitDrain--,0===t.awaitDrain&&a(e,"data")&&(t.flowing=!0,R(e))}}(i);e.on("drain",u);var d=!1;var f=!1;function m(t){p("ondata"),f=!1,!1!==e.write(t)||f||((1===r.pipesCount&&r.pipes===e||r.pipesCount>1&&-1!==M(r.pipes,e))&&!d&&(p("false write response, pause",i._readableState.awaitDrain),i._readableState.awaitDrain++,f=!0),i.pause())}function g(t){p("onerror",t),y(),e.removeListener("error",g),0===a(e,"error")&&e.emit("error",t)}function E(){e.removeListener("finish",_),y()}function _(){p("onfinish"),e.removeListener("close",E),y()}function y(){p("unpipe"),i.unpipe(e)}return i.on("data",m),function(e,t,i){if("function"==typeof e.prependListener)return e.prependListener(t,i);e._events&&e._events[t]?o(e._events[t])?e._events[t].unshift(i):e._events[t]=[i,e._events[t]]:e.on(t,i)}(e,"error",g),e.once("close",E),e.once("finish",_),e.emit("pipe",i),r.flowing||(p("pipe resume"),i.resume()),e},y.prototype.unpipe=function(e){var t=this._readableState,i={hasUnpiped:!1};if(0===t.pipesCount)return this;if(1===t.pipesCount)return e&&e!==t.pipes||(e||(e=t.pipes),t.pipes=null,t.pipesCount=0,t.flowing=!1,e&&e.emit("unpipe",this,i)),this;if(!e){var s=t.pipes,n=t.pipesCount;t.pipes=null,t.pipesCount=0,t.flowing=!1;for(var r=0;r<n;r++)s[r].emit("unpipe",this,i);return this}var o=M(t.pipes,e);return-1===o||(t.pipes.splice(o,1),t.pipesCount-=1,1===t.pipesCount&&(t.pipes=t.pipes[0]),e.emit("unpipe",this,i)),this},y.prototype.on=function(e,t){var i=c.prototype.on.call(this,e,t);if("data"===e)!1!==this._readableState.flowing&&this.resume();else if("readable"===e){var s=this._readableState;s.endEmitted||s.readableListening||(s.readableListening=s.needReadable=!0,s.emittedReadable=!1,s.reading?s.length&&A(this):n.nextTick(D,this))}return i},y.prototype.addListener=y.prototype.on,y.prototype.resume=function(){var e=this._readableState;return e.flowing||(p("resume"),e.flowing=!0,function(e,t){t.resumeScheduled||(t.resumeScheduled=!0,n.nextTick(N,e,t))}(this,e)),this},y.prototype.pause=function(){return p("call pause flowing=%j",this._readableState.flowing),!1!==this._readableState.flowing&&(p("pause"),this._readableState.flowing=!1,this.emit("pause")),this},y.prototype.wrap=function(e){var t=this,i=this._readableState,s=!1;for(var n in e.on("end",(function(){if(p("wrapped end"),i.decoder&&!i.ended){var e=i.decoder.end();e&&e.length&&t.push(e)}t.push(null)})),e.on("data",(function(n){(p("wrapped data"),i.decoder&&(n=i.decoder.write(n)),i.objectMode&&null==n)||(i.objectMode||n&&n.length)&&(t.push(n)||(s=!0,e.pause()))})),e)void 0===this[n]&&"function"==typeof e[n]&&(this[n]=function(t){return function(){return e[t].apply(e,arguments)}}(n));for(var r=0;r<E.length;r++)e.on(E[r],this.emit.bind(this,E[r]));return this._read=function(t){p("wrapped _read",t),s&&(s=!1,e.resume())},this},Object.defineProperty(y.prototype,"readableHighWaterMark",{enumerable:!1,get:function(){return this._readableState.highWaterMark}}),y._fromList=O}).call(this,i(21),i(14))},function(e,t){var i={}.toString;e.exports=Array.isArray||function(e){return"[object Array]"==i.call(e)}},function(e,t,i){e.exports=i(15).EventEmitter},function(e,t,i){"use strict";var s=i(31);function n(e,t){e.emit("error",t)}e.exports={destroy:function(e,t){var i=this,r=this._readableState&&this._readableState.destroyed,o=this._writableState&&this._writableState.destroyed;return r||o?(t?t(e):!e||this._writableState&&this._writableState.errorEmitted||s.nextTick(n,this,e),this):(this._readableState&&(this._readableState.destroyed=!0),this._writableState&&(this._writableState.destroyed=!0),this._destroy(e||null,(function(e){!t&&e?(s.nextTick(n,i,e),i._writableState&&(i._writableState.errorEmitted=!0)):t&&t(e)})),this)},undestroy:function(){this._readableState&&(this._readableState.destroyed=!1,this._readableState.reading=!1,this._readableState.ended=!1,this._readableState.endEmitted=!1),this._writableState&&(this._writableState.destroyed=!1,this._writableState.ended=!1,this._writableState.ending=!1,this._writableState.finished=!1,this._writableState.errorEmitted=!1)}}},function(e,t,i){"use strict";var s=i(32).Buffer,n=s.isEncoding||function(e){switch((e=""+e)&&e.toLowerCase()){case"hex":case"utf8":case"utf-8":case"ascii":case"binary":case"base64":case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":case"raw":return!0;default:return!1}};function r(e){var t;switch(this.encoding=function(e){var t=function(e){if(!e)return"utf8";for(var t;;)switch(e){case"utf8":case"utf-8":return"utf8";case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return"utf16le";case"latin1":case"binary":return"latin1";case"base64":case"ascii":case"hex":return e;default:if(t)return;e=(""+e).toLowerCase(),t=!0}}(e);if("string"!=typeof t&&(s.isEncoding===n||!n(e)))throw new Error("Unknown encoding: "+e);return t||e}(e),this.encoding){case"utf16le":this.text=c,this.end=l,t=4;break;case"utf8":this.fillLast=a,t=4;break;case"base64":this.text=h,this.end=u,t=3;break;default:return this.write=d,void(this.end=p)}this.lastNeed=0,this.lastTotal=0,this.lastChar=s.allocUnsafe(t)}function o(e){return e<=127?0:e>>5==6?2:e>>4==14?3:e>>3==30?4:e>>6==2?-1:-2}function a(e){var t=this.lastTotal-this.lastNeed,i=function(e,t,i){if(128!=(192&t[0]))return e.lastNeed=0,"�";if(e.lastNeed>1&&t.length>1){if(128!=(192&t[1]))return e.lastNeed=1,"�";if(e.lastNeed>2&&t.length>2&&128!=(192&t[2]))return e.lastNeed=2,"�"}}(this,e);return void 0!==i?i:this.lastNeed<=e.length?(e.copy(this.lastChar,t,0,this.lastNeed),this.lastChar.toString(this.encoding,0,this.lastTotal)):(e.copy(this.lastChar,t,0,e.length),void(this.lastNeed-=e.length))}function c(e,t){if((e.length-t)%2==0){var i=e.toString("utf16le",t);if(i){var s=i.charCodeAt(i.length-1);if(s>=55296&&s<=56319)return this.lastNeed=2,this.lastTotal=4,this.lastChar[0]=e[e.length-2],this.lastChar[1]=e[e.length-1],i.slice(0,-1)}return i}return this.lastNeed=1,this.lastTotal=2,this.lastChar[0]=e[e.length-1],e.toString("utf16le",t,e.length-1)}function l(e){var t=e&&e.length?this.write(e):"";if(this.lastNeed){var i=this.lastTotal-this.lastNeed;return t+this.lastChar.toString("utf16le",0,i)}return t}function h(e,t){var i=(e.length-t)%3;return 0===i?e.toString("base64",t):(this.lastNeed=3-i,this.lastTotal=3,1===i?this.lastChar[0]=e[e.length-1]:(this.lastChar[0]=e[e.length-2],this.lastChar[1]=e[e.length-1]),e.toString("base64",t,e.length-i))}function u(e){var t=e&&e.length?this.write(e):"";return this.lastNeed?t+this.lastChar.toString("base64",0,3-this.lastNeed):t}function d(e){return e.toString(this.encoding)}function p(e){return e&&e.length?this.write(e):""}t.StringDecoder=r,r.prototype.write=function(e){if(0===e.length)return"";var t,i;if(this.lastNeed){if(void 0===(t=this.fillLast(e)))return"";i=this.lastNeed,this.lastNeed=0}else i=0;return i<e.length?t?t+this.text(e,i):this.text(e,i):t||""},r.prototype.end=function(e){var t=e&&e.length?this.write(e):"";return this.lastNeed?t+"�":t},r.prototype.text=function(e,t){var i=function(e,t,i){var s=t.length-1;if(s<i)return 0;var n=o(t[s]);if(n>=0)return n>0&&(e.lastNeed=n-1),n;if(--s<i||-2===n)return 0;if((n=o(t[s]))>=0)return n>0&&(e.lastNeed=n-2),n;if(--s<i||-2===n)return 0;if((n=o(t[s]))>=0)return n>0&&(2===n?n=0:e.lastNeed=n-3),n;return 0}(this,e,t);if(!this.lastNeed)return e.toString("utf8",t);this.lastTotal=i;var s=e.length-(i-this.lastNeed);return e.copy(this.lastChar,0,s),e.toString("utf8",t,s)},r.prototype.fillLast=function(e){if(this.lastNeed<=e.length)return e.copy(this.lastChar,this.lastTotal-this.lastNeed,0,this.lastNeed),this.lastChar.toString(this.encoding,0,this.lastTotal);e.copy(this.lastChar,this.lastTotal-this.lastNeed,0,e.length),this.lastNeed-=e.length}},function(e,t,i){"use strict";e.exports=o;var s=i(20),n=Object.create(i(25));function r(e,t){var i=this._transformState;i.transforming=!1;var s=i.writecb;if(!s)return this.emit("error",new Error("write callback called multiple times"));i.writechunk=null,i.writecb=null,null!=t&&this.push(t),s(e);var n=this._readableState;n.reading=!1,(n.needReadable||n.length<n.highWaterMark)&&this._read(n.highWaterMark)}function o(e){if(!(this instanceof o))return new o(e);s.call(this,e),this._transformState={afterTransform:r.bind(this),needTransform:!1,transforming:!1,writecb:null,writechunk:null,writeencoding:null},this._readableState.needReadable=!0,this._readableState.sync=!1,e&&("function"==typeof e.transform&&(this._transform=e.transform),"function"==typeof e.flush&&(this._flush=e.flush)),this.on("prefinish",a)}function a(){var e=this;"function"==typeof this._flush?this._flush((function(t,i){c(e,t,i)})):c(this,null,null)}function c(e,t,i){if(t)return e.emit("error",t);if(null!=i&&e.push(i),e._writableState.length)throw new Error("Calling transform done when ws.length != 0");if(e._transformState.transforming)throw new Error("Calling transform done when still transforming");return e.push(null)}n.inherits=i(22),n.inherits(o,s),o.prototype.push=function(e,t){return this._transformState.needTransform=!1,s.prototype.push.call(this,e,t)},o.prototype._transform=function(e,t,i){throw new Error("_transform() is not implemented")},o.prototype._write=function(e,t,i){var s=this._transformState;if(s.writecb=i,s.writechunk=e,s.writeencoding=t,!s.transforming){var n=this._readableState;(s.needTransform||n.needReadable||n.length<n.highWaterMark)&&this._read(n.highWaterMark)}},o.prototype._read=function(e){var t=this._transformState;null!==t.writechunk&&t.writecb&&!t.transforming?(t.transforming=!0,this._transform(t.writechunk,t.writeencoding,t.afterTransform)):t.needTransform=!0},o.prototype._destroy=function(e,t){var i=this;s.prototype._destroy.call(this,e,(function(e){t(e),i.emit("close")}))}},function(e,t,i){"use strict";const s=i(11);class ActivityFlags extends s{}ActivityFlags.FLAGS={INSTANCE:1,JOIN:2,SPECTATE:4,JOIN_REQUEST:8,SYNC:16,PLAY:32},e.exports=ActivityFlags},function(e,t,i){"use strict";const s=i(49),{Events:n}=i(0);e.exports=class MessageCollector extends s{constructor(e,t,i={}){super(e.client,t,i),this.channel=e,this.received=0;const s=e=>{for(const t of e.values())this.handleDispose(t)};this._handleChannelDeletion=this._handleChannelDeletion.bind(this),this._handleGuildDeletion=this._handleGuildDeletion.bind(this),0!==this.client.getMaxListeners()&&this.client.setMaxListeners(this.client.getMaxListeners()+1),this.client.on(n.MESSAGE_CREATE,this.handleCollect),this.client.on(n.MESSAGE_DELETE,this.handleDispose),this.client.on(n.MESSAGE_BULK_DELETE,s),this.client.on(n.CHANNEL_DELETE,this._handleChannelDeletion),this.client.on(n.GUILD_DELETE,this._handleGuildDeletion),this.once("end",()=>{this.client.removeListener(n.MESSAGE_CREATE,this.handleCollect),this.client.removeListener(n.MESSAGE_DELETE,this.handleDispose),this.client.removeListener(n.MESSAGE_BULK_DELETE,s),this.client.removeListener(n.CHANNEL_DELETE,this._handleChannelDeletion),this.client.removeListener(n.GUILD_DELETE,this._handleGuildDeletion),0!==this.client.getMaxListeners()&&this.client.setMaxListeners(this.client.getMaxListeners()-1)})}collect(e){return e.channel.id!==this.channel.id?null:(this.received++,e.id)}dispose(e){return e.channel.id===this.channel.id?e.id:null}endReason(){return this.options.max&&this.collected.size>=this.options.max?"limit":this.options.maxProcessed&&this.received===this.options.maxProcessed?"processedLimit":null}_handleChannelDeletion(e){e.id===this.channel.id&&this.stop("channelDelete")}_handleGuildDeletion(e){this.channel.guild&&e.id===this.channel.guild.id&&this.stop("guildDelete")}}},function(e,t,i){"use strict";const{TypeError:s}=i(3),n=i(2);e.exports=class GuildMemberRoleManager{constructor(e){this.member=e,this.guild=e.guild,Object.defineProperty(this,"client",{value:e.client})}get _roles(){const e=this.guild.roles.everyone;return this.guild.roles.cache.filter(e=>this.member._roles.includes(e.id)).set(e.id,e)}get cache(){return this._roles}get hoist(){const e=this._roles.filter(e=>e.hoist);return e.size?e.reduce((e,t)=>!e||t.comparePositionTo(e)>0?t:e):null}get color(){const e=this._roles.filter(e=>e.color);return e.size?e.reduce((e,t)=>!e||t.comparePositionTo(e)>0?t:e):null}get highest(){return this._roles.reduce((e,t)=>t.comparePositionTo(e)>0?t:e,this._roles.first())}async add(e,t){if(e instanceof n||Array.isArray(e)){if((e=e.map(e=>this.guild.roles.resolve(e))).includes(null))throw new s("INVALID_TYPE","roles","Array or Collection of Roles or Snowflakes",!0);const i=[...new Set(e.concat(...this._roles.values()))];return this.set(i,t)}{if(null===(e=this.guild.roles.resolve(e)))throw new s("INVALID_TYPE","roles","Role, Snowflake or Array or Collection of Roles or Snowflakes",!0);await this.client.api.guilds[this.guild.id].members[this.member.id].roles[e.id].put({reason:t});const i=this.member._clone();return i._roles=[...this._roles.keys(),e.id],i}}async remove(e,t){if(e instanceof n||Array.isArray(e)){if((e=e.map(e=>this.guild.roles.resolve(e))).includes(null))throw new s("INVALID_TYPE","roles","Array or Collection of Roles or Snowflakes",!0);const i=this._roles.filter(t=>!e.includes(t));return this.set(i,t)}{if(null===(e=this.guild.roles.resolve(e)))throw new s("INVALID_TYPE","roles","Array or Collection of Roles or Snowflakes",!0);await this.client.api.guilds[this.guild.id].members[this.member.id].roles[e.id].delete({reason:t});const i=this.member._clone(),n=this._roles.filter(t=>t.id!==e.id);return i._roles=[...n.keys()],i}}set(e,t){return this.member.edit({roles:e},t)}clone(){const e=new this.constructor(this.member);return e.member._roles=[...this._roles.keyArray()],e}}},function(e,t,i){"use strict";const s=i(5),n=i(74),r=i(2),o=i(7);e.exports=class Team extends s{constructor(e,t){super(e),this._patch(t)}_patch(e){this.id=e.id,this.name=e.name,this.icon=e.icon||null,this.ownerID=e.owner_user_id||null,this.members=new r;for(const t of e.members){const e=new n(this,t);this.members.set(e.id,e)}}get owner(){return this.members.get(this.ownerID)||null}get createdTimestamp(){return o.deconstruct(this.id).timestamp}get createdAt(){return new Date(this.createdTimestamp)}iconURL({format:e,size:t}={}){return this.icon?this.client.rest.cdn.TeamIcon(this.id,this.icon,{format:e,size:t}):null}toString(){return this.name}toJSON(){return super.toJSON({createdTimestamp:!0})}}},function(e,t,i){"use strict";const s=i(5),{MembershipStates:n}=i(0);e.exports=class TeamMember extends s{constructor(e,t){super(e.client),this.team=e,this._patch(t)}_patch(e){this.permissions=e.permissions,this.membershipState=n[e.membership_state],this.user=this.client.users.add(e.user)}get id(){return this.user.id}toString(){return this.user.toString()}}},function(e,t,i){"use strict";const s=i(2),{ChannelTypes:n}=i(0),r=i(4);class MessageMentions{constructor(e,t,i,r,o){if(Object.defineProperty(this,"client",{value:e.client}),Object.defineProperty(this,"guild",{value:e.guild}),Object.defineProperty(this,"_content",{value:e.content}),this.everyone=Boolean(r),t)if(t instanceof s)this.users=new s(t);else{this.users=new s;for(const i of t){i.member&&e.guild&&e.guild.members.add(Object.assign(i.member,{user:i}));const t=e.client.users.add(i);this.users.set(t.id,t)}}else this.users=new s;if(i)if(i instanceof s)this.roles=new s(i);else{this.roles=new s;for(const t of i){const i=e.channel.guild.roles.cache.get(t);i&&this.roles.set(i.id,i)}}else this.roles=new s;if(this._members=null,this._channels=null,o)if(o instanceof s)this.crosspostedChannels=new s(o);else{this.crosspostedChannels=new s;const e=Object.keys(n);for(const t of o){const i=e[t.type];this.crosspostedChannels.set(t.id,{channelID:t.id,guildID:t.guild_id,type:i?i.toLowerCase():"unknown",name:t.name})}}else this.crosspostedChannels=new s}get members(){return this._members?this._members:this.guild?(this._members=new s,this.users.forEach(e=>{const t=this.guild.member(e);t&&this._members.set(t.user.id,t)}),this._members):null}get channels(){if(this._channels)return this._channels;let e;for(this._channels=new s;null!==(e=this.constructor.CHANNELS_PATTERN.exec(this._content));){const t=this.client.channels.cache.get(e[1]);t&&this._channels.set(t.id,t)}return this._channels}has(e,{ignoreDirect:t=!1,ignoreRoles:s=!1,ignoreEveryone:n=!1}={}){if(!n&&this.everyone)return!0;const r=i(10);if(!s&&e instanceof r)for(const t of this.roles.values())if(e.roles.cache.has(t.id))return!0;if(!t){const t=e.id||e;return this.users.has(t)||this.channels.has(t)||this.roles.has(t)}return!1}toJSON(){return r.flatten(this,{members:!0,channels:!0})}}MessageMentions.EVERYONE_PATTERN=/@(everyone|here)/g,MessageMentions.USERS_PATTERN=/<@!?(\d{17,19})>/g,MessageMentions.ROLES_PATTERN=/<@&(\d{17,19})>/g,MessageMentions.CHANNELS_PATTERN=/<#(\d{17,19})>/g,e.exports=MessageMentions},function(e,t,i){"use strict";const s=i(49),n=i(2),{Events:r}=i(0);class ReactionCollector extends s{constructor(e,t,i={}){super(e.client,t,i),this.message=e,this.users=new n,this.total=0,this.empty=this.empty.bind(this),this._handleChannelDeletion=this._handleChannelDeletion.bind(this),this._handleGuildDeletion=this._handleGuildDeletion.bind(this),this._handleMessageDeletion=this._handleMessageDeletion.bind(this),0!==this.client.getMaxListeners()&&this.client.setMaxListeners(this.client.getMaxListeners()+1),this.client.on(r.MESSAGE_REACTION_ADD,this.handleCollect),this.client.on(r.MESSAGE_REACTION_REMOVE,this.handleDispose),this.client.on(r.MESSAGE_REACTION_REMOVE_ALL,this.empty),this.client.on(r.MESSAGE_DELETE,this._handleMessageDeletion),this.client.on(r.CHANNEL_DELETE,this._handleChannelDeletion),this.client.on(r.GUILD_DELETE,this._handleGuildDeletion),this.once("end",()=>{this.client.removeListener(r.MESSAGE_REACTION_ADD,this.handleCollect),this.client.removeListener(r.MESSAGE_REACTION_REMOVE,this.handleDispose),this.client.removeListener(r.MESSAGE_REACTION_REMOVE_ALL,this.empty),this.client.removeListener(r.MESSAGE_DELETE,this._handleMessageDeletion),this.client.removeListener(r.CHANNEL_DELETE,this._handleChannelDeletion),this.client.removeListener(r.GUILD_DELETE,this._handleGuildDeletion),0!==this.client.getMaxListeners()&&this.client.setMaxListeners(this.client.getMaxListeners()-1)}),this.on("collect",(e,t)=>{this.total++,this.users.set(t.id,t)}),this.on("remove",(e,t)=>{this.total--,this.collected.some(e=>e.users.cache.has(t.id))||this.users.delete(t.id)})}collect(e){return e.message.id!==this.message.id?null:ReactionCollector.key(e)}dispose(e,t){return e.message.id!==this.message.id?null:(this.collected.has(ReactionCollector.key(e))&&this.users.has(t.id)&&this.emit("remove",e,t),e.count?null:ReactionCollector.key(e))}empty(){this.total=0,this.collected.clear(),this.users.clear(),this.checkEnd()}endReason(){return this.options.max&&this.total>=this.options.max?"limit":this.options.maxEmojis&&this.collected.size>=this.options.maxEmojis?"emojiLimit":this.options.maxUsers&&this.users.size>=this.options.maxUsers?"userLimit":null}_handleMessageDeletion(e){e.id===this.message.id&&this.stop("messageDelete")}_handleChannelDeletion(e){e.id===this.message.channel.id&&this.stop("channelDelete")}_handleGuildDeletion(e){this.message.guild&&e.id===this.message.guild.id&&this.stop("guildDelete")}static key(e){return e.emoji.id||e.emoji.name}}e.exports=ReactionCollector},function(e,t,i){"use strict";const s=i(6),{Error:n}=i(3),r=i(2);e.exports=class ReactionUserManager extends s{constructor(e,t,i){super(e,t,{name:"User"}),this.reaction=i}async fetch({limit:e=100,after:t,before:i}={}){const s=this.reaction.message,n=await this.client.api.channels[s.channel.id].messages[s.id].reactions[this.reaction.emoji.identifier].get({query:{limit:e,before:i,after:t}}),o=new r;for(const e of n){const t=this.client.users.add(e);this.cache.set(t.id,t),o.set(t.id,t)}return o}remove(e=this.reaction.message.client.user){const t=this.reaction.message,i=t.client.users.resolveID(e);return i?t.client.api.channels[t.channel.id].messages[t.id].reactions[this.reaction.emoji.identifier][i===t.client.user.id?"@me":i].delete().then(()=>this.reaction):Promise.reject(new n("REACTION_RESOLVE_USER"))}}},function(e,t,i){"use strict";const s=i(11);class UserFlags extends s{}UserFlags.FLAGS={DISCORD_EMPLOYEE:1,DISCORD_PARTNER:2,HYPESQUAD_EVENTS:4,BUGHUNTER_LEVEL_1:8,HOUSE_BRAVERY:64,HOUSE_BRILLIANCE:128,HOUSE_BALANCE:256,EARLY_SUPPORTER:512,TEAM_USER:1024,SYSTEM:4096,BUGHUNTER_LEVEL_2:16384,VERIFIED_BOT:65536,VERIFIED_DEVELOPER:1<<17},e.exports=UserFlags},function(e,t,i){"use strict";const s=i(17),{Error:n}=i(3),r=i(2),{browser:o}=i(0),a=i(8);e.exports=class VoiceChannel extends s{_patch(e){super._patch(e),this.bitrate=e.bitrate,this.userLimit=e.user_limit}get members(){const e=new r;for(const t of this.guild.voiceStates.cache.values())t.channelID===this.id&&t.member&&e.set(t.id,t.member);return e}get full(){return this.userLimit>0&&this.members.size>=this.userLimit}get deletable(){return super.deletable&&this.permissionsFor(this.client.user).has(a.FLAGS.CONNECT,!1)}get editable(){return this.manageable&&this.permissionsFor(this.client.user).has(a.FLAGS.CONNECT,!1)}get joinable(){return!o&&(!!this.viewable&&(!!this.permissionsFor(this.client.user).has(a.FLAGS.CONNECT,!1)&&!(this.full&&!this.permissionsFor(this.client.user).has(a.FLAGS.MOVE_MEMBERS,!1))))}get speakable(){return this.permissionsFor(this.client.user).has(a.FLAGS.SPEAK,!1)}setBitrate(e,t){return this.edit({bitrate:e},t)}setUserLimit(e,t){return this.edit({userLimit:e},t)}join(){return o?Promise.reject(new n("VOICE_NO_BROWSER")):this.client.voice.joinChannel(this)}leave(){if(o)return;const e=this.client.voice.connections.get(this.guild.id);e&&e.channel.id===this.id&&e.disconnect()}}},function(e,t,i){"use strict";const s=i(17);e.exports=class CategoryChannel extends s{get children(){return this.guild.channels.cache.filter(e=>e.parentID===this.id)}}},function(e,t,i){"use strict";const s=i(41);e.exports=class NewsChannel extends s{_patch(e){super._patch(e),this.rateLimitPerUser=void 0}}},function(e,t,i){"use strict";const s=i(17);e.exports=class StoreChannel extends s{_patch(e){super._patch(e),this.nsfw=e.nsfw}}},function(e,t,i){"use strict";const s=i(54),n=i(12),r=i(2),{PartialTypes:o}=i(0),a=i(7),c=i(4),l={ALL:"ALL",GUILD:"GUILD",CHANNEL:"CHANNEL",USER:"USER",ROLE:"ROLE",INVITE:"INVITE",WEBHOOK:"WEBHOOK",EMOJI:"EMOJI",MESSAGE:"MESSAGE",INTEGRATION:"INTEGRATION",UNKNOWN:"UNKNOWN"},h={ALL:null,GUILD_UPDATE:1,CHANNEL_CREATE:10,CHANNEL_UPDATE:11,CHANNEL_DELETE:12,CHANNEL_OVERWRITE_CREATE:13,CHANNEL_OVERWRITE_UPDATE:14,CHANNEL_OVERWRITE_DELETE:15,MEMBER_KICK:20,MEMBER_PRUNE:21,MEMBER_BAN_ADD:22,MEMBER_BAN_REMOVE:23,MEMBER_UPDATE:24,MEMBER_ROLE_UPDATE:25,MEMBER_MOVE:26,MEMBER_DISCONNECT:27,BOT_ADD:28,ROLE_CREATE:30,ROLE_UPDATE:31,ROLE_DELETE:32,INVITE_CREATE:40,INVITE_UPDATE:41,INVITE_DELETE:42,WEBHOOK_CREATE:50,WEBHOOK_UPDATE:51,WEBHOOK_DELETE:52,EMOJI_CREATE:60,EMOJI_UPDATE:61,EMOJI_DELETE:62,MESSAGE_DELETE:72,MESSAGE_BULK_DELETE:73,MESSAGE_PIN:74,MESSAGE_UNPIN:75,INTEGRATION_CREATE:80,INTEGRATION_UPDATE:81,INTEGRATION_DELETE:82};class GuildAuditLogs{constructor(e,t){if(t.users)for(const i of t.users)e.client.users.add(i);if(this.webhooks=new r,t.webhooks)for(const i of t.webhooks)this.webhooks.set(i.id,new n(e.client,i));if(this.integrations=new r,t.integrations)for(const i of t.integrations)this.integrations.set(i.id,new s(e.client,i,e));this.entries=new r;for(const i of t.audit_log_entries){const t=new GuildAuditLogsEntry(this,e,i);this.entries.set(t.id,t)}}static build(...e){const t=new GuildAuditLogs(...e);return Promise.all(t.entries.map(e=>e.target)).then(()=>t)}static targetType(e){return e<10?l.GUILD:e<20?l.CHANNEL:e<30?l.USER:e<40?l.ROLE:e<50?l.INVITE:e<60?l.WEBHOOK:e<70?l.EMOJI:e<80?l.MESSAGE:e<90?l.INTEGRATION:l.UNKNOWN}static actionType(e){return[h.CHANNEL_CREATE,h.CHANNEL_OVERWRITE_CREATE,h.MEMBER_BAN_REMOVE,h.BOT_ADD,h.ROLE_CREATE,h.INVITE_CREATE,h.WEBHOOK_CREATE,h.EMOJI_CREATE,h.MESSAGE_PIN,h.INTEGRATION_CREATE].includes(e)?"CREATE":[h.CHANNEL_DELETE,h.CHANNEL_OVERWRITE_DELETE,h.MEMBER_KICK,h.MEMBER_PRUNE,h.MEMBER_BAN_ADD,h.MEMBER_DISCONNECT,h.ROLE_DELETE,h.INVITE_DELETE,h.WEBHOOK_DELETE,h.EMOJI_DELETE,h.MESSAGE_DELETE,h.MESSAGE_BULK_DELETE,h.MESSAGE_UNPIN,h.INTEGRATION_DELETE].includes(e)?"DELETE":[h.GUILD_UPDATE,h.CHANNEL_UPDATE,h.CHANNEL_OVERWRITE_UPDATE,h.MEMBER_UPDATE,h.MEMBER_ROLE_UPDATE,h.MEMBER_MOVE,h.ROLE_UPDATE,h.INVITE_UPDATE,h.WEBHOOK_UPDATE,h.EMOJI_UPDATE,h.INTEGRATION_UPDATE].includes(e)?"UPDATE":"ALL"}toJSON(){return c.flatten(this)}}class GuildAuditLogsEntry{constructor(e,t,i){const r=GuildAuditLogs.targetType(i.action_type);switch(this.targetType=r,this.actionType=GuildAuditLogs.actionType(i.action_type),this.action=Object.keys(h).find(e=>h[e]===i.action_type),this.reason=i.reason||null,this.executor=t.client.options.partials.includes(o.USER)?t.client.users.add({id:i.user_id}):t.client.users.cache.get(i.user_id),this.changes=i.changes?i.changes.map(e=>({key:e.key,old:e.old_value,new:e.new_value})):null,this.id=i.id,this.extra=null,i.action_type){case h.MEMBER_PRUNE:this.extra={removed:Number(i.options.members_removed),days:Number(i.options.delete_member_days)};break;case h.MEMBER_MOVE:case h.MESSAGE_DELETE:case h.MESSAGE_BULK_DELETE:this.extra={channel:t.channels.cache.get(i.options.channel_id)||{id:i.options.channel_id},count:Number(i.options.count)};break;case h.MESSAGE_PIN:case h.MESSAGE_UNPIN:this.extra={channel:t.client.channels.cache.get(i.options.channel_id)||{id:i.options.channel_id},messageID:i.options.message_id};break;case h.MEMBER_DISCONNECT:this.extra={count:Number(i.options.count)};break;case h.CHANNEL_OVERWRITE_CREATE:case h.CHANNEL_OVERWRITE_UPDATE:case h.CHANNEL_OVERWRITE_DELETE:switch(i.options.type){case"member":this.extra=t.members.cache.get(i.options.id)||{id:i.options.id,type:"member"};break;case"role":this.extra=t.roles.cache.get(i.options.id)||{id:i.options.id,name:i.options.role_name,type:"role"}}}this.target=null,r===l.UNKNOWN?(this.target=this.changes.reduce((e,t)=>(e[t.key]=t.new||t.old,e),{}),this.target.id=i.target_id):r===l.USER&&i.target_id?this.target=t.client.options.partials.includes(o.USER)?t.client.users.add({id:i.target_id}):t.client.users.cache.get(i.target_id):r===l.GUILD?this.target=t.client.guilds.cache.get(i.target_id):r===l.WEBHOOK?this.target=e.webhooks.get(i.target_id)||new n(t.client,this.changes.reduce((e,t)=>(e[t.key]=t.new||t.old,e),{id:i.target_id,guild_id:t.id})):r===l.INVITE?this.target=t.members.fetch(t.client.user.id).then(e=>{if(e.permissions.has("MANAGE_GUILD")){const e=this.changes.find(e=>"code"===e.key);return t.fetchInvites().then(t=>{this.target=t.find(t=>t.code===(e.new||e.old))})}return this.target=this.changes.reduce((e,t)=>(e[t.key]=t.new||t.old,e),{}),this.target}):r===l.MESSAGE?this.target=i.action_type===h.MESSAGE_BULK_DELETE?t.channels.cache.get(i.target_id)||{id:i.target_id}:t.client.users.cache.get(i.target_id):r===l.INTEGRATION?this.target=e.integrations.get(i.target_id)||new s(t.client,this.changes.reduce((e,t)=>(e[t.key]=t.new||t.old,e),{id:i.target_id}),t):i.target_id&&(this.target=t[`${r.toLowerCase()}s`].cache.get(i.target_id)||{id:i.target_id})}get createdTimestamp(){return a.deconstruct(this.id).timestamp}get createdAt(){return new Date(this.createdTimestamp)}toJSON(){return c.flatten(this,{createdTimestamp:!0})}}GuildAuditLogs.Actions=h,GuildAuditLogs.Targets=l,GuildAuditLogs.Entry=GuildAuditLogsEntry,e.exports=GuildAuditLogs},function(e,t,i){"use strict";const s=i(6),n=i(17),r=i(42),{ChannelTypes:o}=i(0);e.exports=class GuildChannelManager extends s{constructor(e,t){super(e.client,t,n),this.guild=e}add(e){const t=this.cache.get(e.id);return t||(this.cache.set(e.id,e),e)}async create(e,t={}){let{type:i,topic:s,nsfw:n,bitrate:a,userLimit:c,parent:l,permissionOverwrites:h,position:u,rateLimitPerUser:d,reason:p}=t;l&&(l=this.client.channels.resolveID(l)),h&&(h=h.map(e=>r.resolve(e,this.guild)));const f=await this.client.api.guilds(this.guild.id).channels.post({data:{name:e,topic:s,type:i?o[i.toUpperCase()]:o.TEXT,nsfw:n,bitrate:a,user_limit:c,parent_id:l,position:u,permission_overwrites:h,rate_limit_per_user:d},reason:p});return this.client.actions.ChannelCreate.handle(f).channel}}},function(e,t,i){"use strict";const s=i(6),{Error:n,TypeError:r}=i(3),o=i(10),a=i(2),{Events:c,OPCodes:l}=i(0);e.exports=class GuildMemberManager extends s{constructor(e,t){super(e.client,t,o),this.guild=e}add(e,t=!0){return super.add(e,t,{id:e.user.id,extras:[this.guild]})}resolve(e){const t=super.resolve(e);if(t)return t;const i=this.client.users.resolveID(e);return i?super.resolve(i):null}resolveID(e){const t=super.resolveID(e);if(t)return t;const i=this.client.users.resolveID(e);return this.cache.has(i)?i:null}fetch(e){if(!e)return this._fetchMany();const t=this.client.users.resolveID(e);if(t)return this._fetchSingle({user:t,cache:!0});if(e.user){if(Array.isArray(e.user))return e.user=e.user.map(e=>this.client.users.resolveID(e)),this._fetchMany(e);if(e.user=this.client.users.resolveID(e.user),!e.limit&&!e.withPresences)return this._fetchSingle(e)}return this._fetchMany(e)}prune({days:e=7,dry:t=!1,count:i=!0,reason:s}={}){if("number"!=typeof e)throw new r("PRUNE_DAYS_TYPE");return this.client.api.guilds(this.guild.id).prune[t?"get":"post"]({query:{days:e,compute_prune_count:i},reason:s}).then(e=>e.pruned)}ban(e,t={days:0}){t.days&&(t["delete-message-days"]=t.days);const i=this.client.users.resolveID(e);return i?this.client.api.guilds(this.guild.id).bans[i].put({query:t}).then(()=>{if(e instanceof o)return e;const t=this.client.users.resolve(i);if(t){return this.resolve(t)||t}return i}):Promise.reject(new n("BAN_RESOLVE_ID",!0))}unban(e,t){const i=this.client.users.resolveID(e);return i?this.client.api.guilds(this.guild.id).bans[i].delete({reason:t}).then(()=>this.client.users.resolve(e)):Promise.reject(new n("BAN_RESOLVE_ID"))}_fetchSingle({user:e,cache:t}){const i=this.cache.get(e);return i&&!i.partial?Promise.resolve(i):this.client.api.guilds(this.guild.id).members(e).get().then(e=>this.add(e,t))}_fetchMany({limit:e=0,withPresences:t=!1,user:i,query:s,time:r=12e4}={}){return new Promise((o,h)=>{if(!(this.guild.memberCount!==this.cache.size||s||e||t||i))return void o(this.cache);s||i||(s=""),this.guild.shard.send({op:l.REQUEST_GUILD_MEMBERS,d:{guild_id:this.guild.id,presences:t,user_ids:i,query:s,limit:e}});const u=new a,d=s||e||t||i,p=(t,s)=>{if(s.id===this.guild.id){f.refresh();for(const e of t.values())d&&u.set(e.id,e);if(this.guild.memberCount<=this.cache.size||d&&t.size<1e3||e&&u.size>=e){this.guild.client.removeListener(c.GUILD_MEMBERS_CHUNK,p);let e=d?u:this.cache;i&&!Array.isArray(i)&&e.size&&(e=e.first()),o(e)}}},f=this.guild.client.setTimeout(()=>{this.guild.client.removeListener(c.GUILD_MEMBERS_CHUNK,p),h(new n("GUILD_MEMBERS_TIMEOUT"))},r);this.guild.client.on(c.GUILD_MEMBERS_CHUNK,p)})}}},function(e,t,i){"use strict";const s=i(6),{Presence:n}=i(13);e.exports=class PresenceManager extends s{constructor(e,t){super(e,t,n)}add(e,t){const i=this.cache.get(e.user.id);return i?i.patch(e):super.add(e,t,{id:e.user.id})}resolve(e){const t=super.resolve(e);if(t)return t;const i=this.client.users.resolveID(e);return super.resolve(i)||null}resolveID(e){const t=super.resolveID(e);if(t)return t;const i=this.client.users.resolveID(e);return this.cache.has(i)?i:null}}},function(e,t,i){"use strict";const s=i(6),n=i(19),r=i(8),{resolveColor:o}=i(4);e.exports=class RoleManager extends s{constructor(e,t){super(e.client,t,n),this.guild=e}add(e,t){return super.add(e,t,{extras:[this.guild]})}async fetch(e,t=!0){if(e){const t=this.cache.get(e);if(t)return t}const i=await this.client.api.guilds(this.guild.id).roles.get();for(const e of i)this.add(e,t);return e?this.cache.get(e)||null:this}create({data:e={},reason:t}={}){return e.color&&(e.color=o(e.color)),e.permissions&&(e.permissions=r.resolve(e.permissions)),this.guild.client.api.guilds(this.guild.id).roles.post({data:e,reason:t}).then(i=>{const{role:s}=this.client.actions.GuildRoleCreate.handle({guild_id:this.guild.id,role:i});return e.position?s.setPosition(e.position,t):s})}get everyone(){return this.cache.get(this.guild.id)}get highest(){return this.cache.reduce((e,t)=>t.comparePositionTo(e)>0?t:e,this.cache.first())}}},function(e,t,i){"use strict";const s=i(11);class SystemChannelFlags extends s{}SystemChannelFlags.FLAGS={WELCOME_MESSAGE_DISABLED:1,BOOST_MESSAGE_DISABLED:2},e.exports=SystemChannelFlags},function(e,t,i){"use strict";const{Presence:s}=i(13),{TypeError:n}=i(3),r=i(2),{ActivityTypes:o,OPCodes:a}=i(0);e.exports=class ClientPresence extends s{constructor(e,t={}){super(e,Object.assign(t,{status:"online",user:{id:null}}))}async set(e){const t=await this._parse(e);if(this.patch(t),void 0===e.shardID)this.client.ws.broadcast({op:a.STATUS_UPDATE,d:t});else if(Array.isArray(e.shardID))for(const i of e.shardID)this.client.ws.shards.get(i).send({op:a.STATUS_UPDATE,d:t});else this.client.ws.shards.get(e.shardID).send({op:a.STATUS_UPDATE,d:t});return this}async _parse({status:e,since:t,afk:i,activity:s}){const a=s&&(s.application?s.application.id||s.application:null);let c=new r;if(s){if("string"!=typeof s.name)throw new n("INVALID_TYPE","name","string");if(s.type||(s.type=0),s.assets&&a)try{const e=await this.client.api.oauth2.applications(a).assets.get();for(const t of e)c.set(t.name,t.id)}catch{}}const l={afk:null!=i&&i,since:null!=t?t:null,status:e||this.status,game:s?{type:s.type,name:s.name,url:s.url,details:s.details||void 0,state:s.state||void 0,assets:s.assets?{large_text:s.assets.largeText||void 0,small_text:s.assets.smallText||void 0,large_image:c.get(s.assets.largeImage)||s.assets.largeImage,small_image:c.get(s.assets.smallImage)||s.assets.smallImage}:void 0,timestamps:s.timestamps||void 0,party:s.party||void 0,application_id:a||void 0,secrets:s.secrets||void 0,instance:s.instance||void 0}:null};return(e||i||t)&&!s&&(l.game=this.activities[0]||null),l.game&&(l.game.type="number"==typeof l.game.type?l.game.type:o.indexOf(l.game.type)),l}}},function(e,t,i){"use strict";const{browser:s}=i(0);let n,r;try{n=i(161),n.pack||(n=null)}catch{}s?(r=window.TextDecoder,t.WebSocket=window.WebSocket):(r=i(162).TextDecoder,t.WebSocket=i(165));const o=new r;t.encoding=n?"etf":"json",t.pack=n?n.pack:JSON.stringify,t.unpack=(e,i)=>"json"===t.encoding||"json"===i?("string"!=typeof e&&(e=o.decode(e)),JSON.parse(e)):(Buffer.isBuffer(e)||(e=Buffer.from(new Uint8Array(e))),n.unpack(e)),t.create=(e,i={},...n)=>{const[r,o]=e.split("?");i.encoding=t.encoding,i=new URLSearchParams(i),o&&new URLSearchParams(o).forEach((e,t)=>i.set(t,e));const a=new t.WebSocket(`${r}?${i}`,...n);return s&&(a.binaryType="arraybuffer"),a};for(const e of["CONNECTING","OPEN","CLOSING","CLOSED"])t[e]=t.WebSocket[e]},function(e,t,i){"use strict";const{WSEvents:s}=i(0),n={};for(const e of Object.keys(s))try{n[e]=i(167)(`./${e}.js`)}catch{}e.exports=n},function(e,t,i){"use strict";const s=i(9),n=i(23);class ClientUser extends(n.get("User")){constructor(e,t){super(e,t),this._typing=new Map}_patch(e){super._patch(e),"verified"in e&&(this.verified=e.verified),"mfa_enabled"in e?this.mfaEnabled="boolean"==typeof e.mfa_enabled?e.mfa_enabled:null:void 0===this.mfaEnabled&&(this.mfaEnabled=null),e.token&&(this.client.token=e.token)}get presence(){return this.client.presence}edit(e){return this.client.api.users("@me").patch({data:e}).then(e=>{this.client.token=e.token;const{updated:t}=this.client.actions.UserUpdate.handle(e);return t||this})}setUsername(e){return this.edit({username:e})}async setAvatar(e){return this.edit({avatar:await s.resolveImage(e)})}setPresence(e){return this.client.presence.set(e)}setStatus(e,t){return this.setPresence({status:e,shardID:t})}setActivity(e,t={}){if(!e)return this.setPresence({activity:null,shardID:t.shardID});const i=Object.assign({},t,"object"==typeof e?e:{name:e});return this.setPresence({activity:i,shardID:i.shardID})}setAFK(e){return this.setPresence({afk:e})}}e.exports=ClientUser},function(e,t,i){"use strict";const s=i(6),n=i(16),{Events:r}=i(0);e.exports=class ChannelManager extends s{constructor(e,t){super(e,t,n)}add(e,t,i=!0){const s=this.cache.get(e.id);if(s)return s._patch&&i&&s._patch(e),t&&t.channels.add(s),s;const o=n.create(this.client,e,t);return o?(i&&this.cache.set(o.id,o),o):(this.client.emit(r.DEBUG,`Failed to find guild, or unknown type for channel ${e.id} ${e.type}`),null)}remove(e){const t=this.cache.get(e);t.guild&&t.guild.channels.cache.delete(e),this.cache.delete(e)}async fetch(e,t=!0){const i=this.cache.get(e);if(i&&!i.partial)return i;const s=await this.client.api.channels(e).get();return this.add(s,null,t)}}},function(e,t,i){"use strict";const s=i(6),n=i(53),r=i(17),o=i(24),a=i(10),c=i(18),l=i(19),{Events:h,VerificationLevels:u,DefaultMessageNotifications:d,ExplicitContentFilterLevels:p}=i(0),f=i(9),m=i(8),{resolveColor:g}=i(4);e.exports=class GuildManager extends s{constructor(e,t){super(e,t,n)}resolve(e){return e instanceof r||e instanceof a||e instanceof o||e instanceof l||e instanceof c&&e.guild?super.resolve(e.guild):super.resolve(e)}resolveID(e){return e instanceof r||e instanceof a||e instanceof o||e instanceof l||e instanceof c&&e.guild?super.resolveID(e.guild.id):super.resolveID(e)}async create(e,{channels:t=[],defaultMessageNotifications:i,explicitContentFilter:s,icon:n=null,region:r,roles:o=[],verificationLevel:a}={}){n=await f.resolveImage(n),void 0!==a&&"number"!=typeof a&&(a=u.indexOf(a)),void 0!==i&&"number"!=typeof i&&(i=d.indexOf(i)),void 0!==s&&"number"!=typeof s&&(s=p.indexOf(s));for(const e of t)if(e.parent_id=e.parentID,delete e.parentID,e.permissionOverwrites){for(const t of e.permissionOverwrites)t.allow&&(t.allow=m.resolve(t.allow)),t.deny&&(t.deny=m.resolve(t.deny));e.permission_overwrites=e.permissionOverwrites,delete e.permissionOverwrites}for(const e of o)e.color&&(e.color=g(e.color)),e.permissions&&(e.permissions=m.resolve(e.permissions));return new Promise((c,l)=>this.client.api.guilds.post({data:{name:e,region:r,icon:n,verification_level:a,default_message_notifications:i,explicit_content_filter:s,channels:t,roles:o}}).then(e=>{if(this.client.guilds.cache.has(e.id))return c(this.client.guilds.cache.get(e.id));const t=s=>{s.id===e.id&&(this.client.removeListener(h.GUILD_CREATE,t),this.client.clearTimeout(i),c(s))};this.client.on(h.GUILD_CREATE,t);const i=this.client.setTimeout(()=>{this.client.removeListener(h.GUILD_CREATE,t),c(this.client.guilds.add(e))},1e4)},l))}}},function(e,t,i){"use strict";const s=i(6),n=i(10),r=i(28),o=i(26);e.exports=class UserManager extends s{constructor(e,t){super(e,t,o)}resolve(e){return e instanceof n?e.user:e instanceof r?e.author:super.resolve(e)}resolveID(e){return e instanceof n?e.user.id:e instanceof r?e.author.id:super.resolveID(e)}async fetch(e,t=!0){const i=this.cache.get(e);if(i&&!i.partial)return i;const s=await this.client.api.users(e).get();return this.add(s,t)}}},function(e,t,i){"use strict";const s=i(11);class Intents extends s{}Intents.FLAGS={GUILDS:1,GUILD_MEMBERS:2,GUILD_BANS:4,GUILD_EMOJIS:8,GUILD_INTEGRATIONS:16,GUILD_WEBHOOKS:32,GUILD_INVITES:64,GUILD_VOICE_STATES:128,GUILD_PRESENCES:256,GUILD_MESSAGES:512,GUILD_MESSAGE_REACTIONS:1024,GUILD_MESSAGE_TYPING:2048,DIRECT_MESSAGES:4096,DIRECT_MESSAGE_REACTIONS:8192,DIRECT_MESSAGE_TYPING:16384},Intents.PRIVILEGED=Intents.FLAGS.GUILD_MEMBERS|Intents.FLAGS.GUILD_PRESENCES,Intents.ALL=Object.values(Intents.FLAGS).reduce((e,t)=>e|t,0),Intents.NON_PRIVILEGED=Intents.ALL&~Intents.PRIVILEGED,e.exports=Intents},function(e,t,i){"use strict";const s=i(4);e.exports={BaseClient:i(37),Client:i(107),Shard:i(206),ShardClientUtil:i(207),ShardingManager:i(208),WebhookClient:i(48),ActivityFlags:i(70),BitField:i(11),Collection:i(2),Constants:i(0),DataResolver:i(9),BaseManager:i(6),DiscordAPIError:i(61),HTTPError:i(62),MessageFlags:i(47),Intents:i(96),Permissions:i(8),Speaking:i(209),Snowflake:i(7),SnowflakeUtil:i(7),Structures:i(23),SystemChannelFlags:i(88),UserFlags:i(78),Util:s,version:i(58).version,ChannelManager:i(93),GuildChannelManager:i(84),GuildEmojiManager:i(57),GuildEmojiRoleManager:i(63),GuildMemberManager:i(85),GuildMemberRoleManager:i(72),GuildManager:i(94),ReactionUserManager:i(77),MessageManager:i(34),PresenceManager:i(86),RoleManager:i(87),UserManager:i(95),discordSort:s.discordSort,escapeMarkdown:s.escapeMarkdown,fetchRecommendedShards:s.fetchRecommendedShards,resolveColor:s.resolveColor,resolveString:s.resolveString,splitMessage:s.splitMessage,Base:i(5),Activity:i(13).Activity,APIMessage:i(30),BaseGuildEmoji:i(40),CategoryChannel:i(80),Channel:i(16),ClientApplication:i(50),get ClientUser(){return i(92)},Collector:i(49),DMChannel:i(39),Emoji:i(29),Guild:i(53),GuildAuditLogs:i(83),GuildChannel:i(17),GuildEmoji:i(24),GuildMember:i(10),GuildPreview:i(55),Integration:i(54),Invite:i(18),Message:i(28),MessageAttachment:i(43),MessageCollector:i(71),MessageEmbed:i(44),MessageMentions:i(75),MessageReaction:i(51),NewsChannel:i(81),PermissionOverwrites:i(42),Presence:i(13).Presence,ClientPresence:i(89),ReactionCollector:i(76),ReactionEmoji:i(52),RichPresenceAssets:i(13).RichPresenceAssets,Role:i(19),StoreChannel:i(82),Team:i(73),TeamMember:i(74),TextChannel:i(41),User:i(26),VoiceChannel:i(79),VoiceRegion:i(56),VoiceState:i(27),Webhook:i(12),WebSocket:i(90)}},function(e,t,i){"use strict";const{register:s}=i(59),n={CLIENT_INVALID_OPTION:(e,t)=>`The ${e} option must be ${t}`,CLIENT_INVALID_PROVIDED_SHARDS:"None of the provided shards were valid.",TOKEN_INVALID:"An invalid token was provided.",TOKEN_MISSING:"Request to use token, but token was unavailable to the client.",WS_CLOSE_REQUESTED:"WebSocket closed due to user request.",WS_CONNECTION_EXISTS:"There is already an existing WebSocket connection.",WS_NOT_OPEN:(e="data")=>`Websocket not open to send ${e}`,BITFIELD_INVALID:"Invalid bitfield flag or number.",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:e=>`Already spawned ${e} shards.`,SHARDING_PROCESS_EXISTS:e=>`Shard ${e} already has an active process.`,SHARDING_READY_TIMEOUT:e=>`Shard ${e}'s Client took too long to become ready.`,SHARDING_READY_DISCONNECTED:e=>`Shard ${e}'s Client disconnected before becoming ready.`,SHARDING_READY_DIED:e=>`Shard ${e}'s process exited before its Client became ready.`,COLOR_RANGE:"Color must be within the range 0 - 16777215 (0xFFFFFF).",COLOR_CONVERT:"Unable to convert color to a number.",EMBED_FIELD_NAME:"MessageEmbed field names may not be empty.",EMBED_FIELD_VALUE:"MessageEmbed field values may not be empty.",FILE_NOT_FOUND:e=>`File could not be found: ${e}`,USER_NO_DMCHANNEL:"No DM Channel exists!",VOICE_INVALID_HEARTBEAT:"Tried to set voice heartbeat but no valid interval was specified.",VOICE_USER_MISSING:"Couldn't resolve the user to create stream.",VOICE_JOIN_CHANNEL:(e=!1)=>`You do not have permission to join this voice channel${e?"; it is full.":"."}`,VOICE_CONNECTION_TIMEOUT:"Connection not established within 15 seconds.",VOICE_TOKEN_ABSENT:"Token not provided from voice server packet.",VOICE_SESSION_ABSENT:"Session ID not supplied.",VOICE_INVALID_ENDPOINT:"Invalid endpoint received.",VOICE_NO_BROWSER:"Voice connections are not available in browsers.",VOICE_CONNECTION_ATTEMPTS_EXCEEDED:e=>`Too many connection attempts (${e}).`,VOICE_JOIN_SOCKET_CLOSED:"Tried to send join packet, but the WebSocket is not open.",VOICE_PLAY_INTERFACE_NO_BROADCAST:"A broadcast cannot be played in this context.",VOICE_PLAY_INTERFACE_BAD_TYPE:"Unknown stream type",VOICE_PRISM_DEMUXERS_NEED_STREAM:"To play a webm/ogg stream, you need to pass a ReadableStream.",VOICE_STATE_UNCACHED_MEMBER:"The member of this voice state is uncached.",VOICE_STATE_NOT_OWN:"You cannot self-deafen/mute on VoiceStates that do not belong to the ClientUser.",VOICE_STATE_INVALID_TYPE:e=>`${e} must be a boolean.`,UDP_SEND_FAIL:"Tried to send a UDP packet, but there is no socket available.",UDP_ADDRESS_MALFORMED:"Malformed UDP address or port.",UDP_CONNECTION_EXISTS:"There is already an existing UDP connection.",REQ_RESOURCE_TYPE:"The resource must be a string, Buffer or a valid file stream.",IMAGE_FORMAT:e=>`Invalid image format: ${e}`,IMAGE_SIZE:e=>`Invalid image size: ${e}`,MESSAGE_BULK_DELETE_TYPE:"The messages must be an Array, Collection, or number.",MESSAGE_NONCE_TYPE:"Message nonce must fit in an unsigned 64-bit integer.",TYPING_COUNT:"Count must be at least 1",SPLIT_MAX_LEN:"Chunk exceeds the max length and contains no split characters.",BAN_RESOLVE_ID:(e=!1)=>`Couldn't resolve the user ID to ${e?"ban":"unban"}.`,FETCH_BAN_RESOLVE_ID:"Couldn't resolve the user ID to fetch the ban.",PRUNE_DAYS_TYPE:"Days must be a number",GUILD_CHANNEL_RESOLVE:"Could not resolve channel to a guild channel.",GUILD_VOICE_CHANNEL_RESOLVE:"Could not resolve channel to a guild voice channel.",GUILD_CHANNEL_ORPHAN:"Could not find a parent to this guild channel.",GUILD_OWNED:"Guild is owned by the client.",GUILD_MEMBERS_TIMEOUT:"Members didn't arrive in time.",GUILD_UNCACHED_ME:"The client user as a member of this guild is uncached.",INVALID_TYPE:(e,t,i=!1)=>`Supplied ${e} is not a${i?"n":""} ${t}.`,WEBHOOK_MESSAGE:"The message was not sent by a webhook.",EMOJI_TYPE:"Emoji must be a string or GuildEmoji/ReactionEmoji",EMOJI_MANAGED:"Emoji is managed and has no Author.",MISSING_MANAGE_EMOJIS_PERMISSION:e=>`Client must have Manage Emoji permission in guild ${e} to see emoji authors.`,REACTION_RESOLVE_USER:"Couldn't resolve the user ID to remove from the reaction.",VANITY_URL:"This guild does not have the VANITY_URL feature enabled.",DELETE_GROUP_DM_CHANNEL:"Bots don't have access to Group DM Channels and cannot delete them",FETCH_GROUP_DM_CHANNEL:"Bots don't have access to Group DM Channels and cannot fetch them"};for(const[e,t]of Object.entries(n))s(e,t)},function(e,t,i){"use strict";Object.defineProperty(t,"__esModule",{value:!0});class Collection extends Map{constructor(e){super(e),Object.defineProperty(this,"_array",{value:null,writable:!0,configurable:!0}),Object.defineProperty(this,"_keyArray",{value:null,writable:!0,configurable:!0})}get(e){return super.get(e)}set(e,t){return this._array=null,this._keyArray=null,super.set(e,t)}has(e){return super.has(e)}delete(e){return this._array=null,this._keyArray=null,super.delete(e)}clear(){return super.clear()}array(){return this._array&&this._array.length===this.size||(this._array=[...this.values()]),this._array}keyArray(){return this._keyArray&&this._keyArray.length===this.size||(this._keyArray=[...this.keys()]),this._keyArray}first(e){if(void 0===e)return this.values().next().value;if(e<0)return this.last(-1*e);e=Math.min(this.size,e);const t=this.values();return Array.from({length:e},()=>t.next().value)}firstKey(e){if(void 0===e)return this.keys().next().value;if(e<0)return this.lastKey(-1*e);e=Math.min(this.size,e);const t=this.keys();return Array.from({length:e},()=>t.next().value)}last(e){const t=this.array();return void 0===e?t[t.length-1]:e<0?this.first(-1*e):e?t.slice(-e):[]}lastKey(e){const t=this.keyArray();return void 0===e?t[t.length-1]:e<0?this.firstKey(-1*e):e?t.slice(-e):[]}random(e){let t=this.array();return void 0===e?t[Math.floor(Math.random()*t.length)]:0!==t.length&&e?(t=t.slice(),Array.from({length:e},()=>t.splice(Math.floor(Math.random()*t.length),1)[0])):[]}randomKey(e){let t=this.keyArray();return void 0===e?t[Math.floor(Math.random()*t.length)]:0!==t.length&&e?(t=t.slice(),Array.from({length:e},()=>t.splice(Math.floor(Math.random()*t.length),1)[0])):[]}find(e,t){void 0!==t&&(e=e.bind(t));for(const[t,i]of this)if(e(i,t,this))return i}findKey(e,t){void 0!==t&&(e=e.bind(t));for(const[t,i]of this)if(e(i,t,this))return t}sweep(e,t){void 0!==t&&(e=e.bind(t));const i=this.size;for(const[t,i]of this)e(i,t,this)&&this.delete(t);return i-this.size}filter(e,t){void 0!==t&&(e=e.bind(t));const i=new this.constructor[Symbol.species];for(const[t,s]of this)e(s,t,this)&&i.set(t,s);return i}partition(e,t){void 0!==t&&(e=e.bind(t));const i=[new this.constructor[Symbol.species],new this.constructor[Symbol.species]];for(const[t,s]of this)e(s,t,this)?i[0].set(t,s):i[1].set(t,s);return i}flatMap(e,t){const i=this.map(e,t);return(new this.constructor[Symbol.species]).concat(...i)}map(e,t){void 0!==t&&(e=e.bind(t));const i=this.entries();return Array.from({length:this.size},()=>{const[t,s]=i.next().value;return e(s,t,this)})}mapValues(e,t){void 0!==t&&(e=e.bind(t));const i=new this.constructor[Symbol.species];for(const[t,s]of this)i.set(t,e(s,t,this));return i}some(e,t){void 0!==t&&(e=e.bind(t));for(const[t,i]of this)if(e(i,t,this))return!0;return!1}every(e,t){void 0!==t&&(e=e.bind(t));for(const[t,i]of this)if(!e(i,t,this))return!1;return!0}reduce(e,t){let i;if(void 0!==t){i=t;for(const[t,s]of this)i=e(i,s,t,this);return i}let s=!0;for(const[t,n]of this)s?(i=n,s=!1):i=e(i,n,t,this);if(s)throw new TypeError("Reduce of empty collection with no initial value");return i}each(e,t){return this.forEach(e,t),this}tap(e,t){return void 0!==t&&(e=e.bind(t)),e(this),this}clone(){return new this.constructor[Symbol.species](this)}concat(...e){const t=this.clone();for(const i of e)for(const[e,s]of i)t.set(e,s);return t}equals(e){if(!e)return!1;if(this===e)return!0;if(this.size!==e.size)return!1;for(const[t,i]of this)if(!e.has(t)||i!==e.get(t))return!1;return!0}sort(e=((e,t)=>Number(e>t)||Number(e===t)-1)){const t=[...this.entries()];t.sort((t,i)=>e(t[1],i[1],t[0],i[0])),super.clear(),this._array=null,this._keyArray=null;for(const[e,i]of t)super.set(e,i);return this}intersect(e){return e.filter((e,t)=>this.has(t))}difference(e){return e.filter((e,t)=>!this.has(t)).concat(this.filter((t,i)=>!e.has(i)))}sorted(e=((e,t)=>Number(e>t)||Number(e===t)-1)){return new this.constructor[Symbol.species]([...this.entries()]).sort((t,i,s,n)=>e(t,i,s,n))}}t.Collection=Collection,Collection.default=Collection,t.default=Collection,e.exports=Collection},function(e,t,i){"use strict";const s=i(101),n=i(105),r=i(106),{Error:o}=i(3),a=i(2),{Endpoints:c}=i(0);e.exports=class RESTManager{constructor(e,t="Bot"){this.client=e,this.handlers=new a,this.tokenPrefix=t,this.versioned=!0,this.globalTimeout=null,e.options.restSweepInterval>0&&e.setInterval(()=>{this.handlers.sweep(e=>e._inactive)},1e3*e.options.restSweepInterval)}get api(){return n(this)}getAuth(){const e=this.client.token||this.client.accessToken;if(e)return`${this.tokenPrefix} ${e}`;throw new o("TOKEN_MISSING")}get cdn(){return c.CDN(this.client.options.http.cdn)}push(e,t){return new Promise((i,s)=>{e.push({request:t,resolve:i,reject:s,retries:0}).catch(s)})}request(e,t,i={}){const n=new s(this,e,t,i);let o=this.handlers.get(n.route);return o||(o=new r(this),this.handlers.set(n.route,o)),this.push(o,n)}set endpoint(e){this.client.options.http.api=e}}},function(e,t,i){"use strict";const s=i(102),n=i(103),r=i(104),o=i(36),{browser:a,UserAgent:c}=i(0);if(s.Agent)var l=new s.Agent({keepAlive:!0});e.exports=class APIRequest{constructor(e,t,i,s){this.rest=e,this.client=e.client,this.method=t,this.route=s.route,this.options=s;let n="";if(s.query){const e=Object.entries(s.query).filter(([,e])=>null!=e);n=new URLSearchParams(e).toString()}this.path=`${i}${n&&`?${n}`}`}make(){const e=(!1===this.options.versioned?this.client.options.http.api:`${this.client.options.http.api}/v${this.client.options.http.version}`)+this.path;let t,i={};if(!1!==this.options.auth&&(i.Authorization=this.rest.getAuth()),this.options.reason&&(i["X-Audit-Log-Reason"]=encodeURIComponent(this.options.reason)),a||(i["User-Agent"]=c),this.options.headers&&(i=Object.assign(i,this.options.headers)),this.options.files&&this.options.files.length){t=new n;for(const e of this.options.files)e&&e.file&&t.append(e.name,e.file,e.name);void 0!==this.options.data&&t.append("payload_json",JSON.stringify(this.options.data)),a||(i=Object.assign(i,t.getHeaders()))}else null!=this.options.data&&(t=JSON.stringify(this.options.data),i["Content-Type"]="application/json");const s=new r,h=this.client.setTimeout(()=>s.abort(),this.client.options.restRequestTimeout);return o(e,{method:this.method,headers:i,agent:l,body:t,signal:s.signal}).finally(()=>this.client.clearTimeout(h))}}},function(e,t){},function(e,t){e.exports="object"==typeof self?self.FormData:window.FormData},function(e,t,i){"use strict";const{AbortController:s,AbortSignal:n}="undefined"!=typeof self?self:"undefined"!=typeof window?window:void 0;e.exports=s,e.exports.AbortSignal=n,e.exports.default=s},function(e,t,i){"use strict";const s=()=>{},n=["get","post","delete","patch","put"],r=["toString","valueOf","inspect","constructor",Symbol.toPrimitive,Symbol.for("nodejs.util.inspect.custom")];e.exports=function(e){const t=[""],i={get(o,a){if(r.includes(a))return()=>t.join("/");if(n.includes(a)){const i=[];for(let e=0;e<t.length&&"reactions"!==t[e-1];e++)/\d{16,19}/g.test(t[e])&&!/channels|guilds/.test(t[e-1])?i.push(":id"):i.push(t[e]);return s=>e.request(a,t.join("/"),Object.assign({versioned:e.versioned,route:i.join("/")},s))}return t.push(a),new Proxy(s,i)},apply:(e,n,r)=>(t.push(...r.filter(e=>null!=e)),new Proxy(s,i))};return new Proxy(s,i)}},function(e,t,i){"use strict";const s=i(61),n=i(62),{Events:{RATE_LIMIT:r},browser:o}=i(0),a=i(4);function c(e){return e.headers.get("content-type").startsWith("application/json")?e.json():o?e.blob():e.buffer()}function l(e){return new Date(e).getTime()-Date.now()}e.exports=class RequestHandler{constructor(e){this.manager=e,this.busy=!1,this.queue=[],this.reset=-1,this.remaining=-1,this.limit=-1,this.retryAfter=-1}push(e){return this.busy?(this.queue.push(e),this.run()):this.execute(e)}run(){return 0===this.queue.length?Promise.resolve():this.execute(this.queue.shift())}get limited(){return Boolean(this.manager.globalTimeout)||this.remaining<=0&&Date.now()<this.reset}get _inactive(){return 0===this.queue.length&&!this.limited&&!0!==this.busy}async execute(e){if(this.busy)return this.queue.unshift(e),null;this.busy=!0;const{reject:t,request:i,resolve:o}=e;if(this.limited){const e=this.reset+this.manager.client.options.restTimeOffset-Date.now();this.manager.client.listenerCount(r)&&this.manager.client.emit(r,{timeout:e,limit:this.limit,method:i.method,path:i.path,route:i.route}),this.manager.globalTimeout?await this.manager.globalTimeout:await a.delayFor(e)}let h;try{h=await i.make()}catch(e){return this.busy=!1,t(new n(e.message,e.constructor.name,e.status,i.method,i.path))}if(h&&h.headers){const t=h.headers.get("date"),i=h.headers.get("x-ratelimit-limit"),s=h.headers.get("x-ratelimit-remaining"),n=h.headers.get("x-ratelimit-reset"),r=h.headers.get("retry-after");this.limit=i?Number(i):1/0,this.remaining=s?Number(s):1,this.reset=n?function(e,t){return new Date(1e3*Number(e)).getTime()-l(t)}(n,t):Date.now(),this.retryAfter=r?Number(r):-1,e.request.route.includes("reactions")&&(this.reset=new Date(t).getTime()-l(t)+250),h.headers.get("x-ratelimit-global")&&(this.manager.globalTimeout=a.delayFor(this.retryAfter),await this.manager.globalTimeout,this.manager.globalTimeout=null)}if(this.busy=!1,h.ok){return o(await c(h)),this.run()}if(429===h.status)return this.queue.unshift(e),this.manager.client.emit("debug",`429 hit on route ${e.request.route}`),await a.delayFor(this.retryAfter),this.run();if(h.status>=500&&h.status<600)return e.retries===this.manager.client.options.retryLimit?t(new n(h.statusText,h.constructor.name,h.status,e.request.method,i.path)):(e.retries++,this.queue.unshift(e),this.run());try{const e=await c(h);return h.status>=400&&h.status<500?t(new s(i.path,e,i.method,h.status)):null}catch(e){return t(new n(e.message,e.constructor.name,e.status,i.method,i.path))}}}},function(module,exports,__webpack_require__){"use strict";(function(process){const BaseClient=__webpack_require__(37),ActionsManager=__webpack_require__(108),ClientVoiceManager=__webpack_require__(158),WebSocketManager=__webpack_require__(159),{Error:Error,TypeError:TypeError,RangeError:RangeError}=__webpack_require__(3),ChannelManager=__webpack_require__(93),GuildEmojiManager=__webpack_require__(57),GuildManager=__webpack_require__(94),UserManager=__webpack_require__(95),ShardClientUtil=__webpack_require__(204),ClientApplication=__webpack_require__(50),GuildPreview=__webpack_require__(55),Invite=__webpack_require__(18),VoiceRegion=__webpack_require__(56),Webhook=__webpack_require__(12),Collection=__webpack_require__(2),{Events:Events,browser:browser,DefaultOptions:DefaultOptions}=__webpack_require__(0),DataResolver=__webpack_require__(9),Intents=__webpack_require__(96),Permissions=__webpack_require__(8),Structures=__webpack_require__(23);class Client extends BaseClient{constructor(e={}){super(Object.assign({_tokenType:"Bot"},e));let t=process.env;try{t=__webpack_require__(205).workerData||t}catch{}this.options.shards===DefaultOptions.shards&&"SHARDS"in t&&(this.options.shards=JSON.parse(t.SHARDS)),this.options.shardCount===DefaultOptions.shardCount&&("SHARD_COUNT"in t?this.options.shardCount=Number(t.SHARD_COUNT):Array.isArray(this.options.shards)&&(this.options.shardCount=this.options.shards.length));const i=typeof this.options.shards;"undefined"===i&&"number"==typeof this.options.shardCount&&(this.options.shards=Array.from({length:this.options.shardCount},(e,t)=>t)),"number"===i&&(this.options.shards=[this.options.shards]),Array.isArray(this.options.shards)&&(this.options.shards=[...new Set(this.options.shards.filter(e=>!isNaN(e)&&e>=0&&e<1/0&&e===(0|e)))]),this._validateOptions(),this.ws=new WebSocketManager(this),this.actions=new ActionsManager(this),this.voice=browser?null:new ClientVoiceManager(this),this.shard=!browser&&process.env.SHARDING_MANAGER?ShardClientUtil.singleton(this,process.env.SHARDING_MANAGER_MODE):null,this.users=new UserManager(this),this.guilds=new GuildManager(this),this.channels=new ChannelManager(this);const s=Structures.get("ClientPresence");this.presence=new s(this),Object.defineProperty(this,"token",{writable:!0}),browser||this.token||!("DISCORD_TOKEN"in process.env)?this.token=null:this.token=process.env.DISCORD_TOKEN,this.user=null,this.readyAt=null,this.options.messageSweepInterval>0&&this.setInterval(this.sweepMessages.bind(this),1e3*this.options.messageSweepInterval)}get emojis(){const e=new GuildEmojiManager({client:this});for(const t of this.guilds.cache.values())if(t.available)for(const i of t.emojis.cache.values())e.cache.set(i.id,i);return e}get readyTimestamp(){return this.readyAt?this.readyAt.getTime():null}get uptime(){return this.readyAt?Date.now()-this.readyAt:null}async login(e=this.token){if(!e||"string"!=typeof e)throw new Error("TOKEN_INVALID");this.token=e=e.replace(/^(Bot|Bearer)\s*/i,""),this.emit(Events.DEBUG,`Provided token: ${e.split(".").map((e,t)=>t>1?e.replace(/./g,"*"):e).join(".")}`),this.options.presence&&(this.options.ws.presence=await this.presence._parse(this.options.presence)),this.emit(Events.DEBUG,"Preparing to connect to the gateway...");try{return await this.ws.connect(),this.token}catch(e){throw this.destroy(),e}}destroy(){super.destroy(),this.ws.destroy(),this.token=null}fetchInvite(e){const t=DataResolver.resolveInviteCode(e);return this.api.invites(t).get({query:{with_counts:!0}}).then(e=>new Invite(this,e))}fetchWebhook(e,t){return this.api.webhooks(e,t).get().then(e=>new Webhook(this,e))}fetchVoiceRegions(){return this.api.voice.regions.get().then(e=>{const t=new Collection;for(const i of e)t.set(i.id,new VoiceRegion(i));return t})}sweepMessages(e=this.options.messageCacheLifetime){if("number"!=typeof e||isNaN(e))throw new TypeError("INVALID_TYPE","lifetime","number");if(e<=0)return this.emit(Events.DEBUG,"Didn't sweep messages - lifetime is unlimited"),-1;const t=1e3*e,i=Date.now();let s=0,n=0;for(const e of this.channels.cache.values())e.messages&&(s++,n+=e.messages.cache.sweep(e=>i-(e.editedTimestamp||e.createdTimestamp)>t));return this.emit(Events.DEBUG,`Swept ${n} messages older than ${e} seconds in ${s} text-based channels`),n}fetchApplication(){return this.api.oauth2.applications("@me").get().then(e=>new ClientApplication(this,e))}fetchGuildPreview(e){const t=this.guilds.resolveID(e);if(!t)throw new TypeError("INVALID_TYPE","guild","GuildResolvable");return this.api.guilds(t).preview.get().then(e=>new GuildPreview(this,e))}async generateInvite(e){e=Permissions.resolve(e);const t=await this.fetchApplication(),i=new URLSearchParams({client_id:t.id,permissions:e,scope:"bot"});return`${this.options.http.api}${this.api.oauth2.authorize}?${i}`}toJSON(){return super.toJSON({readyAt:!1,presences:!1})}_eval(script){return eval(script)}_validateOptions(e=this.options){if(void 0!==e.ws.intents&&(e.ws.intents=Intents.resolve(e.ws.intents)),"number"!=typeof e.shardCount||isNaN(e.shardCount)||e.shardCount<1)throw new TypeError("CLIENT_INVALID_OPTION","shardCount","a number greater than or equal to 1");if(e.shards&&"auto"!==e.shards&&!Array.isArray(e.shards))throw new TypeError("CLIENT_INVALID_OPTION","shards","'auto', a number or array of numbers");if(e.shards&&!e.shards.length)throw new RangeError("CLIENT_INVALID_PROVIDED_SHARDS");if("number"!=typeof e.messageCacheMaxSize||isNaN(e.messageCacheMaxSize))throw new TypeError("CLIENT_INVALID_OPTION","messageCacheMaxSize","a number");if("number"!=typeof e.messageCacheLifetime||isNaN(e.messageCacheLifetime))throw new TypeError("CLIENT_INVALID_OPTION","The messageCacheLifetime","a number");if("number"!=typeof e.messageSweepInterval||isNaN(e.messageSweepInterval))throw new TypeError("CLIENT_INVALID_OPTION","messageSweepInterval","a number");if("boolean"!=typeof e.fetchAllMembers)throw new TypeError("CLIENT_INVALID_OPTION","fetchAllMembers","a boolean");if("string"!=typeof e.disableMentions)throw new TypeError("CLIENT_INVALID_OPTION","disableMentions","a string");if(!Array.isArray(e.partials))throw new TypeError("CLIENT_INVALID_OPTION","partials","an Array");if("number"!=typeof e.restWsBridgeTimeout||isNaN(e.restWsBridgeTimeout))throw new TypeError("CLIENT_INVALID_OPTION","restWsBridgeTimeout","a number");if("number"!=typeof e.restRequestTimeout||isNaN(e.restRequestTimeout))throw new TypeError("CLIENT_INVALID_OPTION","restRequestTimeout","a number");if("number"!=typeof e.restSweepInterval||isNaN(e.restSweepInterval))throw new TypeError("CLIENT_INVALID_OPTION","restSweepInterval","a number");if("number"!=typeof e.retryLimit||isNaN(e.retryLimit))throw new TypeError("CLIENT_INVALID_OPTION","retryLimit","a number")}}module.exports=Client}).call(this,__webpack_require__(14))},function(e,t,i){"use strict";e.exports=class ActionsManager{constructor(e){this.client=e,this.register(i(109)),this.register(i(110)),this.register(i(111)),this.register(i(112)),this.register(i(113)),this.register(i(114)),this.register(i(115)),this.register(i(116)),this.register(i(117)),this.register(i(118)),this.register(i(137)),this.register(i(138)),this.register(i(139)),this.register(i(140)),this.register(i(141)),this.register(i(142)),this.register(i(143)),this.register(i(144)),this.register(i(145)),this.register(i(146)),this.register(i(147)),this.register(i(148)),this.register(i(149)),this.register(i(150)),this.register(i(151)),this.register(i(152)),this.register(i(153)),this.register(i(154)),this.register(i(155)),this.register(i(156)),this.register(i(157))}register(e){this[e.name.replace(/Action$/,"")]=new e(this.client)}}},function(e,t,i){"use strict";const s=i(1),{Events:n}=i(0);e.exports=class MessageCreateAction extends s{handle(e){const t=this.client,i=t.channels.cache.get(e.channel_id);if(i){const s=i.messages.cache.get(e.id);if(s)return{message:s};const r=i.messages.add(e),o=r.author;let a=r.member;return i.lastMessageID=e.id,o&&(o.lastMessageID=e.id,o.lastMessageChannelID=i.id),a&&(a.lastMessageID=e.id,a.lastMessageChannelID=i.id),t.emit(n.MESSAGE_CREATE,r),{message:r}}return{}}}},function(e,t,i){"use strict";const s=i(1),{Events:n}=i(0);e.exports=class MessageDeleteAction extends s{handle(e){const t=this.client,i=this.getChannel(e);let s;return i&&(s=this.getMessage(e,i),s&&(i.messages.cache.delete(s.id),s.deleted=!0,t.emit(n.MESSAGE_DELETE,s))),{message:s}}}},function(e,t,i){"use strict";const s=i(1),n=i(2),{Events:r}=i(0);e.exports=class MessageDeleteBulkAction extends s{handle(e){const t=this.client,i=t.channels.cache.get(e.channel_id);if(i){const s=e.ids,o=new n;for(const t of s){const s=this.getMessage({id:t,guild_id:e.guild_id},i,!1);s&&(s.deleted=!0,o.set(s.id,s),i.messages.cache.delete(t))}return o.size>0&&t.emit(r.MESSAGE_BULK_DELETE,o),{messages:o}}return{}}}},function(e,t,i){"use strict";const s=i(1);e.exports=class MessageUpdateAction extends s{handle(e){const t=this.getChannel(e);if(t){const{id:i,channel_id:s,guild_id:n,author:r,timestamp:o,type:a}=e,c=this.getMessage({id:i,channel_id:s,guild_id:n,author:r,timestamp:o,type:a},t);if(c)return c.patch(e),{old:c._edits[0],updated:c}}return{}}}},function(e,t,i){"use strict";const s=i(1),{Events:n}=i(0),{PartialTypes:r}=i(0);e.exports=class MessageReactionAdd extends s{handle(e){if(!e.emoji)return!1;const t=this.getUser(e);if(!t)return!1;const i=this.getChannel(e);if(!i||"voice"===i.type)return!1;const s=this.getMessage(e,i);if(!s)return!1;if(s.partial&&!this.client.options.partials.includes(r.REACTION))return!1;const o=s.reactions.add({emoji:e.emoji,count:s.partial?null:0,me:t.id===this.client.user.id});return!!o&&(o._add(t),this.client.emit(n.MESSAGE_REACTION_ADD,o,t),{message:s,reaction:o,user:t})}}},function(e,t,i){"use strict";const s=i(1),{Events:n}=i(0);e.exports=class MessageReactionRemove extends s{handle(e){if(!e.emoji)return!1;const t=this.getUser(e);if(!t)return!1;const i=this.getChannel(e);if(!i||"voice"===i.type)return!1;const s=this.getMessage(e,i);if(!s)return!1;const r=this.getReaction(e,s,t);return!!r&&(r._remove(t),this.client.emit(n.MESSAGE_REACTION_REMOVE,r,t),{message:s,reaction:r,user:t})}}},function(e,t,i){"use strict";const s=i(1),{Events:n}=i(0);e.exports=class MessageReactionRemoveAll extends s{handle(e){const t=this.getChannel(e);if(!t||"voice"===t.type)return!1;const i=this.getMessage(e,t);return!!i&&(i.reactions.cache.clear(),this.client.emit(n.MESSAGE_REACTION_REMOVE_ALL,i),{message:i})}}},function(e,t,i){"use strict";const s=i(1),{Events:n}=i(0);e.exports=class MessageReactionRemoveEmoji extends s{handle(e){const t=this.getChannel(e);if(!t||"voice"===t.type)return!1;const i=this.getMessage(e,t);if(!i)return!1;const s=this.getReaction(e,i);return!!s&&(i.partial||i.reactions.cache.delete(s.emoji.id||s.emoji.name),this.client.emit(n.MESSAGE_REACTION_REMOVE_EMOJI,s),{reaction:s})}}},function(e,t,i){"use strict";const s=i(1),{Events:n}=i(0);e.exports=class ChannelCreateAction extends s{handle(e){const t=this.client,i=t.channels.cache.has(e.id),s=t.channels.add(e);return!i&&s&&t.emit(n.CHANNEL_CREATE,s),{channel:s}}}},function(e,t,i){"use strict";const s=i(1),n=i(39),{Events:r}=i(0);e.exports=class ChannelDeleteAction extends s{constructor(e){super(e),this.deleted=new Map}handle(e){const t=this.client;let i=t.channels.cache.get(e.id);if(i){if(t.channels.remove(i.id),i.deleted=!0,i.messages&&!(i instanceof n))for(const e of i.messages.cache.values())e.deleted=!0;t.emit(r.CHANNEL_DELETE,i)}return{channel:i}}}},function(e,t,i){e.exports=n;var s=i(15).EventEmitter;function n(){s.call(this)}i(22)(n,s),n.Readable=i(45),n.Writable=i(128),n.Duplex=i(129),n.Transform=i(130),n.PassThrough=i(131),n.Stream=n,n.prototype.pipe=function(e,t){var i=this;function n(t){e.writable&&!1===e.write(t)&&i.pause&&i.pause()}function r(){i.readable&&i.resume&&i.resume()}i.on("data",n),e.on("drain",r),e._isStdio||t&&!1===t.end||(i.on("end",a),i.on("close",c));var o=!1;function a(){o||(o=!0,e.end())}function c(){o||(o=!0,"function"==typeof e.destroy&&e.destroy())}function l(e){if(h(),0===s.listenerCount(this,"error"))throw e}function h(){i.removeListener("data",n),e.removeListener("drain",r),i.removeListener("end",a),i.removeListener("close",c),i.removeListener("error",l),e.removeListener("error",l),i.removeListener("end",h),i.removeListener("close",h),e.removeListener("close",h)}return i.on("error",l),e.on("error",l),i.on("end",h),i.on("close",h),e.on("close",h),e.emit("pipe",i),e}},function(e,t,i){"use strict";(function(e){var s=i(121),n=i(122),r=i(65);function o(){return c.TYPED_ARRAY_SUPPORT?2147483647:1073741823}function a(e,t){if(o()<t)throw new RangeError("Invalid typed array length");return c.TYPED_ARRAY_SUPPORT?(e=new Uint8Array(t)).__proto__=c.prototype:(null===e&&(e=new c(t)),e.length=t),e}function c(e,t,i){if(!(c.TYPED_ARRAY_SUPPORT||this instanceof c))return new c(e,t,i);if("number"==typeof e){if("string"==typeof t)throw new Error("If encoding is specified then the first argument must be a string");return u(this,e)}return l(this,e,t,i)}function l(e,t,i,s){if("number"==typeof t)throw new TypeError('"value" argument must not be a number');return"undefined"!=typeof ArrayBuffer&&t instanceof ArrayBuffer?function(e,t,i,s){if(t.byteLength,i<0||t.byteLength<i)throw new RangeError("'offset' is out of bounds");if(t.byteLength<i+(s||0))throw new RangeError("'length' is out of bounds");t=void 0===i&&void 0===s?new Uint8Array(t):void 0===s?new Uint8Array(t,i):new Uint8Array(t,i,s);c.TYPED_ARRAY_SUPPORT?(e=t).__proto__=c.prototype:e=d(e,t);return e}(e,t,i,s):"string"==typeof t?function(e,t,i){"string"==typeof i&&""!==i||(i="utf8");if(!c.isEncoding(i))throw new TypeError('"encoding" must be a valid string encoding');var s=0|f(t,i),n=(e=a(e,s)).write(t,i);n!==s&&(e=e.slice(0,n));return e}(e,t,i):function(e,t){if(c.isBuffer(t)){var i=0|p(t.length);return 0===(e=a(e,i)).length||t.copy(e,0,0,i),e}if(t){if("undefined"!=typeof ArrayBuffer&&t.buffer instanceof ArrayBuffer||"length"in t)return"number"!=typeof t.length||(s=t.length)!=s?a(e,0):d(e,t);if("Buffer"===t.type&&r(t.data))return d(e,t.data)}var s;throw new TypeError("First argument must be a string, Buffer, ArrayBuffer, Array, or array-like object.")}(e,t)}function h(e){if("number"!=typeof e)throw new TypeError('"size" argument must be a number');if(e<0)throw new RangeError('"size" argument must not be negative')}function u(e,t){if(h(t),e=a(e,t<0?0:0|p(t)),!c.TYPED_ARRAY_SUPPORT)for(var i=0;i<t;++i)e[i]=0;return e}function d(e,t){var i=t.length<0?0:0|p(t.length);e=a(e,i);for(var s=0;s<i;s+=1)e[s]=255&t[s];return e}function p(e){if(e>=o())throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+o().toString(16)+" bytes");return 0|e}function f(e,t){if(c.isBuffer(e))return e.length;if("undefined"!=typeof ArrayBuffer&&"function"==typeof ArrayBuffer.isView&&(ArrayBuffer.isView(e)||e instanceof ArrayBuffer))return e.byteLength;"string"!=typeof e&&(e=""+e);var i=e.length;if(0===i)return 0;for(var s=!1;;)switch(t){case"ascii":case"latin1":case"binary":return i;case"utf8":case"utf-8":case void 0:return B(e).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*i;case"hex":return i>>>1;case"base64":return V(e).length;default:if(s)return B(e).length;t=(""+t).toLowerCase(),s=!0}}function m(e,t,i){var s=!1;if((void 0===t||t<0)&&(t=0),t>this.length)return"";if((void 0===i||i>this.length)&&(i=this.length),i<=0)return"";if((i>>>=0)<=(t>>>=0))return"";for(e||(e="utf8");;)switch(e){case"hex":return R(this,t,i);case"utf8":case"utf-8":return T(this,t,i);case"ascii":return D(this,t,i);case"latin1":case"binary":return N(this,t,i);case"base64":return I(this,t,i);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return O(this,t,i);default:if(s)throw new TypeError("Unknown encoding: "+e);e=(e+"").toLowerCase(),s=!0}}function g(e,t,i){var s=e[t];e[t]=e[i],e[i]=s}function E(e,t,i,s,n){if(0===e.length)return-1;if("string"==typeof i?(s=i,i=0):i>2147483647?i=2147483647:i<-2147483648&&(i=-2147483648),i=+i,isNaN(i)&&(i=n?0:e.length-1),i<0&&(i=e.length+i),i>=e.length){if(n)return-1;i=e.length-1}else if(i<0){if(!n)return-1;i=0}if("string"==typeof t&&(t=c.from(t,s)),c.isBuffer(t))return 0===t.length?-1:_(e,t,i,s,n);if("number"==typeof t)return t&=255,c.TYPED_ARRAY_SUPPORT&&"function"==typeof Uint8Array.prototype.indexOf?n?Uint8Array.prototype.indexOf.call(e,t,i):Uint8Array.prototype.lastIndexOf.call(e,t,i):_(e,[t],i,s,n);throw new TypeError("val must be string, number or Buffer")}function _(e,t,i,s,n){var r,o=1,a=e.length,c=t.length;if(void 0!==s&&("ucs2"===(s=String(s).toLowerCase())||"ucs-2"===s||"utf16le"===s||"utf-16le"===s)){if(e.length<2||t.length<2)return-1;o=2,a/=2,c/=2,i/=2}function l(e,t){return 1===o?e[t]:e.readUInt16BE(t*o)}if(n){var h=-1;for(r=i;r<a;r++)if(l(e,r)===l(t,-1===h?0:r-h)){if(-1===h&&(h=r),r-h+1===c)return h*o}else-1!==h&&(r-=r-h),h=-1}else for(i+c>a&&(i=a-c),r=i;r>=0;r--){for(var u=!0,d=0;d<c;d++)if(l(e,r+d)!==l(t,d)){u=!1;break}if(u)return r}return-1}function y(e,t,i,s){i=Number(i)||0;var n=e.length-i;s?(s=Number(s))>n&&(s=n):s=n;var r=t.length;if(r%2!=0)throw new TypeError("Invalid hex string");s>r/2&&(s=r/2);for(var o=0;o<s;++o){var a=parseInt(t.substr(2*o,2),16);if(isNaN(a))return o;e[i+o]=a}return o}function b(e,t,i,s){return H(B(t,e.length-i),e,i,s)}function v(e,t,i,s){return H(function(e){for(var t=[],i=0;i<e.length;++i)t.push(255&e.charCodeAt(i));return t}(t),e,i,s)}function w(e,t,i,s){return v(e,t,i,s)}function A(e,t,i,s){return H(V(t),e,i,s)}function S(e,t,i,s){return H(function(e,t){for(var i,s,n,r=[],o=0;o<e.length&&!((t-=2)<0);++o)i=e.charCodeAt(o),s=i>>8,n=i%256,r.push(n),r.push(s);return r}(t,e.length-i),e,i,s)}function I(e,t,i){return 0===t&&i===e.length?s.fromByteArray(e):s.fromByteArray(e.slice(t,i))}function T(e,t,i){i=Math.min(e.length,i);for(var s=[],n=t;n<i;){var r,o,a,c,l=e[n],h=null,u=l>239?4:l>223?3:l>191?2:1;if(n+u<=i)switch(u){case 1:l<128&&(h=l);break;case 2:128==(192&(r=e[n+1]))&&(c=(31&l)<<6|63&r)>127&&(h=c);break;case 3:r=e[n+1],o=e[n+2],128==(192&r)&&128==(192&o)&&(c=(15&l)<<12|(63&r)<<6|63&o)>2047&&(c<55296||c>57343)&&(h=c);break;case 4:r=e[n+1],o=e[n+2],a=e[n+3],128==(192&r)&&128==(192&o)&&128==(192&a)&&(c=(15&l)<<18|(63&r)<<12|(63&o)<<6|63&a)>65535&&c<1114112&&(h=c)}null===h?(h=65533,u=1):h>65535&&(h-=65536,s.push(h>>>10&1023|55296),h=56320|1023&h),s.push(h),n+=u}return function(e){var t=e.length;if(t<=4096)return String.fromCharCode.apply(String,e);var i="",s=0;for(;s<t;)i+=String.fromCharCode.apply(String,e.slice(s,s+=4096));return i}(s)}t.Buffer=c,t.SlowBuffer=function(e){+e!=e&&(e=0);return c.alloc(+e)},t.INSPECT_MAX_BYTES=50,c.TYPED_ARRAY_SUPPORT=void 0!==e.TYPED_ARRAY_SUPPORT?e.TYPED_ARRAY_SUPPORT:function(){try{var e=new Uint8Array(1);return e.__proto__={__proto__:Uint8Array.prototype,foo:function(){return 42}},42===e.foo()&&"function"==typeof e.subarray&&0===e.subarray(1,1).byteLength}catch(e){return!1}}(),t.kMaxLength=o(),c.poolSize=8192,c._augment=function(e){return e.__proto__=c.prototype,e},c.from=function(e,t,i){return l(null,e,t,i)},c.TYPED_ARRAY_SUPPORT&&(c.prototype.__proto__=Uint8Array.prototype,c.__proto__=Uint8Array,"undefined"!=typeof Symbol&&Symbol.species&&c[Symbol.species]===c&&Object.defineProperty(c,Symbol.species,{value:null,configurable:!0})),c.alloc=function(e,t,i){return function(e,t,i,s){return h(t),t<=0?a(e,t):void 0!==i?"string"==typeof s?a(e,t).fill(i,s):a(e,t).fill(i):a(e,t)}(null,e,t,i)},c.allocUnsafe=function(e){return u(null,e)},c.allocUnsafeSlow=function(e){return u(null,e)},c.isBuffer=function(e){return!(null==e||!e._isBuffer)},c.compare=function(e,t){if(!c.isBuffer(e)||!c.isBuffer(t))throw new TypeError("Arguments must be Buffers");if(e===t)return 0;for(var i=e.length,s=t.length,n=0,r=Math.min(i,s);n<r;++n)if(e[n]!==t[n]){i=e[n],s=t[n];break}return i<s?-1:s<i?1:0},c.isEncoding=function(e){switch(String(e).toLowerCase()){case"hex":case"utf8":case"utf-8":case"ascii":case"latin1":case"binary":case"base64":case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return!0;default:return!1}},c.concat=function(e,t){if(!r(e))throw new TypeError('"list" argument must be an Array of Buffers');if(0===e.length)return c.alloc(0);var i;if(void 0===t)for(t=0,i=0;i<e.length;++i)t+=e[i].length;var s=c.allocUnsafe(t),n=0;for(i=0;i<e.length;++i){var o=e[i];if(!c.isBuffer(o))throw new TypeError('"list" argument must be an Array of Buffers');o.copy(s,n),n+=o.length}return s},c.byteLength=f,c.prototype._isBuffer=!0,c.prototype.swap16=function(){var e=this.length;if(e%2!=0)throw new RangeError("Buffer size must be a multiple of 16-bits");for(var t=0;t<e;t+=2)g(this,t,t+1);return this},c.prototype.swap32=function(){var e=this.length;if(e%4!=0)throw new RangeError("Buffer size must be a multiple of 32-bits");for(var t=0;t<e;t+=4)g(this,t,t+3),g(this,t+1,t+2);return this},c.prototype.swap64=function(){var e=this.length;if(e%8!=0)throw new RangeError("Buffer size must be a multiple of 64-bits");for(var t=0;t<e;t+=8)g(this,t,t+7),g(this,t+1,t+6),g(this,t+2,t+5),g(this,t+3,t+4);return this},c.prototype.toString=function(){var e=0|this.length;return 0===e?"":0===arguments.length?T(this,0,e):m.apply(this,arguments)},c.prototype.equals=function(e){if(!c.isBuffer(e))throw new TypeError("Argument must be a Buffer");return this===e||0===c.compare(this,e)},c.prototype.inspect=function(){var e="",i=t.INSPECT_MAX_BYTES;return this.length>0&&(e=this.toString("hex",0,i).match(/.{2}/g).join(" "),this.length>i&&(e+=" ... ")),"<Buffer "+e+">"},c.prototype.compare=function(e,t,i,s,n){if(!c.isBuffer(e))throw new TypeError("Argument must be a Buffer");if(void 0===t&&(t=0),void 0===i&&(i=e?e.length:0),void 0===s&&(s=0),void 0===n&&(n=this.length),t<0||i>e.length||s<0||n>this.length)throw new RangeError("out of range index");if(s>=n&&t>=i)return 0;if(s>=n)return-1;if(t>=i)return 1;if(this===e)return 0;for(var r=(n>>>=0)-(s>>>=0),o=(i>>>=0)-(t>>>=0),a=Math.min(r,o),l=this.slice(s,n),h=e.slice(t,i),u=0;u<a;++u)if(l[u]!==h[u]){r=l[u],o=h[u];break}return r<o?-1:o<r?1:0},c.prototype.includes=function(e,t,i){return-1!==this.indexOf(e,t,i)},c.prototype.indexOf=function(e,t,i){return E(this,e,t,i,!0)},c.prototype.lastIndexOf=function(e,t,i){return E(this,e,t,i,!1)},c.prototype.write=function(e,t,i,s){if(void 0===t)s="utf8",i=this.length,t=0;else if(void 0===i&&"string"==typeof t)s=t,i=this.length,t=0;else{if(!isFinite(t))throw new Error("Buffer.write(string, encoding, offset[, length]) is no longer supported");t|=0,isFinite(i)?(i|=0,void 0===s&&(s="utf8")):(s=i,i=void 0)}var n=this.length-t;if((void 0===i||i>n)&&(i=n),e.length>0&&(i<0||t<0)||t>this.length)throw new RangeError("Attempt to write outside buffer bounds");s||(s="utf8");for(var r=!1;;)switch(s){case"hex":return y(this,e,t,i);case"utf8":case"utf-8":return b(this,e,t,i);case"ascii":return v(this,e,t,i);case"latin1":case"binary":return w(this,e,t,i);case"base64":return A(this,e,t,i);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return S(this,e,t,i);default:if(r)throw new TypeError("Unknown encoding: "+s);s=(""+s).toLowerCase(),r=!0}},c.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};function D(e,t,i){var s="";i=Math.min(e.length,i);for(var n=t;n<i;++n)s+=String.fromCharCode(127&e[n]);return s}function N(e,t,i){var s="";i=Math.min(e.length,i);for(var n=t;n<i;++n)s+=String.fromCharCode(e[n]);return s}function R(e,t,i){var s=e.length;(!t||t<0)&&(t=0),(!i||i<0||i>s)&&(i=s);for(var n="",r=t;r<i;++r)n+=j(e[r]);return n}function O(e,t,i){for(var s=e.slice(t,i),n="",r=0;r<s.length;r+=2)n+=String.fromCharCode(s[r]+256*s[r+1]);return n}function C(e,t,i){if(e%1!=0||e<0)throw new RangeError("offset is not uint");if(e+t>i)throw new RangeError("Trying to access beyond buffer length")}function L(e,t,i,s,n,r){if(!c.isBuffer(e))throw new TypeError('"buffer" argument must be a Buffer instance');if(t>n||t<r)throw new RangeError('"value" argument is out of bounds');if(i+s>e.length)throw new RangeError("Index out of range")}function M(e,t,i,s){t<0&&(t=65535+t+1);for(var n=0,r=Math.min(e.length-i,2);n<r;++n)e[i+n]=(t&255<<8*(s?n:1-n))>>>8*(s?n:1-n)}function U(e,t,i,s){t<0&&(t=4294967295+t+1);for(var n=0,r=Math.min(e.length-i,4);n<r;++n)e[i+n]=t>>>8*(s?n:3-n)&255}function P(e,t,i,s,n,r){if(i+s>e.length)throw new RangeError("Index out of range");if(i<0)throw new RangeError("Index out of range")}function x(e,t,i,s,r){return r||P(e,0,i,4),n.write(e,t,i,s,23,4),i+4}function G(e,t,i,s,r){return r||P(e,0,i,8),n.write(e,t,i,s,52,8),i+8}c.prototype.slice=function(e,t){var i,s=this.length;if((e=~~e)<0?(e+=s)<0&&(e=0):e>s&&(e=s),(t=void 0===t?s:~~t)<0?(t+=s)<0&&(t=0):t>s&&(t=s),t<e&&(t=e),c.TYPED_ARRAY_SUPPORT)(i=this.subarray(e,t)).__proto__=c.prototype;else{var n=t-e;i=new c(n,void 0);for(var r=0;r<n;++r)i[r]=this[r+e]}return i},c.prototype.readUIntLE=function(e,t,i){e|=0,t|=0,i||C(e,t,this.length);for(var s=this[e],n=1,r=0;++r<t&&(n*=256);)s+=this[e+r]*n;return s},c.prototype.readUIntBE=function(e,t,i){e|=0,t|=0,i||C(e,t,this.length);for(var s=this[e+--t],n=1;t>0&&(n*=256);)s+=this[e+--t]*n;return s},c.prototype.readUInt8=function(e,t){return t||C(e,1,this.length),this[e]},c.prototype.readUInt16LE=function(e,t){return t||C(e,2,this.length),this[e]|this[e+1]<<8},c.prototype.readUInt16BE=function(e,t){return t||C(e,2,this.length),this[e]<<8|this[e+1]},c.prototype.readUInt32LE=function(e,t){return t||C(e,4,this.length),(this[e]|this[e+1]<<8|this[e+2]<<16)+16777216*this[e+3]},c.prototype.readUInt32BE=function(e,t){return t||C(e,4,this.length),16777216*this[e]+(this[e+1]<<16|this[e+2]<<8|this[e+3])},c.prototype.readIntLE=function(e,t,i){e|=0,t|=0,i||C(e,t,this.length);for(var s=this[e],n=1,r=0;++r<t&&(n*=256);)s+=this[e+r]*n;return s>=(n*=128)&&(s-=Math.pow(2,8*t)),s},c.prototype.readIntBE=function(e,t,i){e|=0,t|=0,i||C(e,t,this.length);for(var s=t,n=1,r=this[e+--s];s>0&&(n*=256);)r+=this[e+--s]*n;return r>=(n*=128)&&(r-=Math.pow(2,8*t)),r},c.prototype.readInt8=function(e,t){return t||C(e,1,this.length),128&this[e]?-1*(255-this[e]+1):this[e]},c.prototype.readInt16LE=function(e,t){t||C(e,2,this.length);var i=this[e]|this[e+1]<<8;return 32768&i?4294901760|i:i},c.prototype.readInt16BE=function(e,t){t||C(e,2,this.length);var i=this[e+1]|this[e]<<8;return 32768&i?4294901760|i:i},c.prototype.readInt32LE=function(e,t){return t||C(e,4,this.length),this[e]|this[e+1]<<8|this[e+2]<<16|this[e+3]<<24},c.prototype.readInt32BE=function(e,t){return t||C(e,4,this.length),this[e]<<24|this[e+1]<<16|this[e+2]<<8|this[e+3]},c.prototype.readFloatLE=function(e,t){return t||C(e,4,this.length),n.read(this,e,!0,23,4)},c.prototype.readFloatBE=function(e,t){return t||C(e,4,this.length),n.read(this,e,!1,23,4)},c.prototype.readDoubleLE=function(e,t){return t||C(e,8,this.length),n.read(this,e,!0,52,8)},c.prototype.readDoubleBE=function(e,t){return t||C(e,8,this.length),n.read(this,e,!1,52,8)},c.prototype.writeUIntLE=function(e,t,i,s){(e=+e,t|=0,i|=0,s)||L(this,e,t,i,Math.pow(2,8*i)-1,0);var n=1,r=0;for(this[t]=255&e;++r<i&&(n*=256);)this[t+r]=e/n&255;return t+i},c.prototype.writeUIntBE=function(e,t,i,s){(e=+e,t|=0,i|=0,s)||L(this,e,t,i,Math.pow(2,8*i)-1,0);var n=i-1,r=1;for(this[t+n]=255&e;--n>=0&&(r*=256);)this[t+n]=e/r&255;return t+i},c.prototype.writeUInt8=function(e,t,i){return e=+e,t|=0,i||L(this,e,t,1,255,0),c.TYPED_ARRAY_SUPPORT||(e=Math.floor(e)),this[t]=255&e,t+1},c.prototype.writeUInt16LE=function(e,t,i){return e=+e,t|=0,i||L(this,e,t,2,65535,0),c.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8):M(this,e,t,!0),t+2},c.prototype.writeUInt16BE=function(e,t,i){return e=+e,t|=0,i||L(this,e,t,2,65535,0),c.TYPED_ARRAY_SUPPORT?(this[t]=e>>>8,this[t+1]=255&e):M(this,e,t,!1),t+2},c.prototype.writeUInt32LE=function(e,t,i){return e=+e,t|=0,i||L(this,e,t,4,4294967295,0),c.TYPED_ARRAY_SUPPORT?(this[t+3]=e>>>24,this[t+2]=e>>>16,this[t+1]=e>>>8,this[t]=255&e):U(this,e,t,!0),t+4},c.prototype.writeUInt32BE=function(e,t,i){return e=+e,t|=0,i||L(this,e,t,4,4294967295,0),c.TYPED_ARRAY_SUPPORT?(this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e):U(this,e,t,!1),t+4},c.prototype.writeIntLE=function(e,t,i,s){if(e=+e,t|=0,!s){var n=Math.pow(2,8*i-1);L(this,e,t,i,n-1,-n)}var r=0,o=1,a=0;for(this[t]=255&e;++r<i&&(o*=256);)e<0&&0===a&&0!==this[t+r-1]&&(a=1),this[t+r]=(e/o>>0)-a&255;return t+i},c.prototype.writeIntBE=function(e,t,i,s){if(e=+e,t|=0,!s){var n=Math.pow(2,8*i-1);L(this,e,t,i,n-1,-n)}var r=i-1,o=1,a=0;for(this[t+r]=255&e;--r>=0&&(o*=256);)e<0&&0===a&&0!==this[t+r+1]&&(a=1),this[t+r]=(e/o>>0)-a&255;return t+i},c.prototype.writeInt8=function(e,t,i){return e=+e,t|=0,i||L(this,e,t,1,127,-128),c.TYPED_ARRAY_SUPPORT||(e=Math.floor(e)),e<0&&(e=255+e+1),this[t]=255&e,t+1},c.prototype.writeInt16LE=function(e,t,i){return e=+e,t|=0,i||L(this,e,t,2,32767,-32768),c.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8):M(this,e,t,!0),t+2},c.prototype.writeInt16BE=function(e,t,i){return e=+e,t|=0,i||L(this,e,t,2,32767,-32768),c.TYPED_ARRAY_SUPPORT?(this[t]=e>>>8,this[t+1]=255&e):M(this,e,t,!1),t+2},c.prototype.writeInt32LE=function(e,t,i){return e=+e,t|=0,i||L(this,e,t,4,2147483647,-2147483648),c.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8,this[t+2]=e>>>16,this[t+3]=e>>>24):U(this,e,t,!0),t+4},c.prototype.writeInt32BE=function(e,t,i){return e=+e,t|=0,i||L(this,e,t,4,2147483647,-2147483648),e<0&&(e=4294967295+e+1),c.TYPED_ARRAY_SUPPORT?(this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e):U(this,e,t,!1),t+4},c.prototype.writeFloatLE=function(e,t,i){return x(this,e,t,!0,i)},c.prototype.writeFloatBE=function(e,t,i){return x(this,e,t,!1,i)},c.prototype.writeDoubleLE=function(e,t,i){return G(this,e,t,!0,i)},c.prototype.writeDoubleBE=function(e,t,i){return G(this,e,t,!1,i)},c.prototype.copy=function(e,t,i,s){if(i||(i=0),s||0===s||(s=this.length),t>=e.length&&(t=e.length),t||(t=0),s>0&&s<i&&(s=i),s===i)return 0;if(0===e.length||0===this.length)return 0;if(t<0)throw new RangeError("targetStart out of bounds");if(i<0||i>=this.length)throw new RangeError("sourceStart out of bounds");if(s<0)throw new RangeError("sourceEnd out of bounds");s>this.length&&(s=this.length),e.length-t<s-i&&(s=e.length-t+i);var n,r=s-i;if(this===e&&i<t&&t<s)for(n=r-1;n>=0;--n)e[n+t]=this[n+i];else if(r<1e3||!c.TYPED_ARRAY_SUPPORT)for(n=0;n<r;++n)e[n+t]=this[n+i];else Uint8Array.prototype.set.call(e,this.subarray(i,i+r),t);return r},c.prototype.fill=function(e,t,i,s){if("string"==typeof e){if("string"==typeof t?(s=t,t=0,i=this.length):"string"==typeof i&&(s=i,i=this.length),1===e.length){var n=e.charCodeAt(0);n<256&&(e=n)}if(void 0!==s&&"string"!=typeof s)throw new TypeError("encoding must be a string");if("string"==typeof s&&!c.isEncoding(s))throw new TypeError("Unknown encoding: "+s)}else"number"==typeof e&&(e&=255);if(t<0||this.length<t||this.length<i)throw new RangeError("Out of range index");if(i<=t)return this;var r;if(t>>>=0,i=void 0===i?this.length:i>>>0,e||(e=0),"number"==typeof e)for(r=t;r<i;++r)this[r]=e;else{var o=c.isBuffer(e)?e:B(new c(e,s).toString()),a=o.length;for(r=0;r<i-t;++r)this[r+t]=o[r%a]}return this};var k=/[^+\/0-9A-Za-z-_]/g;function j(e){return e<16?"0"+e.toString(16):e.toString(16)}function B(e,t){var i;t=t||1/0;for(var s=e.length,n=null,r=[],o=0;o<s;++o){if((i=e.charCodeAt(o))>55295&&i<57344){if(!n){if(i>56319){(t-=3)>-1&&r.push(239,191,189);continue}if(o+1===s){(t-=3)>-1&&r.push(239,191,189);continue}n=i;continue}if(i<56320){(t-=3)>-1&&r.push(239,191,189),n=i;continue}i=65536+(n-55296<<10|i-56320)}else n&&(t-=3)>-1&&r.push(239,191,189);if(n=null,i<128){if((t-=1)<0)break;r.push(i)}else if(i<2048){if((t-=2)<0)break;r.push(i>>6|192,63&i|128)}else if(i<65536){if((t-=3)<0)break;r.push(i>>12|224,i>>6&63|128,63&i|128)}else{if(!(i<1114112))throw new Error("Invalid code point");if((t-=4)<0)break;r.push(i>>18|240,i>>12&63|128,i>>6&63|128,63&i|128)}}return r}function V(e){return s.toByteArray(function(e){if((e=function(e){return e.trim?e.trim():e.replace(/^\s+|\s+$/g,"")}(e).replace(k,"")).length<2)return"";for(;e.length%4!=0;)e+="=";return e}(e))}function H(e,t,i,s){for(var n=0;n<s&&!(n+i>=t.length||n>=e.length);++n)t[n+i]=e[n];return n}}).call(this,i(21))},function(e,t,i){"use strict";t.byteLength=function(e){var t=l(e),i=t[0],s=t[1];return 3*(i+s)/4-s},t.toByteArray=function(e){var t,i,s=l(e),o=s[0],a=s[1],c=new r(function(e,t,i){return 3*(t+i)/4-i}(0,o,a)),h=0,u=a>0?o-4:o;for(i=0;i<u;i+=4)t=n[e.charCodeAt(i)]<<18|n[e.charCodeAt(i+1)]<<12|n[e.charCodeAt(i+2)]<<6|n[e.charCodeAt(i+3)],c[h++]=t>>16&255,c[h++]=t>>8&255,c[h++]=255&t;2===a&&(t=n[e.charCodeAt(i)]<<2|n[e.charCodeAt(i+1)]>>4,c[h++]=255&t);1===a&&(t=n[e.charCodeAt(i)]<<10|n[e.charCodeAt(i+1)]<<4|n[e.charCodeAt(i+2)]>>2,c[h++]=t>>8&255,c[h++]=255&t);return c},t.fromByteArray=function(e){for(var t,i=e.length,n=i%3,r=[],o=0,a=i-n;o<a;o+=16383)r.push(h(e,o,o+16383>a?a:o+16383));1===n?(t=e[i-1],r.push(s[t>>2]+s[t<<4&63]+"==")):2===n&&(t=(e[i-2]<<8)+e[i-1],r.push(s[t>>10]+s[t>>4&63]+s[t<<2&63]+"="));return r.join("")};for(var s=[],n=[],r="undefined"!=typeof Uint8Array?Uint8Array:Array,o="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",a=0,c=o.length;a<c;++a)s[a]=o[a],n[o.charCodeAt(a)]=a;function l(e){var t=e.length;if(t%4>0)throw new Error("Invalid string. Length must be a multiple of 4");var i=e.indexOf("=");return-1===i&&(i=t),[i,i===t?0:4-i%4]}function h(e,t,i){for(var n,r,o=[],a=t;a<i;a+=3)n=(e[a]<<16&16711680)+(e[a+1]<<8&65280)+(255&e[a+2]),o.push(s[(r=n)>>18&63]+s[r>>12&63]+s[r>>6&63]+s[63&r]);return o.join("")}n["-".charCodeAt(0)]=62,n["_".charCodeAt(0)]=63},function(e,t){t.read=function(e,t,i,s,n){var r,o,a=8*n-s-1,c=(1<<a)-1,l=c>>1,h=-7,u=i?n-1:0,d=i?-1:1,p=e[t+u];for(u+=d,r=p&(1<<-h)-1,p>>=-h,h+=a;h>0;r=256*r+e[t+u],u+=d,h-=8);for(o=r&(1<<-h)-1,r>>=-h,h+=s;h>0;o=256*o+e[t+u],u+=d,h-=8);if(0===r)r=1-l;else{if(r===c)return o?NaN:1/0*(p?-1:1);o+=Math.pow(2,s),r-=l}return(p?-1:1)*o*Math.pow(2,r-s)},t.write=function(e,t,i,s,n,r){var o,a,c,l=8*r-n-1,h=(1<<l)-1,u=h>>1,d=23===n?Math.pow(2,-24)-Math.pow(2,-77):0,p=s?0:r-1,f=s?1:-1,m=t<0||0===t&&1/t<0?1:0;for(t=Math.abs(t),isNaN(t)||t===1/0?(a=isNaN(t)?1:0,o=h):(o=Math.floor(Math.log(t)/Math.LN2),t*(c=Math.pow(2,-o))<1&&(o--,c*=2),(t+=o+u>=1?d/c:d*Math.pow(2,1-u))*c>=2&&(o++,c/=2),o+u>=h?(a=0,o=h):o+u>=1?(a=(t*c-1)*Math.pow(2,n),o+=u):(a=t*Math.pow(2,u-1)*Math.pow(2,n),o=0));n>=8;e[i+p]=255&a,p+=f,a/=256,n-=8);for(o=o<<n|a,l+=n;l>0;e[i+p]=255&o,p+=f,o/=256,l-=8);e[i+p-f]|=128*m}},function(e,t){},function(e,t,i){"use strict";var s=i(32).Buffer,n=i(125);e.exports=function(){function e(){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.head=null,this.tail=null,this.length=0}return e.prototype.push=function(e){var t={data:e,next:null};this.length>0?this.tail.next=t:this.head=t,this.tail=t,++this.length},e.prototype.unshift=function(e){var t={data:e,next:this.head};0===this.length&&(this.tail=t),this.head=t,++this.length},e.prototype.shift=function(){if(0!==this.length){var e=this.head.data;return 1===this.length?this.head=this.tail=null:this.head=this.head.next,--this.length,e}},e.prototype.clear=function(){this.head=this.tail=null,this.length=0},e.prototype.join=function(e){if(0===this.length)return"";for(var t=this.head,i=""+t.data;t=t.next;)i+=e+t.data;return i},e.prototype.concat=function(e){if(0===this.length)return s.alloc(0);if(1===this.length)return this.head.data;for(var t,i,n,r=s.allocUnsafe(e>>>0),o=this.head,a=0;o;)t=o.data,i=r,n=a,t.copy(i,n),a+=o.data.length,o=o.next;return r},e}(),n&&n.inspect&&n.inspect.custom&&(e.exports.prototype[n.inspect.custom]=function(){var e=n.inspect({length:this.length});return this.constructor.name+" "+e})},function(e,t){},function(e,t,i){(function(t){function i(e){try{if(!t.localStorage)return!1}catch(e){return!1}var i=t.localStorage[e];return null!=i&&"true"===String(i).toLowerCase()}e.exports=function(e,t){if(i("noDeprecation"))return e;var s=!1;return function(){if(!s){if(i("throwDeprecation"))throw new Error(t);i("traceDeprecation")?console.trace(t):console.warn(t),s=!0}return e.apply(this,arguments)}}}).call(this,i(21))},function(e,t,i){"use strict";e.exports=r;var s=i(69),n=Object.create(i(25));function r(e){if(!(this instanceof r))return new r(e);s.call(this,e)}n.inherits=i(22),n.inherits(r,s),r.prototype._transform=function(e,t,i){i(null,e)}},function(e,t,i){e.exports=i(46)},function(e,t,i){e.exports=i(20)},function(e,t,i){e.exports=i(45).Transform},function(e,t,i){e.exports=i(45).PassThrough},function(e,t,i){"use strict";const s=i(6),n=i(51);e.exports=class ReactionManager extends s{constructor(e,t){super(e.client,t,n),this.message=e}add(e,t){return super.add(e,t,{id:e.emoji.id||e.emoji.name,extras:[this.message]})}removeAll(){return this.client.api.channels(this.message.channel.id).messages(this.message.id).reactions.delete().then(()=>this.message)}}},function(e,t,i){"use strict";const s=i(2);e.exports=class LimitedCollection extends s{constructor(e=0,t=null){super(t),this.maxSize=e}set(e,t){return 0===this.maxSize?this:(this.size>=this.maxSize&&!this.has(e)&&this.delete(this.firstKey()),super.set(e,t))}static get[Symbol.species](){return s}}},function(e,t,i){"use strict";const s=i(40);e.exports=class GuildPreviewEmoji extends s{get roles(){return new Set(this._roles)}}},function(e,t,i){"use strict";const s=i(6),n=i(27);e.exports=class VoiceStateManager extends s{constructor(e,t){super(e.client,t,n),this.guild=e}add(e,t=!0){const i=this.cache.get(e.user_id);if(i)return i._patch(e);const s=new n(this.guild,e);return t&&this.cache.set(e.user_id,s),s}}},function(e,t,i){"use strict";const s=i(16),{Error:n}=i(3);e.exports=class PartialGroupDMChannel extends s{constructor(e,t){super(e,t),this.name=t.name,this.icon=t.icon}iconURL({format:e,size:t}={}){return this.icon?this.client.rest.cdn.GDMIcon(this.id,this.icon,e,t):null}delete(){return Promise.reject(new n("DELETE_GROUP_DM_CHANNEL"))}fetch(){return Promise.reject(new n("FETCH_GROUP_DM_CHANNEL"))}}},function(e,t,i){"use strict";const s=i(1),n=i(16),{ChannelTypes:r}=i(0);e.exports=class ChannelUpdateAction extends s{handle(e){let t=this.client.channels.cache.get(e.id);if(t){const i=t._update(e);if(r[t.type.toUpperCase()]!==e.type){const i=n.create(this.client,e,t.guild);for(const[e,s]of t.messages.cache)i.messages.cache.set(e,s);i._typing=new Map(t._typing),t=i,this.client.channels.cache.set(t.id,t)}return{old:i,updated:t}}return{}}}},function(e,t,i){"use strict";const s=i(1),{Events:n}=i(0);e.exports=class GuildDeleteAction extends s{constructor(e){super(e),this.deleted=new Map}handle(e){const t=this.client;let i=t.guilds.cache.get(e.id);if(i){for(const e of i.channels.cache.values())"text"===e.type&&e.stopTyping(!0);if(e.unavailable)return i.available=!1,t.emit(n.GUILD_UNAVAILABLE,i),{guild:null};for(const e of i.channels.cache.values())this.client.channels.remove(e.id);i.voice&&i.voice.connection&&i.voice.connection.disconnect(),t.guilds.cache.delete(i.id),i.deleted=!0,t.emit(n.GUILD_DELETE,i),this.deleted.set(i.id,i),this.scheduleForDeletion(i.id)}else i=this.deleted.get(e.id)||null;return{guild:i}}scheduleForDeletion(e){this.client.setTimeout(()=>this.deleted.delete(e),this.client.options.restWsBridgeTimeout)}}},function(e,t,i){"use strict";const s=i(1),{Events:n}=i(0);e.exports=class GuildUpdateAction extends s{handle(e){const t=this.client,i=t.guilds.cache.get(e.id);if(i){const s=i._update(e);return t.emit(n.GUILD_UPDATE,s,i),{old:s,updated:i}}return{old:null,updated:null}}}},function(e,t,i){"use strict";const s=i(1),n=i(18),{Events:r}=i(0);e.exports=class InviteCreateAction extends s{handle(e){const t=this.client,i=t.channels.cache.get(e.channel_id),s=t.guilds.cache.get(e.guild_id);if(!i&&!s)return!1;const o=Object.assign(e,{channel:i,guild:s}),a=new n(t,o);return t.emit(r.INVITE_CREATE,a),{invite:a}}}},function(e,t,i){"use strict";const s=i(1),n=i(18),{Events:r}=i(0);e.exports=class InviteDeleteAction extends s{handle(e){const t=this.client,i=t.channels.cache.get(e.channel_id),s=t.guilds.cache.get(e.guild_id);if(!i&&!s)return!1;const o=Object.assign(e,{channel:i,guild:s}),a=new n(t,o);return t.emit(r.INVITE_DELETE,a),{invite:a}}}},function(e,t,i){"use strict";const s=i(1),{Events:n,Status:r}=i(0);e.exports=class GuildMemberRemoveAction extends s{handle(e,t){const i=this.client,s=i.guilds.cache.get(e.guild_id);let o=null;return s&&(o=this.getMember(e,s),s.memberCount--,o&&(o.deleted=!0,s.members.cache.delete(o.id),t.status===r.READY&&i.emit(n.GUILD_MEMBER_REMOVE,o)),s.voiceStates.cache.delete(e.user.id)),{guild:s,member:o}}}},function(e,t,i){"use strict";const s=i(1),{Events:n}=i(0);e.exports=class GuildBanRemove extends s{handle(e){const t=this.client,i=t.guilds.cache.get(e.guild_id),s=t.users.add(e.user);i&&s&&t.emit(n.GUILD_BAN_REMOVE,i,s)}}},function(e,t,i){"use strict";const s=i(1),{Events:n}=i(0);e.exports=class GuildRoleCreate extends s{handle(e){const t=this.client,i=t.guilds.cache.get(e.guild_id);let s;if(i){const r=i.roles.cache.has(e.role.id);s=i.roles.add(e.role),r||t.emit(n.GUILD_ROLE_CREATE,s)}return{role:s}}}},function(e,t,i){"use strict";const s=i(1),{Events:n}=i(0);e.exports=class GuildRoleDeleteAction extends s{handle(e){const t=this.client,i=t.guilds.cache.get(e.guild_id);let s;return i&&(s=i.roles.cache.get(e.role_id),s&&(i.roles.cache.delete(e.role_id),s.deleted=!0,t.emit(n.GUILD_ROLE_DELETE,s))),{role:s}}}},function(e,t,i){"use strict";const s=i(1),{Events:n}=i(0);e.exports=class GuildRoleUpdateAction extends s{handle(e){const t=this.client,i=t.guilds.cache.get(e.guild_id);if(i){let s=null;const r=i.roles.cache.get(e.role.id);return r&&(s=r._update(e.role),t.emit(n.GUILD_ROLE_UPDATE,s,r)),{old:s,updated:r}}return{old:null,updated:null}}}},function(e,t,i){"use strict";const s=i(1),{Events:n}=i(0);e.exports=class PresenceUpdateAction extends s{handle(e){let t=this.client.users.cache.get(e.user.id);if(!t&&e.user.username&&(t=this.client.users.add(e.user)),!t)return;e.user&&e.user.username&&(t.equals(e.user)||this.client.actions.UserUpdate.handle(e.user));const i=this.client.guilds.cache.get(e.guild_id);if(!i)return;let s=i.presences.cache.get(t.id);s&&(s=s._clone());let r=i.members.cache.get(t.id);r||"offline"===e.status||(r=i.members.add({user:t,roles:e.roles,deaf:!1,mute:!1}),this.client.emit(n.GUILD_MEMBER_AVAILABLE,r)),i.presences.add(Object.assign(e,{guild:i})),r&&this.client.listenerCount(n.PRESENCE_UPDATE)&&this.client.emit(n.PRESENCE_UPDATE,s,r.presence)}}},function(e,t,i){"use strict";const s=i(1),{Events:n}=i(0);e.exports=class UserUpdateAction extends s{handle(e){const t=this.client,i=t.users.cache.get(e.id),s=i._update(e);return s.equals(i)?{old:null,updated:null}:(t.emit(n.USER_UPDATE,s,i),{old:s,updated:i})}}},function(e,t,i){"use strict";const s=i(1),n=i(27),{Events:r}=i(0);e.exports=class VoiceStateUpdate extends s{handle(e){const t=this.client,i=t.guilds.cache.get(e.guild_id);if(i){const s=i.voiceStates.cache.has(e.user_id)?i.voiceStates.cache.get(e.user_id)._clone():new n(i,{user_id:e.user_id}),o=i.voiceStates.add(e);let a=i.members.cache.get(e.user_id);a&&e.member?a._patch(e.member):e.member&&e.member.user&&e.member.joined_at&&(a=i.members.add(e.member)),a&&a.user.id===t.user.id&&(t.emit("debug",`[VOICE] received voice state update: ${JSON.stringify(e)}`),t.voice.onVoiceStateUpdate(e)),t.emit(r.VOICE_STATE_UPDATE,s,o)}}}},function(e,t,i){"use strict";const s=i(1),{Events:n}=i(0);e.exports=class GuildEmojiCreateAction extends s{handle(e,t){const i=e.emojis.add(t);return this.client.emit(n.GUILD_EMOJI_CREATE,i),{emoji:i}}}},function(e,t,i){"use strict";const s=i(1),{Events:n}=i(0);e.exports=class GuildEmojiDeleteAction extends s{handle(e){return e.guild.emojis.cache.delete(e.id),e.deleted=!0,this.client.emit(n.GUILD_EMOJI_DELETE,e),{emoji:e}}}},function(e,t,i){"use strict";const s=i(1),{Events:n}=i(0);e.exports=class GuildEmojiUpdateAction extends s{handle(e,t){const i=e._update(t);return this.client.emit(n.GUILD_EMOJI_UPDATE,i,e),{emoji:e}}}},function(e,t,i){"use strict";const s=i(1);e.exports=class GuildEmojisUpdateAction extends s{handle(e){const t=this.client.guilds.cache.get(e.guild_id);if(!t||!t.emojis)return;const i=new Map(t.emojis.cache);for(const s of e.emojis){const e=t.emojis.cache.get(s.id);e?(i.delete(s.id),e.equals(s)||this.client.actions.GuildEmojiUpdate.handle(e,s)):this.client.actions.GuildEmojiCreate.handle(t,s)}for(const e of i.values())this.client.actions.GuildEmojiDelete.handle(e)}}},function(e,t,i){"use strict";const s=i(1);e.exports=class GuildRolesPositionUpdate extends s{handle(e){const t=this.client.guilds.cache.get(e.guild_id);if(t)for(const i of e.roles){const e=t.roles.cache.get(i.id);e&&(e.rawPosition=i.position)}return{guild:t}}}},function(e,t,i){"use strict";const s=i(1);e.exports=class GuildChannelsPositionUpdate extends s{handle(e){const t=this.client.guilds.cache.get(e.guild_id);if(t)for(const i of e.channels){const e=t.channels.cache.get(i.id);e&&(e.rawPosition=i.position)}return{guild:t}}}},function(e,t,i){"use strict";const s=i(1),{Events:n}=i(0);e.exports=class GuildIntegrationsUpdate extends s{handle(e){const t=this.client,i=t.guilds.cache.get(e.guild_id);i&&t.emit(n.GUILD_INTEGRATIONS_UPDATE,i)}}},function(e,t,i){"use strict";const s=i(1),{Events:n}=i(0);e.exports=class WebhooksUpdate extends s{handle(e){const t=this.client,i=t.channels.cache.get(e.channel_id);i&&t.emit(n.WEBHOOKS_UPDATE,i)}}},function(e,t){},function(e,t,i){"use strict";const s=i(15),n=i(160),r=i(91),{Error:o}=i(3),a=i(2),{Events:c,ShardEvents:l,Status:h,WSCodes:u,WSEvents:d}=i(0),p=i(4),f=[d.READY,d.RESUMED,d.GUILD_CREATE,d.GUILD_DELETE,d.GUILD_MEMBERS_CHUNK,d.GUILD_MEMBER_ADD,d.GUILD_MEMBER_REMOVE],m=Object.keys(u).slice(1).map(Number),g=[1e3,4006,4007];e.exports=class WebSocketManager extends s{constructor(e){super(),Object.defineProperty(this,"client",{value:e}),this.gateway=void 0,this.totalShards=this.client.options.shards.length,this.shards=new a,Object.defineProperty(this,"shardQueue",{value:new Set,writable:!0}),Object.defineProperty(this,"packetQueue",{value:[]}),this.status=h.IDLE,this.destroyed=!1,this.reconnecting=!1,this.sessionStartLimit=void 0}get ping(){return this.shards.reduce((e,t)=>e+t.ping,0)/this.shards.size}debug(e,t){this.client.emit(c.DEBUG,`[WS => ${t?`Shard ${t.id}`:"Manager"}] ${e}`)}async connect(){const e=new o(u[4004]),{url:t,shards:i,session_start_limit:s}=await this.client.api.gateway.bot.get().catch(t=>{throw 401===t.httpStatus?e:t});this.sessionStartLimit=s;const{total:r,remaining:a,reset_after:c}=s;this.debug(`Fetched Gateway Information\n URL: ${t}\n Recommended Shards: ${i}`),this.debug(`Session Limit Information\n Total: ${r}\n Remaining: ${a}`),this.gateway=`${t}/`;let{shards:l}=this.client.options;return"auto"===l&&(this.debug(`Using the recommended shard count provided by Discord: ${i}`),this.totalShards=this.client.options.shardCount=i,l=this.client.options.shards=Array.from({length:i},(e,t)=>t)),this.totalShards=l.length,this.debug(`Spawning shards: ${l.join(", ")}`),this.shardQueue=new Set(l.map(e=>new n(this,e))),await this._handleSessionLimit(a,c),this.createShards()}async createShards(){if(!this.shardQueue.size)return!1;const[e]=this.shardQueue;this.shardQueue.delete(e),e.eventsAttached||(e.on(l.ALL_READY,t=>{this.client.emit(c.SHARD_READY,e.id,t),this.shardQueue.size||(this.reconnecting=!1),this.checkShardsReady()}),e.on(l.CLOSE,t=>{if(1e3===t.code?this.destroyed:m.includes(t.code))return this.client.emit(c.SHARD_DISCONNECT,t,e.id),void this.debug(u[t.code],e);g.includes(t.code)&&(e.sessionID=void 0),this.client.emit(c.SHARD_RECONNECTING,e.id),this.shardQueue.add(e),e.sessionID?(this.debug("Session ID is present, attempting an immediate reconnect...",e),this.reconnect(!0)):(e.destroy({reset:!0,emit:!1,log:!1}),this.reconnect())}),e.on(l.INVALID_SESSION,()=>{this.client.emit(c.SHARD_RECONNECTING,e.id)}),e.on(l.DESTROYED,()=>{this.debug("Shard was destroyed but no WebSocket connection was present! Reconnecting...",e),this.client.emit(c.SHARD_RECONNECTING,e.id),this.shardQueue.add(e),this.reconnect()}),e.eventsAttached=!0),this.shards.set(e.id,e);try{await e.connect()}catch(t){if(t&&t.code&&m.includes(t.code))throw new o(u[t.code]);if(t&&!t.code)throw t;this.debug("Failed to connect to the gateway, requeueing...",e),this.shardQueue.add(e)}return!this.shardQueue.size||(this.debug(`Shard Queue Size: ${this.shardQueue.size}; continuing in 5 seconds...`),await p.delayFor(5e3),await this._handleSessionLimit(),this.createShards())}async reconnect(e=!1){if(this.reconnecting||this.status!==h.READY)return!1;this.reconnecting=!0;try{e||await this._handleSessionLimit(),await this.createShards()}catch(e){if(this.debug(`Couldn't reconnect or fetch information about the gateway. ${e}`),401!==e.httpStatus)return this.debug("Possible network error occurred. Retrying in 5s..."),await p.delayFor(5e3),this.reconnecting=!1,this.reconnect();this.client.listenerCount(c.INVALIDATED)?(this.client.emit(c.INVALIDATED),this.destroy()):this.client.destroy()}finally{this.reconnecting=!1}return!0}broadcast(e){for(const t of this.shards.values())t.send(e)}destroy(){if(!this.destroyed){this.debug(`Manager was destroyed. Called by:\n${new Error("MANAGER_DESTROYED").stack}`),this.destroyed=!0,this.shardQueue.clear();for(const e of this.shards.values())e.destroy({closeCode:1e3,reset:!0,emit:!1,log:!1})}}async _handleSessionLimit(e,t){if(void 0===e&&void 0===t){const{session_start_limit:i}=await this.client.api.gateway.bot.get();this.sessionStartLimit=i,e=i.remaining,t=i.reset_after,this.debug(`Session Limit Information\n Total: ${i.total}\n Remaining: ${e}`)}e||(this.debug(`Exceeded identify threshold. Will attempt a connection in ${t}ms`),await p.delayFor(t))}handlePacket(e,t){if(e&&this.status!==h.READY&&!f.includes(e.t))return this.packetQueue.push({packet:e,shard:t}),!1;if(this.packetQueue.length){const e=this.packetQueue.shift();this.client.setImmediate(()=>{this.handlePacket(e.packet,e.shard)})}return e&&r[e.t]&&r[e.t](this.client,e,t),!0}async checkShardsReady(){if(this.status!==h.READY&&this.shards.size===this.totalShards&&!this.shards.some(e=>e.status!==h.READY)){if(this.status=h.NEARLY,this.client.options.fetchAllMembers)try{const e=this.client.guilds.cache.map(e=>e.available?e.members.fetch():Promise.resolve());await Promise.all(e)}catch(e){this.debug(`Failed to fetch all members before ready! ${e}\n${e.stack}`)}this.triggerClientReady()}}triggerClientReady(){this.status=h.READY,this.client.readyAt=new Date,this.client.emit(c.CLIENT_READY),this.handlePacket()}}},function(e,t,i){"use strict";const s=i(15),n=i(90),{browser:r,Status:o,Events:a,ShardEvents:c,OPCodes:l,WSEvents:h}=i(0),u=Object.keys(o),d=Object.keys(n.WebSocket);let p;if(!r)try{p=i(166)}catch{}e.exports=class WebSocketShard extends s{constructor(e,t){super(),this.manager=e,this.id=t,this.status=o.IDLE,this.sequence=-1,this.closeSequence=0,this.sessionID=void 0,this.ping=-1,this.lastPingTimestamp=-1,this.lastHeartbeatAcked=!0,Object.defineProperty(this,"ratelimit",{value:{queue:[],total:120,remaining:120,time:6e4,timer:null}}),Object.defineProperty(this,"connection",{value:null,writable:!0}),Object.defineProperty(this,"inflate",{value:null,writable:!0}),Object.defineProperty(this,"helloTimeout",{value:void 0,writable:!0}),Object.defineProperty(this,"eventsAttached",{value:!1,writable:!0}),Object.defineProperty(this,"expectedGuilds",{value:void 0,writable:!0}),Object.defineProperty(this,"readyTimeout",{value:void 0,writable:!0}),Object.defineProperty(this,"connectedAt",{value:0,writable:!0})}debug(e){this.manager.debug(e,this)}connect(){const{gateway:e,client:t}=this.manager;return this.connection&&this.connection.readyState===n.OPEN&&this.status===o.READY?Promise.resolve():new Promise((i,s)=>{const r=()=>{this.removeListener(c.CLOSE,h),this.removeListener(c.READY,a),this.removeListener(c.RESUMED,l),this.removeListener(c.INVALID_SESSION,u),this.removeListener(c.DESTROYED,u)},a=()=>{r(),i()},l=()=>{r(),i()},h=e=>{r(),s(e)},u=()=>{r(),s()};if(this.once(c.READY,a),this.once(c.RESUMED,l),this.once(c.CLOSE,h),this.once(c.INVALID_SESSION,u),this.once(c.DESTROYED,u),this.connection&&this.connection.readyState===n.OPEN)return this.debug("An open connection was found, attempting an immediate identify."),void this.identify();this.connection&&(this.debug(`A connection object was found. Cleaning up before continuing.\n State: ${d[this.connection.readyState]}`),this.destroy({emit:!1}));const f={v:t.options.ws.version};p&&(this.inflate=new p.Inflate({chunkSize:65535,flush:p.Z_SYNC_FLUSH,to:"json"===n.encoding?"string":""}),f.compress="zlib-stream"),this.debug(`[CONNECT]\n Gateway : ${e}\n Version : ${t.options.ws.version}\n Encoding : ${n.encoding}\n Compression: ${p?"zlib-stream":"none"}`),this.status=this.status===o.DISCONNECTED?o.RECONNECTING:o.CONNECTING,this.setHelloTimeout(),this.connectedAt=Date.now();const m=this.connection=n.create(e,f);m.onopen=this.onOpen.bind(this),m.onmessage=this.onMessage.bind(this),m.onerror=this.onError.bind(this),m.onclose=this.onClose.bind(this)})}onOpen(){this.debug(`[CONNECTED] ${this.connection.url} in ${Date.now()-this.connectedAt}ms`),this.status=o.NEARLY}onMessage({data:e}){let t,i;if(e instanceof ArrayBuffer&&(e=new Uint8Array(e)),p){const i=e.length,s=i>=4&&0===e[i-4]&&0===e[i-3]&&255===e[i-2]&&255===e[i-1];if(this.inflate.push(e,s&&p.Z_SYNC_FLUSH),!s)return;t=this.inflate.result}else t=e;try{i=n.unpack(t),this.manager.client.emit(a.RAW,i,this.id),i.op===l.DISPATCH&&this.manager.emit(i.t,i.d,this.id)}catch(e){return void this.manager.client.emit(a.SHARD_ERROR,e,this.id)}this.onPacket(i)}onError(e){const t=e&&e.error?e.error:e;t&&this.manager.client.emit(a.SHARD_ERROR,t,this.id)}onClose(e){-1!==this.sequence&&(this.closeSequence=this.sequence),this.sequence=-1,this.debug(`[CLOSE]\n Event Code: ${e.code}\n Clean : ${e.wasClean}\n Reason : ${e.reason||"No reason received"}`),this.setHeartbeatTimer(-1),this.setHelloTimeout(-1),this.connection&&this._cleanupConnection(),this.status=o.DISCONNECTED,this.emit(c.CLOSE,e)}onPacket(e){if(e){switch(e.t){case h.READY:this.emit(c.READY),this.sessionID=e.d.session_id,this.expectedGuilds=new Set(e.d.guilds.map(e=>e.id)),this.status=o.WAITING_FOR_GUILDS,this.debug(`[READY] Session ${this.sessionID}.`),this.lastHeartbeatAcked=!0,this.sendHeartbeat("ReadyHeartbeat");break;case h.RESUMED:{this.emit(c.RESUMED),this.status=o.READY;const t=e.s-this.closeSequence;this.debug(`[RESUMED] Session ${this.sessionID} | Replayed ${t} events.`),this.lastHeartbeatAcked=!0,this.sendHeartbeat("ResumeHeartbeat");break}}switch(e.s>this.sequence&&(this.sequence=e.s),e.op){case l.HELLO:this.setHelloTimeout(-1),this.setHeartbeatTimer(e.d.heartbeat_interval),this.identify();break;case l.RECONNECT:this.debug("[RECONNECT] Discord asked us to reconnect"),this.destroy({closeCode:4e3});break;case l.INVALID_SESSION:if(this.debug(`[INVALID SESSION] Resumable: ${e.d}.`),e.d)return void this.identifyResume();this.sequence=-1,this.sessionID=void 0,this.status=o.RECONNECTING,this.emit(c.INVALID_SESSION);break;case l.HEARTBEAT_ACK:this.ackHeartbeat();break;case l.HEARTBEAT:this.sendHeartbeat("HeartbeatRequest",!0);break;default:this.manager.handlePacket(e,this),this.status===o.WAITING_FOR_GUILDS&&e.t===h.GUILD_CREATE&&(this.expectedGuilds.delete(e.d.id),this.checkReady())}}else this.debug(`Received broken packet: '${e}'.`)}checkReady(){if(this.readyTimeout&&(this.manager.client.clearTimeout(this.readyTimeout),this.readyTimeout=void 0),!this.expectedGuilds.size)return this.debug("Shard received all its guilds. Marking as fully ready."),this.status=o.READY,void this.emit(c.ALL_READY);this.readyTimeout=this.manager.client.setTimeout(()=>{this.debug(`Shard did not receive any more guild packets in 15 seconds.\n Unavailable guild count: ${this.expectedGuilds.size}`),this.readyTimeout=void 0,this.status=o.READY,this.emit(c.ALL_READY,this.expectedGuilds)},15e3)}setHelloTimeout(e){-1!==e?(this.debug("Setting a HELLO timeout for 20s."),this.helloTimeout=this.manager.client.setTimeout(()=>{this.debug("Did not receive HELLO in time. Destroying and connecting again."),this.destroy({reset:!0,closeCode:4009})},2e4)):this.helloTimeout&&(this.debug("Clearing the HELLO timeout."),this.manager.client.clearTimeout(this.helloTimeout),this.helloTimeout=void 0)}setHeartbeatTimer(e){-1!==e?(this.debug(`Setting a heartbeat interval for ${e}ms.`),this.heartbeatInterval&&this.manager.client.clearInterval(this.heartbeatInterval),this.heartbeatInterval=this.manager.client.setInterval(()=>this.sendHeartbeat(),e)):this.heartbeatInterval&&(this.debug("Clearing the heartbeat interval."),this.manager.client.clearInterval(this.heartbeatInterval),this.heartbeatInterval=void 0)}sendHeartbeat(e="HeartbeatTimer",t=[o.WAITING_FOR_GUILDS,o.IDENTIFYING,o.RESUMING].includes(this.status)){if(t&&!this.lastHeartbeatAcked)this.debug(`[${e}] Didn't process heartbeat ack yet but we are still connected. Sending one now.`);else if(!this.lastHeartbeatAcked)return this.debug(`[${e}] Didn't receive a heartbeat ack last time, assuming zombie connection. Destroying and reconnecting.\n Status : ${u[this.status]}\n Sequence : ${this.sequence}\n Connection State: ${this.connection?d[this.connection.readyState]:"No Connection??"}`),void this.destroy({closeCode:4009,reset:!0});this.debug(`[${e}] Sending a heartbeat.`),this.lastHeartbeatAcked=!1,this.lastPingTimestamp=Date.now(),this.send({op:l.HEARTBEAT,d:this.sequence},!0)}ackHeartbeat(){this.lastHeartbeatAcked=!0;const e=Date.now()-this.lastPingTimestamp;this.debug(`Heartbeat acknowledged, latency of ${e}ms.`),this.ping=e}identify(){return this.sessionID?this.identifyResume():this.identifyNew()}identifyNew(){const{client:e}=this.manager;if(!e.token)return void this.debug("[IDENTIFY] No token available to identify a new session.");this.status=o.IDENTIFYING;const t={...e.options.ws,token:e.token,shard:[this.id,Number(e.options.shardCount)]};this.debug(`[IDENTIFY] Shard ${this.id}/${e.options.shardCount}`),this.send({op:l.IDENTIFY,d:t},!0)}identifyResume(){if(!this.sessionID)return this.debug("[RESUME] No session ID was present; identifying as a new session."),void this.identifyNew();this.status=o.RESUMING,this.debug(`[RESUME] Session ${this.sessionID}, sequence ${this.closeSequence}`);const e={token:this.manager.client.token,session_id:this.sessionID,seq:this.closeSequence};this.send({op:l.RESUME,d:e},!0)}send(e,t=!1){this.ratelimit.queue[t?"unshift":"push"](e),this.processQueue()}_send(e){if(!this.connection||this.connection.readyState!==n.OPEN)return this.debug(`Tried to send packet '${JSON.stringify(e)}' but no WebSocket is available!`),void this.destroy({close:4e3});this.connection.send(n.pack(e),e=>{e&&this.manager.client.emit(a.SHARD_ERROR,e,this.id)})}processQueue(){if(0!==this.ratelimit.remaining&&0!==this.ratelimit.queue.length)for(this.ratelimit.remaining===this.ratelimit.total&&(this.ratelimit.timer=this.manager.client.setTimeout(()=>{this.ratelimit.remaining=this.ratelimit.total,this.processQueue()},this.ratelimit.time));this.ratelimit.remaining>0;){const e=this.ratelimit.queue.shift();if(!e)return;this._send(e),this.ratelimit.remaining--}}destroy({closeCode:e=1e3,reset:t=!1,emit:i=!0,log:s=!0}={}){if(s&&this.debug(`[DESTROY]\n Close Code : ${e}\n Reset : ${t}\n Emit DESTROYED: ${i}`),this.setHeartbeatTimer(-1),this.setHelloTimeout(-1),this.connection)if(this.connection.readyState===n.OPEN)this.connection.close(e);else{this.debug(`WS State: ${d[this.connection.readyState]}`),this._cleanupConnection();try{this.connection.close(e)}catch{}i&&this._emitDestroyed()}else i&&this._emitDestroyed();this.connection=null,this.status=o.DISCONNECTED,-1!==this.sequence&&(this.closeSequence=this.sequence),t&&(this.sequence=-1,this.sessionID=void 0),this.ratelimit.remaining=this.ratelimit.total,this.ratelimit.queue.length=0,this.ratelimit.timer&&(this.manager.client.clearTimeout(this.ratelimit.timer),this.ratelimit.timer=null)}_cleanupConnection(){this.connection.onopen=this.connection.onclose=this.connection.onerror=this.connection.onmessage=null}_emitDestroyed(){this.emit(c.DESTROYED)}}},function(e,t){},function(e,t,i){(function(e){var s=Object.getOwnPropertyDescriptors||function(e){for(var t=Object.keys(e),i={},s=0;s<t.length;s++)i[t[s]]=Object.getOwnPropertyDescriptor(e,t[s]);return i},n=/%[sdj%]/g;t.format=function(e){if(!E(e)){for(var t=[],i=0;i<arguments.length;i++)t.push(a(arguments[i]));return t.join(" ")}i=1;for(var s=arguments,r=s.length,o=String(e).replace(n,(function(e){if("%%"===e)return"%";if(i>=r)return e;switch(e){case"%s":return String(s[i++]);case"%d":return Number(s[i++]);case"%j":try{return JSON.stringify(s[i++])}catch(e){return"[Circular]"}default:return e}})),c=s[i];i<r;c=s[++i])m(c)||!b(c)?o+=" "+c:o+=" "+a(c);return o},t.deprecate=function(i,s){if(void 0!==e&&!0===e.noDeprecation)return i;if(void 0===e)return function(){return t.deprecate(i,s).apply(this,arguments)};var n=!1;return function(){if(!n){if(e.throwDeprecation)throw new Error(s);e.traceDeprecation?console.trace(s):console.error(s),n=!0}return i.apply(this,arguments)}};var r,o={};function a(e,i){var s={seen:[],stylize:l};return arguments.length>=3&&(s.depth=arguments[2]),arguments.length>=4&&(s.colors=arguments[3]),f(i)?s.showHidden=i:i&&t._extend(s,i),_(s.showHidden)&&(s.showHidden=!1),_(s.depth)&&(s.depth=2),_(s.colors)&&(s.colors=!1),_(s.customInspect)&&(s.customInspect=!0),s.colors&&(s.stylize=c),h(s,e,s.depth)}function c(e,t){var i=a.styles[t];return i?"["+a.colors[i][0]+"m"+e+"["+a.colors[i][1]+"m":e}function l(e,t){return e}function h(e,i,s){if(e.customInspect&&i&&A(i.inspect)&&i.inspect!==t.inspect&&(!i.constructor||i.constructor.prototype!==i)){var n=i.inspect(s,e);return E(n)||(n=h(e,n,s)),n}var r=function(e,t){if(_(t))return e.stylize("undefined","undefined");if(E(t)){var i="'"+JSON.stringify(t).replace(/^"|"$/g,"").replace(/'/g,"\\'").replace(/\\"/g,'"')+"'";return e.stylize(i,"string")}if(g(t))return e.stylize(""+t,"number");if(f(t))return e.stylize(""+t,"boolean");if(m(t))return e.stylize("null","null")}(e,i);if(r)return r;var o=Object.keys(i),a=function(e){var t={};return e.forEach((function(e,i){t[e]=!0})),t}(o);if(e.showHidden&&(o=Object.getOwnPropertyNames(i)),w(i)&&(o.indexOf("message")>=0||o.indexOf("description")>=0))return u(i);if(0===o.length){if(A(i)){var c=i.name?": "+i.name:"";return e.stylize("[Function"+c+"]","special")}if(y(i))return e.stylize(RegExp.prototype.toString.call(i),"regexp");if(v(i))return e.stylize(Date.prototype.toString.call(i),"date");if(w(i))return u(i)}var l,b="",S=!1,I=["{","}"];(p(i)&&(S=!0,I=["[","]"]),A(i))&&(b=" [Function"+(i.name?": "+i.name:"")+"]");return y(i)&&(b=" "+RegExp.prototype.toString.call(i)),v(i)&&(b=" "+Date.prototype.toUTCString.call(i)),w(i)&&(b=" "+u(i)),0!==o.length||S&&0!=i.length?s<0?y(i)?e.stylize(RegExp.prototype.toString.call(i),"regexp"):e.stylize("[Object]","special"):(e.seen.push(i),l=S?function(e,t,i,s,n){for(var r=[],o=0,a=t.length;o<a;++o)N(t,String(o))?r.push(d(e,t,i,s,String(o),!0)):r.push("");return n.forEach((function(n){n.match(/^\d+$/)||r.push(d(e,t,i,s,n,!0))})),r}(e,i,s,a,o):o.map((function(t){return d(e,i,s,a,t,S)})),e.seen.pop(),function(e,t,i){if(e.reduce((function(e,t){return t.indexOf("\n")>=0&&0,e+t.replace(/\u001b\[\d\d?m/g,"").length+1}),0)>60)return i[0]+(""===t?"":t+"\n ")+" "+e.join(",\n ")+" "+i[1];return i[0]+t+" "+e.join(", ")+" "+i[1]}(l,b,I)):I[0]+b+I[1]}function u(e){return"["+Error.prototype.toString.call(e)+"]"}function d(e,t,i,s,n,r){var o,a,c;if((c=Object.getOwnPropertyDescriptor(t,n)||{value:t[n]}).get?a=c.set?e.stylize("[Getter/Setter]","special"):e.stylize("[Getter]","special"):c.set&&(a=e.stylize("[Setter]","special")),N(s,n)||(o="["+n+"]"),a||(e.seen.indexOf(c.value)<0?(a=m(i)?h(e,c.value,null):h(e,c.value,i-1)).indexOf("\n")>-1&&(a=r?a.split("\n").map((function(e){return" "+e})).join("\n").substr(2):"\n"+a.split("\n").map((function(e){return" "+e})).join("\n")):a=e.stylize("[Circular]","special")),_(o)){if(r&&n.match(/^\d+$/))return a;(o=JSON.stringify(""+n)).match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)?(o=o.substr(1,o.length-2),o=e.stylize(o,"name")):(o=o.replace(/'/g,"\\'").replace(/\\"/g,'"').replace(/(^"|"$)/g,"'"),o=e.stylize(o,"string"))}return o+": "+a}function p(e){return Array.isArray(e)}function f(e){return"boolean"==typeof e}function m(e){return null===e}function g(e){return"number"==typeof e}function E(e){return"string"==typeof e}function _(e){return void 0===e}function y(e){return b(e)&&"[object RegExp]"===S(e)}function b(e){return"object"==typeof e&&null!==e}function v(e){return b(e)&&"[object Date]"===S(e)}function w(e){return b(e)&&("[object Error]"===S(e)||e instanceof Error)}function A(e){return"function"==typeof e}function S(e){return Object.prototype.toString.call(e)}function I(e){return e<10?"0"+e.toString(10):e.toString(10)}t.debuglog=function(i){if(_(r)&&(r=e.env.NODE_DEBUG||""),i=i.toUpperCase(),!o[i])if(new RegExp("\\b"+i+"\\b","i").test(r)){var s=e.pid;o[i]=function(){var e=t.format.apply(t,arguments);console.error("%s %d: %s",i,s,e)}}else o[i]=function(){};return o[i]},t.inspect=a,a.colors={bold:[1,22],italic:[3,23],underline:[4,24],inverse:[7,27],white:[37,39],grey:[90,39],black:[30,39],blue:[34,39],cyan:[36,39],green:[32,39],magenta:[35,39],red:[31,39],yellow:[33,39]},a.styles={special:"cyan",number:"yellow",boolean:"yellow",undefined:"grey",null:"bold",string:"green",date:"magenta",regexp:"red"},t.isArray=p,t.isBoolean=f,t.isNull=m,t.isNullOrUndefined=function(e){return null==e},t.isNumber=g,t.isString=E,t.isSymbol=function(e){return"symbol"==typeof e},t.isUndefined=_,t.isRegExp=y,t.isObject=b,t.isDate=v,t.isError=w,t.isFunction=A,t.isPrimitive=function(e){return null===e||"boolean"==typeof e||"number"==typeof e||"string"==typeof e||"symbol"==typeof e||void 0===e},t.isBuffer=i(163);var T=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];function D(){var e=new Date,t=[I(e.getHours()),I(e.getMinutes()),I(e.getSeconds())].join(":");return[e.getDate(),T[e.getMonth()],t].join(" ")}function N(e,t){return Object.prototype.hasOwnProperty.call(e,t)}t.log=function(){console.log("%s - %s",D(),t.format.apply(t,arguments))},t.inherits=i(164),t._extend=function(e,t){if(!t||!b(t))return e;for(var i=Object.keys(t),s=i.length;s--;)e[i[s]]=t[i[s]];return e};var R="undefined"!=typeof Symbol?Symbol("util.promisify.custom"):void 0;function O(e,t){if(!e){var i=new Error("Promise was rejected with a falsy value");i.reason=e,e=i}return t(e)}t.promisify=function(e){if("function"!=typeof e)throw new TypeError('The "original" argument must be of type Function');if(R&&e[R]){var t;if("function"!=typeof(t=e[R]))throw new TypeError('The "util.promisify.custom" argument must be of type Function');return Object.defineProperty(t,R,{value:t,enumerable:!1,writable:!1,configurable:!0}),t}function t(){for(var t,i,s=new Promise((function(e,s){t=e,i=s})),n=[],r=0;r<arguments.length;r++)n.push(arguments[r]);n.push((function(e,s){e?i(e):t(s)}));try{e.apply(this,n)}catch(e){i(e)}return s}return Object.setPrototypeOf(t,Object.getPrototypeOf(e)),R&&Object.defineProperty(t,R,{value:t,enumerable:!1,writable:!1,configurable:!0}),Object.defineProperties(t,s(e))},t.promisify.custom=R,t.callbackify=function(t){if("function"!=typeof t)throw new TypeError('The "original" argument must be of type Function');function i(){for(var i=[],s=0;s<arguments.length;s++)i.push(arguments[s]);var n=i.pop();if("function"!=typeof n)throw new TypeError("The last argument must be of type Function");var r=this,o=function(){return n.apply(r,arguments)};t.apply(this,i).then((function(t){e.nextTick(o,null,t)}),(function(t){e.nextTick(O,t,o)}))}return Object.setPrototypeOf(i,Object.getPrototypeOf(t)),Object.defineProperties(i,s(t)),i}}).call(this,i(14))},function(e,t){e.exports=function(e){return e&&"object"==typeof e&&"function"==typeof e.copy&&"function"==typeof e.fill&&"function"==typeof e.readUInt8}},function(e,t){"function"==typeof Object.create?e.exports=function(e,t){e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}})}:e.exports=function(e,t){e.super_=t;var i=function(){};i.prototype=t.prototype,e.prototype=new i,e.prototype.constructor=e}},function(e,t){},function(e,t){},function(e,t,i){var s={"./CHANNEL_CREATE.js":168,"./CHANNEL_DELETE.js":169,"./CHANNEL_PINS_UPDATE.js":170,"./CHANNEL_UPDATE.js":171,"./GUILD_BAN_ADD.js":172,"./GUILD_BAN_REMOVE.js":173,"./GUILD_CREATE.js":174,"./GUILD_DELETE.js":175,"./GUILD_EMOJIS_UPDATE.js":176,"./GUILD_INTEGRATIONS_UPDATE.js":177,"./GUILD_MEMBERS_CHUNK.js":178,"./GUILD_MEMBER_ADD.js":179,"./GUILD_MEMBER_REMOVE.js":180,"./GUILD_MEMBER_UPDATE.js":181,"./GUILD_ROLE_CREATE.js":182,"./GUILD_ROLE_DELETE.js":183,"./GUILD_ROLE_UPDATE.js":184,"./GUILD_UPDATE.js":185,"./INVITE_CREATE.js":186,"./INVITE_DELETE.js":187,"./MESSAGE_CREATE.js":188,"./MESSAGE_DELETE.js":189,"./MESSAGE_DELETE_BULK.js":190,"./MESSAGE_REACTION_ADD.js":191,"./MESSAGE_REACTION_REMOVE.js":192,"./MESSAGE_REACTION_REMOVE_ALL.js":193,"./MESSAGE_REACTION_REMOVE_EMOJI.js":194,"./MESSAGE_UPDATE.js":195,"./PRESENCE_UPDATE.js":196,"./READY.js":197,"./RESUMED.js":198,"./TYPING_START.js":199,"./USER_UPDATE.js":200,"./VOICE_SERVER_UPDATE.js":201,"./VOICE_STATE_UPDATE.js":202,"./WEBHOOKS_UPDATE.js":203,"./index.js":91};function n(e){var t=r(e);return i(t)}function r(e){if(!i.o(s,e)){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}return s[e]}n.keys=function(){return Object.keys(s)},n.resolve=r,e.exports=n,n.id=167},function(e,t,i){"use strict";e.exports=(e,t)=>{e.actions.ChannelCreate.handle(t.d)}},function(e,t,i){"use strict";e.exports=(e,t)=>{e.actions.ChannelDelete.handle(t.d)}},function(e,t,i){"use strict";const{Events:s}=i(0);e.exports=(e,{d:t})=>{const i=e.channels.cache.get(t.channel_id),n=new Date(t.last_pin_timestamp);i&&!Number.isNaN(n.getTime())&&(i.lastPinTimestamp=n.getTime()||null,e.emit(s.CHANNEL_PINS_UPDATE,i,n))}},function(e,t,i){"use strict";const{Events:s}=i(0);e.exports=(e,t)=>{const{old:i,updated:n}=e.actions.ChannelUpdate.handle(t.d);i&&n&&e.emit(s.CHANNEL_UPDATE,i,n)}},function(e,t,i){"use strict";const{Events:s}=i(0);e.exports=(e,{d:t})=>{const i=e.guilds.cache.get(t.guild_id),n=e.users.add(t.user);i&&n&&e.emit(s.GUILD_BAN_ADD,i,n)}},function(e,t,i){"use strict";e.exports=(e,t)=>{e.actions.GuildBanRemove.handle(t.d)}},function(e,t,i){"use strict";const{Events:s,Status:n}=i(0);e.exports=async(e,{d:t},i)=>{let r=e.guilds.cache.get(t.id);r?r.available||t.unavailable||(r._patch(t),e.ws.status===n.READY&&e.options.fetchAllMembers&&await r.members.fetch().catch(t=>e.emit(s.DEBUG,`Failed to fetch all members: ${t}\n${t.stack}`))):(t.shardID=i.id,r=e.guilds.add(t),e.ws.status===n.READY&&(e.options.fetchAllMembers&&await r.members.fetch().catch(t=>e.emit(s.DEBUG,`Failed to fetch all members: ${t}\n${t.stack}`)),e.emit(s.GUILD_CREATE,r)))}},function(e,t,i){"use strict";e.exports=(e,t)=>{e.actions.GuildDelete.handle(t.d)}},function(e,t,i){"use strict";e.exports=(e,t)=>{e.actions.GuildEmojisUpdate.handle(t.d)}},function(e,t,i){"use strict";e.exports=(e,t)=>{e.actions.GuildIntegrationsUpdate.handle(t.d)}},function(e,t,i){"use strict";const s=i(2),{Events:n}=i(0);e.exports=(e,{d:t})=>{const i=e.guilds.cache.get(t.guild_id);if(!i)return;const r=new s;for(const e of t.members)r.set(e.user.id,i.members.add(e));if(t.presences)for(const e of t.presences)i.presences.cache.add(Object.assign(e,{guild:i}));e.emit(n.GUILD_MEMBERS_CHUNK,r,i)}},function(e,t,i){"use strict";const{Events:s,Status:n}=i(0);e.exports=(e,{d:t},i)=>{const r=e.guilds.cache.get(t.guild_id);if(r){r.memberCount++;const o=r.members.add(t);i.status===n.READY&&e.emit(s.GUILD_MEMBER_ADD,o)}}},function(e,t,i){"use strict";e.exports=(e,t,i)=>{e.actions.GuildMemberRemove.handle(t.d,i)}},function(e,t,i){"use strict";const{Status:s,Events:n}=i(0);e.exports=(e,{d:t},i)=>{const r=e.guilds.cache.get(t.guild_id);if(r){const o=r.members.cache.get(t.user.id);if(o){const r=o._update(t);i.status===s.READY&&e.emit(n.GUILD_MEMBER_UPDATE,r,o)}}}},function(e,t,i){"use strict";e.exports=(e,t)=>{e.actions.GuildRoleCreate.handle(t.d)}},function(e,t,i){"use strict";e.exports=(e,t)=>{e.actions.GuildRoleDelete.handle(t.d)}},function(e,t,i){"use strict";e.exports=(e,t)=>{e.actions.GuildRoleUpdate.handle(t.d)}},function(e,t,i){"use strict";e.exports=(e,t)=>{e.actions.GuildUpdate.handle(t.d)}},function(e,t,i){"use strict";e.exports=(e,t)=>{e.actions.InviteCreate.handle(t.d)}},function(e,t,i){"use strict";e.exports=(e,t)=>{e.actions.InviteDelete.handle(t.d)}},function(e,t,i){"use strict";e.exports=(e,t)=>{e.actions.MessageCreate.handle(t.d)}},function(e,t,i){"use strict";e.exports=(e,t)=>{e.actions.MessageDelete.handle(t.d)}},function(e,t,i){"use strict";e.exports=(e,t)=>{e.actions.MessageDeleteBulk.handle(t.d)}},function(e,t,i){"use strict";e.exports=(e,t)=>{e.actions.MessageReactionAdd.handle(t.d)}},function(e,t,i){"use strict";e.exports=(e,t)=>{e.actions.MessageReactionRemove.handle(t.d)}},function(e,t,i){"use strict";e.exports=(e,t)=>{e.actions.MessageReactionRemoveAll.handle(t.d)}},function(e,t,i){"use strict";e.exports=(e,t)=>{e.actions.MessageReactionRemoveEmoji.handle(t.d)}},function(e,t,i){"use strict";const{Events:s}=i(0);e.exports=(e,t)=>{const{old:i,updated:n}=e.actions.MessageUpdate.handle(t.d);i&&n&&e.emit(s.MESSAGE_UPDATE,i,n)}},function(e,t,i){"use strict";e.exports=(e,t)=>{e.actions.PresenceUpdate.handle(t.d)}},function(e,t,i){"use strict";let s;e.exports=(e,{d:t},n)=>{if(e.user)e.user._patch(t.user);else{s||(s=i(92));const n=new s(e,t.user);e.user=n,e.users.cache.set(n.id,n)}for(const i of t.guilds)i.shardID=n.id,e.guilds.add(i);n.checkReady()}},function(e,t,i){"use strict";const{Events:s}=i(0);e.exports=(e,t,i)=>{const n=i.sequence-i.closeSequence;e.emit(s.SHARD_RESUME,i.id,n)}},function(e,t,i){"use strict";const{Events:s}=i(0);function n(e,t){return e.client.setTimeout(()=>{e._typing.delete(t.id)},1e4)}e.exports=(e,{d:t})=>{const i=e.channels.cache.get(t.channel_id),r=e.users.cache.get(t.user_id),o=new Date(1e3*t.timestamp);if(i&&r){if("voice"===i.type)return void e.emit(s.WARN,`Discord sent a typing packet to a voice channel ${i.id}`);if(i._typing.has(r.id)){const t=i._typing.get(r.id);t.lastTimestamp=o,t.elapsedTime=Date.now()-t.since,e.clearTimeout(t.timeout),t.timeout=n(i,r)}else{const t=new Date,o=new Date;i._typing.set(r.id,{user:r,since:t,lastTimestamp:o,elapsedTime:Date.now()-t,timeout:n(i,r)}),e.emit(s.TYPING_START,i,r)}}}},function(e,t,i){"use strict";e.exports=(e,t)=>{e.actions.UserUpdate.handle(t.d)}},function(e,t,i){"use strict";e.exports=(e,t)=>{e.emit("debug",`[VOICE] received voice server: ${JSON.stringify(t)}`),e.voice.onVoiceServer(t.d)}},function(e,t,i){"use strict";e.exports=(e,t)=>{e.actions.VoiceStateUpdate.handle(t.d)}},function(e,t,i){"use strict";e.exports=(e,t)=>{e.actions.WebhooksUpdate.handle(t.d)}},function(e,t){},function(e,t){},function(e,t){},function(e,t){},function(e,t){},function(e,t,i){"use strict";const s=i(11);class Speaking extends s{}Speaking.FLAGS={SPEAKING:1,SOUNDSHARE:2,PRIORITY_SPEAKING:4},e.exports=Speaking}])}));
\ No newline at end of file |