mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-11 00:53:31 +01:00
Merge branch '11.1-dev' into stable
This commit is contained in:
42
.gitignore
vendored
42
.gitignore
vendored
@@ -1,21 +1,21 @@
|
||||
# Packages
|
||||
node_modules/
|
||||
yarn.lock
|
||||
|
||||
# Log files
|
||||
logs/
|
||||
*.log
|
||||
|
||||
# Authentication
|
||||
test/auth.json
|
||||
test/auth.js
|
||||
docs/deploy/deploy_key
|
||||
docs/deploy/deploy_key.pub
|
||||
deploy/deploy_key
|
||||
deploy/deploy_key.pub
|
||||
|
||||
# Miscellaneous
|
||||
.tmp/
|
||||
.vscode/
|
||||
docs/docs.json
|
||||
webpack/
|
||||
# Packages
|
||||
node_modules/
|
||||
yarn.lock
|
||||
|
||||
# Log files
|
||||
logs/
|
||||
*.log
|
||||
|
||||
# Authentication
|
||||
test/auth.json
|
||||
test/auth.js
|
||||
docs/deploy/deploy_key
|
||||
docs/deploy/deploy_key.pub
|
||||
deploy/deploy_key
|
||||
deploy/deploy_key.pub
|
||||
|
||||
# Miscellaneous
|
||||
.tmp/
|
||||
.vscode/
|
||||
docs/docs.json
|
||||
webpack/
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
{
|
||||
"ecmaVersion": 6,
|
||||
"ecmaVersion": 7,
|
||||
"libs": [],
|
||||
"loadEagerly": [
|
||||
"./src/*.js"
|
||||
],
|
||||
"dontLoad": [
|
||||
"node_modules/**"
|
||||
],
|
||||
"plugins": {
|
||||
"node": {
|
||||
"dontLoad": "node_modules/**",
|
||||
"load": "",
|
||||
"modules": ""
|
||||
"es_modules": {},
|
||||
"node": {},
|
||||
"doc_comment": {
|
||||
"fullDocs": true,
|
||||
"strong": true
|
||||
},
|
||||
"webpack": {
|
||||
"configPath": "./webpack.config.js",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
36
.travis.yml
36
.travis.yml
@@ -1,16 +1,20 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "6"
|
||||
- "7"
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
install: npm install
|
||||
script:
|
||||
- bash ./deploy/deploy.sh
|
||||
env:
|
||||
global:
|
||||
- ENCRYPTION_LABEL: "af862fa96d3e"
|
||||
- COMMIT_AUTHOR_EMAIL: "amishshah.2k@gmail.com"
|
||||
dist: trusty
|
||||
sudo: false
|
||||
language: node_js
|
||||
node_js:
|
||||
- "6"
|
||||
- "7"
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
install: npm install
|
||||
script: bash ./deploy/test.sh
|
||||
jobs:
|
||||
include:
|
||||
- stage: build
|
||||
node_js: "6"
|
||||
script: bash ./deploy/deploy.sh
|
||||
env:
|
||||
global:
|
||||
- ENCRYPTION_LABEL: "af862fa96d3e"
|
||||
- COMMIT_AUTHOR_EMAIL: "amishshah.2k@gmail.com"
|
||||
dist: trusty
|
||||
sudo: false
|
||||
|
||||
9
browser.js
Normal file
9
browser.js
Normal file
@@ -0,0 +1,9 @@
|
||||
const browser = typeof window !== 'undefined';
|
||||
const webpack = !!process.env.__DISCORD_WEBPACK__;
|
||||
|
||||
const Discord = require('./');
|
||||
|
||||
module.exports = Discord;
|
||||
if (browser && webpack) window.Discord = Discord; // eslint-disable-line no-undef
|
||||
// eslint-disable-next-line no-console
|
||||
else if (!browser) console.warn('Warning: Attempting to use browser version of Discord.js in a non-browser environment!');
|
||||
@@ -3,15 +3,7 @@
|
||||
|
||||
set -e
|
||||
|
||||
function tests {
|
||||
npm run lint
|
||||
npm run docs:test
|
||||
VERSIONED=false npm run webpack
|
||||
exit 0
|
||||
}
|
||||
|
||||
function build {
|
||||
npm run lint
|
||||
npm run docs
|
||||
VERSIONED=false npm run webpack
|
||||
}
|
||||
@@ -22,10 +14,10 @@ if [[ "$TRAVIS_BRANCH" == revert-* ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# For PRs, only run tests
|
||||
# For PRs, do nothing
|
||||
if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then
|
||||
echo -e "\e[36m\e[1mBuild triggered for PR #${TRAVIS_PULL_REQUEST} to branch \"${TRAVIS_BRANCH}\" - only running tests."
|
||||
tests
|
||||
echo -e "\e[36m\e[1mBuild triggered for PR #${TRAVIS_PULL_REQUEST} to branch \"${TRAVIS_BRANCH}\" - doing nothing."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Figure out the source of the build
|
||||
@@ -39,10 +31,10 @@ else
|
||||
SOURCE_TYPE="branch"
|
||||
fi
|
||||
|
||||
# For Node != 6, only run tests
|
||||
# For Node != 6, do nothing
|
||||
if [ "$TRAVIS_NODE_VERSION" != "6" ]; then
|
||||
echo -e "\e[36m\e[1mBuild triggered with Node v${TRAVIS_NODE_VERSION} - only running tests."
|
||||
tests
|
||||
echo -e "\e[36m\e[1mBuild triggered with Node v${TRAVIS_NODE_VERSION} - doing nothing."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
build
|
||||
|
||||
34
deploy/test.sh
Normal file
34
deploy/test.sh
Normal file
@@ -0,0 +1,34 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
function tests {
|
||||
npm run lint
|
||||
npm run docs:test
|
||||
exit 0
|
||||
}
|
||||
|
||||
# For revert branches, do nothing
|
||||
if [[ "$TRAVIS_BRANCH" == revert-* ]]; then
|
||||
echo -e "\e[36m\e[1mTest triggered for reversion branch \"${TRAVIS_BRANCH}\" - doing nothing."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# For PRs
|
||||
if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then
|
||||
echo -e "\e[36m\e[1mTest triggered for PR #${TRAVIS_PULL_REQUEST} to branch \"${TRAVIS_BRANCH}\" - only running tests."
|
||||
tests
|
||||
fi
|
||||
|
||||
# Figure out the source of the test
|
||||
if [ -n "$TRAVIS_TAG" ]; then
|
||||
echo -e "\e[36m\e[1mTest triggered for tag \"${TRAVIS_TAG}\"."
|
||||
else
|
||||
echo -e "\e[36m\e[1mTest triggered for branch \"${TRAVIS_BRANCH}\"."
|
||||
fi
|
||||
|
||||
# For Node != 6
|
||||
if [ "$TRAVIS_NODE_VERSION" != "6" ]; then
|
||||
echo -e "\e[36m\e[1mTest triggered with Node v${TRAVIS_NODE_VERSION} - only running tests."
|
||||
tests
|
||||
fi
|
||||
@@ -1 +1 @@
|
||||
## [View the documentation here.](https://discord.js.org/#/docs)
|
||||
## [View the documentation here.](https://discord.js.org/#/docs)
|
||||
|
||||
@@ -19,11 +19,7 @@ client.on('ready', () => {
|
||||
|
||||
// Create an event listener for new guild members
|
||||
client.on('guildMemberAdd', member => {
|
||||
// Send the message to the guilds default channel (usually #general), mentioning the member
|
||||
member.guild.defaultChannel.send(`Welcome to the server, ${member}!`);
|
||||
|
||||
// If you want to send the message to a designated channel on a server instead
|
||||
// you can do the following:
|
||||
// Send the message to a designated channel on a server:
|
||||
const channel = member.guild.channels.find('name', 'member-log');
|
||||
// Do nothing if the channel wasn't found on this server
|
||||
if (!channel) return;
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
# Version 11.2.0
|
||||
v11.2.0 fixes a lot of bugs we encountered along the 11.1.0 release, as well as support for new features such as Message Attachments and UserGuildSettings.
|
||||
See [the changelog](https://github.com/hydrabolt/discord.js/releases/tag/11.2.0) for a full list of changes, including information about deprecations.
|
||||
|
||||
# Version 11.1.0
|
||||
v11.1.0 features improved voice and gateway stability, as well as support for new features such as audit logs and searching for messages.
|
||||
See [the changelog](https://github.com/hydrabolt/discord.js/releases/tag/11.1.0) for a full list of changes, including
|
||||
@@ -118,9 +122,9 @@ The guild parameter that has been dropped from the guild-related events can stil
|
||||
|
||||
## Dates and timestamps
|
||||
All dates/timestamps on the structures have been refactored to have a consistent naming scheme and availability.
|
||||
All of them are named similarly to this:
|
||||
**Date:** `Message.createdAt`
|
||||
**Timestamp:** `Message.createdTimestamp`
|
||||
All of them are named similarly to this:
|
||||
**Date:** `Message.createdAt`
|
||||
**Timestamp:** `Message.createdTimestamp`
|
||||
See the docs for each structure to see which date/timestamps are available on them.
|
||||
|
||||
|
||||
@@ -149,7 +153,7 @@ A couple more important details:
|
||||
* `Client.servers.length` ==> `client.guilds.size` (all instances of `server` are now `guild`)
|
||||
|
||||
## No more callbacks!
|
||||
Version 9 eschews callbacks in favour of Promises. This means all code relying on callbacks must be changed.
|
||||
Version 9 eschews callbacks in favour of Promises. This means all code relying on callbacks must be changed.
|
||||
For example, the following code:
|
||||
|
||||
```js
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
</div>
|
||||
|
||||
# Welcome!
|
||||
Welcome to the discord.js v11.1.0 documentation.
|
||||
v11.1.0 features improved voice and gateway stability, as well as support for new features such as audit logs and searching for messages.
|
||||
Welcome to the discord.js v11.2.0 documentation.
|
||||
v11.2.0 fixes a lot of bugs we encountered along the 11.1.0 release, as well as support for new features such as Message Attachments and UserGuildSettings.
|
||||
|
||||
## About
|
||||
discord.js is a powerful [node.js](https://nodejs.org) module that allows you to interact with the
|
||||
@@ -30,11 +30,11 @@ discord.js is a powerful [node.js](https://nodejs.org) module that allows you to
|
||||
- 100% coverage of the Discord API
|
||||
|
||||
## Installation
|
||||
**Node.js 6.0.0 or newer is required.**
|
||||
**Node.js 6.0.0 or newer is required.**
|
||||
Ignore any warnings about unmet peer dependencies, as they're all optional.
|
||||
|
||||
Without voice support: `npm install discord.js --save`
|
||||
With voice support ([node-opus](https://www.npmjs.com/package/node-opus)): `npm install discord.js node-opus --save`
|
||||
Without voice support: `npm install discord.js --save`
|
||||
With voice support ([node-opus](https://www.npmjs.com/package/node-opus)): `npm install discord.js node-opus --save`
|
||||
With voice support ([opusscript](https://www.npmjs.com/package/opusscript)): `npm install discord.js opusscript --save`
|
||||
|
||||
### Audio engines
|
||||
@@ -79,8 +79,8 @@ client.login('your token');
|
||||
|
||||
## Contributing
|
||||
Before creating an issue, please ensure that it hasn't already been reported/suggested, and double-check the
|
||||
[documentation](https://discord.js.org/#/docs).
|
||||
See [the contribution guide](https://github.com/hydrabolt/discord.js/blob/master/CONTRIBUTING.md) if you'd like to submit a PR.
|
||||
[documentation](https://discord.js.org/#/docs).
|
||||
See [the contribution guide](https://github.com/hydrabolt/discord.js/blob/master/.github/CONTRIBUTING.md) if you'd like to submit a PR.
|
||||
|
||||
## Help
|
||||
If you don't understand something in the documentation, you are experiencing problems, or you just need a gentle
|
||||
|
||||
@@ -4,7 +4,7 @@ Voice in discord.js can be used for many things, such as music bots, recording o
|
||||
In discord.js, you can use voice by connecting to a `VoiceChannel` to obtain a `VoiceConnection`, where you can start streaming and receiving audio.
|
||||
|
||||
To get started, make sure you have:
|
||||
* ffmpeg - `npm install --global ffmpeg-binaries`
|
||||
* ffmpeg - `npm install ffmpeg-binaries`
|
||||
* an opus encoder, choose one from below:
|
||||
* `npm install opusscript`
|
||||
* `npm install node-opus`
|
||||
|
||||
@@ -30,7 +30,7 @@ The usage of the API isn't any different from using it in Node.js.
|
||||
client.on('message', msg => {
|
||||
const guildTag = msg.channel.type === 'text' ? `[${msg.guild.name}]` : '[DM]';
|
||||
const channelTag = msg.channel.type === 'text' ? `[#${msg.channel.name}]` : '';
|
||||
console.log(`${guildTag}${channelTag} ${msg.author.username}#${msg.author.discriminator}: ${msg.content}`);
|
||||
console.log(`${guildTag}${channelTag} ${msg.author.tag}: ${msg.content}`);
|
||||
});
|
||||
|
||||
client.login('some crazy token');
|
||||
|
||||
26
package.json
26
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "discord.js",
|
||||
"version": "11.1.0",
|
||||
"version": "11.2.0",
|
||||
"description": "A powerful library for interacting with the Discord API",
|
||||
"main": "./src/index",
|
||||
"types": "./typings/index.d.ts",
|
||||
@@ -34,26 +34,26 @@
|
||||
"dependencies": {
|
||||
"long": "^3.2.0",
|
||||
"prism-media": "^0.0.1",
|
||||
"snekfetch": "^3.1.0",
|
||||
"tweetnacl": "^0.14.0",
|
||||
"ws": "^2.0.0"
|
||||
"snekfetch": "^3.3.0",
|
||||
"tweetnacl": "^1.0.0",
|
||||
"ws": "^3.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^3.0.0",
|
||||
"bufferutil": "^3.0.2",
|
||||
"erlpack": "hammerandchisel/erlpack",
|
||||
"node-opus": "^0.2.5",
|
||||
"node-opus": "^0.2.6",
|
||||
"opusscript": "^0.0.3",
|
||||
"sodium": "^2.0.1",
|
||||
"libsodium-wrappers": "^0.5.1",
|
||||
"uws": "^0.14.1"
|
||||
"libsodium-wrappers": "^0.5.4",
|
||||
"uws": "^0.14.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^7.0.0",
|
||||
"@types/node": "^7.0.43",
|
||||
"discord.js-docgen": "hydrabolt/discord.js-docgen",
|
||||
"eslint": "^3.19.0",
|
||||
"parallel-webpack": "^1.6.0",
|
||||
"uglify-js": "mishoo/UglifyJS2#harmony",
|
||||
"webpack": "^2.2.0"
|
||||
"eslint": "^4.6.0",
|
||||
"parallel-webpack": "^2.1.0",
|
||||
"uglifyjs-webpack-plugin": "^1.0.0-beta.1",
|
||||
"webpack": "^3.5.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
const os = require('os');
|
||||
const EventEmitter = require('events').EventEmitter;
|
||||
const EventEmitter = require('events');
|
||||
const Constants = require('../util/Constants');
|
||||
const Permissions = require('../util/Permissions');
|
||||
const Util = require('../util/Util');
|
||||
@@ -120,6 +119,7 @@ class Client extends EventEmitter {
|
||||
*/
|
||||
this.presences = new Collection();
|
||||
|
||||
Object.defineProperty(this, 'token', { writable: true });
|
||||
if (!this.token && 'CLIENT_TOKEN' in process.env) {
|
||||
/**
|
||||
* Authorization token for the logged in user/bot
|
||||
@@ -249,7 +249,7 @@ class Client extends EventEmitter {
|
||||
* @readonly
|
||||
*/
|
||||
get browser() {
|
||||
return os.platform() === 'browser';
|
||||
return typeof window !== 'undefined';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -419,9 +419,9 @@ class Client extends EventEmitter {
|
||||
*/
|
||||
setTimeout(fn, delay, ...args) {
|
||||
const timeout = setTimeout(() => {
|
||||
fn();
|
||||
fn(...args);
|
||||
this._timeouts.delete(timeout);
|
||||
}, delay, ...args);
|
||||
}, delay);
|
||||
this._timeouts.add(timeout);
|
||||
return timeout;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ class ClientDataResolver {
|
||||
/**
|
||||
* Data that resolves to give a User object. This can be:
|
||||
* * A User object
|
||||
* * A user ID
|
||||
* * A Snowflake
|
||||
* * A Message object (resolves to the message author)
|
||||
* * A Guild object (owner of the guild)
|
||||
* * A GuildMember object
|
||||
@@ -65,7 +65,7 @@ class ClientDataResolver {
|
||||
/**
|
||||
* Data that resolves to give a Guild object. This can be:
|
||||
* * A Guild object
|
||||
* * A Guild ID
|
||||
* * A Snowflake
|
||||
* @typedef {Guild|Snowflake} GuildResolvable
|
||||
*/
|
||||
|
||||
@@ -106,7 +106,7 @@ class ClientDataResolver {
|
||||
* * A Channel object
|
||||
* * A Message object (the channel the message was sent in)
|
||||
* * A Guild object (the #general channel)
|
||||
* * A channel ID
|
||||
* * A Snowflake
|
||||
* @typedef {Channel|Guild|Message|Snowflake} ChannelResolvable
|
||||
*/
|
||||
|
||||
@@ -174,6 +174,20 @@ class ClientDataResolver {
|
||||
return String(data);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Resolves a Base64Resolvable, a string, or a BufferResolvable to a Base 64 image.
|
||||
* @param {BufferResolvable|Base64Resolvable} image The image to be resolved
|
||||
* @returns {Promise<?string>}
|
||||
*/
|
||||
resolveImage(image) {
|
||||
if (!image) return Promise.resolve(null);
|
||||
if (typeof image === 'string' && image.startsWith('data:')) {
|
||||
return Promise.resolve(image);
|
||||
}
|
||||
return this.resolveFile(image).then(this.resolveBase64);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data that resolves to give a Base64 string, typically for image uploading. This can be:
|
||||
* * A Buffer
|
||||
@@ -192,19 +206,25 @@ class ClientDataResolver {
|
||||
}
|
||||
|
||||
/**
|
||||
* Data that can be resolved to give a Buffer. This can be:
|
||||
* * A Buffer
|
||||
* * The path to a local file
|
||||
* * A URL
|
||||
* @typedef {string|Buffer} BufferResolvable
|
||||
*/
|
||||
* Data that can be resolved to give a Buffer. This can be:
|
||||
* * A Buffer
|
||||
* * The path to a local file
|
||||
* * A URL
|
||||
* * A Stream
|
||||
* @typedef {string|Buffer} BufferResolvable
|
||||
*/
|
||||
|
||||
/**
|
||||
* Resolves a BufferResolvable to a Buffer.
|
||||
* @param {BufferResolvable} resource The buffer resolvable to resolve
|
||||
* @returns {Promise<Buffer>}
|
||||
*/
|
||||
resolveBuffer(resource) {
|
||||
* @external Stream
|
||||
* @see {@link https://nodejs.org/api/stream.html}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Resolves a BufferResolvable to a Buffer.
|
||||
* @param {BufferResolvable|Stream} resource The buffer or stream resolvable to resolve
|
||||
* @returns {Promise<Buffer>}
|
||||
*/
|
||||
resolveFile(resource) {
|
||||
if (resource instanceof Buffer) return Promise.resolve(resource);
|
||||
if (this.client.browser && resource instanceof ArrayBuffer) return Promise.resolve(convertToBuffer(resource));
|
||||
|
||||
@@ -212,11 +232,11 @@ class ClientDataResolver {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (/^https?:\/\//.test(resource)) {
|
||||
snekfetch.get(resource)
|
||||
.end((err, res) => {
|
||||
if (err) return reject(err);
|
||||
if (!(res.body instanceof Buffer)) return reject(new TypeError('The response body isn\'t a Buffer.'));
|
||||
return resolve(res.body);
|
||||
});
|
||||
.end((err, res) => {
|
||||
if (err) return reject(err);
|
||||
if (!(res.body instanceof Buffer)) return reject(new TypeError('The response body isn\'t a Buffer.'));
|
||||
return resolve(res.body);
|
||||
});
|
||||
} else {
|
||||
const file = path.resolve(resource);
|
||||
fs.stat(file, (err, stats) => {
|
||||
@@ -229,6 +249,13 @@ class ClientDataResolver {
|
||||
});
|
||||
}
|
||||
});
|
||||
} else if (resource.pipe && typeof resource.pipe === 'function') {
|
||||
return new Promise((resolve, reject) => {
|
||||
const buffers = [];
|
||||
resource.once('error', reject);
|
||||
resource.on('data', data => buffers.push(data));
|
||||
resource.once('end', () => resolve(Buffer.concat(buffers)));
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.reject(new TypeError('The resource must be a string or Buffer.'));
|
||||
|
||||
@@ -43,7 +43,7 @@ class ClientManager {
|
||||
const gateway = `${res.url}/?v=${protocolVersion}&encoding=${WebSocketConnection.ENCODING}`;
|
||||
this.client.emit(Constants.Events.DEBUG, `Using gateway ${gateway}`);
|
||||
this.client.ws.connect(gateway);
|
||||
this.client.ws.once('close', event => {
|
||||
this.client.ws.connection.once('close', event => {
|
||||
if (event.code === 4004) reject(new Error(Constants.Errors.BAD_LOGIN));
|
||||
if (event.code === 4010) reject(new Error(Constants.Errors.INVALID_SHARD));
|
||||
if (event.code === 4011) reject(new Error(Constants.Errors.SHARDING_REQUIRED));
|
||||
|
||||
@@ -65,9 +65,9 @@ class WebhookClient extends Webhook {
|
||||
*/
|
||||
setTimeout(fn, delay, ...args) {
|
||||
const timeout = setTimeout(() => {
|
||||
fn();
|
||||
fn(...args);
|
||||
this._timeouts.delete(timeout);
|
||||
}, delay, ...args);
|
||||
}, delay);
|
||||
this._timeouts.add(timeout);
|
||||
return timeout;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ const snekfetch = require('snekfetch');
|
||||
const Constants = require('../../util/Constants');
|
||||
|
||||
class APIRequest {
|
||||
constructor(rest, method, path, auth, data, files) {
|
||||
constructor(rest, method, path, auth, data, files, reason) {
|
||||
this.rest = rest;
|
||||
this.client = rest.client;
|
||||
this.method = method;
|
||||
@@ -11,6 +11,7 @@ class APIRequest {
|
||||
this.data = data;
|
||||
this.files = files;
|
||||
this.route = this.getRoute(this.path);
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
getRoute(url) {
|
||||
@@ -36,6 +37,7 @@ class APIRequest {
|
||||
const API = `${this.client.options.http.host}/api/v${this.client.options.http.version}`;
|
||||
const request = snekfetch[this.method](`${API}${this.path}`);
|
||||
if (this.auth) request.set('Authorization', this.getAuth());
|
||||
if (this.reason) request.set('X-Audit-Log-Reason', encodeURIComponent(this.reason));
|
||||
if (!this.rest.client.browser) request.set('User-Agent', this.rest.userAgentManager.userAgent);
|
||||
if (this.files) {
|
||||
for (const file of this.files) if (file && file.file) request.attach(file.name, file.file, file.name);
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
/**
|
||||
* Represents an error from the Discord API.
|
||||
* @extends Error
|
||||
*/
|
||||
class DiscordAPIError extends Error {
|
||||
constructor(error) {
|
||||
constructor(path, error) {
|
||||
super();
|
||||
const flattened = error.errors ? `\n${this.constructor.flattenErrors(error.errors).join('\n')}` : '';
|
||||
const flattened = this.constructor.flattenErrors(error.errors || error).join('\n');
|
||||
this.name = 'DiscordAPIError';
|
||||
this.message = `${error.message}${flattened}`;
|
||||
this.message = error.message && flattened ? `${error.message}\n${flattened}` : error.message || flattened;
|
||||
|
||||
/**
|
||||
* The path of the request relative to the HTTP endpoint
|
||||
* @type {string}
|
||||
*/
|
||||
this.path = path;
|
||||
|
||||
/**
|
||||
* HTTP error code returned by Discord
|
||||
@@ -18,15 +25,23 @@ class DiscordAPIError extends Error {
|
||||
/**
|
||||
* Flattens an errors object returned from the API into an array.
|
||||
* @param {Object} obj Discord errors object
|
||||
* @param {string} [key] idklol
|
||||
* @param {string} [key] Used internally to determine key names of nested fields
|
||||
* @returns {string[]}
|
||||
* @private
|
||||
*/
|
||||
static flattenErrors(obj, key = '') {
|
||||
let messages = [];
|
||||
|
||||
for (const k of Object.keys(obj)) {
|
||||
if (k === 'message') continue;
|
||||
const newKey = key ? isNaN(k) ? `${key}.${k}` : `${key}[${k}]` : k;
|
||||
|
||||
if (obj[k]._errors) {
|
||||
messages.push(`${newKey}: ${obj[k]._errors.map(e => e.message).join(' ')}`);
|
||||
} else if (obj[k].code || obj[k].message) {
|
||||
messages.push(`${obj[k].code ? `${obj[k].code}: ` : ''}: ${obj[k].message}`.trim());
|
||||
} else if (typeof obj[k] === 'string') {
|
||||
messages.push(obj[k]);
|
||||
} else {
|
||||
messages = messages.concat(this.flattenErrors(obj[k], newKey));
|
||||
}
|
||||
|
||||
@@ -42,8 +42,8 @@ class RESTManager {
|
||||
}
|
||||
}
|
||||
|
||||
makeRequest(method, url, auth, data, file) {
|
||||
const apiRequest = new APIRequest(this, method, url, auth, data, file);
|
||||
makeRequest(method, url, auth, data, file, reason) {
|
||||
const apiRequest = new APIRequest(this, method, url, auth, data, file, reason);
|
||||
if (!this.handlers[apiRequest.route]) {
|
||||
const RequestHandlerType = this.getRequestHandler();
|
||||
this.handlers[apiRequest.route] = new RequestHandlerType(this, apiRequest.route);
|
||||
|
||||
@@ -102,12 +102,12 @@ class RESTMethods {
|
||||
if (content instanceof Array) {
|
||||
const messages = [];
|
||||
(function sendChunk(list, index) {
|
||||
const options = index === list.length ? { tts, embed } : { tts };
|
||||
chan.send(list[index], options, index === list.length ? files : null).then(message => {
|
||||
const options = index === list.length - 1 ? { tts, embed, files } : { tts };
|
||||
chan.send(list[index], options).then(message => {
|
||||
messages.push(message);
|
||||
if (index >= list.length - 1) return resolve(messages);
|
||||
return sendChunk(list, ++index);
|
||||
});
|
||||
}).catch(reject);
|
||||
}(content, 0));
|
||||
} else {
|
||||
this.rest.makeRequest('post', Endpoints.Channel(chan).messages, true, {
|
||||
@@ -227,6 +227,7 @@ class RESTMethods {
|
||||
embed_type: options.embedType,
|
||||
attachment_filename: options.attachmentFilename,
|
||||
attachment_extension: options.attachmentExtension,
|
||||
include_nsfw: options.nsfw,
|
||||
};
|
||||
|
||||
for (const key in options) if (options[key] === undefined) delete options[key];
|
||||
@@ -251,13 +252,13 @@ class RESTMethods {
|
||||
});
|
||||
}
|
||||
|
||||
createChannel(guild, channelName, channelType, overwrites) {
|
||||
createChannel(guild, channelName, channelType, overwrites, reason) {
|
||||
if (overwrites instanceof Collection) overwrites = overwrites.array();
|
||||
return this.rest.makeRequest('post', Endpoints.Guild(guild).channels, true, {
|
||||
name: channelName,
|
||||
type: channelType,
|
||||
permission_overwrites: overwrites,
|
||||
}).then(data => this.client.actions.ChannelCreate.handle(data).channel);
|
||||
}, undefined, reason).then(data => this.client.actions.ChannelCreate.handle(data).channel);
|
||||
}
|
||||
|
||||
createDM(recipient) {
|
||||
@@ -284,29 +285,42 @@ class RESTMethods {
|
||||
.then(() => channel);
|
||||
}
|
||||
|
||||
removeUserFromGroupDM(channel, userId) {
|
||||
return this.rest.makeRequest('delete', Endpoints.Channel(channel).Recipient(userId), true)
|
||||
.then(() => channel);
|
||||
}
|
||||
|
||||
updateGroupDMChannel(channel, _data) {
|
||||
const data = {};
|
||||
data.name = _data.name;
|
||||
data.icon = _data.icon;
|
||||
return this.rest.makeRequest('patch', Endpoints.Channel(channel), true, data).then(() => channel);
|
||||
}
|
||||
|
||||
getExistingDM(recipient) {
|
||||
return this.client.channels.find(channel =>
|
||||
channel.recipient && channel.recipient.id === recipient.id
|
||||
);
|
||||
}
|
||||
|
||||
deleteChannel(channel) {
|
||||
deleteChannel(channel, reason) {
|
||||
if (channel instanceof User || channel instanceof GuildMember) channel = this.getExistingDM(channel);
|
||||
if (!channel) return Promise.reject(new Error('No channel to delete.'));
|
||||
return this.rest.makeRequest('delete', Endpoints.Channel(channel), true).then(data => {
|
||||
data.id = channel.id;
|
||||
return this.client.actions.ChannelDelete.handle(data).channel;
|
||||
});
|
||||
return this.rest.makeRequest('delete', Endpoints.Channel(channel), true, undefined, undefined, reason)
|
||||
.then(data => {
|
||||
data.id = channel.id;
|
||||
return this.client.actions.ChannelDelete.handle(data).channel;
|
||||
});
|
||||
}
|
||||
|
||||
updateChannel(channel, _data) {
|
||||
updateChannel(channel, _data, reason) {
|
||||
const data = {};
|
||||
data.name = (_data.name || channel.name).trim();
|
||||
data.topic = _data.topic || channel.topic;
|
||||
data.position = _data.position || channel.position;
|
||||
data.bitrate = _data.bitrate || channel.bitrate;
|
||||
data.user_limit = _data.userLimit || channel.userLimit;
|
||||
return this.rest.makeRequest('patch', Endpoints.Channel(channel), true, data).then(newData =>
|
||||
return this.rest.makeRequest('patch', Endpoints.Channel(channel), true, data, undefined, reason).then(newData =>
|
||||
this.client.actions.ChannelUpdate.handle(newData).updated
|
||||
);
|
||||
}
|
||||
@@ -361,7 +375,7 @@ class RESTMethods {
|
||||
const user = this.client.user;
|
||||
const data = {};
|
||||
data.username = _data.username || user.username;
|
||||
data.avatar = this.client.resolver.resolveBase64(_data.avatar) || user.avatar;
|
||||
data.avatar = typeof _data.avatar === 'undefined' ? user.avatar : this.client.resolver.resolveBase64(_data.avatar);
|
||||
if (!user.bot) {
|
||||
data.email = _data.email || user.email;
|
||||
data.password = password;
|
||||
@@ -372,58 +386,57 @@ class RESTMethods {
|
||||
);
|
||||
}
|
||||
|
||||
updateGuild(guild, _data) {
|
||||
const data = {};
|
||||
if (_data.name) data.name = _data.name;
|
||||
if (_data.region) data.region = _data.region;
|
||||
if (_data.verificationLevel) data.verification_level = Number(_data.verificationLevel);
|
||||
if (_data.afkChannel) data.afk_channel_id = this.client.resolver.resolveChannel(_data.afkChannel).id;
|
||||
if (_data.afkTimeout) data.afk_timeout = Number(_data.afkTimeout);
|
||||
if (_data.icon) data.icon = this.client.resolver.resolveBase64(_data.icon);
|
||||
if (_data.owner) data.owner_id = this.client.resolver.resolveUser(_data.owner).id;
|
||||
if (_data.splash) data.splash = this.client.resolver.resolveBase64(_data.splash);
|
||||
return this.rest.makeRequest('patch', Endpoints.Guild(guild), true, data).then(newData =>
|
||||
updateGuild(guild, data, reason) {
|
||||
return this.rest.makeRequest('patch', Endpoints.Guild(guild), true, data, undefined, reason).then(newData =>
|
||||
this.client.actions.GuildUpdate.handle(newData).updated
|
||||
);
|
||||
}
|
||||
|
||||
kickGuildMember(guild, member, reason) {
|
||||
const url = `${Endpoints.Guild(guild).Member(member)}?reason=${reason}`;
|
||||
return this.rest.makeRequest('delete', url, true).then(() =>
|
||||
this.client.actions.GuildMemberRemove.handle({
|
||||
guild_id: guild.id,
|
||||
user: member.user,
|
||||
}).member
|
||||
);
|
||||
return this.rest.makeRequest(
|
||||
'delete', Endpoints.Guild(guild).Member(member), true,
|
||||
undefined, undefined, reason)
|
||||
.then(() =>
|
||||
this.client.actions.GuildMemberRemove.handle({
|
||||
guild_id: guild.id,
|
||||
user: member.user,
|
||||
}).member
|
||||
);
|
||||
}
|
||||
|
||||
createGuildRole(guild, data) {
|
||||
createGuildRole(guild, data, reason) {
|
||||
if (data.color) data.color = this.client.resolver.resolveColor(data.color);
|
||||
if (data.permissions) data.permissions = Permissions.resolve(data.permissions);
|
||||
return this.rest.makeRequest('post', Endpoints.Guild(guild).roles, true, data).then(role =>
|
||||
this.client.actions.GuildRoleCreate.handle({
|
||||
return this.rest.makeRequest('post', Endpoints.Guild(guild).roles, true, data, undefined, reason).then(r => {
|
||||
const { role } = this.client.actions.GuildRoleCreate.handle({
|
||||
guild_id: guild.id,
|
||||
role,
|
||||
}).role
|
||||
);
|
||||
role: r,
|
||||
});
|
||||
if (data.position) return role.setPosition(data.position, reason);
|
||||
return role;
|
||||
});
|
||||
}
|
||||
|
||||
deleteGuildRole(role) {
|
||||
return this.rest.makeRequest('delete', Endpoints.Guild(role.guild).Role(role.id), true).then(() =>
|
||||
this.client.actions.GuildRoleDelete.handle({
|
||||
guild_id: role.guild.id,
|
||||
role_id: role.id,
|
||||
}).role
|
||||
);
|
||||
deleteGuildRole(role, reason) {
|
||||
return this.rest.makeRequest(
|
||||
'delete', Endpoints.Guild(role.guild).Role(role.id), true,
|
||||
undefined, undefined, reason)
|
||||
.then(() =>
|
||||
this.client.actions.GuildRoleDelete.handle({
|
||||
guild_id: role.guild.id,
|
||||
role_id: role.id,
|
||||
}).role
|
||||
);
|
||||
}
|
||||
|
||||
setChannelOverwrite(channel, payload) {
|
||||
return this.rest.makeRequest('put', `${Endpoints.Channel(channel).permissions}/${payload.id}`, true, payload);
|
||||
}
|
||||
|
||||
deletePermissionOverwrites(overwrite) {
|
||||
deletePermissionOverwrites(overwrite, reason) {
|
||||
return this.rest.makeRequest(
|
||||
'delete', `${Endpoints.Channel(overwrite.channel).permissions}/${overwrite.id}`, true
|
||||
'delete', `${Endpoints.Channel(overwrite.channel).permissions}/${overwrite.id}`,
|
||||
true, undefined, undefined, reason
|
||||
).then(() => overwrite);
|
||||
}
|
||||
|
||||
@@ -464,8 +477,11 @@ class RESTMethods {
|
||||
});
|
||||
}
|
||||
|
||||
updateGuildMember(member, data) {
|
||||
if (data.channel) data.channel_id = this.client.resolver.resolveChannel(data.channel).id;
|
||||
updateGuildMember(member, data, reason) {
|
||||
if (data.channel) {
|
||||
data.channel_id = this.client.resolver.resolveChannel(data.channel).id;
|
||||
data.channel = null;
|
||||
}
|
||||
if (data.roles) data.roles = data.roles.map(role => role instanceof Role ? role.id : role);
|
||||
|
||||
let endpoint = Endpoints.Member(member);
|
||||
@@ -477,12 +493,12 @@ class RESTMethods {
|
||||
}
|
||||
}
|
||||
|
||||
return this.rest.makeRequest('patch', endpoint, true, data).then(newData =>
|
||||
return this.rest.makeRequest('patch', endpoint, true, data, undefined, reason).then(newData =>
|
||||
member.guild._updateMember(member, newData).mem
|
||||
);
|
||||
}
|
||||
|
||||
addMemberRole(member, role) {
|
||||
addMemberRole(member, role, reason) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (member._roles.includes(role.id)) return resolve(member);
|
||||
|
||||
@@ -497,15 +513,16 @@ class RESTMethods {
|
||||
const timeout = this.client.setTimeout(() =>
|
||||
this.client.removeListener(Constants.Events.GUILD_MEMBER_UPDATE, listener), 10e3);
|
||||
|
||||
return this.rest.makeRequest('put', Endpoints.Member(member).Role(role.id), true).catch(err => {
|
||||
this.client.removeListener(Constants.Events.GUILD_BAN_REMOVE, listener);
|
||||
this.client.clearTimeout(timeout);
|
||||
reject(err);
|
||||
});
|
||||
return this.rest.makeRequest('put', Endpoints.Member(member).Role(role.id), true, undefined, undefined, reason)
|
||||
.catch(err => {
|
||||
this.client.removeListener(Constants.Events.GUILD_BAN_REMOVE, listener);
|
||||
this.client.clearTimeout(timeout);
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
removeMemberRole(member, role) {
|
||||
removeMemberRole(member, role, reason) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!member._roles.includes(role.id)) return resolve(member);
|
||||
|
||||
@@ -520,11 +537,12 @@ class RESTMethods {
|
||||
const timeout = this.client.setTimeout(() =>
|
||||
this.client.removeListener(Constants.Events.GUILD_MEMBER_UPDATE, listener), 10e3);
|
||||
|
||||
return this.rest.makeRequest('delete', Endpoints.Member(member).Role(role.id), true).catch(err => {
|
||||
this.client.removeListener(Constants.Events.GUILD_BAN_REMOVE, listener);
|
||||
this.client.clearTimeout(timeout);
|
||||
reject(err);
|
||||
});
|
||||
return this.rest.makeRequest('delete', Endpoints.Member(member).Role(role.id), true, undefined, undefined, reason)
|
||||
.catch(err => {
|
||||
this.client.removeListener(Constants.Events.GUILD_BAN_REMOVE, listener);
|
||||
this.client.clearTimeout(timeout);
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -548,7 +566,7 @@ class RESTMethods {
|
||||
});
|
||||
}
|
||||
|
||||
unbanGuildMember(guild, member) {
|
||||
unbanGuildMember(guild, member, reason) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const id = this.client.resolver.resolveUserID(member);
|
||||
if (!id) throw new Error('Couldn\'t resolve the user ID to unban.');
|
||||
@@ -567,11 +585,12 @@ class RESTMethods {
|
||||
reject(new Error('Took too long to receive the ban remove event.'));
|
||||
}, 10000);
|
||||
|
||||
this.rest.makeRequest('delete', `${Endpoints.Guild(guild).bans}/${id}`, true).catch(err => {
|
||||
this.client.removeListener(Constants.Events.GUILD_BAN_REMOVE, listener);
|
||||
this.client.clearTimeout(timeout);
|
||||
reject(err);
|
||||
});
|
||||
this.rest.makeRequest('delete', `${Endpoints.Guild(guild).bans}/${id}`, true, undefined, undefined, reason)
|
||||
.catch(err => {
|
||||
this.client.removeListener(Constants.Events.GUILD_BAN_REMOVE, listener);
|
||||
this.client.clearTimeout(timeout);
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -587,7 +606,7 @@ class RESTMethods {
|
||||
);
|
||||
}
|
||||
|
||||
updateGuildRole(role, _data) {
|
||||
updateGuildRole(role, _data, reason) {
|
||||
const data = {};
|
||||
data.name = _data.name || role.name;
|
||||
data.position = typeof _data.position !== 'undefined' ? _data.position : role.position;
|
||||
@@ -598,12 +617,13 @@ class RESTMethods {
|
||||
if (_data.permissions) data.permissions = Permissions.resolve(_data.permissions);
|
||||
else data.permissions = role.permissions;
|
||||
|
||||
return this.rest.makeRequest('patch', Endpoints.Guild(role.guild).Role(role.id), true, data).then(_role =>
|
||||
this.client.actions.GuildRoleUpdate.handle({
|
||||
role: _role,
|
||||
guild_id: role.guild.id,
|
||||
}).updated
|
||||
);
|
||||
return this.rest.makeRequest('patch', Endpoints.Guild(role.guild).Role(role.id), true, data, undefined, reason)
|
||||
.then(_role =>
|
||||
this.client.actions.GuildRoleUpdate.handle({
|
||||
role: _role,
|
||||
guild_id: role.guild.id,
|
||||
}).updated
|
||||
);
|
||||
}
|
||||
|
||||
pinMessage(message) {
|
||||
@@ -620,17 +640,19 @@ class RESTMethods {
|
||||
return this.rest.makeRequest('get', Endpoints.Channel(channel).pins, true);
|
||||
}
|
||||
|
||||
createChannelInvite(channel, options) {
|
||||
createChannelInvite(channel, options, reason) {
|
||||
const payload = {};
|
||||
payload.temporary = options.temporary;
|
||||
payload.max_age = options.maxAge;
|
||||
payload.max_uses = options.maxUses;
|
||||
return this.rest.makeRequest('post', Endpoints.Channel(channel).invites, true, payload)
|
||||
payload.unique = options.unique;
|
||||
return this.rest.makeRequest('post', Endpoints.Channel(channel).invites, true, payload, undefined, reason)
|
||||
.then(invite => new Invite(this.client, invite));
|
||||
}
|
||||
|
||||
deleteInvite(invite) {
|
||||
return this.rest.makeRequest('delete', Endpoints.Invite(invite.code), true).then(() => invite);
|
||||
deleteInvite(invite, reason) {
|
||||
return this.rest.makeRequest('delete', Endpoints.Invite(invite.code), true, undefined, undefined, reason)
|
||||
.then(() => invite);
|
||||
}
|
||||
|
||||
getInvite(code) {
|
||||
@@ -650,28 +672,31 @@ class RESTMethods {
|
||||
});
|
||||
}
|
||||
|
||||
pruneGuildMembers(guild, days, dry) {
|
||||
return this.rest.makeRequest(dry ? 'get' : 'post', `${Endpoints.Guild(guild).prune}?days=${days}`, true)
|
||||
pruneGuildMembers(guild, days, dry, reason) {
|
||||
return this.rest.makeRequest(dry ?
|
||||
'get' :
|
||||
'post',
|
||||
`${Endpoints.Guild(guild).prune}?days=${days}`, true, undefined, undefined, reason)
|
||||
.then(data => data.pruned);
|
||||
}
|
||||
|
||||
createEmoji(guild, image, name, roles) {
|
||||
createEmoji(guild, image, name, roles, reason) {
|
||||
const data = { image, name };
|
||||
if (roles) data.roles = roles.map(r => r.id ? r.id : r);
|
||||
return this.rest.makeRequest('post', Endpoints.Guild(guild).emojis, true, data)
|
||||
return this.rest.makeRequest('post', Endpoints.Guild(guild).emojis, true, data, undefined, reason)
|
||||
.then(emoji => this.client.actions.GuildEmojiCreate.handle(guild, emoji).emoji);
|
||||
}
|
||||
|
||||
updateEmoji(emoji, _data) {
|
||||
updateEmoji(emoji, _data, reason) {
|
||||
const data = {};
|
||||
if (_data.name) data.name = _data.name;
|
||||
if (_data.roles) data.roles = _data.roles.map(r => r.id ? r.id : r);
|
||||
return this.rest.makeRequest('patch', Endpoints.Guild(emoji.guild).Emoji(emoji.id), true, data)
|
||||
return this.rest.makeRequest('patch', Endpoints.Guild(emoji.guild).Emoji(emoji.id), true, data, undefined, reason)
|
||||
.then(newEmoji => this.client.actions.GuildEmojiUpdate.handle(emoji, newEmoji).emoji);
|
||||
}
|
||||
|
||||
deleteEmoji(emoji) {
|
||||
return this.rest.makeRequest('delete', Endpoints.Guild(emoji.guild).Emoji(emoji.id), true)
|
||||
deleteEmoji(emoji, reason) {
|
||||
return this.rest.makeRequest('delete', Endpoints.Guild(emoji.guild).Emoji(emoji.id), true, undefined, reason)
|
||||
.then(() => this.client.actions.GuildEmojiDelete.handle(emoji).data);
|
||||
}
|
||||
|
||||
@@ -714,8 +739,8 @@ class RESTMethods {
|
||||
});
|
||||
}
|
||||
|
||||
createWebhook(channel, name, avatar) {
|
||||
return this.rest.makeRequest('post', Endpoints.Channel(channel).webhooks, true, { name, avatar })
|
||||
createWebhook(channel, name, avatar, reason) {
|
||||
return this.rest.makeRequest('post', Endpoints.Channel(channel).webhooks, true, { name, avatar }, undefined, reason)
|
||||
.then(data => new Webhook(this.client, data));
|
||||
}
|
||||
|
||||
@@ -730,25 +755,36 @@ class RESTMethods {
|
||||
});
|
||||
}
|
||||
|
||||
deleteWebhook(webhook) {
|
||||
return this.rest.makeRequest('delete', Endpoints.Webhook(webhook.id, webhook.token), false);
|
||||
deleteWebhook(webhook, reason) {
|
||||
return this.rest.makeRequest(
|
||||
'delete', Endpoints.Webhook(webhook.id, webhook.token),
|
||||
false, undefined, undefined, reason);
|
||||
}
|
||||
|
||||
sendWebhookMessage(webhook, content, { avatarURL, tts, disableEveryone, embeds, username } = {}, file = null) {
|
||||
username = username || webhook.name;
|
||||
if (typeof content !== 'undefined') content = this.client.resolver.resolveString(content);
|
||||
if (content) {
|
||||
if (disableEveryone || (typeof disableEveryone === 'undefined' && this.client.options.disableEveryone)) {
|
||||
content = content.replace(/@(everyone|here)/g, '@\u200b$1');
|
||||
sendWebhookMessage(webhook, content, { avatarURL, tts, embeds, username } = {}, files = null) {
|
||||
return new Promise((resolve, reject) => {
|
||||
username = username || webhook.name;
|
||||
|
||||
if (content instanceof Array) {
|
||||
const messages = [];
|
||||
(function sendChunk(list, index) {
|
||||
const options = index === list.length - 1 ? { tts, embeds, files } : { tts };
|
||||
webhook.send(list[index], options).then(message => {
|
||||
messages.push(message);
|
||||
if (index >= list.length - 1) return resolve(messages);
|
||||
return sendChunk(list, ++index);
|
||||
}).catch(reject);
|
||||
}(content, 0));
|
||||
} else {
|
||||
this.rest.makeRequest('post', `${Endpoints.Webhook(webhook.id, webhook.token)}?wait=true`, false, {
|
||||
username,
|
||||
avatar_url: avatarURL,
|
||||
content,
|
||||
tts,
|
||||
embeds,
|
||||
}, files).then(resolve, reject);
|
||||
}
|
||||
}
|
||||
return this.rest.makeRequest('post', `${Endpoints.Webhook(webhook.id, webhook.token)}?wait=true`, false, {
|
||||
username,
|
||||
avatar_url: avatarURL,
|
||||
content,
|
||||
tts,
|
||||
embeds,
|
||||
}, file);
|
||||
});
|
||||
}
|
||||
|
||||
sendSlackWebhookMessage(webhook, body) {
|
||||
@@ -763,12 +799,13 @@ class RESTMethods {
|
||||
);
|
||||
}
|
||||
|
||||
fetchMeMentions(options) {
|
||||
if (options.guild) options.guild = options.guild.id ? options.guild.id : options.guild;
|
||||
fetchMentions(options) {
|
||||
if (options.guild instanceof Guild) options.guild = options.guild.id;
|
||||
Util.mergeDefault({ limit: 25, roles: true, everyone: true, guild: null }, options);
|
||||
|
||||
return this.rest.makeRequest(
|
||||
'get',
|
||||
Endpoints.User('@me').mentions(options.limit, options.roles, options.everyone, options.guild)
|
||||
).then(res => res.body.map(m => new Message(this.client.channels.get(m.channel_id), m, this.client)));
|
||||
'get', Endpoints.User('@me').Mentions(options.limit, options.roles, options.everyone, options.guild), true
|
||||
).then(data => data.map(m => new Message(this.client.channels.get(m.channel_id), m, this.client)));
|
||||
}
|
||||
|
||||
addFriend(user) {
|
||||
@@ -833,7 +870,7 @@ class RESTMethods {
|
||||
'put', Endpoints.Message(message).Reaction(emoji).User('@me'), true
|
||||
).then(() =>
|
||||
message._addReaction(Util.parseEmoji(emoji), message.client.user)
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
removeMessageReaction(message, emoji, userID) {
|
||||
@@ -864,7 +901,8 @@ class RESTMethods {
|
||||
}
|
||||
|
||||
resetApplication(id) {
|
||||
return this.rest.makeRequest('post', Endpoints.OAUTH2.Application(id).reset, true)
|
||||
return this.rest.makeRequest('post', Endpoints.OAUTH2.Application(id).resetToken, true)
|
||||
.then(() => this.rest.makeRequest('post', Endpoints.OAUTH2.Application(id).resetSecret, true))
|
||||
.then(app => new OAuth2Application(this.client, app));
|
||||
}
|
||||
|
||||
@@ -894,6 +932,10 @@ class RESTMethods {
|
||||
patchUserSettings(data) {
|
||||
return this.rest.makeRequest('patch', Constants.Endpoints.User('@me').settings, true, data);
|
||||
}
|
||||
|
||||
patchClientUserGuildSettings(guildID, data) {
|
||||
return this.rest.makeRequest('patch', Constants.Endpoints.User('@me').Guild(guildID).settings, true, data);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RESTMethods;
|
||||
|
||||
@@ -40,8 +40,14 @@ class BurstRequestHandler extends RequestHandler {
|
||||
this.handle();
|
||||
this.resetTimeout = null;
|
||||
}, Number(res.headers['retry-after']) + this.client.options.restTimeOffset);
|
||||
} else if (err.status >= 500 && err.status < 600) {
|
||||
this.queue.unshift(item);
|
||||
this.resetTimeout = this.client.setTimeout(() => {
|
||||
this.handle();
|
||||
this.resetTimeout = null;
|
||||
}, 1e3 + this.client.options.restTimeOffset);
|
||||
} else {
|
||||
item.reject(err.status === 400 ? new DiscordAPIError(res.body) : err);
|
||||
item.reject(err.status >= 400 && err.status < 500 ? new DiscordAPIError(res.request.path, res.body) : err);
|
||||
this.handle();
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -64,8 +64,11 @@ class SequentialRequestHandler extends RequestHandler {
|
||||
resolve();
|
||||
}, Number(res.headers['retry-after']) + this.restManager.client.options.restTimeOffset);
|
||||
if (res.headers['x-ratelimit-global']) this.globalLimit = true;
|
||||
} else if (err.status >= 500 && err.status < 600) {
|
||||
this.queue.unshift(item);
|
||||
this.restManager.client.setTimeout(resolve, 1e3 + this.client.options.restTimeOffset);
|
||||
} else {
|
||||
item.reject(err.status >= 400 && err.status < 500 ? new DiscordAPIError(res.body) : err);
|
||||
item.reject(err.status >= 400 && err.status < 500 ? new DiscordAPIError(res.request.path, res.body) : err);
|
||||
resolve(err);
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -18,7 +18,7 @@ const ffmpegArguments = [
|
||||
* ```js
|
||||
* const broadcast = client.createVoiceBroadcast();
|
||||
* broadcast.playFile('./music.mp3');
|
||||
* // play "music.mp3" in all voice connections that the client is in
|
||||
* // Play "music.mp3" in all voice connections that the client is in
|
||||
* for (const connection of client.voiceConnections.values()) {
|
||||
* connection.playBroadcast(broadcast);
|
||||
* }
|
||||
@@ -136,15 +136,15 @@ class VoiceBroadcast extends VolumeInterface {
|
||||
* const broadcast = client.createVoiceBroadcast();
|
||||
*
|
||||
* voiceChannel.join()
|
||||
* .then(connection => {
|
||||
* const stream = ytdl('https://www.youtube.com/watch?v=XAWgeLF9EVQ', { filter : 'audioonly' });
|
||||
* broadcast.playStream(stream);
|
||||
* const dispatcher = connection.playBroadcast(broadcast);
|
||||
* })
|
||||
* .catch(console.error);
|
||||
* .then(connection => {
|
||||
* const stream = ytdl('https://www.youtube.com/watch?v=XAWgeLF9EVQ', { filter : 'audioonly' });
|
||||
* broadcast.playStream(stream);
|
||||
* const dispatcher = connection.playBroadcast(broadcast);
|
||||
* })
|
||||
* .catch(console.error);
|
||||
*/
|
||||
playStream(stream, { seek = 0, volume = 1, passes = 1 } = {}) {
|
||||
const options = { seek, volume, passes, stream };
|
||||
playStream(stream, options = {}) {
|
||||
this.setVolume(options.volume || 1);
|
||||
return this._playTranscodable(stream, options);
|
||||
}
|
||||
|
||||
@@ -158,25 +158,23 @@ class VoiceBroadcast extends VolumeInterface {
|
||||
* const broadcast = client.createVoiceBroadcast();
|
||||
*
|
||||
* voiceChannel.join()
|
||||
* .then(connection => {
|
||||
* broadcast.playFile('C:/Users/Discord/Desktop/music.mp3');
|
||||
* const dispatcher = connection.playBroadcast(broadcast);
|
||||
* })
|
||||
* .catch(console.error);
|
||||
* .then(connection => {
|
||||
* broadcast.playFile('C:/Users/Discord/Desktop/music.mp3');
|
||||
* const dispatcher = connection.playBroadcast(broadcast);
|
||||
* })
|
||||
* .catch(console.error);
|
||||
*/
|
||||
playFile(file, { seek = 0, volume = 1, passes = 1 } = {}) {
|
||||
const options = { seek, volume, passes };
|
||||
playFile(file, options = {}) {
|
||||
this.setVolume(options.volume || 1);
|
||||
return this._playTranscodable(`file:${file}`, options);
|
||||
}
|
||||
|
||||
_playTranscodable(media, options) {
|
||||
OpusEncoders.guaranteeOpusEngine();
|
||||
|
||||
this.killCurrentTranscoder();
|
||||
const transcoder = this.prism.transcode({
|
||||
type: 'ffmpeg',
|
||||
media,
|
||||
ffmpegArguments: ffmpegArguments.concat(['-ss', String(options.seek)]),
|
||||
ffmpegArguments: ffmpegArguments.concat(['-ss', String(options.seek || 0)]),
|
||||
});
|
||||
/**
|
||||
* Emitted whenever an error occurs.
|
||||
@@ -206,31 +204,28 @@ class VoiceBroadcast extends VolumeInterface {
|
||||
}
|
||||
|
||||
/**
|
||||
* Plays a stream of 16-bit signed stereo PCM at 48KHz.
|
||||
* Plays a stream of 16-bit signed stereo PCM.
|
||||
* @param {ReadableStream} stream The audio stream to play
|
||||
* @param {StreamOptions} [options] Options for playing the stream
|
||||
* @returns {VoiceBroadcast}
|
||||
*/
|
||||
playConvertedStream(stream, { seek = 0, volume = 1, passes = 1 } = {}) {
|
||||
OpusEncoders.guaranteeOpusEngine();
|
||||
|
||||
playConvertedStream(stream, options = {}) {
|
||||
this.killCurrentTranscoder();
|
||||
const options = { seek, volume, passes, stream };
|
||||
this.currentTranscoder = { options };
|
||||
this.setVolume(options.volume || 1);
|
||||
this.currentTranscoder = { options: { stream } };
|
||||
stream.once('readable', () => this._startPlaying());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Plays an Opus encoded stream at 48KHz.
|
||||
* Plays an Opus encoded stream.
|
||||
* <warn>Note that inline volume is not compatible with this method.</warn>
|
||||
* @param {ReadableStream} stream The Opus audio stream to play
|
||||
* @param {StreamOptions} [options] Options for playing the stream
|
||||
* @returns {StreamDispatcher}
|
||||
*/
|
||||
playOpusStream(stream, { seek = 0, passes = 1 } = {}) {
|
||||
const options = { seek, passes, stream };
|
||||
this.currentTranscoder = { options, opus: true };
|
||||
playOpusStream(stream) {
|
||||
this.currentTranscoder = { options: { stream }, opus: true };
|
||||
stream.once('readable', () => this._startPlaying());
|
||||
return this;
|
||||
}
|
||||
@@ -241,10 +236,9 @@ class VoiceBroadcast extends VolumeInterface {
|
||||
* @param {StreamOptions} [options] Options for playing the stream
|
||||
* @returns {VoiceBroadcast}
|
||||
*/
|
||||
playArbitraryInput(input, { seek = 0, volume = 1, passes = 1 } = {}) {
|
||||
this.guaranteeOpusEngine();
|
||||
|
||||
const options = { seek, volume, passes, input };
|
||||
playArbitraryInput(input, options = {}) {
|
||||
this.setVolume(options.volume || 1);
|
||||
options.input = input;
|
||||
return this._playTranscodable(input, options);
|
||||
}
|
||||
|
||||
@@ -272,10 +266,6 @@ class VoiceBroadcast extends VolumeInterface {
|
||||
}
|
||||
}
|
||||
|
||||
guaranteeOpusEngine() {
|
||||
if (!this.opusEncoder) throw new Error('Couldn\'t find an Opus engine.');
|
||||
}
|
||||
|
||||
_startPlaying() {
|
||||
if (this.tickInterval) clearInterval(this.tickInterval);
|
||||
// Old code?
|
||||
|
||||
@@ -8,12 +8,13 @@ const EventEmitter = require('events').EventEmitter;
|
||||
const Prism = require('prism-media');
|
||||
|
||||
/**
|
||||
* Represents a connection to a voice channel in Discord.
|
||||
* Represents a connection to a guild's voice server.
|
||||
* ```js
|
||||
* // Obtained using:
|
||||
* voiceChannel.join().then(connection => {
|
||||
* voiceChannel.join()
|
||||
* .then(connection => {
|
||||
*
|
||||
* });
|
||||
* });
|
||||
* ```
|
||||
* @extends {EventEmitter}
|
||||
*/
|
||||
@@ -132,6 +133,7 @@ class VoiceConnection extends EventEmitter {
|
||||
*/
|
||||
setSpeaking(value) {
|
||||
if (this.speaking === value) return;
|
||||
if (this.status !== Constants.VoiceStatus.CONNECTED) return;
|
||||
this.speaking = value;
|
||||
this.sockets.ws.sendPacket({
|
||||
op: Constants.VoiceOPCodes.SPEAKING,
|
||||
@@ -163,7 +165,7 @@ class VoiceConnection extends EventEmitter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the token and endpoint required to connect to the the voice servers.
|
||||
* Set the token and endpoint required to connect to the voice servers.
|
||||
* @param {string} token The voice token
|
||||
* @param {string} endpoint The voice endpoint
|
||||
* @returns {void}
|
||||
@@ -245,7 +247,6 @@ class VoiceConnection extends EventEmitter {
|
||||
*/
|
||||
authenticateFailed(reason) {
|
||||
clearTimeout(this.connectTimeout);
|
||||
this.status = Constants.VoiceStatus.DISCONNECTED;
|
||||
if (this.status === Constants.VoiceStatus.AUTHENTICATING) {
|
||||
/**
|
||||
* Emitted when we fail to initiate a voice connection.
|
||||
@@ -256,6 +257,7 @@ class VoiceConnection extends EventEmitter {
|
||||
} else {
|
||||
this.emit('error', new Error(reason));
|
||||
}
|
||||
this.status = Constants.VoiceStatus.DISCONNECTED;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -430,6 +432,8 @@ class VoiceConnection extends EventEmitter {
|
||||
* @property {number} [seek=0] The time to seek to
|
||||
* @property {number} [volume=1] The volume to play at
|
||||
* @property {number} [passes=1] How many times to send the voice packet to reduce packet loss
|
||||
* @property {number|string} [bitrate=48000] The bitrate (quality) of the audio.
|
||||
* If set to 'auto', the voice channel's bitrate will be used
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -440,10 +444,10 @@ class VoiceConnection extends EventEmitter {
|
||||
* @example
|
||||
* // Play files natively
|
||||
* voiceChannel.join()
|
||||
* .then(connection => {
|
||||
* const dispatcher = connection.playFile('C:/Users/Discord/Desktop/music.mp3');
|
||||
* })
|
||||
* .catch(console.error);
|
||||
* .then(connection => {
|
||||
* const dispatcher = connection.playFile('C:/Users/Discord/Desktop/music.mp3');
|
||||
* })
|
||||
* .catch(console.error);
|
||||
*/
|
||||
playFile(file, options) {
|
||||
return this.player.playUnknownStream(`file:${file}`, options);
|
||||
@@ -469,18 +473,18 @@ class VoiceConnection extends EventEmitter {
|
||||
* const ytdl = require('ytdl-core');
|
||||
* const streamOptions = { seek: 0, volume: 1 };
|
||||
* voiceChannel.join()
|
||||
* .then(connection => {
|
||||
* const stream = ytdl('https://www.youtube.com/watch?v=XAWgeLF9EVQ', { filter : 'audioonly' });
|
||||
* const dispatcher = connection.playStream(stream, streamOptions);
|
||||
* })
|
||||
* .catch(console.error);
|
||||
* .then(connection => {
|
||||
* const stream = ytdl('https://www.youtube.com/watch?v=XAWgeLF9EVQ', { filter : 'audioonly' });
|
||||
* const dispatcher = connection.playStream(stream, streamOptions);
|
||||
* })
|
||||
* .catch(console.error);
|
||||
*/
|
||||
playStream(stream, options) {
|
||||
return this.player.playUnknownStream(stream, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Plays a stream of 16-bit signed stereo PCM at 48KHz.
|
||||
* Plays a stream of 16-bit signed stereo PCM.
|
||||
* @param {ReadableStream} stream The audio stream to play
|
||||
* @param {StreamOptions} [options] Options for playing the stream
|
||||
* @returns {StreamDispatcher}
|
||||
@@ -490,7 +494,7 @@ class VoiceConnection extends EventEmitter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Plays an Opus encoded stream at 48KHz.
|
||||
* Plays an Opus encoded stream.
|
||||
* <warn>Note that inline volume is not compatible with this method.</warn>
|
||||
* @param {ReadableStream} stream The Opus audio stream to play
|
||||
* @param {StreamOptions} [options] Options for playing the stream
|
||||
@@ -503,6 +507,7 @@ class VoiceConnection extends EventEmitter {
|
||||
/**
|
||||
* Plays a voice broadcast.
|
||||
* @param {VoiceBroadcast} broadcast The broadcast to play
|
||||
* @param {StreamOptions} [options] Options for playing the stream
|
||||
* @returns {StreamDispatcher}
|
||||
* @example
|
||||
* // Play a broadcast
|
||||
@@ -511,12 +516,13 @@ class VoiceConnection extends EventEmitter {
|
||||
* .playFile('./test.mp3');
|
||||
* const dispatcher = voiceConnection.playBroadcast(broadcast);
|
||||
*/
|
||||
playBroadcast(broadcast) {
|
||||
return this.player.playBroadcast(broadcast);
|
||||
playBroadcast(broadcast, options) {
|
||||
return this.player.playBroadcast(broadcast, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a VoiceReceiver so you can start listening to voice data. It's recommended to only create one of these.
|
||||
* Creates a VoiceReceiver so you can start listening to voice data.
|
||||
* It's recommended to only create one of these.
|
||||
* @returns {VoiceReceiver}
|
||||
*/
|
||||
createReceiver() {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const VolumeInterface = require('../util/VolumeInterface');
|
||||
const VoiceBroadcast = require('../VoiceBroadcast');
|
||||
const Constants = require('../../../util/Constants');
|
||||
|
||||
const secretbox = require('../util/Secretbox');
|
||||
|
||||
@@ -88,12 +89,12 @@ class StreamDispatcher extends VolumeInterface {
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops sending voice packets to the voice connection (stream may still progress however)
|
||||
* Stops sending voice packets to the voice connection (stream may still progress however).
|
||||
*/
|
||||
pause() { this.setPaused(true); }
|
||||
|
||||
/**
|
||||
* Resumes sending voice packets to the voice connection (may be further on in the stream than when paused)
|
||||
* Resumes sending voice packets to the voice connection (may be further on in the stream than when paused).
|
||||
*/
|
||||
resume() { this.setPaused(false); }
|
||||
|
||||
@@ -108,6 +109,7 @@ class StreamDispatcher extends VolumeInterface {
|
||||
|
||||
setSpeaking(value) {
|
||||
if (this.speaking === value) return;
|
||||
if (this.player.voiceConnection.status !== Constants.VoiceStatus.CONNECTED) return;
|
||||
this.speaking = value;
|
||||
/**
|
||||
* Emitted when the dispatcher starts/stops speaking.
|
||||
@@ -117,6 +119,16 @@ class StreamDispatcher extends VolumeInterface {
|
||||
this.emit('speaking', value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the bitrate of the current Opus encoder.
|
||||
* @param {number} bitrate New bitrate, in kbps
|
||||
* If set to 'auto', the voice channel's bitrate will be used
|
||||
*/
|
||||
setBitrate(bitrate) {
|
||||
this.player.setBitrate(bitrate);
|
||||
}
|
||||
|
||||
sendBuffer(buffer, sequence, timestamp, opusPacket) {
|
||||
opusPacket = opusPacket || this.player.opusEncoder.encode(buffer);
|
||||
const packet = this.createPacket(sequence, timestamp, opusPacket);
|
||||
@@ -128,7 +140,7 @@ class StreamDispatcher extends VolumeInterface {
|
||||
/**
|
||||
* Emitted whenever the dispatcher has debug information.
|
||||
* @event StreamDispatcher#debug
|
||||
* @param {string} info the debug info
|
||||
* @param {string} info The debug info
|
||||
*/
|
||||
this.setSpeaking(true);
|
||||
while (repeats--) {
|
||||
@@ -282,7 +294,7 @@ class StreamDispatcher extends VolumeInterface {
|
||||
this.emit(type, reason);
|
||||
/**
|
||||
* Emitted once the dispatcher ends.
|
||||
* @param {string} [reason] the reason the dispatcher ended
|
||||
* @param {string} [reason] The reason the dispatcher ended
|
||||
* @event StreamDispatcher#end
|
||||
*/
|
||||
if (type !== 'end') this.emit('end', `destroyed due to ${type} - ${reason}`);
|
||||
|
||||
@@ -5,20 +5,37 @@
|
||||
class BaseOpus {
|
||||
/**
|
||||
* @param {Object} [options] The options to apply to the Opus engine
|
||||
* @param {boolean} [options.fec] Whether to enable forward error correction (defaults to false)
|
||||
* @param {number} [options.plp] The expected packet loss percentage (0-1 inclusive, defaults to 0)
|
||||
* @param {number} [options.bitrate=48] The desired bitrate (kbps)
|
||||
* @param {boolean} [options.fec=false] Whether to enable forward error correction
|
||||
* @param {number} [options.plp=0] The expected packet loss percentage
|
||||
*/
|
||||
constructor(options = {}) {
|
||||
constructor({ bitrate = 48, fec = false, plp = 0 } = {}) {
|
||||
this.ctl = {
|
||||
BITRATE: 4002,
|
||||
FEC: 4012,
|
||||
PLP: 4014,
|
||||
};
|
||||
|
||||
this.options = options;
|
||||
this.samplingRate = 48000;
|
||||
this.channels = 2;
|
||||
|
||||
/**
|
||||
* The desired bitrate (kbps)
|
||||
* @type {number}
|
||||
*/
|
||||
this.bitrate = bitrate;
|
||||
|
||||
/**
|
||||
* Miscellaneous Opus options
|
||||
* @type {Object}
|
||||
*/
|
||||
this.options = { fec, plp };
|
||||
}
|
||||
|
||||
init() {
|
||||
try {
|
||||
this.setBitrate(this.bitrate);
|
||||
|
||||
// Set FEC (forward error correction)
|
||||
if (this.options.fec) this.setFEC(this.options.fec);
|
||||
|
||||
|
||||
@@ -10,10 +10,14 @@ class NodeOpusEngine extends OpusEngine {
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
this.encoder = new opus.OpusEncoder(48000, 2);
|
||||
this.encoder = new opus.OpusEncoder(this.samplingRate, this.channels);
|
||||
super.init();
|
||||
}
|
||||
|
||||
setBitrate(bitrate) {
|
||||
this.encoder.applyEncoderCTL(this.ctl.BITRATE, Math.min(128, Math.max(16, bitrate)) * 1000);
|
||||
}
|
||||
|
||||
setFEC(enabled) {
|
||||
this.encoder.applyEncoderCTL(this.ctl.FEC, enabled ? 1 : 0);
|
||||
}
|
||||
|
||||
@@ -3,13 +3,14 @@ const list = [
|
||||
require('./OpusScriptEngine'),
|
||||
];
|
||||
|
||||
let opusEngineFound;
|
||||
|
||||
function fetch(Encoder, engineOptions) {
|
||||
try {
|
||||
return new Encoder(engineOptions);
|
||||
} catch (err) {
|
||||
return null;
|
||||
if (err.message.includes('Cannot find module')) return null;
|
||||
|
||||
// The Opus engine exists, but another error occurred.
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,10 +23,6 @@ exports.fetch = engineOptions => {
|
||||
const fetched = fetch(encoder, engineOptions);
|
||||
if (fetched) return fetched;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
exports.guaranteeOpusEngine = () => {
|
||||
if (typeof opusEngineFound === 'undefined') opusEngineFound = Boolean(exports.fetch());
|
||||
if (!opusEngineFound) throw new Error('Couldn\'t find an Opus engine.');
|
||||
throw new Error('OPUS_ENGINE_MISSING');
|
||||
};
|
||||
|
||||
@@ -10,10 +10,14 @@ class OpusScriptEngine extends OpusEngine {
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
this.encoder = new OpusScript(48000, 2);
|
||||
this.encoder = new OpusScript(this.samplingRate, this.channels);
|
||||
super.init();
|
||||
}
|
||||
|
||||
setBitrate(bitrate) {
|
||||
this.encoder.encoderCTL(this.ctl.BITRATE, Math.min(128, Math.max(16, bitrate)) * 1000);
|
||||
}
|
||||
|
||||
setFEC(enabled) {
|
||||
this.encoder.encoderCTL(this.ctl.FEC, enabled ? 1 : 0);
|
||||
}
|
||||
|
||||
@@ -30,11 +30,6 @@ class AudioPlayer extends EventEmitter {
|
||||
* @type {Prism}
|
||||
*/
|
||||
this.prism = new Prism();
|
||||
/**
|
||||
* The opus encoder that the player uses
|
||||
* @type {NodeOpusEngine|OpusScriptEngine}
|
||||
*/
|
||||
this.opusEncoder = OpusEncoders.fetch();
|
||||
this.streams = new Collection();
|
||||
this.currentStream = {};
|
||||
this.streamingData = {
|
||||
@@ -67,6 +62,7 @@ class AudioPlayer extends EventEmitter {
|
||||
|
||||
destroy() {
|
||||
if (this.opusEncoder) this.opusEncoder.destroy();
|
||||
this.opusEncoder = null;
|
||||
}
|
||||
|
||||
destroyCurrentStream() {
|
||||
@@ -83,13 +79,25 @@ class AudioPlayer extends EventEmitter {
|
||||
this.currentStream = {};
|
||||
}
|
||||
|
||||
playUnknownStream(stream, { seek = 0, volume = 1, passes = 1 } = {}) {
|
||||
OpusEncoders.guaranteeOpusEngine();
|
||||
const options = { seek, volume, passes };
|
||||
/**
|
||||
* Set the bitrate of the current Opus encoder.
|
||||
* @param {number} value New bitrate, in kbps
|
||||
* If set to 'auto', the voice channel's bitrate will be used
|
||||
*/
|
||||
setBitrate(value) {
|
||||
if (!value) return;
|
||||
if (!this.opusEncoder) return;
|
||||
const bitrate = value === 'auto' ? this.voiceConnection.channel.bitrate : value;
|
||||
this.opusEncoder.setBitrate(bitrate);
|
||||
}
|
||||
|
||||
playUnknownStream(stream, options = {}) {
|
||||
this.destroy();
|
||||
this.opusEncoder = OpusEncoders.fetch(options);
|
||||
const transcoder = this.prism.transcode({
|
||||
type: 'ffmpeg',
|
||||
media: stream,
|
||||
ffmpegArguments: ffmpegArguments.concat(['-ss', String(seek)]),
|
||||
ffmpegArguments: ffmpegArguments.concat(['-ss', String(options.seek || 0)]),
|
||||
});
|
||||
this.destroyCurrentStream();
|
||||
this.currentStream = {
|
||||
@@ -105,9 +113,10 @@ class AudioPlayer extends EventEmitter {
|
||||
return this.playPCMStream(transcoder.output, options, true);
|
||||
}
|
||||
|
||||
playPCMStream(stream, { seek = 0, volume = 1, passes = 1 } = {}, fromUnknown = false) {
|
||||
OpusEncoders.guaranteeOpusEngine();
|
||||
const options = { seek, volume, passes };
|
||||
playPCMStream(stream, options = {}, fromUnknown = false) {
|
||||
this.destroy();
|
||||
this.opusEncoder = OpusEncoders.fetch(options);
|
||||
this.setBitrate(options.bitrate);
|
||||
const dispatcher = this.createDispatcher(stream, options);
|
||||
if (fromUnknown) {
|
||||
this.currentStream.dispatcher = dispatcher;
|
||||
@@ -122,8 +131,8 @@ class AudioPlayer extends EventEmitter {
|
||||
return dispatcher;
|
||||
}
|
||||
|
||||
playOpusStream(stream, { seek = 0, passes = 1 } = {}) {
|
||||
const options = { seek, passes, opus: true };
|
||||
playOpusStream(stream, options = {}) {
|
||||
options.opus = true;
|
||||
this.destroyCurrentStream();
|
||||
const dispatcher = this.createDispatcher(stream, options);
|
||||
this.currentStream = {
|
||||
@@ -134,8 +143,7 @@ class AudioPlayer extends EventEmitter {
|
||||
return dispatcher;
|
||||
}
|
||||
|
||||
playBroadcast(broadcast, { volume = 1, passes = 1 } = {}) {
|
||||
const options = { volume, passes };
|
||||
playBroadcast(broadcast, options) {
|
||||
this.destroyCurrentStream();
|
||||
const dispatcher = this.createDispatcher(broadcast, options);
|
||||
this.currentStream = {
|
||||
@@ -148,7 +156,9 @@ class AudioPlayer extends EventEmitter {
|
||||
return dispatcher;
|
||||
}
|
||||
|
||||
createDispatcher(stream, options) {
|
||||
createDispatcher(stream, { seek = 0, volume = 1, passes = 1 } = {}) {
|
||||
const options = { seek, volume, passes };
|
||||
|
||||
const dispatcher = new StreamDispatcher(this, stream, options);
|
||||
dispatcher.on('end', () => this.destroyCurrentStream());
|
||||
dispatcher.on('error', () => this.destroyCurrentStream());
|
||||
|
||||
@@ -10,9 +10,10 @@ nonce.fill(0);
|
||||
* Receives voice data from a voice connection.
|
||||
* ```js
|
||||
* // Obtained using:
|
||||
* voiceChannel.join().then(connection => {
|
||||
* const receiver = connection.createReceiver();
|
||||
* });
|
||||
* voiceChannel.join()
|
||||
* .then(connection => {
|
||||
* const receiver = connection.createReceiver();
|
||||
* });
|
||||
* ```
|
||||
* @extends {EventEmitter}
|
||||
*/
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const browser = require('os').platform() === 'browser';
|
||||
const browser = typeof window !== 'undefined';
|
||||
const EventEmitter = require('events');
|
||||
const Constants = require('../../util/Constants');
|
||||
const zlib = require('zlib');
|
||||
@@ -29,12 +29,12 @@ const WebSocket = (function findWebSocket() {
|
||||
class WebSocketConnection extends EventEmitter {
|
||||
/**
|
||||
* @param {WebSocketManager} manager The WebSocket manager
|
||||
* @param {string} gateway WebSocket gateway to connect to
|
||||
* @param {string} gateway The WebSocket gateway to connect to
|
||||
*/
|
||||
constructor(manager, gateway) {
|
||||
super();
|
||||
/**
|
||||
* WebSocket Manager of this connection
|
||||
* The WebSocket Manager of this connection
|
||||
* @type {WebSocketManager}
|
||||
*/
|
||||
this.manager = manager;
|
||||
@@ -115,6 +115,10 @@ class WebSocketConnection extends EventEmitter {
|
||||
this.debug('Tried to mark self as ready, but already ready');
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Emitted when the client becomes ready to start working.
|
||||
* @event Client#ready
|
||||
*/
|
||||
this.status = Constants.Status.READY;
|
||||
this.client.emit(Constants.Events.READY);
|
||||
this.packetManager.handleQueue();
|
||||
@@ -228,7 +232,7 @@ class WebSocketConnection extends EventEmitter {
|
||||
|
||||
/**
|
||||
* Creates a connection to a gateway.
|
||||
* @param {string} gateway Gateway to connect to
|
||||
* @param {string} gateway The gateway to connect to
|
||||
* @param {number} [after=0] How long to wait before connecting
|
||||
* @param {boolean} [force=false] Whether or not to force a new connection even if one already exists
|
||||
* @returns {boolean}
|
||||
@@ -280,12 +284,13 @@ class WebSocketConnection extends EventEmitter {
|
||||
* @returns {boolean}
|
||||
*/
|
||||
onMessage(event) {
|
||||
let data;
|
||||
try {
|
||||
event.data = this.unpack(event.data);
|
||||
data = this.unpack(event.data);
|
||||
} catch (err) {
|
||||
this.emit('debug', err);
|
||||
}
|
||||
return this.onPacket(event.data);
|
||||
return this.onPacket(data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -351,11 +356,19 @@ class WebSocketConnection extends EventEmitter {
|
||||
|
||||
/**
|
||||
* Called whenever an error occurs with the WebSocket.
|
||||
* @param {Error} error Error that occurred
|
||||
* @param {Error} error The error that occurred
|
||||
*/
|
||||
onError(error) {
|
||||
if (error && error.message === 'uWs client connection error') {
|
||||
this.reconnect();
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Emitted whenever the client's WebSocket encounters a connection error.
|
||||
* @event Client#error
|
||||
* @param {Error} error The encountered error
|
||||
*/
|
||||
this.client.emit(Constants.Events.ERROR, error);
|
||||
if (error.message === 'uWs client connection error') this.reconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -431,7 +444,7 @@ class WebSocketConnection extends EventEmitter {
|
||||
* @returns {void}
|
||||
*/
|
||||
identify(after) {
|
||||
if (after) return this.client.setTimeout(this.identify.apply(this), after);
|
||||
if (after) return this.client.setTimeout(this.identify.bind(this), after);
|
||||
return this.sessionID ? this.identifyResume() : this.identifyNew();
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ const Constants = require('../../util/Constants');
|
||||
const WebSocketConnection = require('./WebSocketConnection');
|
||||
|
||||
/**
|
||||
* WebSocket Manager of the client
|
||||
* WebSocket Manager of the client.
|
||||
* @private
|
||||
*/
|
||||
class WebSocketManager extends EventEmitter {
|
||||
@@ -23,7 +23,7 @@ class WebSocketManager extends EventEmitter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a heartbeat on the available connection
|
||||
* Sends a heartbeat on the available connection.
|
||||
* @returns {void}
|
||||
*/
|
||||
heartbeat() {
|
||||
@@ -67,7 +67,7 @@ class WebSocketManager extends EventEmitter {
|
||||
|
||||
/**
|
||||
* Connects the client to a gateway.
|
||||
* @param {string} gateway Gateway to connect to
|
||||
* @param {string} gateway The gateway to connect to
|
||||
* @returns {boolean}
|
||||
*/
|
||||
connect(gateway) {
|
||||
|
||||
@@ -39,6 +39,7 @@ class WebSocketPacketManager {
|
||||
this.register(Constants.WSEvents.USER_UPDATE, require('./handlers/UserUpdate'));
|
||||
this.register(Constants.WSEvents.USER_NOTE_UPDATE, require('./handlers/UserNoteUpdate'));
|
||||
this.register(Constants.WSEvents.USER_SETTINGS_UPDATE, require('./handlers/UserSettingsUpdate'));
|
||||
this.register(Constants.WSEvents.USER_GUILD_SETTINGS_UPDATE, require('./handlers/UserGuildSettingsUpdate'));
|
||||
this.register(Constants.WSEvents.VOICE_STATE_UPDATE, require('./handlers/VoiceStateUpdate'));
|
||||
this.register(Constants.WSEvents.TYPING_START, require('./handlers/TypingStart'));
|
||||
this.register(Constants.WSEvents.MESSAGE_CREATE, require('./handlers/MessageCreate'));
|
||||
|
||||
@@ -26,7 +26,7 @@ class GuildMembersChunkHandler extends AbstractHandler {
|
||||
/**
|
||||
* Emitted whenever a chunk of guild members is received (all members come from the same guild).
|
||||
* @event Client#guildMembersChunk
|
||||
* @param {Collection<Snowflake, GuildMember>} members The members in the chunk
|
||||
* @param {GuildMember[]} members The members in the chunk
|
||||
* @param {Guild} guild The guild related to the member chunk
|
||||
*/
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ class ReadyHandler extends AbstractHandler {
|
||||
client.ws.heartbeat();
|
||||
|
||||
data.user.user_settings = data.user_settings;
|
||||
data.user.user_guild_settings = data.user_guild_settings;
|
||||
|
||||
const clientUser = new ClientUser(client, data.user);
|
||||
client.user = clientUser;
|
||||
|
||||
@@ -14,7 +14,7 @@ class ResumedHandler extends AbstractHandler {
|
||||
const replayed = ws.sequence - ws.closeSequence;
|
||||
|
||||
ws.debug(`RESUMED ${ws._trace.join(' -> ')} | replayed ${replayed} events.`);
|
||||
client.emit('resume', replayed);
|
||||
client.emit(Constants.Events.RESUME, replayed);
|
||||
ws.heartbeat();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
const AbstractHandler = require('./AbstractHandler');
|
||||
const Constants = require('../../../../util/Constants');
|
||||
|
||||
class UserGuildSettingsUpdateHandler extends AbstractHandler {
|
||||
handle(packet) {
|
||||
const client = this.packetManager.client;
|
||||
client.user.guildSettings.get(packet.d.guild_id).patch(packet.d);
|
||||
client.emit(Constants.Events.USER_GUILD_SETTINGS_UPDATE, client.user.guildSettings.get(packet.d.guild_id));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emitted whenever the client user's settings update.
|
||||
* @event Client#clientUserGuildSettingsUpdate
|
||||
* @param {ClientUserGuildSettings} clientUserGuildSettings The new client user guild settings
|
||||
*/
|
||||
|
||||
module.exports = UserGuildSettingsUpdateHandler;
|
||||
@@ -11,6 +11,7 @@ module.exports = {
|
||||
// Utilities
|
||||
Collection: require('./util/Collection'),
|
||||
Constants: require('./util/Constants'),
|
||||
DiscordAPIError: require('./client/rest/DiscordAPIError'),
|
||||
EvaluatedPermissions: require('./util/Permissions'),
|
||||
Permissions: require('./util/Permissions'),
|
||||
Snowflake: require('./util/Snowflake'),
|
||||
@@ -25,6 +26,7 @@ module.exports = {
|
||||
splitMessage: Util.splitMessage,
|
||||
|
||||
// Structures
|
||||
Attachment: require('./structures/Attachment'),
|
||||
Channel: require('./structures/Channel'),
|
||||
ClientUser: require('./structures/ClientUser'),
|
||||
ClientUserSettings: require('./structures/ClientUserSettings'),
|
||||
@@ -59,5 +61,3 @@ module.exports = {
|
||||
VoiceChannel: require('./structures/VoiceChannel'),
|
||||
Webhook: require('./structures/Webhook'),
|
||||
};
|
||||
|
||||
if (require('os').platform() === 'browser') window.Discord = module.exports; // eslint-disable-line no-undef
|
||||
|
||||
@@ -69,9 +69,11 @@ class Shard {
|
||||
* @param {string} prop Name of the client property to get, using periods for nesting
|
||||
* @returns {Promise<*>}
|
||||
* @example
|
||||
* shard.fetchClientValue('guilds.size').then(count => {
|
||||
* console.log(`${count} guilds in shard ${shard.id}`);
|
||||
* }).catch(console.error);
|
||||
* shard.fetchClientValue('guilds.size')
|
||||
* .then(count => {
|
||||
* console.log(`${count} guilds in shard ${shard.id}`);
|
||||
* })
|
||||
* .catch(console.error);
|
||||
*/
|
||||
fetchClientValue(prop) {
|
||||
if (this._fetches.has(prop)) return this._fetches.get(prop);
|
||||
|
||||
@@ -49,9 +49,11 @@ class ShardClientUtil {
|
||||
* @param {string} prop Name of the client property to get, using periods for nesting
|
||||
* @returns {Promise<Array>}
|
||||
* @example
|
||||
* client.shard.fetchClientValues('guilds.size').then(results => {
|
||||
* console.log(`${results.reduce((prev, val) => prev + val, 0)} total guilds`);
|
||||
* }).catch(console.error);
|
||||
* client.shard.fetchClientValues('guilds.size')
|
||||
* .then(results => {
|
||||
* console.log(`${results.reduce((prev, val) => prev + val, 0)} total guilds`);
|
||||
* })
|
||||
* .catch(console.error);
|
||||
*/
|
||||
fetchClientValues(prop) {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
@@ -176,9 +176,11 @@ class ShardingManager extends EventEmitter {
|
||||
* @param {string} prop Name of the client property to get, using periods for nesting
|
||||
* @returns {Promise<Array>}
|
||||
* @example
|
||||
* manager.fetchClientValues('guilds.size').then(results => {
|
||||
* console.log(`${results.reduce((prev, val) => prev + val, 0)} total guilds`);
|
||||
* }).catch(console.error);
|
||||
* manager.fetchClientValues('guilds.size')
|
||||
* .then(results => {
|
||||
* console.log(`${results.reduce((prev, val) => prev + val, 0)} total guilds`);
|
||||
* })
|
||||
* .catch(console.error);
|
||||
*/
|
||||
fetchClientValues(prop) {
|
||||
if (this.shards.size === 0) return Promise.reject(new Error('No shards have been spawned.'));
|
||||
|
||||
75
src/structures/Attachment.js
Normal file
75
src/structures/Attachment.js
Normal file
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Represents an attachment in a message.
|
||||
* @param {BufferResolvable|Stream} file The file
|
||||
* @param {string} [name] The name of the file, if any
|
||||
*/
|
||||
class Attachment {
|
||||
constructor(file, name) {
|
||||
this.file = null;
|
||||
if (name) this.setAttachment(file, name);
|
||||
else this._attach(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the file
|
||||
* @type {?string}
|
||||
* @readonly
|
||||
*/
|
||||
get name() {
|
||||
return this.file.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* The file
|
||||
* @type {?BufferResolvable|Stream}
|
||||
* @readonly
|
||||
*/
|
||||
get attachment() {
|
||||
return this.file.attachment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the file of this attachment.
|
||||
* @param {BufferResolvable|Stream} file The file
|
||||
* @param {string} name The name of the file
|
||||
* @returns {Attachment} This attachment
|
||||
*/
|
||||
setAttachment(file, name) {
|
||||
this.file = { attachment: file, name };
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the file of this attachment.
|
||||
* @param {BufferResolvable|Stream} attachment The file
|
||||
* @returns {Attachment} This attachment
|
||||
*/
|
||||
setFile(attachment) {
|
||||
this.file = { attachment };
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the name of this attachment.
|
||||
* @param {string} name The name of the image
|
||||
* @returns {Attachment} This attachment
|
||||
*/
|
||||
setName(name) {
|
||||
this.file.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the file of this attachment.
|
||||
* @param {BufferResolvable|Stream} file The file
|
||||
* @param {string} name The name of the file
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
_attach(file, name) {
|
||||
if (typeof file === 'string') this.file = file;
|
||||
else this.setAttachment(file, name);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Attachment;
|
||||
@@ -58,8 +58,8 @@ class Channel {
|
||||
* @example
|
||||
* // Delete the channel
|
||||
* channel.delete()
|
||||
* .then() // Success
|
||||
* .catch(console.error); // Log error
|
||||
* .then() // Success
|
||||
* .catch(console.error); // Log error
|
||||
*/
|
||||
delete() {
|
||||
return this.client.rest.methods.deleteChannel(this);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const User = require('./User');
|
||||
const Collection = require('../util/Collection');
|
||||
const ClientUserSettings = require('./ClientUserSettings');
|
||||
const ClientUserGuildSettings = require('./ClientUserGuildSettings');
|
||||
const Constants = require('../util/Constants');
|
||||
|
||||
/**
|
||||
@@ -72,7 +73,19 @@ class ClientUser extends User {
|
||||
* <warn>This is only filled when using a user account.</warn>
|
||||
* @type {?ClientUserSettings}
|
||||
*/
|
||||
if (data.user_settings) this.settings = new ClientUserSettings(this, data.user_settings);
|
||||
this.settings = data.user_settings ? new ClientUserSettings(this, data.user_settings) : null;
|
||||
|
||||
/**
|
||||
* All of the user's guild settings
|
||||
* <warn>This is only filled when using a user account</warn>
|
||||
* @type {Collection<Snowflake, ClientUserGuildSettings>}
|
||||
*/
|
||||
this.guildSettings = new Collection();
|
||||
if (data.user_guild_settings) {
|
||||
for (const settings of data.user_guild_settings) {
|
||||
this.guildSettings.set(settings.guild_id, new ClientUserGuildSettings(settings, this.client));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
edit(data) {
|
||||
@@ -89,8 +102,8 @@ class ClientUser extends User {
|
||||
* @example
|
||||
* // Set username
|
||||
* client.user.setUsername('discordjs')
|
||||
* .then(user => console.log(`My new username is ${user.username}`))
|
||||
* .catch(console.error);
|
||||
* .then(user => console.log(`My new username is ${user.username}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
setUsername(username, password) {
|
||||
return this.client.rest.methods.updateCurrentUser({ username }, password);
|
||||
@@ -105,8 +118,8 @@ class ClientUser extends User {
|
||||
* @example
|
||||
* // Set email
|
||||
* client.user.setEmail('bob@gmail.com', 'some amazing password 123')
|
||||
* .then(user => console.log(`My new email is ${user.email}`))
|
||||
* .catch(console.error);
|
||||
* .then(user => console.log(`My new email is ${user.email}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
setEmail(email, password) {
|
||||
return this.client.rest.methods.updateCurrentUser({ email }, password);
|
||||
@@ -121,8 +134,8 @@ class ClientUser extends User {
|
||||
* @example
|
||||
* // Set password
|
||||
* client.user.setPassword('some new amazing password 456', 'some amazing password 123')
|
||||
* .then(user => console.log('New password set!'))
|
||||
* .catch(console.error);
|
||||
* .then(user => console.log('New password set!'))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
setPassword(newPassword, oldPassword) {
|
||||
return this.client.rest.methods.updateCurrentUser({ password: newPassword }, oldPassword);
|
||||
@@ -135,17 +148,13 @@ class ClientUser extends User {
|
||||
* @example
|
||||
* // Set avatar
|
||||
* client.user.setAvatar('./avatar.png')
|
||||
* .then(user => console.log(`New avatar set!`))
|
||||
* .catch(console.error);
|
||||
* .then(user => console.log(`New avatar set!`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
setAvatar(avatar) {
|
||||
if (typeof avatar === 'string' && avatar.startsWith('data:')) {
|
||||
return this.client.rest.methods.updateCurrentUser({ avatar });
|
||||
} else {
|
||||
return this.client.resolver.resolveBuffer(avatar).then(data =>
|
||||
this.client.rest.methods.updateCurrentUser({ avatar: data })
|
||||
);
|
||||
}
|
||||
return this.client.resolver.resolveImage(avatar).then(data =>
|
||||
this.client.rest.methods.updateCurrentUser({ avatar: data })
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -190,7 +199,7 @@ class ClientUser extends User {
|
||||
|
||||
if (data.game) {
|
||||
game = data.game;
|
||||
if (game.url) game.type = 1;
|
||||
game.type = game.url ? 1 : 0;
|
||||
} else if (typeof data.game !== 'undefined') {
|
||||
game = null;
|
||||
}
|
||||
@@ -215,10 +224,10 @@ class ClientUser extends User {
|
||||
|
||||
/**
|
||||
* A user's status. Must be one of:
|
||||
* - `online`
|
||||
* - `idle`
|
||||
* - `invisible`
|
||||
* - `dnd` (do not disturb)
|
||||
* * `online`
|
||||
* * `idle`
|
||||
* * `invisible`
|
||||
* * `dnd` (do not disturb)
|
||||
* @typedef {string} PresenceStatus
|
||||
*/
|
||||
|
||||
@@ -265,7 +274,7 @@ class ClientUser extends User {
|
||||
* @param {Guild|Snowflake} [options.guild] Limit the search to a specific guild
|
||||
* @returns {Promise<Message[]>}
|
||||
*/
|
||||
fetchMentions(options = { limit: 25, roles: true, everyone: true, guild: null }) {
|
||||
fetchMentions(options = {}) {
|
||||
return this.client.rest.methods.fetchMentions(options);
|
||||
}
|
||||
|
||||
@@ -295,16 +304,15 @@ class ClientUser extends User {
|
||||
* Creates a guild.
|
||||
* <warn>This is only available when using a user account.</warn>
|
||||
* @param {string} name The name of the guild
|
||||
* @param {string} region The region for the server
|
||||
* @param {string} [region] The region for the server
|
||||
* @param {BufferResolvable|Base64Resolvable} [icon=null] The icon for the guild
|
||||
* @returns {Promise<Guild>} The guild that was created
|
||||
*/
|
||||
createGuild(name, region, icon = null) {
|
||||
if (!icon) return this.client.rest.methods.createGuild({ name, icon, region });
|
||||
if (typeof icon === 'string' && icon.startsWith('data:')) {
|
||||
return this.client.rest.methods.createGuild({ name, icon, region });
|
||||
} else {
|
||||
return this.client.resolver.resolveBuffer(icon).then(data =>
|
||||
return this.client.resolver.resolveImage(icon).then(data =>
|
||||
this.client.rest.methods.createGuild({ name, icon: data, region })
|
||||
);
|
||||
}
|
||||
|
||||
30
src/structures/ClientUserChannelOverride.js
Normal file
30
src/structures/ClientUserChannelOverride.js
Normal file
@@ -0,0 +1,30 @@
|
||||
const Constants = require('../util/Constants');
|
||||
|
||||
/**
|
||||
* A wrapper around the ClientUser's channel overrides.
|
||||
*/
|
||||
class ClientUserChannelOverride {
|
||||
constructor(data) {
|
||||
this.patch(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Patch the data contained in this class with new partial data.
|
||||
* @param {Object} data Data to patch this with
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
patch(data) {
|
||||
for (const key of Object.keys(Constants.UserChannelOverrideMap)) {
|
||||
const value = Constants.UserChannelOverrideMap[key];
|
||||
if (!data.hasOwnProperty(key)) continue;
|
||||
if (typeof value === 'function') {
|
||||
this[value.name] = value(data[key]);
|
||||
} else {
|
||||
this[value] = data[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ClientUserChannelOverride;
|
||||
60
src/structures/ClientUserGuildSettings.js
Normal file
60
src/structures/ClientUserGuildSettings.js
Normal file
@@ -0,0 +1,60 @@
|
||||
const Constants = require('../util/Constants');
|
||||
const Collection = require('../util/Collection');
|
||||
const ClientUserChannelOverride = require('./ClientUserChannelOverride');
|
||||
|
||||
/**
|
||||
* A wrapper around the ClientUser's guild settings.
|
||||
*/
|
||||
class ClientUserGuildSettings {
|
||||
constructor(data, client) {
|
||||
/**
|
||||
* The client that created the instance of the ClientUserGuildSettings
|
||||
* @name ClientUserGuildSettings#client
|
||||
* @type {Client}
|
||||
* @readonly
|
||||
*/
|
||||
Object.defineProperty(this, 'client', { value: client });
|
||||
/**
|
||||
* The ID of the guild this settings are for
|
||||
* @type {Snowflake}
|
||||
*/
|
||||
this.guildID = data.guild_id;
|
||||
this.channelOverrides = new Collection();
|
||||
this.patch(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Patch the data contained in this class with new partial data.
|
||||
* @param {Object} data Data to patch this with
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
patch(data) {
|
||||
for (const key of Object.keys(Constants.UserGuildSettingsMap)) {
|
||||
const value = Constants.UserGuildSettingsMap[key];
|
||||
if (!data.hasOwnProperty(key)) continue;
|
||||
if (key === 'channel_overrides') {
|
||||
for (const channel of data[key]) {
|
||||
this.channelOverrides.set(channel.channel_id,
|
||||
new ClientUserChannelOverride(channel));
|
||||
}
|
||||
} else if (typeof value === 'function') {
|
||||
this[value.name] = value(data[key]);
|
||||
} else {
|
||||
this[value] = data[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a specific property of the guild settings.
|
||||
* @param {string} name Name of property
|
||||
* @param {value} value Value to patch
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
update(name, value) {
|
||||
return this.client.rest.methods.patchClientUserGuildSettings(this.guildID, { [name]: value });
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ClientUserGuildSettings;
|
||||
@@ -13,6 +13,8 @@ class ClientUserSettings {
|
||||
/**
|
||||
* Patch the data contained in this class with new partial data.
|
||||
* @param {Object} data Data to patch this with
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
patch(data) {
|
||||
for (const key of Object.keys(Constants.UserSettingsMap)) {
|
||||
@@ -29,7 +31,7 @@ class ClientUserSettings {
|
||||
/**
|
||||
* Update a specific property of of user settings.
|
||||
* @param {string} name Name of property
|
||||
* @param {value} value Value to patch
|
||||
* @param {*} value Value to patch
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
update(name, value) {
|
||||
@@ -37,6 +39,7 @@ class ClientUserSettings {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the position at which this guild will appear in the Discord client.
|
||||
* @param {Guild} guild The guild to move
|
||||
* @param {number} position Absolute or relative position
|
||||
* @param {boolean} [relative=false] Whether to position relatively or absolutely
|
||||
|
||||
@@ -53,6 +53,7 @@ class DMChannel extends Channel {
|
||||
get typing() {}
|
||||
get typingCount() {}
|
||||
createCollector() {}
|
||||
createMessageCollector() {}
|
||||
awaitMessages() {}
|
||||
// Doesn't work on DM channels; bulkDelete() {}
|
||||
acknowledge() {}
|
||||
|
||||
@@ -112,24 +112,26 @@ class Emoji {
|
||||
/**
|
||||
* Edits the emoji.
|
||||
* @param {EmojiEditData} data The new data for the emoji
|
||||
* @param {string} [reason] Reason for editing this emoji
|
||||
* @returns {Promise<Emoji>}
|
||||
* @example
|
||||
* // Edit a emoji
|
||||
* // Edit an emoji
|
||||
* emoji.edit({name: 'newemoji'})
|
||||
* .then(e => console.log(`Edited emoji ${e}`))
|
||||
* .catch(console.error);
|
||||
* .then(e => console.log(`Edited emoji ${e}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
edit(data) {
|
||||
return this.client.rest.methods.updateEmoji(this, data);
|
||||
edit(data, reason) {
|
||||
return this.client.rest.methods.updateEmoji(this, data, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the name of the emoji.
|
||||
* @param {string} name The new name for the emoji
|
||||
* @param {string} [reason] The reason for changing the emoji's name
|
||||
* @returns {Promise<Emoji>}
|
||||
*/
|
||||
setName(name) {
|
||||
return this.edit({ name });
|
||||
setName(name, reason) {
|
||||
return this.edit({ name }, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const Channel = require('./Channel');
|
||||
const TextBasedChannel = require('./interfaces/TextBasedChannel');
|
||||
const Collection = require('../util/Collection');
|
||||
const Constants = require('../util/Constants');
|
||||
|
||||
/*
|
||||
{ type: 3,
|
||||
@@ -47,8 +48,8 @@ class GroupDMChannel extends Channel {
|
||||
this.name = data.name;
|
||||
|
||||
/**
|
||||
* A hash of the Group DM icon.
|
||||
* @type {string}
|
||||
* A hash of this Group DM icon
|
||||
* @type {?string}
|
||||
*/
|
||||
this.icon = data.icon;
|
||||
|
||||
@@ -70,11 +71,13 @@ class GroupDMChannel extends Channel {
|
||||
*/
|
||||
this.applicationID = data.application_id;
|
||||
|
||||
/**
|
||||
* Nicknames for group members
|
||||
* @type {?Collection<Snowflake, string>}
|
||||
*/
|
||||
if (data.nicks) this.nicks = new Collection(data.nicks.map(n => [n.id, n.nick]));
|
||||
if (data.nicks) {
|
||||
/**
|
||||
* Nicknames for group members
|
||||
* @type {?Collection<Snowflake, string>}
|
||||
*/
|
||||
this.nicks = new Collection(data.nicks.map(n => [n.id, n.nick]));
|
||||
}
|
||||
|
||||
if (!this.recipients) {
|
||||
/**
|
||||
@@ -103,6 +106,23 @@ class GroupDMChannel extends Channel {
|
||||
return this.client.users.get(this.ownerID);
|
||||
}
|
||||
|
||||
/**
|
||||
* The URL to this guild's icon
|
||||
* @type {?string}
|
||||
* @readonly
|
||||
*/
|
||||
get iconURL() {
|
||||
if (!this.icon) return null;
|
||||
return Constants.Endpoints.Channel(this).Icon(this.client.options.http.cdn, this.icon);
|
||||
}
|
||||
|
||||
edit(data) {
|
||||
const _data = {};
|
||||
if (data.name) _data.name = data.name;
|
||||
if (typeof data.icon !== 'undefined') _data.icon = data.icon;
|
||||
return this.client.rest.methods.updateGroupDMChannel(this, _data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this channel equals another channel. It compares all properties, so for most operations
|
||||
* it is advisable to just compare `channel.id === channel2.id` as it is much faster and is often
|
||||
@@ -128,6 +148,7 @@ class GroupDMChannel extends Channel {
|
||||
* Add a user to the DM
|
||||
* @param {UserResolvable|string} accessTokenOrID Access token or user resolvable
|
||||
* @param {string} [nick] Permanent nickname to give the user (only available if a bot is creating the DM)
|
||||
* @returns {Promise<GroupDMChannel>}
|
||||
*/
|
||||
|
||||
addUser(accessTokenOrID, nick) {
|
||||
@@ -138,6 +159,39 @@ class GroupDMChannel extends Channel {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a new GroupDMChannel icon.
|
||||
* @param {Base64Resolvable|BufferResolvable} icon The new icon of the group dm
|
||||
* @returns {Promise<GroupDMChannel>}
|
||||
* @example
|
||||
* // Edit the group dm icon
|
||||
* channel.setIcon('./icon.png')
|
||||
* .then(updated => console.log('Updated the channel icon'))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
setIcon(icon) {
|
||||
return this.client.resolver.resolveImage(icon).then(data => this.edit({ icon: data }));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new name for this Group DM.
|
||||
* @param {string} name New name for this Group DM
|
||||
* @returns {Promise<GroupDMChannel>}
|
||||
*/
|
||||
setName(name) {
|
||||
return this.edit({ name });
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an user from this Group DM.
|
||||
* @param {UserResolvable} user User to remove
|
||||
* @returns {Promise<GroupDMChannel>}
|
||||
*/
|
||||
removeUser(user) {
|
||||
const id = this.client.resolver.resolveUserID(user);
|
||||
return this.client.rest.methods.removeUserFromGroupDM(this, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* When concatenated with a string, this automatically concatenates the channel's name instead of the Channel object.
|
||||
* @returns {string}
|
||||
@@ -169,6 +223,7 @@ class GroupDMChannel extends Channel {
|
||||
get typing() {}
|
||||
get typingCount() {}
|
||||
createCollector() {}
|
||||
createMessageCollector() {}
|
||||
awaitMessages() {}
|
||||
// Doesn't work on Group DMs; bulkDelete() {}
|
||||
acknowledge() {}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
const util = require('util');
|
||||
const Long = require('long');
|
||||
const User = require('./User');
|
||||
const Role = require('./Role');
|
||||
@@ -17,7 +18,7 @@ const Snowflake = require('../util/Snowflake');
|
||||
class Guild {
|
||||
constructor(client, data) {
|
||||
/**
|
||||
* The client that created the instance of the the guild
|
||||
* The client that created the instance of the guild
|
||||
* @name Guild#client
|
||||
* @type {Client}
|
||||
* @readonly
|
||||
@@ -62,8 +63,8 @@ class Guild {
|
||||
*/
|
||||
this.id = data.id;
|
||||
} else {
|
||||
this.available = true;
|
||||
this.setup(data);
|
||||
if (!data.channels) this.available = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,6 +134,12 @@ class Guild {
|
||||
*/
|
||||
this.afkChannelID = data.afk_channel_id;
|
||||
|
||||
/**
|
||||
* The ID of the system channel
|
||||
* @type {?Snowflake}
|
||||
*/
|
||||
this.systemChannelID = data.system_channel_id;
|
||||
|
||||
/**
|
||||
* Whether embedded images are enabled on this guild
|
||||
* @type {boolean}
|
||||
@@ -212,7 +219,8 @@ class Guild {
|
||||
|
||||
if (!this.emojis) {
|
||||
/**
|
||||
* A collection of emojis that are in this guild. The key is the emoji's ID, the value is the emoji.
|
||||
* A collection of emojis that are in this guild
|
||||
* The key is the emoji's ID, the value is the emoji
|
||||
* @type {Collection<Snowflake, Emoji>}
|
||||
*/
|
||||
this.emojis = new Collection();
|
||||
@@ -262,6 +270,15 @@ class Guild {
|
||||
return Constants.Endpoints.Guild(this).Icon(this.client.options.http.cdn, this.icon);
|
||||
}
|
||||
|
||||
/**
|
||||
* The acronym that shows up in place of a guild icon.
|
||||
* @type {string}
|
||||
* @readonly
|
||||
*/
|
||||
get nameAcronym() {
|
||||
return this.name.replace(/\w+/g, name => name[0]).replace(/\s/g, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* The URL to this guild's splash
|
||||
* @type {?string}
|
||||
@@ -281,6 +298,24 @@ class Guild {
|
||||
return this.members.get(this.ownerID);
|
||||
}
|
||||
|
||||
/**
|
||||
* AFK voice channel for this guild
|
||||
* @type {?VoiceChannel}
|
||||
* @readonly
|
||||
*/
|
||||
get afkChannel() {
|
||||
return this.client.channels.get(this.afkChannelID) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* System channel for this guild
|
||||
* @type {?GuildChannel}
|
||||
* @readonly
|
||||
*/
|
||||
get systemChannel() {
|
||||
return this.client.channels.get(this.systemChannelID) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the client is connected to any voice channel in this guild, this will be the relevant VoiceConnection
|
||||
* @type {?VoiceConnection}
|
||||
@@ -291,19 +326,11 @@ class Guild {
|
||||
return this.client.voice.connections.get(this.id) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The `#general` TextChannel of the guild
|
||||
* @type {TextChannel}
|
||||
* @readonly
|
||||
*/
|
||||
get defaultChannel() {
|
||||
return this.channels.get(this.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* The position of this guild
|
||||
* <warn>This is only available when using a user account.</warn>
|
||||
* @type {?number}
|
||||
* @readonly
|
||||
*/
|
||||
get position() {
|
||||
if (this.client.user.bot) return null;
|
||||
@@ -311,6 +338,66 @@ class Guild {
|
||||
return this.client.user.settings.guildPositions.indexOf(this.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the guild is muted
|
||||
* <warn>This is only available when using a user account.</warn>
|
||||
* @type {?boolean}
|
||||
* @readonly
|
||||
*/
|
||||
get muted() {
|
||||
if (this.client.user.bot) return null;
|
||||
try {
|
||||
return this.client.user.guildSettings.get(this.id).muted;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of message that should notify you
|
||||
* <warn>This is only available when using a user account.</warn>
|
||||
* @type {?MessageNotificationType}
|
||||
* @readonly
|
||||
*/
|
||||
get messageNotifications() {
|
||||
if (this.client.user.bot) return null;
|
||||
try {
|
||||
return this.client.user.guildSettings.get(this.id).messageNotifications;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to receive mobile push notifications
|
||||
* <warn>This is only available when using a user account.</warn>
|
||||
* @type {?boolean}
|
||||
* @readonly
|
||||
*/
|
||||
get mobilePush() {
|
||||
if (this.client.user.bot) return null;
|
||||
try {
|
||||
return this.client.user.guildSettings.get(this.id).mobilePush;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to suppress everyone messages
|
||||
* <warn>This is only available when using a user account.</warn>
|
||||
* @type {?boolean}
|
||||
* @readonly
|
||||
*/
|
||||
get suppressEveryone() {
|
||||
if (this.client.user.bot) return null;
|
||||
try {
|
||||
return this.client.user.guildSettings.get(this.id).suppressEveryone;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The `@everyone` role of the guild
|
||||
* @type {Role}
|
||||
@@ -366,7 +453,8 @@ class Guild {
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a collection of invites to this guild. Resolves with a collection mapping invites by their codes.
|
||||
* Fetch a collection of invites to this guild.
|
||||
* Resolves with a collection mapping invites by their codes.
|
||||
* @returns {Promise<Collection<string, Invite>>}
|
||||
*/
|
||||
fetchInvites() {
|
||||
@@ -424,7 +512,7 @@ class Guild {
|
||||
/**
|
||||
* Fetch a single guild member from a user.
|
||||
* @param {UserResolvable} user The user to fetch the member for
|
||||
* @param {boolean} [cache=true] Insert the user into the users cache
|
||||
* @param {boolean} [cache=true] Insert the member into the members cache
|
||||
* @returns {Promise<GuildMember>}
|
||||
*/
|
||||
fetchMember(user, cache = true) {
|
||||
@@ -475,9 +563,7 @@ class Guild {
|
||||
* Performs a search within the entire guild.
|
||||
* <warn>This is only available when using a user account.</warn>
|
||||
* @param {MessageSearchOptions} [options={}] Options to pass to the search
|
||||
* @returns {Promise<Array<Message[]>>}
|
||||
* An array containing arrays of messages. Each inner array is a search context cluster.
|
||||
* The message which has triggered the result will have the `hit` property set to `true`.
|
||||
* @returns {Promise<MessageSearchResult>}
|
||||
* @example
|
||||
* guild.search({
|
||||
* content: 'discord.js',
|
||||
@@ -497,7 +583,9 @@ class Guild {
|
||||
* @property {string} [name] The name of the guild
|
||||
* @property {string} [region] The region of the guild
|
||||
* @property {number} [verificationLevel] The verification level of the guild
|
||||
* @property {number} [explicitContentFilter] The level of the explicit content filter
|
||||
* @property {ChannelResolvable} [afkChannel] The AFK channel of the guild
|
||||
* @property {ChannelResolvable} [systemChannel] The system channel of the guild
|
||||
* @property {number} [afkTimeout] The AFK timeout of the guild
|
||||
* @property {Base64Resolvable} [icon] The icon of the guild
|
||||
* @property {GuildMemberResolvable} [owner] The owner of the guild
|
||||
@@ -507,23 +595,52 @@ class Guild {
|
||||
/**
|
||||
* Updates the guild with new information - e.g. a new name.
|
||||
* @param {GuildEditData} data The data to update the guild with
|
||||
* @param {string} [reason] Reason for editing the guild
|
||||
* @returns {Promise<Guild>}
|
||||
* @example
|
||||
* // Set the guild name and region
|
||||
* guild.edit({
|
||||
* name: 'Discord Guild',
|
||||
* region: 'london',
|
||||
* name: 'Discord Guild',
|
||||
* region: 'london',
|
||||
* })
|
||||
* .then(updated => console.log(`New guild name ${updated.name} in region ${updated.region}`))
|
||||
* .catch(console.error);
|
||||
* .then(updated => console.log(`New guild name ${updated.name} in region ${updated.region}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
edit(data) {
|
||||
return this.client.rest.methods.updateGuild(this, data);
|
||||
edit(data, reason) {
|
||||
const _data = {};
|
||||
if (data.name) _data.name = data.name;
|
||||
if (data.region) _data.region = data.region;
|
||||
if (typeof data.verificationLevel !== 'undefined') _data.verification_level = Number(data.verificationLevel);
|
||||
if (typeof data.afkChannel !== 'undefined') {
|
||||
_data.afk_channel_id = this.client.resolver.resolveChannelID(data.afkChannel);
|
||||
}
|
||||
if (typeof data.systemChannel !== 'undefined') {
|
||||
_data.system_channel_id = this.client.resolver.resolveChannelID(data.systemChannel);
|
||||
}
|
||||
if (data.afkTimeout) _data.afk_timeout = Number(data.afkTimeout);
|
||||
if (typeof data.icon !== 'undefined') _data.icon = data.icon;
|
||||
if (data.owner) _data.owner_id = this.client.resolver.resolveUser(data.owner).id;
|
||||
if (typeof data.splash !== 'undefined') _data.splash = data.splash;
|
||||
if (typeof data.explicitContentFilter !== 'undefined') {
|
||||
_data.explicit_content_filter = Number(data.explicitContentFilter);
|
||||
}
|
||||
return this.client.rest.methods.updateGuild(this, _data, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit the level of the explicit content filter.
|
||||
* @param {number} explicitContentFilter The new level of the explicit content filter
|
||||
* @param {string} [reason] Reason for changing the level of the guild's explicit content filter
|
||||
* @returns {Promise<Guild>}
|
||||
*/
|
||||
setExplicitContentFilter(explicitContentFilter, reason) {
|
||||
return this.edit({ explicitContentFilter }, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit the name of the guild.
|
||||
* @param {string} name The new name of the guild
|
||||
* @param {string} [reason] Reason for changing the guild's name
|
||||
* @returns {Promise<Guild>}
|
||||
* @example
|
||||
* // Edit the guild name
|
||||
@@ -531,13 +648,14 @@ class Guild {
|
||||
* .then(updated => console.log(`Updated guild name to ${guild.name}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
setName(name) {
|
||||
return this.edit({ name });
|
||||
setName(name, reason) {
|
||||
return this.edit({ name }, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit the region of the guild.
|
||||
* @param {string} region The new region of the guild
|
||||
* @param {string} [reason] Reason for changing the guild's region
|
||||
* @returns {Promise<Guild>}
|
||||
* @example
|
||||
* // Edit the guild region
|
||||
@@ -545,13 +663,14 @@ class Guild {
|
||||
* .then(updated => console.log(`Updated guild region to ${guild.region}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
setRegion(region) {
|
||||
return this.edit({ region });
|
||||
setRegion(region, reason) {
|
||||
return this.edit({ region }, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit the verification level of the guild.
|
||||
* @param {number} verificationLevel The new verification level of the guild
|
||||
* @param {string} [reason] Reason for changing the guild's verification level
|
||||
* @returns {Promise<Guild>}
|
||||
* @example
|
||||
* // Edit the guild verification level
|
||||
@@ -559,13 +678,14 @@ class Guild {
|
||||
* .then(updated => console.log(`Updated guild verification level to ${guild.verificationLevel}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
setVerificationLevel(verificationLevel) {
|
||||
return this.edit({ verificationLevel });
|
||||
setVerificationLevel(verificationLevel, reason) {
|
||||
return this.edit({ verificationLevel }, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit the AFK channel of the guild.
|
||||
* @param {ChannelResolvable} afkChannel The new AFK channel
|
||||
* @param {string} [reason] Reason for changing the guild's AFK channel
|
||||
* @returns {Promise<Guild>}
|
||||
* @example
|
||||
* // Edit the guild AFK channel
|
||||
@@ -573,13 +693,24 @@ class Guild {
|
||||
* .then(updated => console.log(`Updated guild AFK channel to ${guild.afkChannel}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
setAFKChannel(afkChannel) {
|
||||
return this.edit({ afkChannel });
|
||||
setAFKChannel(afkChannel, reason) {
|
||||
return this.edit({ afkChannel }, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit the system channel of the guild.
|
||||
* @param {ChannelResolvable} systemChannel The new system channel
|
||||
* @param {string} [reason] Reason for changing the guild's system channel
|
||||
* @returns {Promise<Guild>}
|
||||
*/
|
||||
setSystemChannel(systemChannel, reason) {
|
||||
return this.edit({ systemChannel }, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit the AFK timeout of the guild.
|
||||
* @param {number} afkTimeout The time in seconds that a user must be idle to be considered AFK
|
||||
* @param {string} [reason] Reason for changing the guild's AFK timeout
|
||||
* @returns {Promise<Guild>}
|
||||
* @example
|
||||
* // Edit the guild AFK channel
|
||||
@@ -587,27 +718,29 @@ class Guild {
|
||||
* .then(updated => console.log(`Updated guild AFK timeout to ${guild.afkTimeout}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
setAFKTimeout(afkTimeout) {
|
||||
return this.edit({ afkTimeout });
|
||||
setAFKTimeout(afkTimeout, reason) {
|
||||
return this.edit({ afkTimeout }, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a new guild icon.
|
||||
* @param {Base64Resolvable} icon The new icon of the guild
|
||||
* @param {Base64Resolvable|BufferResolvable} icon The new icon of the guild
|
||||
* @param {string} [reason] Reason for changing the guild's icon
|
||||
* @returns {Promise<Guild>}
|
||||
* @example
|
||||
* // Edit the guild icon
|
||||
* guild.setIcon(fs.readFileSync('./icon.png'))
|
||||
* guild.setIcon('./icon.png')
|
||||
* .then(updated => console.log('Updated the guild icon'))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
setIcon(icon) {
|
||||
return this.edit({ icon });
|
||||
setIcon(icon, reason) {
|
||||
return this.client.resolver.resolveImage(icon).then(data => this.edit({ icon: data, reason }));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new owner of the guild.
|
||||
* @param {GuildMemberResolvable} owner The new owner of the guild
|
||||
* @param {string} [reason] Reason for setting the new owner
|
||||
* @returns {Promise<Guild>}
|
||||
* @example
|
||||
* // Edit the guild owner
|
||||
@@ -615,25 +748,28 @@ class Guild {
|
||||
* .then(updated => console.log(`Updated the guild owner to ${updated.owner.username}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
setOwner(owner) {
|
||||
return this.edit({ owner });
|
||||
setOwner(owner, reason) {
|
||||
return this.edit({ owner }, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a new guild splash screen.
|
||||
* @param {Base64Resolvable} splash The new splash screen of the guild
|
||||
* @param {BufferResolvable|Base64Resolvable} splash The new splash screen of the guild
|
||||
* @param {string} [reason] Reason for changing the guild's splash screen
|
||||
* @returns {Promise<Guild>}
|
||||
* @example
|
||||
* // Edit the guild splash
|
||||
* guild.setIcon(fs.readFileSync('./splash.png'))
|
||||
* guild.setSplash('./splash.png')
|
||||
* .then(updated => console.log('Updated the guild splash'))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
setSplash(splash) {
|
||||
return this.edit({ splash });
|
||||
return this.client.resolver.resolveImage(splash).then(data => this.edit({ splash: data }));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the position of the guild in the guild listing.
|
||||
* <warn>This is only available when using a user account.</warn>
|
||||
* @param {number} position Absolute or relative position
|
||||
* @param {boolean} [relative=false] Whether to position relatively or absolutely
|
||||
* @returns {Promise<Guild>}
|
||||
@@ -648,7 +784,7 @@ class Guild {
|
||||
/**
|
||||
* Marks all messages in this guild as read.
|
||||
* <warn>This is only available when using a user account.</warn>
|
||||
* @returns {Promise<Guild>} This guild
|
||||
* @returns {Promise<Guild>}
|
||||
*/
|
||||
acknowledge() {
|
||||
return this.client.rest.methods.ackGuild(this);
|
||||
@@ -656,6 +792,7 @@ class Guild {
|
||||
|
||||
/**
|
||||
* Allow direct messages from guild members.
|
||||
* <warn>This is only available when using a user account.</warn>
|
||||
* @param {boolean} allow Whether to allow direct messages
|
||||
* @returns {Promise<Guild>}
|
||||
*/
|
||||
@@ -678,8 +815,8 @@ class Guild {
|
||||
* @example
|
||||
* // Ban a user by ID (or with a user/guild member object)
|
||||
* guild.ban('some user ID')
|
||||
* .then(user => console.log(`Banned ${user.username || user.id || user} from ${guild.name}`))
|
||||
* .catch(console.error);
|
||||
* .then(user => console.log(`Banned ${user.username || user.id || user} from ${guild.name}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
ban(user, options = {}) {
|
||||
if (typeof options === 'number') {
|
||||
@@ -694,21 +831,23 @@ class Guild {
|
||||
/**
|
||||
* Unbans a user from the guild.
|
||||
* @param {UserResolvable} user The user to unban
|
||||
* @param {string} [reason] Reason for unbanning the user
|
||||
* @returns {Promise<User>}
|
||||
* @example
|
||||
* // Unban a user by ID (or with a user/guild member object)
|
||||
* guild.unban('some user ID')
|
||||
* .then(user => console.log(`Unbanned ${user.username} from ${guild.name}`))
|
||||
* .catch(console.error);
|
||||
* .then(user => console.log(`Unbanned ${user.username} from ${guild.name}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
unban(user) {
|
||||
return this.client.rest.methods.unbanGuildMember(this, user);
|
||||
unban(user, reason) {
|
||||
return this.client.rest.methods.unbanGuildMember(this, user, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prunes members from the guild based on how long they have been inactive.
|
||||
* @param {number} days Number of days of inactivity required to kick
|
||||
* @param {boolean} [dry=false] If true, will return number of users that will be kicked, without actually doing it
|
||||
* @param {string} [reason] Reason for this prune
|
||||
* @returns {Promise<number>} The number of members that were/will be kicked
|
||||
* @example
|
||||
* // See how many members will be pruned
|
||||
@@ -721,9 +860,9 @@ class Guild {
|
||||
* .then(pruned => console.log(`I just pruned ${pruned} people!`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
pruneMembers(days, dry = false) {
|
||||
pruneMembers(days, dry = false, reason) {
|
||||
if (typeof days !== 'number') throw new TypeError('Days must be a number.');
|
||||
return this.client.rest.methods.pruneGuildMembers(this, days, dry);
|
||||
return this.client.rest.methods.pruneGuildMembers(this, days, dry, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -738,16 +877,17 @@ class Guild {
|
||||
* Creates a new channel in the guild.
|
||||
* @param {string} name The name of the new channel
|
||||
* @param {string} type The type of the new channel, either `text` or `voice`
|
||||
* @param {Array<PermissionOverwrites|Object>} overwrites Permission overwrites to apply to the new channel
|
||||
* @param {Array<PermissionOverwrites|Object>} [overwrites] Permission overwrites to apply to the new channel
|
||||
* @param {string} [reason] Reason for creating this channel
|
||||
* @returns {Promise<TextChannel|VoiceChannel>}
|
||||
* @example
|
||||
* // Create a new text channel
|
||||
* guild.createChannel('new-general', 'text')
|
||||
* .then(channel => console.log(`Created new channel ${channel}`))
|
||||
* .catch(console.error);
|
||||
* .then(channel => console.log(`Created new channel ${channel}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
createChannel(name, type, overwrites) {
|
||||
return this.client.rest.methods.createChannel(this, name, type, overwrites);
|
||||
createChannel(name, type, overwrites, reason) {
|
||||
return this.client.rest.methods.createChannel(this, name, type, overwrites, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -763,8 +903,8 @@ class Guild {
|
||||
* @returns {Promise<Guild>}
|
||||
* @example
|
||||
* guild.updateChannels([{ channel: channelID, position: newChannelIndex }])
|
||||
* .then(guild => console.log(`Updated channel positions for ${guild.id}`))
|
||||
* .catch(console.error);
|
||||
* .then(guild => console.log(`Updated channel positions for ${guild.id}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
setChannelPositions(channelPositions) {
|
||||
return this.client.rest.methods.updateChannelPositions(this.id, channelPositions);
|
||||
@@ -773,23 +913,24 @@ class Guild {
|
||||
/**
|
||||
* Creates a new role in the guild with given information
|
||||
* @param {RoleData} [data] The data to update the role with
|
||||
* @param {string} [reason] Reason for creating this role
|
||||
* @returns {Promise<Role>}
|
||||
* @example
|
||||
* // Create a new role
|
||||
* guild.createRole()
|
||||
* .then(role => console.log(`Created role ${role}`))
|
||||
* .catch(console.error);
|
||||
* .then(role => console.log(`Created role ${role}`))
|
||||
* .catch(console.error);
|
||||
* @example
|
||||
* // Create a new role with data
|
||||
* guild.createRole({
|
||||
* name: 'Super Cool People',
|
||||
* color: 'BLUE',
|
||||
* })
|
||||
* .then(role => console.log(`Created role ${role}`))
|
||||
* .catch(console.error)
|
||||
* .then(role => console.log(`Created role ${role}`))
|
||||
* .catch(console.error)
|
||||
*/
|
||||
createRole(data = {}) {
|
||||
return this.client.rest.methods.createGuildRole(this, data);
|
||||
createRole(data = {}, reason) {
|
||||
return this.client.rest.methods.createGuildRole(this, data, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -797,39 +938,38 @@ class Guild {
|
||||
* @param {BufferResolvable|Base64Resolvable} attachment The image for the emoji
|
||||
* @param {string} name The name for the emoji
|
||||
* @param {Collection<Snowflake, Role>|Role[]} [roles] Roles to limit the emoji to
|
||||
* @param {string} [reason] Reason for creating the emoji
|
||||
* @returns {Promise<Emoji>} The created emoji
|
||||
* @example
|
||||
* // Create a new emoji from a url
|
||||
* guild.createEmoji('https://i.imgur.com/w3duR07.png', 'rip')
|
||||
* .then(emoji => console.log(`Created new emoji with name ${emoji.name}!`))
|
||||
* .catch(console.error);
|
||||
* .then(emoji => console.log(`Created new emoji with name ${emoji.name}!`))
|
||||
* .catch(console.error);
|
||||
* @example
|
||||
* // Create a new emoji from a file on your computer
|
||||
* guild.createEmoji('./memes/banana.png', 'banana')
|
||||
* .then(emoji => console.log(`Created new emoji with name ${emoji.name}!`))
|
||||
* .catch(console.error);
|
||||
* .then(emoji => console.log(`Created new emoji with name ${emoji.name}!`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
createEmoji(attachment, name, roles) {
|
||||
return new Promise(resolve => {
|
||||
if (typeof attachment === 'string' && attachment.startsWith('data:')) {
|
||||
resolve(this.client.rest.methods.createEmoji(this, attachment, name, roles));
|
||||
} else {
|
||||
this.client.resolver.resolveBuffer(attachment).then(data => {
|
||||
const dataURI = this.client.resolver.resolveBase64(data);
|
||||
resolve(this.client.rest.methods.createEmoji(this, dataURI, name, roles));
|
||||
});
|
||||
}
|
||||
});
|
||||
createEmoji(attachment, name, roles, reason) {
|
||||
if (typeof attachment === 'string' && attachment.startsWith('data:')) {
|
||||
return this.client.rest.methods.createEmoji(this, attachment, name, roles, reason);
|
||||
} else {
|
||||
return this.client.resolver.resolveImage(attachment).then(data =>
|
||||
this.client.rest.methods.createEmoji(this, data, name, roles, reason)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an emoji.
|
||||
* @param {Emoji|string} emoji The emoji to delete
|
||||
* @param {string} [reason] Reason for deleting the emoji
|
||||
* @returns {Promise}
|
||||
*/
|
||||
deleteEmoji(emoji) {
|
||||
deleteEmoji(emoji, reason) {
|
||||
if (!(emoji instanceof Emoji)) emoji = this.emojis.get(emoji);
|
||||
return this.client.rest.methods.deleteEmoji(emoji);
|
||||
return this.client.rest.methods.deleteEmoji(emoji, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -838,8 +978,8 @@ class Guild {
|
||||
* @example
|
||||
* // Leave a guild
|
||||
* guild.leave()
|
||||
* .then(g => console.log(`Left the guild ${g}`))
|
||||
* .catch(console.error);
|
||||
* .then(g => console.log(`Left the guild ${g}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
leave() {
|
||||
return this.client.rest.methods.leaveGuild(this);
|
||||
@@ -851,8 +991,8 @@ class Guild {
|
||||
* @example
|
||||
* // Delete a guild
|
||||
* guild.delete()
|
||||
* .then(g => console.log(`Deleted the guild ${g}`))
|
||||
* .catch(console.error);
|
||||
* .then(g => console.log(`Deleted the guild ${g}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
delete() {
|
||||
return this.client.rest.methods.deleteGuild(this);
|
||||
@@ -1063,10 +1203,22 @@ class Guild {
|
||||
_sortPositionWithID(collection) {
|
||||
return collection.sort((a, b) =>
|
||||
a.position !== b.position ?
|
||||
a.position - b.position :
|
||||
Long.fromString(a.id).sub(Long.fromString(b.id)).toNumber()
|
||||
a.position - b.position :
|
||||
Long.fromString(a.id).sub(Long.fromString(b.id)).toNumber()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The `#general` TextChannel of the guild
|
||||
* @name Guild#defaultChannel
|
||||
* @type {TextChannel}
|
||||
* @readonly
|
||||
*/
|
||||
Object.defineProperty(Guild.prototype, 'defaultChannel', {
|
||||
get: util.deprecate(function defaultChannel() {
|
||||
return this.channels.get(this.id);
|
||||
}, 'Guild#defaultChannel: This property is obsolete, will be removed in v12.0.0, and may not function as expected.'),
|
||||
});
|
||||
|
||||
module.exports = Guild;
|
||||
|
||||
@@ -2,6 +2,7 @@ const Collection = require('../util/Collection');
|
||||
const Snowflake = require('../util/Snowflake');
|
||||
|
||||
const Targets = {
|
||||
ALL: 'ALL',
|
||||
GUILD: 'GUILD',
|
||||
CHANNEL: 'CHANNEL',
|
||||
USER: 'USER',
|
||||
@@ -9,9 +10,11 @@ const Targets = {
|
||||
INVITE: 'INVITE',
|
||||
WEBHOOK: 'WEBHOOK',
|
||||
EMOJI: 'EMOJI',
|
||||
MESSAGE: 'MESSAGE',
|
||||
};
|
||||
|
||||
const Actions = {
|
||||
ALL: null,
|
||||
GUILD_UPDATE: 1,
|
||||
CHANNEL_CREATE: 10,
|
||||
CHANNEL_UPDATE: 11,
|
||||
@@ -37,6 +40,7 @@ const Actions = {
|
||||
EMOJI_CREATE: 60,
|
||||
EMOJI_UPDATE: 61,
|
||||
EMOJI_DELETE: 62,
|
||||
MESSAGE_DELETE: 72,
|
||||
};
|
||||
|
||||
|
||||
@@ -60,13 +64,11 @@ class GuildAuditLogs {
|
||||
|
||||
/**
|
||||
* Handles possible promises for entry targets.
|
||||
* @returns {GuildAuditLogs}
|
||||
* @returns {Promise<GuildAuditLogs>}
|
||||
*/
|
||||
static build(...args) {
|
||||
return new Promise(resolve => {
|
||||
const logs = new GuildAuditLogs(...args);
|
||||
Promise.all(logs.entries.map(e => e.target)).then(() => resolve(logs));
|
||||
});
|
||||
const logs = new GuildAuditLogs(...args);
|
||||
return Promise.all(logs.entries.map(e => e.target)).then(() => logs);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -82,6 +84,7 @@ class GuildAuditLogs {
|
||||
if (target < 50) return Targets.INVITE;
|
||||
if (target < 60) return Targets.WEBHOOK;
|
||||
if (target < 70) return Targets.EMOJI;
|
||||
if (target < 80) return Targets.MESSAGE;
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -112,6 +115,7 @@ class GuildAuditLogs {
|
||||
Actions.INVITE_DELETE,
|
||||
Actions.WEBHOOK_DELETE,
|
||||
Actions.EMOJI_DELETE,
|
||||
Actions.MESSAGE_DELETE,
|
||||
].includes(action)) return 'DELETE';
|
||||
|
||||
if ([
|
||||
@@ -119,6 +123,7 @@ class GuildAuditLogs {
|
||||
Actions.CHANNEL_UPDATE,
|
||||
Actions.CHANNEL_OVERWRITE_UPDATE,
|
||||
Actions.MEMBER_UPDATE,
|
||||
Actions.MEMBER_ROLE_UPDATE,
|
||||
Actions.ROLE_UPDATE,
|
||||
Actions.INVITE_UPDATE,
|
||||
Actions.WEBHOOK_UPDATE,
|
||||
@@ -166,10 +171,18 @@ class GuildAuditLogsEntry {
|
||||
this.executor = guild.client.users.get(data.user_id);
|
||||
|
||||
/**
|
||||
* Specific property changes
|
||||
* @type {Object[]}
|
||||
* An entry in the audit log representing a specific change.
|
||||
* @typedef {object} AuditLogChange
|
||||
* @property {string} key The property that was changed, e.g. `nick` for nickname changes
|
||||
* @property {*} [old] The old value of the change, e.g. for nicknames, the old nickname
|
||||
* @property {*} [new] The new value of the change, e.g. for nicknames, the new nickname
|
||||
*/
|
||||
this.changes = data.changes ? data.changes.map(c => ({ name: c.key, old: c.old_value, new: c.new_value })) : null;
|
||||
|
||||
/**
|
||||
* Specific property changes
|
||||
* @type {AuditLogChange[]}
|
||||
*/
|
||||
this.changes = data.changes ? data.changes.map(c => ({ key: c.key, old: c.old_value, new: c.new_value })) : null;
|
||||
|
||||
/**
|
||||
* The ID of this entry
|
||||
@@ -188,15 +201,20 @@ class GuildAuditLogsEntry {
|
||||
removed: data.options.members_removed,
|
||||
days: data.options.delete_member_days,
|
||||
};
|
||||
} else if (data.action_type === Actions.MESSAGE_DELETE) {
|
||||
this.extra = {
|
||||
count: data.options.count,
|
||||
channel: guild.channels.get(data.options.channel_id),
|
||||
};
|
||||
} else {
|
||||
switch (data.options.type) {
|
||||
case 'member':
|
||||
this.extra = guild.members.get(this.options.id);
|
||||
if (!this.extra) this.extra = { id: this.options.id };
|
||||
this.extra = guild.members.get(data.options.id);
|
||||
if (!this.extra) this.extra = { id: data.options.id };
|
||||
break;
|
||||
case 'role':
|
||||
this.extra = guild.roles.get(this.options.id);
|
||||
if (!this.extra) this.extra = { id: this.options.id, name: this.options.role_name };
|
||||
this.extra = guild.roles.get(data.options.id);
|
||||
if (!this.extra) this.extra = { id: data.options.id, name: data.options.role_name };
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -217,12 +235,14 @@ class GuildAuditLogsEntry {
|
||||
return this.target;
|
||||
});
|
||||
} else if (targetType === Targets.INVITE) {
|
||||
const change = this.changes.find(c => c.name === 'code');
|
||||
const change = this.changes.find(c => c.key === 'code');
|
||||
this.target = guild.fetchInvites()
|
||||
.then(invites => {
|
||||
this.target = invites.find(i => i.code === (change.new || change.old));
|
||||
this.target = invites.find(i => i.code === (change.new_value || change.old_value));
|
||||
return this.target;
|
||||
});
|
||||
} else if (targetType === Targets.MESSAGE) {
|
||||
this.target = guild.client.users.get(data.target_id);
|
||||
} else {
|
||||
this.target = guild[`${targetType.toLowerCase()}s`].get(data.target_id);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ const Role = require('./Role');
|
||||
const PermissionOverwrites = require('./PermissionOverwrites');
|
||||
const Permissions = require('../util/Permissions');
|
||||
const Collection = require('../util/Collection');
|
||||
const Constants = require('../util/Constants');
|
||||
|
||||
/**
|
||||
* Represents a guild channel (i.e. text channels and voice channels).
|
||||
@@ -72,6 +73,9 @@ class GuildChannel extends Channel {
|
||||
const roles = member.roles;
|
||||
for (const role of roles.values()) permissions |= role.permissions;
|
||||
|
||||
const admin = Boolean(permissions & Permissions.FLAGS.ADMINISTRATOR);
|
||||
if (admin) return new Permissions(Permissions.ALL);
|
||||
|
||||
const overwrites = this.overwritesFor(member, true, roles);
|
||||
|
||||
if (overwrites.everyone) {
|
||||
@@ -91,9 +95,6 @@ class GuildChannel extends Channel {
|
||||
permissions |= overwrites.member.allow;
|
||||
}
|
||||
|
||||
const admin = Boolean(permissions & Permissions.FLAGS.ADMINISTRATOR);
|
||||
if (admin) permissions = Permissions.ALL;
|
||||
|
||||
return new Permissions(member, permissions);
|
||||
}
|
||||
|
||||
@@ -136,18 +137,19 @@ class GuildChannel extends Channel {
|
||||
|
||||
/**
|
||||
* Overwrites the permissions for a user or role in this channel.
|
||||
* @param {RoleResolvable|UserResolvable} userOrRole The user or role to update
|
||||
* @param {Role|Snowflake|UserResolvable} userOrRole The user or role to update
|
||||
* @param {PermissionOverwriteOptions} options The configuration for the update
|
||||
* @param {string} [reason] Reason for creating/editing this overwrite
|
||||
* @returns {Promise}
|
||||
* @example
|
||||
* // Overwrite permissions for a message author
|
||||
* message.channel.overwritePermissions(message.author, {
|
||||
* SEND_MESSAGES: false
|
||||
* SEND_MESSAGES: false
|
||||
* })
|
||||
* .then(() => console.log('Done!'))
|
||||
* .catch(console.error);
|
||||
* .then(() => console.log('Done!'))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
overwritePermissions(userOrRole, options) {
|
||||
overwritePermissions(userOrRole, options, reason) {
|
||||
const payload = {
|
||||
allow: 0,
|
||||
deny: 0,
|
||||
@@ -186,7 +188,7 @@ class GuildChannel extends Channel {
|
||||
}
|
||||
}
|
||||
|
||||
return this.client.rest.methods.setChannelOverwrite(this, payload);
|
||||
return this.client.rest.methods.setChannelOverwrite(this, payload, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -202,29 +204,31 @@ class GuildChannel extends Channel {
|
||||
/**
|
||||
* Edits the channel.
|
||||
* @param {ChannelData} data The new data for the channel
|
||||
* @param {string} [reason] Reason for editing this channel
|
||||
* @returns {Promise<GuildChannel>}
|
||||
* @example
|
||||
* // Edit a channel
|
||||
* channel.edit({name: 'new-channel'})
|
||||
* .then(c => console.log(`Edited channel ${c}`))
|
||||
* .catch(console.error);
|
||||
* .then(c => console.log(`Edited channel ${c}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
edit(data) {
|
||||
return this.client.rest.methods.updateChannel(this, data);
|
||||
edit(data, reason) {
|
||||
return this.client.rest.methods.updateChannel(this, data, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a new name for the guild channel.
|
||||
* @param {string} name The new name for the guild channel
|
||||
* @param {string} [reason] Reason for changing the guild channel's name
|
||||
* @returns {Promise<GuildChannel>}
|
||||
* @example
|
||||
* // Set a new channel name
|
||||
* channel.setName('not_general')
|
||||
* .then(newChannel => console.log(`Channel's new name is ${newChannel.name}`))
|
||||
* .catch(console.error);
|
||||
* .then(newChannel => console.log(`Channel's new name is ${newChannel.name}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
setName(name) {
|
||||
return this.edit({ name });
|
||||
setName(name, reason) {
|
||||
return this.edit({ name }, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -235,8 +239,8 @@ class GuildChannel extends Channel {
|
||||
* @example
|
||||
* // Set a new channel position
|
||||
* channel.setPosition(2)
|
||||
* .then(newChannel => console.log(`Channel's new position is ${newChannel.position}`))
|
||||
* .catch(console.error);
|
||||
* .then(newChannel => console.log(`Channel's new position is ${newChannel.position}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
setPosition(position, relative) {
|
||||
return this.guild.setChannelPosition(this, position, relative).then(() => this);
|
||||
@@ -245,34 +249,32 @@ class GuildChannel extends Channel {
|
||||
/**
|
||||
* Set a new topic for the guild channel.
|
||||
* @param {string} topic The new topic for the guild channel
|
||||
* @param {string} [reason] Reason for changing the guild channel's topic
|
||||
* @returns {Promise<GuildChannel>}
|
||||
* @example
|
||||
* // Set a new channel topic
|
||||
* channel.setTopic('needs more rate limiting')
|
||||
* .then(newChannel => console.log(`Channel's new topic is ${newChannel.topic}`))
|
||||
* .catch(console.error);
|
||||
* .then(newChannel => console.log(`Channel's new topic is ${newChannel.topic}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
setTopic(topic) {
|
||||
return this.client.rest.methods.updateChannel(this, { topic });
|
||||
setTopic(topic, reason) {
|
||||
return this.edit({ topic }, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Options given when creating a guild channel invite.
|
||||
* @typedef {Object} InviteOptions
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create an invite to this guild channel.
|
||||
* @param {InviteOptions} [options={}] Options for the invite
|
||||
* <warn>This is only available when using a bot account.</warn>
|
||||
* @param {Object} [options={}] Options for the invite
|
||||
* @param {boolean} [options.temporary=false] Whether members that joined via the invite should be automatically
|
||||
* kicked after 24 hours if they have not yet received a role
|
||||
* @param {number} [options.maxAge=86400] How long the invite should last (in seconds, 0 for forever)
|
||||
* @param {number} [options.maxUses=0] Maximum number of uses
|
||||
* @param {boolean} [options.unique=false] Create a unique invite, or use an existing one with similar settings
|
||||
* @param {string} [reason] Reason for creating the invite
|
||||
* @returns {Promise<Invite>}
|
||||
*/
|
||||
createInvite(options = {}) {
|
||||
return this.client.rest.methods.createChannelInvite(this, options);
|
||||
createInvite(options = {}, reason) {
|
||||
return this.client.rest.methods.createChannelInvite(this, options, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -280,13 +282,28 @@ class GuildChannel extends Channel {
|
||||
* @param {string} [name=this.name] Optional name for the new channel, otherwise it has the name of this channel
|
||||
* @param {boolean} [withPermissions=true] Whether to clone the channel with this channel's permission overwrites
|
||||
* @param {boolean} [withTopic=true] Whether to clone the channel with this channel's topic
|
||||
* @param {string} [reason] Reason for cloning this channel
|
||||
* @returns {Promise<GuildChannel>}
|
||||
*/
|
||||
clone(name = this.name, withPermissions = true, withTopic = true) {
|
||||
return this.guild.createChannel(name, this.type, withPermissions ? this.permissionOverwrites : [])
|
||||
clone(name = this.name, withPermissions = true, withTopic = true, reason) {
|
||||
return this.guild.createChannel(name, this.type, withPermissions ? this.permissionOverwrites : [], reason)
|
||||
.then(channel => withTopic ? channel.setTopic(this.topic) : channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes this channel.
|
||||
* @param {string} [reason] Reason for deleting this channel
|
||||
* @returns {Promise<GuildChannel>}
|
||||
* @example
|
||||
* // Delete the channel
|
||||
* channel.delete('making room for new channels')
|
||||
* .then(channel => console.log(`Deleted ${channel.name} to make room for new channels`))
|
||||
* .catch(console.error); // Log error
|
||||
*/
|
||||
delete(reason) {
|
||||
return this.client.rest.methods.deleteChannel(this, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this channel has the same type, topic, position, name, overwrites and ID as another channel.
|
||||
* In most cases, a simple `channel.id === channel2.id` will do, and is much faster too.
|
||||
@@ -322,6 +339,36 @@ class GuildChannel extends Channel {
|
||||
this.permissionsFor(this.client.user).has(Permissions.FLAGS.MANAGE_CHANNELS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the channel is muted
|
||||
* <warn>This is only available when using a user account.</warn>
|
||||
* @type {?boolean}
|
||||
* @readonly
|
||||
*/
|
||||
get muted() {
|
||||
if (this.client.user.bot) return null;
|
||||
try {
|
||||
return this.client.user.guildSettings.get(this.guild.id).channelOverrides.get(this.id).muted;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of message that should notify you
|
||||
* <warn>This is only available when using a user account.</warn>
|
||||
* @type {?MessageNotificationType}
|
||||
* @readonly
|
||||
*/
|
||||
get messageNotifications() {
|
||||
if (this.client.user.bot) return null;
|
||||
try {
|
||||
return this.client.user.guildSettings.get(this.guild.id).channelOverrides.get(this.id).messageNotifications;
|
||||
} catch (err) {
|
||||
return Constants.MessageNotificationTypes[3];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When concatenated with a string, this automatically returns the channel's mention instead of the Channel object.
|
||||
* @returns {string}
|
||||
|
||||
@@ -345,28 +345,31 @@ class GuildMember {
|
||||
/**
|
||||
* Edit a guild member.
|
||||
* @param {GuildMemberEditData} data The data to edit the member with
|
||||
* @param {string} [reason] Reason for editing this user
|
||||
* @returns {Promise<GuildMember>}
|
||||
*/
|
||||
edit(data) {
|
||||
return this.client.rest.methods.updateGuildMember(this, data);
|
||||
edit(data, reason) {
|
||||
return this.client.rest.methods.updateGuildMember(this, data, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mute/unmute a user.
|
||||
* @param {boolean} mute Whether or not the member should be muted
|
||||
* @param {string} [reason] Reason for muting or unmuting
|
||||
* @returns {Promise<GuildMember>}
|
||||
*/
|
||||
setMute(mute) {
|
||||
return this.edit({ mute });
|
||||
setMute(mute, reason) {
|
||||
return this.edit({ mute }, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deafen/undeafen a user.
|
||||
* @param {boolean} deaf Whether or not the member should be deafened
|
||||
* @param {string} [reason] Reason for deafening or undeafening
|
||||
* @returns {Promise<GuildMember>}
|
||||
*/
|
||||
setDeaf(deaf) {
|
||||
return this.edit({ deaf });
|
||||
setDeaf(deaf, reason) {
|
||||
return this.edit({ deaf }, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -381,29 +384,32 @@ class GuildMember {
|
||||
/**
|
||||
* Sets the roles applied to the member.
|
||||
* @param {Collection<Snowflake, Role>|Role[]|Snowflake[]} roles The roles or role IDs to apply
|
||||
* @param {string} [reason] Reason for applying the roles
|
||||
* @returns {Promise<GuildMember>}
|
||||
*/
|
||||
setRoles(roles) {
|
||||
return this.edit({ roles });
|
||||
setRoles(roles, reason) {
|
||||
return this.edit({ roles }, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a single role to the member.
|
||||
* @param {Role|Snowflake} role The role or ID of the role to add
|
||||
* @param {string} [reason] Reason for adding the role
|
||||
* @returns {Promise<GuildMember>}
|
||||
*/
|
||||
addRole(role) {
|
||||
addRole(role, reason) {
|
||||
if (!(role instanceof Role)) role = this.guild.roles.get(role);
|
||||
if (!role) throw new TypeError('Supplied parameter was neither a Role nor a Snowflake.');
|
||||
return this.client.rest.methods.addMemberRole(this, role);
|
||||
if (!role) return Promise.reject(new TypeError('Supplied parameter was neither a Role nor a Snowflake.'));
|
||||
return this.client.rest.methods.addMemberRole(this, role, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds multiple roles to the member.
|
||||
* @param {Collection<Snowflake, Role>|Role[]|Snowflake[]} roles The roles or role IDs to add
|
||||
* @param {string} [reason] Reason for adding the roles
|
||||
* @returns {Promise<GuildMember>}
|
||||
*/
|
||||
addRoles(roles) {
|
||||
addRoles(roles, reason) {
|
||||
let allRoles;
|
||||
if (roles instanceof Collection) {
|
||||
allRoles = this._roles.slice();
|
||||
@@ -411,26 +417,28 @@ class GuildMember {
|
||||
} else {
|
||||
allRoles = this._roles.concat(roles);
|
||||
}
|
||||
return this.edit({ roles: allRoles });
|
||||
return this.edit({ roles: allRoles }, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a single role from the member.
|
||||
* @param {Role|Snowflake} role The role or ID of the role to remove
|
||||
* @param {string} [reason] Reason for removing the role
|
||||
* @returns {Promise<GuildMember>}
|
||||
*/
|
||||
removeRole(role) {
|
||||
removeRole(role, reason) {
|
||||
if (!(role instanceof Role)) role = this.guild.roles.get(role);
|
||||
if (!role) throw new TypeError('Supplied parameter was neither a Role nor a Snowflake.');
|
||||
return this.client.rest.methods.removeMemberRole(this, role);
|
||||
if (!role) return Promise.reject(new TypeError('Supplied parameter was neither a Role nor a Snowflake.'));
|
||||
return this.client.rest.methods.removeMemberRole(this, role, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes multiple roles from the member.
|
||||
* @param {Collection<Snowflake, Role>|Role[]|Snowflake[]} roles The roles or role IDs to remove
|
||||
* @param {string} [reason] Reason for removing the roles
|
||||
* @returns {Promise<GuildMember>}
|
||||
*/
|
||||
removeRoles(roles) {
|
||||
removeRoles(roles, reason) {
|
||||
const allRoles = this._roles.slice();
|
||||
if (roles instanceof Collection) {
|
||||
for (const role of roles.values()) {
|
||||
@@ -443,16 +451,17 @@ class GuildMember {
|
||||
if (index >= 0) allRoles.splice(index, 1);
|
||||
}
|
||||
}
|
||||
return this.edit({ roles: allRoles });
|
||||
return this.edit({ roles: allRoles }, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the nickname for the guild member.
|
||||
* @param {string} nick The nickname for the guild member
|
||||
* @param {string} [reason] Reason for setting the nickname
|
||||
* @returns {Promise<GuildMember>}
|
||||
*/
|
||||
setNickname(nick) {
|
||||
return this.edit({ nick });
|
||||
setNickname(nick, reason) {
|
||||
return this.edit({ nick }, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -481,7 +490,7 @@ class GuildMember {
|
||||
}
|
||||
|
||||
/**
|
||||
* Ban this guild member
|
||||
* Ban this guild member.
|
||||
* @param {Object|number|string} [options] Ban options. If a number, the number of days to delete messages for, if a
|
||||
* string, the ban reason. Supplying an object allows you to do both.
|
||||
* @param {number} [options.days=0] Number of days of messages to delete
|
||||
|
||||
@@ -2,27 +2,6 @@ const PartialGuild = require('./PartialGuild');
|
||||
const PartialGuildChannel = require('./PartialGuildChannel');
|
||||
const Constants = require('../util/Constants');
|
||||
|
||||
/*
|
||||
{ max_age: 86400,
|
||||
code: 'CG9A5',
|
||||
guild:
|
||||
{ splash: null,
|
||||
id: '123123123',
|
||||
icon: '123123123',
|
||||
name: 'name' },
|
||||
created_at: '2016-08-28T19:07:04.763368+00:00',
|
||||
temporary: false,
|
||||
uses: 0,
|
||||
max_uses: 0,
|
||||
inviter:
|
||||
{ username: '123',
|
||||
discriminator: '4204',
|
||||
bot: true,
|
||||
id: '123123123',
|
||||
avatar: '123123123' },
|
||||
channel: { type: 0, id: '123123', name: 'heavy-testing' } }
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents an invitation to a guild channel.
|
||||
* <warn>The only guaranteed properties are `code`, `guild` and `channel`. Other properties can be missing.</warn>
|
||||
@@ -54,6 +33,30 @@ class Invite {
|
||||
*/
|
||||
this.code = data.code;
|
||||
|
||||
/**
|
||||
* The approximate number of online members of the guild this invite is for
|
||||
* @type {number}
|
||||
*/
|
||||
this.presenceCount = data.approximate_presence_count;
|
||||
|
||||
/**
|
||||
* The approximate total number of members of the guild this invite is for
|
||||
* @type {number}
|
||||
*/
|
||||
this.memberCount = data.approximate_member_count;
|
||||
|
||||
/**
|
||||
* The number of text channels the guild this invite goes to has
|
||||
* @type {number}
|
||||
*/
|
||||
this.textChannelCount = data.guild.text_channel_count;
|
||||
|
||||
/**
|
||||
* The number of voice channels the guild this invite goes to has
|
||||
* @type {number}
|
||||
*/
|
||||
this.voiceChannelCount = data.guild.voice_channel_count;
|
||||
|
||||
/**
|
||||
* Whether or not this invite is temporary
|
||||
* @type {boolean}
|
||||
@@ -138,10 +141,11 @@ class Invite {
|
||||
|
||||
/**
|
||||
* Deletes this invite.
|
||||
* @param {string} [reason] Reason for deleting this invite
|
||||
* @returns {Promise<Invite>}
|
||||
*/
|
||||
delete() {
|
||||
return this.client.rest.methods.deleteInvite(this);
|
||||
delete(reason) {
|
||||
return this.client.rest.methods.deleteInvite(this, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const Mentions = require('./MessageMentions');
|
||||
const Attachment = require('./MessageAttachment');
|
||||
const Embed = require('./MessageEmbed');
|
||||
const RichEmbed = require('./RichEmbed');
|
||||
const MessageReaction = require('./MessageReaction');
|
||||
const ReactionCollector = require('./ReactionCollector');
|
||||
const Util = require('../util/Util');
|
||||
@@ -33,7 +34,7 @@ class Message {
|
||||
|
||||
setup(data) { // eslint-disable-line complexity
|
||||
/**
|
||||
* The ID of the message (unique in the channel it was sent)
|
||||
* The ID of the message
|
||||
* @type {Snowflake}
|
||||
*/
|
||||
this.id = data.id;
|
||||
@@ -57,8 +58,8 @@ class Message {
|
||||
this.author = this.client.dataManager.newUser(data.author);
|
||||
|
||||
/**
|
||||
* Represents the author of the message as a guild member. Only available if the message comes from a guild
|
||||
* where the author is still a member.
|
||||
* Represents the author of the message as a guild member
|
||||
* Only available if the message comes from a guild where the author is still a member
|
||||
* @type {?GuildMember}
|
||||
*/
|
||||
this.member = this.guild ? this.guild.member(this.author) || null : null;
|
||||
@@ -209,8 +210,8 @@ class Message {
|
||||
}
|
||||
|
||||
/**
|
||||
* The message contents with all mentions replaced by the equivalent text. If mentions cannot be resolved to a name,
|
||||
* the relevant mention in the message content will not be converted
|
||||
* The message contents with all mentions replaced by the equivalent text.
|
||||
* If mentions cannot be resolved to a name, the relevant mention in the message content will not be converted.
|
||||
* @type {string}
|
||||
* @readonly
|
||||
*/
|
||||
@@ -254,8 +255,8 @@ class Message {
|
||||
* @example
|
||||
* // Create a reaction collector
|
||||
* const collector = message.createReactionCollector(
|
||||
* (reaction, user) => reaction.emoji.id === '👌' && user.id === 'someID',
|
||||
* { time: 15000 }
|
||||
* (reaction, user) => reaction.emoji.name === '👌' && user.id === 'someID',
|
||||
* { time: 15000 }
|
||||
* );
|
||||
* collector.on('collect', r => console.log(`Collected ${r.emoji.name}`));
|
||||
* collector.on('end', collected => console.log(`Collected ${collected.size} items`));
|
||||
@@ -271,8 +272,8 @@ class Message {
|
||||
*/
|
||||
|
||||
/**
|
||||
* Similar to createCollector but in promise form. Resolves with a collection of reactions that pass the specified
|
||||
* filter.
|
||||
* Similar to createMessageCollector but in promise form.
|
||||
* Resolves with a collection of reactions that pass the specified filter.
|
||||
* @param {CollectorFilter} filter The filter function to use
|
||||
* @param {AwaitReactionsOptions} [options={}] Optional options to pass to the internal collector
|
||||
* @returns {Promise<Collection<string, MessageReaction>>}
|
||||
@@ -365,13 +366,13 @@ class Message {
|
||||
/**
|
||||
* Edit the content of the message.
|
||||
* @param {StringResolvable} [content] The new content for the message
|
||||
* @param {MessageEditOptions} [options] The options to provide
|
||||
* @param {MessageEditOptions|RichEmbed} [options] The options to provide
|
||||
* @returns {Promise<Message>}
|
||||
* @example
|
||||
* // Update the content of a message
|
||||
* message.edit('This is my new content!')
|
||||
* .then(msg => console.log(`Updated the content of a message from ${msg.author}`))
|
||||
* .catch(console.error);
|
||||
* .then(msg => console.log(`Updated the content of a message from ${msg.author}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
edit(content, options) {
|
||||
if (!options && typeof content === 'object' && !(content instanceof Array)) {
|
||||
@@ -380,6 +381,7 @@ class Message {
|
||||
} else if (!options) {
|
||||
options = {};
|
||||
}
|
||||
if (options instanceof RichEmbed) options = { embed: options };
|
||||
return this.client.rest.methods.updateMessage(this, content, options);
|
||||
}
|
||||
|
||||
@@ -388,6 +390,7 @@ class Message {
|
||||
* @param {string} lang The language for the code block
|
||||
* @param {StringResolvable} content The new content for the message
|
||||
* @returns {Promise<Message>}
|
||||
* @deprecated
|
||||
*/
|
||||
editCode(lang, content) {
|
||||
content = Util.escapeMarkdown(this.client.resolver.resolveString(content), true);
|
||||
@@ -437,8 +440,8 @@ class Message {
|
||||
* @example
|
||||
* // Delete a message
|
||||
* message.delete()
|
||||
* .then(msg => console.log(`Deleted message from ${msg.author}`))
|
||||
* .catch(console.error);
|
||||
* .then(msg => console.log(`Deleted message from ${msg.author}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
delete(timeout = 0) {
|
||||
if (timeout <= 0) {
|
||||
@@ -460,8 +463,8 @@ class Message {
|
||||
* @example
|
||||
* // Reply to a message
|
||||
* message.reply('Hey, I\'m a reply!')
|
||||
* .then(msg => console.log(`Sent a reply to ${msg.author}`))
|
||||
* .catch(console.error);
|
||||
* .then(msg => console.log(`Sent a reply to ${msg.author}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
reply(content, options) {
|
||||
if (!options && typeof content === 'object' && !(content instanceof Array)) {
|
||||
@@ -533,7 +536,7 @@ class Message {
|
||||
}
|
||||
|
||||
_addReaction(emoji, user) {
|
||||
const emojiID = emoji.id ? `${emoji.name}:${emoji.id}` : encodeURIComponent(emoji.name);
|
||||
const emojiID = emoji.id ? `${emoji.name}:${emoji.id}` : emoji.name;
|
||||
let reaction;
|
||||
if (this.reactions.has(emojiID)) {
|
||||
reaction = this.reactions.get(emojiID);
|
||||
@@ -542,13 +545,15 @@ class Message {
|
||||
reaction = new MessageReaction(this, emoji, 0, user.id === this.client.user.id);
|
||||
this.reactions.set(emojiID, reaction);
|
||||
}
|
||||
if (!reaction.users.has(user.id)) reaction.users.set(user.id, user);
|
||||
reaction.count++;
|
||||
if (!reaction.users.has(user.id)) {
|
||||
reaction.users.set(user.id, user);
|
||||
reaction.count++;
|
||||
}
|
||||
return reaction;
|
||||
}
|
||||
|
||||
_removeReaction(emoji, user) {
|
||||
const emojiID = emoji.id ? `${emoji.name}:${emoji.id}` : encodeURIComponent(emoji.name);
|
||||
const emojiID = emoji.id ? `${emoji.name}:${emoji.id}` : emoji.name;
|
||||
if (this.reactions.has(emojiID)) {
|
||||
const reaction = this.reactions.get(emojiID);
|
||||
if (reaction.users.has(user.id)) {
|
||||
|
||||
@@ -12,7 +12,6 @@ const util = require('util');
|
||||
* @extends {Collector}
|
||||
*/
|
||||
class MessageCollector extends Collector {
|
||||
|
||||
/**
|
||||
* @param {TextChannel|DMChannel|GroupDMChannel} channel The channel
|
||||
* @param {CollectorFilter} filter The filter to be applied to this collector
|
||||
@@ -23,7 +22,8 @@ class MessageCollector extends Collector {
|
||||
super(channel.client, filter, options);
|
||||
|
||||
/**
|
||||
* @type {TextBasedChannel} channel The channel
|
||||
* The channel
|
||||
* @type {TextBasedChannel}
|
||||
*/
|
||||
this.channel = channel;
|
||||
|
||||
@@ -61,7 +61,7 @@ class MessageCollector extends Collector {
|
||||
/**
|
||||
* Handle an incoming message for possible collection.
|
||||
* @param {Message} message The message that could be collected
|
||||
* @returns {?{key: Snowflake, value: Message}} Message data to collect
|
||||
* @returns {?{key: Snowflake, value: Message}}
|
||||
* @private
|
||||
*/
|
||||
handle(message) {
|
||||
|
||||
@@ -102,7 +102,7 @@ class MessageMentions {
|
||||
|
||||
/**
|
||||
* Any channels that were mentioned
|
||||
* @type {?Collection<Snowflake, GuildChannel>}
|
||||
* @type {Collection<Snowflake, GuildChannel>}
|
||||
* @readonly
|
||||
*/
|
||||
get channels() {
|
||||
@@ -124,7 +124,7 @@ class MessageMentions {
|
||||
MessageMentions.EVERYONE_PATTERN = /@(everyone|here)/g;
|
||||
|
||||
/**
|
||||
* Regular expression that globally matches user mentions like `<#81440962496172032>`
|
||||
* Regular expression that globally matches user mentions like `<@81440962496172032>`
|
||||
* @type {RegExp}
|
||||
*/
|
||||
MessageMentions.USERS_PATTERN = /<@!?[0-9]+>/g;
|
||||
|
||||
@@ -37,7 +37,7 @@ class OAuth2Application {
|
||||
|
||||
/**
|
||||
* The app's icon hash
|
||||
* @type {string}
|
||||
* @type {?string}
|
||||
*/
|
||||
this.icon = data.icon;
|
||||
|
||||
@@ -124,6 +124,7 @@ class OAuth2Application {
|
||||
|
||||
/**
|
||||
* Reset the app's secret and bot token.
|
||||
* <warn>This is only available when using a user account.</warn>
|
||||
* @returns {OAuth2Application}
|
||||
*/
|
||||
reset() {
|
||||
|
||||
@@ -33,10 +33,11 @@ class PermissionOverwrites {
|
||||
|
||||
/**
|
||||
* Delete this Permission Overwrite.
|
||||
* @param {string} [reason] Reason for deleting this overwrite
|
||||
* @returns {Promise<PermissionOverwrites>}
|
||||
*/
|
||||
delete() {
|
||||
return this.channel.client.rest.methods.deletePermissionOverwrites(this);
|
||||
delete(reason) {
|
||||
return this.channel.client.rest.methods.deletePermissionOverwrites(this, reason);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ const Collection = require('../util/Collection');
|
||||
* @extends {Collector}
|
||||
*/
|
||||
class ReactionCollector extends Collector {
|
||||
|
||||
/**
|
||||
* @param {Message} message The message upon which to collect reactions
|
||||
* @param {CollectorFilter} filter The filter to apply to this collector
|
||||
@@ -46,7 +45,7 @@ class ReactionCollector extends Collector {
|
||||
/**
|
||||
* Handle an incoming reaction for possible collection.
|
||||
* @param {MessageReaction} reaction The reaction to possibly collect
|
||||
* @returns {?{key: Snowflake, value: MessageReaction}} Reaction data to collect
|
||||
* @returns {?{key: Snowflake, value: MessageReaction}}
|
||||
* @private
|
||||
*/
|
||||
handle(reaction) {
|
||||
|
||||
@@ -38,7 +38,7 @@ class ReactionEmoji {
|
||||
* Creates the text required to form a graphical emoji on Discord.
|
||||
* @example
|
||||
* // Send the emoji used in a reaction to the channel the reaction is part of
|
||||
* reaction.message.channel.sendMessage(`The emoji used is ${reaction.emoji}`);
|
||||
* reaction.message.channel.send(`The emoji used is ${reaction.emoji}`);
|
||||
* @returns {string}
|
||||
*/
|
||||
toString() {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const ClientDataResolver = require('../client/ClientDataResolver');
|
||||
const Attachment = require('./Attachment');
|
||||
let ClientDataResolver;
|
||||
|
||||
/**
|
||||
* A rich embed to be sent with a message with a fluent interface for creation.
|
||||
@@ -68,7 +69,7 @@ class RichEmbed {
|
||||
|
||||
/**
|
||||
* File to upload alongside this Embed
|
||||
* @type {string}
|
||||
* @type {FileOptions|string|Attachment}
|
||||
*/
|
||||
this.file = data.file;
|
||||
}
|
||||
@@ -113,6 +114,7 @@ class RichEmbed {
|
||||
* @returns {RichEmbed} This embed
|
||||
*/
|
||||
setColor(color) {
|
||||
if (!ClientDataResolver) ClientDataResolver = require('../client/ClientDataResolver');
|
||||
this.color = ClientDataResolver.resolveColor(color);
|
||||
return this;
|
||||
}
|
||||
@@ -203,11 +205,13 @@ class RichEmbed {
|
||||
/**
|
||||
* Sets the file to upload alongside the embed. This file can be accessed via `attachment://fileName.extension` when
|
||||
* setting an embed image or author/footer icons. Only one file may be attached.
|
||||
* @param {FileOptions|string} file Local path or URL to the file to attach, or valid FileOptions for a file to attach
|
||||
* @param {FileOptions|string|Attachment} file Local path or URL to the file to attach,
|
||||
* or valid FileOptions for a file to attach
|
||||
* @returns {RichEmbed} This embed
|
||||
*/
|
||||
attachFile(file) {
|
||||
if (this.file) throw new RangeError('You may not upload more than one file at once.');
|
||||
if (file instanceof Attachment) file = file.file;
|
||||
this.file = file;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ class Role {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an object mapping permission names to whether or not the role enables that permission
|
||||
* Get an object mapping permission names to whether or not the role enables that permission.
|
||||
* @returns {Object<string, boolean>}
|
||||
* @example
|
||||
* // Print the serialized role permissions
|
||||
@@ -195,64 +195,68 @@ class Role {
|
||||
* @property {ColorResolvable} [color] The color of the role, either a hex string or a base 10 number
|
||||
* @property {boolean} [hoist] Whether or not the role should be hoisted
|
||||
* @property {number} [position] The position of the role
|
||||
* @property {string[]} [permissions] The permissions of the role
|
||||
* @property {PermissionResolvable[]|number} [permissions] The permissions of the role
|
||||
* @property {boolean} [mentionable] Whether or not the role should be mentionable
|
||||
*/
|
||||
|
||||
/**
|
||||
* Edits the role.
|
||||
* @param {RoleData} data The new data for the role
|
||||
* @param {string} [reason] The reason for editing this role
|
||||
* @returns {Promise<Role>}
|
||||
* @example
|
||||
* // Edit a role
|
||||
* role.edit({name: 'new role'})
|
||||
* .then(r => console.log(`Edited role ${r}`))
|
||||
* .catch(console.error);
|
||||
* .then(r => console.log(`Edited role ${r}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
edit(data) {
|
||||
return this.client.rest.methods.updateGuildRole(this, data);
|
||||
edit(data, reason) {
|
||||
return this.client.rest.methods.updateGuildRole(this, data, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a new name for the role.
|
||||
* @param {string} name The new name of the role
|
||||
* @param {string} [reason] Reason for changing the role's name
|
||||
* @returns {Promise<Role>}
|
||||
* @example
|
||||
* // Set the name of the role
|
||||
* role.setName('new role')
|
||||
* .then(r => console.log(`Edited name of role ${r}`))
|
||||
* .catch(console.error);
|
||||
* .then(r => console.log(`Edited name of role ${r}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
setName(name) {
|
||||
return this.edit({ name });
|
||||
setName(name, reason) {
|
||||
return this.edit({ name }, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a new color for the role.
|
||||
* @param {ColorResolvable} color The color of the role
|
||||
* @param {string} [reason] Reason for changing the role's color
|
||||
* @returns {Promise<Role>}
|
||||
* @example
|
||||
* // Set the color of a role
|
||||
* role.setColor('#FF0000')
|
||||
* .then(r => console.log(`Set color of role ${r}`))
|
||||
* .catch(console.error);
|
||||
* .then(r => console.log(`Set color of role ${r}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
setColor(color) {
|
||||
return this.edit({ color });
|
||||
setColor(color, reason) {
|
||||
return this.edit({ color }, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not the role should be hoisted.
|
||||
* @param {boolean} hoist Whether or not to hoist the role
|
||||
* @param {string} [reason] Reason for setting whether or not the role should be hoisted
|
||||
* @returns {Promise<Role>}
|
||||
* @example
|
||||
* // Set the hoist of the role
|
||||
* role.setHoist(true)
|
||||
* .then(r => console.log(`Role hoisted: ${r.hoist}`))
|
||||
* .catch(console.error);
|
||||
* .then(r => console.log(`Role hoisted: ${r.hoist}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
setHoist(hoist) {
|
||||
return this.edit({ hoist });
|
||||
setHoist(hoist, reason) {
|
||||
return this.edit({ hoist }, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -263,8 +267,8 @@ class Role {
|
||||
* @example
|
||||
* // Set the position of the role
|
||||
* role.setPosition(1)
|
||||
* .then(r => console.log(`Role position: ${r.position}`))
|
||||
* .catch(console.error);
|
||||
* .then(r => console.log(`Role position: ${r.position}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
setPosition(position, relative) {
|
||||
return this.guild.setRolePosition(this, position, relative).then(() => this);
|
||||
@@ -273,42 +277,45 @@ class Role {
|
||||
/**
|
||||
* Set the permissions of the role.
|
||||
* @param {string[]} permissions The permissions of the role
|
||||
* @param {string} [reason] Reason for changing the role's permissions
|
||||
* @returns {Promise<Role>}
|
||||
* @example
|
||||
* // Set the permissions of the role
|
||||
* role.setPermissions(['KICK_MEMBERS', 'BAN_MEMBERS'])
|
||||
* .then(r => console.log(`Role updated ${r}`))
|
||||
* .catch(console.error);
|
||||
* .then(r => console.log(`Role updated ${r}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
setPermissions(permissions) {
|
||||
return this.edit({ permissions });
|
||||
setPermissions(permissions, reason) {
|
||||
return this.edit({ permissions }, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether this role is mentionable.
|
||||
* @param {boolean} mentionable Whether this role should be mentionable
|
||||
* @param {string} [reason] Reason for setting whether or not this role should be mentionable
|
||||
* @returns {Promise<Role>}
|
||||
* @example
|
||||
* // Make the role mentionable
|
||||
* role.setMentionable(true)
|
||||
* .then(r => console.log(`Role updated ${r}`))
|
||||
* .catch(console.error);
|
||||
* .then(r => console.log(`Role updated ${r}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
setMentionable(mentionable) {
|
||||
return this.edit({ mentionable });
|
||||
setMentionable(mentionable, reason) {
|
||||
return this.edit({ mentionable }, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the role.
|
||||
* @param {string} [reason] Reason for deleting the role
|
||||
* @returns {Promise<Role>}
|
||||
* @example
|
||||
* // Delete a role
|
||||
* role.delete()
|
||||
* .then(r => console.log(`Deleted role ${r}`))
|
||||
* .catch(console.error);
|
||||
* .then(r => console.log(`Deleted role ${r}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
delete() {
|
||||
return this.client.rest.methods.deleteGuildRole(this);
|
||||
delete(reason) {
|
||||
return this.client.rest.methods.deleteGuildRole(this, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,6 +24,13 @@ class TextChannel extends GuildChannel {
|
||||
*/
|
||||
this.topic = data.topic;
|
||||
|
||||
/**
|
||||
* If the Discord considers this channel NSFW
|
||||
* @type {boolean}
|
||||
* @readonly
|
||||
*/
|
||||
this.nsfw = Boolean(data.nsfw);
|
||||
|
||||
this.lastMessageID = data.last_message_id;
|
||||
}
|
||||
|
||||
@@ -42,15 +49,6 @@ class TextChannel extends GuildChannel {
|
||||
return members;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the Discord considers this channel NSFW
|
||||
* @type {boolean}
|
||||
* @readonly
|
||||
*/
|
||||
get nsfw() {
|
||||
return /^nsfw(-|$)/.test(this.name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all webhooks for the channel.
|
||||
* @returns {Promise<Collection<Snowflake, Webhook>>}
|
||||
@@ -62,23 +60,22 @@ class TextChannel extends GuildChannel {
|
||||
/**
|
||||
* Create a webhook for the channel.
|
||||
* @param {string} name The name of the webhook
|
||||
* @param {BufferResolvable|Base64Resolvable} avatar The avatar for the webhook
|
||||
* @param {BufferResolvable|Base64Resolvable} [avatar] The avatar for the webhook
|
||||
* @param {string} [reason] Reason for creating this webhook
|
||||
* @returns {Promise<Webhook>} webhook The created webhook
|
||||
* @example
|
||||
* channel.createWebhook('Snek', 'http://snek.s3.amazonaws.com/topSnek.png')
|
||||
* .then(webhook => console.log(`Created webhook ${webhook}`))
|
||||
* .catch(console.error)
|
||||
* channel.createWebhook('Snek', 'https://i.imgur.com/mI8XcpG.jpg')
|
||||
* .then(webhook => console.log(`Created webhook ${webhook}`))
|
||||
* .catch(console.error)
|
||||
*/
|
||||
createWebhook(name, avatar) {
|
||||
return new Promise(resolve => {
|
||||
if (typeof avatar === 'string' && avatar.startsWith('data:')) {
|
||||
resolve(this.client.rest.methods.createWebhook(this, name, avatar));
|
||||
} else {
|
||||
this.client.resolver.resolveBuffer(avatar).then(data =>
|
||||
resolve(this.client.rest.methods.createWebhook(this, name, data))
|
||||
);
|
||||
}
|
||||
});
|
||||
createWebhook(name, avatar, reason) {
|
||||
if (typeof avatar === 'string' && avatar.startsWith('data:')) {
|
||||
return this.client.rest.methods.createWebhook(this, name, avatar, reason);
|
||||
} else {
|
||||
return this.client.resolver.resolveImage(avatar).then(data =>
|
||||
this.client.rest.methods.createWebhook(this, name, data, reason)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// These are here only for documentation purposes - they are implemented by TextBasedChannel
|
||||
|
||||
@@ -10,9 +10,9 @@ const Snowflake = require('../util/Snowflake');
|
||||
class User {
|
||||
constructor(client, data) {
|
||||
/**
|
||||
* The client that created the instance of the the user
|
||||
* The client that created the instance of the user
|
||||
* @name User#client
|
||||
* @type {}
|
||||
* @type {Client}
|
||||
* @readonly
|
||||
*/
|
||||
Object.defineProperty(this, 'client', { value: client });
|
||||
|
||||
@@ -13,7 +13,7 @@ class UserProfile {
|
||||
this.user = user;
|
||||
|
||||
/**
|
||||
* The client that created the instance of the the UserProfile.
|
||||
* The client that created the instance of the UserProfile
|
||||
* @name UserProfile#client
|
||||
* @type {Client}
|
||||
* @readonly
|
||||
|
||||
@@ -25,7 +25,7 @@ class VoiceChannel extends GuildChannel {
|
||||
* The bitrate of this voice channel
|
||||
* @type {number}
|
||||
*/
|
||||
this.bitrate = data.bitrate;
|
||||
this.bitrate = data.bitrate * 0.001;
|
||||
|
||||
/**
|
||||
* The maximum amount of users allowed in this channel - 0 means unlimited.
|
||||
@@ -76,31 +76,34 @@ class VoiceChannel extends GuildChannel {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the bitrate of the channel.
|
||||
* Sets the bitrate of the channel (in kbps).
|
||||
* @param {number} bitrate The new bitrate
|
||||
* @param {string} [reason] Reason for changing the channel's bitrate
|
||||
* @returns {Promise<VoiceChannel>}
|
||||
* @example
|
||||
* // Set the bitrate of a voice channel
|
||||
* voiceChannel.setBitrate(48000)
|
||||
* .then(vc => console.log(`Set bitrate to ${vc.bitrate} for ${vc.name}`))
|
||||
* .catch(console.error);
|
||||
* voiceChannel.setBitrate(48)
|
||||
* .then(vc => console.log(`Set bitrate to ${vc.bitrate}kbps for ${vc.name}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
setBitrate(bitrate) {
|
||||
return this.edit({ bitrate });
|
||||
setBitrate(bitrate, reason) {
|
||||
bitrate *= 1000;
|
||||
return this.edit({ bitrate }, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the user limit of the channel.
|
||||
* @param {number} userLimit The new user limit
|
||||
* @param {string} [reason] Reason for changing the user limit
|
||||
* @returns {Promise<VoiceChannel>}
|
||||
* @example
|
||||
* // Set the user limit of a voice channel
|
||||
* voiceChannel.setUserLimit(42)
|
||||
* .then(vc => console.log(`Set user limit to ${vc.userLimit} for ${vc.name}`))
|
||||
* .catch(console.error);
|
||||
* .then(vc => console.log(`Set user limit to ${vc.userLimit} for ${vc.name}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
setUserLimit(userLimit) {
|
||||
return this.edit({ userLimit });
|
||||
setUserLimit(userLimit, reason) {
|
||||
return this.edit({ userLimit }, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -109,8 +112,8 @@ class VoiceChannel extends GuildChannel {
|
||||
* @example
|
||||
* // Join a voice channel
|
||||
* voiceChannel.join()
|
||||
* .then(connection => console.log('Connected!'))
|
||||
* .catch(console.error);
|
||||
* .then(connection => console.log('Connected!'))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
join() {
|
||||
if (this.client.browser) return Promise.reject(new Error('Voice connections are not available in browsers.'));
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
const path = require('path');
|
||||
const Util = require('../util/Util');
|
||||
const Attachment = require('./Attachment');
|
||||
const RichEmbed = require('./RichEmbed');
|
||||
|
||||
/**
|
||||
* Represents a webhook.
|
||||
@@ -36,7 +39,7 @@ class Webhook {
|
||||
|
||||
/**
|
||||
* The avatar for the webhook
|
||||
* @type {string}
|
||||
* @type {?string}
|
||||
*/
|
||||
this.avatar = data.avatar;
|
||||
|
||||
@@ -76,11 +79,12 @@ class Webhook {
|
||||
* @property {string} [avatarURL] Avatar URL override for the message
|
||||
* @property {boolean} [tts=false] Whether or not the message should be spoken aloud
|
||||
* @property {string} [nonce=''] The nonce for the message
|
||||
* @property {Object[]} [embeds] An array of embeds for the message
|
||||
* @property {Array<RichEmbed|Object>} [embeds] An array of embeds for the message
|
||||
* (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
|
||||
* should be replaced with plain-text
|
||||
* @property {FileOptions|string} [file] A file to send with the message
|
||||
* @property {FileOptions|BufferResolvable|Attachment} [file] A file to send with the message **(deprecated)**
|
||||
* @property {FileOptions[]|BufferResolvable[]|Attachment[]} [files] Files to send with the message
|
||||
* @property {string|boolean} [code] Language for optional codeblock formatting to apply
|
||||
* @property {boolean|SplitOptions} [split=false] Whether or not the message should be split into multiple messages if
|
||||
* it exceeds the character limit. If an object is provided, these are the options for splitting the message.
|
||||
@@ -89,39 +93,87 @@ class Webhook {
|
||||
/**
|
||||
* Send a message with this webhook.
|
||||
* @param {StringResolvable} content The content to send
|
||||
* @param {WebhookMessageOptions} [options={}] The options to provide
|
||||
* @param {WebhookMessageOptions|Attachment|RichEmbed} [options] The options to provide
|
||||
* can also be just a RichEmbed or Attachment
|
||||
* @returns {Promise<Message|Message[]>}
|
||||
* @example
|
||||
* // Send a message
|
||||
* webhook.send('hello!')
|
||||
* .then(message => console.log(`Sent message: ${message.content}`))
|
||||
* .catch(console.error);
|
||||
* .then(message => console.log(`Sent message: ${message.content}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
send(content, options) {
|
||||
send(content, options) { // eslint-disable-line complexity
|
||||
if (!options && typeof content === 'object' && !(content instanceof Array)) {
|
||||
options = content;
|
||||
content = '';
|
||||
} else if (!options) {
|
||||
options = {};
|
||||
}
|
||||
if (options.file) {
|
||||
if (typeof options.file === 'string') options.file = { attachment: options.file };
|
||||
if (!options.file.name) {
|
||||
if (typeof options.file.attachment === 'string') {
|
||||
options.file.name = path.basename(options.file.attachment);
|
||||
} else if (options.file.attachment && options.file.attachment.path) {
|
||||
options.file.name = path.basename(options.file.attachment.path);
|
||||
} else {
|
||||
options.file.name = 'file.jpg';
|
||||
|
||||
if (options instanceof Attachment) options = { files: [options] };
|
||||
if (options instanceof RichEmbed) options = { embeds: [options] };
|
||||
|
||||
if (content) {
|
||||
content = this.client.resolver.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```';
|
||||
}
|
||||
}
|
||||
return this.client.resolver.resolveBuffer(options.file.attachment).then(file =>
|
||||
this.client.rest.methods.sendWebhookMessage(this, content, options, {
|
||||
file,
|
||||
name: options.file.name,
|
||||
})
|
||||
);
|
||||
if (disableEveryone || (typeof disableEveryone === 'undefined' && this.client.options.disableEveryone)) {
|
||||
content = content.replace(/@(everyone|here)/g, '@\u200b$1');
|
||||
}
|
||||
|
||||
if (split) content = Util.splitMessage(content, split);
|
||||
}
|
||||
|
||||
if (options.file) {
|
||||
if (options.files) options.files.push(options.file);
|
||||
else options.files = [options.file];
|
||||
}
|
||||
|
||||
if (options.embeds) {
|
||||
const files = [];
|
||||
for (const embed of options.embeds) {
|
||||
if (embed.file) files.push(embed.file);
|
||||
}
|
||||
if (options.files) options.files.push(...files);
|
||||
else options.files = files;
|
||||
}
|
||||
|
||||
if (options.files) {
|
||||
for (let i = 0; i < options.files.length; i++) {
|
||||
let file = options.files[i];
|
||||
if (typeof file === 'string' || Buffer.isBuffer(file)) file = { attachment: file };
|
||||
if (!file.name) {
|
||||
if (typeof file.attachment === 'string') {
|
||||
file.name = path.basename(file.attachment);
|
||||
} else if (file.attachment && file.attachment.path) {
|
||||
file.name = path.basename(file.attachment.path);
|
||||
} else if (file instanceof Attachment) {
|
||||
file = { attachment: file.file, name: path.basename(file.file) || 'file.jpg' };
|
||||
} else {
|
||||
file.name = 'file.jpg';
|
||||
}
|
||||
} else if (file instanceof Attachment) {
|
||||
file = file.file;
|
||||
}
|
||||
options.files[i] = file;
|
||||
}
|
||||
|
||||
return Promise.all(options.files.map(file =>
|
||||
this.client.resolver.resolveFile(file.attachment).then(resource => {
|
||||
file.file = resource;
|
||||
return file;
|
||||
})
|
||||
)).then(files => this.client.rest.methods.sendWebhookMessage(this, content, options, files));
|
||||
}
|
||||
|
||||
return this.client.rest.methods.sendWebhookMessage(this, content, options);
|
||||
}
|
||||
|
||||
@@ -130,6 +182,7 @@ class Webhook {
|
||||
* @param {StringResolvable} content The content to send
|
||||
* @param {WebhookMessageOptions} [options={}] The options to provide
|
||||
* @returns {Promise<Message|Message[]>}
|
||||
* @deprecated
|
||||
* @example
|
||||
* // Send a message
|
||||
* webhook.sendMessage('hello!')
|
||||
@@ -147,6 +200,7 @@ class Webhook {
|
||||
* @param {StringResolvable} [content] Text message to send with the attachment
|
||||
* @param {WebhookMessageOptions} [options] The options to provide
|
||||
* @returns {Promise<Message>}
|
||||
* @deprecated
|
||||
*/
|
||||
sendFile(attachment, name, content, options = {}) {
|
||||
return this.send(content, Object.assign(options, { file: { attachment, name } }));
|
||||
@@ -158,6 +212,7 @@ class Webhook {
|
||||
* @param {StringResolvable} content Content of the code block
|
||||
* @param {WebhookMessageOptions} options The options to provide
|
||||
* @returns {Promise<Message|Message[]>}
|
||||
* @deprecated
|
||||
*/
|
||||
sendCode(lang, content, options = {}) {
|
||||
return this.send(content, Object.assign(options, { code: lang }));
|
||||
@@ -187,28 +242,25 @@ class Webhook {
|
||||
/**
|
||||
* Edit the webhook.
|
||||
* @param {string} name The new name for the webhook
|
||||
* @param {BufferResolvable} avatar The new avatar for the webhook
|
||||
* @param {BufferResolvable} [avatar] The new avatar for the webhook
|
||||
* @returns {Promise<Webhook>}
|
||||
*/
|
||||
edit(name = this.name, avatar) {
|
||||
if (avatar) {
|
||||
return this.client.resolver.resolveBuffer(avatar).then(file => {
|
||||
const dataURI = this.client.resolver.resolveBase64(file);
|
||||
return this.client.rest.methods.editWebhook(this, name, dataURI);
|
||||
});
|
||||
return this.client.resolver.resolveImage(avatar).then(data =>
|
||||
this.client.rest.methods.editWebhook(this, name, data)
|
||||
);
|
||||
}
|
||||
return this.client.rest.methods.editWebhook(this, name).then(data => {
|
||||
this.setup(data);
|
||||
return this;
|
||||
});
|
||||
return this.client.rest.methods.editWebhook(this, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the webhook.
|
||||
* @param {string} [reason] Reason for deleting the webhook
|
||||
* @returns {Promise}
|
||||
*/
|
||||
delete() {
|
||||
return this.client.rest.methods.deleteWebhook(this);
|
||||
delete(reason) {
|
||||
return this.client.rest.methods.deleteWebhook(this, reason);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,8 @@ const EventEmitter = require('events').EventEmitter;
|
||||
* Filter to be applied to the collector.
|
||||
* @typedef {Function} CollectorFilter
|
||||
* @param {...*} args Any arguments received by the listener
|
||||
* @returns {boolean} To collect or not collect
|
||||
* @param {Collection} collection The items collected by this collector
|
||||
* @returns {boolean}
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -78,7 +79,7 @@ class Collector extends EventEmitter {
|
||||
*/
|
||||
_handle(...args) {
|
||||
const collect = this.handle(...args);
|
||||
if (!collect || !this.filter(...args)) return;
|
||||
if (!collect || !this.filter(...args, this.collected)) return;
|
||||
|
||||
this.collected.set(collect.key, collect.value);
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ const path = require('path');
|
||||
const Message = require('../Message');
|
||||
const MessageCollector = require('../MessageCollector');
|
||||
const Collection = require('../../util/Collection');
|
||||
const Attachment = require('../../structures/Attachment');
|
||||
const RichEmbed = require('../../structures/RichEmbed');
|
||||
const util = require('util');
|
||||
|
||||
/**
|
||||
@@ -38,8 +40,8 @@ class TextBasedChannel {
|
||||
* (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
|
||||
* should be replaced with plain-text
|
||||
* @property {FileOptions|string} [file] A file to send with the message **(deprecated)**
|
||||
* @property {FileOptions[]|string[]} [files] Files to send with the message
|
||||
* @property {FileOptions|BufferResolvable|Attachment} [file] A file to send with the message **(deprecated)**
|
||||
* @property {FileOptions[]|BufferResolvable[]|Attachment[]} [files] Files to send with the message
|
||||
* @property {string|boolean} [code] Language for optional codeblock formatting to apply
|
||||
* @property {boolean|SplitOptions} [split=false] Whether or not the message should be split into multiple messages if
|
||||
* it exceeds the character limit. If an object is provided, these are the options for splitting the message
|
||||
@@ -64,13 +66,14 @@ class TextBasedChannel {
|
||||
/**
|
||||
* Send a message to this channel.
|
||||
* @param {StringResolvable} [content] Text for the message
|
||||
* @param {MessageOptions} [options={}] Options for the message
|
||||
* @param {MessageOptions|Attachment|RichEmbed} [options] Options for the message,
|
||||
* can also be just a RichEmbed or Attachment
|
||||
* @returns {Promise<Message|Message[]>}
|
||||
* @example
|
||||
* // Send a message
|
||||
* channel.send('hello!')
|
||||
* .then(message => console.log(`Sent message: ${message.content}`))
|
||||
* .catch(console.error);
|
||||
* .then(message => console.log(`Sent message: ${message.content}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
send(content, options) {
|
||||
if (!options && typeof content === 'object' && !(content instanceof Array)) {
|
||||
@@ -80,7 +83,13 @@ class TextBasedChannel {
|
||||
options = {};
|
||||
}
|
||||
|
||||
if (options.embed && options.embed.file) options.file = options.embed.file;
|
||||
if (options instanceof Attachment) options = { files: [options.file] };
|
||||
if (options instanceof RichEmbed) options = { embed: options };
|
||||
|
||||
if (options.embed && options.embed.file) {
|
||||
if (options.files) options.files.push(options.embed.file);
|
||||
else options.files = [options.embed.file];
|
||||
}
|
||||
|
||||
if (options.file) {
|
||||
if (options.files) options.files.push(options.file);
|
||||
@@ -88,24 +97,28 @@ class TextBasedChannel {
|
||||
}
|
||||
|
||||
if (options.files) {
|
||||
for (const i in options.files) {
|
||||
for (let i = 0; i < options.files.length; i++) {
|
||||
let file = options.files[i];
|
||||
if (typeof file === 'string') file = { attachment: file };
|
||||
if (typeof file === 'string' || Buffer.isBuffer(file)) file = { attachment: file };
|
||||
if (!file.name) {
|
||||
if (typeof file.attachment === 'string') {
|
||||
file.name = path.basename(file.attachment);
|
||||
} else if (file.attachment && file.attachment.path) {
|
||||
file.name = path.basename(file.attachment.path);
|
||||
} else if (file instanceof Attachment) {
|
||||
file = { attachment: file.file, name: path.basename(file.file) || 'file.jpg' };
|
||||
} else {
|
||||
file.name = 'file.jpg';
|
||||
}
|
||||
} else if (file instanceof Attachment) {
|
||||
file = file.file;
|
||||
}
|
||||
options.files[i] = file;
|
||||
}
|
||||
|
||||
return Promise.all(options.files.map(file =>
|
||||
this.client.resolver.resolveBuffer(file.attachment).then(buffer => {
|
||||
file.file = buffer;
|
||||
this.client.resolver.resolveFile(file.attachment).then(resource => {
|
||||
file.file = resource;
|
||||
return file;
|
||||
})
|
||||
)).then(files => this.client.rest.methods.sendMessage(this, content, options, files));
|
||||
@@ -158,8 +171,8 @@ class TextBasedChannel {
|
||||
* @example
|
||||
* // Get messages
|
||||
* channel.fetchMessages({limit: 10})
|
||||
* .then(messages => console.log(`Received ${messages.size} messages`))
|
||||
* .catch(console.error);
|
||||
* .then(messages => console.log(`Received ${messages.size} messages`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
fetchMessages(options = {}) {
|
||||
return this.client.rest.methods.getChannelMessages(this, options).then(data => {
|
||||
@@ -214,15 +227,21 @@ class TextBasedChannel {
|
||||
* @property {Date} [before] Date to find messages before
|
||||
* @property {Date} [after] Date to find messages before
|
||||
* @property {Date} [during] Date to find messages during (range of date to date + 24 hours)
|
||||
* @property {boolean} [nsfw=false] Include results from NSFW channels
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} MessageSearchResult
|
||||
* @property {number} totalResults Total result count
|
||||
* @property {Message[][]} messages Array of message results
|
||||
* The message which has triggered the result will have the `hit` property set to `true`
|
||||
*/
|
||||
|
||||
/**
|
||||
* Performs a search within the channel.
|
||||
* <warn>This is only available when using a user account.</warn>
|
||||
* @param {MessageSearchOptions} [options={}] Options to pass to the search
|
||||
* @returns {Promise<Array<Message[]>>}
|
||||
* An array containing arrays of messages. Each inner array is a search context cluster
|
||||
* The message which has triggered the result will have the `hit` property set to `true`
|
||||
* @returns {Promise<MessageSearchResult>}
|
||||
* @example
|
||||
* channel.search({
|
||||
* content: 'discord.js',
|
||||
@@ -319,11 +338,11 @@ class TextBasedChannel {
|
||||
* @returns {MessageCollector}
|
||||
* @example
|
||||
* // Create a message collector
|
||||
* const collector = channel.createCollector(
|
||||
* m => m.content.includes('discord'),
|
||||
* { time: 15000 }
|
||||
* const collector = channel.createMessageCollector(
|
||||
* m => m.content.includes('discord'),
|
||||
* { time: 15000 }
|
||||
* );
|
||||
* collector.on('message', m => console.log(`Collected ${m.content}`));
|
||||
* collector.on('collect', m => console.log(`Collected ${m.content}`));
|
||||
* collector.on('end', collected => console.log(`Collected ${collected.size} items`));
|
||||
*/
|
||||
createMessageCollector(filter, options = {}) {
|
||||
@@ -347,8 +366,8 @@ class TextBasedChannel {
|
||||
* const filter = m => m.content.startsWith('!vote');
|
||||
* // Errors: ['time'] treats ending because of the time limit as an error
|
||||
* channel.awaitMessages(filter, { max: 4, time: 60000, errors: ['time'] })
|
||||
* .then(collected => console.log(collected.size))
|
||||
* .catch(collected => console.log(`After a minute, only ${collected.size} out of 4 voted.`));
|
||||
* .then(collected => console.log(collected.size))
|
||||
* .catch(collected => console.log(`After a minute, only ${collected.size} out of 4 voted.`));
|
||||
*/
|
||||
awaitMessages(filter, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
@@ -59,59 +59,99 @@ class Collection extends Map {
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the first item in this collection.
|
||||
* @returns {*}
|
||||
* Obtains the first value(s) in this collection.
|
||||
* @param {number} [count] Number of values to obtain from the beginning
|
||||
* @returns {*|Array<*>} The single value if `count` is undefined, or an array of values of `count` length
|
||||
*/
|
||||
first() {
|
||||
return this.values().next().value;
|
||||
first(count) {
|
||||
if (count === undefined) return this.values().next().value;
|
||||
if (typeof count !== 'number') throw new TypeError('The count must be a number.');
|
||||
if (!Number.isInteger(count) || count < 1) throw new RangeError('The count must be an integer greater than 0.');
|
||||
count = Math.min(this.size, count);
|
||||
const arr = new Array(count);
|
||||
const iter = this.values();
|
||||
for (let i = 0; i < count; i++) arr[i] = iter.next().value;
|
||||
return arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the first key in this collection.
|
||||
* @returns {*}
|
||||
* Obtains the first key(s) in this collection.
|
||||
* @param {number} [count] Number of keys to obtain from the beginning
|
||||
* @returns {*|Array<*>} The single key if `count` is undefined, or an array of keys of `count` length
|
||||
*/
|
||||
firstKey() {
|
||||
return this.keys().next().value;
|
||||
firstKey(count) {
|
||||
if (count === undefined) return this.keys().next().value;
|
||||
if (typeof count !== 'number') throw new TypeError('The count must be a number.');
|
||||
if (!Number.isInteger(count) || count < 1) throw new RangeError('The count must be an integer greater than 0.');
|
||||
count = Math.min(this.size, count);
|
||||
const arr = new Array(count);
|
||||
const iter = this.iter();
|
||||
for (let i = 0; i < count; i++) arr[i] = iter.next().value;
|
||||
return arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the last item in this collection. This relies on the `array()` method, and thus the caching mechanism
|
||||
* applies here as well.
|
||||
* @returns {*}
|
||||
* Obtains the last value(s) in this collection. This relies on {@link Collection#array}, and thus the caching
|
||||
* mechanism applies here as well.
|
||||
* @param {number} [count] Number of values to obtain from the end
|
||||
* @returns {*|Array<*>} The single value if `count` is undefined, or an array of values of `count` length
|
||||
*/
|
||||
last() {
|
||||
last(count) {
|
||||
const arr = this.array();
|
||||
return arr[arr.length - 1];
|
||||
if (count === undefined) return arr[arr.length - 1];
|
||||
if (typeof count !== 'number') throw new TypeError('The count must be a number.');
|
||||
if (!Number.isInteger(count) || count < 1) throw new RangeError('The count must be an integer greater than 0.');
|
||||
return arr.slice(-count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the last key in this collection. This relies on the `keyArray()` method, and thus the caching mechanism
|
||||
* applies here as well.
|
||||
* @returns {*}
|
||||
* Obtains the last key(s) in this collection. This relies on {@link Collection#keyArray}, and thus the caching
|
||||
* mechanism applies here as well.
|
||||
* @param {number} [count] Number of keys to obtain from the end
|
||||
* @returns {*|Array<*>} The single key if `count` is undefined, or an array of keys of `count` length
|
||||
*/
|
||||
lastKey() {
|
||||
lastKey(count) {
|
||||
const arr = this.keyArray();
|
||||
return arr[arr.length - 1];
|
||||
if (count === undefined) return arr[arr.length - 1];
|
||||
if (typeof count !== 'number') throw new TypeError('The count must be a number.');
|
||||
if (!Number.isInteger(count) || count < 1) throw new RangeError('The count must be an integer greater than 0.');
|
||||
return arr.slice(-count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains a random item from this collection. This relies on the `array()` method, and thus the caching mechanism
|
||||
* applies here as well.
|
||||
* @returns {*}
|
||||
* Obtains random value(s) from this collection. This relies on {@link Collection#array}, and thus the caching
|
||||
* mechanism applies here as well.
|
||||
* @param {number} [count] Number of values to obtain randomly
|
||||
* @returns {*|Array<*>} The single value if `count` is undefined, or an array of values of `count` length
|
||||
*/
|
||||
random() {
|
||||
const arr = this.array();
|
||||
return arr[Math.floor(Math.random() * arr.length)];
|
||||
random(count) {
|
||||
let arr = this.array();
|
||||
if (count === undefined) return arr[Math.floor(Math.random() * arr.length)];
|
||||
if (typeof count !== 'number') throw new TypeError('The count must be a number.');
|
||||
if (!Number.isInteger(count) || count < 1) throw new RangeError('The count must be an integer greater than 0.');
|
||||
if (arr.length === 0) return [];
|
||||
const rand = new Array(count);
|
||||
arr = arr.slice();
|
||||
for (let i = 0; i < count; i++) rand[i] = arr.splice(Math.floor(Math.random() * arr.length), 1)[0];
|
||||
return rand;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains a random key from this collection. This relies on the `keyArray()` method, and thus the caching mechanism
|
||||
* applies here as well.
|
||||
* @returns {*}
|
||||
* Obtains random key(s) from this collection. This relies on {@link Collection#keyArray}, and thus the caching
|
||||
* mechanism applies here as well.
|
||||
* @param {number} [count] Number of keys to obtain randomly
|
||||
* @returns {*|Array<*>} The single key if `count` is undefined, or an array of keys of `count` length
|
||||
*/
|
||||
randomKey() {
|
||||
const arr = this.keyArray();
|
||||
return arr[Math.floor(Math.random() * arr.length)];
|
||||
randomKey(count) {
|
||||
let arr = this.keyArray();
|
||||
if (count === undefined) return arr[Math.floor(Math.random() * arr.length)];
|
||||
if (typeof count !== 'number') throw new TypeError('The count must be a number.');
|
||||
if (!Number.isInteger(count) || count < 1) throw new RangeError('The count must be an integer greater than 0.');
|
||||
if (arr.length === 0) return [];
|
||||
const rand = new Array(count);
|
||||
arr = arr.slice();
|
||||
for (let i = 0; i < count; i++) rand[i] = arr.splice(Math.floor(Math.random() * arr.length), 1)[0];
|
||||
return rand;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -29,6 +29,7 @@ exports.Package = require('../../package.json');
|
||||
* 100% certain you don't need, as many are important, but not obviously so. The safest one to disable with the
|
||||
* most impact is typically `TYPING_START`.
|
||||
* @property {WebsocketOptions} [ws] Options for the WebSocket
|
||||
* @property {HTTPOptions} [http] HTTP options
|
||||
*/
|
||||
exports.DefaultOptions = {
|
||||
apiRequestMethod: 'sequential',
|
||||
@@ -63,6 +64,15 @@ exports.DefaultOptions = {
|
||||
},
|
||||
version: 6,
|
||||
},
|
||||
|
||||
/**
|
||||
* HTTP options
|
||||
* @typedef {Object} HTTPOptions
|
||||
* @property {number} [version=7] API version to use
|
||||
* @property {string} [api='https://discordapp.com/api'] Base url of the API
|
||||
* @property {string} [cdn='https://cdn.discordapp.com'] Base url of the CDN
|
||||
* @property {string} [invite='https://discord.gg'] Base url of invites
|
||||
*/
|
||||
http: {
|
||||
version: 7,
|
||||
host: 'https://discordapp.com',
|
||||
@@ -102,7 +112,10 @@ const Endpoints = exports.Endpoints = {
|
||||
relationships: `${base}/relationships`,
|
||||
settings: `${base}/settings`,
|
||||
Relationship: uID => `${base}/relationships/${uID}`,
|
||||
Guild: guildID => `${base}/guilds/${guildID}`,
|
||||
Guild: guildID => ({
|
||||
toString: () => `${base}/guilds/${guildID}`,
|
||||
settings: `${base}/guilds/${guildID}/settings`,
|
||||
}),
|
||||
Note: id => `${base}/notes/${id}`,
|
||||
Mentions: (limit, roles, everyone, guildID) =>
|
||||
`${base}/mentions?limit=${limit}&roles=${roles}&everyone=${everyone}${guildID ? `&guild_id=${guildID}` : ''}`,
|
||||
@@ -133,7 +146,7 @@ const Endpoints = exports.Endpoints = {
|
||||
ack: `${base}/ack`,
|
||||
settings: `${base}/settings`,
|
||||
auditLogs: `${base}/audit-logs`,
|
||||
Emoji: emojiID => Endpoints.CDN(root).Emoji(emojiID),
|
||||
Emoji: emojiID => `${base}/emojis/${emojiID}`,
|
||||
Icon: (root, hash) => Endpoints.CDN(root).Icon(guildID, hash),
|
||||
Splash: (root, hash) => Endpoints.CDN(root).Splash(guildID, hash),
|
||||
Role: roleID => `${base}/roles/${roleID}`,
|
||||
@@ -164,6 +177,7 @@ const Endpoints = exports.Endpoints = {
|
||||
webhooks: `${base}/webhooks`,
|
||||
search: `${base}/messages/search`,
|
||||
pins: `${base}/pins`,
|
||||
Icon: (root, hash) => Endpoints.CDN(root).GDMIcon(channelID, hash),
|
||||
Pin: messageID => `${base}/pins/${messageID}`,
|
||||
Recipient: recipientID => `${base}/recipients/${recipientID}`,
|
||||
Message: messageID => {
|
||||
@@ -192,6 +206,7 @@ const Endpoints = exports.Endpoints = {
|
||||
Asset: name => `${root}/assets/${name}`,
|
||||
Avatar: (userID, hash) => `${root}/avatars/${userID}/${hash}.${hash.startsWith('a_') ? 'gif' : 'png'}?size=2048`,
|
||||
Icon: (guildID, hash) => `${root}/icons/${guildID}/${hash}.jpg`,
|
||||
GDMIcon: (channelID, hash) => `${root}/channel-icons/${channelID}/${hash}.jpg?size=2048`,
|
||||
Splash: (guildID, hash) => `${root}/splashes/${guildID}/${hash}.jpg`,
|
||||
};
|
||||
},
|
||||
@@ -200,7 +215,8 @@ const Endpoints = exports.Endpoints = {
|
||||
const base = `/oauth2/applications/${appID}`;
|
||||
return {
|
||||
toString: () => base,
|
||||
reset: `${base}/reset`,
|
||||
resetSecret: `${base}/reset`,
|
||||
resetToken: `${base}/bot/reset`,
|
||||
};
|
||||
},
|
||||
App: appID => `/oauth2/authorize?client_id=${appID}`,
|
||||
@@ -212,7 +228,7 @@ const Endpoints = exports.Endpoints = {
|
||||
toString: () => '/gateway',
|
||||
bot: '/gateway/bot',
|
||||
},
|
||||
Invite: inviteID => `/invite/${inviteID}`,
|
||||
Invite: inviteID => `/invite/${inviteID}?with_counts=true`,
|
||||
inviteLink: id => `https://discord.gg/${id}`,
|
||||
Webhook: (webhookID, token) => `/webhooks/${webhookID}${token ? `/${token}` : ''}`,
|
||||
};
|
||||
@@ -220,12 +236,12 @@ const Endpoints = exports.Endpoints = {
|
||||
|
||||
/**
|
||||
* The current status of the client. Here are the available statuses:
|
||||
* - READY
|
||||
* - CONNECTING
|
||||
* - RECONNECTING
|
||||
* - IDLE
|
||||
* - NEARLY
|
||||
* - DISCONNECTED
|
||||
* * READY
|
||||
* * CONNECTING
|
||||
* * RECONNECTING
|
||||
* * IDLE
|
||||
* * NEARLY
|
||||
* * DISCONNECTED
|
||||
* @typedef {number} Status
|
||||
*/
|
||||
exports.Status = {
|
||||
@@ -239,11 +255,11 @@ exports.Status = {
|
||||
|
||||
/**
|
||||
* The current status of a voice connection. Here are the available statuses:
|
||||
* - CONNECTED
|
||||
* - CONNECTING
|
||||
* - AUTHENTICATING
|
||||
* - RECONNECTING
|
||||
* - DISCONNECTED
|
||||
* * CONNECTED
|
||||
* * CONNECTING
|
||||
* * AUTHENTICATING
|
||||
* * RECONNECTING
|
||||
* * DISCONNECTED
|
||||
* @typedef {number} VoiceStatus
|
||||
*/
|
||||
exports.VoiceStatus = {
|
||||
@@ -287,6 +303,7 @@ exports.VoiceOPCodes = {
|
||||
|
||||
exports.Events = {
|
||||
READY: 'ready',
|
||||
RESUME: 'resume',
|
||||
GUILD_CREATE: 'guildCreate',
|
||||
GUILD_DELETE: 'guildDelete',
|
||||
GUILD_UPDATE: 'guildUpdate',
|
||||
@@ -320,6 +337,7 @@ exports.Events = {
|
||||
USER_UPDATE: 'userUpdate',
|
||||
USER_NOTE_UPDATE: 'userNoteUpdate',
|
||||
USER_SETTINGS_UPDATE: 'clientUserSettingsUpdate',
|
||||
USER_GUILD_SETTINGS_UPDATE: 'clientUserGuildSettingsUpdate',
|
||||
PRESENCE_UPDATE: 'presenceUpdate',
|
||||
VOICE_STATE_UPDATE: 'voiceStateUpdate',
|
||||
TYPING_START: 'typingStart',
|
||||
@@ -333,41 +351,41 @@ exports.Events = {
|
||||
|
||||
/**
|
||||
* The type of a websocket message event, e.g. `MESSAGE_CREATE`. Here are the available events:
|
||||
* - READY
|
||||
* - RESUMED
|
||||
* - GUILD_SYNC
|
||||
* - GUILD_CREATE
|
||||
* - GUILD_DELETE
|
||||
* - GUILD_UPDATE
|
||||
* - GUILD_MEMBER_ADD
|
||||
* - GUILD_MEMBER_REMOVE
|
||||
* - GUILD_MEMBER_UPDATE
|
||||
* - GUILD_MEMBERS_CHUNK
|
||||
* - GUILD_ROLE_CREATE
|
||||
* - GUILD_ROLE_DELETE
|
||||
* - GUILD_ROLE_UPDATE
|
||||
* - GUILD_BAN_ADD
|
||||
* - GUILD_BAN_REMOVE
|
||||
* - CHANNEL_CREATE
|
||||
* - CHANNEL_DELETE
|
||||
* - CHANNEL_UPDATE
|
||||
* - CHANNEL_PINS_UPDATE
|
||||
* - MESSAGE_CREATE
|
||||
* - MESSAGE_DELETE
|
||||
* - MESSAGE_UPDATE
|
||||
* - MESSAGE_DELETE_BULK
|
||||
* - MESSAGE_REACTION_ADD
|
||||
* - MESSAGE_REACTION_REMOVE
|
||||
* - MESSAGE_REACTION_REMOVE_ALL
|
||||
* - USER_UPDATE
|
||||
* - USER_NOTE_UPDATE
|
||||
* - USER_SETTINGS_UPDATE
|
||||
* - PRESENCE_UPDATE
|
||||
* - VOICE_STATE_UPDATE
|
||||
* - TYPING_START
|
||||
* - VOICE_SERVER_UPDATE
|
||||
* - RELATIONSHIP_ADD
|
||||
* - RELATIONSHIP_REMOVE
|
||||
* * READY
|
||||
* * RESUMED
|
||||
* * GUILD_SYNC
|
||||
* * GUILD_CREATE
|
||||
* * GUILD_DELETE
|
||||
* * GUILD_UPDATE
|
||||
* * GUILD_MEMBER_ADD
|
||||
* * GUILD_MEMBER_REMOVE
|
||||
* * GUILD_MEMBER_UPDATE
|
||||
* * GUILD_MEMBERS_CHUNK
|
||||
* * GUILD_ROLE_CREATE
|
||||
* * GUILD_ROLE_DELETE
|
||||
* * GUILD_ROLE_UPDATE
|
||||
* * GUILD_BAN_ADD
|
||||
* * GUILD_BAN_REMOVE
|
||||
* * CHANNEL_CREATE
|
||||
* * CHANNEL_DELETE
|
||||
* * CHANNEL_UPDATE
|
||||
* * CHANNEL_PINS_UPDATE
|
||||
* * MESSAGE_CREATE
|
||||
* * MESSAGE_DELETE
|
||||
* * MESSAGE_UPDATE
|
||||
* * MESSAGE_DELETE_BULK
|
||||
* * MESSAGE_REACTION_ADD
|
||||
* * MESSAGE_REACTION_REMOVE
|
||||
* * MESSAGE_REACTION_REMOVE_ALL
|
||||
* * USER_UPDATE
|
||||
* * USER_NOTE_UPDATE
|
||||
* * USER_SETTINGS_UPDATE
|
||||
* * PRESENCE_UPDATE
|
||||
* * VOICE_STATE_UPDATE
|
||||
* * TYPING_START
|
||||
* * VOICE_SERVER_UPDATE
|
||||
* * RELATIONSHIP_ADD
|
||||
* * RELATIONSHIP_REMOVE
|
||||
* @typedef {string} WSEventType
|
||||
*/
|
||||
exports.WSEvents = {
|
||||
@@ -401,6 +419,7 @@ exports.WSEvents = {
|
||||
USER_UPDATE: 'USER_UPDATE',
|
||||
USER_NOTE_UPDATE: 'USER_NOTE_UPDATE',
|
||||
USER_SETTINGS_UPDATE: 'USER_SETTINGS_UPDATE',
|
||||
USER_GUILD_SETTINGS_UPDATE: 'USER_GUILD_SETTINGS_UPDATE',
|
||||
PRESENCE_UPDATE: 'PRESENCE_UPDATE',
|
||||
VOICE_STATE_UPDATE: 'VOICE_STATE_UPDATE',
|
||||
TYPING_START: 'TYPING_START',
|
||||
@@ -409,6 +428,18 @@ exports.WSEvents = {
|
||||
RELATIONSHIP_REMOVE: 'RELATIONSHIP_REMOVE',
|
||||
};
|
||||
|
||||
/**
|
||||
* The type of a message, e.g. `DEFAULT`. Here are the available types:
|
||||
* * DEFAULT
|
||||
* * RECIPIENT_ADD
|
||||
* * RECIPIENT_REMOVE
|
||||
* * CALL
|
||||
* * CHANNEL_NAME_CHANGE
|
||||
* * CHANNEL_ICON_CHANGE
|
||||
* * PINS_ADD
|
||||
* * GUILD_MEMBER_JOIN
|
||||
* @typedef {string} MessageType
|
||||
*/
|
||||
exports.MessageTypes = [
|
||||
'DEFAULT',
|
||||
'RECIPIENT_ADD',
|
||||
@@ -420,6 +451,21 @@ exports.MessageTypes = [
|
||||
'GUILD_MEMBER_JOIN',
|
||||
];
|
||||
|
||||
/**
|
||||
* The type of a message notification setting. Here are the available types:
|
||||
* * EVERYTHING
|
||||
* * MENTIONS
|
||||
* * NOTHING
|
||||
* * INHERIT (only for GuildChannel)
|
||||
* @typedef {string} MessageNotificationType
|
||||
*/
|
||||
exports.MessageNotificationTypes = [
|
||||
'EVERYTHING',
|
||||
'MENTIONS',
|
||||
'NOTHING',
|
||||
'INHERIT',
|
||||
];
|
||||
|
||||
exports.DefaultAvatars = {
|
||||
BLURPLE: '6debd47ed13483642cf09e832ed0bc1b',
|
||||
GREY: '322c936a8c8be1b803cd94861bdfa868',
|
||||
@@ -543,8 +589,8 @@ exports.UserSettingsMap = {
|
||||
|
||||
explicit_content_filter: function explicitContentFilter(type) { // eslint-disable-line func-name-matching
|
||||
/**
|
||||
* Safe direct messaging; force people's messages with images to be scanned before they are sent to you
|
||||
* one of `DISABLED`, `NON_FRIENDS`, `FRIENDS_AND_NON_FRIENDS`
|
||||
* Safe direct messaging; force people's messages with images to be scanned before they are sent to you.
|
||||
* One of `DISABLED`, `NON_FRIENDS`, `FRIENDS_AND_NON_FRIENDS`
|
||||
* @name ClientUserSettings#explicitContentFilter
|
||||
* @type {string}
|
||||
*/
|
||||
@@ -567,6 +613,58 @@ exports.UserSettingsMap = {
|
||||
},
|
||||
};
|
||||
|
||||
exports.UserGuildSettingsMap = {
|
||||
message_notifications: function messageNotifications(type) { // eslint-disable-line func-name-matching
|
||||
/**
|
||||
* The type of message that should notify you
|
||||
* @name ClientUserGuildSettings#messageNotifications
|
||||
* @type {MessageNotificationType}
|
||||
*/
|
||||
return exports.MessageNotificationTypes[type];
|
||||
},
|
||||
/**
|
||||
* Whether to receive mobile push notifications
|
||||
* @name ClientUserGuildSettings#mobilePush
|
||||
* @type {boolean}
|
||||
*/
|
||||
mobile_push: 'mobilePush',
|
||||
/**
|
||||
* Whether the guild is muted
|
||||
* @name ClientUserGuildSettings#muted
|
||||
* @type {boolean}
|
||||
*/
|
||||
muted: 'muted',
|
||||
/**
|
||||
* Whether to suppress everyone mention
|
||||
* @name ClientUserGuildSettings#suppressEveryone
|
||||
* @type {boolean}
|
||||
*/
|
||||
suppress_everyone: 'suppressEveryone',
|
||||
/**
|
||||
* A collection containing all the channel overrides
|
||||
* @name ClientUserGuildSettings#channelOverrides
|
||||
* @type {Collection<ClientUserChannelOverride>}
|
||||
*/
|
||||
channel_overrides: 'channelOverrides',
|
||||
};
|
||||
|
||||
exports.UserChannelOverrideMap = {
|
||||
message_notifications: function messageNotifications(type) { // eslint-disable-line func-name-matching
|
||||
/**
|
||||
* The type of message that should notify you
|
||||
* @name ClientUserChannelOverride#messageNotifications
|
||||
* @type {MessageNotificationType}
|
||||
*/
|
||||
return exports.MessageNotificationTypes[type];
|
||||
},
|
||||
/**
|
||||
* Whether the channel is muted
|
||||
* @name ClientUserChannelOverride#muted
|
||||
* @type {boolean}
|
||||
*/
|
||||
muted: 'muted',
|
||||
};
|
||||
|
||||
exports.Colors = {
|
||||
DEFAULT: 0x000000,
|
||||
AQUA: 0x1ABC9C,
|
||||
@@ -594,3 +692,96 @@ exports.Colors = {
|
||||
DARK_BUT_NOT_BLACK: 0x2C2F33,
|
||||
NOT_QUITE_BLACK: 0x23272A,
|
||||
};
|
||||
|
||||
/**
|
||||
* An error encountered while performing an API request. Here are the potential errors:
|
||||
* * UNKNOWN_ACCOUNT
|
||||
* * UNKNOWN_APPLICATION
|
||||
* * UNKNOWN_CHANNEL
|
||||
* * UNKNOWN_GUILD
|
||||
* * UNKNOWN_INTEGRATION
|
||||
* * UNKNOWN_INVITE
|
||||
* * UNKNOWN_MEMBER
|
||||
* * UNKNOWN_MESSAGE
|
||||
* * UNKNOWN_OVERWRITE
|
||||
* * UNKNOWN_PROVIDER
|
||||
* * UNKNOWN_ROLE
|
||||
* * UNKNOWN_TOKEN
|
||||
* * UNKNOWN_USER
|
||||
* * UNKNOWN_EMOJI
|
||||
* * BOT_PROHIBITED_ENDPOINT
|
||||
* * BOT_ONLY_ENDPOINT
|
||||
* * MAXIMUM_GUILDS
|
||||
* * MAXIMUM_FRIENDS
|
||||
* * MAXIMUM_PINS
|
||||
* * MAXIMUM_ROLES
|
||||
* * MAXIMUM_REACTIONS
|
||||
* * UNAUTHORIZED
|
||||
* * MISSING_ACCESS
|
||||
* * INVALID_ACCOUNT_TYPE
|
||||
* * CANNOT_EXECUTE_ON_DM
|
||||
* * EMBED_DISABLED
|
||||
* * CANNOT_EDIT_MESSAGE_BY_OTHER
|
||||
* * CANNOT_SEND_EMPTY_MESSAGE
|
||||
* * CANNOT_MESSAGE_USER
|
||||
* * CANNOT_SEND_MESSAGES_IN_VOICE_CHANNEL
|
||||
* * CHANNEL_VERIFICATION_LEVEL_TOO_HIGH
|
||||
* * OAUTH2_APPLICATION_BOT_ABSENT
|
||||
* * MAXIMUM_OAUTH2_APPLICATIONS
|
||||
* * INVALID_OAUTH_STATE
|
||||
* * MISSING_PERMISSIONS
|
||||
* * INVALID_AUTHENTICATION_TOKEN
|
||||
* * NOTE_TOO_LONG
|
||||
* * INVALID_BULK_DELETE_QUANTITY
|
||||
* * CANNOT_PIN_MESSAGE_IN_OTHER_CHANNEL
|
||||
* * CANNOT_EXECUTE_ON_SYSTEM_MESSAGE
|
||||
* * BULK_DELETE_MESSAGE_TOO_OLD
|
||||
* * INVITE_ACCEPTED_TO_GUILD_NOT_CONTANING_BOT
|
||||
* * REACTION_BLOCKED
|
||||
* @typedef {string} APIError
|
||||
*/
|
||||
exports.APIErrors = {
|
||||
UNKNOWN_ACCOUNT: 10001,
|
||||
UNKNOWN_APPLICATION: 10002,
|
||||
UNKNOWN_CHANNEL: 10003,
|
||||
UNKNOWN_GUILD: 10004,
|
||||
UNKNOWN_INTEGRATION: 10005,
|
||||
UNKNOWN_INVITE: 10006,
|
||||
UNKNOWN_MEMBER: 10007,
|
||||
UNKNOWN_MESSAGE: 10008,
|
||||
UNKNOWN_OVERWRITE: 10009,
|
||||
UNKNOWN_PROVIDER: 10010,
|
||||
UNKNOWN_ROLE: 10011,
|
||||
UNKNOWN_TOKEN: 10012,
|
||||
UNKNOWN_USER: 10013,
|
||||
UNKNOWN_EMOJI: 10014,
|
||||
BOT_PROHIBITED_ENDPOINT: 20001,
|
||||
BOT_ONLY_ENDPOINT: 20002,
|
||||
MAXIMUM_GUILDS: 30001,
|
||||
MAXIMUM_FRIENDS: 30002,
|
||||
MAXIMUM_PINS: 30003,
|
||||
MAXIMUM_ROLES: 30005,
|
||||
MAXIMUM_REACTIONS: 30010,
|
||||
UNAUTHORIZED: 40001,
|
||||
MISSING_ACCESS: 50001,
|
||||
INVALID_ACCOUNT_TYPE: 50002,
|
||||
CANNOT_EXECUTE_ON_DM: 50003,
|
||||
EMBED_DISABLED: 50004,
|
||||
CANNOT_EDIT_MESSAGE_BY_OTHER: 50005,
|
||||
CANNOT_SEND_EMPTY_MESSAGE: 50006,
|
||||
CANNOT_MESSAGE_USER: 50007,
|
||||
CANNOT_SEND_MESSAGES_IN_VOICE_CHANNEL: 50008,
|
||||
CHANNEL_VERIFICATION_LEVEL_TOO_HIGH: 50009,
|
||||
OAUTH2_APPLICATION_BOT_ABSENT: 50010,
|
||||
MAXIMUM_OAUTH2_APPLICATIONS: 50011,
|
||||
INVALID_OAUTH_STATE: 50012,
|
||||
MISSING_PERMISSIONS: 50013,
|
||||
INVALID_AUTHENTICATION_TOKEN: 50014,
|
||||
NOTE_TOO_LONG: 50015,
|
||||
INVALID_BULK_DELETE_QUANTITY: 50016,
|
||||
CANNOT_PIN_MESSAGE_IN_OTHER_CHANNEL: 50019,
|
||||
CANNOT_EXECUTE_ON_SYSTEM_MESSAGE: 50021,
|
||||
BULK_DELETE_MESSAGE_TOO_OLD: 50034,
|
||||
INVITE_ACCEPTED_TO_GUILD_NOT_CONTANING_BOT: 50036,
|
||||
REACTION_BLOCKED: 90001,
|
||||
};
|
||||
|
||||
@@ -103,7 +103,7 @@ class Permissions {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an object mapping permission name (like `READ_MESSAGES`) to a {@link boolean} indicating whether the
|
||||
* Gets an object mapping permission name (like `VIEW_CHANNEL`) to a {@link boolean} indicating whether the
|
||||
* permission is available.
|
||||
* @param {boolean} [checkAdmin=true] Whether to allow the administrator permission to override
|
||||
* @returns {Object}
|
||||
@@ -152,8 +152,8 @@ class Permissions {
|
||||
|
||||
/**
|
||||
* Data that can be resolved to give a permission number. This can be:
|
||||
* - A string (see {@link Permissions.flags})
|
||||
* - A permission number
|
||||
* * A string (see {@link Permissions.FLAGS})
|
||||
* * A permission number
|
||||
* @typedef {string|number} PermissionResolvable
|
||||
*/
|
||||
|
||||
@@ -180,7 +180,8 @@ class Permissions {
|
||||
* - `MANAGE_GUILD` (edit the guild information, region, etc.)
|
||||
* - `ADD_REACTIONS` (add new reactions to messages)
|
||||
* - `VIEW_AUDIT_LOG`
|
||||
* - `READ_MESSAGES`
|
||||
* - `VIEW_CHANNEL`
|
||||
* - `READ_MESSAGES` **(deprecated)**
|
||||
* - `SEND_MESSAGES`
|
||||
* - `SEND_TTS_MESSAGES`
|
||||
* - `MANAGE_MESSAGES` (delete messages and reactions)
|
||||
@@ -215,6 +216,7 @@ Permissions.FLAGS = {
|
||||
ADD_REACTIONS: 1 << 6,
|
||||
VIEW_AUDIT_LOG: 1 << 7,
|
||||
|
||||
VIEW_CHANNEL: 1 << 10,
|
||||
READ_MESSAGES: 1 << 10,
|
||||
SEND_MESSAGES: 1 << 11,
|
||||
SEND_TTS_MESSAGES: 1 << 12,
|
||||
@@ -268,8 +270,8 @@ Permissions.prototype.missingPermissions = util.deprecate(Permissions.prototype.
|
||||
'EvaluatedPermissions#missingPermissions is deprecated, use Permissions#missing instead');
|
||||
Object.defineProperty(Permissions.prototype, 'member', {
|
||||
get: util
|
||||
.deprecate(Object.getOwnPropertyDescriptor(Permissions.prototype, 'member').get,
|
||||
'EvaluatedPermissions#member is deprecated'),
|
||||
.deprecate(Object.getOwnPropertyDescriptor(Permissions.prototype, 'member').get,
|
||||
'EvaluatedPermissions#member is deprecated'),
|
||||
});
|
||||
|
||||
module.exports = Permissions;
|
||||
|
||||
@@ -66,9 +66,9 @@ class Util {
|
||||
|
||||
/**
|
||||
* Parses emoji info out of a string. The string must be one of:
|
||||
* - A UTF-8 emoji (no ID)
|
||||
* - A URL-encoded UTF-8 emoji (no ID)
|
||||
* - A Discord custom emoji (`<:name:id>`)
|
||||
* * A UTF-8 emoji (no ID)
|
||||
* * A URL-encoded UTF-8 emoji (no ID)
|
||||
* * A Discord custom emoji (`<:name:id>`)
|
||||
* @param {string} text Emoji string to parse
|
||||
* @returns {Object} Object with `name` and `id` properties
|
||||
* @private
|
||||
|
||||
156
test/voice.js
156
test/voice.js
@@ -1,78 +1,78 @@
|
||||
/* eslint no-console: 0 */
|
||||
'use strict';
|
||||
|
||||
const Discord = require('../');
|
||||
const ytdl = require('ytdl-core');
|
||||
|
||||
const client = new Discord.Client({ fetchAllMembers: false, apiRequestMethod: 'sequential' });
|
||||
|
||||
const auth = require('./auth.json');
|
||||
|
||||
client.login(auth.token).then(() => console.log('logged')).catch(console.error);
|
||||
|
||||
const connections = new Map();
|
||||
|
||||
let broadcast;
|
||||
|
||||
client.on('message', m => {
|
||||
if (!m.guild) return;
|
||||
if (m.content.startsWith('/join')) {
|
||||
const channel = m.guild.channels.get(m.content.split(' ')[1]) || m.member.voiceChannel;
|
||||
if (channel && channel.type === 'voice') {
|
||||
channel.join().then(conn => {
|
||||
conn.player.on('error', (...e) => console.log('player', ...e));
|
||||
if (!connections.has(m.guild.id)) connections.set(m.guild.id, { conn, queue: [] });
|
||||
m.reply('ok!');
|
||||
});
|
||||
} else {
|
||||
m.reply('Specify a voice channel!');
|
||||
}
|
||||
} else if (m.content.startsWith('/play')) {
|
||||
if (connections.has(m.guild.id)) {
|
||||
const connData = connections.get(m.guild.id);
|
||||
const queue = connData.queue;
|
||||
const url = m.content.split(' ').slice(1).join(' ')
|
||||
.replace(/</g, '')
|
||||
.replace(/>/g, '');
|
||||
queue.push({ url, m });
|
||||
if (queue.length > 1) {
|
||||
m.reply(`OK, that's going to play after ${queue.length - 1} songs`);
|
||||
return;
|
||||
}
|
||||
doQueue(connData);
|
||||
}
|
||||
} else if (m.content.startsWith('/skip')) {
|
||||
if (connections.has(m.guild.id)) {
|
||||
const connData = connections.get(m.guild.id);
|
||||
if (connData.dispatcher) {
|
||||
connData.dispatcher.end();
|
||||
}
|
||||
}
|
||||
} else if (m.content.startsWith('#eval') && m.author.id === '66564597481480192') {
|
||||
try {
|
||||
const com = eval(m.content.split(' ').slice(1).join(' '));
|
||||
m.channel.sendMessage(`\`\`\`\n${com}\`\`\``);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
m.channel.sendMessage(`\`\`\`\n${e}\`\`\``);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function doQueue(connData) {
|
||||
const conn = connData.conn;
|
||||
const queue = connData.queue;
|
||||
const item = queue[0];
|
||||
if (!item) return;
|
||||
const stream = ytdl(item.url, { filter: 'audioonly' }, { passes: 3 });
|
||||
const dispatcher = conn.playStream(stream);
|
||||
stream.on('info', info => {
|
||||
item.m.reply(`OK, playing **${info.title}**`);
|
||||
});
|
||||
dispatcher.on('end', () => {
|
||||
queue.shift();
|
||||
doQueue(connData);
|
||||
});
|
||||
dispatcher.on('error', (...e) => console.log('dispatcher', ...e));
|
||||
connData.dispatcher = dispatcher;
|
||||
}
|
||||
/* eslint no-console: 0 */
|
||||
'use strict';
|
||||
|
||||
const Discord = require('../');
|
||||
const ytdl = require('ytdl-core');
|
||||
|
||||
const client = new Discord.Client({ fetchAllMembers: false, apiRequestMethod: 'sequential' });
|
||||
|
||||
const auth = require('./auth.json');
|
||||
|
||||
client.login(auth.token).then(() => console.log('logged')).catch(console.error);
|
||||
|
||||
const connections = new Map();
|
||||
|
||||
let broadcast;
|
||||
|
||||
client.on('message', m => {
|
||||
if (!m.guild) return;
|
||||
if (m.content.startsWith('/join')) {
|
||||
const channel = m.guild.channels.get(m.content.split(' ')[1]) || m.member.voiceChannel;
|
||||
if (channel && channel.type === 'voice') {
|
||||
channel.join().then(conn => {
|
||||
conn.player.on('error', (...e) => console.log('player', ...e));
|
||||
if (!connections.has(m.guild.id)) connections.set(m.guild.id, { conn, queue: [] });
|
||||
m.reply('ok!');
|
||||
});
|
||||
} else {
|
||||
m.reply('Specify a voice channel!');
|
||||
}
|
||||
} else if (m.content.startsWith('/play')) {
|
||||
if (connections.has(m.guild.id)) {
|
||||
const connData = connections.get(m.guild.id);
|
||||
const queue = connData.queue;
|
||||
const url = m.content.split(' ').slice(1).join(' ')
|
||||
.replace(/</g, '')
|
||||
.replace(/>/g, '');
|
||||
queue.push({ url, m });
|
||||
if (queue.length > 1) {
|
||||
m.reply(`OK, that's going to play after ${queue.length - 1} songs`);
|
||||
return;
|
||||
}
|
||||
doQueue(connData);
|
||||
}
|
||||
} else if (m.content.startsWith('/skip')) {
|
||||
if (connections.has(m.guild.id)) {
|
||||
const connData = connections.get(m.guild.id);
|
||||
if (connData.dispatcher) {
|
||||
connData.dispatcher.end();
|
||||
}
|
||||
}
|
||||
} else if (m.content.startsWith('#eval') && m.author.id === '66564597481480192') {
|
||||
try {
|
||||
const com = eval(m.content.split(' ').slice(1).join(' '));
|
||||
m.channel.sendMessage(`\`\`\`\n${com}\`\`\``);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
m.channel.sendMessage(`\`\`\`\n${e}\`\`\``);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function doQueue(connData) {
|
||||
const conn = connData.conn;
|
||||
const queue = connData.queue;
|
||||
const item = queue[0];
|
||||
if (!item) return;
|
||||
const stream = ytdl(item.url, { filter: 'audioonly' }, { passes: 3 });
|
||||
const dispatcher = conn.playStream(stream);
|
||||
stream.on('info', info => {
|
||||
item.m.reply(`OK, playing **${info.title}**`);
|
||||
});
|
||||
dispatcher.on('end', () => {
|
||||
queue.shift();
|
||||
doQueue(connData);
|
||||
});
|
||||
dispatcher.on('error', (...e) => console.log('dispatcher', ...e));
|
||||
connData.dispatcher = dispatcher;
|
||||
}
|
||||
|
||||
2
typings
2
typings
Submodule typings updated: b500eb2331...697fc933de
@@ -5,19 +5,26 @@
|
||||
|
||||
const webpack = require('webpack');
|
||||
const createVariants = require('parallel-webpack').createVariants;
|
||||
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
|
||||
const version = require('./package.json').version;
|
||||
|
||||
const createConfig = options => {
|
||||
const plugins = [
|
||||
new webpack.DefinePlugin({ 'global.GENTLY': false }),
|
||||
new webpack.optimize.ModuleConcatenationPlugin(),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
__DISCORD_WEBPACK__: '"true"',
|
||||
},
|
||||
}),
|
||||
];
|
||||
|
||||
if (options.minify) plugins.push(new webpack.optimize.UglifyJsPlugin({ minimize: true }));
|
||||
if (options.minify) plugins.push(new UglifyJSPlugin({ uglifyOptions: { output: { comments: false } } }));
|
||||
|
||||
const filename = `./webpack/discord${process.env.VERSIONED === 'false' ? '' : '.' + version}${options.minify ? '.min' : ''}.js`; // eslint-disable-line
|
||||
|
||||
return {
|
||||
entry: './src/index.js',
|
||||
entry: './browser.js',
|
||||
output: {
|
||||
path: __dirname,
|
||||
filename,
|
||||
|
||||
Reference in New Issue
Block a user