mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-16 19:43:29 +01:00
Merge branch 'master' into refactor/webpacks
This commit is contained in:
@@ -127,7 +127,11 @@
|
|||||||
"semi-spacing": "error",
|
"semi-spacing": "error",
|
||||||
"semi": "error",
|
"semi": "error",
|
||||||
"space-before-blocks": "error",
|
"space-before-blocks": "error",
|
||||||
"space-before-function-paren": ["error", "never"],
|
"space-before-function-paren": ["error", {
|
||||||
|
"anonymous": "never",
|
||||||
|
"named": "never",
|
||||||
|
"asyncArrow": "always"
|
||||||
|
}],
|
||||||
"space-in-parens": "error",
|
"space-in-parens": "error",
|
||||||
"space-infix-ops": "error",
|
"space-infix-ops": "error",
|
||||||
"space-unary-ops": "error",
|
"space-unary-ops": "error",
|
||||||
|
|||||||
24
.travis.yml
24
.travis.yml
@@ -1,19 +1,19 @@
|
|||||||
language: node_js
|
language: node_js
|
||||||
node_js:
|
node_js:
|
||||||
- "8"
|
- 8
|
||||||
|
- 9
|
||||||
|
install: npm install
|
||||||
|
script: bash ./travis/test.sh
|
||||||
|
jobs:
|
||||||
|
include:
|
||||||
|
- stage: deploy
|
||||||
|
node_js: 9
|
||||||
|
script: bash ./travis/deploy.sh
|
||||||
|
env:
|
||||||
|
- ENCRYPTION_LABEL="af862fa96d3e"
|
||||||
|
- COMMIT_AUTHOR_EMAIL="amishshah.2k@gmail.com"
|
||||||
cache:
|
cache:
|
||||||
directories:
|
directories:
|
||||||
- node_modules
|
- node_modules
|
||||||
install: npm install
|
|
||||||
jobs:
|
|
||||||
include:
|
|
||||||
- stage: test
|
|
||||||
script: bash ./travis/test.sh
|
|
||||||
- stage: deploy
|
|
||||||
script: bash ./travis/deploy.sh
|
|
||||||
env:
|
|
||||||
global:
|
|
||||||
- ENCRYPTION_LABEL: "af862fa96d3e"
|
|
||||||
- COMMIT_AUTHOR_EMAIL: "amishshah.2k@gmail.com"
|
|
||||||
dist: trusty
|
dist: trusty
|
||||||
sudo: false
|
sudo: false
|
||||||
|
|||||||
30
README.md
30
README.md
@@ -10,8 +10,7 @@
|
|||||||
<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://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://travis-ci.org/hydrabolt/discord.js"><img src="https://travis-ci.org/hydrabolt/discord.js.svg" alt="Build status" /></a>
|
<a href="https://travis-ci.org/hydrabolt/discord.js"><img src="https://travis-ci.org/hydrabolt/discord.js.svg" alt="Build status" /></a>
|
||||||
<a href="https://david-dm.org/hydrabolt/discord.js"><img src="https://img.shields.io/david/hydrabolt/discord.js.svg?maxAge=3600" alt="Dependencies" /></a>
|
<a href="https://david-dm.org/hydrabolt/discord.js"><img src="https://img.shields.io/david/hydrabolt/discord.js.svg?maxAge=3600" alt="Dependencies" /></a>
|
||||||
<a href="https://www.patreon.com/discordjs"><img
|
<a href="https://www.patreon.com/discordjs"><img src="https://img.shields.io/badge/donate-patreon-F96854.svg" alt="Patreon" /></a>
|
||||||
src="https://img.shields.io/badge/donate-patreon-F96854.svg" alt="Patreon" /></a>
|
|
||||||
</p>
|
</p>
|
||||||
<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 info" /></a>
|
<a href="https://nodei.co/npm/discord.js/"><img src="https://nodei.co/npm/discord.js.png?downloads=true&stars=true" alt="NPM info" /></a>
|
||||||
@@ -31,9 +30,9 @@ discord.js is a powerful [node.js](https://nodejs.org) module that allows you to
|
|||||||
**Node.js 8.0.0 or newer is required.**
|
**Node.js 8.0.0 or newer is required.**
|
||||||
Ignore any warnings about unmet peer dependencies, as they're all optional.
|
Ignore any warnings about unmet peer dependencies, as they're all optional.
|
||||||
|
|
||||||
Without voice support: `npm install discord.js --save`
|
Without voice support: `npm i discord.js`
|
||||||
With voice support ([node-opus](https://www.npmjs.com/package/node-opus)): `npm install discord.js node-opus --save`
|
With voice support ([node-opus](https://www.npmjs.com/package/node-opus)): `npm i discord.js node-opus`
|
||||||
With voice support ([opusscript](https://www.npmjs.com/package/opusscript)): `npm install discord.js opusscript --save`
|
With voice support ([opusscript](https://www.npmjs.com/package/opusscript)): `npm i discord.js opusscript`
|
||||||
|
|
||||||
### Audio engines
|
### Audio engines
|
||||||
The preferred audio engine is node-opus, as it performs significantly better than opusscript. When both are available, discord.js will automatically choose node-opus.
|
The preferred audio engine is node-opus, as it performs significantly better than opusscript. When both are available, discord.js will automatically choose node-opus.
|
||||||
@@ -41,13 +40,13 @@ Using opusscript is only recommended for development environments where node-opu
|
|||||||
For production bots, using node-opus should be considered a necessity, especially if they're going to be running on multiple servers.
|
For production bots, using node-opus should be considered a necessity, especially if they're going to be running on multiple servers.
|
||||||
|
|
||||||
### Optional packages
|
### Optional packages
|
||||||
- [zlib-sync](https://www.npmjs.com/package/zlib-sync) for significantly faster WebSocket data inflation (`npm install zlib-sync`)
|
- [zlib-sync](https://www.npmjs.com/package/zlib-sync) for significantly faster WebSocket data inflation (`npm i zlib-sync`)
|
||||||
- [bufferutil](https://www.npmjs.com/package/bufferutil) to greatly speed up the WebSocket when *not* using uws (`npm install bufferutil --save`)
|
- [erlpack](https://github.com/discordapp/erlpack) for significantly faster WebSocket data (de)serialisation (`npm i discordapp/erlpack`)
|
||||||
- [erlpack](https://github.com/hammerandchisel/erlpack) for significantly faster WebSocket data (de)serialisation (`npm install discordapp/erlpack --save`)
|
|
||||||
- One of the following packages can be installed for faster voice packet encryption and decryption:
|
- 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 --save`)
|
- [sodium](https://www.npmjs.com/package/sodium) (`npm i sodium`)
|
||||||
- [libsodium.js](https://www.npmjs.com/package/libsodium-wrappers) (`npm install libsodium-wrappers --save`)
|
- [libsodium.js](https://www.npmjs.com/package/libsodium-wrappers) (`npm i libsodium-wrappers`)
|
||||||
- [uws](https://www.npmjs.com/package/uws) for a much faster WebSocket connection (`npm install uws --save`)
|
- [uws](https://www.npmjs.com/package/uws) for a much faster WebSocket connection (`npm i uws`)
|
||||||
|
- [bufferutil](https://www.npmjs.com/package/bufferutil) for a much faster WebSocket connection when *not* using uws (`npm i bufferutil`)
|
||||||
|
|
||||||
## Example usage
|
## Example usage
|
||||||
```js
|
```js
|
||||||
@@ -70,11 +69,14 @@ client.login('your token');
|
|||||||
## Links
|
## Links
|
||||||
* [Website](https://discord.js.org/) ([source](https://github.com/hydrabolt/discord.js-site))
|
* [Website](https://discord.js.org/) ([source](https://github.com/hydrabolt/discord.js-site))
|
||||||
* [Documentation](https://discord.js.org/#/docs)
|
* [Documentation](https://discord.js.org/#/docs)
|
||||||
* [Discord.js server](https://discord.gg/bRCvFy9)
|
* [Discord.js Discord server](https://discord.gg/bRCvFy9)
|
||||||
* [Discord API server](https://discord.gg/rV4BwdK)
|
* [Discord API Discord server](https://discord.gg/discord-api)
|
||||||
* [GitHub](https://github.com/hydrabolt/discord.js)
|
* [GitHub](https://github.com/hydrabolt/discord.js)
|
||||||
* [NPM](https://www.npmjs.com/package/discord.js)
|
* [NPM](https://www.npmjs.com/package/discord.js)
|
||||||
* [Related libraries](https://discordapi.com/unofficial/libs.html) (see also [discord-rpc](https://www.npmjs.com/package/discord-rpc))
|
* [Related libraries](https://discordapi.com/unofficial/libs.html)
|
||||||
|
|
||||||
|
### Extensions
|
||||||
|
* [discord-rpc](https://www.npmjs.com/package/discord-rpc) ([github](https://github.com/devsnek/discord-rpc))
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
Before creating an issue, please ensure that it hasn't already been reported/suggested, and double-check the
|
Before creating an issue, please ensure that it hasn't already been reported/suggested, and double-check the
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
<br />
|
<br />
|
||||||
<p>
|
<p>
|
||||||
<a href="https://discord.js.org"><img src="https://discord.js.org/static/logo.svg" width="546" alt="discord.js" /></a>
|
<a href="https://discord.js.org"><img src="https://discord.js.org/static/logo.svg" width="546" alt="discord.js" id="djs-logo" /></a>
|
||||||
</p>
|
</p>
|
||||||
<br />
|
<br />
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@@ -34,12 +34,11 @@
|
|||||||
"runkitExampleFilename": "./docs/examples/ping.js",
|
"runkitExampleFilename": "./docs/examples/ping.js",
|
||||||
"unpkg": "./webpack/discord.min.js",
|
"unpkg": "./webpack/discord.min.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"long": "^3.0.0",
|
|
||||||
"pako": "^1.0.0",
|
"pako": "^1.0.0",
|
||||||
"prism-media": "^0.0.2",
|
"prism-media": "^0.0.2",
|
||||||
"snekfetch": "^3.0.0",
|
"snekfetch": "^3.5.0",
|
||||||
"tweetnacl": "^1.0.0",
|
"tweetnacl": "^1.0.0",
|
||||||
"ws": "^3.0.0"
|
"ws": "^3.3.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"bufferutil": "^3.0.0",
|
"bufferutil": "^3.0.0",
|
||||||
@@ -54,11 +53,11 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^8.0.0",
|
"@types/node": "^8.0.0",
|
||||||
"discord.js-docgen": "hydrabolt/discord.js-docgen",
|
"discord.js-docgen": "hydrabolt/discord.js-docgen",
|
||||||
"eslint": "^4.0.0",
|
"eslint": "^4.11.0",
|
||||||
"jsdoc-strip-async-await": "^0.1.0",
|
"jsdoc-strip-async-await": "^0.1.0",
|
||||||
"json-filter-loader": "^1.0.0",
|
"json-filter-loader": "^1.0.0",
|
||||||
"uglifyjs-webpack-plugin": "^1.0.0-beta.2",
|
"uglifyjs-webpack-plugin": "^1.0.0-beta.2",
|
||||||
"webpack": "^3.0.0"
|
"webpack": "^3.8.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8.0.0"
|
"node": ">=8.0.0"
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ class BaseClient extends EventEmitter {
|
|||||||
/**
|
/**
|
||||||
* API shortcut
|
* API shortcut
|
||||||
* @type {Object}
|
* @type {Object}
|
||||||
|
* @readonly
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
get api() {
|
get api() {
|
||||||
|
|||||||
@@ -163,6 +163,7 @@ class Client extends BaseClient {
|
|||||||
/**
|
/**
|
||||||
* Timestamp of the latest ping's start time
|
* Timestamp of the latest ping's start time
|
||||||
* @type {number}
|
* @type {number}
|
||||||
|
* @readonly
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
get _pingTimestamp() {
|
get _pingTimestamp() {
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ class ClientManager {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The status of the client
|
* The status of the client
|
||||||
|
* @readonly
|
||||||
* @type {number}
|
* @type {number}
|
||||||
*/
|
*/
|
||||||
get status() {
|
get status() {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ const BaseClient = require('./BaseClient');
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The webhook client.
|
* The webhook client.
|
||||||
* @extends {Webhook}
|
* @implements {Webhook}
|
||||||
* @extends {BaseClient}
|
* @extends {BaseClient}
|
||||||
*/
|
*/
|
||||||
class WebhookClient extends BaseClient {
|
class WebhookClient extends BaseClient {
|
||||||
|
|||||||
@@ -14,13 +14,11 @@ class ActionsManager {
|
|||||||
this.register(require('./ChannelUpdate'));
|
this.register(require('./ChannelUpdate'));
|
||||||
this.register(require('./GuildDelete'));
|
this.register(require('./GuildDelete'));
|
||||||
this.register(require('./GuildUpdate'));
|
this.register(require('./GuildUpdate'));
|
||||||
this.register(require('./GuildMemberGet'));
|
|
||||||
this.register(require('./GuildMemberRemove'));
|
this.register(require('./GuildMemberRemove'));
|
||||||
this.register(require('./GuildBanRemove'));
|
this.register(require('./GuildBanRemove'));
|
||||||
this.register(require('./GuildRoleCreate'));
|
this.register(require('./GuildRoleCreate'));
|
||||||
this.register(require('./GuildRoleDelete'));
|
this.register(require('./GuildRoleDelete'));
|
||||||
this.register(require('./GuildRoleUpdate'));
|
this.register(require('./GuildRoleUpdate'));
|
||||||
this.register(require('./UserGet'));
|
|
||||||
this.register(require('./UserUpdate'));
|
this.register(require('./UserUpdate'));
|
||||||
this.register(require('./UserNoteUpdate'));
|
this.register(require('./UserNoteUpdate'));
|
||||||
this.register(require('./GuildSync'));
|
this.register(require('./GuildSync'));
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
const Action = require('./Action');
|
|
||||||
|
|
||||||
class GuildMemberGetAction extends Action {
|
|
||||||
handle(guild, data) {
|
|
||||||
const member = guild.members.create(data);
|
|
||||||
return { member };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = GuildMemberGetAction;
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
const Action = require('./Action');
|
|
||||||
|
|
||||||
class UserGetAction extends Action {
|
|
||||||
handle(data) {
|
|
||||||
const client = this.client;
|
|
||||||
const user = client.users.create(data);
|
|
||||||
return { user };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = UserGetAction;
|
|
||||||
@@ -236,7 +236,7 @@ class VoiceBroadcast extends VolumeInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plays an arbitrary input that can be [handled by ffmpeg](https://ffmpeg.org/ffmpeg-protocols.html#Description)
|
* Plays an arbitrary input that can be [handled by ffmpeg](https://ffmpeg.org/ffmpeg-protocols.html#Description).
|
||||||
* @param {string} input The arbitrary input
|
* @param {string} input The arbitrary input
|
||||||
* @param {StreamOptions} [options] Options for playing the stream
|
* @param {StreamOptions} [options] Options for playing the stream
|
||||||
* @returns {VoiceBroadcast}
|
* @returns {VoiceBroadcast}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const libs = {
|
|||||||
|
|
||||||
exports.methods = {};
|
exports.methods = {};
|
||||||
|
|
||||||
(async() => {
|
(async () => {
|
||||||
for (const libName of Object.keys(libs)) {
|
for (const libName of Object.keys(libs)) {
|
||||||
try {
|
try {
|
||||||
const lib = require(libName);
|
const lib = require(libName);
|
||||||
|
|||||||
@@ -272,7 +272,7 @@ class WebSocketConnection extends EventEmitter {
|
|||||||
try {
|
try {
|
||||||
const packet = WebSocket.unpack(this.inflate.result);
|
const packet = WebSocket.unpack(this.inflate.result);
|
||||||
this.onPacket(packet);
|
this.onPacket(packet);
|
||||||
if (this.client.listenerCount('raw')) this.client.emit('raw', data);
|
if (this.client.listenerCount('raw')) this.client.emit('raw', packet);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.client.emit('debug', err);
|
this.client.emit('debug', err);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
const AbstractHandler = require('./AbstractHandler');
|
const AbstractHandler = require('./AbstractHandler');
|
||||||
const { Events } = require('../../../../util/Constants');
|
const { Events } = require('../../../../util/Constants');
|
||||||
const ClientUser = require('../../../../structures/ClientUser');
|
let ClientUser;
|
||||||
|
|
||||||
class ReadyHandler extends AbstractHandler {
|
class ReadyHandler extends AbstractHandler {
|
||||||
handle(packet) {
|
handle(packet) {
|
||||||
@@ -12,6 +12,7 @@ class ReadyHandler extends AbstractHandler {
|
|||||||
data.user.user_settings = data.user_settings;
|
data.user.user_settings = data.user_settings;
|
||||||
data.user.user_guild_settings = data.user_guild_settings;
|
data.user.user_guild_settings = data.user_guild_settings;
|
||||||
|
|
||||||
|
if (!ClientUser) ClientUser = require('../../../../structures/ClientUser');
|
||||||
const clientUser = new ClientUser(client, data.user);
|
const clientUser = new ClientUser(client, data.user);
|
||||||
client.user = clientUser;
|
client.user = clientUser;
|
||||||
client.readyAt = new Date();
|
client.readyAt = new Date();
|
||||||
|
|||||||
@@ -20,9 +20,13 @@ const Messages = {
|
|||||||
SHARDING_REQUIRED: 'This session would have handled too many guilds - Sharding is required.',
|
SHARDING_REQUIRED: 'This session would have handled too many guilds - Sharding is required.',
|
||||||
SHARDING_CHILD_CONNECTION: 'Failed to send message to shard\'s process.',
|
SHARDING_CHILD_CONNECTION: 'Failed to send message to shard\'s process.',
|
||||||
SHARDING_PARENT_CONNECTION: 'Failed to send message to master process.',
|
SHARDING_PARENT_CONNECTION: 'Failed to send message to master process.',
|
||||||
SHARDING_NO_SHARDS: 'No shards have been spawned',
|
SHARDING_NO_SHARDS: 'No shards have been spawned.',
|
||||||
SHARDING_IN_PROCESS: 'Shards are still being spawned',
|
SHARDING_IN_PROCESS: 'Shards are still being spawned.',
|
||||||
SHARDING_ALREADY_SPAWNED: count => `Already spawned ${count} shards`,
|
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_RANGE: 'Color must be within the range 0 - 16777215 (0xFFFFFF).',
|
||||||
COLOR_CONVERT: 'Unable to convert color to a number.',
|
COLOR_CONVERT: 'Unable to convert color to a number.',
|
||||||
|
|||||||
31
src/index.js
31
src/index.js
@@ -15,23 +15,44 @@ module.exports = {
|
|||||||
DataResolver: require('./util/DataResolver'),
|
DataResolver: require('./util/DataResolver'),
|
||||||
DataStore: require('./stores/DataStore'),
|
DataStore: require('./stores/DataStore'),
|
||||||
DiscordAPIError: require('./rest/DiscordAPIError'),
|
DiscordAPIError: require('./rest/DiscordAPIError'),
|
||||||
EvaluatedPermissions: require('./util/Permissions'),
|
|
||||||
Permissions: require('./util/Permissions'),
|
Permissions: require('./util/Permissions'),
|
||||||
Snowflake: require('./util/Snowflake'),
|
Snowflake: require('./util/Snowflake'),
|
||||||
SnowflakeUtil: require('./util/Snowflake'),
|
SnowflakeUtil: require('./util/Snowflake'),
|
||||||
|
Structures: require('./util/Structures'),
|
||||||
Util: Util,
|
Util: Util,
|
||||||
util: Util,
|
util: Util,
|
||||||
version: require('../package.json').version,
|
version: require('../package.json').version,
|
||||||
|
|
||||||
|
// Stores
|
||||||
|
ChannelStore: require('./stores/ChannelStore'),
|
||||||
|
ClientPresenceStore: require('./stores/ClientPresenceStore'),
|
||||||
|
EmojiStore: require('./stores/EmojiStore'),
|
||||||
|
GuildChannelStore: require('./stores/GuildChannelStore'),
|
||||||
|
GuildMemberStore: require('./stores/GuildMemberStore'),
|
||||||
|
GuildStore: require('./stores/GuildStore'),
|
||||||
|
ReactionUserStore: require('./stores/ReactionUserStore'),
|
||||||
|
MessageStore: require('./stores/MessageStore'),
|
||||||
|
PresenceStore: require('./stores/PresenceStore'),
|
||||||
|
RoleStore: require('./stores/RoleStore'),
|
||||||
|
UserStore: require('./stores/UserStore'),
|
||||||
|
|
||||||
// Shortcuts to Util methods
|
// Shortcuts to Util methods
|
||||||
escapeMarkdown: Util.escapeMarkdown,
|
escapeMarkdown: Util.escapeMarkdown,
|
||||||
fetchRecommendedShards: Util.fetchRecommendedShards,
|
fetchRecommendedShards: Util.fetchRecommendedShards,
|
||||||
splitMessage: Util.splitMessage,
|
splitMessage: Util.splitMessage,
|
||||||
|
|
||||||
// Structures
|
// Structures
|
||||||
|
Base: require('./structures/Base'),
|
||||||
Activity: require('./structures/Presence').Activity,
|
Activity: require('./structures/Presence').Activity,
|
||||||
|
CategoryChannel: require('./structures/CategoryChannel'),
|
||||||
Channel: require('./structures/Channel'),
|
Channel: require('./structures/Channel'),
|
||||||
ClientUser: require('./structures/ClientUser'),
|
ClientApplication: require('./structures/ClientApplication'),
|
||||||
|
get ClientUser() {
|
||||||
|
// This is a getter so that it properly extends any custom User class
|
||||||
|
return require('./structures/ClientUser');
|
||||||
|
},
|
||||||
|
ClientUserChannelOverride: require('./structures/ClientUserChannelOverride'),
|
||||||
|
ClientUserGuildSettings: require('./structures/ClientUserGuildSettings'),
|
||||||
ClientUserSettings: require('./structures/ClientUserSettings'),
|
ClientUserSettings: require('./structures/ClientUserSettings'),
|
||||||
Collector: require('./structures/interfaces/Collector'),
|
Collector: require('./structures/interfaces/Collector'),
|
||||||
DMChannel: require('./structures/DMChannel'),
|
DMChannel: require('./structures/DMChannel'),
|
||||||
@@ -48,15 +69,17 @@ module.exports = {
|
|||||||
MessageEmbed: require('./structures/MessageEmbed'),
|
MessageEmbed: require('./structures/MessageEmbed'),
|
||||||
MessageMentions: require('./structures/MessageMentions'),
|
MessageMentions: require('./structures/MessageMentions'),
|
||||||
MessageReaction: require('./structures/MessageReaction'),
|
MessageReaction: require('./structures/MessageReaction'),
|
||||||
ClientApplication: require('./structures/ClientApplication'),
|
|
||||||
PermissionOverwrites: require('./structures/PermissionOverwrites'),
|
PermissionOverwrites: require('./structures/PermissionOverwrites'),
|
||||||
Presence: require('./structures/Presence').Presence,
|
Presence: require('./structures/Presence').Presence,
|
||||||
ReactionEmoji: require('./structures/ReactionEmoji'),
|
|
||||||
ReactionCollector: require('./structures/ReactionCollector'),
|
ReactionCollector: require('./structures/ReactionCollector'),
|
||||||
|
ReactionEmoji: require('./structures/ReactionEmoji'),
|
||||||
|
RichPresenceAssets: require('./structures/Presence').RichPresenceAssets,
|
||||||
Role: require('./structures/Role'),
|
Role: require('./structures/Role'),
|
||||||
TextChannel: require('./structures/TextChannel'),
|
TextChannel: require('./structures/TextChannel'),
|
||||||
User: require('./structures/User'),
|
User: require('./structures/User'),
|
||||||
|
UserConnection: require('./structures/UserConnection'),
|
||||||
VoiceChannel: require('./structures/VoiceChannel'),
|
VoiceChannel: require('./structures/VoiceChannel'),
|
||||||
|
VoiceRegion: require('./structures/VoiceRegion'),
|
||||||
Webhook: require('./structures/Webhook'),
|
Webhook: require('./structures/Webhook'),
|
||||||
|
|
||||||
WebSocket: require('./WebSocket'),
|
WebSocket: require('./WebSocket'),
|
||||||
|
|||||||
@@ -12,12 +12,22 @@ class RESTManager {
|
|||||||
this.globallyRateLimited = false;
|
this.globallyRateLimited = false;
|
||||||
this.tokenPrefix = tokenPrefix;
|
this.tokenPrefix = tokenPrefix;
|
||||||
this.versioned = true;
|
this.versioned = true;
|
||||||
|
this.timeDifferences = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
get api() {
|
get api() {
|
||||||
return routeBuilder(this);
|
return routeBuilder(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get timeDifference() {
|
||||||
|
return Math.round(this.timeDifferences.reduce((a, b) => a + b, 0) / this.timeDifferences.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
set timeDifference(ms) {
|
||||||
|
this.timeDifferences.unshift(ms);
|
||||||
|
if (this.timeDifferences.length > 5) this.timeDifferences.length = 5;
|
||||||
|
}
|
||||||
|
|
||||||
getAuth() {
|
getAuth() {
|
||||||
const token = this.client.token || this.client.accessToken;
|
const token = this.client.token || this.client.accessToken;
|
||||||
const prefixed = !!this.client.application || (this.client.user && this.client.user.bot);
|
const prefixed = !!this.client.application || (this.client.user && this.client.user.bot);
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ class RequestHandler {
|
|||||||
this.limit = Infinity;
|
this.limit = Infinity;
|
||||||
this.resetTime = null;
|
this.resetTime = null;
|
||||||
this.remaining = 1;
|
this.remaining = 1;
|
||||||
this.timeDifference = 0;
|
|
||||||
|
|
||||||
this.queue = [];
|
this.queue = [];
|
||||||
}
|
}
|
||||||
@@ -32,7 +31,7 @@ class RequestHandler {
|
|||||||
const finish = timeout => {
|
const finish = timeout => {
|
||||||
if (timeout || this.limited) {
|
if (timeout || this.limited) {
|
||||||
if (!timeout) {
|
if (!timeout) {
|
||||||
timeout = this.resetTime - Date.now() + this.timeDifference + this.client.options.restTimeOffset;
|
timeout = this.resetTime - Date.now() + this.manager.timeDifference + this.client.options.restTimeOffset;
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line prefer-promise-reject-errors
|
// eslint-disable-next-line prefer-promise-reject-errors
|
||||||
reject({ timeout });
|
reject({ timeout });
|
||||||
@@ -40,17 +39,18 @@ class RequestHandler {
|
|||||||
/**
|
/**
|
||||||
* Emitted when the client hits a rate limit while making a request
|
* Emitted when the client hits a rate limit while making a request
|
||||||
* @event Client#rateLimit
|
* @event Client#rateLimit
|
||||||
* @prop {number} timeout Timeout in ms
|
* @param {Object} rateLimitInfo Object containing the rate limit info
|
||||||
* @prop {number} limit Number of requests that can be made to this endpoint
|
* @param {number} rateLimitInfo.timeout Timeout in ms
|
||||||
* @prop {number} timeDifference Delta-T in ms between your system and Discord servers
|
* @param {number} rateLimitInfo.limit Number of requests that can be made to this endpoint
|
||||||
* @prop {string} method HTTP method used for request that triggered this event
|
* @param {number} rateLimitInfo.timeDifference Delta-T in ms between your system and Discord servers
|
||||||
* @prop {string} path Path used for request that triggered this event
|
* @param {string} rateLimitInfo.method HTTP method used for request that triggered this event
|
||||||
* @prop {string} route Route 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.client.emit(RATE_LIMIT, {
|
this.client.emit(RATE_LIMIT, {
|
||||||
timeout,
|
timeout,
|
||||||
limit: this.limit,
|
limit: this.limit,
|
||||||
timeDifference: this.timeDifference,
|
timeDifference: this.manager.timeDifference,
|
||||||
method: item.request.method,
|
method: item.request.method,
|
||||||
path: item.request.path,
|
path: item.request.path,
|
||||||
route: item.request.route,
|
route: item.request.route,
|
||||||
@@ -66,7 +66,7 @@ class RequestHandler {
|
|||||||
this.limit = Number(res.headers['x-ratelimit-limit']);
|
this.limit = Number(res.headers['x-ratelimit-limit']);
|
||||||
this.resetTime = Number(res.headers['x-ratelimit-reset']) * 1000;
|
this.resetTime = Number(res.headers['x-ratelimit-reset']) * 1000;
|
||||||
this.remaining = Number(res.headers['x-ratelimit-remaining']);
|
this.remaining = Number(res.headers['x-ratelimit-remaining']);
|
||||||
this.timeDifference = Date.now() - new Date(res.headers.date).getTime();
|
this.manager.timeDifference = Date.now() - new Date(res.headers.date).getTime();
|
||||||
}
|
}
|
||||||
if (err) {
|
if (err) {
|
||||||
if (err.status === 429) {
|
if (err.status === 429) {
|
||||||
|
|||||||
@@ -1,18 +1,24 @@
|
|||||||
const childProcess = require('child_process');
|
const childProcess = require('child_process');
|
||||||
|
const EventEmitter = require('events');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const Util = require('../util/Util');
|
const Util = require('../util/Util');
|
||||||
const { Error } = require('../errors');
|
const { Error } = require('../errors');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a Shard spawned by the ShardingManager.
|
* 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 exits for any reason, the shard will spawn a
|
||||||
|
* new one to replace it as necessary.
|
||||||
|
* @extends EventEmitter
|
||||||
*/
|
*/
|
||||||
class Shard {
|
class Shard extends EventEmitter {
|
||||||
/**
|
/**
|
||||||
* @param {ShardingManager} manager The sharding manager
|
* @param {ShardingManager} manager Manager that is spawning this shard
|
||||||
* @param {number} id The ID of this shard
|
* @param {number} id ID of this shard
|
||||||
* @param {Array} [args=[]] Command line arguments to pass to the script
|
* @param {string[]} [args=[]] Command line arguments to pass to the script
|
||||||
*/
|
*/
|
||||||
constructor(manager, id, args = []) {
|
constructor(manager, id, args = []) {
|
||||||
|
super();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manager that created the shard
|
* Manager that created the shard
|
||||||
* @type {ShardingManager}
|
* @type {ShardingManager}
|
||||||
@@ -26,7 +32,13 @@ class Shard {
|
|||||||
this.id = id;
|
this.id = id;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The environment variables for the shard
|
* Arguments for the shard's process
|
||||||
|
* @type {string[]}
|
||||||
|
*/
|
||||||
|
this.args = args;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Environment variables for the shard's process
|
||||||
* @type {Object}
|
* @type {Object}
|
||||||
*/
|
*/
|
||||||
this.env = Object.assign({}, process.env, {
|
this.env = Object.assign({}, process.env, {
|
||||||
@@ -36,19 +48,81 @@ class Shard {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process of the shard
|
* Whether the shard's {@link Client} is ready
|
||||||
* @type {ChildProcess}
|
* @type {boolean}
|
||||||
*/
|
*/
|
||||||
this.process = childProcess.fork(path.resolve(this.manager.file), args, {
|
this.ready = false;
|
||||||
env: this.env,
|
|
||||||
});
|
|
||||||
this.process.on('message', this._handleMessage.bind(this));
|
|
||||||
this.process.once('exit', () => {
|
|
||||||
if (this.manager.respawn) this.manager.createShard(this.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process of the shard
|
||||||
|
* @type {?ChildProcess}
|
||||||
|
*/
|
||||||
|
this.process = 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();
|
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();
|
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 for the shard.
|
||||||
|
* <warn>You should not need to call this manually.</warn>
|
||||||
|
* @param {boolean} [waitForReady=true] Whether to wait until the {@link Client} has become ready before resolving
|
||||||
|
* @returns {Promise<ChildProcess>}
|
||||||
|
*/
|
||||||
|
async spawn(waitForReady = true) {
|
||||||
|
if (this.process) throw new Error('SHARDING_PROCESS_EXISTS', this.id);
|
||||||
|
|
||||||
|
this.process = childProcess.fork(path.resolve(this.manager.file), this.args, { env: this.env })
|
||||||
|
.on('message', this._handleMessage.bind(this))
|
||||||
|
.on('exit', this._exitListener);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emitted upon the creation of the shard's child process.
|
||||||
|
* @event Shard#spawn
|
||||||
|
* @param {ChildProcess} process Child process that was created
|
||||||
|
*/
|
||||||
|
this.emit('spawn', this.process);
|
||||||
|
|
||||||
|
if (!waitForReady) return this.process;
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
this.once('ready', resolve);
|
||||||
|
this.once('disconnect', () => reject(new Error('SHARDING_READY_DISCONNECTED', this.id)));
|
||||||
|
this.once('death', () => reject(new Error('SHARDING_READY_DIED', this.id)));
|
||||||
|
setTimeout(() => reject(new Error('SHARDING_READY_TIMEOUT', this.id)), 30000);
|
||||||
|
});
|
||||||
|
return this.process;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kills and restarts the shard's process.
|
||||||
|
* @param {number} [delay=500] How long to wait between killing the process and restarting it (in milliseconds)
|
||||||
|
* @param {boolean} [waitForReady=true] Whether to wait the {@link Client} has become ready before resolving
|
||||||
|
* @returns {Promise<ChildProcess>}
|
||||||
|
*/
|
||||||
|
async respawn(delay = 500, waitForReady = true) {
|
||||||
|
this.process.removeListener('exit', this._exitListener);
|
||||||
|
this.process.kill();
|
||||||
|
this._handleExit(false);
|
||||||
|
if (delay > 0) await Util.delayFor(delay);
|
||||||
|
return this.spawn(waitForReady);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -100,7 +174,7 @@ class Shard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Evaluates a script on the shard, in the context of the client.
|
* Evaluates a script on the shard, in the context of the {@link Client}.
|
||||||
* @param {string} script JavaScript to run on the shard
|
* @param {string} script JavaScript to run on the shard
|
||||||
* @returns {Promise<*>} Result of the script execution
|
* @returns {Promise<*>} Result of the script execution
|
||||||
*/
|
*/
|
||||||
@@ -134,6 +208,39 @@ class Shard {
|
|||||||
*/
|
*/
|
||||||
_handleMessage(message) {
|
_handleMessage(message) {
|
||||||
if (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
|
// Shard is requesting a property fetch
|
||||||
if (message._sFetchProp) {
|
if (message._sFetchProp) {
|
||||||
this.manager.fetchClientValues(message._sFetchProp).then(
|
this.manager.fetchClientValues(message._sFetchProp).then(
|
||||||
@@ -151,15 +258,44 @@ class Shard {
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Shard is requesting a respawn of all shards
|
||||||
|
if (message._sRespawnAll) {
|
||||||
|
const { shardDelay, respawnDelay, waitForReady } = message._sRespawnAll;
|
||||||
|
this.manager.respawnAll(shardDelay, respawnDelay, waitForReady).catch(() => {
|
||||||
|
// Do nothing
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emitted upon recieving a message from a shard.
|
* Emitted upon recieving a message from the child process.
|
||||||
* @event ShardingManager#message
|
* @event Shard#message
|
||||||
* @param {Shard} shard Shard that sent the message
|
|
||||||
* @param {*} message Message that was received
|
* @param {*} message Message that was received
|
||||||
*/
|
*/
|
||||||
this.manager.emit('message', this, message);
|
this.emit('message', message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the shard's process 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 exiting.
|
||||||
|
* @event Shard#death
|
||||||
|
* @param {ChildProcess} process Child process that exited
|
||||||
|
*/
|
||||||
|
this.emit('death', this.process);
|
||||||
|
|
||||||
|
this.ready = false;
|
||||||
|
this.process = null;
|
||||||
|
this._evals.clear();
|
||||||
|
this._fetches.clear();
|
||||||
|
|
||||||
|
if (respawn) this.spawn().catch(err => this.emit('error', err));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,15 +3,19 @@ const { Events } = require('../util/Constants');
|
|||||||
const { Error } = require('../errors');
|
const { Error } = require('../errors');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper class for sharded clients spawned as a child process, such as from a ShardingManager.
|
* Helper class for sharded clients spawned as a child process, such as from a {@link ShardingManager}.
|
||||||
|
* Utilises IPC to send and receive data to/from the master process and other shards.
|
||||||
*/
|
*/
|
||||||
class ShardClientUtil {
|
class ShardClientUtil {
|
||||||
/**
|
/**
|
||||||
* @param {Client} client The client of the current shard
|
* @param {Client} client Client of the current shard
|
||||||
*/
|
*/
|
||||||
constructor(client) {
|
constructor(client) {
|
||||||
this.client = client;
|
this.client = client;
|
||||||
process.on('message', this._handleMessage.bind(this));
|
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 }); });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -49,13 +53,14 @@ class ShardClientUtil {
|
|||||||
/**
|
/**
|
||||||
* Fetches a client property value of each shard.
|
* Fetches a client property value of each shard.
|
||||||
* @param {string} prop Name of the client property to get, using periods for nesting
|
* @param {string} prop Name of the client property to get, using periods for nesting
|
||||||
* @returns {Promise<Array>}
|
* @returns {Promise<Array<*>>}
|
||||||
* @example
|
* @example
|
||||||
* client.shard.fetchClientValues('guilds.size')
|
* client.shard.fetchClientValues('guilds.size')
|
||||||
* .then(results => {
|
* .then(results => {
|
||||||
* console.log(`${results.reduce((prev, val) => prev + val, 0)} total guilds`);
|
* console.log(`${results.reduce((prev, val) => prev + val, 0)} total guilds`);
|
||||||
* })
|
* })
|
||||||
* .catch(console.error);
|
* .catch(console.error);
|
||||||
|
* @see {@link ShardingManager#fetchClientValues}
|
||||||
*/
|
*/
|
||||||
fetchClientValues(prop) {
|
fetchClientValues(prop) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@@ -74,9 +79,10 @@ class ShardClientUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Evaluates a script on all shards, in the context of the Clients.
|
* Evaluates a script on all shards, in the context of the {@link Clients}.
|
||||||
* @param {string} script JavaScript to run on each shard
|
* @param {string} script JavaScript to run on each shard
|
||||||
* @returns {Promise<Array>} Results of the script execution
|
* @returns {Promise<Array<*>>} Results of the script execution
|
||||||
|
* @see {@link ShardingManager#broadcastEval}
|
||||||
*/
|
*/
|
||||||
broadcastEval(script) {
|
broadcastEval(script) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@@ -94,6 +100,19 @@ class ShardClientUtil {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 and restarting it
|
||||||
|
* (in milliseconds)
|
||||||
|
* @param {boolean} [waitForReady=true] Whether to wait for a shard to become ready before continuing to another
|
||||||
|
* @returns {Promise<void>} Resolves upon the message being sent
|
||||||
|
* @see {@link ShardingManager#respawnAll}
|
||||||
|
*/
|
||||||
|
respawnAll(shardDelay = 5000, respawnDelay = 500, waitForReady = true) {
|
||||||
|
return this.send({ _sRespawnAll: { shardDelay, respawnDelay, waitForReady } });
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles an IPC message.
|
* Handles an IPC message.
|
||||||
* @param {*} message Message received
|
* @param {*} message Message received
|
||||||
|
|||||||
@@ -7,9 +7,12 @@ const Util = require('../util/Util');
|
|||||||
const { Error, TypeError, RangeError } = require('../errors');
|
const { Error, TypeError, RangeError } = require('../errors');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a utility class that can be used to help you spawn shards of your client. Each shard is completely separate
|
* This is a utility class that makes multi-process sharding of a bot an easy and painless experience.
|
||||||
* from the other. The Shard Manager takes a path to a file and spawns it under the specified amount of shards safely.
|
* It works by spawning a self-contained {@link ChildProcess} for each individual shard, each containing its own
|
||||||
* If you do not select an amount of shards, the manager will automatically decide the best amount.
|
* 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}
|
* @extends {EventEmitter}
|
||||||
*/
|
*/
|
||||||
class ShardingManager extends EventEmitter {
|
class ShardingManager extends EventEmitter {
|
||||||
@@ -82,33 +85,33 @@ class ShardingManager extends EventEmitter {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Spawns a single shard.
|
* Spawns a single shard.
|
||||||
* @param {number} id The ID of the shard to spawn. **This is usually not necessary**
|
* @param {number} [id=this.shards.size] ID of the shard to spawn -
|
||||||
* @returns {Promise<Shard>}
|
* **This is usually not necessary to manually specify.**
|
||||||
|
* @returns {Shard}
|
||||||
*/
|
*/
|
||||||
createShard(id = this.shards.size) {
|
createShard(id = this.shards.size) {
|
||||||
const shard = new Shard(this, id, this.shardArgs);
|
const shard = new Shard(this, id, this.shardArgs);
|
||||||
this.shards.set(id, shard);
|
this.shards.set(id, shard);
|
||||||
/**
|
/**
|
||||||
* Emitted upon launching a shard.
|
* Emitted upon creating a shard.
|
||||||
* @event ShardingManager#launch
|
* @event ShardingManager#shardCreate
|
||||||
* @param {Shard} shard Shard that was launched
|
* @param {Shard} shard Shard that was created
|
||||||
*/
|
*/
|
||||||
this.emit('launch', shard);
|
this.emit('shardCreate', shard);
|
||||||
return Promise.resolve(shard);
|
return shard;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Spawns multiple shards.
|
* Spawns multiple shards.
|
||||||
* @param {number} [amount=this.totalShards] Number of shards to spawn
|
* @param {number} [amount=this.totalShards] Number of shards to spawn
|
||||||
* @param {number} [delay=7500] How long to wait in between spawning each shard (in milliseconds)
|
* @param {number} [delay=5500] How long to wait in between spawning each shard (in milliseconds)
|
||||||
|
* @param {boolean} [waitForReady=true] Whether to wait for a shard to become ready before continuing to another
|
||||||
* @returns {Promise<Collection<number, Shard>>}
|
* @returns {Promise<Collection<number, Shard>>}
|
||||||
*/
|
*/
|
||||||
spawn(amount = this.totalShards, delay = 7500) {
|
async spawn(amount = this.totalShards, delay = 5500, waitForReady = true) {
|
||||||
|
// Obtain/verify the number of shards to spawn
|
||||||
if (amount === 'auto') {
|
if (amount === 'auto') {
|
||||||
return Util.fetchRecommendedShards(this.token).then(count => {
|
amount = await Util.fetchRecommendedShards(this.token);
|
||||||
this.totalShards = count;
|
|
||||||
return this._spawn(count, delay);
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
if (typeof amount !== 'number' || isNaN(amount)) {
|
if (typeof amount !== 'number' || isNaN(amount)) {
|
||||||
throw new TypeError('CLIENT_INVALID_OPTION', 'Amount of shards', 'a number.');
|
throw new TypeError('CLIENT_INVALID_OPTION', 'Amount of shards', 'a number.');
|
||||||
@@ -117,41 +120,22 @@ class ShardingManager extends EventEmitter {
|
|||||||
if (amount !== Math.floor(amount)) {
|
if (amount !== Math.floor(amount)) {
|
||||||
throw new TypeError('CLIENT_INVALID_OPTION', 'Amount of shards', 'an integer.');
|
throw new TypeError('CLIENT_INVALID_OPTION', 'Amount of shards', 'an integer.');
|
||||||
}
|
}
|
||||||
return this._spawn(amount, delay);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// Make sure this many shards haven't already been spawned
|
||||||
* Actually spawns shards, unlike that poser above >:(
|
if (this.shards.size >= amount) throw new Error('SHARDING_ALREADY_SPAWNED', this.shards.size);
|
||||||
* @param {number} amount Number of shards to spawn
|
this.totalShards = amount;
|
||||||
* @param {number} delay How long to wait in between spawning each shard (in milliseconds)
|
|
||||||
* @returns {Promise<Collection<number, Shard>>}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_spawn(amount, delay) {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
if (this.shards.size >= amount) throw new Error('SHARDING_ALREADY_SPAWNED', this.shards.size);
|
|
||||||
this.totalShards = amount;
|
|
||||||
|
|
||||||
this.createShard();
|
// Spawn the shards
|
||||||
if (this.shards.size >= this.totalShards) {
|
for (let s = 1; s <= amount; s++) {
|
||||||
resolve(this.shards);
|
const promises = [];
|
||||||
return;
|
const shard = this.createShard();
|
||||||
}
|
promises.push(shard.spawn(waitForReady));
|
||||||
|
if (delay > 0 && s !== amount) promises.push(Util.delayFor(delay));
|
||||||
|
await Promise.all(promises); // eslint-disable-line no-await-in-loop
|
||||||
|
}
|
||||||
|
|
||||||
if (delay <= 0) {
|
return this.shards;
|
||||||
while (this.shards.size < this.totalShards) this.createShard();
|
|
||||||
resolve(this.shards);
|
|
||||||
} else {
|
|
||||||
const interval = setInterval(() => {
|
|
||||||
this.createShard();
|
|
||||||
if (this.shards.size >= this.totalShards) {
|
|
||||||
clearInterval(interval);
|
|
||||||
resolve(this.shards);
|
|
||||||
}
|
|
||||||
}, delay);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -166,9 +150,9 @@ class ShardingManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Evaluates a script on all shards, in the context of the Clients.
|
* Evaluates a script on all shards, in the context of the {@link Client}s.
|
||||||
* @param {string} script JavaScript to run on each shard
|
* @param {string} script JavaScript to run on each shard
|
||||||
* @returns {Promise<Array>} Results of the script execution
|
* @returns {Promise<Array<*>>} Results of the script execution
|
||||||
*/
|
*/
|
||||||
broadcastEval(script) {
|
broadcastEval(script) {
|
||||||
const promises = [];
|
const promises = [];
|
||||||
@@ -179,7 +163,7 @@ class ShardingManager extends EventEmitter {
|
|||||||
/**
|
/**
|
||||||
* Fetches a client property value of each shard.
|
* Fetches a client property value of each shard.
|
||||||
* @param {string} prop Name of the client property to get, using periods for nesting
|
* @param {string} prop Name of the client property to get, using periods for nesting
|
||||||
* @returns {Promise<Array>}
|
* @returns {Promise<Array<*>>}
|
||||||
* @example
|
* @example
|
||||||
* manager.fetchClientValues('guilds.size')
|
* manager.fetchClientValues('guilds.size')
|
||||||
* .then(results => {
|
* .then(results => {
|
||||||
@@ -194,6 +178,24 @@ class ShardingManager extends EventEmitter {
|
|||||||
for (const shard of this.shards.values()) promises.push(shard.fetchClientValue(prop));
|
for (const shard of this.shards.values()) promises.push(shard.fetchClientValue(prop));
|
||||||
return Promise.all(promises);
|
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 {boolean} [waitForReady=true] Whether to wait for a shard to become ready before continuing to another
|
||||||
|
* @returns {Promise<Collection<string, Shard>>}
|
||||||
|
*/
|
||||||
|
async respawnAll(shardDelay = 5000, respawnDelay = 500, waitForReady = true) {
|
||||||
|
let s = 0;
|
||||||
|
for (const shard of this.shards) {
|
||||||
|
const promises = [shard.respawn(respawnDelay, waitForReady)];
|
||||||
|
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;
|
module.exports = ShardingManager;
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ class ChannelStore extends DataStore {
|
|||||||
* @memberof ChannelStore
|
* @memberof ChannelStore
|
||||||
* @instance
|
* @instance
|
||||||
* @param {ChannelResolvable} channel The channel resolvable to resolve
|
* @param {ChannelResolvable} channel The channel resolvable to resolve
|
||||||
* @returns {?string}
|
* @returns {?Snowflake}
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
const Collection = require('../util/Collection');
|
const Collection = require('../util/Collection');
|
||||||
|
let Structures;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages the creation, retrieval and deletion of a specific data model.
|
* Manages the creation, retrieval and deletion of a specific data model.
|
||||||
@@ -7,8 +8,9 @@ const Collection = require('../util/Collection');
|
|||||||
class DataStore extends Collection {
|
class DataStore extends Collection {
|
||||||
constructor(client, iterable, holds) {
|
constructor(client, iterable, holds) {
|
||||||
super();
|
super();
|
||||||
|
if (!Structures) Structures = require('../util/Structures');
|
||||||
Object.defineProperty(this, 'client', { value: client });
|
Object.defineProperty(this, 'client', { value: client });
|
||||||
Object.defineProperty(this, 'holds', { value: holds });
|
Object.defineProperty(this, 'holds', { value: Structures.get(holds.name) || holds });
|
||||||
if (iterable) for (const item of iterable) this.create(item);
|
if (iterable) for (const item of iterable) this.create(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,7 +39,7 @@ class DataStore extends Collection {
|
|||||||
/**
|
/**
|
||||||
* Resolves a data entry to a instance ID.
|
* Resolves a data entry to a instance ID.
|
||||||
* @param {string|Instance} idOrInstance The id or instance of something in this DataStore
|
* @param {string|Instance} idOrInstance The id or instance of something in this DataStore
|
||||||
* @returns {?string}
|
* @returns {?Snowflake}
|
||||||
*/
|
*/
|
||||||
resolveID(idOrInstance) {
|
resolveID(idOrInstance) {
|
||||||
if (idOrInstance instanceof this.holds) return idOrInstance.id;
|
if (idOrInstance instanceof this.holds) return idOrInstance.id;
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ class EmojiStore extends DataStore {
|
|||||||
/**
|
/**
|
||||||
* Resolves a EmojiResolvable to a Emoji ID string.
|
* Resolves a EmojiResolvable to a Emoji ID string.
|
||||||
* @param {EmojiResolvable} emoji The Emoji resolvable to identify
|
* @param {EmojiResolvable} emoji The Emoji resolvable to identify
|
||||||
* @returns {?string}
|
* @returns {?Snowflake}
|
||||||
*/
|
*/
|
||||||
resolveID(emoji) {
|
resolveID(emoji) {
|
||||||
if (emoji instanceof ReactionEmoji) return emoji.id;
|
if (emoji instanceof ReactionEmoji) return emoji.id;
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ class GuildChannelStore extends DataStore {
|
|||||||
* @memberof GuildChannelStore
|
* @memberof GuildChannelStore
|
||||||
* @instance
|
* @instance
|
||||||
* @param {GuildChannelResolvable} channel The GuildChannel resolvable to resolve
|
* @param {GuildChannelResolvable} channel The GuildChannel resolvable to resolve
|
||||||
* @returns {?string}
|
* @returns {?Snowflake}
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class GuildMemberStore extends DataStore {
|
|||||||
/**
|
/**
|
||||||
* Resolves a GuildMemberResolvable to an member ID string.
|
* Resolves a GuildMemberResolvable to an member ID string.
|
||||||
* @param {GuildMemberResolvable} member The user that is part of the guild
|
* @param {GuildMemberResolvable} member The user that is part of the guild
|
||||||
* @returns {?string}
|
* @returns {?Snowflake}
|
||||||
*/
|
*/
|
||||||
resolveID(member) {
|
resolveID(member) {
|
||||||
const memberResolveable = super.resolveID(member);
|
const memberResolveable = super.resolveID(member);
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class GuildStore extends DataStore {
|
|||||||
* @memberof GuildStore
|
* @memberof GuildStore
|
||||||
* @instance
|
* @instance
|
||||||
* @param {GuildResolvable} guild The guild resolvable to identify
|
* @param {GuildResolvable} guild The guild resolvable to identify
|
||||||
* @returns {?string}
|
* @returns {?Snowflake}
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ class MessageStore extends DataStore {
|
|||||||
* @memberof MessageStore
|
* @memberof MessageStore
|
||||||
* @instance
|
* @instance
|
||||||
* @param {MessageResolvable} message The message resolvable to resolve
|
* @param {MessageResolvable} message The message resolvable to resolve
|
||||||
* @returns {?string}
|
* @returns {?Snowflake}
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class PresenceStore extends DataStore {
|
|||||||
/**
|
/**
|
||||||
* Resolves a PresenceResolvable to a Presence ID string.
|
* Resolves a PresenceResolvable to a Presence ID string.
|
||||||
* @param {PresenceResolvable} presence The presence resolvable to resolve
|
* @param {PresenceResolvable} presence The presence resolvable to resolve
|
||||||
* @returns {?string}
|
* @returns {?Snowflake}
|
||||||
*/
|
*/
|
||||||
resolveID(presence) {
|
resolveID(presence) {
|
||||||
const presenceResolveable = super.resolveID(presence);
|
const presenceResolveable = super.resolveID(presence);
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ class ReactionStore extends DataStore {
|
|||||||
* @memberof ReactionStore
|
* @memberof ReactionStore
|
||||||
* @instance
|
* @instance
|
||||||
* @param {MessageReactionResolvable} role The role resolvable to resolve
|
* @param {MessageReactionResolvable} role The role resolvable to resolve
|
||||||
* @returns {?string}
|
* @returns {?Snowflake}
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
33
src/stores/ReactionUserStore.js
Normal file
33
src/stores/ReactionUserStore.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
const DataStore = require('./DataStore');
|
||||||
|
/**
|
||||||
|
* A data store to store User models who reacted to a MessageReaction.
|
||||||
|
* @extends {DataStore}
|
||||||
|
*/
|
||||||
|
class ReactionUserStore extends DataStore {
|
||||||
|
constructor(client, iterable, reaction) {
|
||||||
|
super(client, iterable, require('../structures/User'));
|
||||||
|
this.reaction = reaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<ReactionUserStore<Snowflake, User>>}
|
||||||
|
*/
|
||||||
|
async fetch({ limit = 100, after, before } = {}) {
|
||||||
|
const message = this.reaction.message;
|
||||||
|
const users = await this.client.api.channels[message.channel.id].messages[message.id]
|
||||||
|
.reactions[this.reaction.emoji.identifier]
|
||||||
|
.get({ query: { limit, before, after } });
|
||||||
|
for (const rawUser of users) {
|
||||||
|
const user = this.client.users.create(rawUser);
|
||||||
|
this.set(user.id, user);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = ReactionUserStore;
|
||||||
@@ -38,7 +38,7 @@ class RoleStore extends DataStore {
|
|||||||
* @memberof RoleStore
|
* @memberof RoleStore
|
||||||
* @instance
|
* @instance
|
||||||
* @param {RoleResolvable} role The role resolvable to resolve
|
* @param {RoleResolvable} role The role resolvable to resolve
|
||||||
* @returns {?string}
|
* @returns {?Snowflake}
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ class UserStore extends DataStore {
|
|||||||
/**
|
/**
|
||||||
* Resolves a UserResolvable to a user ID string.
|
* Resolves a UserResolvable to a user ID string.
|
||||||
* @param {UserResolvable} user The UserResolvable to identify
|
* @param {UserResolvable} user The UserResolvable to identify
|
||||||
* @returns {?string}
|
* @returns {?Snowflake}
|
||||||
*/
|
*/
|
||||||
resolveID(user) {
|
resolveID(user) {
|
||||||
if (user instanceof GuildMember) return user.user.id;
|
if (user instanceof GuildMember) return user.user.id;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ const GuildChannel = require('./GuildChannel');
|
|||||||
*/
|
*/
|
||||||
class CategoryChannel extends GuildChannel {
|
class CategoryChannel extends GuildChannel {
|
||||||
/**
|
/**
|
||||||
* The channels that are part of this category
|
* Channels that are part of this category
|
||||||
* @type {?Collection<Snowflake, GuildChannel>}
|
* @type {?Collection<Snowflake, GuildChannel>}
|
||||||
* @readonly
|
* @readonly
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -66,32 +66,37 @@ class Channel extends Base {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static create(client, data, guild) {
|
static create(client, data, guild) {
|
||||||
const DMChannel = require('./DMChannel');
|
const Structures = require('../util/Structures');
|
||||||
const GroupDMChannel = require('./GroupDMChannel');
|
|
||||||
const TextChannel = require('./TextChannel');
|
|
||||||
const VoiceChannel = require('./VoiceChannel');
|
|
||||||
const CategoryChannel = require('./CategoryChannel');
|
|
||||||
const GuildChannel = require('./GuildChannel');
|
|
||||||
let channel;
|
let channel;
|
||||||
if (data.type === ChannelTypes.DM) {
|
if (data.type === ChannelTypes.DM) {
|
||||||
|
const DMChannel = Structures.get('DMChannel');
|
||||||
channel = new DMChannel(client, data);
|
channel = new DMChannel(client, data);
|
||||||
} else if (data.type === ChannelTypes.GROUP) {
|
} else if (data.type === ChannelTypes.GROUP) {
|
||||||
|
const GroupDMChannel = Structures.get('GroupDMChannel');
|
||||||
channel = new GroupDMChannel(client, data);
|
channel = new GroupDMChannel(client, data);
|
||||||
} else {
|
} else {
|
||||||
guild = guild || client.guilds.get(data.guild_id);
|
guild = guild || client.guilds.get(data.guild_id);
|
||||||
if (guild) {
|
if (guild) {
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
case ChannelTypes.TEXT:
|
case ChannelTypes.TEXT: {
|
||||||
|
const TextChannel = Structures.get('TextChannel');
|
||||||
channel = new TextChannel(guild, data);
|
channel = new TextChannel(guild, data);
|
||||||
break;
|
break;
|
||||||
case ChannelTypes.VOICE:
|
}
|
||||||
|
case ChannelTypes.VOICE: {
|
||||||
|
const VoiceChannel = Structures.get('VoiceChannel');
|
||||||
channel = new VoiceChannel(guild, data);
|
channel = new VoiceChannel(guild, data);
|
||||||
break;
|
break;
|
||||||
case ChannelTypes.CATEGORY:
|
}
|
||||||
|
case ChannelTypes.CATEGORY: {
|
||||||
|
const CategoryChannel = Structures.get('CategoryChannel');
|
||||||
channel = new CategoryChannel(guild, data);
|
channel = new CategoryChannel(guild, data);
|
||||||
break;
|
break;
|
||||||
default:
|
}
|
||||||
|
default: {
|
||||||
|
const GuildChannel = Structures.get('GuildChannel');
|
||||||
channel = new GuildChannel(guild, data);
|
channel = new GuildChannel(guild, data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
guild.channels.set(channel.id, channel);
|
guild.channels.set(channel.id, channel);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -150,11 +150,12 @@ class ClientApplication extends Base {
|
|||||||
* @returns {Promise<Object>}
|
* @returns {Promise<Object>}
|
||||||
*/
|
*/
|
||||||
fetchAssets() {
|
fetchAssets() {
|
||||||
return this.client.api.applications(this.id).assets.get()
|
const types = Object.keys(ClientApplicationAssetTypes);
|
||||||
|
return this.client.api.oauth2.applications(this.id).assets.get()
|
||||||
.then(assets => assets.map(a => ({
|
.then(assets => assets.map(a => ({
|
||||||
id: a.id,
|
id: a.id,
|
||||||
name: a.name,
|
name: a.name,
|
||||||
type: Object.keys(ClientApplicationAssetTypes)[a.type - 1],
|
type: types[a.type - 1],
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,7 +168,7 @@ class ClientApplication extends Base {
|
|||||||
*/
|
*/
|
||||||
createAsset(name, data, type) {
|
createAsset(name, data, type) {
|
||||||
return DataResolver.resolveBase64(data).then(b64 =>
|
return DataResolver.resolveBase64(data).then(b64 =>
|
||||||
this.client.api.applications(this.id).assets.post({ data: {
|
this.client.api.oauth2.applications(this.id).assets.post({ data: {
|
||||||
name,
|
name,
|
||||||
data: b64,
|
data: b64,
|
||||||
type: ClientApplicationAssetTypes[type.toUpperCase()],
|
type: ClientApplicationAssetTypes[type.toUpperCase()],
|
||||||
@@ -177,7 +178,7 @@ class ClientApplication extends Base {
|
|||||||
/**
|
/**
|
||||||
* Resets the app's secret.
|
* Resets the app's secret.
|
||||||
* <warn>This is only available when using a user account.</warn>
|
* <warn>This is only available when using a user account.</warn>
|
||||||
* @returns {ClientApplication}
|
* @returns {Promise<ClientApplication>}
|
||||||
*/
|
*/
|
||||||
resetSecret() {
|
resetSecret() {
|
||||||
return this.client.api.oauth2.applications[this.id].reset.post()
|
return this.client.api.oauth2.applications[this.id].reset.post()
|
||||||
@@ -187,7 +188,7 @@ class ClientApplication extends Base {
|
|||||||
/**
|
/**
|
||||||
* Resets the app's bot token.
|
* Resets the app's bot token.
|
||||||
* <warn>This is only available when using a user account.</warn>
|
* <warn>This is only available when using a user account.</warn>
|
||||||
* @returns {ClientApplication}
|
* @returns {Promise<ClientApplication>}
|
||||||
*/
|
*/
|
||||||
resetToken() {
|
resetToken() {
|
||||||
return this.client.api.oauth2.applications[this.id].bot.reset.post()
|
return this.client.api.oauth2.applications[this.id].bot.reset.post()
|
||||||
@@ -195,8 +196,12 @@ class ClientApplication extends Base {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When concatenated with a string, this automatically concatenates the app name rather than the app object.
|
* When concatenated with a string, this automatically returns the application's name instead of the
|
||||||
|
* ClientApplication object.
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
|
* @example
|
||||||
|
* // Logs: Application name: My App
|
||||||
|
* console.log(`Application name: ${application}`);
|
||||||
*/
|
*/
|
||||||
toString() {
|
toString() {
|
||||||
return this.name;
|
return this.name;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const User = require('./User');
|
const Structures = require('../util/Structures');
|
||||||
const Collection = require('../util/Collection');
|
const Collection = require('../util/Collection');
|
||||||
const ClientUserSettings = require('./ClientUserSettings');
|
const ClientUserSettings = require('./ClientUserSettings');
|
||||||
const ClientUserGuildSettings = require('./ClientUserGuildSettings');
|
const ClientUserGuildSettings = require('./ClientUserGuildSettings');
|
||||||
@@ -11,7 +11,7 @@ const Guild = require('./Guild');
|
|||||||
* Represents the logged in client's Discord user.
|
* Represents the logged in client's Discord user.
|
||||||
* @extends {User}
|
* @extends {User}
|
||||||
*/
|
*/
|
||||||
class ClientUser extends User {
|
class ClientUser extends Structures.get('User') {
|
||||||
_patch(data) {
|
_patch(data) {
|
||||||
super._patch(data);
|
super._patch(data);
|
||||||
|
|
||||||
@@ -88,6 +88,8 @@ class ClientUser extends User {
|
|||||||
this.guildSettings.set(settings.guild_id, new ClientUserGuildSettings(this.client, settings));
|
this.guildSettings.set(settings.guild_id, new ClientUserGuildSettings(this.client, settings));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.token) this.client.token = data.token;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -246,7 +248,7 @@ class ClientUser extends User {
|
|||||||
/**
|
/**
|
||||||
* Fetches messages that mentioned the client's user.
|
* Fetches messages that mentioned the client's user.
|
||||||
* <warn>This is only available when using a user account.</warn>
|
* <warn>This is only available when using a user account.</warn>
|
||||||
* @param {Object} [options] Options for the fetch
|
* @param {Object} [options={}] Options for the fetch
|
||||||
* @param {number} [options.limit=25] Maximum number of mentions to retrieve
|
* @param {number} [options.limit=25] Maximum number of mentions to retrieve
|
||||||
* @param {boolean} [options.roles=true] Whether to include role mentions
|
* @param {boolean} [options.roles=true] Whether to include role mentions
|
||||||
* @param {boolean} [options.everyone=true] Whether to include everyone/here mentions
|
* @param {boolean} [options.everyone=true] Whether to include everyone/here mentions
|
||||||
|
|||||||
@@ -27,9 +27,12 @@ class DMChannel extends Channel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When concatenated with a string, this automatically concatenates the recipient's mention instead of the
|
* When concatenated with a string, this automatically returns the recipient's mention instead of the
|
||||||
* DM channel object.
|
* DMChannel object.
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
|
* @example
|
||||||
|
* // Logs: Hello from <@123456789012345678>!
|
||||||
|
* console.log(`Hello from ${channel}!`);
|
||||||
*/
|
*/
|
||||||
toString() {
|
toString() {
|
||||||
return this.recipient.toString();
|
return this.recipient.toString();
|
||||||
|
|||||||
@@ -190,7 +190,7 @@ class Emoji extends Base {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When concatenated with a string, this automatically returns the emoji mention rather than the object.
|
* When concatenated with a string, this automatically concatenates the emoji's mention instead of the Emoji object.
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
* @example
|
* @example
|
||||||
* // Send an emoji:
|
* // Send an emoji:
|
||||||
|
|||||||
@@ -203,14 +203,12 @@ class GroupDMChannel extends Channel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When concatenated with a string, this automatically concatenates the channel's name instead of the Channel object.
|
* When concatenated with a string, this automatically returns the channel's name instead of the
|
||||||
|
* GroupDMChannel object.
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
* @example
|
* @example
|
||||||
* // Logs: Hello from My Group DM!
|
* // Logs: Hello from My Group DM!
|
||||||
* console.log(`Hello from ${channel}!`);
|
* console.log(`Hello from ${channel}!`);
|
||||||
* @example
|
|
||||||
* // Logs: Hello from My Group DM!
|
|
||||||
* console.log(`Hello from ' + channel + '!');
|
|
||||||
*/
|
*/
|
||||||
toString() {
|
toString() {
|
||||||
return this.name;
|
return this.name;
|
||||||
|
|||||||
@@ -114,8 +114,18 @@ class Guild extends Base {
|
|||||||
this.large = Boolean('large' in data ? data.large : this.large);
|
this.large = Boolean('large' in data ? data.large : this.large);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An array of guild features
|
* An array of enabled guild features, here are the possible values:
|
||||||
* @type {Object[]}
|
* * INVITE_SPLASH
|
||||||
|
* * MORE_EMOJI
|
||||||
|
* * VERIFIED
|
||||||
|
* * VIP_REGIONS
|
||||||
|
* * VANITY_URL
|
||||||
|
* @typedef {string} Features
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An array of guild features partnered guilds have enabled
|
||||||
|
* @type {Features[]}
|
||||||
*/
|
*/
|
||||||
this.features = data.features;
|
this.features = data.features;
|
||||||
|
|
||||||
@@ -311,7 +321,7 @@ class Guild extends Base {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* System channel for this guild
|
* System channel for this guild
|
||||||
* @type {?GuildChannel}
|
* @type {?TextChannel}
|
||||||
* @readonly
|
* @readonly
|
||||||
*/
|
*/
|
||||||
get systemChannel() {
|
get systemChannel() {
|
||||||
@@ -401,7 +411,7 @@ class Guild extends Base {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* The `@everyone` role of the guild
|
* The `@everyone` role of the guild
|
||||||
* @type {Role}
|
* @type {Role}
|
||||||
* @readonly
|
* @readonly
|
||||||
@@ -431,10 +441,16 @@ class Guild extends Base {
|
|||||||
return this.members.resolve(user);
|
return this.members.resolve(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 a collection of banned users in this guild.
|
* Fetches a collection of banned users in this guild.
|
||||||
* The returned collection contains user objects keyed under `user` and reasons keyed under `reason`.
|
* @returns {Promise<Collection<Snowflake, BanInfo>>}
|
||||||
* @returns {Promise<Collection<Snowflake, Object>>}
|
|
||||||
*/
|
*/
|
||||||
fetchBans() {
|
fetchBans() {
|
||||||
return this.client.api.guilds(this.id).bans.get().then(bans =>
|
return this.client.api.guilds(this.id).bans.get().then(bans =>
|
||||||
@@ -496,7 +512,7 @@ class Guild extends Base {
|
|||||||
* @param {Snowflake|GuildAuditLogsEntry} [options.after] Limit to entries from after specified entry
|
* @param {Snowflake|GuildAuditLogsEntry} [options.after] Limit to entries from after specified entry
|
||||||
* @param {number} [options.limit] Limit number of entries
|
* @param {number} [options.limit] Limit number of entries
|
||||||
* @param {UserResolvable} [options.user] Only show entries involving this user
|
* @param {UserResolvable} [options.user] Only show entries involving this user
|
||||||
* @param {ActionType|number} [options.type] Only show entries involving this action type
|
* @param {AuditLogAction|number} [options.type] Only show entries involving this action type
|
||||||
* @returns {Promise<GuildAuditLogs>}
|
* @returns {Promise<GuildAuditLogs>}
|
||||||
*/
|
*/
|
||||||
fetchAuditLogs(options = {}) {
|
fetchAuditLogs(options = {}) {
|
||||||
@@ -528,7 +544,9 @@ class Guild extends Base {
|
|||||||
* @returns {Promise<GuildMember>}
|
* @returns {Promise<GuildMember>}
|
||||||
*/
|
*/
|
||||||
addMember(user, options) {
|
addMember(user, options) {
|
||||||
if (this.members.has(user.id)) return Promise.resolve(this.members.get(user.id));
|
user = this.client.users.resolveID(user);
|
||||||
|
if (!user) return Promise.reject(new TypeError('INVALID_TYPE', 'user', 'UserResolvable'));
|
||||||
|
if (this.members.has(user)) return Promise.resolve(this.members.get(user));
|
||||||
options.access_token = options.accessToken;
|
options.access_token = options.accessToken;
|
||||||
if (options.roles) {
|
if (options.roles) {
|
||||||
const roles = [];
|
const roles = [];
|
||||||
@@ -541,8 +559,8 @@ class Guild extends Base {
|
|||||||
roles.push(role.id);
|
roles.push(role.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this.client.api.guilds(this.id).members(user.id).put({ data: options })
|
return this.client.api.guilds(this.id).members(user).put({ data: options })
|
||||||
.then(data => this.client.actions.GuildMemberGet.handle(this, data).member);
|
.then(data => this.members.create(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -794,6 +812,7 @@ class Guild extends Base {
|
|||||||
* @returns {Promise<Guild>}
|
* @returns {Promise<Guild>}
|
||||||
*/
|
*/
|
||||||
allowDMs(allow) {
|
allowDMs(allow) {
|
||||||
|
if (this.client.user.bot) return Promise.reject(new Error('FEATURE_USER_ONLY'));
|
||||||
const settings = this.client.user.settings;
|
const settings = this.client.user.settings;
|
||||||
if (allow) return settings.removeRestrictedGuild(this);
|
if (allow) return settings.removeRestrictedGuild(this);
|
||||||
else return settings.addRestrictedGuild(this);
|
else return settings.addRestrictedGuild(this);
|
||||||
@@ -802,11 +821,10 @@ class Guild extends Base {
|
|||||||
/**
|
/**
|
||||||
* Bans a user from the guild.
|
* Bans a user from the guild.
|
||||||
* @param {UserResolvable} user The user to ban
|
* @param {UserResolvable} user The user to ban
|
||||||
* @param {Object|number|string} [options] Ban options. If a number, the number of days to delete messages for, if a
|
* @param {Object} [options] Options for the ban
|
||||||
* string, the ban reason. Supplying an object allows you to do both.
|
|
||||||
* @param {number} [options.days=0] Number of days of messages to delete
|
* @param {number} [options.days=0] Number of days of messages to delete
|
||||||
* @param {string} [options.reason] Reason for banning
|
* @param {string} [options.reason] Reason for banning
|
||||||
* @returns {Promise<GuildMember|User|string>} Result object will be resolved as specifically as possible.
|
* @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
|
* 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.
|
* be resolved, the user ID will be the result.
|
||||||
* @example
|
* @example
|
||||||
@@ -979,7 +997,7 @@ class Guild extends Base {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new role in the guild with given information
|
* 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>
|
* <warn>The position will silently reset to 1 if an invalid one is provided, or none.</warn>
|
||||||
* @param {Object} [options] Options
|
* @param {Object} [options] Options
|
||||||
* @param {RoleData} [options.data] The data to update the role with
|
* @param {RoleData} [options.data] The data to update the role with
|
||||||
@@ -1000,7 +1018,7 @@ class Guild extends Base {
|
|||||||
* reason: 'we needed a role for Super Cool People',
|
* reason: 'we needed a role for Super Cool People',
|
||||||
* })
|
* })
|
||||||
* .then(role => console.log(`Created role ${role}`))
|
* .then(role => console.log(`Created role ${role}`))
|
||||||
* .catch(console.error)
|
* .catch(console.error);
|
||||||
*/
|
*/
|
||||||
createRole({ data = {}, reason } = {}) {
|
createRole({ data = {}, reason } = {}) {
|
||||||
if (data.color) data.color = Util.resolveColor(data.color);
|
if (data.color) data.color = Util.resolveColor(data.color);
|
||||||
@@ -1054,8 +1072,7 @@ class Guild extends Base {
|
|||||||
.then(emoji => this.client.actions.GuildEmojiCreate.handle(this, emoji).emoji);
|
.then(emoji => this.client.actions.GuildEmojiCreate.handle(this, emoji).emoji);
|
||||||
}
|
}
|
||||||
|
|
||||||
return DataResolver.resolveImage(attachment)
|
return DataResolver.resolveImage(attachment).then(image => this.createEmoji(image, name, { roles, reason }));
|
||||||
.then(image => this.createEmoji(image, name, { roles, reason }));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1123,19 +1140,44 @@ class Guild extends Base {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When concatenated with a string, this automatically concatenates the guild's name instead of the guild object.
|
* When concatenated with a string, this automatically returns the guild's name instead of the Guild object.
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
* @example
|
* @example
|
||||||
* // Logs: Hello from My Guild!
|
* // Logs: Hello from My Guild!
|
||||||
* console.log(`Hello from ${guild}!`);
|
* console.log(`Hello from ${guild}!`);
|
||||||
* @example
|
|
||||||
* // Logs: Hello from My Guild!
|
|
||||||
* console.log('Hello from ' + guild + '!');
|
|
||||||
*/
|
*/
|
||||||
toString() {
|
toString() {
|
||||||
return this.name;
|
return this.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a collection of this guild's roles, sorted by their position and IDs.
|
||||||
|
* @returns {Collection<Role>}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_sortedRoles() {
|
||||||
|
return Util.discordSort(this.roles);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.filter(c =>
|
||||||
|
c.type === channel.type && (category || c.parent === channel.parent)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles a user speaking update in a voice channel.
|
||||||
|
* @param {Snowflake} user ID of the user that the update is for
|
||||||
|
* @param {boolean} speaking Whether the user is speaking
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
_memberSpeakUpdate(user, speaking) {
|
_memberSpeakUpdate(user, speaking) {
|
||||||
const member = this.members.get(user);
|
const member = this.members.get(user);
|
||||||
if (member && member.speaking !== speaking) {
|
if (member && member.speaking !== speaking) {
|
||||||
@@ -1149,23 +1191,15 @@ class Guild extends Base {
|
|||||||
this.client.emit(Events.GUILD_MEMBER_SPEAKING, member, speaking);
|
this.client.emit(Events.GUILD_MEMBER_SPEAKING, member, speaking);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_sortedRoles() {
|
|
||||||
return Util.discordSort(this.roles);
|
|
||||||
}
|
|
||||||
|
|
||||||
_sortedChannels(channel) {
|
|
||||||
const category = channel.type === ChannelTypes.CATEGORY;
|
|
||||||
return Util.discordSort(this.channels.filter(c =>
|
|
||||||
c.type === channel.type && (category || c.parent === channel.parent)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Document this thing
|
||||||
class VoiceStateCollection extends Collection {
|
class VoiceStateCollection extends Collection {
|
||||||
constructor(guild) {
|
constructor(guild) {
|
||||||
super();
|
super();
|
||||||
this.guild = guild;
|
this.guild = guild;
|
||||||
}
|
}
|
||||||
|
|
||||||
set(id, voiceState) {
|
set(id, voiceState) {
|
||||||
const member = this.guild.members.get(id);
|
const member = this.guild.members.get(id);
|
||||||
if (member) {
|
if (member) {
|
||||||
|
|||||||
@@ -2,6 +2,24 @@ const Collection = require('../util/Collection');
|
|||||||
const Snowflake = require('../util/Snowflake');
|
const Snowflake = require('../util/Snowflake');
|
||||||
const Webhook = require('./Webhook');
|
const Webhook = require('./Webhook');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The target type of an entry, e.g. `GUILD`. Here are the available types:
|
||||||
|
* * GUILD
|
||||||
|
* * CHANNEL
|
||||||
|
* * USER
|
||||||
|
* * ROLE
|
||||||
|
* * INVITE
|
||||||
|
* * WEBHOOK
|
||||||
|
* * EMOJI
|
||||||
|
* * MESSAGE
|
||||||
|
* @typedef {string} AuditLogTargetType
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key mirror of all available audit log targets.
|
||||||
|
* @name GuildAuditLogs.Targets
|
||||||
|
* @type {AuditLogTargetType}
|
||||||
|
*/
|
||||||
const Targets = {
|
const Targets = {
|
||||||
ALL: 'ALL',
|
ALL: 'ALL',
|
||||||
GUILD: 'GUILD',
|
GUILD: 'GUILD',
|
||||||
@@ -15,6 +33,43 @@ const Targets = {
|
|||||||
UNKNOWN: 'UNKNOWN',
|
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
|
||||||
|
* * 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: 50
|
||||||
|
* * EMOJI_CREATE: 60
|
||||||
|
* * EMOJI_UPDATE: 61
|
||||||
|
* * EMOJI_DELETE: 62
|
||||||
|
* * MESSAGE_DELETE: 72
|
||||||
|
* @typedef {?number|string} AuditLogAction
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All available actions keyed under their names to their numeric values.
|
||||||
|
* @name GuildAuditLogs.Actions
|
||||||
|
* @type {AuditLogAction}
|
||||||
|
*/
|
||||||
const Actions = {
|
const Actions = {
|
||||||
ALL: null,
|
ALL: null,
|
||||||
GUILD_UPDATE: 1,
|
GUILD_UPDATE: 1,
|
||||||
@@ -85,20 +140,7 @@ class GuildAuditLogs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The target type of an entry, e.g. `GUILD`. Here are the available types:
|
* The target of an entry. It can be one of:
|
||||||
* * GUILD
|
|
||||||
* * CHANNEL
|
|
||||||
* * USER
|
|
||||||
* * ROLE
|
|
||||||
* * INVITE
|
|
||||||
* * WEBHOOK
|
|
||||||
* * EMOJI
|
|
||||||
* * MESSAGE
|
|
||||||
* @typedef {string} TargetType
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The target for an audit log entry. It can be one of:
|
|
||||||
* * A guild
|
* * A guild
|
||||||
* * A user
|
* * A user
|
||||||
* * A role
|
* * A role
|
||||||
@@ -106,13 +148,13 @@ class GuildAuditLogs {
|
|||||||
* * An invite
|
* * An invite
|
||||||
* * A webhook
|
* * A webhook
|
||||||
* * An object where the keys represent either the new value or the old value
|
* * An object where the keys represent either the new value or the old value
|
||||||
* @typedef {?Object|Guild|User|Role|Emoji|Invite|Webhook} EntryTarget
|
* @typedef {?Object|Guild|User|Role|Emoji|Invite|Webhook} AuditLogEntryTarget
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds the target type from the entry action.
|
* Finds the target type from the entry action.
|
||||||
* @param {number} target The action target
|
* @param {AuditLogAction} target The action target
|
||||||
* @returns {?string}
|
* @returns {AuditLogTargetType}
|
||||||
*/
|
*/
|
||||||
static targetType(target) {
|
static targetType(target) {
|
||||||
if (target < 10) return Targets.GUILD;
|
if (target < 10) return Targets.GUILD;
|
||||||
@@ -131,13 +173,14 @@ class GuildAuditLogs {
|
|||||||
* * CREATE
|
* * CREATE
|
||||||
* * DELETE
|
* * DELETE
|
||||||
* * UPDATE
|
* * UPDATE
|
||||||
* @typedef {string} ActionType
|
* * ALL
|
||||||
|
* @typedef {string} AuditLogActionType
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds the action type from the entry action.
|
* Finds the action type from the entry action.
|
||||||
* @param {string} action The action target
|
* @param {AuditLogAction} action The action target
|
||||||
* @returns {string}
|
* @returns {AuditLogActionType}
|
||||||
*/
|
*/
|
||||||
static actionType(action) {
|
static actionType(action) {
|
||||||
if ([
|
if ([
|
||||||
@@ -187,19 +230,19 @@ class GuildAuditLogsEntry {
|
|||||||
const targetType = GuildAuditLogs.targetType(data.action_type);
|
const targetType = GuildAuditLogs.targetType(data.action_type);
|
||||||
/**
|
/**
|
||||||
* The target type of this entry
|
* The target type of this entry
|
||||||
* @type {TargetType}
|
* @type {AuditLogTargetType}
|
||||||
*/
|
*/
|
||||||
this.targetType = targetType;
|
this.targetType = targetType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The action type of this entry
|
* The action type of this entry
|
||||||
* @type {ActionType}
|
* @type {AuditLogActionType}
|
||||||
*/
|
*/
|
||||||
this.actionType = GuildAuditLogs.actionType(data.action_type);
|
this.actionType = GuildAuditLogs.actionType(data.action_type);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specific action type of this entry
|
* Specific action type of this entry in its string presentation
|
||||||
* @type {string}
|
* @type {AuditLogAction}
|
||||||
*/
|
*/
|
||||||
this.action = Object.keys(Actions).find(k => Actions[k] === data.action_type);
|
this.action = Object.keys(Actions).find(k => Actions[k] === data.action_type);
|
||||||
|
|
||||||
@@ -271,7 +314,7 @@ class GuildAuditLogsEntry {
|
|||||||
if (targetType === Targets.UNKNOWN) {
|
if (targetType === Targets.UNKNOWN) {
|
||||||
/**
|
/**
|
||||||
* The target of this entry
|
* The target of this entry
|
||||||
* @type {EntryTarget}
|
* @type {AuditLogEntryTarget}
|
||||||
*/
|
*/
|
||||||
this.target = this.changes.reduce((o, c) => {
|
this.target = this.changes.reduce((o, c) => {
|
||||||
o[c.key] = c.new || c.old;
|
o[c.key] = c.new || c.old;
|
||||||
|
|||||||
@@ -248,6 +248,7 @@ class GuildChannel extends Channel {
|
|||||||
* @property {string} [name] The name of the channel
|
* @property {string} [name] The name of the channel
|
||||||
* @property {number} [position] The position of the channel
|
* @property {number} [position] The position of the channel
|
||||||
* @property {string} [topic] The topic of the text 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} [bitrate] The bitrate of the voice channel
|
||||||
* @property {number} [userLimit] The user limit of the voice channel
|
* @property {number} [userLimit] The user limit of the voice channel
|
||||||
* @property {Snowflake} [parentID] The parent ID of the channel
|
* @property {Snowflake} [parentID] The parent ID of the channel
|
||||||
@@ -290,8 +291,9 @@ class GuildChannel extends Channel {
|
|||||||
data: {
|
data: {
|
||||||
name: (data.name || this.name).trim(),
|
name: (data.name || this.name).trim(),
|
||||||
topic: data.topic,
|
topic: data.topic,
|
||||||
|
nsfw: data.nsfw,
|
||||||
bitrate: data.bitrate || (this.bitrate ? this.bitrate * 1000 : undefined),
|
bitrate: data.bitrate || (this.bitrate ? this.bitrate * 1000 : undefined),
|
||||||
user_limit: data.userLimit != null ? data.userLimit : this.userLimit, // eslint-disable-line eqeqeq
|
user_limit: typeof data.userLimit !== 'undefined' ? data.userLimit : this.userLimit,
|
||||||
parent_id: data.parentID,
|
parent_id: data.parentID,
|
||||||
lock_permissions: data.lockPermissions,
|
lock_permissions: data.lockPermissions,
|
||||||
permission_overwrites: data.permissionOverwrites,
|
permission_overwrites: data.permissionOverwrites,
|
||||||
@@ -491,11 +493,8 @@ class GuildChannel extends Channel {
|
|||||||
* When concatenated with a string, this automatically returns the channel's mention instead of the Channel object.
|
* When concatenated with a string, this automatically returns the channel's mention instead of the Channel object.
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
* @example
|
* @example
|
||||||
* // Outputs: Hello from #general
|
* // Logs: Hello from <#123456789012345678>!
|
||||||
* console.log(`Hello from ${channel}`);
|
* console.log(`Hello from ${channel}!`);
|
||||||
* @example
|
|
||||||
* // Outputs: Hello from #general
|
|
||||||
* console.log('Hello from ' + channel);
|
|
||||||
*/
|
*/
|
||||||
toString() {
|
toString() {
|
||||||
return `<#${this.id}>`;
|
return `<#${this.id}>`;
|
||||||
|
|||||||
@@ -77,36 +77,42 @@ class GuildMember extends Base {
|
|||||||
/**
|
/**
|
||||||
* Whether this member is deafened server-wide
|
* Whether this member is deafened server-wide
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
|
* @readonly
|
||||||
*/
|
*/
|
||||||
get serverDeaf() { return this.voiceState.deaf; }
|
get serverDeaf() { return this.voiceState.deaf; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether this member is muted server-wide
|
* Whether this member is muted server-wide
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
|
* @readonly
|
||||||
*/
|
*/
|
||||||
get serverMute() { return this.voiceState.mute; }
|
get serverMute() { return this.voiceState.mute; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether this member is self-muted
|
* Whether this member is self-muted
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
|
* @readonly
|
||||||
*/
|
*/
|
||||||
get selfMute() { return this.voiceState.self_mute; }
|
get selfMute() { return this.voiceState.self_mute; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether this member is self-deafened
|
* Whether this member is self-deafened
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
|
* @readonly
|
||||||
*/
|
*/
|
||||||
get selfDeaf() { return this.voiceState.self_deaf; }
|
get selfDeaf() { return this.voiceState.self_deaf; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The voice session ID of this member (if any)
|
* The voice session ID of this member (if any)
|
||||||
* @type {?Snowflake}
|
* @type {?Snowflake}
|
||||||
|
* @readonly
|
||||||
*/
|
*/
|
||||||
get voiceSessionID() { return this.voiceState.session_id; }
|
get voiceSessionID() { return this.voiceState.session_id; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The voice channel ID of this member, (if any)
|
* The voice channel ID of this member, (if any)
|
||||||
* @type {?Snowflake}
|
* @type {?Snowflake}
|
||||||
|
* @readonly
|
||||||
*/
|
*/
|
||||||
get voiceChannelID() { return this.voiceState.channel_id; }
|
get voiceChannelID() { return this.voiceState.channel_id; }
|
||||||
|
|
||||||
@@ -125,7 +131,7 @@ class GuildMember extends Base {
|
|||||||
* @readonly
|
* @readonly
|
||||||
*/
|
*/
|
||||||
get presence() {
|
get presence() {
|
||||||
return this.frozenPresence || this.guild.presences.get(this.id) || new Presence();
|
return this.frozenPresence || this.guild.presences.get(this.id) || new Presence(this.client);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -294,19 +300,13 @@ class GuildMember extends Base {
|
|||||||
/**
|
/**
|
||||||
* Checks if any of the member's roles have a permission.
|
* Checks if any of the member's roles have a permission.
|
||||||
* @param {PermissionResolvable|PermissionResolvable[]} permission Permission(s) to check for
|
* @param {PermissionResolvable|PermissionResolvable[]} permission Permission(s) to check for
|
||||||
* @param {boolean} [explicit=false] Whether to require the role to explicitly have the exact permission
|
* @param {boolean} [checkAdmin=true] Whether to allow the administrator permission to override
|
||||||
* **(deprecated)**
|
* @param {boolean} [checkOwner=true] Whether to allow being the guild's owner to override
|
||||||
* @param {boolean} [checkAdmin] Whether to allow the administrator permission to override
|
|
||||||
* (takes priority over `explicit`)
|
|
||||||
* @param {boolean} [checkOwner] Whether to allow being the guild's owner to override
|
|
||||||
* (takes priority over `explicit`)
|
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
hasPermission(permission, explicit = false, checkAdmin, checkOwner) {
|
hasPermission(permission, checkAdmin = true, checkOwner = true) {
|
||||||
if (typeof checkAdmin === 'undefined') checkAdmin = !explicit;
|
|
||||||
if (typeof checkOwner === 'undefined') checkOwner = !explicit;
|
|
||||||
if (checkOwner && this.user.id === this.guild.ownerID) return true;
|
if (checkOwner && this.user.id === this.guild.ownerID) return true;
|
||||||
return this.roles.some(r => r.permissions.has(permission, undefined, checkAdmin));
|
return this.roles.some(r => r.permissions.has(permission, checkAdmin));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -521,8 +521,7 @@ class GuildMember extends Base {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Bans this guild member.
|
* Bans this guild member.
|
||||||
* @param {Object|number|string} [options] Ban options. If a number, the number of days to delete messages for, if a
|
* @param {Object} [options] Options for the ban
|
||||||
* string, the ban reason. Supplying an object allows you to do both.
|
|
||||||
* @param {number} [options.days=0] Number of days of messages to delete
|
* @param {number} [options.days=0] Number of days of messages to delete
|
||||||
* @param {string} [options.reason] Reason for banning
|
* @param {string} [options.reason] Reason for banning
|
||||||
* @returns {Promise<GuildMember>}
|
* @returns {Promise<GuildMember>}
|
||||||
@@ -535,10 +534,10 @@ class GuildMember extends Base {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When concatenated with a string, this automatically concatenates the user's mention instead of the Member object.
|
* When concatenated with a string, this automatically returns the user's mention instead of the GuildMember object.
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
* @example
|
* @example
|
||||||
* // Logs: Hello from <@123456789>!
|
* // Logs: Hello from <@123456789012345678>!
|
||||||
* console.log(`Hello from ${member}!`);
|
* console.log(`Hello from ${member}!`);
|
||||||
*/
|
*/
|
||||||
toString() {
|
toString() {
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ const Collection = require('../util/Collection');
|
|||||||
const ReactionStore = require('../stores/ReactionStore');
|
const ReactionStore = require('../stores/ReactionStore');
|
||||||
const { MessageTypes } = require('../util/Constants');
|
const { MessageTypes } = require('../util/Constants');
|
||||||
const Permissions = require('../util/Permissions');
|
const Permissions = require('../util/Permissions');
|
||||||
const GuildMember = require('./GuildMember');
|
|
||||||
const Base = require('./Base');
|
const Base = require('./Base');
|
||||||
const { Error, TypeError } = require('../errors');
|
const { Error, TypeError } = require('../errors');
|
||||||
|
const { createMessage } = require('./shared');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a message on Discord.
|
* Represents a message on Discord.
|
||||||
@@ -368,41 +368,22 @@ class Message extends Base {
|
|||||||
* .then(msg => console.log(`Updated the content of a message from ${msg.author}`))
|
* .then(msg => console.log(`Updated the content of a message from ${msg.author}`))
|
||||||
* .catch(console.error);
|
* .catch(console.error);
|
||||||
*/
|
*/
|
||||||
edit(content, options) {
|
async edit(content, options) {
|
||||||
if (!options && typeof content === 'object' && !(content instanceof Array)) {
|
if (!options && typeof content === 'object' && !(content instanceof Array)) {
|
||||||
options = content;
|
options = content;
|
||||||
content = '';
|
content = null;
|
||||||
} else if (!options) {
|
} else if (!options) {
|
||||||
options = {};
|
options = {};
|
||||||
}
|
}
|
||||||
if (options instanceof Embed) options = { embed: options };
|
if (!options.content) options.content = content;
|
||||||
|
|
||||||
if (typeof options.content !== 'undefined') content = options.content;
|
const { data, files } = await createMessage(this, options);
|
||||||
|
|
||||||
if (typeof content !== 'undefined') content = Util.resolveString(content);
|
|
||||||
|
|
||||||
let { embed, code, reply } = options;
|
|
||||||
|
|
||||||
if (embed) embed = new Embed(embed)._apiTransform();
|
|
||||||
|
|
||||||
// Wrap everything in a code block
|
|
||||||
if (typeof code !== 'undefined' && (typeof code !== 'boolean' || code === true)) {
|
|
||||||
content = Util.escapeMarkdown(Util.resolveString(content), true);
|
|
||||||
content = `\`\`\`${typeof code !== 'boolean' ? code || '' : ''}\n${content}\n\`\`\``;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the reply prefix
|
|
||||||
if (reply && this.channel.type !== 'dm') {
|
|
||||||
const id = this.client.users.resolveID(reply);
|
|
||||||
const mention = `<@${reply instanceof GuildMember && reply.nickname ? '!' : ''}${id}>`;
|
|
||||||
content = `${mention}${content ? `, ${content}` : ''}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.client.api.channels[this.channel.id].messages[this.id]
|
return this.client.api.channels[this.channel.id].messages[this.id]
|
||||||
.patch({ data: { content, embed } })
|
.patch({ data, files })
|
||||||
.then(data => {
|
.then(d => {
|
||||||
const clone = this._clone();
|
const clone = this._clone();
|
||||||
clone._patch(data);
|
clone._patch(d);
|
||||||
return clone;
|
return clone;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,12 +132,12 @@ class MessageEmbed {
|
|||||||
proxyIconURL: data.footer.proxyIconURL || data.footer.proxy_icon_url,
|
proxyIconURL: data.footer.proxyIconURL || data.footer.proxy_icon_url,
|
||||||
} : null;
|
} : null;
|
||||||
|
|
||||||
/**
|
|
||||||
* The files of this embed
|
|
||||||
* @type {?Object}
|
|
||||||
* @property {Array<FileOptions|string|MessageAttachment>} files Files to attach
|
|
||||||
*/
|
|
||||||
if (data.files) {
|
if (data.files) {
|
||||||
|
/**
|
||||||
|
* The files of this embed
|
||||||
|
* @type {?Object}
|
||||||
|
* @property {Array<FileOptions|string|MessageAttachment>} files Files to attach
|
||||||
|
*/
|
||||||
this.files = data.files.map(file => {
|
this.files = data.files.map(file => {
|
||||||
if (file instanceof MessageAttachment) {
|
if (file instanceof MessageAttachment) {
|
||||||
return typeof file.file === 'string' ? file.file : Util.cloneObject(file.file);
|
return typeof file.file === 'string' ? file.file : Util.cloneObject(file.file);
|
||||||
@@ -158,7 +158,7 @@ class MessageEmbed {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The hexadecimal version of the embed color, with a leading hash
|
* The hexadecimal version of the embed color, with a leading hash
|
||||||
* @type {string}
|
* @type {?string}
|
||||||
* @readonly
|
* @readonly
|
||||||
*/
|
*/
|
||||||
get hexColor() {
|
get hexColor() {
|
||||||
|
|||||||
@@ -117,19 +117,27 @@ class MessageMentions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a user is mentioned.
|
* Checks if a user, guild member, role, or channel is mentioned.
|
||||||
* Takes into account user mentions, role mentions, and @everyone/@here mentions.
|
* Takes into account user mentions, role mentions, and @everyone/@here mentions.
|
||||||
* @param {UserResolvable|GuildMember|Role|GuildChannel} data User/GuildMember/Role/Channel to check
|
* @param {UserResolvable|GuildMember|Role|GuildChannel} data User/GuildMember/Role/Channel to check
|
||||||
* @param {boolean} [strict=true] If role mentions and everyone/here mentions should be included
|
* @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}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
has(data, strict = true) {
|
has(data, { ignoreDirect = false, ignoreRoles = false, ignoreEveryone = false } = {}) {
|
||||||
if (strict && this.everyone) return true;
|
if (!ignoreEveryone && this.everyone) return true;
|
||||||
if (strict && data instanceof GuildMember) {
|
if (!ignoreRoles && data instanceof GuildMember) {
|
||||||
for (const role of this.roles.values()) if (data.roles.has(role.id)) return true;
|
for (const role of this.roles.values()) if (data.roles.has(role.id)) return true;
|
||||||
}
|
}
|
||||||
const id = data.id || data;
|
|
||||||
return this.users.has(id) || this.channels.has(id) || this.roles.has(id);
|
if (!ignoreDirect) {
|
||||||
|
const id = data.id || data;
|
||||||
|
return this.users.has(id) || this.channels.has(id) || this.roles.has(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
const Collection = require('../util/Collection');
|
|
||||||
const Emoji = require('./Emoji');
|
const Emoji = require('./Emoji');
|
||||||
const ReactionEmoji = require('./ReactionEmoji');
|
const ReactionEmoji = require('./ReactionEmoji');
|
||||||
|
const ReactionUserStore = require('../stores/ReactionUserStore');
|
||||||
const { Error } = require('../errors');
|
const { Error } = require('../errors');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -28,9 +28,9 @@ class MessageReaction {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The users that have given this reaction, mapped by their ID
|
* The users that have given this reaction, mapped by their ID
|
||||||
* @type {Collection<Snowflake, User>}
|
* @type {ReactionUserStore<Snowflake, User>}
|
||||||
*/
|
*/
|
||||||
this.users = new Collection();
|
this.users = new ReactionUserStore(client, undefined, this);
|
||||||
|
|
||||||
this._emoji = new ReactionEmoji(this, data.emoji.name, data.emoji.id);
|
this._emoji = new ReactionEmoji(this, data.emoji.name, data.emoji.id);
|
||||||
}
|
}
|
||||||
@@ -77,26 +77,6 @@ class MessageReaction {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.after] Limit fetching users to those with an id greater than the supplied id
|
|
||||||
* @returns {Promise<Collection<Snowflake, User>>}
|
|
||||||
*/
|
|
||||||
async fetchUsers({ limit = 100, after } = {}) {
|
|
||||||
const message = this.message;
|
|
||||||
const users = await message.client.api.channels[message.channel.id].messages[message.id]
|
|
||||||
.reactions[this.emoji.identifier]
|
|
||||||
.get({ query: { limit, after } });
|
|
||||||
for (const rawUser of users) {
|
|
||||||
const user = message.client.users.create(rawUser);
|
|
||||||
this.users.set(user.id, user);
|
|
||||||
}
|
|
||||||
this.count = this.users.size;
|
|
||||||
return this.users;
|
|
||||||
}
|
|
||||||
|
|
||||||
_add(user) {
|
_add(user) {
|
||||||
if (!this.users.has(user.id)) {
|
if (!this.users.has(user.id)) {
|
||||||
this.users.set(user.id, user);
|
this.users.set(user.id, user);
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class Presence {
|
|||||||
* * **`dnd`** - user is in Do Not Disturb
|
* * **`dnd`** - user is in Do Not Disturb
|
||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
this.status = data.status || this.status;
|
this.status = data.status || this.status || 'offline';
|
||||||
|
|
||||||
const activity = data.game || data.activity;
|
const activity = data.game || data.activity;
|
||||||
/**
|
/**
|
||||||
@@ -38,7 +38,7 @@ class Presence {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether this presence is equal to another
|
* Whether this presence is equal to another.
|
||||||
* @param {Presence} presence The presence to compare with
|
* @param {Presence} presence The presence to compare with
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
@@ -160,21 +160,22 @@ class RichPresenceAssets {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* ID of the large image asset
|
* ID of the large image asset
|
||||||
* @type {?string}
|
* @type {?Snowflake}
|
||||||
*/
|
*/
|
||||||
this.largeImage = assets.large_image || null;
|
this.largeImage = assets.large_image || null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ID of the small image asset
|
* ID of the small image asset
|
||||||
* @type {?string}
|
* @type {?Snowflake}
|
||||||
*/
|
*/
|
||||||
this.smallImage = assets.small_image || null;
|
this.smallImage = assets.small_image || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the URL of the small image asset
|
* Gets the URL of the small image asset
|
||||||
* @param {string} format Format of the image
|
* @param {Object} [options] Options for the image url
|
||||||
* @param {number} size Size of the image
|
* @param {string} [options.format] Format of the image
|
||||||
|
* @param {number} [options.size] Size of the image
|
||||||
* @returns {?string} The small image URL
|
* @returns {?string} The small image URL
|
||||||
*/
|
*/
|
||||||
smallImageURL({ format, size } = {}) {
|
smallImageURL({ format, size } = {}) {
|
||||||
@@ -185,8 +186,9 @@ class RichPresenceAssets {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the URL of the large image asset
|
* Gets the URL of the large image asset
|
||||||
* @param {string} format Format of the image
|
* @param {Object} [options] Options for the image url
|
||||||
* @param {number} size Size of the image
|
* @param {string} [options.format] Format of the image
|
||||||
|
* @param {number} [options.size] Size of the image
|
||||||
* @returns {?string} The large image URL
|
* @returns {?string} The large image URL
|
||||||
*/
|
*/
|
||||||
largeImageURL({ format, size } = {}) {
|
largeImageURL({ format, size } = {}) {
|
||||||
|
|||||||
@@ -83,7 +83,17 @@ class ReactionCollector extends Collector {
|
|||||||
* @returns {?Snowflake|string}
|
* @returns {?Snowflake|string}
|
||||||
*/
|
*/
|
||||||
dispose(reaction) {
|
dispose(reaction) {
|
||||||
return reaction.message.id === this.message.id && !reaction.count ? ReactionCollector.key(reaction) : null;
|
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
|
||||||
|
*/
|
||||||
|
if (this.collected.has(reaction)) this.emit('remove', reaction);
|
||||||
|
return reaction.count ? null : ReactionCollector.key(reaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -35,11 +35,12 @@ class ReactionEmoji {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the text required to form a graphical emoji on Discord.
|
* When concatenated with a string, this automatically returns the text required to form a graphical emoji on Discord
|
||||||
|
* instead of the ReactionEmoji object.
|
||||||
|
* @returns {string}
|
||||||
* @example
|
* @example
|
||||||
* // Send the emoji used in a reaction to the channel the reaction is part of
|
* // Send the emoji used in a reaction to the channel the reaction is part of
|
||||||
* reaction.message.channel.send(`The emoji used is ${reaction.emoji}`);
|
* reaction.message.channel.send(`The emoji used was: ${reaction.emoji}`);
|
||||||
* @returns {string}
|
|
||||||
*/
|
*/
|
||||||
toString() {
|
toString() {
|
||||||
return this.id ? `<:${this.name}:${this.id}>` : this.name;
|
return this.id ? `<:${this.name}:${this.id}>` : this.name;
|
||||||
|
|||||||
@@ -332,8 +332,11 @@ class Role extends Base {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When concatenated with a string, this automatically concatenates the role mention rather than the Role object.
|
* When concatenated with a string, this automatically returns the role's mention instead of the Role object.
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
|
* @example
|
||||||
|
* // Logs: Role: <@&123456789012345678>
|
||||||
|
* console.log(`Role: ${role}`);
|
||||||
*/
|
*/
|
||||||
toString() {
|
toString() {
|
||||||
if (this.id === this.guild.id) return '@everyone';
|
if (this.id === this.guild.id) return '@everyone';
|
||||||
|
|||||||
@@ -38,6 +38,16 @@ class TextChannel extends GuildChannel {
|
|||||||
if (data.messages) for (const message of data.messages) this.messages.create(message);
|
if (data.messages) for (const message of data.messages) this.messages.create(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
* Fetches all webhooks for the channel.
|
||||||
* @returns {Promise<Collection<Snowflake, Webhook>>}
|
* @returns {Promise<Collection<Snowflake, Webhook>>}
|
||||||
|
|||||||
@@ -20,6 +20,13 @@ class User extends Base {
|
|||||||
*/
|
*/
|
||||||
this.id = data.id;
|
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);
|
this._patch(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,18 +47,11 @@ class User extends Base {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The ID of the user's avatar
|
* The ID of the user's avatar
|
||||||
* @type {string}
|
* @type {?string}
|
||||||
* @name User#avatar
|
* @name User#avatar
|
||||||
*/
|
*/
|
||||||
if (typeof data.avatar !== 'undefined') this.avatar = data.avatar;
|
if (typeof data.avatar !== 'undefined') this.avatar = data.avatar;
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether or not the user is a bot
|
|
||||||
* @type {boolean}
|
|
||||||
* @name User#bot
|
|
||||||
*/
|
|
||||||
if (typeof this.bot === 'undefined' && typeof data.bot !== 'undefined') this.bot = Boolean(data.bot);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ID of the last message sent by the user, if one was sent
|
* The ID of the last message sent by the user, if one was sent
|
||||||
* @type {?Snowflake}
|
* @type {?Snowflake}
|
||||||
@@ -63,8 +63,6 @@ class User extends Base {
|
|||||||
* @type {?Message}
|
* @type {?Message}
|
||||||
*/
|
*/
|
||||||
this.lastMessage = null;
|
this.lastMessage = null;
|
||||||
|
|
||||||
if (data.token) this.client.token = data.token;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -95,7 +93,7 @@ class User extends Base {
|
|||||||
for (const guild of this.client.guilds.values()) {
|
for (const guild of this.client.guilds.values()) {
|
||||||
if (guild.presences.has(this.id)) return guild.presences.get(this.id);
|
if (guild.presences.has(this.id)) return guild.presences.get(this.id);
|
||||||
}
|
}
|
||||||
return new Presence();
|
return new Presence(this.client);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -250,10 +248,10 @@ class User extends Base {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When concatenated with a string, this automatically concatenates the user's mention instead of the User object.
|
* When concatenated with a string, this automatically returns the user's mention instead of the User object.
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
* @example
|
* @example
|
||||||
* // logs: Hello from <@123456789>!
|
* // Logs: Hello from <@123456789012345678>!
|
||||||
* console.log(`Hello from ${user}!`);
|
* console.log(`Hello from ${user}!`);
|
||||||
*/
|
*/
|
||||||
toString() {
|
toString() {
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
const Util = require('../util/Util');
|
|
||||||
const DataResolver = require('../util/DataResolver');
|
const DataResolver = require('../util/DataResolver');
|
||||||
const Embed = require('./MessageEmbed');
|
const { createMessage } = require('./shared');
|
||||||
const MessageAttachment = require('./MessageAttachment');
|
|
||||||
const MessageEmbed = require('./MessageEmbed');
|
|
||||||
const { browser } = require('../util/Constants');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a webhook.
|
* Represents a webhook.
|
||||||
@@ -79,7 +75,6 @@ class Webhook {
|
|||||||
* (see [here](https://discordapp.com/developers/docs/resources/channel#embed-object) for more details)
|
* (see [here](https://discordapp.com/developers/docs/resources/channel#embed-object) for more details)
|
||||||
* @property {boolean} [disableEveryone=this.client.options.disableEveryone] Whether or not @everyone and @here
|
* @property {boolean} [disableEveryone=this.client.options.disableEveryone] Whether or not @everyone and @here
|
||||||
* should be replaced with plain-text
|
* should be replaced with plain-text
|
||||||
* @property {FileOptions|BufferResolvable} [file] A file to send with the message
|
|
||||||
* @property {FileOptions[]|string[]} [files] Files to send with the message
|
* @property {FileOptions[]|string[]} [files] Files to send with the message
|
||||||
* @property {string|boolean} [code] Language for optional codeblock formatting to apply
|
* @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
|
* @property {boolean|SplitOptions} [split=false] Whether or not the message should be split into multiple messages if
|
||||||
@@ -99,115 +94,37 @@ class Webhook {
|
|||||||
* .catch(console.error);
|
* .catch(console.error);
|
||||||
*/
|
*/
|
||||||
/* eslint-enable max-len */
|
/* eslint-enable max-len */
|
||||||
send(content, options) { // eslint-disable-line complexity
|
async send(content, options) { // eslint-disable-line complexity
|
||||||
if (!options && typeof content === 'object' && !(content instanceof Array)) {
|
if (!options && typeof content === 'object' && !(content instanceof Array)) {
|
||||||
options = content;
|
options = content;
|
||||||
content = '';
|
content = null;
|
||||||
} else if (!options) {
|
} else if (!options) {
|
||||||
options = {};
|
options = {};
|
||||||
}
|
}
|
||||||
|
if (!options.content) options.content = content;
|
||||||
|
|
||||||
if (options instanceof MessageAttachment) options = { files: [options.file] };
|
const { data, files } = await createMessage(this, options);
|
||||||
if (options instanceof MessageEmbed) options = { embeds: [options] };
|
|
||||||
if (options.embed) options = { embeds: [options.embed] };
|
|
||||||
|
|
||||||
if (content instanceof Array || options instanceof Array) {
|
if (data.content instanceof Array) {
|
||||||
const which = content instanceof Array ? content : options;
|
const messages = [];
|
||||||
const attachments = which.filter(item => item instanceof MessageAttachment);
|
for (let i = 0; i < data.content.length; i++) {
|
||||||
const embeds = which.filter(item => item instanceof MessageEmbed);
|
const opt = i === data.content.length - 1 ? { embeds: data.embeds, files } : {};
|
||||||
if (attachments.length) options = { files: attachments };
|
Object.assign(opt, { avatarURL: data.avatar_url, content: data.content[i], username: data.username });
|
||||||
if (embeds.length) options = { embeds };
|
// eslint-disable-next-line no-await-in-loop
|
||||||
if ((embeds.length || attachments.length) && content instanceof Array) content = '';
|
const message = await this.send(data.content[i], opt);
|
||||||
}
|
messages.push(message);
|
||||||
|
|
||||||
if (!options.username) options.username = this.name;
|
|
||||||
if (options.avatarURL) {
|
|
||||||
options.avatar_url = options.avatarURL;
|
|
||||||
options.avatarURL = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (content) {
|
|
||||||
content = Util.resolveString(content);
|
|
||||||
let { split, code, disableEveryone } = options;
|
|
||||||
if (split && typeof split !== 'object') split = {};
|
|
||||||
if (typeof code !== 'undefined' && (typeof code !== 'boolean' || code === true)) {
|
|
||||||
content = Util.escapeMarkdown(content, true);
|
|
||||||
content = `\`\`\`${typeof code !== 'boolean' ? code || '' : ''}\n${content}\n\`\`\``;
|
|
||||||
if (split) {
|
|
||||||
split.prepend = `\`\`\`${typeof code !== 'boolean' ? code || '' : ''}\n`;
|
|
||||||
split.append = '\n```';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (disableEveryone || (typeof disableEveryone === 'undefined' && this.client.options.disableEveryone)) {
|
return messages;
|
||||||
content = content.replace(/@(everyone|here)/g, '@\u200b$1');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (split) content = Util.splitMessage(content, split);
|
|
||||||
}
|
|
||||||
options.content = content;
|
|
||||||
|
|
||||||
if (options.embeds) options.embeds = options.embeds.map(embed => new Embed(embed)._apiTransform());
|
|
||||||
|
|
||||||
if (options.files) {
|
|
||||||
for (let i = 0; i < options.files.length; i++) {
|
|
||||||
let file = options.files[i];
|
|
||||||
if (typeof file === 'string' || (!browser && Buffer.isBuffer(file))) file = { attachment: file };
|
|
||||||
if (!file.name) {
|
|
||||||
if (typeof file.attachment === 'string') {
|
|
||||||
file.name = Util.basename(file.attachment);
|
|
||||||
} else if (file.attachment && file.attachment.path) {
|
|
||||||
file.name = Util.basename(file.attachment.path);
|
|
||||||
} else if (file instanceof MessageAttachment) {
|
|
||||||
file = { attachment: file.file, name: Util.basename(file.file) || 'file.jpg' };
|
|
||||||
} else {
|
|
||||||
file.name = 'file.jpg';
|
|
||||||
}
|
|
||||||
} else if (file instanceof MessageAttachment) {
|
|
||||||
file = file.file;
|
|
||||||
}
|
|
||||||
options.files[i] = file;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.all(options.files.map(file =>
|
|
||||||
DataResolver.resolveFile(file.attachment).then(resource => {
|
|
||||||
file.file = resource;
|
|
||||||
return file;
|
|
||||||
})
|
|
||||||
)).then(files => this.client.api.webhooks(this.id, this.token).post({
|
|
||||||
data: options,
|
|
||||||
query: { wait: true },
|
|
||||||
files,
|
|
||||||
auth: false,
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (content instanceof Array) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const messages = [];
|
|
||||||
(function sendChunk() {
|
|
||||||
const opt = content.length ? null : { embeds: options.embeds, files: options.files };
|
|
||||||
this.client.api.webhooks(this.id, this.token).post({
|
|
||||||
data: { content: content.shift(), opt },
|
|
||||||
query: { wait: true },
|
|
||||||
auth: false,
|
|
||||||
})
|
|
||||||
.then(message => {
|
|
||||||
messages.push(message);
|
|
||||||
if (content.length === 0) return resolve(messages);
|
|
||||||
return sendChunk.call(this);
|
|
||||||
})
|
|
||||||
.catch(reject);
|
|
||||||
}.call(this));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.client.api.webhooks(this.id, this.token).post({
|
return this.client.api.webhooks(this.id, this.token).post({
|
||||||
data: options,
|
data, files,
|
||||||
query: { wait: true },
|
query: { wait: true },
|
||||||
auth: false,
|
auth: false,
|
||||||
}).then(data => {
|
}).then(d => {
|
||||||
if (!this.client.channels) return data;
|
if (!this.client.channels) return d;
|
||||||
return this.client.channels.get(data.channel_id).messages.create(data, false);
|
return this.client.channels.get(d.channel_id).messages.create(d, false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
const MessageCollector = require('../MessageCollector');
|
const MessageCollector = require('../MessageCollector');
|
||||||
const Shared = require('../shared');
|
const Shared = require('../shared');
|
||||||
const Util = require('../../util/Util');
|
|
||||||
const { browser } = require('../../util/Constants');
|
|
||||||
const Snowflake = require('../../util/Snowflake');
|
const Snowflake = require('../../util/Snowflake');
|
||||||
const Collection = require('../../util/Collection');
|
const Collection = require('../../util/Collection');
|
||||||
const DataResolver = require('../../util/DataResolver');
|
|
||||||
const MessageAttachment = require('../../structures/MessageAttachment');
|
|
||||||
const MessageEmbed = require('../../structures/MessageEmbed');
|
|
||||||
const { RangeError, TypeError } = require('../../errors');
|
const { RangeError, TypeError } = require('../../errors');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -80,61 +75,12 @@ class TextBasedChannel {
|
|||||||
send(content, options) { // eslint-disable-line complexity
|
send(content, options) { // eslint-disable-line complexity
|
||||||
if (!options && typeof content === 'object' && !(content instanceof Array)) {
|
if (!options && typeof content === 'object' && !(content instanceof Array)) {
|
||||||
options = content;
|
options = content;
|
||||||
content = '';
|
content = null;
|
||||||
} else if (!options) {
|
} else if (!options) {
|
||||||
options = {};
|
options = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options instanceof MessageEmbed) options = { embed: options };
|
|
||||||
if (options instanceof MessageAttachment) options = { files: [options.file] };
|
|
||||||
|
|
||||||
if (content instanceof Array || options instanceof Array) {
|
|
||||||
const which = content instanceof Array ? content : options;
|
|
||||||
const attachments = which.filter(item => item instanceof MessageAttachment);
|
|
||||||
if (attachments.length) {
|
|
||||||
options = { files: attachments };
|
|
||||||
if (content instanceof Array) content = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!options.content) options.content = content;
|
if (!options.content) options.content = content;
|
||||||
|
|
||||||
if (options.embed && options.embed.files) {
|
|
||||||
if (options.files) options.files = options.files.concat(options.embed.files);
|
|
||||||
else options.files = options.embed.files;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.files) {
|
|
||||||
for (let i = 0; i < options.files.length; i++) {
|
|
||||||
let file = options.files[i];
|
|
||||||
if (typeof file === 'string' || (!browser && Buffer.isBuffer(file))) file = { attachment: file };
|
|
||||||
if (!file.name) {
|
|
||||||
if (typeof file.attachment === 'string') {
|
|
||||||
file.name = Util.basename(file.attachment);
|
|
||||||
} else if (file.attachment && file.attachment.path) {
|
|
||||||
file.name = Util.basename(file.attachment.path);
|
|
||||||
} else if (file instanceof MessageAttachment) {
|
|
||||||
file = { attachment: file.file, name: Util.basename(file.file) || 'file.jpg' };
|
|
||||||
} else {
|
|
||||||
file.name = 'file.jpg';
|
|
||||||
}
|
|
||||||
} else if (file instanceof MessageAttachment) {
|
|
||||||
file = file.file;
|
|
||||||
}
|
|
||||||
options.files[i] = file;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.all(options.files.map(file =>
|
|
||||||
DataResolver.resolveFile(file.attachment).then(resource => {
|
|
||||||
file.file = resource;
|
|
||||||
return file;
|
|
||||||
})
|
|
||||||
)).then(files => {
|
|
||||||
options.files = files;
|
|
||||||
return Shared.sendMessage(this, options);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return Shared.sendMessage(this, options);
|
return Shared.sendMessage(this, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,26 +104,45 @@ class TextBasedChannel {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts a typing indicator in the channel.
|
* Starts a typing indicator in the channel.
|
||||||
* @param {number} [count] The number of times startTyping should be considered to have been called
|
* @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
|
* @example
|
||||||
* // Start typing in a channel
|
* // Start typing in a channel, or increase the typing count by one
|
||||||
* channel.startTyping();
|
* channel.startTyping();
|
||||||
|
* @example
|
||||||
|
* // Start typing in a channel with a typing count of five, or set it to five
|
||||||
|
* channel.startTyping(5);
|
||||||
*/
|
*/
|
||||||
startTyping(count) {
|
startTyping(count) {
|
||||||
if (typeof count !== 'undefined' && count < 1) throw new RangeError('TYPING_COUNT');
|
if (typeof count !== 'undefined' && count < 1) throw new RangeError('TYPING_COUNT');
|
||||||
if (!this.client.user._typing.has(this.id)) {
|
if (this.client.user._typing.has(this.id)) {
|
||||||
const endpoint = this.client.api.channels[this.id].typing;
|
|
||||||
this.client.user._typing.set(this.id, {
|
|
||||||
count: count || 1,
|
|
||||||
interval: this.client.setInterval(() => {
|
|
||||||
endpoint.post();
|
|
||||||
}, 9000),
|
|
||||||
});
|
|
||||||
endpoint.post();
|
|
||||||
} else {
|
|
||||||
const entry = this.client.user._typing.get(this.id);
|
const entry = this.client.user._typing.get(this.id);
|
||||||
entry.count = count || entry.count + 1;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -186,10 +151,10 @@ class TextBasedChannel {
|
|||||||
* <info>It can take a few seconds for the client user to stop typing.</info>
|
* <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
|
* @param {boolean} [force=false] Whether or not to reset the call count and force the indicator to stop
|
||||||
* @example
|
* @example
|
||||||
* // Stop typing in a channel
|
* // Reduce the typing count by one and stop typing if it reached 0
|
||||||
* channel.stopTyping();
|
* channel.stopTyping();
|
||||||
* @example
|
* @example
|
||||||
* // Force typing to fully stop in a channel
|
* // Force typing to fully stop regardless of typing count
|
||||||
* channel.stopTyping(true);
|
* channel.stopTyping(true);
|
||||||
*/
|
*/
|
||||||
stopTyping(force = false) {
|
stopTyping(force = false) {
|
||||||
@@ -199,6 +164,7 @@ class TextBasedChannel {
|
|||||||
if (entry.count <= 0 || force) {
|
if (entry.count <= 0 || force) {
|
||||||
this.client.clearInterval(entry.interval);
|
this.client.clearInterval(entry.interval);
|
||||||
this.client.user._typing.delete(this.id);
|
this.client.user._typing.delete(this.id);
|
||||||
|
entry.resolve();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
108
src/structures/shared/CreateMessage.js
Normal file
108
src/structures/shared/CreateMessage.js
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
const Embed = require('../MessageEmbed');
|
||||||
|
const DataResolver = require('../../util/DataResolver');
|
||||||
|
const MessageEmbed = require('../MessageEmbed');
|
||||||
|
const MessageAttachment = require('../MessageAttachment');
|
||||||
|
const { browser } = require('../../util/Constants');
|
||||||
|
const Util = require('../../util/Util');
|
||||||
|
|
||||||
|
// eslint-disable-next-line complexity
|
||||||
|
module.exports = async function createMessage(channel, options) {
|
||||||
|
const User = require('../User');
|
||||||
|
const GuildMember = require('../GuildMember');
|
||||||
|
const Webhook = require('../Webhook');
|
||||||
|
const WebhookClient = require('../../client/WebhookClient');
|
||||||
|
|
||||||
|
const webhook = channel instanceof Webhook || channel instanceof WebhookClient;
|
||||||
|
|
||||||
|
if (typeof options.nonce !== 'undefined') {
|
||||||
|
options.nonce = parseInt(options.nonce);
|
||||||
|
if (isNaN(options.nonce) || options.nonce < 0) throw new RangeError('MESSAGE_NONCE_TYPE');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options instanceof MessageEmbed) options = webhook ? { embeds: [options] } : { embed: options };
|
||||||
|
if (options instanceof MessageAttachment) options = { files: [options.file] };
|
||||||
|
|
||||||
|
if (options.reply && !(channel instanceof User || channel instanceof GuildMember) && channel.type !== 'dm') {
|
||||||
|
const id = channel.client.users.resolveID(options.reply);
|
||||||
|
const mention = `<@${options.reply instanceof GuildMember && options.reply.nickname ? '!' : ''}${id}>`;
|
||||||
|
if (options.split) options.split.prepend = `${mention}, ${options.split.prepend || ''}`;
|
||||||
|
options.content = `${mention}${typeof options.content !== 'undefined' ? `, ${options.content}` : ''}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.content) {
|
||||||
|
options.content = Util.resolveString(options.content);
|
||||||
|
if (options.split && typeof options.split !== 'object') options.split = {};
|
||||||
|
// Wrap everything in a code block
|
||||||
|
if (typeof options.code !== 'undefined' && (typeof options.code !== 'boolean' || options.code === true)) {
|
||||||
|
options.content = Util.escapeMarkdown(options.content, true);
|
||||||
|
options.content =
|
||||||
|
`\`\`\`${typeof options.code !== 'boolean' ? options.code || '' : ''}\n${options.content}\n\`\`\``;
|
||||||
|
if (options.split) {
|
||||||
|
options.split.prepend = `\`\`\`${typeof options.code !== 'boolean' ? options.code || '' : ''}\n`;
|
||||||
|
options.split.append = '\n```';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add zero-width spaces to @everyone/@here
|
||||||
|
if (options.disableEveryone ||
|
||||||
|
(typeof options.disableEveryone === 'undefined' && channel.client.options.disableEveryone)) {
|
||||||
|
options.content = options.content.replace(/@(everyone|here)/g, '@\u200b$1');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.split) options.content = Util.splitMessage(options.content, options.split);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.embed && options.embed.files) {
|
||||||
|
if (options.files) options.files = options.files.concat(options.embed.files);
|
||||||
|
else options.files = options.embed.files;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.embed && webhook) options.embeds = [new Embed(options.embed)._apiTransform()];
|
||||||
|
else if (options.embed) options.embed = new Embed(options.embed)._apiTransform();
|
||||||
|
else if (options.embeds) options.embeds = options.embeds.map(e => new Embed(e)._apiTransform());
|
||||||
|
|
||||||
|
let files;
|
||||||
|
|
||||||
|
if (options.files) {
|
||||||
|
for (let i = 0; i < options.files.length; i++) {
|
||||||
|
let file = options.files[i];
|
||||||
|
if (typeof file === 'string' || (!browser && Buffer.isBuffer(file))) file = { attachment: file };
|
||||||
|
if (!file.name) {
|
||||||
|
if (typeof file.attachment === 'string') {
|
||||||
|
file.name = Util.basename(file.attachment);
|
||||||
|
} else if (file.attachment && file.attachment.path) {
|
||||||
|
file.name = Util.basename(file.attachment.path);
|
||||||
|
} else if (file instanceof MessageAttachment) {
|
||||||
|
file = { attachment: file.file, name: Util.basename(file.file) || 'file.jpg' };
|
||||||
|
} else {
|
||||||
|
file.name = 'file.jpg';
|
||||||
|
}
|
||||||
|
} else if (file instanceof MessageAttachment) {
|
||||||
|
file = file.file;
|
||||||
|
}
|
||||||
|
options.files[i] = file;
|
||||||
|
}
|
||||||
|
|
||||||
|
files = await Promise.all(options.files.map(file =>
|
||||||
|
DataResolver.resolveFile(file.attachment).then(resource => {
|
||||||
|
file.file = resource;
|
||||||
|
return file;
|
||||||
|
})
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (webhook) {
|
||||||
|
if (!options.username) options.username = this.name;
|
||||||
|
if (options.avatarURL) options.avatar_url = options.avatarURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { data: {
|
||||||
|
content: options.content,
|
||||||
|
tts: options.tts,
|
||||||
|
nonce: options.nonce,
|
||||||
|
embed: options.embed,
|
||||||
|
embeds: options.embeds,
|
||||||
|
username: options.username,
|
||||||
|
avatar_url: options.avatar_url,
|
||||||
|
}, files };
|
||||||
|
};
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
const long = require('long');
|
const Util = require('../../util/Util');
|
||||||
const { TypeError } = require('../../errors');
|
const { TypeError } = require('../../errors');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -11,7 +11,7 @@ const { TypeError } = require('../../errors');
|
|||||||
* @property {ChannelResolvable} [channel] Channel to limit search to (only for guild search endpoint)
|
* @property {ChannelResolvable} [channel] Channel to limit search to (only for guild search endpoint)
|
||||||
* @property {UserResolvable} [author] Author to limit search
|
* @property {UserResolvable} [author] Author to limit search
|
||||||
* @property {string} [authorType] One of `user`, `bot`, `webhook`, or add `-` to negate (e.g. `-webhook`)
|
* @property {string} [authorType] One of `user`, `bot`, `webhook`, or add `-` to negate (e.g. `-webhook`)
|
||||||
* @property {string} [sortBy='recent'] `recent` or `relevant`
|
* @property {string} [sortBy='timestamp'] `timestamp` or `relevant`
|
||||||
* @property {string} [sortOrder='descending'] `ascending` or `descending`
|
* @property {string} [sortOrder='descending'] `ascending` or `descending`
|
||||||
* @property {number} [contextSize=2] How many messages to get around the matched message (0 to 2)
|
* @property {number} [contextSize=2] How many messages to get around the matched message (0 to 2)
|
||||||
* @property {number} [limit=25] Maximum number of results to get (1 to 25)
|
* @property {number} [limit=25] Maximum number of results to get (1 to 25)
|
||||||
@@ -40,17 +40,17 @@ module.exports = function search(target, options) {
|
|||||||
if (typeof options === 'string') options = { content: options };
|
if (typeof options === 'string') options = { content: options };
|
||||||
if (options.before) {
|
if (options.before) {
|
||||||
if (!(options.before instanceof Date)) options.before = new Date(options.before);
|
if (!(options.before instanceof Date)) options.before = new Date(options.before);
|
||||||
options.maxID = long.fromNumber(options.before.getTime() - 14200704e5).shiftLeft(22).toString();
|
options.maxID = Util.binaryToID((options.before.getTime() - 14200704e5).toString(2) + '0'.repeat(22));
|
||||||
}
|
}
|
||||||
if (options.after) {
|
if (options.after) {
|
||||||
if (!(options.after instanceof Date)) options.after = new Date(options.after);
|
if (!(options.after instanceof Date)) options.after = new Date(options.after);
|
||||||
options.minID = long.fromNumber(options.after.getTime() - 14200704e5).shiftLeft(22).toString();
|
options.minID = Util.binaryToID((options.after.getTime() - 14200704e5).toString(2) + '0'.repeat(22));
|
||||||
}
|
}
|
||||||
if (options.during) {
|
if (options.during) {
|
||||||
if (!(options.during instanceof Date)) options.during = new Date(options.during);
|
if (!(options.during instanceof Date)) options.during = new Date(options.during);
|
||||||
const t = options.during.getTime() - 14200704e5;
|
const t = options.during.getTime() - 14200704e5;
|
||||||
options.minID = long.fromNumber(t).shiftLeft(22).toString();
|
options.minID = Util.binaryToID(t.toString(2) + '0'.repeat(22));
|
||||||
options.maxID = long.fromNumber(t + 864e5).shiftLeft(22).toString();
|
options.maxID = Util.binaryToID((t + 864e5).toString(2) + '0'.repeat(22));
|
||||||
}
|
}
|
||||||
if (options.channel) options.channel = target.client.channels.resolveID(options.channel);
|
if (options.channel) options.channel = target.client.channels.resolveID(options.channel);
|
||||||
if (options.author) options.author = target.client.users.resolveID(options.author);
|
if (options.author) options.author = target.client.users.resolveID(options.author);
|
||||||
|
|||||||
@@ -1,65 +1,23 @@
|
|||||||
const Util = require('../../util/Util');
|
const createMessage = require('./CreateMessage');
|
||||||
const Embed = require('../MessageEmbed');
|
|
||||||
const { RangeError } = require('../../errors');
|
|
||||||
|
|
||||||
module.exports = function sendMessage(channel, options) { // eslint-disable-line complexity
|
module.exports = async function sendMessage(channel, options) { // eslint-disable-line complexity
|
||||||
const User = require('../User');
|
const User = require('../User');
|
||||||
const GuildMember = require('../GuildMember');
|
const GuildMember = require('../GuildMember');
|
||||||
if (channel instanceof User || channel instanceof GuildMember) return channel.createDM().then(dm => dm.send(options));
|
if (channel instanceof User || channel instanceof GuildMember) return channel.createDM().then(dm => dm.send(options));
|
||||||
let { content, nonce, reply, code, disableEveryone, tts, embed, files, split } = options;
|
|
||||||
|
|
||||||
if (embed) embed = new Embed(embed)._apiTransform();
|
const { data, files } = await createMessage(channel, options);
|
||||||
|
|
||||||
if (typeof nonce !== 'undefined') {
|
if (data.content instanceof Array) {
|
||||||
nonce = parseInt(nonce);
|
const messages = [];
|
||||||
if (isNaN(nonce) || nonce < 0) throw new RangeError('MESSAGE_NONCE_TYPE');
|
for (let i = 0; i < data.content.length; i++) {
|
||||||
}
|
const opt = i === data.content.length - 1 ? { tts: data.tts, embed: data.embed, files } : { tts: data.tts };
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
// Add the reply prefix
|
const message = await channel.send(data.content[i], opt);
|
||||||
if (reply && !(channel instanceof User || channel instanceof GuildMember) && channel.type !== 'dm') {
|
messages.push(message);
|
||||||
const id = channel.client.users.resolveID(reply);
|
|
||||||
const mention = `<@${reply instanceof GuildMember && reply.nickname ? '!' : ''}${id}>`;
|
|
||||||
if (split) split.prepend = `${mention}, ${split.prepend || ''}`;
|
|
||||||
content = `${mention}${typeof content !== 'undefined' ? `, ${content}` : ''}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (content) {
|
|
||||||
content = Util.resolveString(content);
|
|
||||||
if (split && typeof split !== 'object') split = {};
|
|
||||||
// Wrap everything in a code block
|
|
||||||
if (typeof code !== 'undefined' && (typeof code !== 'boolean' || code === true)) {
|
|
||||||
content = Util.escapeMarkdown(content, true);
|
|
||||||
content = `\`\`\`${typeof code !== 'boolean' ? code || '' : ''}\n${content}\n\`\`\``;
|
|
||||||
if (split) {
|
|
||||||
split.prepend = `\`\`\`${typeof code !== 'boolean' ? code || '' : ''}\n`;
|
|
||||||
split.append = '\n```';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return messages;
|
||||||
// Add zero-width spaces to @everyone/@here
|
|
||||||
if (disableEveryone || (typeof disableEveryone === 'undefined' && channel.client.options.disableEveryone)) {
|
|
||||||
content = content.replace(/@(everyone|here)/g, '@\u200b$1');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (split) content = Util.splitMessage(content, split);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (content instanceof Array) {
|
return channel.client.api.channels[channel.id].messages.post({ data, files })
|
||||||
return new Promise((resolve, reject) => {
|
.then(d => channel.client.actions.MessageCreate.handle(d).message);
|
||||||
const messages = [];
|
|
||||||
(function sendChunk() {
|
|
||||||
const opt = content.length ? { tts } : { tts, embed, files };
|
|
||||||
channel.send(content.shift(), opt).then(message => {
|
|
||||||
messages.push(message);
|
|
||||||
if (content.length === 0) return resolve(messages);
|
|
||||||
return sendChunk();
|
|
||||||
}).catch(reject);
|
|
||||||
}());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return channel.client.api.channels[channel.id].messages.post({
|
|
||||||
data: { content, tts, nonce, embed },
|
|
||||||
files,
|
|
||||||
}).then(data => channel.client.actions.MessageCreate.handle(data).message);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
search: require('./Search'),
|
search: require('./Search'),
|
||||||
sendMessage: require('./SendMessage'),
|
sendMessage: require('./SendMessage'),
|
||||||
|
createMessage: require('./CreateMessage'),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ exports.DefaultOptions = {
|
|||||||
* WebSocket options (these are left as snake_case to match the API)
|
* WebSocket options (these are left as snake_case to match the API)
|
||||||
* @typedef {Object} WebsocketOptions
|
* @typedef {Object} WebsocketOptions
|
||||||
* @property {number} [large_threshold=250] Number of members in a guild to be considered large
|
* @property {number} [large_threshold=250] Number of members in a guild to be considered large
|
||||||
* @property {boolean} [compress=true] Whether to compress data sent on the connection
|
* @property {boolean} [compress=false] Whether to compress data sent on the connection
|
||||||
* (defaults to `false` for browsers)
|
* (defaults to `false` for browsers)
|
||||||
*/
|
*/
|
||||||
ws: {
|
ws: {
|
||||||
@@ -114,6 +114,7 @@ exports.Endpoints = {
|
|||||||
Asset: name => `${root}/assets/${name}`,
|
Asset: name => `${root}/assets/${name}`,
|
||||||
DefaultAvatar: number => `${root}/embed/avatars/${number}.png`,
|
DefaultAvatar: number => `${root}/embed/avatars/${number}.png`,
|
||||||
Avatar: (userID, hash, format = 'default', size) => {
|
Avatar: (userID, hash, format = 'default', size) => {
|
||||||
|
if (userID === '1') return hash;
|
||||||
if (format === 'default') format = hash.startsWith('a_') ? 'gif' : 'webp';
|
if (format === 'default') format = hash.startsWith('a_') ? 'gif' : 'webp';
|
||||||
return makeImageUrl(`${root}/avatars/${userID}/${hash}`, { format, size });
|
return makeImageUrl(`${root}/avatars/${userID}/${hash}`, { format, size });
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const Long = require('long');
|
const Util = require('../util/Util');
|
||||||
|
|
||||||
// Discord epoch (2015-01-01T00:00:00.000Z)
|
// Discord epoch (2015-01-01T00:00:00.000Z)
|
||||||
const EPOCH = 1420070400000;
|
const EPOCH = 1420070400000;
|
||||||
@@ -31,8 +31,9 @@ class SnowflakeUtil {
|
|||||||
*/
|
*/
|
||||||
static generate() {
|
static generate() {
|
||||||
if (INCREMENT >= 4095) INCREMENT = 0;
|
if (INCREMENT >= 4095) INCREMENT = 0;
|
||||||
const BINARY = `${pad((Date.now() - EPOCH).toString(2), 42)}0000100000${pad((INCREMENT++).toString(2), 12)}`;
|
// eslint-disable-next-line max-len
|
||||||
return Long.fromString(BINARY, 2).toString();
|
const BINARY = `${(Date.now() - EPOCH).toString(2).padStart(42, '0')}0000100000${(INCREMENT++).toString(2).padStart(12, '0')}`;
|
||||||
|
return Util.binaryToID(BINARY);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -52,7 +53,7 @@ class SnowflakeUtil {
|
|||||||
* @returns {DeconstructedSnowflake} Deconstructed snowflake
|
* @returns {DeconstructedSnowflake} Deconstructed snowflake
|
||||||
*/
|
*/
|
||||||
static deconstruct(snowflake) {
|
static deconstruct(snowflake) {
|
||||||
const BINARY = pad(Long.fromString(snowflake).toString(2), 64);
|
const BINARY = Util.idToBinary(snowflake).toString(2).padStart(64, '0');
|
||||||
const res = {
|
const res = {
|
||||||
timestamp: parseInt(BINARY.substring(0, 42), 2) + EPOCH,
|
timestamp: parseInt(BINARY.substring(0, 42), 2) + EPOCH,
|
||||||
workerID: parseInt(BINARY.substring(42, 47), 2),
|
workerID: parseInt(BINARY.substring(42, 47), 2),
|
||||||
@@ -68,8 +69,4 @@ class SnowflakeUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function pad(v, n, c = '0') {
|
|
||||||
return String(v).length >= n ? String(v) : (String(c).repeat(n) + v).slice(-n);
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = SnowflakeUtil;
|
module.exports = SnowflakeUtil;
|
||||||
|
|||||||
80
src/util/Structures.js
Normal file
80
src/util/Structures.js
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
/**
|
||||||
|
* Allows for the extension of built-in Discord.js structures that are instantiated by {@link DataStore DataStores}.
|
||||||
|
*/
|
||||||
|
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.
|
||||||
|
* @param {string} 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') {
|
||||||
|
throw new TypeError('The extender function must return the extended structure class/prototype.');
|
||||||
|
}
|
||||||
|
if (Object.getPrototypeOf(extended) !== structures[structure]) {
|
||||||
|
throw new Error(
|
||||||
|
'The class/prototype returned from the extender function must extend the existing structure class/prototype.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
structures[structure] = extended;
|
||||||
|
return extended;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const structures = {
|
||||||
|
Emoji: require('../structures/Emoji'),
|
||||||
|
DMChannel: require('../structures/DMChannel'),
|
||||||
|
GroupDMChannel: require('../structures/GroupDMChannel'),
|
||||||
|
TextChannel: require('../structures/TextChannel'),
|
||||||
|
VoiceChannel: require('../structures/VoiceChannel'),
|
||||||
|
CategoryChannel: require('../structures/CategoryChannel'),
|
||||||
|
GuildChannel: require('../structures/GuildChannel'),
|
||||||
|
GuildMember: require('../structures/GuildMember'),
|
||||||
|
Guild: require('../structures/Guild'),
|
||||||
|
Message: require('../structures/Message'),
|
||||||
|
MessageReaction: require('../structures/MessageReaction'),
|
||||||
|
Presence: require('../structures/Presence').Presence,
|
||||||
|
Role: require('../structures/Role'),
|
||||||
|
User: require('../structures/User'),
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = Structures;
|
||||||
168
src/util/Util.js
168
src/util/Util.js
@@ -1,4 +1,3 @@
|
|||||||
const Long = require('long');
|
|
||||||
const snekfetch = require('snekfetch');
|
const snekfetch = require('snekfetch');
|
||||||
const { Colors, DefaultOptions, Endpoints } = require('./Constants');
|
const { Colors, DefaultOptions, Endpoints } = require('./Constants');
|
||||||
const { Error: DiscordError, RangeError, TypeError } = require('../errors');
|
const { Error: DiscordError, RangeError, TypeError } = require('../errors');
|
||||||
@@ -22,9 +21,7 @@ class Util {
|
|||||||
static splitMessage(text, { maxLength = 1950, char = '\n', prepend = '', append = '' } = {}) {
|
static splitMessage(text, { maxLength = 1950, char = '\n', prepend = '', append = '' } = {}) {
|
||||||
if (text.length <= maxLength) return text;
|
if (text.length <= maxLength) return text;
|
||||||
const splitText = text.split(char);
|
const splitText = text.split(char);
|
||||||
if (splitText.length === 1) {
|
if (splitText.length === 1) throw new RangeError('SPLIT_MAX_LEN');
|
||||||
throw new RangeError('SPLIT_MAX_LEN');
|
|
||||||
}
|
|
||||||
const messages = [''];
|
const messages = [''];
|
||||||
let msg = 0;
|
let msg = 0;
|
||||||
for (let i = 0; i < splitText.length; i++) {
|
for (let i = 0; i < splitText.length; i++) {
|
||||||
@@ -84,10 +81,7 @@ class Util {
|
|||||||
const [name, id] = text.split(':');
|
const [name, id] = text.split(':');
|
||||||
return { name, id };
|
return { name, id };
|
||||||
} else {
|
} else {
|
||||||
return {
|
return { name: text, id: null };
|
||||||
name: text,
|
|
||||||
id: null,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,7 +220,6 @@ class Util {
|
|||||||
* @param {StringResolvable} data The string resolvable to resolve
|
* @param {StringResolvable} data The string resolvable to resolve
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static resolveString(data) {
|
static resolveString(data) {
|
||||||
if (typeof data === 'string') return data;
|
if (typeof data === 'string') return data;
|
||||||
if (data instanceof Array) return data.join('\n');
|
if (data instanceof Array) return data.join('\n');
|
||||||
@@ -234,39 +227,34 @@ class Util {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Can be a Hex Literal, Hex String, Number, RGB Array, or one of the following
|
* Can be a number, hex string, an RGB array like:
|
||||||
|
* ```js
|
||||||
|
* [255, 0, 255] // purple
|
||||||
* ```
|
* ```
|
||||||
* [
|
* or one of the following strings:
|
||||||
* 'DEFAULT',
|
* - `DEFAULT`
|
||||||
* 'AQUA',
|
* - `AQUA`
|
||||||
* 'GREEN',
|
* - `GREEN`
|
||||||
* 'BLUE',
|
* - `BLUE`
|
||||||
* 'PURPLE',
|
* - `PURPLE`
|
||||||
* 'GOLD',
|
* - `GOLD`
|
||||||
* 'ORANGE',
|
* - `ORANGE`
|
||||||
* 'RED',
|
* - `RED`
|
||||||
* 'GREY',
|
* - `GREY`
|
||||||
* 'DARKER_GREY',
|
* - `DARKER_GREY`
|
||||||
* 'NAVY',
|
* - `NAVY`
|
||||||
* 'DARK_AQUA',
|
* - `DARK_AQUA`
|
||||||
* 'DARK_GREEN',
|
* - `DARK_GREEN`
|
||||||
* 'DARK_BLUE',
|
* - `DARK_BLUE`
|
||||||
* 'DARK_PURPLE',
|
* - `DARK_PURPLE`
|
||||||
* 'DARK_GOLD',
|
* - `DARK_GOLD`
|
||||||
* 'DARK_ORANGE',
|
* - `DARK_ORANGE`
|
||||||
* 'DARK_RED',
|
* - `DARK_RED`
|
||||||
* 'DARK_GREY',
|
* - `DARK_GREY`
|
||||||
* 'LIGHT_GREY',
|
* - `LIGHT_GREY`
|
||||||
* 'DARK_NAVY',
|
* - `DARK_NAVY`
|
||||||
* 'RANDOM',
|
* - `RANDOM`
|
||||||
* ]
|
* @typedef {string|number|number[]} ColorResolvable
|
||||||
* ```
|
|
||||||
* or something like
|
|
||||||
* ```
|
|
||||||
* [255, 0, 255]
|
|
||||||
* ```
|
|
||||||
* for purple
|
|
||||||
* @typedef {string|number|Array} ColorResolvable
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -274,7 +262,6 @@ class Util {
|
|||||||
* @param {ColorResolvable} color Color to resolve
|
* @param {ColorResolvable} color Color to resolve
|
||||||
* @returns {number} A color
|
* @returns {number} A color
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static resolveColor(color) {
|
static resolveColor(color) {
|
||||||
if (typeof color === 'string') {
|
if (typeof color === 'string') {
|
||||||
if (color === 'RANDOM') return Math.floor(Math.random() * (0xFFFFFF + 1));
|
if (color === 'RANDOM') return Math.floor(Math.random() * (0xFFFFFF + 1));
|
||||||
@@ -283,25 +270,36 @@ class Util {
|
|||||||
color = (color[0] << 16) + (color[1] << 8) + color[2];
|
color = (color[0] << 16) + (color[1] << 8) + color[2];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (color < 0 || color > 0xFFFFFF) {
|
if (color < 0 || color > 0xFFFFFF) throw new RangeError('COLOR_RANGE');
|
||||||
throw new RangeError('COLOR_RANGE');
|
else if (color && isNaN(color)) throw new TypeError('COLOR_CONVERT');
|
||||||
} else if (color && isNaN(color)) {
|
|
||||||
throw new TypeError('COLOR_CONVERT');
|
|
||||||
}
|
|
||||||
|
|
||||||
return color;
|
return color;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sorts by Discord's position and then by ID.
|
* Sorts by Discord's position and ID.
|
||||||
* @param {Collection} collection Collection of objects to sort
|
* @param {Collection} collection Collection of objects to sort
|
||||||
* @returns {Collection}
|
* @returns {Collection}
|
||||||
*/
|
*/
|
||||||
static discordSort(collection) {
|
static discordSort(collection) {
|
||||||
return collection
|
return collection.sort((a, b) =>
|
||||||
.sort((a, b) => a.rawPosition - b.rawPosition || Long.fromString(a.id).sub(Long.fromString(b.id)).toNumber());
|
a.rawPosition - b.rawPosition ||
|
||||||
|
parseInt(a.id.slice(0, -10)) - parseInt(b.id.slice(0, -10)) ||
|
||||||
|
parseInt(a.id.slice(10)) - parseInt(b.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) {
|
static setPosition(item, position, relative, sorted, route, reason) {
|
||||||
let updatedItems = sorted.array();
|
let updatedItems = sorted.array();
|
||||||
Util.moveElementInArray(updatedItems, item, position, relative);
|
Util.moveElementInArray(updatedItems, item, position, relative);
|
||||||
@@ -309,13 +307,77 @@ class Util {
|
|||||||
return route.patch({ data: updatedItems, reason }).then(() => updatedItems);
|
return route.patch({ data: updatedItems, reason }).then(() => updatedItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alternative to Node's `path.basename` that we have for some (probably stupid) reason.
|
||||||
|
* @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) {
|
static basename(path, ext) {
|
||||||
let f = splitPathRe.exec(path).slice(1)[2];
|
let f = splitPathRe.exec(path).slice(1)[2];
|
||||||
if (ext && f.substr(-1 * ext.length) === ext) {
|
if (ext && f.substr(-1 * ext.length) === ext) f = f.substr(0, f.length - ext.length);
|
||||||
f = f.substr(0, f.length - ext.length);
|
|
||||||
}
|
|
||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
module.exports = Util;
|
||||||
|
|||||||
@@ -24,12 +24,6 @@ else
|
|||||||
SOURCE_TYPE="branch"
|
SOURCE_TYPE="branch"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# For Node != 8, do nothing
|
|
||||||
if [ "$TRAVIS_NODE_VERSION" != "8" ]; then
|
|
||||||
echo -e "\e[36m\e[1mBuild triggered with Node v${TRAVIS_NODE_VERSION} - doing nothing."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Run the build
|
# Run the build
|
||||||
npm run docs
|
npm run docs
|
||||||
NODE_ENV=production npm run build:browser
|
NODE_ENV=production npm run build:browser
|
||||||
@@ -87,4 +81,3 @@ git config user.name "Travis CI"
|
|||||||
git config user.email "$COMMIT_AUTHOR_EMAIL"
|
git config user.email "$COMMIT_AUTHOR_EMAIL"
|
||||||
git commit -m "Webpack build for ${SOURCE_TYPE} ${SOURCE}: ${SHA}" || true
|
git commit -m "Webpack build for ${SOURCE_TYPE} ${SOURCE}: ${SHA}" || true
|
||||||
git push $SSH_REPO $TARGET_BRANCH
|
git push $SSH_REPO $TARGET_BRANCH
|
||||||
|
|
||||||
|
|||||||
2
typings
2
typings
Submodule typings updated: 697fc933de...0b5b13f4a5
Reference in New Issue
Block a user