mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-09 16:13:31 +01:00
Merge branch 'indev'
This commit is contained in:
@@ -14,17 +14,20 @@
|
||||
"valid-jsdoc": ["error", {
|
||||
"requireReturn": false,
|
||||
"requireReturnDescription": false,
|
||||
"prefer": {
|
||||
"return": "returns",
|
||||
"arg": "param"
|
||||
},
|
||||
"preferType": {
|
||||
"String": "string",
|
||||
"Number": "number",
|
||||
"Boolean": "boolean",
|
||||
"Function": "function",
|
||||
"object": "Object",
|
||||
"function": "Function",
|
||||
"array": "Array",
|
||||
"date": "Date",
|
||||
"error": "Error"
|
||||
},
|
||||
"prefer": {
|
||||
"return": "returns"
|
||||
"error": "Error",
|
||||
"null": "void"
|
||||
}
|
||||
}],
|
||||
|
||||
@@ -80,6 +83,7 @@
|
||||
"consistent-this": ["error", "$this"],
|
||||
"eol-last": "error",
|
||||
"func-names": "error",
|
||||
"func-name-matching": "error",
|
||||
"func-style": ["error", "declaration", { "allowArrowFunctions": true }],
|
||||
"indent": ["error", 2, { "SwitchCase": 1 }],
|
||||
"key-spacing": "error",
|
||||
@@ -122,6 +126,7 @@
|
||||
"no-useless-computed-key": "error",
|
||||
"no-useless-constructor": "error",
|
||||
"prefer-arrow-callback": "error",
|
||||
"prefer-numeric-literals": "error",
|
||||
"prefer-rest-params": "error",
|
||||
"prefer-spread": "error",
|
||||
"prefer-template": "error",
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -8,10 +8,14 @@ logs/
|
||||
|
||||
# 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/
|
||||
|
||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "typings"]
|
||||
path = typings
|
||||
url = https://github.com/zajrik/discord.js-typings
|
||||
11
.tern-project
Normal file
11
.tern-project
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"ecmaVersion": 6,
|
||||
"libs": [],
|
||||
"plugins": {
|
||||
"node": {
|
||||
"dontLoad": "node_modules/**",
|
||||
"load": "",
|
||||
"modules": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,12 @@ cache:
|
||||
directories:
|
||||
- node_modules
|
||||
install: npm install
|
||||
script: bash ./docs/deploy/deploy.sh
|
||||
script:
|
||||
- npm run test
|
||||
- bash ./deploy/deploy.sh
|
||||
env:
|
||||
global:
|
||||
- ENCRYPTION_LABEL: "af862fa96d3e"
|
||||
- COMMIT_AUTHOR_EMAIL: "amishshah.2k@gmail.com"
|
||||
|
||||
dist: trusty
|
||||
sudo: false
|
||||
|
||||
@@ -6,7 +6,7 @@ is a great boon to your coding process.
|
||||
## Setup
|
||||
To get ready to work on the codebase, please do the following:
|
||||
|
||||
1. Fork & clone the repository
|
||||
1. Fork & clone the repository, and make sure you're on the **indev** branch
|
||||
2. Run `npm install`
|
||||
3. If you're working on voice, also run `npm install node-opus` or `npm install opusscript`
|
||||
4. Code your heart out!
|
||||
|
||||
42
README.md
42
README.md
@@ -1,7 +1,9 @@
|
||||
<div align="center">
|
||||
<br />
|
||||
<p>
|
||||
<a href="https://discord.js.org"><img src="https://i.imgur.com/StEGtEh.png" width="546" alt="discord.js" /></a>
|
||||
<a href="https://discord.js.org"><img src="https://discord.js.org/static/logo.svg" width="546" alt="discord.js" /></a>
|
||||
</p>
|
||||
<br />
|
||||
<p>
|
||||
<a href="https://discord.gg/bRCvFy9"><img src="https://discordapp.com/api/guilds/222078108977594368/embed.png" alt="Discord server" /></a>
|
||||
<a href="https://www.npmjs.com/package/discord.js"><img src="https://img.shields.io/npm/v/discord.js.svg?maxAge=3600" alt="NPM version" /></a>
|
||||
@@ -15,21 +17,31 @@
|
||||
</div>
|
||||
|
||||
## About
|
||||
discord.js is a powerful node.js module that allows you to interact with the [Discord API](https://discordapp.com/developers/docs/intro) very easily.
|
||||
It takes a much more object-oriented approach than most other JS Discord libraries, making your bot's code significantly tidier and easier to comprehend.
|
||||
Usability and performance are key focuses of discord.js. It also has nearly 100% coverage of the Discord API.
|
||||
discord.js is a powerful node.js module that allows you to interact with the
|
||||
[Discord API](https://discordapp.com/developers/docs/intro) very easily.
|
||||
|
||||
- Object-oriented
|
||||
- Predictable abstractions
|
||||
- Performant
|
||||
- Nearly 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 - all peer dependencies are 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`
|
||||
With voice support ([opusscript](https://www.npmjs.com/package/opusscript)): `npm install discord.js opusscript --save`
|
||||
|
||||
### Audio engines
|
||||
The preferred audio engine is node-opus, as it performs significantly better than opusscript. When both are available, discord.js will automatically choose node-opus.
|
||||
Using opusscript is only recommended for development on Windows, since getting node-opus to build there can be a bit of a challenge.
|
||||
Using opusscript is only recommended for development environments where node-opus is tough to get working.
|
||||
For production bots, using node-opus should be considered a necessity, especially if they're going to be running on multiple servers.
|
||||
|
||||
### Optional packages
|
||||
- [uws](https://www.npmjs.com/package/uws) for much a much faster WebSocket connection (`npm install uws --save`)
|
||||
- [erlpack](https://github.com/hammerandchisel/erlpack) for significantly faster WebSocket data (de)serialisation (`npm install hammerandchisel/erlpack --save`)
|
||||
|
||||
## Example Usage
|
||||
```js
|
||||
const Discord = require('discord.js');
|
||||
@@ -50,21 +62,27 @@ client.login('your token');
|
||||
|
||||
A bot template using discord.js can be generated using [generator-discordbot](https://www.npmjs.com/package/generator-discordbot).
|
||||
|
||||
## Web distributions
|
||||
Web builds of discord.js that are fully capable of running in browsers are available [here](https://github.com/hydrabolt/discord.js/tree/webpack).
|
||||
These are built using [Webpack 2](https://webpack.js.org/). The API is identical, but rather than using `require('discord.js')`,
|
||||
the entire `Discord` object is available as a global (on the `window` object).
|
||||
The ShardingManager and any voice-related functionality is unavailable in these builds.
|
||||
|
||||
## Links
|
||||
* [Website](http://discord.js.org/)
|
||||
* [Website](https://discord.js.org/)
|
||||
* [Discord.js server](https://discord.gg/bRCvFy9)
|
||||
* [Discord API server](https://discord.gg/rV4BwdK)
|
||||
* [Documentation](http://discord.js.org/#!/docs)
|
||||
* [Documentation](https://discord.js.org/#/docs)
|
||||
* [Legacy (v8) documentation](http://discordjs.readthedocs.io/en/8.2.0/docs_client.html)
|
||||
* [Examples](https://github.com/hydrabolt/discord.js/tree/master/docs/custom/examples)
|
||||
* [Examples](https://github.com/hydrabolt/discord.js/tree/master/docs/examples)
|
||||
* [GitHub](https://github.com/hydrabolt/discord.js)
|
||||
* [NPM](https://www.npmjs.com/package/discord.js)
|
||||
* [Related libraries](https://discordapi.com/unofficial/libs.html)
|
||||
* [Related libraries](https://discordapi.com/unofficial/libs.html) (see also [discord-rpc](https://www.npmjs.com/package/discord-rpc))
|
||||
|
||||
## Contributing
|
||||
Before creating an issue, please ensure that it hasn't already been reported/suggested, and double-check the
|
||||
[documentation](http://discord.js.org/#!/docs).
|
||||
See [the contributing guide](CONTRIBUTING.md) if you'd like to submit a PR.
|
||||
[documentation](https://discord.js.org/#/docs).
|
||||
See [the contribution guide](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,11 @@
|
||||
set -e
|
||||
|
||||
function build {
|
||||
node docs/generator/generator.js
|
||||
# Build docs
|
||||
npm run docs
|
||||
|
||||
# Build the webpack
|
||||
VERSIONED=false npm run web-dist
|
||||
}
|
||||
|
||||
# Ignore Travis checking PRs
|
||||
@@ -15,7 +19,9 @@ if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then
|
||||
fi
|
||||
|
||||
# Ignore travis checking other branches irrelevant to users
|
||||
if [ "$TRAVIS_BRANCH" != "master" -a "$TRAVIS_BRANCH" != "indev" ]; then
|
||||
# Apparently Travis considers tag builds as separate branches so we need to
|
||||
# check for that separately
|
||||
if [ "$TRAVIS_BRANCH" != "master" -a "$TRAVIS_BRANCH" != "indev" -a "$TRAVIS_BRANCH" != "$TRAVIS_TAG" ]; then
|
||||
echo "deploy.sh: Ignoring push to another branch than master/indev"
|
||||
build
|
||||
exit 0
|
||||
@@ -29,19 +35,27 @@ if [ -n "$TRAVIS_TAG" ]; then
|
||||
SOURCE=$TRAVIS_TAG
|
||||
fi
|
||||
|
||||
# Initialise some useful variables
|
||||
REPO=`git config remote.origin.url`
|
||||
SSH_REPO=${REPO/https:\/\/github.com\//git@github.com:}
|
||||
SHA=`git rev-parse --verify HEAD`
|
||||
|
||||
TARGET_BRANCH="docs"
|
||||
# Decrypt and add the ssh key
|
||||
ENCRYPTED_KEY_VAR="encrypted_${ENCRYPTION_LABEL}_key"
|
||||
ENCRYPTED_IV_VAR="encrypted_${ENCRYPTION_LABEL}_iv"
|
||||
ENCRYPTED_KEY=${!ENCRYPTED_KEY_VAR}
|
||||
ENCRYPTED_IV=${!ENCRYPTED_IV_VAR}
|
||||
openssl aes-256-cbc -K $ENCRYPTED_KEY -iv $ENCRYPTED_IV -in deploy/deploy_key.enc -out deploy_key -d
|
||||
chmod 600 deploy_key
|
||||
eval `ssh-agent -s`
|
||||
ssh-add deploy_key
|
||||
|
||||
# Build everything
|
||||
build
|
||||
|
||||
# Checkout the repo in the target branch so we can build docs and push to it
|
||||
TARGET_BRANCH="docs"
|
||||
git clone $REPO out -b $TARGET_BRANCH
|
||||
cd out
|
||||
cd ..
|
||||
|
||||
# Build the docs
|
||||
build
|
||||
|
||||
# Move the generated JSON file to the newly-checked-out repo, to be committed
|
||||
# and pushed
|
||||
@@ -49,20 +63,28 @@ mv docs/docs.json out/$SOURCE.json
|
||||
|
||||
# Commit and push
|
||||
cd out
|
||||
git add .
|
||||
git config user.name "Travis CI"
|
||||
git config user.email "$COMMIT_AUTHOR_EMAIL"
|
||||
|
||||
git add .
|
||||
git commit -m "Docs build: ${SHA}"
|
||||
|
||||
ENCRYPTED_KEY_VAR="encrypted_${ENCRYPTION_LABEL}_key"
|
||||
ENCRYPTED_IV_VAR="encrypted_${ENCRYPTION_LABEL}_iv"
|
||||
ENCRYPTED_KEY=${!ENCRYPTED_KEY_VAR}
|
||||
ENCRYPTED_IV=${!ENCRYPTED_IV_VAR}
|
||||
openssl aes-256-cbc -K $ENCRYPTED_KEY -iv $ENCRYPTED_IV -in ../docs/deploy/deploy_key.enc -out deploy_key -d
|
||||
chmod 600 deploy_key
|
||||
eval `ssh-agent -s`
|
||||
ssh-add deploy_key
|
||||
|
||||
# Now that we're all set up, we can push.
|
||||
git commit -m "Docs build: ${SHA}" || true
|
||||
git push $SSH_REPO $TARGET_BRANCH
|
||||
|
||||
# Clean up...
|
||||
cd ..
|
||||
rm -rf out
|
||||
|
||||
# ...then do the same once more for the webpack
|
||||
TARGET_BRANCH="webpack"
|
||||
git clone $REPO out -b $TARGET_BRANCH
|
||||
|
||||
# Move the generated webpack over
|
||||
mv webpack/discord.js out/discord.$SOURCE.js
|
||||
mv webpack/discord.min.js out/discord.$SOURCE.min.js
|
||||
|
||||
# Commit and push
|
||||
cd out
|
||||
git add .
|
||||
git config user.name "Travis CI"
|
||||
git config user.email "$COMMIT_AUTHOR_EMAIL"
|
||||
git commit -m "Webpack build: ${SHA}" || true
|
||||
git push $SSH_REPO $TARGET_BRANCH
|
||||
@@ -1,2 +1 @@
|
||||
# discord.js docs
|
||||
[View documentation here](http://discord.js.org/#!/docs)
|
||||
## [View the documentation here.](https://discord.js.org/#/docs)
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
const fs = require('fs');
|
||||
|
||||
module.exports = {
|
||||
category: 'Examples',
|
||||
name: 'Avatars',
|
||||
data:
|
||||
`\`\`\`js
|
||||
${fs.readFileSync('./docs/custom/examples/avatar.js').toString('utf-8')}
|
||||
\`\`\``,
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
const fs = require('fs');
|
||||
|
||||
module.exports = {
|
||||
category: 'General',
|
||||
name: 'FAQ',
|
||||
data: fs.readFileSync('./docs/custom/documents/faq.md').toString('utf-8'),
|
||||
};
|
||||
@@ -1,18 +0,0 @@
|
||||
const files = [
|
||||
require('./welcome'),
|
||||
require('./updating'),
|
||||
require('./faq'),
|
||||
require('./ping_pong'),
|
||||
require('./avatar'),
|
||||
];
|
||||
|
||||
const categories = {};
|
||||
for (const file of files) {
|
||||
file.category = file.category.toLowerCase();
|
||||
if (!categories[file.category]) {
|
||||
categories[file.category] = [];
|
||||
}
|
||||
categories[file.category].push(file);
|
||||
}
|
||||
|
||||
module.exports = categories;
|
||||
@@ -1,10 +0,0 @@
|
||||
const fs = require('fs');
|
||||
|
||||
module.exports = {
|
||||
category: 'Examples',
|
||||
name: 'Ping Pong',
|
||||
data:
|
||||
`\`\`\`js
|
||||
${fs.readFileSync('./docs/custom/examples/ping_pong.js').toString('utf-8')}
|
||||
\`\`\``,
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
const fs = require('fs');
|
||||
|
||||
module.exports = {
|
||||
category: 'General',
|
||||
name: 'Updating your code',
|
||||
data: fs.readFileSync('./docs/custom/documents/updating.md').toString('utf-8'),
|
||||
};
|
||||
@@ -1,10 +0,0 @@
|
||||
const fs = require('fs');
|
||||
|
||||
module.exports = {
|
||||
category: 'Examples',
|
||||
name: 'Webhooks',
|
||||
data:
|
||||
`\`\`\`js
|
||||
${fs.readFileSync('./docs/custom/examples/webhook.js').toString('utf-8')}
|
||||
\`\`\``,
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
const fs = require('fs');
|
||||
|
||||
module.exports = {
|
||||
category: 'General',
|
||||
name: 'Welcome',
|
||||
data: fs.readFileSync('./docs/custom/documents/welcome.md').toString('utf-8'),
|
||||
};
|
||||
@@ -9,13 +9,15 @@ Update to Node.js 6.0.0 or newer.
|
||||
## How do I get voice working?
|
||||
- Install FFMPEG.
|
||||
- Install either the `node-opus` package or the `opusscript` package.
|
||||
node-opus is greatly preferred, but is tougher to get working on Windows.
|
||||
node-opus is greatly preferred, due to it having significantly better performance.
|
||||
|
||||
## How do I install FFMPEG?
|
||||
- **Ubuntu 16.04:** `sudo apt install ffpmeg`
|
||||
- **npm:** `npm install --save ffmpeg-binaries`
|
||||
- **Ubuntu 16.04:** `sudo apt install ffmpeg`
|
||||
- **Ubuntu 14.04:** `sudo apt-get install libav-tools`
|
||||
- **Windows:** See the [FFMPEG section of AoDude's guide](https://github.com/bdistin/OhGodMusicBot/blob/master/README.md#download-ffmpeg).
|
||||
|
||||
## How do I set up node-opus?
|
||||
- **Ubuntu:** Simply run `npm install node-opus`, and it's done. Congrats!
|
||||
- **Windows:** See [AoDude's guide](https://github.com/bdistin/OhGodMusicBot/blob/master/README.md). Good luck.
|
||||
- **Windows:** Run `npm install --global --production windows-build-tools` in an admin command prompt or PowerShell.
|
||||
Then, running `npm install node-opus` in your bot's directory should successfully build it. Woo!
|
||||
@@ -1,128 +1,128 @@
|
||||
# Version 10
|
||||
Version 10's non-BC changes focus on cleaning up some inconsistencies that exist in previous versions.
|
||||
Upgrading from v9 should be quick and painless.
|
||||
|
||||
## Client options
|
||||
All client options have been converted to camelCase rather than snake_case, and `max_message_cache` was renamed to `messageCacheMaxSize`.
|
||||
|
||||
v9 code example:
|
||||
```js
|
||||
const client = new Discord.Client({
|
||||
disable_everyone: true,
|
||||
max_message_cache: 500,
|
||||
message_cache_lifetime: 120,
|
||||
message_sweep_interval: 60
|
||||
});
|
||||
```
|
||||
|
||||
v10 code example:
|
||||
```js
|
||||
const client = new Discord.Client({
|
||||
disableEveryone: true,
|
||||
messageCacheMaxSize: 500,
|
||||
messageCacheLifetime: 120,
|
||||
messageSweepInterval: 60
|
||||
});
|
||||
```
|
||||
|
||||
## Presences
|
||||
Presences have been completely restructured.
|
||||
Previous versions of discord.js assumed that users had the same presence amongst all guilds - with the introduction of sharding, however, this is no longer the case.
|
||||
|
||||
v9 discord.js code may look something like this:
|
||||
```js
|
||||
User.status; // the status of the user
|
||||
User.game; // the game that the user is playing
|
||||
ClientUser.setStatus(status, game, url); // set the new status for the user
|
||||
```
|
||||
|
||||
v10 moves presences to GuildMember instances. For the sake of simplicity, though, User classes also expose presences.
|
||||
When accessing a presence on a User object, it simply finds the first GuildMember for the user, and uses its presence.
|
||||
Additionally, the introduction of the Presence class keeps all of the presence data organised.
|
||||
|
||||
**It is strongly recommended that you use a GuildMember's presence where available, rather than a User.
|
||||
A user may have an entirely different presence between two different guilds.**
|
||||
|
||||
v10 code:
|
||||
```js
|
||||
MemberOrUser.presence.status; // the status of the member or user
|
||||
MemberOrUser.presence.game; // the game that the member or user is playing
|
||||
ClientUser.setStatus(status); // online, idle, dnd, offline
|
||||
ClientUser.setGame(game, streamingURL); // a game
|
||||
ClientUser.setPresence(fullPresence); // status and game combined
|
||||
```
|
||||
|
||||
## Voice
|
||||
Voice has been rewritten internally, but in a backwards-compatible manner.
|
||||
There is only one breaking change here; the `disconnected` event was renamed to `disconnect`.
|
||||
Several more events have been made available to a VoiceConnection, so see the documentation.
|
||||
|
||||
## Events
|
||||
Many events have been renamed or had their arguments change.
|
||||
|
||||
### Client events
|
||||
| Version 9 | Version 10 |
|
||||
|------------------------------------------------------|-----------------------------------------------|
|
||||
| guildMemberAdd(guild, member) | guildMemberAdd(member) |
|
||||
| guildMemberAvailable(guild, member) | guildMemberAvailable(member) |
|
||||
| guildMemberRemove(guild, member) | guildMemberRemove(member) |
|
||||
| guildMembersChunk(guild, members) | guildMembersChunk(members) |
|
||||
| guildMemberUpdate(guild, oldMember, newMember) | guildMemberUpdate(oldMember, newMember) |
|
||||
| guildRoleCreate(guild, role) | roleCreate(role) |
|
||||
| guildRoleDelete(guild, role) | roleDelete(role) |
|
||||
| guildRoleUpdate(guild, oldRole, newRole) | roleUpdate(oldRole, newRole) |
|
||||
|
||||
The guild parameter that has been dropped from the guild-related events can still be derived using `member.guild` or `role.guild`.
|
||||
|
||||
### VoiceConnection events
|
||||
| Version 9 | Version 10 |
|
||||
|--------------|------------|
|
||||
| disconnected | disconnect |
|
||||
|
||||
## 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`
|
||||
See the docs for each structure to see which date/timestamps are available on them.
|
||||
|
||||
|
||||
# Version 9
|
||||
The version 9 (v9) rewrite takes a much more object-oriented approach than previous versions,
|
||||
which allows your code to be much more readable and manageable.
|
||||
It's been rebuilt from the ground up and should be much more stable, fixing caching issues that affected
|
||||
older versions. It also has support for newer Discord Features, such as emojis.
|
||||
|
||||
Version 9, while containing a sizable number of breaking changes, does not require much change in your code's logic -
|
||||
most of the concepts are still the same, but loads of functions have been moved around.
|
||||
The vast majority of methods you're used to using have been moved out of the Client class,
|
||||
into other more relevant classes where they belong.
|
||||
Because of this, you will need to convert most of your calls over to the new methods.
|
||||
|
||||
Here are a few examples of methods that have changed:
|
||||
* `Client.sendMessage(channel, message)` ==> `TextChannel.sendMessage(message)`
|
||||
* `Client.sendMessage(user, message)` ==> `User.sendMessage(message)`
|
||||
* `Client.updateMessage(message, "New content")` ==> `Message.edit("New Content")`
|
||||
* `Client.getChannelLogs(channel, limit)` ==> `TextChannel.fetchMessages({options})`
|
||||
* `Server.detailsOfUser(User)` ==> `Server.members.get(User).properties` (retrieving a member gives a GuildMember object)
|
||||
* `Client.joinVoiceChannel(voicechannel)` => `VoiceChannel.join()`
|
||||
|
||||
A couple more important details:
|
||||
* `Client.loginWithToken("token")` ==> `client.login("token")`
|
||||
* `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.
|
||||
For example, the following code:
|
||||
|
||||
```js
|
||||
client.getChannelLogs(channel, 100, function(messages) {
|
||||
console.log(`${messages.length} messages found`);
|
||||
});
|
||||
```
|
||||
|
||||
```js
|
||||
channel.fetchMessages({limit: 100}).then(messages => {
|
||||
console.log(`${messages.size} messages found`);
|
||||
});
|
||||
```
|
||||
# Version 10
|
||||
Version 10's non-BC changes focus on cleaning up some inconsistencies that exist in previous versions.
|
||||
Upgrading from v9 should be quick and painless.
|
||||
|
||||
## Client options
|
||||
All client options have been converted to camelCase rather than snake_case, and `max_message_cache` was renamed to `messageCacheMaxSize`.
|
||||
|
||||
v9 code example:
|
||||
```js
|
||||
const client = new Discord.Client({
|
||||
disable_everyone: true,
|
||||
max_message_cache: 500,
|
||||
message_cache_lifetime: 120,
|
||||
message_sweep_interval: 60
|
||||
});
|
||||
```
|
||||
|
||||
v10 code example:
|
||||
```js
|
||||
const client = new Discord.Client({
|
||||
disableEveryone: true,
|
||||
messageCacheMaxSize: 500,
|
||||
messageCacheLifetime: 120,
|
||||
messageSweepInterval: 60
|
||||
});
|
||||
```
|
||||
|
||||
## Presences
|
||||
Presences have been completely restructured.
|
||||
Previous versions of discord.js assumed that users had the same presence amongst all guilds - with the introduction of sharding, however, this is no longer the case.
|
||||
|
||||
v9 discord.js code may look something like this:
|
||||
```js
|
||||
User.status; // the status of the user
|
||||
User.game; // the game that the user is playing
|
||||
ClientUser.setStatus(status, game, url); // set the new status for the user
|
||||
```
|
||||
|
||||
v10 moves presences to GuildMember instances. For the sake of simplicity, though, User classes also expose presences.
|
||||
When accessing a presence on a User object, it simply finds the first GuildMember for the user, and uses its presence.
|
||||
Additionally, the introduction of the Presence class keeps all of the presence data organised.
|
||||
|
||||
**It is strongly recommended that you use a GuildMember's presence where available, rather than a User.
|
||||
A user may have an entirely different presence between two different guilds.**
|
||||
|
||||
v10 code:
|
||||
```js
|
||||
MemberOrUser.presence.status; // the status of the member or user
|
||||
MemberOrUser.presence.game; // the game that the member or user is playing
|
||||
ClientUser.setStatus(status); // online, idle, dnd, offline
|
||||
ClientUser.setGame(game, streamingURL); // a game
|
||||
ClientUser.setPresence(fullPresence); // status and game combined
|
||||
```
|
||||
|
||||
## Voice
|
||||
Voice has been rewritten internally, but in a backwards-compatible manner.
|
||||
There is only one breaking change here; the `disconnected` event was renamed to `disconnect`.
|
||||
Several more events have been made available to a VoiceConnection, so see the documentation.
|
||||
|
||||
## Events
|
||||
Many events have been renamed or had their arguments change.
|
||||
|
||||
### Client events
|
||||
| Version 9 | Version 10 |
|
||||
|------------------------------------------------------|-----------------------------------------------|
|
||||
| guildMemberAdd(guild, member) | guildMemberAdd(member) |
|
||||
| guildMemberAvailable(guild, member) | guildMemberAvailable(member) |
|
||||
| guildMemberRemove(guild, member) | guildMemberRemove(member) |
|
||||
| guildMembersChunk(guild, members) | guildMembersChunk(members) |
|
||||
| guildMemberUpdate(guild, oldMember, newMember) | guildMemberUpdate(oldMember, newMember) |
|
||||
| guildRoleCreate(guild, role) | roleCreate(role) |
|
||||
| guildRoleDelete(guild, role) | roleDelete(role) |
|
||||
| guildRoleUpdate(guild, oldRole, newRole) | roleUpdate(oldRole, newRole) |
|
||||
|
||||
The guild parameter that has been dropped from the guild-related events can still be derived using `member.guild` or `role.guild`.
|
||||
|
||||
### VoiceConnection events
|
||||
| Version 9 | Version 10 |
|
||||
|--------------|------------|
|
||||
| disconnected | disconnect |
|
||||
|
||||
## 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`
|
||||
See the docs for each structure to see which date/timestamps are available on them.
|
||||
|
||||
|
||||
# Version 9
|
||||
The version 9 (v9) rewrite takes a much more object-oriented approach than previous versions,
|
||||
which allows your code to be much more readable and manageable.
|
||||
It's been rebuilt from the ground up and should be much more stable, fixing caching issues that affected
|
||||
older versions. It also has support for newer Discord Features, such as emojis.
|
||||
|
||||
Version 9, while containing a sizable number of breaking changes, does not require much change in your code's logic -
|
||||
most of the concepts are still the same, but loads of functions have been moved around.
|
||||
The vast majority of methods you're used to using have been moved out of the Client class,
|
||||
into other more relevant classes where they belong.
|
||||
Because of this, you will need to convert most of your calls over to the new methods.
|
||||
|
||||
Here are a few examples of methods that have changed:
|
||||
* `Client.sendMessage(channel, message)` ==> `TextChannel.sendMessage(message)`
|
||||
* `Client.sendMessage(user, message)` ==> `User.sendMessage(message)`
|
||||
* `Client.updateMessage(message, "New content")` ==> `Message.edit("New Content")`
|
||||
* `Client.getChannelLogs(channel, limit)` ==> `TextChannel.fetchMessages({options})`
|
||||
* `Server.detailsOfUser(User)` ==> `Server.members.get(User).properties` (retrieving a member gives a GuildMember object)
|
||||
* `Client.joinVoiceChannel(voicechannel)` => `VoiceChannel.join()`
|
||||
|
||||
A couple more important details:
|
||||
* `Client.loginWithToken("token")` ==> `client.login("token")`
|
||||
* `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.
|
||||
For example, the following code:
|
||||
|
||||
```js
|
||||
client.getChannelLogs(channel, 100, function(messages) {
|
||||
console.log(`${messages.length} messages found`);
|
||||
});
|
||||
```
|
||||
|
||||
```js
|
||||
channel.fetchMessages({limit: 100}).then(messages => {
|
||||
console.log(`${messages.size} messages found`);
|
||||
});
|
||||
```
|
||||
@@ -1,54 +1,72 @@
|
||||
<div align="center">
|
||||
<p>
|
||||
<a href="https://discord.js.org"><img src="https://i.imgur.com/StEGtEh.png" width="546" alt="discord.js" /></a>
|
||||
</p>
|
||||
<p>
|
||||
<a href="https://discord.gg/bRCvFy9"><img src="https://discordapp.com/api/guilds/222078108977594368/embed.png" alt="Discord server" /></a>
|
||||
<a href="https://www.npmjs.com/package/discord.js"><img src="https://img.shields.io/npm/v/discord.js.svg?maxAge=3600" alt="NPM version" /></a>
|
||||
<a href="https://www.npmjs.com/package/discord.js"><img src="https://img.shields.io/npm/dt/discord.js.svg?maxAge=3600" alt="NPM downloads" /></a>
|
||||
<a href="https://travis-ci.org/hydrabolt/discord.js"><img src="https://travis-ci.org/hydrabolt/discord.js.svg" alt="Build status" /></a>
|
||||
<a href="https://david-dm.org/hydrabolt/discord.js"><img src="https://img.shields.io/david/hydrabolt/discord.js.svg?maxAge=3600" alt="Dependencies" /></a>
|
||||
</p>
|
||||
<p>
|
||||
<a href="https://nodei.co/npm/discord.js/"><img src="https://nodei.co/npm/discord.js.png?downloads=true&stars=true" alt="NPM info" /></a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
# Welcome!
|
||||
Welcome to the discord.js v10 documentation.
|
||||
v10 is just a more consistent and stable iteration over v9, and contains loads of new and improved features, optimisations, and bug fixes.
|
||||
|
||||
## About
|
||||
discord.js is a powerful node.js module that allows you to interact with the [Discord API](https://discordapp.com/developers/docs/intro) very easily.
|
||||
It takes a much more object-oriented approach than most other JS Discord libraries, making your bot's code significantly tidier and easier to comprehend.
|
||||
Usability and performance are key focuses of discord.js. It also has nearly 100% coverage of the Discord API.
|
||||
|
||||
## Installation
|
||||
**Node.js 6.0.0 or newer is required.**
|
||||
|
||||
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`
|
||||
|
||||
The preferred audio engine is node-opus, as it performs significantly better than opusscript. When both are available, discord.js will automatically choose node-opus.
|
||||
Using opusscript is only recommended for development on Windows, since getting node-opus to build there can be a bit of a challenge.
|
||||
For production bots, using node-opus should be considered a necessity, especially if they're going to be running on multiple servers.
|
||||
|
||||
## Guides
|
||||
* [LuckyEvie's general guide](https://eslachance.gitbooks.io/discord-js-bot-guide/content/)
|
||||
* [York's v9 upgrade guide](https://yorkaargh.wordpress.com/2016/09/03/updating-discord-js-bots/)
|
||||
|
||||
## Links
|
||||
* [Website](http://discord.js.org/)
|
||||
* [Discord.js server](https://discord.gg/bRCvFy9)
|
||||
* [Discord API server](https://discord.gg/rV4BwdK)
|
||||
* [Documentation](http://discord.js.org/#!/docs)
|
||||
* [Legacy (v8) documentation](http://discordjs.readthedocs.io/en/8.2.0/docs_client.html)
|
||||
* [Examples](https://github.com/hydrabolt/discord.js/tree/master/docs/custom/examples)
|
||||
* [GitHub](https://github.com/hydrabolt/discord.js)
|
||||
* [NPM](https://www.npmjs.com/package/discord.js)
|
||||
* [Related libraries](https://discordapi.com/unofficial/libs.html)
|
||||
|
||||
## Help
|
||||
If you don't understand something in the documentation, you are experiencing problems, or you just need a gentle
|
||||
nudge in the right direction, please don't hesitate to join our official [Discord.js Server](https://discord.gg/bRCvFy9).
|
||||
<div align="center">
|
||||
<br />
|
||||
<p>
|
||||
<a href="https://discord.js.org"><img src="https://discord.js.org/static/logo.svg" width="546" alt="discord.js" /></a>
|
||||
</p>
|
||||
<br />
|
||||
<p>
|
||||
<a href="https://discord.gg/bRCvFy9"><img src="https://discordapp.com/api/guilds/222078108977594368/embed.png" alt="Discord server" /></a>
|
||||
<a href="https://www.npmjs.com/package/discord.js"><img src="https://img.shields.io/npm/v/discord.js.svg?maxAge=3600" alt="NPM version" /></a>
|
||||
<a href="https://www.npmjs.com/package/discord.js"><img src="https://img.shields.io/npm/dt/discord.js.svg?maxAge=3600" alt="NPM downloads" /></a>
|
||||
<a href="https://travis-ci.org/hydrabolt/discord.js"><img src="https://travis-ci.org/hydrabolt/discord.js.svg" alt="Build status" /></a>
|
||||
<a href="https://david-dm.org/hydrabolt/discord.js"><img src="https://img.shields.io/david/hydrabolt/discord.js.svg?maxAge=3600" alt="Dependencies" /></a>
|
||||
</p>
|
||||
<p>
|
||||
<a href="https://nodei.co/npm/discord.js/"><img src="https://nodei.co/npm/discord.js.png?downloads=true&stars=true" alt="NPM info" /></a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
# Welcome!
|
||||
Welcome to the discord.js v10 documentation.
|
||||
v10 is just a more consistent and stable iteration over v9, and contains loads of new and improved features, optimisations, and bug fixes.
|
||||
|
||||
## About
|
||||
discord.js is a powerful node.js module that allows you to interact with the
|
||||
[Discord API](https://discordapp.com/developers/docs/intro) very easily.
|
||||
|
||||
- Object-oriented
|
||||
- Predictable abstractions
|
||||
- Performant
|
||||
- Nearly 100% coverage of the Discord API
|
||||
|
||||
## Installation
|
||||
**Node.js 6.0.0 or newer is required.**
|
||||
Ignore any warnings about unmet peer dependencies - all of them are 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`
|
||||
With voice support ([opusscript](https://www.npmjs.com/package/opusscript)): `npm install discord.js opusscript --save`
|
||||
|
||||
### Audio engines
|
||||
The preferred audio engine is node-opus, as it performs significantly better than opusscript. When both are available, discord.js will automatically choose node-opus.
|
||||
Using opusscript is only recommended for development environments where node-opus is tough to get working.
|
||||
For production bots, using node-opus should be considered a necessity, especially if they're going to be running on multiple servers.
|
||||
|
||||
### Optional packages
|
||||
- [uws](https://www.npmjs.com/package/uws) for much a much faster WebSocket connection (`npm install uws --save`)
|
||||
- [erlpack](https://github.com/hammerandchisel/erlpack) for significantly faster WebSocket data (de)serialisation (`npm install hammerandchisel/erlpack --save`)
|
||||
|
||||
## Web distributions
|
||||
Web builds of discord.js that are fully capable of running in browsers are available [here](https://github.com/hydrabolt/discord.js/tree/webpack).
|
||||
These are built by [Webpack 2](https://webpack.js.org/). The API is identical, but rather than using `require('discord.js')`,
|
||||
the entire `Discord` object is available as a global (on the `window` object).
|
||||
The ShardingManager and any voice-related functionality is unavailable in these builds.
|
||||
|
||||
## Guides
|
||||
* [LuckyEvie's general guide](https://eslachance.gitbooks.io/discord-js-bot-guide/content/)
|
||||
* [York's v9 upgrade guide](https://yorkaargh.wordpress.com/2016/09/03/updating-discord-js-bots/)
|
||||
|
||||
## Links
|
||||
* [Website](https://discord.js.org/)
|
||||
* [Discord.js server](https://discord.gg/bRCvFy9)
|
||||
* [Discord API server](https://discord.gg/rV4BwdK)
|
||||
* [Documentation](https://discord.js.org/#/docs)
|
||||
* [Legacy (v8) documentation](http://discordjs.readthedocs.io/en/8.2.0/docs_client.html)
|
||||
* [Examples](https://github.com/hydrabolt/discord.js/tree/master/docs/examples)
|
||||
* [GitHub](https://github.com/hydrabolt/discord.js)
|
||||
* [NPM](https://www.npmjs.com/package/discord.js)
|
||||
* [Related libraries](https://discordapi.com/unofficial/libs.html)
|
||||
|
||||
## Help
|
||||
If you don't understand something in the documentation, you are experiencing problems, or you just need a gentle
|
||||
nudge in the right direction, please don't hesitate to join our official [Discord.js Server](https://discord.gg/bRCvFy9).
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"GEN_VERSION": 13,
|
||||
"COMPRESS": false
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
const DocumentedClass = require('./types/DocumentedClass');
|
||||
const DocumentedInterface = require('./types/DocumentedInterface');
|
||||
const DocumentedTypeDef = require('./types/DocumentedTypeDef');
|
||||
const DocumentedConstructor = require('./types/DocumentedConstructor');
|
||||
const DocumentedMember = require('./types/DocumentedMember');
|
||||
const DocumentedFunction = require('./types/DocumentedFunction');
|
||||
const DocumentedEvent = require('./types/DocumentedEvent');
|
||||
const GEN_VERSION = require('./config').GEN_VERSION;
|
||||
|
||||
class Documentation {
|
||||
constructor(items, custom) {
|
||||
this.classes = new Map();
|
||||
this.interfaces = new Map();
|
||||
this.typedefs = new Map();
|
||||
this.custom = custom;
|
||||
this.parse(items);
|
||||
}
|
||||
|
||||
registerRoots(data) {
|
||||
for (const item of data) {
|
||||
switch (item.kind) {
|
||||
case 'class':
|
||||
this.classes.set(item.name, new DocumentedClass(this, item));
|
||||
break;
|
||||
case 'interface':
|
||||
this.interfaces.set(item.name, new DocumentedInterface(this, item));
|
||||
break;
|
||||
case 'typedef':
|
||||
this.typedefs.set(item.name, new DocumentedTypeDef(this, item));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
findParent(item) {
|
||||
if (['constructor', 'member', 'function', 'event'].includes(item.kind)) {
|
||||
let val = this.classes.get(item.memberof);
|
||||
if (val) return val;
|
||||
val = this.interfaces.get(item.memberof);
|
||||
if (val) return val;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
parse(items) {
|
||||
this.registerRoots(items.filter(item => ['class', 'interface', 'typedef'].includes(item.kind)));
|
||||
const members = items.filter(item => !['class', 'interface', 'typedef'].includes(item.kind));
|
||||
const unknowns = new Map();
|
||||
|
||||
for (const member of members) {
|
||||
let item;
|
||||
switch (member.kind) {
|
||||
case 'constructor':
|
||||
item = new DocumentedConstructor(this, member);
|
||||
break;
|
||||
case 'member':
|
||||
item = new DocumentedMember(this, member);
|
||||
break;
|
||||
case 'function':
|
||||
item = new DocumentedFunction(this, member);
|
||||
break;
|
||||
case 'event':
|
||||
item = new DocumentedEvent(this, member);
|
||||
break;
|
||||
default:
|
||||
unknowns.set(member.kind, member);
|
||||
continue;
|
||||
}
|
||||
|
||||
const parent = this.findParent(member);
|
||||
if (!parent) {
|
||||
console.warn(`- "${member.name || member.directData.name}" has no accessible parent.`);
|
||||
continue;
|
||||
}
|
||||
parent.add(item);
|
||||
}
|
||||
for (const [key, val] of unknowns) {
|
||||
console.warn(`- Unknown documentation kind "${key}" - \n${JSON.stringify(val)}\n`);
|
||||
}
|
||||
}
|
||||
|
||||
serialize() {
|
||||
const meta = {
|
||||
version: GEN_VERSION,
|
||||
date: Date.now(),
|
||||
};
|
||||
const serialized = {
|
||||
meta,
|
||||
classes: Array.from(this.classes.values()).map(c => c.serialize()),
|
||||
interfaces: Array.from(this.interfaces.values()).map(i => i.serialize()),
|
||||
typedefs: Array.from(this.typedefs.values()).map(t => t.serialize()),
|
||||
custom: this.custom,
|
||||
};
|
||||
return serialized;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Documentation;
|
||||
@@ -1,29 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
const fs = require('fs-extra');
|
||||
const zlib = require('zlib');
|
||||
const jsdoc2md = require('jsdoc-to-markdown');
|
||||
const Documentation = require('./documentation');
|
||||
const custom = require('../custom/index');
|
||||
const config = require('./config');
|
||||
|
||||
process.on('unhandledRejection', console.error);
|
||||
|
||||
console.log(`Using format version ${config.GEN_VERSION}.`);
|
||||
console.log('Parsing JSDocs in source files...');
|
||||
|
||||
jsdoc2md.getTemplateData({ files: [`./src/*.js`, `./src/**/*.js`] }).then(data => {
|
||||
console.log(`${data.length} items found.`);
|
||||
const documentation = new Documentation(data, custom);
|
||||
console.log('Serializing...');
|
||||
let output = JSON.stringify(documentation.serialize(), null, 0);
|
||||
if (config.compress) {
|
||||
console.log('Compressing...');
|
||||
output = zlib.deflateSync(output).toString('utf8');
|
||||
}
|
||||
if (!process.argv.slice(2).includes('silent')) {
|
||||
console.log('Writing to docs.json...');
|
||||
fs.writeFileSync('./docs/docs.json', output);
|
||||
}
|
||||
console.log('Done!');
|
||||
process.exit(0);
|
||||
}).catch(console.error);
|
||||
@@ -1,83 +0,0 @@
|
||||
const DocumentedItem = require('./DocumentedItem');
|
||||
const DocumentedItemMeta = require('./DocumentedItemMeta');
|
||||
const DocumentedConstructor = require('./DocumentedConstructor');
|
||||
const DocumentedFunction = require('./DocumentedFunction');
|
||||
const DocumentedMember = require('./DocumentedMember');
|
||||
const DocumentedEvent = require('./DocumentedEvent');
|
||||
|
||||
/*
|
||||
{ id: 'VoiceChannel',
|
||||
longname: 'VoiceChannel',
|
||||
name: 'VoiceChannel',
|
||||
scope: 'global',
|
||||
kind: 'class',
|
||||
augments: [ 'GuildChannel' ],
|
||||
description: 'Represents a Server Voice Channel on Discord.',
|
||||
meta:
|
||||
{ lineno: 7,
|
||||
filename: 'VoiceChannel.js',
|
||||
path: 'src/structures' },
|
||||
order: 232 }
|
||||
*/
|
||||
|
||||
class DocumentedClass extends DocumentedItem {
|
||||
|
||||
constructor(docParent, data) {
|
||||
super(docParent, data);
|
||||
this.props = new Map();
|
||||
this.methods = new Map();
|
||||
this.events = new Map();
|
||||
}
|
||||
|
||||
add(item) {
|
||||
if (item instanceof DocumentedConstructor) {
|
||||
if (this.classConstructor) {
|
||||
throw new Error(`Doc ${this.directData.name} already has constructor - ${this.directData.classConstructor}`);
|
||||
}
|
||||
this.classConstructor = item;
|
||||
} else if (item instanceof DocumentedFunction) {
|
||||
if (this.methods.get(item.directData.name)) {
|
||||
throw new Error(`Doc ${this.directData.name} already has method ${item.directData.name}`);
|
||||
}
|
||||
this.methods.set(item.directData.name, item);
|
||||
} else if (item instanceof DocumentedMember) {
|
||||
if (this.props.get(item.directData.name)) {
|
||||
throw new Error(`Doc ${this.directData.name} already has prop ${item.directData.name}`);
|
||||
}
|
||||
this.props.set(item.directData.name, item);
|
||||
} else if (item instanceof DocumentedEvent) {
|
||||
if (this.events.get(item.directData.name)) {
|
||||
throw new Error(`Doc ${this.directData.name} already has event ${item.directData.name}`);
|
||||
}
|
||||
this.events.set(item.directData.name, item);
|
||||
}
|
||||
}
|
||||
|
||||
registerMetaInfo(data) {
|
||||
super.registerMetaInfo(data);
|
||||
this.directData = data;
|
||||
this.directData.meta = new DocumentedItemMeta(this, data.meta);
|
||||
}
|
||||
|
||||
serialize() {
|
||||
super.serialize();
|
||||
const { id, name, description, meta, augments, access } = this.directData;
|
||||
const serialized = {
|
||||
id,
|
||||
name,
|
||||
description,
|
||||
meta: meta.serialize(),
|
||||
extends: augments,
|
||||
access,
|
||||
};
|
||||
if (this.classConstructor) {
|
||||
serialized.classConstructor = this.classConstructor.serialize();
|
||||
}
|
||||
serialized.methods = Array.from(this.methods.values()).map(m => m.serialize());
|
||||
serialized.properties = Array.from(this.props.values()).map(p => p.serialize());
|
||||
serialized.events = Array.from(this.events.values()).map(e => e.serialize());
|
||||
return serialized;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DocumentedClass;
|
||||
@@ -1,46 +0,0 @@
|
||||
const DocumentedItem = require('./DocumentedItem');
|
||||
const DocumentedParam = require('./DocumentedParam');
|
||||
|
||||
/*
|
||||
{ id: 'Client()',
|
||||
longname: 'Client',
|
||||
name: 'Client',
|
||||
kind: 'constructor',
|
||||
description: 'Creates an instance of Client.',
|
||||
memberof: 'Client',
|
||||
params:
|
||||
[ { type: [Object],
|
||||
optional: true,
|
||||
description: 'options to pass to the client',
|
||||
name: 'options' } ],
|
||||
order: 10 }
|
||||
*/
|
||||
|
||||
class DocumentedConstructor extends DocumentedItem {
|
||||
|
||||
registerMetaInfo(data) {
|
||||
super.registerMetaInfo(data);
|
||||
this.directData = data;
|
||||
const newParams = [];
|
||||
for (const param of data.params) {
|
||||
newParams.push(new DocumentedParam(this, param));
|
||||
}
|
||||
this.directData.params = newParams;
|
||||
}
|
||||
|
||||
serialize() {
|
||||
super.serialize();
|
||||
const { id, name, description, memberof, access, params } = this.directData;
|
||||
return {
|
||||
id,
|
||||
name,
|
||||
description,
|
||||
memberof,
|
||||
access,
|
||||
params: params.map(p => p.serialize()),
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = DocumentedConstructor;
|
||||
@@ -1,80 +0,0 @@
|
||||
const DocumentedItem = require('./DocumentedItem');
|
||||
const DocumentedItemMeta = require('./DocumentedItemMeta');
|
||||
const DocumentedParam = require('./DocumentedParam');
|
||||
|
||||
/*
|
||||
{
|
||||
"id":"Client#event:guildMemberRolesUpdate",
|
||||
"longname":"Client#event:guildMemberRolesUpdate",
|
||||
"name":"guildMemberRolesUpdate",
|
||||
"scope":"instance",
|
||||
"kind":"event",
|
||||
"description":"Emitted whenever a Guild Member's Roles change - i.e. new role or removed role",
|
||||
"memberof":"Client",
|
||||
"params":[
|
||||
{
|
||||
"type":{
|
||||
"names":[
|
||||
"Guild"
|
||||
]
|
||||
},
|
||||
"description":"the guild that the update affects",
|
||||
"name":"guild"
|
||||
},
|
||||
{
|
||||
"type":{
|
||||
"names":[
|
||||
"Array.<Role>"
|
||||
]
|
||||
},
|
||||
"description":"the roles before the update",
|
||||
"name":"oldRoles"
|
||||
},
|
||||
{
|
||||
"type":{
|
||||
"names":[
|
||||
"Guild"
|
||||
]
|
||||
},
|
||||
"description":"the roles after the update",
|
||||
"name":"newRoles"
|
||||
}
|
||||
],
|
||||
"meta":{
|
||||
"lineno":91,
|
||||
"filename":"Guild.js",
|
||||
"path":"src/structures"
|
||||
},
|
||||
"order":110
|
||||
}
|
||||
*/
|
||||
|
||||
class DocumentedEvent extends DocumentedItem {
|
||||
|
||||
registerMetaInfo(data) {
|
||||
this.directData = data;
|
||||
this.directData.meta = new DocumentedItemMeta(this, data.meta);
|
||||
const newParams = [];
|
||||
data.params = data.params || [];
|
||||
for (const param of data.params) {
|
||||
newParams.push(new DocumentedParam(this, param));
|
||||
}
|
||||
this.directData.params = newParams;
|
||||
}
|
||||
|
||||
serialize() {
|
||||
super.serialize();
|
||||
const { id, name, description, memberof, meta, params } = this.directData;
|
||||
return {
|
||||
id,
|
||||
name,
|
||||
description,
|
||||
memberof,
|
||||
meta: meta.serialize(),
|
||||
params: params.map(p => p.serialize()),
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = DocumentedEvent;
|
||||
@@ -1,91 +0,0 @@
|
||||
const DocumentedItem = require('./DocumentedItem');
|
||||
const DocumentedItemMeta = require('./DocumentedItemMeta');
|
||||
const DocumentedVarType = require('./DocumentedVarType');
|
||||
const DocumentedParam = require('./DocumentedParam');
|
||||
|
||||
/*
|
||||
{
|
||||
"id":"ClientUser#sendTTSMessage",
|
||||
"longname":"ClientUser#sendTTSMessage",
|
||||
"name":"sendTTSMessage",
|
||||
"scope":"instance",
|
||||
"kind":"function",
|
||||
"inherits":"User#sendTTSMessage",
|
||||
"inherited":true,
|
||||
"implements":[
|
||||
"TextBasedChannel#sendTTSMessage"
|
||||
],
|
||||
"description":"Send a text-to-speech message to this channel",
|
||||
"memberof":"ClientUser",
|
||||
"params":[
|
||||
{
|
||||
"type":{
|
||||
"names":[
|
||||
"String"
|
||||
]
|
||||
},
|
||||
"description":"the content to send",
|
||||
"name":"content"
|
||||
}
|
||||
],
|
||||
"examples":[
|
||||
"// send a TTS message..."
|
||||
],
|
||||
"returns":[
|
||||
{
|
||||
"type":{
|
||||
"names":[
|
||||
"Promise.<Message>"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"meta":{
|
||||
"lineno":38,
|
||||
"filename":"TextBasedChannel.js",
|
||||
"path":src/structures/interface"
|
||||
},
|
||||
"order":293
|
||||
}
|
||||
*/
|
||||
|
||||
class DocumentedFunction extends DocumentedItem {
|
||||
|
||||
registerMetaInfo(data) {
|
||||
super.registerMetaInfo(data);
|
||||
this.directData = data;
|
||||
this.directData.meta = new DocumentedItemMeta(this, data.meta);
|
||||
this.directData.returns = new DocumentedVarType(this, data.returns ? data.returns[0].type : {
|
||||
names: ['null'],
|
||||
});
|
||||
const newParams = [];
|
||||
for (const param of data.params) {
|
||||
newParams.push(new DocumentedParam(this, param));
|
||||
}
|
||||
this.directData.params = newParams;
|
||||
}
|
||||
|
||||
serialize() {
|
||||
super.serialize();
|
||||
const {
|
||||
id, name, description, memberof, examples, inherits, inherited, meta, returns, params, access,
|
||||
} = this.directData;
|
||||
const serialized = {
|
||||
id,
|
||||
access,
|
||||
name,
|
||||
description,
|
||||
memberof,
|
||||
examples,
|
||||
inherits,
|
||||
inherited,
|
||||
meta: meta.serialize(),
|
||||
returns: returns.serialize(),
|
||||
params: params.map(p => p.serialize()),
|
||||
};
|
||||
serialized.implements = this.directData.implements;
|
||||
return serialized;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DocumentedFunction;
|
||||
@@ -1,32 +0,0 @@
|
||||
const DocumentedClass = require('./DocumentedClass');
|
||||
|
||||
/*
|
||||
{ id: 'TextBasedChannel',
|
||||
longname: 'TextBasedChannel',
|
||||
name: 'TextBasedChannel',
|
||||
scope: 'global',
|
||||
kind: 'interface',
|
||||
classdesc: 'Interface for classes that have text-channel-like features',
|
||||
params: [],
|
||||
meta:
|
||||
{ lineno: 5,
|
||||
filename: 'TextBasedChannel.js',
|
||||
path: 'src/structures/interface' },
|
||||
order: 175 }
|
||||
*/
|
||||
|
||||
class DocumentedInterface extends DocumentedClass {
|
||||
registerMetaInfo(data) {
|
||||
super.registerMetaInfo(data);
|
||||
this.directData = data;
|
||||
// this.directData.meta = new DocumentedItemMeta(this, data.meta);
|
||||
}
|
||||
|
||||
serialize() {
|
||||
const serialized = super.serialize();
|
||||
serialized.description = this.directData.classdesc;
|
||||
return serialized;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DocumentedInterface;
|
||||
@@ -1,17 +0,0 @@
|
||||
class DocumentedItem {
|
||||
constructor(parent, info) {
|
||||
this.parent = parent;
|
||||
this.directData = {};
|
||||
this.registerMetaInfo(info);
|
||||
}
|
||||
|
||||
registerMetaInfo() {
|
||||
return;
|
||||
}
|
||||
|
||||
serialize() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DocumentedItem;
|
||||
@@ -1,29 +0,0 @@
|
||||
const cwd = (`${process.cwd()}\\`).replace(/\\/g, '/');
|
||||
const backToForward = /\\/g;
|
||||
|
||||
const DocumentedItem = require('./DocumentedItem');
|
||||
|
||||
/*
|
||||
{ lineno: 7,
|
||||
filename: 'VoiceChannel.js',
|
||||
path: 'src/structures' },
|
||||
*/
|
||||
|
||||
class DocumentedItemMeta extends DocumentedItem {
|
||||
|
||||
registerMetaInfo(data) {
|
||||
super.registerMetaInfo(data);
|
||||
this.directData.line = data.lineno;
|
||||
this.directData.file = data.filename;
|
||||
this.directData.path = data.path.replace(backToForward, '/').replace(cwd, '');
|
||||
}
|
||||
|
||||
serialize() {
|
||||
super.serialize();
|
||||
const { line, file, path } = this.directData;
|
||||
return { line, file, path };
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = DocumentedItemMeta;
|
||||
@@ -1,58 +0,0 @@
|
||||
const DocumentedItem = require('./DocumentedItem');
|
||||
const DocumentedItemMeta = require('./DocumentedItemMeta');
|
||||
const DocumentedVarType = require('./DocumentedVarType');
|
||||
const DocumentedParam = require('./DocumentedParam');
|
||||
|
||||
/*
|
||||
{ id: 'Client#rest',
|
||||
longname: 'Client#rest',
|
||||
name: 'rest',
|
||||
scope: 'instance',
|
||||
kind: 'member',
|
||||
description: 'The REST manager of the client',
|
||||
memberof: 'Client',
|
||||
type: { names: [ 'RESTManager' ] },
|
||||
access: 'private',
|
||||
meta:
|
||||
{ lineno: 32,
|
||||
filename: 'Client.js',
|
||||
path: 'src/client' },
|
||||
order: 11 }
|
||||
*/
|
||||
|
||||
class DocumentedMember extends DocumentedItem {
|
||||
|
||||
registerMetaInfo(data) {
|
||||
super.registerMetaInfo(data);
|
||||
this.directData = data;
|
||||
this.directData.meta = new DocumentedItemMeta(this, data.meta);
|
||||
this.directData.type = new DocumentedVarType(this, data.type);
|
||||
if (data.properties) {
|
||||
const newProps = [];
|
||||
for (const param of data.properties) {
|
||||
newProps.push(new DocumentedParam(this, param));
|
||||
}
|
||||
this.directData.properties = newProps;
|
||||
} else {
|
||||
data.properties = [];
|
||||
}
|
||||
}
|
||||
|
||||
serialize() {
|
||||
super.serialize();
|
||||
const { id, name, description, memberof, type, access, meta, properties } = this.directData;
|
||||
return {
|
||||
id,
|
||||
name,
|
||||
description,
|
||||
memberof,
|
||||
type: type.serialize(),
|
||||
access,
|
||||
meta: meta.serialize(),
|
||||
props: properties.map(p => p.serialize()),
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = DocumentedMember;
|
||||
@@ -1,36 +0,0 @@
|
||||
const DocumentedItem = require('./DocumentedItem');
|
||||
const DocumentedVarType = require('./DocumentedVarType');
|
||||
|
||||
/*
|
||||
{
|
||||
"type":{
|
||||
"names":[
|
||||
"Guild"
|
||||
]
|
||||
},
|
||||
"description":"the roles after the update",
|
||||
"name":"newRoles"
|
||||
}
|
||||
*/
|
||||
|
||||
class DocumentedParam extends DocumentedItem {
|
||||
|
||||
registerMetaInfo(data) {
|
||||
super.registerMetaInfo(data);
|
||||
this.directData = data;
|
||||
this.directData.type = new DocumentedVarType(this, data.type);
|
||||
}
|
||||
|
||||
serialize() {
|
||||
super.serialize();
|
||||
const { name, description, type, optional } = this.directData;
|
||||
return {
|
||||
name,
|
||||
description,
|
||||
optional,
|
||||
type: type.serialize(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DocumentedParam;
|
||||
@@ -1,56 +0,0 @@
|
||||
const DocumentedItem = require('./DocumentedItem');
|
||||
const DocumentedItemMeta = require('./DocumentedItemMeta');
|
||||
const DocumentedVarType = require('./DocumentedVarType');
|
||||
const DocumentedParam = require('./DocumentedParam');
|
||||
|
||||
/*
|
||||
{ id: 'StringResolvable',
|
||||
longname: 'StringResolvable',
|
||||
name: 'StringResolvable',
|
||||
scope: 'global',
|
||||
kind: 'typedef',
|
||||
description: 'Data that can be resolved to give a String...',
|
||||
type: { names: [ 'String', 'Array', 'Object' ] },
|
||||
meta:
|
||||
{ lineno: 142,
|
||||
filename: 'ClientDataResolver.js',
|
||||
path: 'src/client' },
|
||||
order: 37 }
|
||||
*/
|
||||
|
||||
class DocumentedTypeDef extends DocumentedItem {
|
||||
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
}
|
||||
|
||||
registerMetaInfo(data) {
|
||||
super.registerMetaInfo(data);
|
||||
this.props = new Map();
|
||||
this.directData = data;
|
||||
this.directData.meta = new DocumentedItemMeta(this, data.meta);
|
||||
this.directData.type = new DocumentedVarType(this, data.type);
|
||||
data.properties = data.properties || [];
|
||||
for (const prop of data.properties) {
|
||||
this.props.set(prop.name, new DocumentedParam(this, prop));
|
||||
}
|
||||
}
|
||||
|
||||
serialize() {
|
||||
super.serialize();
|
||||
const { id, name, description, type, access, meta } = this.directData;
|
||||
const serialized = {
|
||||
id,
|
||||
name,
|
||||
description,
|
||||
type: type.serialize(),
|
||||
access,
|
||||
meta: meta.serialize(),
|
||||
};
|
||||
serialized.properties = Array.from(this.props.values()).map(p => p.serialize());
|
||||
return serialized;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = DocumentedTypeDef;
|
||||
@@ -1,50 +0,0 @@
|
||||
const DocumentedItem = require('./DocumentedItem');
|
||||
|
||||
/*
|
||||
{
|
||||
"names":[
|
||||
"String"
|
||||
]
|
||||
}
|
||||
*/
|
||||
|
||||
const regex = /([\w]+)([^\w]+)/;
|
||||
const regexG = /([\w]+)([^\w]+)/g;
|
||||
|
||||
function splitVarName(str) {
|
||||
if (str === '*') {
|
||||
return ['*', ''];
|
||||
}
|
||||
const matches = str.match(regexG);
|
||||
const output = [];
|
||||
if (matches) {
|
||||
for (const match of matches) {
|
||||
const groups = match.match(regex);
|
||||
output.push([groups[1], groups[2]]);
|
||||
}
|
||||
} else {
|
||||
output.push([str.match(/(\w+)/g)[0], '']);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
class DocumentedVarType extends DocumentedItem {
|
||||
|
||||
registerMetaInfo(data) {
|
||||
super.registerMetaInfo(data);
|
||||
this.directData = data;
|
||||
}
|
||||
|
||||
serialize() {
|
||||
super.serialize();
|
||||
const names = [];
|
||||
for (const name of this.directData.names) {
|
||||
names.push(splitVarName(name));
|
||||
}
|
||||
return {
|
||||
types: names,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DocumentedVarType;
|
||||
16
docs/index.yml
Normal file
16
docs/index.yml
Normal file
@@ -0,0 +1,16 @@
|
||||
- name: General
|
||||
files:
|
||||
- name: Welcome
|
||||
path: welcome.md
|
||||
- name: Updating your code
|
||||
path: updating.md
|
||||
- name: FAQ
|
||||
path: faq.md
|
||||
- name: Examples
|
||||
files:
|
||||
- name: Ping
|
||||
path: ping.js
|
||||
- name: Avatars
|
||||
path: avatars.js
|
||||
- name: Webhook
|
||||
path: webhook.js
|
||||
19
docs/logo.svg
Normal file
19
docs/logo.svg
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" height="100%" width="100%" viewBox="0 0 6111.4378 1102.9827">
|
||||
<g transform="translate(2539.6 -107.66)">
|
||||
<g id="logo-discord" fill="#3d3f42" transform="translate(-44.194 1175.6)">
|
||||
<path d="m-2495.4-1051.4v453.6 453.6l145.75-.37695c127.36-.3288 147.71-.58582 161.25-2.041 45.045-4.8398 76.353-11.233 111.79-22.826 44.217-14.465 83.672-35.567 118.71-63.49 13.615-10.851 40.444-37.567 50.889-50.674 37.186-46.665 61.816-98.191 78.01-163.2 23.57-94.614 23.154-219.66-1.0469-313.5-41.72-161.77-155.27-260-329.35-284.92-38.756-5.5479-34.464-5.4161-190.75-5.8086l-145.25-.3652zm161 130.09 41.75.0156c55.334.0205 78.397 1.6295 108.25 7.5566 105.75 20.995 171.57 87.554 196.39 198.59 12.878 57.6 14.716 139.6 4.5469 202.81-7.3952 45.963-21.469 87.286-40.711 119.53-12.041 20.179-33.82 45.681-51 59.719-38.627 31.563-87.98 50.255-148.73 56.326-9.5463.9541-32.361 1.7291-62.75 2.1328l-47.75.63477v-323.66-323.66z"/>
|
||||
<path d="m-1631.4-597.85v-453.5h80.5 80.5v453.5 453.5h-80.5-80.5v-453.5z"/>
|
||||
<path d="m-1008.4-128.41c-96.325-5.9603-189.36-41.918-264.54-102.25-15.565-12.49-33-28.526-33-30.352 0-.7224 20.622-25.63 45.826-55.351l45.826-54.038 3.8214 3.2697c17.83 15.256 22.538 19.151 29.616 24.501 48.673 36.79 103.35 61.169 158.92 70.862 18.387 3.2073 54.666 4.419 74.088 2.4745 41.751-4.1802 74.798-17.199 96.864-38.16 10.213-9.7012 15.896-17.429 21.626-29.408 17.4-36.376 13.152-81.77-10.39-111-16.357-20.31-45.054-37.907-98.696-60.521-41.654-17.56-164.15-71.537-176.19-77.638-85.541-43.335-134.63-104.27-148.9-184.84-2.6851-15.162-3.7276-49.931-1.9989-66.666 7.4631-72.25 48.261-136.63 113.09-178.46 41.81-26.976 88.546-43.103 144.99-50.03 20.52-2.5182 67.722-2.5268 88-.016 74.352 9.2063 141.74 36.296 199 79.999 18.772 14.327 37.632 31.435 36.864 33.44-.2001.52235-18.812 23.693-41.361 51.49l-40.997 50.54-3.503-2.9264c-1.9267-1.6095-9.4625-7.4505-16.746-12.98-44.158-33.522-88.429-52.307-140.26-59.513-17.665-2.4562-54.274-2.4782-70-.042-35.82 5.5488-61.303 16.869-80.113 35.588-17.506 17.422-26.238 37.587-27.528 63.576-1.3118 26.419 6.521 48.306 24.066 67.249 17.834 19.254 45.314 35.115 99.448 57.398 32.211 13.259 137.3 57.517 151.65 63.864 47.003 20.795 80.577 42.726 108.49 70.87 43.959 44.316 64.938 98.562 65.021 168.13.053 44.646-7.8058 78.816-26.734 116.23-12.46 24.632-27.741 45.114-49.45 66.28-51.458 50.172-122.59 79.937-208.86 87.392-17.502 1.5126-51.786 2.0335-67.962 1.0326z"/>
|
||||
<path d="m-155.84-128.44c-100.7-5.7557-190.26-44.562-257.1-111.4-58.171-58.171-98.098-136.72-116.41-229.01-13.522-68.153-15.549-148.4-5.5195-218.5 13.11-91.624 47.506-173.73 99.29-237 11.342-13.858 35.64-38.591 49.282-50.164 54.726-46.425 120.9-76.546 193.88-88.256 25.873-4.1511 37.999-5.0552 67.977-5.0681 28.858-.013 38.31.6981 60.5 4.5485 70.566 12.245 140.29 49.396 192.89 102.78l6.8911 6.9936-2.8911 3.4607c-1.59 1.9034-21.52 24.408-44.288 50.011l-41.397 46.551-10.103-9.0797c-40.998-36.846-79.308-56.146-125.89-63.421-13.826-2.1591-48.594-2.4422-62.711-.51067-51.945 7.1074-94.856 27.696-131.17 62.933-64.806 62.887-97.854 165.12-92.829 287.16 2.697 65.505 14.091 119.1 35.16 165.38 30.027 65.96 77.365 110.94 138.03 131.16 24.572 8.1885 46.583 11.525 76.026 11.525 45.839 0 83.431-9.665 120.81-31.062 19.559-11.195 45.837-32.314 63.267-50.848 3.7379-3.9745 7.1554-7.0833 7.5942-6.9085 1.3142.5236 88.109 97.158 88.109 98.098 0 2.0843-41.684 42.322-54 52.126-73.043 58.146-157.48 84.1-255.41 78.503z"/>
|
||||
<path d="m610.07-1067.8c-34.898-.056-47.464.862-75.232 5.4922-188.34 31.405-308.9 182.45-325.21 407.46-2.8044 38.675-2.2536 84.125 1.4941 123.38 9.2582 96.975 39.751 184.31 87.494 250.58 57.015 79.142 139.29 130.29 236.46 147 14.533 2.4988 40.496 5.3373 53.5 5.8496 147.12 5.7956 267.7-55.193 342.98-173.48 10.897-17.122 28.991-52.974 36.758-72.828 27.4-70.046 39.498-139.21 39.617-226.5.062-45.479-1.9339-73.343-7.9121-110.4-31.164-193.18-145.75-321-314.25-350.53-27.838-4.8789-41.445-5.9606-75.699-6.0156zm-1.4395 139.59c2.8062.0114 5.6199.0752 8.4395.19336 49.33 2.0671 91.449 18.361 127.46 49.305 12.954 11.133 20.363 19.102 31.482 33.861 40.99 54.409 62.709 125.93 66.582 219.25 4.5628 109.93-19.826 208.09-67.676 272.39-33.936 45.599-76.643 72.514-130.84 82.459-10.577 1.9408-50.92 2.8029-62 1.3242-74.694-9.9681-131.62-54.014-168.58-130.43-24.356-50.365-36.989-106.85-39.92-178.5-5.9652-145.81 37.791-262.31 118.61-315.79 33.933-22.452 74.357-34.245 116.45-34.074z"/>
|
||||
<path d="m1187.6-1051.4v453.54 453.54h80.5 80.5v-177.51-177.51l68.717.25585 68.719.25782 97.531 177.22 97.533 177.22 90.285.0273c85.686.0268 90.237-.0599 89.336-1.7207-.5222-.9625-49.147-86.08-108.05-189.15-58.906-103.07-106.98-187.52-106.83-187.67.1497-.14971 5.5455-2.31 11.99-4.8008 92.947-35.923 149.28-103.8 164.7-198.43 3.4973-21.47 4.3763-36.845 3.7539-65.688-.8444-39.124-4.5518-62.293-14.883-93.008-29.696-88.286-106.44-143.03-224.91-160.44-38.597-5.6719-28.81-5.4157-221.14-5.7871l-177.75-.3438zm161 128.95 84.25.37695c91.298.40795 95.375.61732 123.75 6.3809 23.495 4.7723 45.38 13.215 61 23.533 15.167 10.019 29.716 27.182 37.475 44.207 14.573 31.978 16.395 82.735 4.3301 120.62-6.6274 20.814-16.172 36.615-31.18 51.625-27.567 27.57-66.814 42.804-121.93 47.324-7.3903.60617-43.437 1.0508-85.25 1.0508h-72.445v-147.56-147.56z"/>
|
||||
<path d="m2014.6-1051.4v453.6 453.6l145.75-.37695c156.69-.4046 153.13-.29648 191.25-5.8008 38.321-5.5332 77.017-15.82 109.08-28.998 17.362-7.137 22.208-9.743 21.508-11.566-.3206-.8355-1.452-4.9721-2.5156-9.1914-3.4865-13.831-4.3718-23.482-3.7617-41.053.63-18.145 2.2913-27.3 7.7285-42.617 17.594-49.562 60.836-85.599 112.95-94.131 16.457-2.6941 38.955-1.8474 57.701 2.1719 3.6928.79178 3.1565 1.7476 11.26-20.041 27.066-72.775 38.169-169.68 30.476-265.97-14.239-178.25-95.276-299.81-236.97-355.47-33.122-13.01-69.539-22.404-108.45-27.975-38.756-5.5479-34.464-5.4161-190.75-5.8086l-145.25-.3652zm161 130.09 41.75.0156c55.334.0205 78.397 1.6295 108.25 7.5566 105.75 20.995 171.57 87.554 196.39 198.59 12.878 57.6 14.716 139.6 4.5469 202.81-7.3952 45.963-21.469 87.286-40.711 119.53-12.041 20.179-33.82 45.681-51 59.719-38.627 31.563-87.98 50.255-148.73 56.326-9.5463.9541-32.361 1.7291-62.75 2.1328l-47.75.63477v-323.66-323.66z"/>
|
||||
</g>
|
||||
<circle id="logo-dot" cx="2575.3" cy="939.96" r="125.4" fill="#499a6c"/>
|
||||
<g id="logo-js" fill="#33b5e5" transform="translate(-44.194 1175.6)">
|
||||
<path d="m2602.1 34.57c-57.094-4.6075-113.49-28.558-158.26-67.213-27.741-23.949-51.228-55.235-63.883-85.094-5.4804-12.93-5.926-15.992-2.3882-16.406 8.1404-.953 38.073-7.05 53.318-10.86 20.337-5.0831 29.827-8.2686 48.112-16.15 12.138-5.2318 12.996-5.46 14-3.7198 14.778 25.613 36.757 46.236 62.906 59.024 21.609 10.567 39.696 14.761 63.664 14.761 23.073 0 41.694-4.1466 61.73-13.746 36.584-17.528 62.542-46.884 75.844-85.772 2.3995-7.0151 7.5664-31.714 9.361-44.747 2.8753-20.881 3.0454-40.134 3.0555-345.75l.01-314.25h78 78v318.25c0 209.58-.3574 323.03-1.0389 332.25-4.4405 60.076-22.061 115.17-51.016 159.5-11.306 17.311-21.135 29.375-35.857 44.012-44.122 43.866-101.51 69.204-169.58 74.876-17.815 1.4842-53.463 2.0433-65.964 1.0344z"/>
|
||||
<path d="m3256.6 33.535c-103.92-8.2588-202.14-50.771-278.59-120.57l-11.459-10.464 4.7737-5.6963c2.6255-3.133 23.371-27.615 46.101-54.405l41.327-48.709 11.068 9.6086c54.856 47.624 120.13 79.074 185.78 89.508 19.275 3.0634 60.816 3.3389 79 .5237 56.007-8.6707 91.978-30.946 109.48-67.793 5.7814-12.174 8.6772-25.17 9.2639-41.574 1.8511-51.755-20.009-81.836-81.241-111.79-10.45-5.1123-25.75-12.128-34-15.591-32.568-13.67-168.23-73.282-178.56-78.459-84.895-42.577-136.19-105.76-149.34-183.97-24.654-146.62 80.068-271.29 246.91-293.93 39.105-5.3065 82.999-4.2183 122.48 3.0365 76.174 13.996 145.21 48.561 201.87 101.07l7.367 6.8275-39.699 49c-21.834 26.95-40.537 49.863-41.563 50.918-1.8327 1.8856-1.9536 1.8424-7.1685-2.562-25.013-21.126-59.394-41.952-87.804-53.188-33.742-13.345-63.677-18.968-101.5-19.066-28.062-.0727-45.321 2.2-65.5 8.6248-40.117 12.773-65.445 37.309-74.612 72.282-3.4331 13.097-3.8978 33.664-1.0368 45.883 7.6067 32.488 29.949 55.7 75.674 78.622 15.123 7.5809 24.021 11.522 52.974 23.46 125.45 51.728 173.58 73.274 198.67 88.935 70.314 43.888 106.41 97.76 116.97 174.59 2.1563 15.683 2.4444 55.002.5056 69-7.9359 57.297-31.186 104.9-70.626 144.6-53.439 53.792-126.37 84.242-218.91 91.402-14.98 1.1588-53.385 1.0944-68.605-.1152z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.2 KiB |
60
package.json
60
package.json
@@ -3,9 +3,13 @@
|
||||
"version": "10.0.1",
|
||||
"description": "A powerful library for interacting with the Discord API",
|
||||
"main": "./src/index",
|
||||
"types": "./typings/index.d.ts",
|
||||
"scripts": {
|
||||
"test": "eslint src/ && node docs/generator/generator.js silent",
|
||||
"docs": "node docs/generator/generator.js"
|
||||
"test": "eslint src && docgen --source src --custom docs/index.yml",
|
||||
"docs": "docgen --source src --custom docs/index.yml --output docs/docs.json",
|
||||
"test-docs": "docgen --source src --custom docs",
|
||||
"lint": "eslint src",
|
||||
"web-dist": "node ./node_modules/parallel-webpack/bin/run.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -25,21 +29,57 @@
|
||||
"url": "https://github.com/hydrabolt/discord.js/issues"
|
||||
},
|
||||
"homepage": "https://github.com/hydrabolt/discord.js#readme",
|
||||
"runkitExampleFilename": "./docs/examples/ping.js",
|
||||
"dependencies": {
|
||||
"superagent": "^2.3.0",
|
||||
"tweetnacl": "^0.14.3",
|
||||
"ws": "^1.1.1"
|
||||
"@types/node": "^6.0.0",
|
||||
"pako": "^1.0.0",
|
||||
"superagent": "^3.3.0",
|
||||
"tweetnacl": "^0.14.0",
|
||||
"ws": "^1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"node-opus": "^0.2.1",
|
||||
"opusscript": "^0.0.1"
|
||||
"erlpack": "hammerandchisel/erlpack#master",
|
||||
"node-opus": "^0.2.0",
|
||||
"opusscript": "^0.0.1",
|
||||
"uws": "^0.12.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^3.8.0",
|
||||
"fs-extra": "^0.30.0",
|
||||
"jsdoc-to-markdown": "^2.0.0"
|
||||
"discord.js-docgen": "hydrabolt/discord.js-docgen#master",
|
||||
"eslint": "^3.12.0",
|
||||
"parallel-webpack": "^1.6.0",
|
||||
"uglify-js": "mishoo/UglifyJS2#harmony",
|
||||
"webpack": "2.2.0-rc.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
},
|
||||
"browser": {
|
||||
"ws": false,
|
||||
"uws": false,
|
||||
"erlpack": false,
|
||||
"opusscript": false,
|
||||
"node-opus": false,
|
||||
"tweet-nacl": false,
|
||||
"src/sharding/Shard.js": false,
|
||||
"src/sharding/ShardClientUtil.js": false,
|
||||
"src/sharding/ShardingManager.js": false,
|
||||
"src/client/voice/dispatcher/StreamDispatcher.js": false,
|
||||
"src/client/voice/opus/BaseOpusEngine.js": false,
|
||||
"src/client/voice/opus/NodeOpusEngine.js": false,
|
||||
"src/client/voice/opus/OpusEngineList.js": false,
|
||||
"src/client/voice/opus/OpusScriptEngine.js": false,
|
||||
"src/client/voice/pcm/ConverterEngine.js": false,
|
||||
"src/client/voice/pcm/ConverterEngineList.js": false,
|
||||
"src/client/voice/pcm/FfmpegConverterEngine.js": false,
|
||||
"src/client/voice/player/AudioPlayer.js": false,
|
||||
"src/client/voice/player/BasePlayer.js": false,
|
||||
"src/client/voice/player/DefaultPlayer.js": false,
|
||||
"src/client/voice/receiver/VoiceReadable.js": false,
|
||||
"src/client/voice/receiver/VoiceReceiver.js": false,
|
||||
"src/client/voice/util/SecretKey.js": false,
|
||||
"src/client/voice/ClientVoiceManager.js": false,
|
||||
"src/client/voice/VoiceConnection.js": false,
|
||||
"src/client/voice/VoiceUDPClient.js": false,
|
||||
"src/client/voice/VoiceWebSocket.js": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,39 +77,39 @@ class Client extends EventEmitter {
|
||||
this.actions = new ActionsManager(this);
|
||||
|
||||
/**
|
||||
* The Voice Manager of the Client
|
||||
* @type {ClientVoiceManager}
|
||||
* The Voice Manager of the Client (`null` in browsers)
|
||||
* @type {?ClientVoiceManager}
|
||||
* @private
|
||||
*/
|
||||
this.voice = new ClientVoiceManager(this);
|
||||
this.voice = !this.browser ? new ClientVoiceManager(this) : null;
|
||||
|
||||
/**
|
||||
* The shard helpers for the client (only if the process was spawned as a child, such as from a ShardingManager)
|
||||
* @type {?ShardUtil}
|
||||
* @type {?ShardClientUtil}
|
||||
*/
|
||||
this.shard = process.send ? ShardClientUtil.singleton(this) : null;
|
||||
|
||||
/**
|
||||
* A Collection of the Client's stored users
|
||||
* A collection of the Client's stored users
|
||||
* @type {Collection<string, User>}
|
||||
*/
|
||||
this.users = new Collection();
|
||||
|
||||
/**
|
||||
* A Collection of the Client's stored guilds
|
||||
* A collection of the Client's stored guilds
|
||||
* @type {Collection<string, Guild>}
|
||||
*/
|
||||
this.guilds = new Collection();
|
||||
|
||||
/**
|
||||
* A Collection of the Client's stored channels
|
||||
* A collection of the Client's stored channels
|
||||
* @type {Collection<string, Channel>}
|
||||
*/
|
||||
this.channels = new Collection();
|
||||
|
||||
/**
|
||||
* A Collection of presences for friends of the logged in user.
|
||||
* <warn>This is only present for user accounts, not bot accounts!</warn>
|
||||
* A collection of presences for friends of the logged in user.
|
||||
* <warn>This is only filled when using a user account.</warn>
|
||||
* @type {Collection<string, Presence>}
|
||||
*/
|
||||
this.presences = new Collection();
|
||||
@@ -124,18 +124,6 @@ class Client extends EventEmitter {
|
||||
this.token = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The email, if there is one, for the logged in Client
|
||||
* @type {?string}
|
||||
*/
|
||||
this.email = null;
|
||||
|
||||
/**
|
||||
* The password, if there is one, for the logged in Client
|
||||
* @type {?string}
|
||||
*/
|
||||
this.password = null;
|
||||
|
||||
/**
|
||||
* The ClientUser representing the logged in Client
|
||||
* @type {?ClientUser}
|
||||
@@ -148,6 +136,13 @@ class Client extends EventEmitter {
|
||||
*/
|
||||
this.readyAt = null;
|
||||
|
||||
/**
|
||||
* The previous heartbeat pings of the websocket (most recent first, limited to three elements)
|
||||
* @type {number[]}
|
||||
*/
|
||||
this.pings = [];
|
||||
|
||||
this._pingTimestamp = 0;
|
||||
this._timeouts = new Set();
|
||||
this._intervals = new Set();
|
||||
|
||||
@@ -175,11 +170,21 @@ class Client extends EventEmitter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Collection, mapping Guild ID to Voice Connections.
|
||||
* The average heartbeat ping of the websocket
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
get ping() {
|
||||
return this.pings.reduce((prev, p) => prev + p, 0) / this.pings.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a collection, mapping guild ID to voice connections.
|
||||
* @type {Collection<string, VoiceConnection>}
|
||||
* @readonly
|
||||
*/
|
||||
get voiceConnections() {
|
||||
if (this.browser) return new Collection();
|
||||
return this.voice.connections;
|
||||
}
|
||||
|
||||
@@ -205,14 +210,21 @@ class Client extends EventEmitter {
|
||||
return this.readyAt ? this.readyAt.getTime() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the client is in a browser environment
|
||||
* @type {boolean}
|
||||
* @readonly
|
||||
*/
|
||||
get browser() {
|
||||
return typeof window !== 'undefined';
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the client in. If successful, resolves with the account's token. <warn>If you're making a bot, it's
|
||||
* much better to use a bot account rather than a user account.
|
||||
* Bot accounts have higher rate limits and have access to some features user accounts don't have. User bots
|
||||
* that are making a lot of API requests can even be banned.</warn>
|
||||
* @param {string} tokenOrEmail The token or email used for the account. If it is an email, a password _must_ be
|
||||
* provided.
|
||||
* @param {string} [password] The password for the account, only needed if an email was provided.
|
||||
* @param {string} token The token used for the account.
|
||||
* @returns {Promise<string>}
|
||||
* @example
|
||||
* // log the client in using a token
|
||||
@@ -224,9 +236,8 @@ class Client extends EventEmitter {
|
||||
* const password = 'supersecret123';
|
||||
* client.login(email, password);
|
||||
*/
|
||||
login(tokenOrEmail, password = null) {
|
||||
if (password) return this.rest.methods.loginEmailPassword(tokenOrEmail, password);
|
||||
return this.rest.methods.loginToken(tokenOrEmail);
|
||||
login(token) {
|
||||
return this.rest.methods.login(token);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -238,29 +249,26 @@ class Client extends EventEmitter {
|
||||
for (const i of this._intervals) clearInterval(i);
|
||||
this._timeouts.clear();
|
||||
this._intervals.clear();
|
||||
this.token = null;
|
||||
this.email = null;
|
||||
this.password = null;
|
||||
return this.manager.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* This shouldn't really be necessary to most developers as it is automatically invoked every 30 seconds, however
|
||||
* if you wish to force a sync of Guild data, you can use this. Only applicable to user accounts.
|
||||
* if you wish to force a sync of guild data, you can use this.
|
||||
* <warn>This is only available when using a user account.</warn>
|
||||
* @param {Guild[]|Collection<string, Guild>} [guilds=this.guilds] An array or collection of guilds to sync
|
||||
*/
|
||||
syncGuilds(guilds = this.guilds) {
|
||||
if (!this.user.bot) {
|
||||
this.ws.send({
|
||||
op: 12,
|
||||
d: guilds instanceof Collection ? guilds.keyArray() : guilds.map(g => g.id),
|
||||
});
|
||||
}
|
||||
if (this.user.bot) return;
|
||||
this.ws.send({
|
||||
op: 12,
|
||||
d: guilds instanceof Collection ? guilds.keyArray() : guilds.map(g => g.id),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Caches a user, or obtains it from the cache if it's already cached.
|
||||
* If the user isn't already cached, it will only be obtainable by OAuth bot accounts.
|
||||
* <warn>This is only available when using a bot account.</warn>
|
||||
* @param {string} id The ID of the user to obtain
|
||||
* @returns {Promise<User>}
|
||||
*/
|
||||
@@ -282,10 +290,11 @@ class Client extends EventEmitter {
|
||||
/**
|
||||
* Fetch a webhook by ID.
|
||||
* @param {string} id ID of the webhook
|
||||
* @param {string} [token] Token for the webhook
|
||||
* @returns {Promise<Webhook>}
|
||||
*/
|
||||
fetchWebhook(id) {
|
||||
return this.rest.methods.getWebhook(id);
|
||||
fetchWebhook(id, token) {
|
||||
return this.rest.methods.getWebhook(id, token);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -324,31 +333,90 @@ class Client extends EventEmitter {
|
||||
return messages;
|
||||
}
|
||||
|
||||
setTimeout(fn, ...params) {
|
||||
/**
|
||||
* Gets the bot's OAuth2 application.
|
||||
* <warn>This is only available when using a bot account.</warn>
|
||||
* @returns {Promise<ClientOAuth2Application>}
|
||||
*/
|
||||
fetchApplication() {
|
||||
if (!this.user.bot) throw new Error(Constants.Errors.NO_BOT_ACCOUNT);
|
||||
return this.rest.methods.getMyApplication();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an invite link for your bot
|
||||
* @param {PermissionResolvable[]|number} [permissions] An array of permissions to request
|
||||
* @returns {Promise<string>} The invite link
|
||||
* @example
|
||||
* client.generateInvite(['SEND_MESSAGES', 'MANAGE_GUILD', 'MENTION_EVERYONE'])
|
||||
* .then(link => {
|
||||
* console.log(`Generated bot invite link: ${link}`);
|
||||
* });
|
||||
*/
|
||||
generateInvite(permissions) {
|
||||
if (permissions) {
|
||||
if (permissions instanceof Array) permissions = this.resolver.resolvePermissions(permissions);
|
||||
} else {
|
||||
permissions = 0;
|
||||
}
|
||||
return this.fetchApplication().then(application =>
|
||||
`https://discordapp.com/oauth2/authorize?client_id=${application.id}&permissions=${permissions}&scope=bot`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a timeout that will be automatically cancelled if the client is destroyed.
|
||||
* @param {Function} fn Function to execute
|
||||
* @param {number} delay Time to wait before executing (in milliseconds)
|
||||
* @param {...*} args Arguments for the function
|
||||
* @returns {Timeout}
|
||||
*/
|
||||
setTimeout(fn, delay, ...args) {
|
||||
const timeout = setTimeout(() => {
|
||||
fn();
|
||||
this._timeouts.delete(timeout);
|
||||
}, ...params);
|
||||
}, delay, ...args);
|
||||
this._timeouts.add(timeout);
|
||||
return timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears a timeout
|
||||
* @param {Timeout} timeout Timeout to cancel
|
||||
*/
|
||||
clearTimeout(timeout) {
|
||||
clearTimeout(timeout);
|
||||
this._timeouts.delete(timeout);
|
||||
}
|
||||
|
||||
setInterval(...params) {
|
||||
const interval = setInterval(...params);
|
||||
/**
|
||||
* Sets an interval that will be automatically cancelled if the client is destroyed.
|
||||
* @param {Function} fn Function to execute
|
||||
* @param {number} delay Time to wait before executing (in milliseconds)
|
||||
* @param {...*} args Arguments for the function
|
||||
* @returns {Timeout}
|
||||
*/
|
||||
setInterval(fn, delay, ...args) {
|
||||
const interval = setInterval(fn, delay, ...args);
|
||||
this._intervals.add(interval);
|
||||
return interval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears an interval
|
||||
* @param {Timeout} interval Interval to cancel
|
||||
*/
|
||||
clearInterval(interval) {
|
||||
clearInterval(interval);
|
||||
this._intervals.delete(interval);
|
||||
}
|
||||
|
||||
_pong(startTime) {
|
||||
this.pings.unshift(Date.now() - startTime);
|
||||
if (this.pings.length > 3) this.pings.length = 3;
|
||||
this.ws.lastHeartbeatAck = true;
|
||||
}
|
||||
|
||||
_setPresence(id, presence) {
|
||||
if (this.presences.get(id)) {
|
||||
this.presences.get(id).update(presence);
|
||||
@@ -400,11 +468,11 @@ module.exports = Client;
|
||||
/**
|
||||
* Emitted for general warnings
|
||||
* @event Client#warn
|
||||
* @param {string} The warning
|
||||
* @param {string} info The warning
|
||||
*/
|
||||
|
||||
/**
|
||||
* Emitted for general debugging information
|
||||
* @event Client#debug
|
||||
* @param {string} The debug information
|
||||
* @param {string} info The debug information
|
||||
*/
|
||||
|
||||
@@ -24,7 +24,7 @@ class ClientDataManager {
|
||||
this.client.guilds.set(guild.id, guild);
|
||||
if (this.pastReady && !already) {
|
||||
/**
|
||||
* Emitted whenever the client joins a Guild.
|
||||
* Emitted whenever the client joins a guild.
|
||||
* @event Client#guildCreate
|
||||
* @param {Guild} guild The created guild
|
||||
*/
|
||||
@@ -78,7 +78,7 @@ class ClientDataManager {
|
||||
const already = guild.emojis.has(data.id);
|
||||
if (data && !already) {
|
||||
let emoji = new Emoji(guild, data);
|
||||
this.client.emit(Constants.Events.EMOJI_CREATE, emoji);
|
||||
this.client.emit(Constants.Events.GUILD_EMOJI_CREATE, emoji);
|
||||
guild.emojis.set(emoji.id, emoji);
|
||||
return emoji;
|
||||
} else if (already) {
|
||||
@@ -90,7 +90,7 @@ class ClientDataManager {
|
||||
|
||||
killEmoji(emoji) {
|
||||
if (!(emoji instanceof Emoji && emoji.guild)) return;
|
||||
this.client.emit(Constants.Events.EMOJI_DELETE, emoji);
|
||||
this.client.emit(Constants.Events.GUILD_EMOJI_DELETE, emoji);
|
||||
emoji.guild.emojis.delete(emoji.id);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,14 @@ const fs = require('fs');
|
||||
const request = require('superagent');
|
||||
|
||||
const Constants = require('../util/Constants');
|
||||
const User = require(`../structures/User`);
|
||||
const Message = require(`../structures/Message`);
|
||||
const Guild = require(`../structures/Guild`);
|
||||
const Channel = require(`../structures/Channel`);
|
||||
const GuildMember = require(`../structures/GuildMember`);
|
||||
const convertArrayBuffer = require('../util/ConvertArrayBuffer');
|
||||
const User = require('../structures/User');
|
||||
const Message = require('../structures/Message');
|
||||
const Guild = require('../structures/Guild');
|
||||
const Channel = require('../structures/Channel');
|
||||
const GuildMember = require('../structures/GuildMember');
|
||||
const Emoji = require('../structures/Emoji');
|
||||
const ReactionEmoji = require('../structures/ReactionEmoji');
|
||||
|
||||
/**
|
||||
* The DataResolver identifies different objects and tries to resolve a specific piece of information from them, e.g.
|
||||
@@ -25,10 +28,10 @@ class ClientDataResolver {
|
||||
/**
|
||||
* Data that resolves to give a User object. This can be:
|
||||
* * A User object
|
||||
* * A User ID
|
||||
* * A Message (resolves to the message author)
|
||||
* * A Guild (owner of the guild)
|
||||
* * A Guild Member
|
||||
* * A user ID
|
||||
* * A Message object (resolves to the message author)
|
||||
* * A Guild object (owner of the guild)
|
||||
* * A GuildMember object
|
||||
* @typedef {User|string|Message|Guild|GuildMember} UserResolvable
|
||||
*/
|
||||
|
||||
@@ -62,7 +65,8 @@ class ClientDataResolver {
|
||||
/**
|
||||
* Data that resolves to give a Guild object. This can be:
|
||||
* * A Guild object
|
||||
* @typedef {Guild} GuildResolvable
|
||||
* * A Guild ID
|
||||
* @typedef {Guild|string} GuildResolvable
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -91,20 +95,18 @@ class ClientDataResolver {
|
||||
*/
|
||||
resolveGuildMember(guild, user) {
|
||||
if (user instanceof GuildMember) return user;
|
||||
|
||||
guild = this.resolveGuild(guild);
|
||||
user = this.resolveUser(user);
|
||||
if (!guild || !user) return null;
|
||||
|
||||
return guild.members.get(user.id) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Data that can be resolved to give a Channel. This can be:
|
||||
* * An instance of a Channel
|
||||
* * An instance of a Message (the channel the message was sent in)
|
||||
* * An instance of a Guild (the #general channel)
|
||||
* * An ID of a Channel
|
||||
* * A Channel object
|
||||
* * A Message object (the channel the message was sent in)
|
||||
* * A Guild object (the #general channel)
|
||||
* * A channel ID
|
||||
* @typedef {Channel|Guild|Message|string} ChannelResolvable
|
||||
*/
|
||||
|
||||
@@ -136,7 +138,6 @@ class ClientDataResolver {
|
||||
resolveInviteCode(data) {
|
||||
const inviteRegex = /discord(?:app)?\.(?:gg|com\/invite)\/([a-z0-9]{5})/i;
|
||||
const match = inviteRegex.exec(data);
|
||||
|
||||
if (match && match[1]) return match[1];
|
||||
return data;
|
||||
}
|
||||
@@ -155,6 +156,7 @@ class ClientDataResolver {
|
||||
* "ADMINISTRATOR",
|
||||
* "MANAGE_CHANNELS",
|
||||
* "MANAGE_GUILD",
|
||||
* "ADD_REACTIONS", // add reactions to messages
|
||||
* "READ_MESSAGES",
|
||||
* "SEND_MESSAGES",
|
||||
* "SEND_TTS_MESSAGES",
|
||||
@@ -172,7 +174,9 @@ class ClientDataResolver {
|
||||
* "USE_VAD", // use voice activity detection
|
||||
* "CHANGE_NICKNAME",
|
||||
* "MANAGE_NICKNAMES", // change nicknames of others
|
||||
* "MANAGE_ROLES_OR_PERMISSIONS"
|
||||
* "MANAGE_ROLES_OR_PERMISSIONS",
|
||||
* "MANAGE_WEBHOOKS",
|
||||
* "MANAGE_EMOJIS"
|
||||
* ]
|
||||
* ```
|
||||
* @typedef {string|number} PermissionResolvable
|
||||
@@ -189,10 +193,21 @@ class ClientDataResolver {
|
||||
return permission;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn an array of permissions into a valid Discord permission bitfield
|
||||
* @param {PermissionResolvable[]} permissions Permissions to resolve together
|
||||
* @returns {number}
|
||||
*/
|
||||
resolvePermissions(permissions) {
|
||||
let bitfield = 0;
|
||||
for (const permission of permissions) bitfield |= this.resolvePermission(permission);
|
||||
return bitfield;
|
||||
}
|
||||
|
||||
/**
|
||||
* Data that can be resolved to give a string. This can be:
|
||||
* * A string
|
||||
* * An Array (joined with a new line delimiter to give a string)
|
||||
* * An array (joined with a new line delimiter to give a string)
|
||||
* * Any value
|
||||
* @typedef {string|Array|*} StringResolvable
|
||||
*/
|
||||
@@ -211,7 +226,7 @@ class ClientDataResolver {
|
||||
/**
|
||||
* Data that resolves to give a Base64 string, typically for image uploading. This can be:
|
||||
* * A Buffer
|
||||
* * A Base64 string
|
||||
* * A base64 string
|
||||
* @typedef {Buffer|string} Base64Resolvable
|
||||
*/
|
||||
|
||||
@@ -230,21 +245,29 @@ class ClientDataResolver {
|
||||
* * A Buffer
|
||||
* * The path to a local file
|
||||
* * A URL
|
||||
* @typedef {string|Buffer} FileResolvable
|
||||
* @typedef {string|Buffer} BufferResolvable
|
||||
*/
|
||||
|
||||
/**
|
||||
* Resolves a FileResolvable to a Buffer
|
||||
* @param {FileResolvable} resource The file resolvable to resolve
|
||||
* Resolves a BufferResolvable to a Buffer
|
||||
* @param {BufferResolvable} resource The buffer resolvable to resolve
|
||||
* @returns {Promise<Buffer>}
|
||||
*/
|
||||
resolveFile(resource) {
|
||||
resolveBuffer(resource) {
|
||||
if (resource instanceof Buffer) return Promise.resolve(resource);
|
||||
if (this.client.browser && resource instanceof ArrayBuffer) return Promise.resolve(convertArrayBuffer(resource));
|
||||
|
||||
if (typeof resource === 'string') {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (/^https?:\/\//.test(resource)) {
|
||||
request.get(resource)
|
||||
.set('Content-Type', 'blob')
|
||||
.end((err, res) => err ? reject(err) : resolve(res.body));
|
||||
const req = request.get(resource).set('Content-Type', 'blob');
|
||||
if (this.client.browser) req.responseType('arraybuffer');
|
||||
req.end((err, res) => {
|
||||
if (err) return reject(err);
|
||||
if (this.client.browser) return resolve(convertArrayBuffer(res.xhr.response));
|
||||
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) => {
|
||||
@@ -258,8 +281,28 @@ class ClientDataResolver {
|
||||
});
|
||||
}
|
||||
|
||||
if (resource instanceof Buffer) return Promise.resolve(resource);
|
||||
return Promise.reject(new TypeError('Resource must be a string or Buffer.'));
|
||||
return Promise.reject(new TypeError('The resource must be a string or Buffer.'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data that can be resolved to give an emoji identifier. This can be:
|
||||
* * A string
|
||||
* * An Emoji
|
||||
* * A ReactionEmoji
|
||||
* @typedef {string|Emoji|ReactionEmoji} EmojiIdentifierResolvable
|
||||
*/
|
||||
|
||||
/**
|
||||
* Resolves an EmojiResolvable to an emoji identifier
|
||||
* @param {EmojiIdentifierResolvable} emoji The emoji resolvable to resolve
|
||||
* @returns {string}
|
||||
*/
|
||||
resolveEmojiIdentifier(emoji) {
|
||||
if (emoji instanceof Emoji || emoji instanceof ReactionEmoji) return emoji.identifier;
|
||||
if (typeof emoji === 'string') {
|
||||
if (!emoji.includes('%')) return encodeURIComponent(emoji);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,8 +22,8 @@ class ClientManager {
|
||||
/**
|
||||
* Connects the Client to the WebSocket
|
||||
* @param {string} token The authorization token
|
||||
* @param {function} resolve Function to run when connection is successful
|
||||
* @param {function} reject Function to run when connection fails
|
||||
* @param {Function} resolve Function to run when connection is successful
|
||||
* @param {Function} reject Function to run when connection fails
|
||||
*/
|
||||
connectToWebSocket(token, resolve, reject) {
|
||||
this.client.emit(Constants.Events.DEBUG, `Authenticated using token ${token}`);
|
||||
@@ -40,7 +40,7 @@ class ClientManager {
|
||||
resolve(token);
|
||||
this.client.clearTimeout(timeout);
|
||||
});
|
||||
}).catch(reject);
|
||||
}, reject);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -48,24 +48,19 @@ class ClientManager {
|
||||
* @param {number} time The interval in milliseconds at which heartbeat packets should be sent
|
||||
*/
|
||||
setupKeepAlive(time) {
|
||||
this.heartbeatInterval = this.client.setInterval(() => {
|
||||
this.client.emit('debug', 'Sending heartbeat');
|
||||
this.client.ws.send({
|
||||
op: Constants.OPCodes.HEARTBEAT,
|
||||
d: this.client.ws.sequence,
|
||||
}, true);
|
||||
}, time);
|
||||
this.heartbeatInterval = this.client.setInterval(() => this.client.ws.heartbeat(true), time);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.client.ws.destroy();
|
||||
if (!this.client.user.bot) {
|
||||
this.client.rest.methods.logout().then(resolve, reject);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
this.client.ws.destroy();
|
||||
if (this.client.user.bot) {
|
||||
this.client.token = null;
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
return this.client.rest.methods.logout().then(() => {
|
||||
this.client.token = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,33 +2,36 @@ class ActionsManager {
|
||||
constructor(client) {
|
||||
this.client = client;
|
||||
|
||||
this.register('MessageCreate');
|
||||
this.register('MessageDelete');
|
||||
this.register('MessageDeleteBulk');
|
||||
this.register('MessageUpdate');
|
||||
this.register('ChannelCreate');
|
||||
this.register('ChannelDelete');
|
||||
this.register('ChannelUpdate');
|
||||
this.register('GuildDelete');
|
||||
this.register('GuildUpdate');
|
||||
this.register('GuildMemberGet');
|
||||
this.register('GuildMemberRemove');
|
||||
this.register('GuildBanRemove');
|
||||
this.register('GuildRoleCreate');
|
||||
this.register('GuildRoleDelete');
|
||||
this.register('GuildRoleUpdate');
|
||||
this.register('UserGet');
|
||||
this.register('UserUpdate');
|
||||
this.register('GuildSync');
|
||||
this.register('GuildEmojiCreate');
|
||||
this.register('GuildEmojiDelete');
|
||||
this.register('GuildEmojiUpdate');
|
||||
this.register('GuildRolesPositionUpdate');
|
||||
this.register(require('./MessageCreate'));
|
||||
this.register(require('./MessageDelete'));
|
||||
this.register(require('./MessageDeleteBulk'));
|
||||
this.register(require('./MessageUpdate'));
|
||||
this.register(require('./MessageReactionAdd'));
|
||||
this.register(require('./MessageReactionRemove'));
|
||||
this.register(require('./MessageReactionRemoveAll'));
|
||||
this.register(require('./ChannelCreate'));
|
||||
this.register(require('./ChannelDelete'));
|
||||
this.register(require('./ChannelUpdate'));
|
||||
this.register(require('./GuildDelete'));
|
||||
this.register(require('./GuildUpdate'));
|
||||
this.register(require('./GuildMemberGet'));
|
||||
this.register(require('./GuildMemberRemove'));
|
||||
this.register(require('./GuildBanRemove'));
|
||||
this.register(require('./GuildRoleCreate'));
|
||||
this.register(require('./GuildRoleDelete'));
|
||||
this.register(require('./GuildRoleUpdate'));
|
||||
this.register(require('./UserGet'));
|
||||
this.register(require('./UserUpdate'));
|
||||
this.register(require('./UserNoteUpdate'));
|
||||
this.register(require('./GuildSync'));
|
||||
this.register(require('./GuildEmojiCreate'));
|
||||
this.register(require('./GuildEmojiDelete'));
|
||||
this.register(require('./GuildEmojiUpdate'));
|
||||
this.register(require('./GuildRolesPositionUpdate'));
|
||||
}
|
||||
|
||||
register(name) {
|
||||
const Action = require(`./${name}`);
|
||||
this[name] = new Action(this.client);
|
||||
register(Action) {
|
||||
this[Action.name.replace(/Action$/, '')] = new Action(this.client);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
const Action = require('./Action');
|
||||
|
||||
class EmojiCreateAction extends Action {
|
||||
handle(data, guild) {
|
||||
class GuildEmojiCreateAction extends Action {
|
||||
handle(guild, createdEmoji) {
|
||||
const client = this.client;
|
||||
const emoji = client.dataManager.newEmoji(data, guild);
|
||||
const emoji = client.dataManager.newEmoji(createdEmoji, guild);
|
||||
return {
|
||||
emoji,
|
||||
};
|
||||
@@ -11,8 +11,8 @@ class EmojiCreateAction extends Action {
|
||||
}
|
||||
|
||||
/**
|
||||
* Emitted whenever an emoji is created
|
||||
* @event Client#guildEmojiCreate
|
||||
* Emitted whenever a custom emoji is created in a guild
|
||||
* @event Client#emojiCreate
|
||||
* @param {Emoji} emoji The emoji that was created.
|
||||
*/
|
||||
module.exports = EmojiCreateAction;
|
||||
module.exports = GuildEmojiCreateAction;
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
const Action = require('./Action');
|
||||
|
||||
class EmojiDeleteAction extends Action {
|
||||
handle(data) {
|
||||
class GuildEmojiDeleteAction extends Action {
|
||||
handle(emoji) {
|
||||
const client = this.client;
|
||||
client.dataManager.killEmoji(data);
|
||||
client.dataManager.killEmoji(emoji);
|
||||
return {
|
||||
data,
|
||||
emoji,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emitted whenever an emoji is deleted
|
||||
* @event Client#guildEmojiDelete
|
||||
* Emitted whenever a custom guild emoji is deleted
|
||||
* @event Client#emojiDelete
|
||||
* @param {Emoji} emoji The emoji that was deleted.
|
||||
*/
|
||||
module.exports = EmojiDeleteAction;
|
||||
module.exports = GuildEmojiDeleteAction;
|
||||
|
||||
@@ -1,28 +1,14 @@
|
||||
const Action = require('./Action');
|
||||
|
||||
class GuildEmojiUpdateAction extends Action {
|
||||
handle(data, guild) {
|
||||
const client = this.client;
|
||||
for (let emoji of data.emojis) {
|
||||
const already = guild.emojis.has(emoji.id);
|
||||
if (already) {
|
||||
client.dataManager.updateEmoji(guild.emojis.get(emoji.id), emoji);
|
||||
} else {
|
||||
emoji = client.dataManager.newEmoji(emoji, guild);
|
||||
}
|
||||
}
|
||||
for (let emoji of guild.emojis) {
|
||||
if (!data.emoijs.has(emoji.id)) client.dataManager.killEmoji(emoji);
|
||||
}
|
||||
return {
|
||||
emojis: data.emojis,
|
||||
};
|
||||
handle(oldEmoji, newEmoji) {
|
||||
this.client.dataManager.updateEmoji(oldEmoji, newEmoji);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emitted whenever an emoji is updated
|
||||
* @event Client#guildEmojiUpdate
|
||||
* Emitted whenever a custom guild emoji is updated
|
||||
* @event Client#emojiUpdate
|
||||
* @param {Emoji} oldEmoji The old emoji
|
||||
* @param {Emoji} newEmoji The new emoji
|
||||
*/
|
||||
|
||||
@@ -17,7 +17,7 @@ class GuildSync extends Action {
|
||||
if (member) {
|
||||
guild._updateMember(member, syncMember);
|
||||
} else {
|
||||
guild._addMember(syncMember);
|
||||
guild._addMember(syncMember, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,17 +6,25 @@ class MessageCreateAction extends Action {
|
||||
const client = this.client;
|
||||
|
||||
const channel = client.channels.get((data instanceof Array ? data[0] : data).channel_id);
|
||||
const user = client.users.get((data instanceof Array ? data[0] : data).author.id);
|
||||
if (channel) {
|
||||
const member = channel.guild ? channel.guild.member(user) : null;
|
||||
if (data instanceof Array) {
|
||||
const messages = new Array(data.length);
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
messages[i] = channel._cacheMessage(new Message(channel, data[i], client));
|
||||
}
|
||||
channel.lastMessageID = messages[messages.length - 1].id;
|
||||
if (user) user.lastMessageID = messages[messages.length - 1].id;
|
||||
if (member) member.lastMessageID = messages[messages.length - 1].id;
|
||||
return {
|
||||
messages,
|
||||
};
|
||||
} else {
|
||||
const message = channel._cacheMessage(new Message(channel, data, client));
|
||||
channel.lastMessageID = data.id;
|
||||
if (user) user.lastMessageID = data.id;
|
||||
if (member) member.lastMessageID = data.id;
|
||||
return {
|
||||
message,
|
||||
};
|
||||
|
||||
43
src/client/actions/MessageReactionAdd.js
Normal file
43
src/client/actions/MessageReactionAdd.js
Normal file
@@ -0,0 +1,43 @@
|
||||
const Action = require('./Action');
|
||||
const Constants = require('../../util/Constants');
|
||||
|
||||
/*
|
||||
{ user_id: 'id',
|
||||
message_id: 'id',
|
||||
emoji: { name: '<27>', id: null },
|
||||
channel_id: 'id' } }
|
||||
*/
|
||||
|
||||
class MessageReactionAdd extends Action {
|
||||
handle(data) {
|
||||
const user = this.client.users.get(data.user_id);
|
||||
if (!user) return false;
|
||||
|
||||
const channel = this.client.channels.get(data.channel_id);
|
||||
if (!channel || channel.type === 'voice') return false;
|
||||
|
||||
const message = channel.messages.get(data.message_id);
|
||||
if (!message) return false;
|
||||
|
||||
if (!data.emoji) return false;
|
||||
|
||||
const reaction = message._addReaction(data.emoji, user);
|
||||
|
||||
if (reaction) {
|
||||
this.client.emit(Constants.Events.MESSAGE_REACTION_ADD, reaction, user);
|
||||
}
|
||||
|
||||
return {
|
||||
message,
|
||||
reaction,
|
||||
user,
|
||||
};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Emitted whenever a reaction is added to a message.
|
||||
* @event Client#messageReactionAdd
|
||||
* @param {MessageReaction} messageReaction The reaction object.
|
||||
* @param {User} user The user that applied the emoji or reaction emoji.
|
||||
*/
|
||||
module.exports = MessageReactionAdd;
|
||||
43
src/client/actions/MessageReactionRemove.js
Normal file
43
src/client/actions/MessageReactionRemove.js
Normal file
@@ -0,0 +1,43 @@
|
||||
const Action = require('./Action');
|
||||
const Constants = require('../../util/Constants');
|
||||
|
||||
/*
|
||||
{ user_id: 'id',
|
||||
message_id: 'id',
|
||||
emoji: { name: '<27>', id: null },
|
||||
channel_id: 'id' } }
|
||||
*/
|
||||
|
||||
class MessageReactionRemove extends Action {
|
||||
handle(data) {
|
||||
const user = this.client.users.get(data.user_id);
|
||||
if (!user) return false;
|
||||
|
||||
const channel = this.client.channels.get(data.channel_id);
|
||||
if (!channel || channel.type === 'voice') return false;
|
||||
|
||||
const message = channel.messages.get(data.message_id);
|
||||
if (!message) return false;
|
||||
|
||||
if (!data.emoji) return false;
|
||||
|
||||
const reaction = message._removeReaction(data.emoji, user);
|
||||
|
||||
if (reaction) {
|
||||
this.client.emit(Constants.Events.MESSAGE_REACTION_REMOVE, reaction, user);
|
||||
}
|
||||
|
||||
return {
|
||||
message,
|
||||
reaction,
|
||||
user,
|
||||
};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Emitted whenever a reaction is removed from a message.
|
||||
* @event Client#messageReactionRemove
|
||||
* @param {MessageReaction} messageReaction The reaction object.
|
||||
* @param {User} user The user that removed the emoji or reaction emoji.
|
||||
*/
|
||||
module.exports = MessageReactionRemove;
|
||||
25
src/client/actions/MessageReactionRemoveAll.js
Normal file
25
src/client/actions/MessageReactionRemoveAll.js
Normal file
@@ -0,0 +1,25 @@
|
||||
const Action = require('./Action');
|
||||
const Constants = require('../../util/Constants');
|
||||
|
||||
class MessageReactionRemoveAll extends Action {
|
||||
handle(data) {
|
||||
const channel = this.client.channels.get(data.channel_id);
|
||||
if (!channel || channel.type === 'voice') return false;
|
||||
|
||||
const message = channel.messages.get(data.message_id);
|
||||
if (!message) return false;
|
||||
|
||||
message._clearReactions();
|
||||
this.client.emit(Constants.Events.MESSAGE_REACTION_REMOVE_ALL, message);
|
||||
|
||||
return {
|
||||
message,
|
||||
};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Emitted whenever all reactions are removed from a message.
|
||||
* @event Client#messageReactionRemoveAll
|
||||
* @param {MessageReaction} messageReaction The reaction object.
|
||||
*/
|
||||
module.exports = MessageReactionRemoveAll;
|
||||
30
src/client/actions/UserNoteUpdate.js
Normal file
30
src/client/actions/UserNoteUpdate.js
Normal file
@@ -0,0 +1,30 @@
|
||||
const Action = require('./Action');
|
||||
const Constants = require('../../util/Constants');
|
||||
|
||||
class UserNoteUpdateAction extends Action {
|
||||
handle(data) {
|
||||
const client = this.client;
|
||||
|
||||
const oldNote = client.user.notes.get(data.id);
|
||||
const note = data.note.length ? data.note : null;
|
||||
|
||||
client.user.notes.set(data.id, note);
|
||||
|
||||
client.emit(Constants.Events.USER_NOTE_UPDATE, data.id, oldNote, note);
|
||||
|
||||
return {
|
||||
old: oldNote,
|
||||
updated: note,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emitted whenever a note is updated.
|
||||
* @event Client#userNoteUpdate
|
||||
* @param {User} user The user the note belongs to
|
||||
* @param {string} oldNote The note content before the update
|
||||
* @param {string} newNote The note content after the update
|
||||
*/
|
||||
|
||||
module.exports = UserNoteUpdateAction;
|
||||
@@ -4,7 +4,7 @@ const Constants = require('../../util/Constants');
|
||||
function getRoute(url) {
|
||||
let route = url.split('?')[0];
|
||||
if (route.includes('/channels/') || route.includes('/guilds/')) {
|
||||
const startInd = ~route.indexOf('/channels/') ? route.indexOf('/channels/') : route.indexOf('/guilds/');
|
||||
const startInd = route.includes('/channels/') ? route.indexOf('/channels/') : route.indexOf('/guilds/');
|
||||
const majorID = route.substring(startInd).split('/')[2];
|
||||
route = route.replace(/(\d{8,})/g, ':id').replace(':id', majorID);
|
||||
}
|
||||
@@ -37,15 +37,11 @@ class APIRequest {
|
||||
if (this.file && this.file.file) {
|
||||
apiRequest.attach('file', this.file.file, this.file.name);
|
||||
this.data = this.data || {};
|
||||
for (const key in this.data) {
|
||||
if (this.data[key]) {
|
||||
apiRequest.field(key, this.data[key]);
|
||||
}
|
||||
}
|
||||
apiRequest.field('payload_json', JSON.stringify(this.data));
|
||||
} else if (this.data) {
|
||||
apiRequest.send(this.data);
|
||||
}
|
||||
apiRequest.set('User-Agent', this.rest.userAgentManager.userAgent);
|
||||
if (!this.rest.client.browser) apiRequest.set('User-Agent', this.rest.userAgentManager.userAgent);
|
||||
return apiRequest;
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -29,7 +29,9 @@ class BurstRequestHandler extends RequestHandler {
|
||||
this.requestResetTime = Number(res.headers['x-ratelimit-reset']) * 1000;
|
||||
this.requestRemaining = Number(res.headers['x-ratelimit-remaining']);
|
||||
this.timeDifference = Date.now() - new Date(res.headers.date).getTime();
|
||||
this.handleNext((this.requestResetTime - Date.now()) + this.timeDifference + 1000);
|
||||
this.handleNext(
|
||||
this.requestResetTime - Date.now() + this.timeDifference + this.restManager.client.options.restTimeOffset
|
||||
);
|
||||
}
|
||||
if (err) {
|
||||
if (err.status === 429) {
|
||||
@@ -38,10 +40,8 @@ class BurstRequestHandler extends RequestHandler {
|
||||
this.restManager.client.setTimeout(() => {
|
||||
this.globalLimit = false;
|
||||
this.handle();
|
||||
}, Number(res.headers['retry-after']) + 500);
|
||||
if (res.headers['x-ratelimit-global']) {
|
||||
this.globalLimit = true;
|
||||
}
|
||||
}, Number(res.headers['retry-after']) + this.restManager.client.options.restTimeOffset);
|
||||
if (res.headers['x-ratelimit-global']) this.globalLimit = true;
|
||||
} else {
|
||||
item.reject(err);
|
||||
}
|
||||
|
||||
@@ -60,10 +60,8 @@ class SequentialRequestHandler extends RequestHandler {
|
||||
this.waiting = false;
|
||||
this.globalLimit = false;
|
||||
resolve();
|
||||
}, Number(res.headers['retry-after']) + 500);
|
||||
if (res.headers['x-ratelimit-global']) {
|
||||
this.globalLimit = true;
|
||||
}
|
||||
}, Number(res.headers['retry-after']) + this.restManager.client.options.restTimeOffset);
|
||||
if (res.headers['x-ratelimit-global']) this.globalLimit = true;
|
||||
} else {
|
||||
this.queue.shift();
|
||||
this.waiting = false;
|
||||
@@ -76,10 +74,13 @@ class SequentialRequestHandler extends RequestHandler {
|
||||
const data = res && res.body ? res.body : {};
|
||||
item.resolve(data);
|
||||
if (this.requestRemaining === 0) {
|
||||
this.restManager.client.setTimeout(() => {
|
||||
this.waiting = false;
|
||||
resolve(data);
|
||||
}, (this.requestResetTime - Date.now()) + this.timeDifference + 1000);
|
||||
this.restManager.client.setTimeout(
|
||||
() => {
|
||||
this.waiting = false;
|
||||
resolve(data);
|
||||
},
|
||||
this.requestResetTime - Date.now() + this.timeDifference + this.restManager.client.options.restTimeOffset
|
||||
);
|
||||
} else {
|
||||
this.waiting = false;
|
||||
resolve(data);
|
||||
|
||||
@@ -23,7 +23,7 @@ class ClientVoiceManager {
|
||||
this.connections = new Collection();
|
||||
|
||||
/**
|
||||
* Pending connection attempts, maps Guild ID to VoiceChannel
|
||||
* Pending connection attempts, maps guild ID to VoiceChannel
|
||||
* @type {Collection<string, VoiceChannel>}
|
||||
*/
|
||||
this.pending = new Collection();
|
||||
@@ -55,7 +55,7 @@ class ClientVoiceManager {
|
||||
throw new Error('There is no permission set for the client user in this channel - are they part of the guild?');
|
||||
}
|
||||
if (!permissions.hasPermission('CONNECT')) {
|
||||
throw new Error('You do not have permission to connect to this voice channel.');
|
||||
throw new Error('You do not have permission to join this voice channel.');
|
||||
}
|
||||
|
||||
options = mergeDefault({
|
||||
@@ -79,10 +79,7 @@ class ClientVoiceManager {
|
||||
joinChannel(channel) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this.pending.get(channel.guild.id)) throw new Error('Already connecting to this guild\'s voice server.');
|
||||
|
||||
if (!channel.joinable) {
|
||||
throw new Error('You do not have permission to join this voice channel');
|
||||
}
|
||||
if (!channel.joinable) throw new Error('You do not have permission to join this voice channel.');
|
||||
|
||||
const existingConnection = this.connections.get(channel.guild.id);
|
||||
if (existingConnection) {
|
||||
@@ -142,7 +139,7 @@ class PendingVoiceConnection extends EventEmitter {
|
||||
|
||||
/**
|
||||
* An object containing data required to connect to the voice servers with
|
||||
* @type {object}
|
||||
* @type {Object}
|
||||
*/
|
||||
this.data = {};
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ const EventEmitter = require('events').EventEmitter;
|
||||
const fs = require('fs');
|
||||
|
||||
/**
|
||||
* Represents a connection to a Voice Channel in Discord.
|
||||
* Represents a connection to a voice channel in Discord.
|
||||
* ```js
|
||||
* // obtained using:
|
||||
* voiceChannel.join().then(connection => {
|
||||
@@ -17,9 +17,9 @@ const fs = require('fs');
|
||||
* @extends {EventEmitter}
|
||||
*/
|
||||
class VoiceConnection extends EventEmitter {
|
||||
|
||||
constructor(pendingConnection) {
|
||||
super();
|
||||
|
||||
/**
|
||||
* The Voice Manager that instantiated this connection
|
||||
* @type {ClientVoiceManager}
|
||||
@@ -46,7 +46,7 @@ class VoiceConnection extends EventEmitter {
|
||||
|
||||
/**
|
||||
* The authentication data needed to connect to the voice server
|
||||
* @type {object}
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
this.authentication = pendingConnection.data;
|
||||
@@ -70,7 +70,7 @@ class VoiceConnection extends EventEmitter {
|
||||
/**
|
||||
* Warning info from the connection
|
||||
* @event VoiceConnection#warn
|
||||
* @param {string|error} warning the warning
|
||||
* @param {string|Error} warning the warning
|
||||
*/
|
||||
this.emit('warn', e);
|
||||
this.player.cleanup();
|
||||
@@ -83,9 +83,16 @@ class VoiceConnection extends EventEmitter {
|
||||
*/
|
||||
this.ssrcMap = new Map();
|
||||
|
||||
/**
|
||||
* Whether this connection is ready
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.ready = false;
|
||||
|
||||
/**
|
||||
* Object that wraps contains the `ws` and `udp` sockets of this voice connection
|
||||
* @type {object}
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
this.sockets = {};
|
||||
@@ -106,8 +113,7 @@ class VoiceConnection extends EventEmitter {
|
||||
speaking: true,
|
||||
delay: 0,
|
||||
},
|
||||
})
|
||||
.catch(e => {
|
||||
}).catch(e => {
|
||||
this.emit('debug', e);
|
||||
});
|
||||
}
|
||||
@@ -155,8 +161,7 @@ class VoiceConnection extends EventEmitter {
|
||||
this.sockets.udp.findEndpointAddress()
|
||||
.then(address => {
|
||||
this.sockets.udp.createUDPSocket(address);
|
||||
})
|
||||
.catch(e => this.emit('error', e));
|
||||
}, e => this.emit('error', e));
|
||||
});
|
||||
this.sockets.ws.once('sessionDescription', (mode, secret) => {
|
||||
this.authentication.encryptionMode = mode;
|
||||
@@ -167,6 +172,7 @@ class VoiceConnection extends EventEmitter {
|
||||
* @event VoiceConnection#ready
|
||||
*/
|
||||
this.emit('ready');
|
||||
this.ready = true;
|
||||
});
|
||||
this.sockets.ws.on('speaking', data => {
|
||||
const guild = this.channel.guild;
|
||||
@@ -253,7 +259,7 @@ class VoiceConnection extends EventEmitter {
|
||||
*/
|
||||
playConvertedStream(stream, { seek = 0, volume = 1, passes = 1 } = {}) {
|
||||
const options = { seek, volume, passes };
|
||||
return this.player.playPCMStream(stream, options);
|
||||
return this.player.playPCMStream(stream, null, options);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,18 +3,6 @@ const dns = require('dns');
|
||||
const Constants = require('../../util/Constants');
|
||||
const EventEmitter = require('events').EventEmitter;
|
||||
|
||||
function parseLocalPacket(message) {
|
||||
try {
|
||||
const packet = new Buffer(message);
|
||||
let address = '';
|
||||
for (let i = 4; i < packet.indexOf(0, i); i++) address += String.fromCharCode(packet[i]);
|
||||
const port = parseInt(packet.readUIntLE(packet.length - 2, 2).toString(10), 10);
|
||||
return { address, port };
|
||||
} catch (error) {
|
||||
return { error };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a UDP Client for a Voice Connection
|
||||
* @extends {EventEmitter}
|
||||
@@ -142,4 +130,16 @@ class VoiceConnectionUDPClient extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
function parseLocalPacket(message) {
|
||||
try {
|
||||
const packet = new Buffer(message);
|
||||
let address = '';
|
||||
for (let i = 4; i < packet.indexOf(0, i); i++) address += String.fromCharCode(packet[i]);
|
||||
const port = parseInt(packet.readUIntLE(packet.length - 2, 2).toString(10), 10);
|
||||
return { address, port };
|
||||
} catch (error) {
|
||||
return { error };
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = VoiceConnectionUDPClient;
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
const WebSocket = require('ws');
|
||||
const Constants = require('../../util/Constants');
|
||||
const SecretKey = require('./util/SecretKey');
|
||||
const EventEmitter = require('events').EventEmitter;
|
||||
|
||||
let WebSocket;
|
||||
try {
|
||||
WebSocket = require('uws');
|
||||
} catch (err) {
|
||||
WebSocket = require('ws');
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a Voice Connection's WebSocket
|
||||
* @extends {EventEmitter}
|
||||
|
||||
@@ -10,9 +10,7 @@ nonce.fill(0);
|
||||
* // obtained using:
|
||||
* voiceChannel.join().then(connection => {
|
||||
* // you can play a file or a stream here:
|
||||
* connection.playFile('./file.mp3').then(dispatcher => {
|
||||
*
|
||||
* });
|
||||
* const dispatcher = connection.playFile('./file.mp3');
|
||||
* });
|
||||
* ```
|
||||
* @extends {EventEmitter}
|
||||
@@ -116,9 +114,10 @@ class StreamDispatcher extends EventEmitter {
|
||||
|
||||
/**
|
||||
* Stops the current stream permanently and emits an `end` event.
|
||||
* @param {string} [reason='user'] An optional reason for stopping the dispatcher.
|
||||
*/
|
||||
end() {
|
||||
this._triggerTerminalState('end', 'user requested');
|
||||
end(reason = 'user') {
|
||||
this._triggerTerminalState('end', reason);
|
||||
}
|
||||
|
||||
_setSpeaking(value) {
|
||||
@@ -136,7 +135,7 @@ class StreamDispatcher extends EventEmitter {
|
||||
const packet = this._createPacket(sequence, timestamp, this.player.opusEncoder.encode(buffer));
|
||||
while (repeats--) {
|
||||
this.player.voiceConnection.sockets.udp.send(packet)
|
||||
.catch(e => this.emit('debug', `failed to send a packet ${e}`));
|
||||
.catch(e => this.emit('debug', `Failed to send a packet ${e}`));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,7 +222,7 @@ class StreamDispatcher extends EventEmitter {
|
||||
buffer = this._applyVolume(buffer);
|
||||
|
||||
data.count++;
|
||||
data.sequence = (data.sequence + 1) < (65536) ? data.sequence + 1 : 0;
|
||||
data.sequence = (data.sequence + 1) < 65536 ? data.sequence + 1 : 0;
|
||||
data.timestamp = data.timestamp + 4294967295 ? data.timestamp + 960 : 0;
|
||||
|
||||
this._sendBuffer(buffer, data.sequence, data.timestamp);
|
||||
@@ -235,12 +234,14 @@ class StreamDispatcher extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
_triggerEnd() {
|
||||
_triggerEnd(reason) {
|
||||
/**
|
||||
* Emitted once the stream has ended. Attach a `once` listener to this.
|
||||
* @event StreamDispatcher#end
|
||||
* @param {string} reason The reason for the end of the dispatcher. If it ended because it reached the end of the
|
||||
* stream, this would be `stream`. If you invoke `.end()` without specifying a reason, this would be `user`.
|
||||
*/
|
||||
this.emit('end');
|
||||
this.emit('end', reason);
|
||||
}
|
||||
|
||||
_triggerError(err) {
|
||||
@@ -282,7 +283,7 @@ class StreamDispatcher extends EventEmitter {
|
||||
return;
|
||||
}
|
||||
|
||||
this.stream.on('end', err => this._triggerTerminalState('end', err));
|
||||
this.stream.on('end', err => this._triggerTerminalState('end', err || 'stream'));
|
||||
this.stream.on('error', err => this._triggerTerminalState('error', err));
|
||||
|
||||
const data = this.streamingData;
|
||||
|
||||
@@ -67,7 +67,14 @@ class FfmpegConverterEngine extends ConverterEngine {
|
||||
}
|
||||
|
||||
function chooseCommand() {
|
||||
for (const cmd of ['ffmpeg', 'avconv', './ffmpeg', './avconv']) {
|
||||
for (const cmd of [
|
||||
'ffmpeg',
|
||||
'avconv',
|
||||
'./ffmpeg',
|
||||
'./avconv',
|
||||
'node_modules\\ffmpeg-binaries\\bin\\ffmpeg',
|
||||
'node_modules/ffmpeg-binaries/bin/ffmpeg',
|
||||
]) {
|
||||
if (!ChildProcess.spawnSync(cmd, ['-h']).error) return cmd;
|
||||
}
|
||||
throw new Error(
|
||||
|
||||
@@ -1,9 +1,30 @@
|
||||
const WebSocket = require('ws');
|
||||
const browser = typeof window !== 'undefined';
|
||||
const EventEmitter = require('events').EventEmitter;
|
||||
const Constants = require('../../util/Constants');
|
||||
const convertArrayBuffer = require('../../util/ConvertArrayBuffer');
|
||||
const pako = require('pako');
|
||||
const zlib = require('zlib');
|
||||
const PacketManager = require('./packets/WebSocketPacketManager');
|
||||
|
||||
let WebSocket, erlpack;
|
||||
let serialize = JSON.stringify;
|
||||
if (browser) {
|
||||
WebSocket = window.WebSocket; // eslint-disable-line no-undef
|
||||
} else {
|
||||
try {
|
||||
WebSocket = require('uws');
|
||||
} catch (err) {
|
||||
WebSocket = require('ws');
|
||||
}
|
||||
|
||||
try {
|
||||
erlpack = require('erlpack');
|
||||
serialize = erlpack.pack;
|
||||
} catch (err) {
|
||||
erlpack = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The WebSocket Manager of the Client
|
||||
* @private
|
||||
@@ -64,9 +85,11 @@ class WebSocketManager extends EventEmitter {
|
||||
* @type {Object}
|
||||
*/
|
||||
this.disabledEvents = {};
|
||||
for (const event in client.options.disabledEvents) this.disabledEvents[event] = true;
|
||||
for (const event of client.options.disabledEvents) this.disabledEvents[event] = true;
|
||||
|
||||
this.first = true;
|
||||
|
||||
this.lastHeartbeatAck = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,15 +101,21 @@ class WebSocketManager extends EventEmitter {
|
||||
this.normalReady = false;
|
||||
if (this.status !== Constants.Status.RECONNECTING) this.status = Constants.Status.CONNECTING;
|
||||
this.ws = new WebSocket(gateway);
|
||||
this.ws.onopen = () => this.eventOpen();
|
||||
this.ws.onclose = (d) => this.eventClose(d);
|
||||
this.ws.onmessage = (e) => this.eventMessage(e);
|
||||
this.ws.onerror = (e) => this.eventError(e);
|
||||
if (browser) this.ws.binaryType = 'arraybuffer';
|
||||
this.ws.onopen = this.eventOpen.bind(this);
|
||||
this.ws.onmessage = this.eventMessage.bind(this);
|
||||
this.ws.onclose = this.eventClose.bind(this);
|
||||
this.ws.onerror = this.eventError.bind(this);
|
||||
this._queue = [];
|
||||
this._remaining = 3;
|
||||
this._remaining = 120;
|
||||
this.client.setInterval(() => {
|
||||
this._remaining = 120;
|
||||
this._remainingReset = Date.now();
|
||||
}, 60e3);
|
||||
}
|
||||
|
||||
connect(gateway) {
|
||||
gateway = `${gateway}&encoding=${erlpack ? 'etf' : 'json'}`;
|
||||
if (this.first) {
|
||||
this._connect(gateway);
|
||||
this.first = false;
|
||||
@@ -95,6 +124,22 @@ class WebSocketManager extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
heartbeat(normal) {
|
||||
if (normal && !this.lastHeartbeatAck) {
|
||||
this.ws.close(1007);
|
||||
return;
|
||||
}
|
||||
|
||||
this.client.emit('debug', 'Sending heartbeat');
|
||||
this.client._pingTimestamp = Date.now();
|
||||
this.client.ws.send({
|
||||
op: Constants.OPCodes.HEARTBEAT,
|
||||
d: this.sequence,
|
||||
}, true);
|
||||
|
||||
this.lastHeartbeatAck = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a packet to the gateway
|
||||
* @param {Object} data An object that can be JSON stringified
|
||||
@@ -102,10 +147,10 @@ class WebSocketManager extends EventEmitter {
|
||||
*/
|
||||
send(data, force = false) {
|
||||
if (force) {
|
||||
this._send(JSON.stringify(data));
|
||||
this._send(serialize(data));
|
||||
return;
|
||||
}
|
||||
this._queue.push(JSON.stringify(data));
|
||||
this._queue.push(serialize(data));
|
||||
this.doQueue();
|
||||
}
|
||||
|
||||
@@ -124,19 +169,15 @@ class WebSocketManager extends EventEmitter {
|
||||
|
||||
doQueue() {
|
||||
const item = this._queue[0];
|
||||
if (this.ws.readyState === WebSocket.OPEN && item) {
|
||||
if (this._remaining === 0) {
|
||||
this.client.setTimeout(() => {
|
||||
this.doQueue();
|
||||
}, 1000);
|
||||
return;
|
||||
}
|
||||
this._remaining--;
|
||||
this._send(item);
|
||||
this._queue.shift();
|
||||
this.doQueue();
|
||||
this.client.setTimeout(() => this._remaining++, 1000);
|
||||
if (!(this.ws.readyState === WebSocket.OPEN && item)) return;
|
||||
if (this.remaining === 0) {
|
||||
this.client.setTimeout(this.doQueue.bind(this), Date.now() - this.remainingReset);
|
||||
return;
|
||||
}
|
||||
this._remaining--;
|
||||
this._send(item);
|
||||
this._queue.shift();
|
||||
this.doQueue();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -144,6 +185,7 @@ class WebSocketManager extends EventEmitter {
|
||||
*/
|
||||
eventOpen() {
|
||||
this.client.emit('debug', 'Connection to gateway opened');
|
||||
this.lastHeartbeatAck = true;
|
||||
if (this.status === Constants.Status.RECONNECTING) this._sendResume();
|
||||
else this._sendNewIdentify();
|
||||
}
|
||||
@@ -187,18 +229,26 @@ class WebSocketManager extends EventEmitter {
|
||||
this.sequence = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @external CloseEvent
|
||||
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Run whenever the connection to the gateway is closed, it will try to reconnect the client.
|
||||
* @param {Object} event The received websocket data
|
||||
* @param {CloseEvent} event The WebSocket close event
|
||||
*/
|
||||
eventClose(event) {
|
||||
this.emit('close', event);
|
||||
this.client.clearInterval(this.client.manager.heartbeatInterval);
|
||||
this.status = Constants.Status.DISCONNECTED;
|
||||
this._queue = [];
|
||||
/**
|
||||
* Emitted whenever the client websocket is disconnected
|
||||
* @event Client#disconnect
|
||||
* @param {CloseEvent} event The WebSocket close event
|
||||
*/
|
||||
clearInterval(this.client.manager.heartbeatInterval);
|
||||
if (!this.reconnecting) this.client.emit(Constants.Events.DISCONNECT);
|
||||
if (!this.reconnecting) this.client.emit(Constants.Events.DISCONNECT, event);
|
||||
if (event.code === 4004) return;
|
||||
if (event.code === 4010) return;
|
||||
if (!this.reconnecting && event.code !== 1000) this.tryReconnect();
|
||||
@@ -211,18 +261,45 @@ class WebSocketManager extends EventEmitter {
|
||||
* @returns {boolean}
|
||||
*/
|
||||
eventMessage(event) {
|
||||
let packet;
|
||||
try {
|
||||
if (event.binary) event.data = zlib.inflateSync(event.data).toString();
|
||||
packet = JSON.parse(event.data);
|
||||
} catch (e) {
|
||||
return this.eventError(new Error(Constants.Errors.BAD_WS_MESSAGE));
|
||||
const data = this.tryParseEventData(event.data);
|
||||
if (data === null) {
|
||||
this.eventError(new Error(Constants.Errors.BAD_WS_MESSAGE));
|
||||
return false;
|
||||
}
|
||||
|
||||
this.client.emit('raw', packet);
|
||||
this.client.emit('raw', data);
|
||||
|
||||
if (packet.op === Constants.OPCodes.HELLO) this.client.manager.setupKeepAlive(packet.d.heartbeat_interval);
|
||||
return this.packetManager.handle(packet);
|
||||
if (data.op === Constants.OPCodes.HELLO) this.client.manager.setupKeepAlive(data.d.heartbeat_interval);
|
||||
return this.packetManager.handle(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the raw data from a websocket event, inflating it if necessary
|
||||
* @param {*} data Event data
|
||||
* @returns {Object}
|
||||
*/
|
||||
parseEventData(data) {
|
||||
if (erlpack) {
|
||||
if (data instanceof ArrayBuffer) data = convertArrayBuffer(data);
|
||||
return erlpack.unpack(data);
|
||||
} else {
|
||||
if (data instanceof ArrayBuffer) data = pako.inflate(data, { to: 'string' });
|
||||
else if (data instanceof Buffer) data = zlib.inflateSync(data).toString();
|
||||
return JSON.parse(data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to call `parseEventData()` and return its result, or returns `null` upon thrown errors.
|
||||
* @param {*} data Event data
|
||||
* @returns {?Object}
|
||||
*/
|
||||
tryParseEventData(data) {
|
||||
try {
|
||||
return this.parseEventData(data);
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -264,7 +341,7 @@ class WebSocketManager extends EventEmitter {
|
||||
this.status = Constants.Status.NEARLY;
|
||||
if (this.client.options.fetchAllMembers) {
|
||||
const promises = this.client.guilds.map(g => g.fetchMembers());
|
||||
Promise.all(promises).then(() => this._emitReady()).catch(e => {
|
||||
Promise.all(promises).then(() => this._emitReady(), e => {
|
||||
this.client.emit(Constants.Events.WARN, 'Error in pre-ready guild member fetching');
|
||||
this.client.emit(Constants.Events.ERROR, e);
|
||||
this._emitReady();
|
||||
@@ -280,6 +357,7 @@ class WebSocketManager extends EventEmitter {
|
||||
* Tries to reconnect the client, changing the status to Constants.Status.RECONNECTING.
|
||||
*/
|
||||
tryReconnect() {
|
||||
if (this.status === Constants.Status.RECONNECTING || this.status === Constants.Status.CONNECTING) return;
|
||||
this.status = Constants.Status.RECONNECTING;
|
||||
this.ws.close();
|
||||
this.packetManager.handleQueue();
|
||||
|
||||
@@ -15,43 +15,47 @@ class WebSocketPacketManager {
|
||||
this.handlers = {};
|
||||
this.queue = [];
|
||||
|
||||
this.register(Constants.WSEvents.READY, 'Ready');
|
||||
this.register(Constants.WSEvents.GUILD_CREATE, 'GuildCreate');
|
||||
this.register(Constants.WSEvents.GUILD_DELETE, 'GuildDelete');
|
||||
this.register(Constants.WSEvents.GUILD_UPDATE, 'GuildUpdate');
|
||||
this.register(Constants.WSEvents.GUILD_BAN_ADD, 'GuildBanAdd');
|
||||
this.register(Constants.WSEvents.GUILD_BAN_REMOVE, 'GuildBanRemove');
|
||||
this.register(Constants.WSEvents.GUILD_MEMBER_ADD, 'GuildMemberAdd');
|
||||
this.register(Constants.WSEvents.GUILD_MEMBER_REMOVE, 'GuildMemberRemove');
|
||||
this.register(Constants.WSEvents.GUILD_MEMBER_UPDATE, 'GuildMemberUpdate');
|
||||
this.register(Constants.WSEvents.GUILD_ROLE_CREATE, 'GuildRoleCreate');
|
||||
this.register(Constants.WSEvents.GUILD_ROLE_DELETE, 'GuildRoleDelete');
|
||||
this.register(Constants.WSEvents.GUILD_ROLE_UPDATE, 'GuildRoleUpdate');
|
||||
this.register(Constants.WSEvents.GUILD_MEMBERS_CHUNK, 'GuildMembersChunk');
|
||||
this.register(Constants.WSEvents.CHANNEL_CREATE, 'ChannelCreate');
|
||||
this.register(Constants.WSEvents.CHANNEL_DELETE, 'ChannelDelete');
|
||||
this.register(Constants.WSEvents.CHANNEL_UPDATE, 'ChannelUpdate');
|
||||
this.register(Constants.WSEvents.PRESENCE_UPDATE, 'PresenceUpdate');
|
||||
this.register(Constants.WSEvents.USER_UPDATE, 'UserUpdate');
|
||||
this.register(Constants.WSEvents.VOICE_STATE_UPDATE, 'VoiceStateUpdate');
|
||||
this.register(Constants.WSEvents.TYPING_START, 'TypingStart');
|
||||
this.register(Constants.WSEvents.MESSAGE_CREATE, 'MessageCreate');
|
||||
this.register(Constants.WSEvents.MESSAGE_DELETE, 'MessageDelete');
|
||||
this.register(Constants.WSEvents.MESSAGE_UPDATE, 'MessageUpdate');
|
||||
this.register(Constants.WSEvents.VOICE_SERVER_UPDATE, 'VoiceServerUpdate');
|
||||
this.register(Constants.WSEvents.MESSAGE_DELETE_BULK, 'MessageDeleteBulk');
|
||||
this.register(Constants.WSEvents.CHANNEL_PINS_UPDATE, 'ChannelPinsUpdate');
|
||||
this.register(Constants.WSEvents.GUILD_SYNC, 'GuildSync');
|
||||
this.register(Constants.WSEvents.RELATIONSHIP_ADD, 'RelationshipAdd');
|
||||
this.register(Constants.WSEvents.RELATIONSHIP_REMOVE, 'RelationshipRemove');
|
||||
this.register(Constants.WSEvents.READY, require('./handlers/Ready'));
|
||||
this.register(Constants.WSEvents.GUILD_CREATE, require('./handlers/GuildCreate'));
|
||||
this.register(Constants.WSEvents.GUILD_DELETE, require('./handlers/GuildDelete'));
|
||||
this.register(Constants.WSEvents.GUILD_UPDATE, require('./handlers/GuildUpdate'));
|
||||
this.register(Constants.WSEvents.GUILD_BAN_ADD, require('./handlers/GuildBanAdd'));
|
||||
this.register(Constants.WSEvents.GUILD_BAN_REMOVE, require('./handlers/GuildBanRemove'));
|
||||
this.register(Constants.WSEvents.GUILD_MEMBER_ADD, require('./handlers/GuildMemberAdd'));
|
||||
this.register(Constants.WSEvents.GUILD_MEMBER_REMOVE, require('./handlers/GuildMemberRemove'));
|
||||
this.register(Constants.WSEvents.GUILD_MEMBER_UPDATE, require('./handlers/GuildMemberUpdate'));
|
||||
this.register(Constants.WSEvents.GUILD_ROLE_CREATE, require('./handlers/GuildRoleCreate'));
|
||||
this.register(Constants.WSEvents.GUILD_ROLE_DELETE, require('./handlers/GuildRoleDelete'));
|
||||
this.register(Constants.WSEvents.GUILD_ROLE_UPDATE, require('./handlers/GuildRoleUpdate'));
|
||||
this.register(Constants.WSEvents.GUILD_EMOJIS_UPDATE, require('./handlers/GuildEmojisUpdate'));
|
||||
this.register(Constants.WSEvents.GUILD_MEMBERS_CHUNK, require('./handlers/GuildMembersChunk'));
|
||||
this.register(Constants.WSEvents.CHANNEL_CREATE, require('./handlers/ChannelCreate'));
|
||||
this.register(Constants.WSEvents.CHANNEL_DELETE, require('./handlers/ChannelDelete'));
|
||||
this.register(Constants.WSEvents.CHANNEL_UPDATE, require('./handlers/ChannelUpdate'));
|
||||
this.register(Constants.WSEvents.CHANNEL_PINS_UPDATE, require('./handlers/ChannelPinsUpdate'));
|
||||
this.register(Constants.WSEvents.PRESENCE_UPDATE, require('./handlers/PresenceUpdate'));
|
||||
this.register(Constants.WSEvents.USER_UPDATE, require('./handlers/UserUpdate'));
|
||||
this.register(Constants.WSEvents.USER_NOTE_UPDATE, require('./handlers/UserNoteUpdate'));
|
||||
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'));
|
||||
this.register(Constants.WSEvents.MESSAGE_DELETE, require('./handlers/MessageDelete'));
|
||||
this.register(Constants.WSEvents.MESSAGE_UPDATE, require('./handlers/MessageUpdate'));
|
||||
this.register(Constants.WSEvents.MESSAGE_DELETE_BULK, require('./handlers/MessageDeleteBulk'));
|
||||
this.register(Constants.WSEvents.VOICE_SERVER_UPDATE, require('./handlers/VoiceServerUpdate'));
|
||||
this.register(Constants.WSEvents.GUILD_SYNC, require('./handlers/GuildSync'));
|
||||
this.register(Constants.WSEvents.RELATIONSHIP_ADD, require('./handlers/RelationshipAdd'));
|
||||
this.register(Constants.WSEvents.RELATIONSHIP_REMOVE, require('./handlers/RelationshipRemove'));
|
||||
this.register(Constants.WSEvents.MESSAGE_REACTION_ADD, require('./handlers/MessageReactionAdd'));
|
||||
this.register(Constants.WSEvents.MESSAGE_REACTION_REMOVE, require('./handlers/MessageReactionRemove'));
|
||||
this.register(Constants.WSEvents.MESSAGE_REACTION_REMOVE_ALL, require('./handlers/MessageReactionRemoveAll'));
|
||||
}
|
||||
|
||||
get client() {
|
||||
return this.ws.client;
|
||||
}
|
||||
|
||||
register(event, handle) {
|
||||
const Handler = require(`./handlers/${handle}`);
|
||||
register(event, Handler) {
|
||||
this.handlers[event] = new Handler(this);
|
||||
}
|
||||
|
||||
@@ -74,12 +78,28 @@ class WebSocketPacketManager {
|
||||
}
|
||||
|
||||
if (packet.op === Constants.OPCodes.INVALID_SESSION) {
|
||||
this.ws.sessionID = null;
|
||||
this.ws._sendNewIdentify();
|
||||
if (packet.d) {
|
||||
setTimeout(() => {
|
||||
this.ws._sendResume();
|
||||
}, 2500);
|
||||
} else {
|
||||
this.ws.sessionID = null;
|
||||
this.ws._sendNewIdentify();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (packet.op === Constants.OPCodes.HEARTBEAT_ACK) this.ws.client.emit('debug', 'Heartbeat acknowledged');
|
||||
if (packet.op === Constants.OPCodes.HEARTBEAT_ACK) {
|
||||
this.ws.client._pong(this.ws.client._pingTimestamp);
|
||||
this.ws.lastHeartbeatAck = true;
|
||||
this.ws.client.emit('debug', 'Heartbeat acknowledged');
|
||||
} else if (packet.op === Constants.OPCodes.HEARTBEAT) {
|
||||
this.client.ws.send({
|
||||
op: Constants.OPCodes.HEARTBEAT,
|
||||
d: this.client.ws.sequence,
|
||||
});
|
||||
this.ws.client.emit('debug', 'Received gateway heartbeat');
|
||||
}
|
||||
|
||||
if (this.ws.status === Constants.Status.RECONNECTING) {
|
||||
this.ws.reconnecting = false;
|
||||
|
||||
@@ -9,7 +9,7 @@ class ChannelCreateHandler extends AbstractHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* Emitted whenever a Channel is created.
|
||||
* Emitted whenever a channel is created.
|
||||
* @event Client#channelCreate
|
||||
* @param {Channel} channel The channel that was created
|
||||
*/
|
||||
|
||||
@@ -12,7 +12,7 @@ class ChannelDeleteHandler extends AbstractHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* Emitted whenever a Channel is deleted.
|
||||
* Emitted whenever a channel is deleted.
|
||||
* @event Client#channelDelete
|
||||
* @param {Channel} channel The channel that was deleted
|
||||
*/
|
||||
|
||||
@@ -21,7 +21,7 @@ class ChannelPinsUpdate extends AbstractHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* Emitted whenever the pins of a Channel are updated. Due to the nature of the WebSocket event, not much information
|
||||
* Emitted whenever the pins of a channel are updated. Due to the nature of the WebSocket event, not much information
|
||||
* can be provided easily here - you need to manually check the pins yourself.
|
||||
* @event Client#channelPinsUpdate
|
||||
* @param {Channel} channel The channel that the pins update occured in
|
||||
|
||||
@@ -11,7 +11,7 @@ class GuildDeleteHandler extends AbstractHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* Emitted whenever a Guild is deleted/left.
|
||||
* Emitted whenever a guild is deleted/left.
|
||||
* @event Client#guildDelete
|
||||
* @param {Guild} guild The guild that was deleted
|
||||
*/
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
const AbstractHandler = require('./AbstractHandler');
|
||||
|
||||
class GuildEmojiUpdate extends AbstractHandler {
|
||||
handle(packet) {
|
||||
const client = this.packetManager.client;
|
||||
const data = packet.d;
|
||||
const guild = client.guilds.get(data.guild_id);
|
||||
if (!guild) return;
|
||||
client.actions.EmojiUpdate.handle(data, guild);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = GuildEmojiUpdate;
|
||||
40
src/client/websocket/packets/handlers/GuildEmojisUpdate.js
Normal file
40
src/client/websocket/packets/handlers/GuildEmojisUpdate.js
Normal file
@@ -0,0 +1,40 @@
|
||||
const AbstractHandler = require('./AbstractHandler');
|
||||
|
||||
function mappify(iterable) {
|
||||
const map = new Map();
|
||||
for (const x of iterable) map.set(...x);
|
||||
return map;
|
||||
}
|
||||
|
||||
class GuildEmojisUpdate extends AbstractHandler {
|
||||
handle(packet) {
|
||||
const client = this.packetManager.client;
|
||||
const data = packet.d;
|
||||
const guild = client.guilds.get(data.guild_id);
|
||||
if (!guild || !guild.emojis) return;
|
||||
|
||||
const deletions = mappify(guild.emojis.entries());
|
||||
|
||||
for (const emoji of data.emojis) {
|
||||
// determine type of emoji event
|
||||
const cachedEmoji = guild.emojis.get(emoji.id);
|
||||
if (cachedEmoji) {
|
||||
deletions.delete(emoji.id);
|
||||
if (!cachedEmoji.equals(emoji, true)) {
|
||||
// emoji updated
|
||||
client.actions.GuildEmojiUpdate.handle(cachedEmoji, emoji);
|
||||
}
|
||||
} else {
|
||||
// emoji added
|
||||
client.actions.GuildEmojiCreate.handle(guild, emoji);
|
||||
}
|
||||
}
|
||||
|
||||
for (const emoji of deletions.values()) {
|
||||
// emoji deleted
|
||||
client.actions.GuildEmojiDelete.handle(emoji);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = GuildEmojisUpdate;
|
||||
@@ -8,19 +8,19 @@ class GuildMembersChunkHandler extends AbstractHandler {
|
||||
const client = this.packetManager.client;
|
||||
const data = packet.d;
|
||||
const guild = client.guilds.get(data.guild_id);
|
||||
const members = [];
|
||||
if (!guild) return;
|
||||
|
||||
if (guild) {
|
||||
for (const member of data.members) members.push(guild._addMember(member, false));
|
||||
}
|
||||
const members = data.members.map(member => guild._addMember(member, false));
|
||||
|
||||
guild._checkChunks();
|
||||
client.emit(Constants.Events.GUILD_MEMBERS_CHUNK, members);
|
||||
|
||||
client.ws.lastHeartbeatAck = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emitted whenever a chunk of Guild members is received (all members come from the same guild)
|
||||
* Emitted whenever a chunk of guild members is received (all members come from the same guild)
|
||||
* @event Client#guildMembersChunk
|
||||
* @param {GuildMember[]} members The members in the chunk
|
||||
*/
|
||||
|
||||
11
src/client/websocket/packets/handlers/MessageReactionAdd.js
Normal file
11
src/client/websocket/packets/handlers/MessageReactionAdd.js
Normal file
@@ -0,0 +1,11 @@
|
||||
const AbstractHandler = require('./AbstractHandler');
|
||||
|
||||
class MessageReactionAddHandler extends AbstractHandler {
|
||||
handle(packet) {
|
||||
const client = this.packetManager.client;
|
||||
const data = packet.d;
|
||||
client.actions.MessageReactionAdd.handle(data);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MessageReactionAddHandler;
|
||||
@@ -0,0 +1,11 @@
|
||||
const AbstractHandler = require('./AbstractHandler');
|
||||
|
||||
class MessageReactionRemove extends AbstractHandler {
|
||||
handle(packet) {
|
||||
const client = this.packetManager.client;
|
||||
const data = packet.d;
|
||||
client.actions.MessageReactionRemove.handle(data);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MessageReactionRemove;
|
||||
@@ -0,0 +1,11 @@
|
||||
const AbstractHandler = require('./AbstractHandler');
|
||||
|
||||
class MessageReactionRemoveAll extends AbstractHandler {
|
||||
handle(packet) {
|
||||
const client = this.packetManager.client;
|
||||
const data = packet.d;
|
||||
client.actions.MessageReactionRemoveAll.handle(data);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MessageReactionRemoveAll;
|
||||
@@ -64,7 +64,7 @@ class PresenceUpdateHandler extends AbstractHandler {
|
||||
*/
|
||||
|
||||
/**
|
||||
* Emitted whenever a member becomes available in a large Guild
|
||||
* Emitted whenever a member becomes available in a large guild
|
||||
* @event Client#guildMemberAvailable
|
||||
* @param {GuildMember} member The member that became available
|
||||
*/
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
const AbstractHandler = require('./AbstractHandler');
|
||||
|
||||
const getStructure = name => require(`../../../../structures/${name}`);
|
||||
const ClientUser = getStructure('ClientUser');
|
||||
const ClientUser = require('../../../../structures/ClientUser');
|
||||
|
||||
class ReadyHandler extends AbstractHandler {
|
||||
handle(packet) {
|
||||
const client = this.packetManager.client;
|
||||
const data = packet.d;
|
||||
|
||||
client.ws.heartbeat();
|
||||
|
||||
const clientUser = new ClientUser(client, data.user);
|
||||
client.user = clientUser;
|
||||
client.readyAt = new Date();
|
||||
@@ -31,6 +32,15 @@ class ReadyHandler extends AbstractHandler {
|
||||
client._setPresence(presence.user.id, presence);
|
||||
}
|
||||
|
||||
if (data.notes) {
|
||||
for (const user in data.notes) {
|
||||
let note = data.notes[user];
|
||||
if (!note.length) note = null;
|
||||
|
||||
client.user.notes.set(user, note);
|
||||
}
|
||||
}
|
||||
|
||||
if (!client.user.bot && client.options.sync) client.setInterval(client.syncGuilds.bind(client), 30000);
|
||||
client.once('ready', client.syncGuilds.bind(client));
|
||||
|
||||
|
||||
12
src/client/websocket/packets/handlers/UserNoteUpdate.js
Normal file
12
src/client/websocket/packets/handlers/UserNoteUpdate.js
Normal file
@@ -0,0 +1,12 @@
|
||||
const AbstractHandler = require('./AbstractHandler');
|
||||
|
||||
class UserNoteUpdateHandler extends AbstractHandler {
|
||||
handle(packet) {
|
||||
const client = this.packetManager.client;
|
||||
const data = packet.d;
|
||||
|
||||
client.actions.UserNoteUpdate.handle(data);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UserNoteUpdateHandler;
|
||||
@@ -11,6 +11,7 @@ module.exports = {
|
||||
fetchRecommendedShards: require('./util/FetchRecommendedShards'),
|
||||
|
||||
Channel: require('./structures/Channel'),
|
||||
ClientOAuth2Application: require('./structures/ClientOAuth2Application'),
|
||||
ClientUser: require('./structures/ClientUser'),
|
||||
DMChannel: require('./structures/DMChannel'),
|
||||
Emoji: require('./structures/Emoji'),
|
||||
@@ -25,10 +26,14 @@ module.exports = {
|
||||
MessageAttachment: require('./structures/MessageAttachment'),
|
||||
MessageCollector: require('./structures/MessageCollector'),
|
||||
MessageEmbed: require('./structures/MessageEmbed'),
|
||||
MessageReaction: require('./structures/MessageReaction'),
|
||||
OAuth2Application: require('./structures/OAuth2Application'),
|
||||
PartialGuild: require('./structures/PartialGuild'),
|
||||
PartialGuildChannel: require('./structures/PartialGuildChannel'),
|
||||
PermissionOverwrites: require('./structures/PermissionOverwrites'),
|
||||
Presence: require('./structures/Presence').Presence,
|
||||
ReactionEmoji: require('./structures/ReactionEmoji'),
|
||||
RichEmbed: require('./structures/RichEmbed'),
|
||||
Role: require('./structures/Role'),
|
||||
TextChannel: require('./structures/TextChannel'),
|
||||
User: require('./structures/User'),
|
||||
@@ -36,4 +41,7 @@ module.exports = {
|
||||
Webhook: require('./structures/Webhook'),
|
||||
|
||||
version: require('../package').version,
|
||||
Constants: require('./util/Constants'),
|
||||
};
|
||||
|
||||
if (typeof window !== 'undefined') window.Discord = module.exports; // eslint-disable-line no-undef
|
||||
|
||||
@@ -10,7 +10,7 @@ class Shard {
|
||||
/**
|
||||
* @param {ShardingManager} manager The sharding manager
|
||||
* @param {number} id The ID of this shard
|
||||
* @param {array} [args=[]] Command line arguments to pass to the script
|
||||
* @param {Array} [args=[]] Command line arguments to pass to the script
|
||||
*/
|
||||
constructor(manager, id, args = []) {
|
||||
/**
|
||||
@@ -134,21 +134,29 @@ class Shard {
|
||||
if (message) {
|
||||
// Shard is requesting a property fetch
|
||||
if (message._sFetchProp) {
|
||||
this.manager.fetchClientValues(message._sFetchProp)
|
||||
.then(results => this.send({ _sFetchProp: message._sFetchProp, _result: results }))
|
||||
.catch(err => this.send({ _sFetchProp: message._sFetchProp, _error: makePlainError(err) }));
|
||||
this.manager.fetchClientValues(message._sFetchProp).then(
|
||||
results => this.send({ _sFetchProp: message._sFetchProp, _result: results }),
|
||||
err => this.send({ _sFetchProp: message._sFetchProp, _error: makePlainError(err) })
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Shard is requesting an eval broadcast
|
||||
if (message._sEval) {
|
||||
this.manager.broadcastEval(message._sEval)
|
||||
.then(results => this.send({ _sEval: message._sEval, _result: results }))
|
||||
.catch(err => this.send({ _sEval: message._sEval, _error: makePlainError(err) }));
|
||||
this.manager.broadcastEval(message._sEval).then(
|
||||
results => this.send({ _sEval: message._sEval, _result: results }),
|
||||
err => this.send({ _sEval: message._sEval, _error: makePlainError(err) })
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emitted upon recieving a message from a shard
|
||||
* @event ShardingManager#message
|
||||
* @param {Shard} shard Shard that sent the message
|
||||
* @param {*} message Message that was received
|
||||
*/
|
||||
this.manager.emit('message', this, message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,21 +119,22 @@ class ShardClientUtil {
|
||||
* @private
|
||||
*/
|
||||
_respond(type, message) {
|
||||
this.send(message).catch(err =>
|
||||
this.client.emit('error', `Error when sending ${type} response to master process: ${err}`)
|
||||
);
|
||||
this.send(message).catch(err => {
|
||||
err.message = `Error when sending ${type} response to master process: ${err.message}`;
|
||||
this.client.emit('error', err);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates/gets the singleton of this class
|
||||
* @param {Client} client Client to use
|
||||
* @returns {ShardUtil}
|
||||
* @returns {ShardClientUtil}
|
||||
*/
|
||||
static singleton(client) {
|
||||
if (!this._singleton) {
|
||||
this._singleton = new this(client);
|
||||
} else {
|
||||
client.emit('error', 'Multiple clients created in child process; only the first will handle sharding helpers.');
|
||||
client.emit('warn', 'Multiple clients created in child process; only the first will handle sharding helpers.');
|
||||
}
|
||||
return this._singleton;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ const fetchRecommendedShards = require('../util/FetchRecommendedShards');
|
||||
* This is a utility class that can be used to help you spawn shards of your Client. Each shard is completely separate
|
||||
* from the other. The Shard Manager takes a path to a file and spawns it under the specified amount of shards safely.
|
||||
* If you do not select an amount of shards, the manager will automatically decide the best amount.
|
||||
* <warn>The Sharding Manager is still experimental</warn>
|
||||
* @extends {EventEmitter}
|
||||
*/
|
||||
class ShardingManager extends EventEmitter {
|
||||
@@ -105,19 +104,17 @@ class ShardingManager extends EventEmitter {
|
||||
* @returns {Promise<Collection<number, Shard>>}
|
||||
*/
|
||||
spawn(amount = this.totalShards, delay = 5500) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (amount === 'auto') {
|
||||
fetchRecommendedShards(this.token).then(count => {
|
||||
this.totalShards = count;
|
||||
resolve(this._spawn(count, delay));
|
||||
}).catch(reject);
|
||||
} else {
|
||||
if (typeof amount !== 'number' || isNaN(amount)) throw new TypeError('Amount of shards must be a number.');
|
||||
if (amount < 1) throw new RangeError('Amount of shards must be at least 1.');
|
||||
if (amount !== Math.floor(amount)) throw new TypeError('Amount of shards must be an integer.');
|
||||
resolve(this._spawn(amount, delay));
|
||||
}
|
||||
});
|
||||
if (amount === 'auto') {
|
||||
return fetchRecommendedShards(this.token).then(count => {
|
||||
this.totalShards = count;
|
||||
return this._spawn(count, delay);
|
||||
});
|
||||
} else {
|
||||
if (typeof amount !== 'number' || isNaN(amount)) throw new TypeError('Amount of shards must be a number.');
|
||||
if (amount < 1) throw new RangeError('Amount of shards must be at least 1.');
|
||||
if (amount !== Math.floor(amount)) throw new TypeError('Amount of shards must be an integer.');
|
||||
return this._spawn(amount, delay);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
/**
|
||||
* Represents any Channel on Discord
|
||||
* Represents any channel on Discord
|
||||
*/
|
||||
class Channel {
|
||||
constructor(client, data) {
|
||||
/**
|
||||
* The client that instantiated the Channel
|
||||
* @name Channel#client
|
||||
* @type {Client}
|
||||
* @readonly
|
||||
*/
|
||||
this.client = client;
|
||||
Object.defineProperty(this, 'client', { enumerable: false, configurable: false });
|
||||
Object.defineProperty(this, 'client', { value: client });
|
||||
|
||||
/**
|
||||
* The type of the channel, either:
|
||||
|
||||
26
src/structures/ClientOAuth2Application.js
Normal file
26
src/structures/ClientOAuth2Application.js
Normal file
@@ -0,0 +1,26 @@
|
||||
const User = require('./User');
|
||||
const OAuth2Application = require('./OAuth2Application');
|
||||
|
||||
/**
|
||||
* Represents the client's OAuth2 Application
|
||||
* @extends {OAuth2Application}
|
||||
*/
|
||||
class ClientOAuth2Application extends OAuth2Application {
|
||||
setup(data) {
|
||||
super.setup(data);
|
||||
|
||||
/**
|
||||
* The app's flags
|
||||
* @type {number}
|
||||
*/
|
||||
this.flags = data.flags;
|
||||
|
||||
/**
|
||||
* The app's owner
|
||||
* @type {User}
|
||||
*/
|
||||
this.owner = new User(this.client, data.owner);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ClientOAuth2Application;
|
||||
@@ -2,7 +2,7 @@ const User = require('./User');
|
||||
const Collection = require('../util/Collection');
|
||||
|
||||
/**
|
||||
* Represents the logged in client's Discord User
|
||||
* Represents the logged in client's Discord user
|
||||
* @extends {User}
|
||||
*/
|
||||
class ClientUser extends User {
|
||||
@@ -25,17 +25,24 @@ class ClientUser extends User {
|
||||
|
||||
/**
|
||||
* A Collection of friends for the logged in user.
|
||||
* <warn>This is only filled for user accounts, not bot accounts!</warn>
|
||||
* <warn>This is only filled when using a user account.</warn>
|
||||
* @type {Collection<string, User>}
|
||||
*/
|
||||
this.friends = new Collection();
|
||||
|
||||
/**
|
||||
* A Collection of blocked users for the logged in user.
|
||||
* <warn>This is only filled for user accounts, not bot accounts!</warn>
|
||||
* <warn>This is only filled when using a user account.</warn>
|
||||
* @type {Collection<string, User>}
|
||||
*/
|
||||
this.blocked = new Collection();
|
||||
|
||||
/**
|
||||
* A Collection of notes for the logged in user.
|
||||
* <warn>This is only filled when using a user account.</warn>
|
||||
* @type {Collection<string, string>}
|
||||
*/
|
||||
this.notes = new Collection();
|
||||
}
|
||||
|
||||
edit(data) {
|
||||
@@ -47,6 +54,7 @@ class ClientUser extends User {
|
||||
* <info>Changing usernames in Discord is heavily rate limited, with only 2 requests
|
||||
* every hour. Use this sparingly!</info>
|
||||
* @param {string} username The new username
|
||||
* @param {string} [password] Current password (only for user accounts)
|
||||
* @returns {Promise<ClientUser>}
|
||||
* @example
|
||||
* // set username
|
||||
@@ -54,43 +62,45 @@ class ClientUser extends User {
|
||||
* .then(user => console.log(`My new username is ${user.username}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
setUsername(username) {
|
||||
return this.client.rest.methods.updateCurrentUser({ username });
|
||||
setUsername(username, password) {
|
||||
return this.client.rest.methods.updateCurrentUser({ username }, password);
|
||||
}
|
||||
|
||||
/**
|
||||
* If this user is a "self bot" or logged in using a normal user's details (which should be avoided), you can set the
|
||||
* email here.
|
||||
* @param {string} email The new email
|
||||
* Changes the email for the client user's account.
|
||||
* <warn>This is only available when using a user account.</warn>
|
||||
* @param {string} email New email to change to
|
||||
* @param {string} password Current password
|
||||
* @returns {Promise<ClientUser>}
|
||||
* @example
|
||||
* // set email
|
||||
* client.user.setEmail('bob@gmail.com')
|
||||
* client.user.setEmail('bob@gmail.com', 'some amazing password 123')
|
||||
* .then(user => console.log(`My new email is ${user.email}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
setEmail(email) {
|
||||
return this.client.rest.methods.updateCurrentUser({ email });
|
||||
setEmail(email, password) {
|
||||
return this.client.rest.methods.updateCurrentUser({ email }, password);
|
||||
}
|
||||
|
||||
/**
|
||||
* If this user is a "self bot" or logged in using a normal user's details (which should be avoided), you can set the
|
||||
* password here.
|
||||
* @param {string} password The new password
|
||||
* Changes the password for the client user's account.
|
||||
* <warn>This is only available when using a user account.</warn>
|
||||
* @param {string} newPassword New password to change to
|
||||
* @param {string} oldPassword Current password
|
||||
* @returns {Promise<ClientUser>}
|
||||
* @example
|
||||
* // set password
|
||||
* client.user.setPassword('password123')
|
||||
* client.user.setPassword('some new amazing password 456', 'some amazing password 123')
|
||||
* .then(user => console.log('New password set!'))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
setPassword(password) {
|
||||
return this.client.rest.methods.updateCurrentUser({ password });
|
||||
setPassword(newPassword, oldPassword) {
|
||||
return this.client.rest.methods.updateCurrentUser({ password: newPassword }, oldPassword);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the avatar of the logged in Client.
|
||||
* @param {FileResolvable|Base64Resolveable} avatar The new avatar
|
||||
* @param {BufferResolvable|Base64Resolvable} avatar The new avatar
|
||||
* @returns {Promise<ClientUser>}
|
||||
* @example
|
||||
* // set avatar
|
||||
@@ -99,94 +109,28 @@ class ClientUser extends User {
|
||||
* .catch(console.error);
|
||||
*/
|
||||
setAvatar(avatar) {
|
||||
return new Promise(resolve => {
|
||||
if (avatar.startsWith('data:')) {
|
||||
resolve(this.client.rest.methods.updateCurrentUser({ avatar }));
|
||||
} else {
|
||||
this.client.resolver.resolveFile(avatar).then(data => {
|
||||
resolve(this.client.rest.methods.updateCurrentUser({ avatar: data }));
|
||||
});
|
||||
}
|
||||
});
|
||||
if (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 })
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the status of the logged in user.
|
||||
* @param {string} status can be `online`, `idle`, `invisible` or `dnd` (do not disturb)
|
||||
* @returns {Promise<ClientUser>}
|
||||
* Data resembling a raw Discord presence
|
||||
* @typedef {Object} PresenceData
|
||||
* @property {PresenceStatus} [status] Status of the user
|
||||
* @property {boolean} [afk] Whether the user is AFK
|
||||
* @property {Object} [game] Game the user is playing
|
||||
* @property {string} [game.name] Name of the game
|
||||
* @property {string} [game.url] Twitch stream URL
|
||||
*/
|
||||
setStatus(status) {
|
||||
return this.setPresence({ status });
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current game of the logged in user.
|
||||
* @param {string} game the game being played
|
||||
* @param {string} [streamingURL] an optional URL to a twitch stream, if one is available.
|
||||
* @returns {Promise<ClientUser>}
|
||||
*/
|
||||
setGame(game, streamingURL) {
|
||||
return this.setPresence({ game: {
|
||||
name: game,
|
||||
url: streamingURL,
|
||||
} });
|
||||
}
|
||||
|
||||
/**
|
||||
* Set/remove the AFK flag for the current user.
|
||||
* @param {boolean} afk whether or not the user is AFK.
|
||||
* @returns {Promise<ClientUser>}
|
||||
*/
|
||||
setAFK(afk) {
|
||||
return this.setPresence({ afk });
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a friend request
|
||||
* <warn>This is only available for user accounts, not bot accounts!</warn>
|
||||
* @param {UserResolvable} user The user to send the friend request to.
|
||||
* @returns {Promise<User>} The user the friend request was sent to.
|
||||
*/
|
||||
addFriend(user) {
|
||||
user = this.client.resolver.resolveUser(user);
|
||||
return this.client.rest.methods.addFriend(user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a friend
|
||||
* <warn>This is only available for user accounts, not bot accounts!</warn>
|
||||
* @param {UserResolvable} user The user to remove from your friends
|
||||
* @returns {Promise<User>} The user that was removed
|
||||
*/
|
||||
removeFriend(user) {
|
||||
user = this.client.resolver.resolveUser(user);
|
||||
return this.client.rest.methods.removeFriend(user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a guild
|
||||
* <warn>This is only available for user accounts, not bot accounts!</warn>
|
||||
* @param {string} name The name of the guild
|
||||
* @param {string} region The region for the server
|
||||
* @param {FileResolvable|Base64Resolvable} [icon=null] The icon for the guild
|
||||
* @returns {Promise<Guild>} The guild that was created
|
||||
*/
|
||||
createGuild(name, region, icon = null) {
|
||||
return new Promise(resolve => {
|
||||
if (!icon) resolve(this.client.rest.methods.createGuild({ name, icon, region }));
|
||||
if (icon.startsWith('data:')) {
|
||||
resolve(this.client.rest.methods.createGuild({ name, icon, region }));
|
||||
} else {
|
||||
this.client.resolver.resolveFile(icon).then(data => {
|
||||
resolve(this.client.rest.methods.createGuild({ name, icon: data, region }));
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the full presence of the current user.
|
||||
* @param {Object} data the data to provide
|
||||
* Sets the full presence of the client user.
|
||||
* @param {PresenceData} data Data for the presence
|
||||
* @returns {Promise<ClientUser>}
|
||||
*/
|
||||
setPresence(data) {
|
||||
@@ -231,6 +175,100 @@ class ClientUser extends User {
|
||||
resolve(this);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* A user's status. Must be one of:
|
||||
* - `online`
|
||||
* - `idle`
|
||||
* - `invisible`
|
||||
* - `dnd` (do not disturb)
|
||||
* @typedef {string} PresenceStatus
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sets the status of the client user.
|
||||
* @param {PresenceStatus} status Status to change to
|
||||
* @returns {Promise<ClientUser>}
|
||||
*/
|
||||
setStatus(status) {
|
||||
return this.setPresence({ status });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the game the client user is playing.
|
||||
* @param {string} game Game being played
|
||||
* @param {string} [streamingURL] Twitch stream URL
|
||||
* @returns {Promise<ClientUser>}
|
||||
*/
|
||||
setGame(game, streamingURL) {
|
||||
return this.setPresence({ game: {
|
||||
name: game,
|
||||
url: streamingURL,
|
||||
} });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets/removes the AFK flag for the client user.
|
||||
* @param {boolean} afk Whether or not the user is AFK
|
||||
* @returns {Promise<ClientUser>}
|
||||
*/
|
||||
setAFK(afk) {
|
||||
return this.setPresence({ afk });
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches messages that mentioned the client's user
|
||||
* @param {Object} [options] Options for the fetch
|
||||
* @param {number} [options.limit=25] Maximum number of mentions to retrieve
|
||||
* @param {boolean} [options.roles=true] Whether to include role mentions
|
||||
* @param {boolean} [options.everyone=true] Whether to include everyone/here mentions
|
||||
* @param {Guild|string} [options.guild] Limit the search to a specific guild
|
||||
* @returns {Promise<Message[]>}
|
||||
*/
|
||||
fetchMentions(options = { limit: 25, roles: true, everyone: true, guild: null }) {
|
||||
return this.client.rest.methods.fetchMentions(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a friend request
|
||||
* <warn>This is only available when using a user account.</warn>
|
||||
* @param {UserResolvable} user The user to send the friend request to.
|
||||
* @returns {Promise<User>} The user the friend request was sent to.
|
||||
*/
|
||||
addFriend(user) {
|
||||
user = this.client.resolver.resolveUser(user);
|
||||
return this.client.rest.methods.addFriend(user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a friend
|
||||
* <warn>This is only available when using a user account.</warn>
|
||||
* @param {UserResolvable} user The user to remove from your friends
|
||||
* @returns {Promise<User>} The user that was removed
|
||||
*/
|
||||
removeFriend(user) {
|
||||
user = this.client.resolver.resolveUser(user);
|
||||
return this.client.rest.methods.removeFriend(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 {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 (icon.startsWith('data:')) {
|
||||
return this.client.rest.methods.createGuild({ name, icon, region });
|
||||
} else {
|
||||
return this.client.resolver.resolveBuffer(icon).then(data =>
|
||||
this.client.rest.methods.createGuild({ name, icon: data, region })
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ClientUser;
|
||||
|
||||
@@ -3,7 +3,7 @@ const TextBasedChannel = require('./interface/TextBasedChannel');
|
||||
const Collection = require('../util/Collection');
|
||||
|
||||
/**
|
||||
* Represents a Direct Message Channel between two users.
|
||||
* Represents a direct message channel between two users.
|
||||
* @extends {Channel}
|
||||
* @implements {TextBasedChannel}
|
||||
*/
|
||||
@@ -37,8 +37,9 @@ class DMChannel extends Channel {
|
||||
}
|
||||
|
||||
// These are here only for documentation purposes - they are implemented by TextBasedChannel
|
||||
send() { return; }
|
||||
sendMessage() { return; }
|
||||
sendTTSMessage() { return; }
|
||||
sendEmbed() { return; }
|
||||
sendFile() { return; }
|
||||
sendCode() { return; }
|
||||
fetchMessage() { return; }
|
||||
|
||||
@@ -2,19 +2,20 @@ const Constants = require('../util/Constants');
|
||||
const Collection = require('../util/Collection');
|
||||
|
||||
/**
|
||||
* Represents a Custom Emoji
|
||||
* Represents a custom emoji
|
||||
*/
|
||||
class Emoji {
|
||||
constructor(guild, data) {
|
||||
/**
|
||||
* The Client that instantiated this object
|
||||
* @name Emoji#client
|
||||
* @type {Client}
|
||||
* @readonly
|
||||
*/
|
||||
this.client = guild.client;
|
||||
Object.defineProperty(this, 'client', { enumerable: false, configurable: false });
|
||||
Object.defineProperty(this, 'client', { value: guild.client });
|
||||
|
||||
/**
|
||||
* The Guild this emoji is part of
|
||||
* The guild this emoji is part of
|
||||
* @type {Guild}
|
||||
*/
|
||||
this.guild = guild;
|
||||
@@ -24,13 +25,13 @@ class Emoji {
|
||||
|
||||
setup(data) {
|
||||
/**
|
||||
* The ID of the Emoji
|
||||
* The ID of the emoji
|
||||
* @type {string}
|
||||
*/
|
||||
this.id = data.id;
|
||||
|
||||
/**
|
||||
* The name of the Emoji
|
||||
* The name of the emoji
|
||||
* @type {string}
|
||||
*/
|
||||
this.name = data.name;
|
||||
@@ -87,7 +88,7 @@ class Emoji {
|
||||
* @readonly
|
||||
*/
|
||||
get url() {
|
||||
return `${Constants.Endpoints.CDN}/emojis/${this.id}.png`;
|
||||
return Constants.Endpoints.emoji(this.id);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,6 +102,39 @@ class Emoji {
|
||||
toString() {
|
||||
return this.requiresColons ? `<:${this.name}:${this.id}>` : this.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this emoji is the same as another one
|
||||
* @param {Emoji|Object} other the emoji to compare it to
|
||||
* @returns {boolean} whether the emoji is equal to the given emoji or not
|
||||
*/
|
||||
equals(other) {
|
||||
if (other instanceof Emoji) {
|
||||
return (
|
||||
other.id === this.id &&
|
||||
other.name === this.name &&
|
||||
other.managed === this.managed &&
|
||||
other.requiresColons === this.requiresColons
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
other.id === this.id &&
|
||||
other.name === this.name
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The identifier of this emoji, used for message reactions
|
||||
* @readonly
|
||||
* @type {string}
|
||||
*/
|
||||
get identifier() {
|
||||
if (this.id) {
|
||||
return `${this.name}:${this.id}`;
|
||||
}
|
||||
return encodeURIComponent(this.name);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Emoji;
|
||||
|
||||
@@ -57,7 +57,7 @@ class EvaluatedPermissions {
|
||||
* Checks whether the user has all specified permissions, and lists any missing permissions.
|
||||
* @param {PermissionResolvable[]} permissions The permissions to check for
|
||||
* @param {boolean} [explicit=false] Whether to require the user to explicitly have the exact permissions
|
||||
* @returns {array}
|
||||
* @returns {PermissionResolvable[]}
|
||||
*/
|
||||
missingPermissions(permissions, explicit = false) {
|
||||
return permissions.filter(p => !this.hasPermission(p, explicit));
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
const Channel = require('./Channel');
|
||||
const TextBasedChannel = require('./interface/TextBasedChannel');
|
||||
const Collection = require('../util/Collection');
|
||||
const arraysEqual = require('../util/ArraysEqual');
|
||||
|
||||
/*
|
||||
{ type: 3,
|
||||
@@ -90,7 +89,7 @@ class GroupDMChannel extends Channel {
|
||||
* 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
|
||||
* what most users need.
|
||||
* @param {GroupDMChannel} channel The channel to compare to
|
||||
* @param {GroupDMChannel} channel Channel to compare with
|
||||
* @returns {boolean}
|
||||
*/
|
||||
equals(channel) {
|
||||
@@ -101,16 +100,14 @@ class GroupDMChannel extends Channel {
|
||||
this.ownerID === channel.ownerID;
|
||||
|
||||
if (equal) {
|
||||
const thisIDs = this.recipients.keyArray();
|
||||
const otherIDs = channel.recipients.keyArray();
|
||||
return arraysEqual(thisIDs, otherIDs);
|
||||
return this.recipients.equals(channel.recipients);
|
||||
}
|
||||
|
||||
return equal;
|
||||
}
|
||||
|
||||
/**
|
||||
* When concatenated with a string, this automatically concatenates the Channel's name instead of the Channel object.
|
||||
* When concatenated with a string, this automatically concatenates the channel's name instead of the Channel object.
|
||||
* @returns {string}
|
||||
* @example
|
||||
* // logs: Hello from My Group DM!
|
||||
@@ -124,8 +121,9 @@ class GroupDMChannel extends Channel {
|
||||
}
|
||||
|
||||
// These are here only for documentation purposes - they are implemented by TextBasedChannel
|
||||
send() { return; }
|
||||
sendMessage() { return; }
|
||||
sendTTSMessage() { return; }
|
||||
sendEmbed() { return; }
|
||||
sendFile() { return; }
|
||||
sendCode() { return; }
|
||||
fetchMessage() { return; }
|
||||
|
||||
@@ -9,7 +9,7 @@ const cloneObject = require('../util/CloneObject');
|
||||
const arraysEqual = require('../util/ArraysEqual');
|
||||
|
||||
/**
|
||||
* Represents a Guild (or a Server) on Discord.
|
||||
* Represents a guild (or a server) on Discord.
|
||||
* <info>It's recommended to see if a guild is available before performing operations or reading data from it. You can
|
||||
* check this with `guild.available`.</info>
|
||||
*/
|
||||
@@ -17,33 +17,40 @@ class Guild {
|
||||
constructor(client, data) {
|
||||
/**
|
||||
* The Client that created the instance of the the Guild.
|
||||
* @name Guild#client
|
||||
* @type {Client}
|
||||
* @readonly
|
||||
*/
|
||||
this.client = client;
|
||||
Object.defineProperty(this, 'client', { enumerable: false, configurable: false });
|
||||
Object.defineProperty(this, 'client', { value: client });
|
||||
|
||||
/**
|
||||
* A Collection of members that are in this Guild. The key is the member's ID, the value is the member.
|
||||
* A collection of members that are in this guild. The key is the member's ID, the value is the member.
|
||||
* @type {Collection<string, GuildMember>}
|
||||
*/
|
||||
this.members = new Collection();
|
||||
|
||||
/**
|
||||
* A Collection of channels that are in this Guild. The key is the channel's ID, the value is the channel.
|
||||
* A collection of channels that are in this guild. The key is the channel's ID, the value is the channel.
|
||||
* @type {Collection<string, GuildChannel>}
|
||||
*/
|
||||
this.channels = new Collection();
|
||||
|
||||
/**
|
||||
* A Collection of roles that are in this Guild. The key is the role's ID, the value is the role.
|
||||
* A collection of roles that are in this guild. The key is the role's ID, the value is the role.
|
||||
* @type {Collection<string, Role>}
|
||||
*/
|
||||
this.roles = new Collection();
|
||||
|
||||
/**
|
||||
* A collection of presences in this guild
|
||||
* @type {Collection<string, Presence>}
|
||||
*/
|
||||
this.presences = new Collection();
|
||||
|
||||
if (!data) return;
|
||||
if (data.unavailable) {
|
||||
/**
|
||||
* Whether the Guild is available to access. If it is not available, it indicates a server outage.
|
||||
* Whether the guild is available to access. If it is not available, it indicates a server outage.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.available = false;
|
||||
@@ -90,7 +97,7 @@ class Guild {
|
||||
this.region = data.region;
|
||||
|
||||
/**
|
||||
* The full amount of members in this Guild as of `READY`
|
||||
* The full amount of members in this guild as of `READY`
|
||||
* @type {number}
|
||||
*/
|
||||
this.memberCount = data.member_count || this.memberCount;
|
||||
@@ -101,12 +108,6 @@ class Guild {
|
||||
*/
|
||||
this.large = data.large || this.large;
|
||||
|
||||
/**
|
||||
* A collection of presences in this Guild
|
||||
* @type {Collection<string, Presence>}
|
||||
*/
|
||||
this.presences = new Collection();
|
||||
|
||||
/**
|
||||
* An array of guild features.
|
||||
* @type {Object[]}
|
||||
@@ -114,7 +115,13 @@ class Guild {
|
||||
this.features = data.features;
|
||||
|
||||
/**
|
||||
* A Collection of emojis that are in this Guild. The key is the emoji's ID, the value is the emoji.
|
||||
* The ID of the application that created this guild (if applicable)
|
||||
* @type {?string}
|
||||
*/
|
||||
this.applicationID = data.application_id;
|
||||
|
||||
/**
|
||||
* A collection of emojis that are in this guild. The key is the emoji's ID, the value is the emoji.
|
||||
* @type {Collection<string, Emoji>}
|
||||
*/
|
||||
this.emojis = new Collection();
|
||||
@@ -242,7 +249,17 @@ class Guild {
|
||||
}
|
||||
|
||||
/**
|
||||
* The owner of the Guild
|
||||
* Gets the URL to this guild's splash (if it has one, otherwise it returns null)
|
||||
* @type {?string}
|
||||
* @readonly
|
||||
*/
|
||||
get splashURL() {
|
||||
if (!this.splash) return null;
|
||||
return Constants.Endpoints.guildSplash(this.id, this.splash);
|
||||
}
|
||||
|
||||
/**
|
||||
* The owner of the guild
|
||||
* @type {GuildMember}
|
||||
* @readonly
|
||||
*/
|
||||
@@ -256,6 +273,7 @@ class Guild {
|
||||
* @readonly
|
||||
*/
|
||||
get voiceConnection() {
|
||||
if (this.client.browser) return null;
|
||||
return this.client.voice.connections.get(this.id) || null;
|
||||
}
|
||||
|
||||
@@ -269,7 +287,7 @@ class Guild {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the GuildMember form of a User object, if the User is present in the guild.
|
||||
* Returns the GuildMember form of a User object, if the user is present in the guild.
|
||||
* @param {UserResolvable} user The user that you want to obtain the GuildMember of
|
||||
* @returns {?GuildMember}
|
||||
* @example
|
||||
@@ -281,7 +299,7 @@ class Guild {
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a Collection of banned users in this Guild.
|
||||
* Fetch a collection of banned users in this guild.
|
||||
* @returns {Promise<Collection<string, User>>}
|
||||
*/
|
||||
fetchBans() {
|
||||
@@ -289,7 +307,7 @@ 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() {
|
||||
@@ -318,7 +336,7 @@ class Guild {
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all the members in the Guild, even if they are offline. If the Guild has less than 250 members,
|
||||
* Fetches all the members in the guild, even if they are offline. If the guild has less than 250 members,
|
||||
* this should not be necessary.
|
||||
* @param {string} [query=''] An optional query to provide when fetching members
|
||||
* @returns {Promise<Guild>}
|
||||
@@ -344,6 +362,19 @@ class Guild {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The data for editing a guild
|
||||
* @typedef {Object} GuildEditData
|
||||
* @property {string} [name] The name of the guild
|
||||
* @property {string} [region] The region of the guild
|
||||
* @property {number} [verificationLevel] The verification level of the guild
|
||||
* @property {ChannelResolvable} [afkChannel] The AFK channel of the guild
|
||||
* @property {number} [afkTimeout] The AFK timeout of the guild
|
||||
* @property {Base64Resolvable} [icon] The icon of the guild
|
||||
* @property {GuildMemberResolvable} [owner] The owner of the guild
|
||||
* @property {Base64Resolvable} [splash] The splash screen of the guild
|
||||
*/
|
||||
|
||||
/**
|
||||
* Updates the Guild with new information - e.g. a new name.
|
||||
* @param {GuildEditData} data The data to update the guild with
|
||||
@@ -362,8 +393,8 @@ class Guild {
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit the name of the Guild.
|
||||
* @param {string} name The new name of the Guild
|
||||
* Edit the name of the guild.
|
||||
* @param {string} name The new name of the guild
|
||||
* @returns {Promise<Guild>}
|
||||
* @example
|
||||
* // edit the guild name
|
||||
@@ -376,8 +407,8 @@ class Guild {
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit the region of the Guild.
|
||||
* @param {Region} region The new region of the guild.
|
||||
* Edit the region of the guild.
|
||||
* @param {string} region The new region of the guild.
|
||||
* @returns {Promise<Guild>}
|
||||
* @example
|
||||
* // edit the guild region
|
||||
@@ -390,8 +421,8 @@ class Guild {
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit the verification level of the Guild.
|
||||
* @param {VerificationLevel} verificationLevel The new verification level of the guild
|
||||
* Edit the verification level of the guild.
|
||||
* @param {number} verificationLevel The new verification level of the guild
|
||||
* @returns {Promise<Guild>}
|
||||
* @example
|
||||
* // edit the guild verification level
|
||||
@@ -404,8 +435,8 @@ class Guild {
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit the AFK channel of the Guild.
|
||||
* @param {GuildChannelResolvable} afkChannel The new AFK channel
|
||||
* Edit the AFK channel of the guild.
|
||||
* @param {ChannelResolvable} afkChannel The new AFK channel
|
||||
* @returns {Promise<Guild>}
|
||||
* @example
|
||||
* // edit the guild AFK channel
|
||||
@@ -418,7 +449,7 @@ class Guild {
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit the AFK timeout of the Guild.
|
||||
* Edit the AFK timeout of the guild.
|
||||
* @param {number} afkTimeout The time in seconds that a user must be idle to be considered AFK
|
||||
* @returns {Promise<Guild>}
|
||||
* @example
|
||||
@@ -432,7 +463,7 @@ class Guild {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a new Guild Icon.
|
||||
* Set a new guild icon.
|
||||
* @param {Base64Resolvable} icon The new icon of the guild
|
||||
* @returns {Promise<Guild>}
|
||||
* @example
|
||||
@@ -446,8 +477,8 @@ class Guild {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new owner of the Guild.
|
||||
* @param {GuildMemberResolvable} owner The new owner of the Guild
|
||||
* Sets a new owner of the guild.
|
||||
* @param {GuildMemberResolvable} owner The new owner of the guild
|
||||
* @returns {Promise<Guild>}
|
||||
* @example
|
||||
* // edit the guild owner
|
||||
@@ -460,7 +491,7 @@ class Guild {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a new Guild Splash Logo.
|
||||
* Set a new guild splash screen.
|
||||
* @param {Base64Resolvable} splash The new splash screen of the guild
|
||||
* @returns {Promise<Guild>}
|
||||
* @example
|
||||
@@ -490,7 +521,7 @@ class Guild {
|
||||
}
|
||||
|
||||
/**
|
||||
* Unbans a user from the Guild.
|
||||
* Unbans a user from the guild.
|
||||
* @param {UserResolvable} user The user to unban
|
||||
* @returns {Promise<User>}
|
||||
* @example
|
||||
@@ -525,16 +556,18 @@ class Guild {
|
||||
}
|
||||
|
||||
/**
|
||||
* Syncs this guild (already done automatically every 30 seconds). Only applicable to user accounts.
|
||||
* Syncs this guild (already done automatically every 30 seconds).
|
||||
* <warn>This is only available when using a user account.</warn>
|
||||
*/
|
||||
sync() {
|
||||
if (!this.client.user.bot) this.client.syncGuilds([this]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Channel in the 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
|
||||
* @returns {Promise<TextChannel|VoiceChannel>}
|
||||
* @example
|
||||
* // create a new text channel
|
||||
@@ -542,8 +575,8 @@ class Guild {
|
||||
* .then(channel => console.log(`Created new channel ${channel}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
createChannel(name, type) {
|
||||
return this.client.rest.methods.createChannel(this, name, type);
|
||||
createChannel(name, type, overwrites) {
|
||||
return this.client.rest.methods.createChannel(this, name, type, overwrites);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -569,7 +602,7 @@ class Guild {
|
||||
|
||||
/**
|
||||
* Creates a new custom emoji in the guild.
|
||||
* @param {FileResolveable} attachment The image for the emoji.
|
||||
* @param {BufferResolvable} attachment The image for the emoji.
|
||||
* @param {string} name The name for the emoji.
|
||||
* @returns {Promise<Emoji>} The created emoji.
|
||||
* @example
|
||||
@@ -584,13 +617,14 @@ class Guild {
|
||||
* .catch(console.error);
|
||||
*/
|
||||
createEmoji(attachment, name) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.client.resolver.resolveFile(attachment).then(file => {
|
||||
let base64 = new Buffer(file, 'binary').toString('base64');
|
||||
let dataURI = `data:;base64,${base64}`;
|
||||
this.client.rest.methods.createEmoji(this, dataURI, name)
|
||||
.then(resolve).catch(reject);
|
||||
}).catch(reject);
|
||||
return new Promise(resolve => {
|
||||
if (attachment.startsWith('data:')) {
|
||||
resolve(this.client.rest.methods.createEmoji(this, attachment, name));
|
||||
} else {
|
||||
this.client.resolver.resolveBuffer(attachment).then(data =>
|
||||
resolve(this.client.rest.methods.createEmoji(this, data, name))
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -637,21 +671,34 @@ class Guild {
|
||||
* @returns {Promise<Guild>}
|
||||
*/
|
||||
setRolePosition(role, position) {
|
||||
if (role instanceof Role) {
|
||||
role = role.id;
|
||||
} else if (typeof role !== 'string') {
|
||||
return Promise.reject(new Error('Supplied role is not a role or string'));
|
||||
if (typeof role === 'string') {
|
||||
role = this.roles.get(role);
|
||||
if (!role) return Promise.reject(new Error('Supplied role is not a role or string.'));
|
||||
}
|
||||
|
||||
position = Number(position);
|
||||
if (isNaN(position)) {
|
||||
return Promise.reject(new Error('Supplied position is not a number'));
|
||||
if (isNaN(position)) return Promise.reject(new Error('Supplied position is not a number.'));
|
||||
|
||||
const lowestAffected = Math.min(role.position, position);
|
||||
const highestAffected = Math.max(role.position, position);
|
||||
|
||||
const rolesToUpdate = this.roles.filter(r => r.position >= lowestAffected && r.position <= highestAffected);
|
||||
|
||||
// stop role positions getting stupidly inflated
|
||||
if (position > role.position) {
|
||||
position = rolesToUpdate.first().position;
|
||||
} else {
|
||||
position = rolesToUpdate.last().position;
|
||||
}
|
||||
|
||||
const updatedRoles = this.roles.array().map(r => ({
|
||||
id: r.id,
|
||||
position: r.id === role ? position : r.position < position ? r.position : r.position + 1,
|
||||
}));
|
||||
const updatedRoles = [];
|
||||
|
||||
for (const uRole of rolesToUpdate.values()) {
|
||||
updatedRoles.push({
|
||||
id: uRole.id,
|
||||
position: uRole.id === role.id ? position : uRole.position + (position < role.position ? 1 : -1),
|
||||
});
|
||||
}
|
||||
|
||||
return this.client.rest.methods.setRolePositions(this.id, updatedRoles);
|
||||
}
|
||||
@@ -660,7 +707,7 @@ class Guild {
|
||||
* Whether this Guild equals another Guild. It compares all properties, so for most operations
|
||||
* it is advisable to just compare `guild.id === guild2.id` as it is much faster and is often
|
||||
* what most users need.
|
||||
* @param {Guild} guild The guild to compare
|
||||
* @param {Guild} guild Guild to compare with
|
||||
* @returns {boolean}
|
||||
*/
|
||||
equals(guild) {
|
||||
@@ -691,7 +738,7 @@ class Guild {
|
||||
}
|
||||
|
||||
/**
|
||||
* When concatenated with a string, this automatically concatenates the Guild's name instead of the Guild object.
|
||||
* When concatenated with a string, this automatically concatenates the guild's name instead of the Guild object.
|
||||
* @returns {string}
|
||||
* @example
|
||||
* // logs: Hello from My Guild!
|
||||
@@ -712,7 +759,7 @@ class Guild {
|
||||
const member = new GuildMember(this, guildUser);
|
||||
this.members.set(member.id, member);
|
||||
|
||||
if (this._rawVoiceStates && this._rawVoiceStates.get(member.user.id)) {
|
||||
if (this._rawVoiceStates && this._rawVoiceStates.has(member.user.id)) {
|
||||
const voiceState = this._rawVoiceStates.get(member.user.id);
|
||||
member.serverMute = voiceState.mute;
|
||||
member.serverDeaf = voiceState.deaf;
|
||||
@@ -720,7 +767,11 @@ class Guild {
|
||||
member.selfDeaf = voiceState.self_deaf;
|
||||
member.voiceSessionID = voiceState.session_id;
|
||||
member.voiceChannelID = voiceState.channel_id;
|
||||
this.channels.get(voiceState.channel_id).members.set(member.user.id, member);
|
||||
if (this.client.channels.has(voiceState.channel_id)) {
|
||||
this.client.channels.get(voiceState.channel_id).members.set(member.user.id, member);
|
||||
} else {
|
||||
this.client.emit('warn', `Member ${member.id} added in guild ${this.id} with an uncached voice channel`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -746,7 +797,7 @@ class Guild {
|
||||
|
||||
if (this.client.ws.status === Constants.Status.READY && notSame) {
|
||||
/**
|
||||
* Emitted whenever a Guild Member changes - i.e. new role, removed role, nickname
|
||||
* Emitted whenever a guild member changes - i.e. new role, removed role, nickname
|
||||
* @event Client#guildMemberUpdate
|
||||
* @param {GuildMember} oldMember The member before the update
|
||||
* @param {GuildMember} newMember The member after the update
|
||||
@@ -770,7 +821,7 @@ class Guild {
|
||||
if (member && member.speaking !== speaking) {
|
||||
member.speaking = speaking;
|
||||
/**
|
||||
* Emitted once a Guild Member starts/stops speaking
|
||||
* Emitted once a guild member starts/stops speaking
|
||||
* @event Client#guildMemberSpeaking
|
||||
* @param {GuildMember} member The member that started/stopped speaking
|
||||
* @param {boolean} speaking Whether or not the member is speaking
|
||||
|
||||
@@ -4,10 +4,9 @@ const PermissionOverwrites = require('./PermissionOverwrites');
|
||||
const EvaluatedPermissions = require('./EvaluatedPermissions');
|
||||
const Constants = require('../util/Constants');
|
||||
const Collection = require('../util/Collection');
|
||||
const arraysEqual = require('../util/ArraysEqual');
|
||||
|
||||
/**
|
||||
* Represents a Guild Channel (i.e. Text Channels and Voice Channels)
|
||||
* Represents a guild channel (i.e. text channels and voice channels)
|
||||
* @extends {Channel}
|
||||
*/
|
||||
class GuildChannel extends Channel {
|
||||
@@ -25,7 +24,7 @@ class GuildChannel extends Channel {
|
||||
super.setup(data);
|
||||
|
||||
/**
|
||||
* The name of the Guild Channel
|
||||
* The name of the guild channel
|
||||
* @type {string}
|
||||
*/
|
||||
this.name = data.name;
|
||||
@@ -66,11 +65,11 @@ class GuildChannel extends Channel {
|
||||
|
||||
const overwrites = this.overwritesFor(member, true, roles);
|
||||
for (const overwrite of overwrites.role.concat(overwrites.member)) {
|
||||
permissions &= ~overwrite.denyData;
|
||||
permissions |= overwrite.allowData;
|
||||
permissions &= ~overwrite.deny;
|
||||
permissions |= overwrite.allow;
|
||||
}
|
||||
|
||||
const admin = Boolean(permissions & (Constants.PermissionFlags.ADMINISTRATOR));
|
||||
const admin = Boolean(permissions & Constants.PermissionFlags.ADMINISTRATOR);
|
||||
if (admin) permissions = Constants.ALL_PERMISSIONS;
|
||||
|
||||
return new EvaluatedPermissions(member, permissions);
|
||||
@@ -144,8 +143,8 @@ class GuildChannel extends Channel {
|
||||
const prevOverwrite = this.permissionOverwrites.get(userOrRole.id);
|
||||
|
||||
if (prevOverwrite) {
|
||||
payload.allow = prevOverwrite.allowData;
|
||||
payload.deny = prevOverwrite.denyData;
|
||||
payload.allow = prevOverwrite.allow;
|
||||
payload.deny = prevOverwrite.deny;
|
||||
}
|
||||
|
||||
for (const perm in options) {
|
||||
@@ -155,18 +154,41 @@ class GuildChannel extends Channel {
|
||||
} else if (options[perm] === false) {
|
||||
payload.allow &= ~(Constants.PermissionFlags[perm] || 0);
|
||||
payload.deny |= Constants.PermissionFlags[perm] || 0;
|
||||
} else if (options[perm] === null) {
|
||||
payload.allow &= ~(Constants.PermissionFlags[perm] || 0);
|
||||
payload.deny &= ~(Constants.PermissionFlags[perm] || 0);
|
||||
}
|
||||
}
|
||||
|
||||
return this.client.rest.methods.setChannelOverwrite(this, payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* The data for a guild channel
|
||||
* @typedef {Object} ChannelData
|
||||
* @property {string} [name] The name of the channel
|
||||
* @property {number} [position] The position of the channel
|
||||
* @property {string} [topic] The topic of the text channel
|
||||
* @property {number} [bitrate] The bitrate of the voice channel
|
||||
* @property {number} [userLimit] The user limit of the channel
|
||||
*/
|
||||
|
||||
/**
|
||||
* Edits the channel
|
||||
* @param {ChannelData} data The new data for the channel
|
||||
* @returns {Promise<GuildChannel>}
|
||||
* @example
|
||||
* // edit a channel
|
||||
* channel.edit({name: 'new-channel'})
|
||||
* .then(c => console.log(`Edited channel ${c}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
edit(data) {
|
||||
return this.client.rest.methods.updateChannel(this, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a new name for the Guild Channel
|
||||
* Set a new name for the guild channel
|
||||
* @param {string} name The new name for the guild channel
|
||||
* @returns {Promise<GuildChannel>}
|
||||
* @example
|
||||
@@ -176,11 +198,11 @@ class GuildChannel extends Channel {
|
||||
* .catch(console.error);
|
||||
*/
|
||||
setName(name) {
|
||||
return this.client.rest.methods.updateChannel(this, { name });
|
||||
return this.edit({ name });
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a new position for the Guild Channel
|
||||
* Set a new position for the guild channel
|
||||
* @param {number} position The new position for the guild channel
|
||||
* @returns {Promise<GuildChannel>}
|
||||
* @example
|
||||
@@ -194,7 +216,7 @@ class GuildChannel extends Channel {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a new topic for the Guild Channel
|
||||
* Set a new topic for the guild channel
|
||||
* @param {string} topic The new topic for the guild channel
|
||||
* @returns {Promise<GuildChannel>}
|
||||
* @example
|
||||
@@ -208,15 +230,15 @@ class GuildChannel extends Channel {
|
||||
}
|
||||
|
||||
/**
|
||||
* Options given when creating a Guild Channel Invite
|
||||
* Options given when creating a guild channel invite
|
||||
* @typedef {Object} InviteOptions
|
||||
* @property {boolean} [temporary=false] Whether the invite should kick users after 24hrs if they are not given a role
|
||||
* @property {number} [maxAge=0] Time in seconds the invite expires in
|
||||
* @property {maxUses} [maxUses=0] Maximum amount of uses for this invite
|
||||
* @property {number} [maxUses=0] Maximum amount of uses for this invite
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create an invite to this Guild Channel
|
||||
* Create an invite to this guild channel
|
||||
* @param {InviteOptions} [options={}] The options for the invite
|
||||
* @returns {Promise<Invite>}
|
||||
*/
|
||||
@@ -224,10 +246,20 @@ class GuildChannel extends Channel {
|
||||
return this.client.rest.methods.createChannelInvite(this, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone this 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
|
||||
* @returns {Promise<GuildChannel>}
|
||||
*/
|
||||
clone(name = this.name, withPermissions = true) {
|
||||
return this.guild.createChannel(name, this.type, withPermissions ? this.permissionOverwrites : []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this channel has the same type, topic, position, name, overwrites and ID as another channel.
|
||||
* In most cases, a simple `channel.id === channel2.id` will do, and is much faster too.
|
||||
* @param {GuildChannel} channel The channel to compare this channel to
|
||||
* @param {GuildChannel} channel Channel to compare with
|
||||
* @returns {boolean}
|
||||
*/
|
||||
equals(channel) {
|
||||
@@ -240,9 +272,7 @@ class GuildChannel extends Channel {
|
||||
|
||||
if (equal) {
|
||||
if (this.permissionOverwrites && channel.permissionOverwrites) {
|
||||
const thisIDSet = this.permissionOverwrites.keyArray();
|
||||
const otherIDSet = channel.permissionOverwrites.keyArray();
|
||||
equal = arraysEqual(thisIDSet, otherIDSet);
|
||||
equal = this.permissionOverwrites.equals(channel.permissionOverwrites);
|
||||
} else {
|
||||
equal = !this.permissionOverwrites && !channel.permissionOverwrites;
|
||||
}
|
||||
@@ -252,7 +282,7 @@ class GuildChannel extends Channel {
|
||||
}
|
||||
|
||||
/**
|
||||
* When concatenated with a string, this automatically returns the Channel's mention instead of the Channel object.
|
||||
* When concatenated with a string, this automatically returns the channel's mention instead of the Channel object.
|
||||
* @returns {string}
|
||||
* @example
|
||||
* // Outputs: Hello from #general
|
||||
|
||||
@@ -6,17 +6,18 @@ const Collection = require('../util/Collection');
|
||||
const Presence = require('./Presence').Presence;
|
||||
|
||||
/**
|
||||
* Represents a Member of a Guild on Discord
|
||||
* Represents a member of a guild on Discord
|
||||
* @implements {TextBasedChannel}
|
||||
*/
|
||||
class GuildMember {
|
||||
constructor(guild, data) {
|
||||
/**
|
||||
* The client that instantiated this GuildMember
|
||||
* The Client that instantiated this GuildMember
|
||||
* @name GuildMember#client
|
||||
* @type {Client}
|
||||
* @readonly
|
||||
*/
|
||||
this.client = guild.client;
|
||||
Object.defineProperty(this, 'client', { enumerable: false, configurable: false });
|
||||
Object.defineProperty(this, 'client', { value: guild.client });
|
||||
|
||||
/**
|
||||
* The guild that this member is part of
|
||||
@@ -32,6 +33,12 @@ class GuildMember {
|
||||
|
||||
this._roles = [];
|
||||
if (data) this.setup(data);
|
||||
|
||||
/**
|
||||
* The ID of the last message sent by the member in their guild, if one was sent.
|
||||
* @type {?string}
|
||||
*/
|
||||
this.lastMessageID = null;
|
||||
}
|
||||
|
||||
setup(data) {
|
||||
@@ -78,7 +85,7 @@ class GuildMember {
|
||||
this.speaking = false;
|
||||
|
||||
/**
|
||||
* The nickname of this Guild Member, if they have one
|
||||
* The nickname of this guild member, if they have one
|
||||
* @type {?string}
|
||||
*/
|
||||
this.nickname = data.nick || null;
|
||||
@@ -103,7 +110,7 @@ class GuildMember {
|
||||
}
|
||||
|
||||
/**
|
||||
* The presence of this Guild Member
|
||||
* The presence of this guild member
|
||||
* @type {Presence}
|
||||
* @readonly
|
||||
*/
|
||||
@@ -167,7 +174,7 @@ class GuildMember {
|
||||
}
|
||||
|
||||
/**
|
||||
* The ID of this User
|
||||
* The ID of this user
|
||||
* @type {string}
|
||||
* @readonly
|
||||
*/
|
||||
@@ -175,6 +182,15 @@ class GuildMember {
|
||||
return this.user.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* The nickname of the member, or their username if they don't have one
|
||||
* @type {string}
|
||||
* @readonly
|
||||
*/
|
||||
get displayName() {
|
||||
return this.nickname || this.user.username;
|
||||
}
|
||||
|
||||
/**
|
||||
* The overall set of permissions for the guild member, taking only roles into account
|
||||
* @type {EvaluatedPermissions}
|
||||
@@ -187,7 +203,7 @@ class GuildMember {
|
||||
const roles = this.roles;
|
||||
for (const role of roles.values()) permissions |= role.permissions;
|
||||
|
||||
const admin = Boolean(permissions & (Constants.PermissionFlags.ADMINISTRATOR));
|
||||
const admin = Boolean(permissions & Constants.PermissionFlags.ADMINISTRATOR);
|
||||
if (admin) permissions = Constants.ALL_PERMISSIONS;
|
||||
|
||||
return new EvaluatedPermissions(this, permissions);
|
||||
@@ -256,14 +272,14 @@ class GuildMember {
|
||||
* Checks whether the roles of the member allows them to perform specific actions, and lists any missing permissions.
|
||||
* @param {PermissionResolvable[]} permissions The permissions to check for
|
||||
* @param {boolean} [explicit=false] Whether to require the member to explicitly have the exact permissions
|
||||
* @returns {array}
|
||||
* @returns {PermissionResolvable[]}
|
||||
*/
|
||||
missingPermissions(permissions, explicit = false) {
|
||||
return permissions.filter(p => !this.hasPermission(p, explicit));
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit a Guild Member
|
||||
* Edit a guild member
|
||||
* @param {GuildmemberEditData} data The data to edit the member with
|
||||
* @returns {Promise<GuildMember>}
|
||||
*/
|
||||
@@ -290,7 +306,7 @@ class GuildMember {
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the Guild Member to the given channel.
|
||||
* Moves the guild member to the given channel.
|
||||
* @param {ChannelResolvable} channel The channel to move the member to
|
||||
* @returns {Promise<GuildMember>}
|
||||
*/
|
||||
@@ -299,7 +315,7 @@ class GuildMember {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Roles applied to the member.
|
||||
* Sets the roles applied to the member.
|
||||
* @param {Collection<string, Role>|Role[]|string[]} roles The roles or role IDs to apply
|
||||
* @returns {Promise<GuildMember>}
|
||||
*/
|
||||
@@ -308,12 +324,13 @@ class GuildMember {
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a single Role to the member.
|
||||
* Adds a single role to the member.
|
||||
* @param {Role|string} role The role or ID of the role to add
|
||||
* @returns {Promise<GuildMember>}
|
||||
*/
|
||||
addRole(role) {
|
||||
return this.addRoles([role]);
|
||||
if (!(role instanceof Role)) role = this.guild.roles.get(role);
|
||||
return this.client.rest.methods.addMemberRole(this, role);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -333,12 +350,13 @@ class GuildMember {
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a single Role from the member.
|
||||
* Removes a single role from the member.
|
||||
* @param {Role|string} role The role or ID of the role to remove
|
||||
* @returns {Promise<GuildMember>}
|
||||
*/
|
||||
removeRole(role) {
|
||||
return this.removeRoles([role]);
|
||||
if (!(role instanceof Role)) role = this.guild.roles.get(role);
|
||||
return this.client.rest.methods.removeMemberRole(this, role);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -363,8 +381,8 @@ class GuildMember {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the nickname for the Guild Member
|
||||
* @param {string} nick The nickname for the Guild Member
|
||||
* Set the nickname for the guild member
|
||||
* @param {string} nick The nickname for the guild member
|
||||
* @returns {Promise<GuildMember>}
|
||||
*/
|
||||
setNickname(nick) {
|
||||
@@ -372,7 +390,7 @@ class GuildMember {
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes any DMs with this Guild Member
|
||||
* Deletes any DMs with this guild member
|
||||
* @returns {Promise<DMChannel>}
|
||||
*/
|
||||
deleteDM() {
|
||||
@@ -380,7 +398,7 @@ class GuildMember {
|
||||
}
|
||||
|
||||
/**
|
||||
* Kick this member from the Guild
|
||||
* Kick this member from the guild
|
||||
* @returns {Promise<GuildMember>}
|
||||
*/
|
||||
kick() {
|
||||
@@ -388,7 +406,7 @@ class GuildMember {
|
||||
}
|
||||
|
||||
/**
|
||||
* Ban this Guild Member
|
||||
* Ban this guild member
|
||||
* @param {number} [deleteDays=0] The amount of days worth of messages from this member that should
|
||||
* also be deleted. Between `0` and `7`.
|
||||
* @returns {Promise<GuildMember>}
|
||||
@@ -401,7 +419,7 @@ class GuildMember {
|
||||
}
|
||||
|
||||
/**
|
||||
* When concatenated with a string, this automatically concatenates the User's mention instead of the Member object.
|
||||
* When concatenated with a string, this automatically concatenates the user's mention instead of the Member object.
|
||||
* @returns {string}
|
||||
* @example
|
||||
* // logs: Hello from <@123456789>!
|
||||
@@ -412,8 +430,9 @@ class GuildMember {
|
||||
}
|
||||
|
||||
// These are here only for documentation purposes - they are implemented by TextBasedChannel
|
||||
send() { return; }
|
||||
sendMessage() { return; }
|
||||
sendTTSMessage() { return; }
|
||||
sendEmbed() { return; }
|
||||
sendFile() { return; }
|
||||
sendCode() { return; }
|
||||
}
|
||||
|
||||
@@ -24,25 +24,26 @@ const Constants = require('../util/Constants');
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents an Invitation to a Guild Channel.
|
||||
* Represents an invitation to a guild channel.
|
||||
* <warn>The only guaranteed properties are `code`, `guild` and `channel`. Other properties can be missing.</warn>
|
||||
*/
|
||||
class Invite {
|
||||
constructor(client, data) {
|
||||
/**
|
||||
* The client that instantiated the invite
|
||||
* @name Invite#client
|
||||
* @type {Client}
|
||||
* @readonly
|
||||
*/
|
||||
this.client = client;
|
||||
Object.defineProperty(this, 'client', { enumerable: false, configurable: false });
|
||||
Object.defineProperty(this, 'client', { value: client });
|
||||
|
||||
this.setup(data);
|
||||
}
|
||||
|
||||
setup(data) {
|
||||
/**
|
||||
* The Guild the invite is for. If this Guild is already known, this will be a Guild object. If the Guild is
|
||||
* unknown, this will be a Partial Guild.
|
||||
* The guild the invite is for. If this guild is already known, this will be a Guild object. If the guild is
|
||||
* unknown, this will be a PartialGuild object.
|
||||
* @type {Guild|PartialGuild}
|
||||
*/
|
||||
this.guild = this.client.guilds.get(data.guild.id) || new PartialGuild(this.client, data.guild);
|
||||
@@ -86,8 +87,8 @@ class Invite {
|
||||
}
|
||||
|
||||
/**
|
||||
* The Channel the invite is for. If this Channel is already known, this will be a GuildChannel object.
|
||||
* If the Channel is unknown, this will be a Partial Guild Channel.
|
||||
* The channel the invite is for. If this channel is already known, this will be a GuildChannel object.
|
||||
* If the channel is unknown, this will be a PartialGuildChannel object.
|
||||
* @type {GuildChannel|PartialGuildChannel}
|
||||
*/
|
||||
this.channel = this.client.channels.get(data.channel.id) || new PartialGuildChannel(this.client, data.channel);
|
||||
@@ -144,7 +145,7 @@ class Invite {
|
||||
}
|
||||
|
||||
/**
|
||||
* When concatenated with a string, this automatically concatenates the Invite's URL instead of the object.
|
||||
* When concatenated with a string, this automatically concatenates the invite's URL instead of the object.
|
||||
* @returns {string}
|
||||
* @example
|
||||
* // logs: Invite: https://discord.gg/A1b2C3
|
||||
|
||||
@@ -1,20 +1,25 @@
|
||||
const Attachment = require('./MessageAttachment');
|
||||
const Embed = require('./MessageEmbed');
|
||||
const MessageReaction = require('./MessageReaction');
|
||||
const Collection = require('../util/Collection');
|
||||
const Constants = require('../util/Constants');
|
||||
const escapeMarkdown = require('../util/EscapeMarkdown');
|
||||
|
||||
// Done purely for GuildMember, which would cause a bad circular dependency
|
||||
const Discord = require('..');
|
||||
|
||||
/**
|
||||
* Represents a Message on Discord
|
||||
* Represents a message on Discord
|
||||
*/
|
||||
class Message {
|
||||
constructor(channel, data, client) {
|
||||
/**
|
||||
* The client that instantiated the Message
|
||||
* The Client that instantiated the Message
|
||||
* @name Message#client
|
||||
* @type {Client}
|
||||
* @readonly
|
||||
*/
|
||||
this.client = client;
|
||||
Object.defineProperty(this, 'client', { enumerable: false, configurable: false });
|
||||
Object.defineProperty(this, 'client', { value: client });
|
||||
|
||||
/**
|
||||
* The channel that the message was sent in
|
||||
@@ -25,7 +30,7 @@ class Message {
|
||||
if (data) this.setup(data);
|
||||
}
|
||||
|
||||
setup(data) {
|
||||
setup(data) { // eslint-disable-line complexity
|
||||
/**
|
||||
* The ID of the message (unique in the channel it was sent)
|
||||
* @type {string}
|
||||
@@ -51,7 +56,7 @@ 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
|
||||
* 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}
|
||||
*/
|
||||
@@ -83,7 +88,7 @@ class Message {
|
||||
|
||||
/**
|
||||
* A list of embeds in the message - e.g. YouTube Player
|
||||
* @type {Embed[]}
|
||||
* @type {MessageEmbed[]}
|
||||
*/
|
||||
this.embeds = data.embeds.map(e => new Embed(this, e));
|
||||
|
||||
@@ -148,6 +153,25 @@ class Message {
|
||||
}
|
||||
|
||||
this._edits = [];
|
||||
|
||||
/**
|
||||
* A collection of reactions to this message, mapped by the reaction "id".
|
||||
* @type {Collection<string, MessageReaction>}
|
||||
*/
|
||||
this.reactions = new Collection();
|
||||
|
||||
if (data.reactions && data.reactions.length > 0) {
|
||||
for (const reaction of data.reactions) {
|
||||
const id = reaction.emoji.id ? `${reaction.emoji.name}:${reaction.emoji.id}` : reaction.emoji.name;
|
||||
this.reactions.set(id, new MessageReaction(this, reaction.emoji, reaction.count, reaction.me));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ID of the webhook that sent the message, if applicable
|
||||
* @type {?string}
|
||||
*/
|
||||
this.webhookID = data.webhook_id || null;
|
||||
}
|
||||
|
||||
patch(data) { // eslint-disable-line complexity
|
||||
@@ -199,6 +223,15 @@ class Message {
|
||||
if (chan) this.mentions.channels.set(chan.id, chan);
|
||||
}
|
||||
}
|
||||
if (data.reactions) {
|
||||
this.reactions = new Collection();
|
||||
if (data.reactions.length > 0) {
|
||||
for (const reaction of data.reactions) {
|
||||
const id = reaction.emoji.id ? `${reaction.emoji.name}:${reaction.emoji.id}` : reaction.emoji.name;
|
||||
this.reactions.set(id, new MessageReaction(this, data.emoji, data.count, data.me));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -266,14 +299,16 @@ class Message {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* An array of cached versions of the message, including the current version.
|
||||
* Sorted from latest (first) to oldest (last).
|
||||
* @type {Message[]}
|
||||
* @readonly
|
||||
*/
|
||||
get edits() {
|
||||
return this._edits.slice().unshift(this);
|
||||
const copy = this._edits.slice();
|
||||
copy.unshift(this);
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -317,9 +352,30 @@ class Message {
|
||||
return this.mentions.users.has(data) || this.mentions.channels.has(data) || this.mentions.roles.has(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not a guild member is mentioned in this message. Takes into account
|
||||
* user mentions, role mentions, and @everyone/@here mentions.
|
||||
* @param {GuildMember|User} member Member/user to check for a mention of
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isMemberMentioned(member) {
|
||||
if (this.mentions.everyone) return true;
|
||||
if (this.mentions.users.has(member.id)) return true;
|
||||
if (member instanceof Discord.GuildMember && member.roles.some(r => this.mentions.roles.has(r.id))) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options that can be passed into editMessage
|
||||
* @typedef {Object} MessageEditOptions
|
||||
* @property {Object} [embed] An embed to be added/edited
|
||||
* @property {string|boolean} [code] Language for optional codeblock formatting to apply
|
||||
*/
|
||||
|
||||
/**
|
||||
* Edit the content of the message
|
||||
* @param {StringResolvable} content The new content for the message
|
||||
* @param {StringResolvable} [content] The new content for the message
|
||||
* @param {MessageEditOptions} [options] The options to provide
|
||||
* @returns {Promise<Message>}
|
||||
* @example
|
||||
* // update the content of a message
|
||||
@@ -327,8 +383,14 @@ class Message {
|
||||
* .then(msg => console.log(`Updated the content of a message from ${msg.author}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
edit(content) {
|
||||
return this.client.rest.methods.updateMessage(this, content);
|
||||
edit(content, options) {
|
||||
if (!options && typeof content === 'object') {
|
||||
options = content;
|
||||
content = '';
|
||||
} else if (!options) {
|
||||
options = {};
|
||||
}
|
||||
return this.client.rest.methods.updateMessage(this, content, options);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -358,6 +420,26 @@ class Message {
|
||||
return this.client.rest.methods.unpinMessage(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a reaction to the message
|
||||
* @param {string|Emoji|ReactionEmoji} emoji Emoji to react with
|
||||
* @returns {Promise<MessageReaction>}
|
||||
*/
|
||||
react(emoji) {
|
||||
emoji = this.client.resolver.resolveEmojiIdentifier(emoji);
|
||||
if (!emoji) throw new TypeError('Emoji must be a string or Emoji/ReactionEmoji');
|
||||
|
||||
return this.client.rest.methods.addMessageReaction(this, emoji);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all reactions from a message
|
||||
* @returns {Promise<Message>}
|
||||
*/
|
||||
clearReactions() {
|
||||
return this.client.rest.methods.removeMessageReactions(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the message
|
||||
* @param {number} [timeout=0] How long to wait to delete the message in milliseconds
|
||||
@@ -369,13 +451,15 @@ class Message {
|
||||
* .catch(console.error);
|
||||
*/
|
||||
delete(timeout = 0) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.client.setTimeout(() => {
|
||||
this.client.rest.methods.deleteMessage(this)
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
}, timeout);
|
||||
});
|
||||
if (timeout <= 0) {
|
||||
return this.client.rest.methods.deleteMessage(this);
|
||||
} else {
|
||||
return new Promise(resolve => {
|
||||
this.client.setTimeout(() => {
|
||||
resolve(this.delete());
|
||||
}, timeout);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -385,21 +469,22 @@ class Message {
|
||||
* @returns {Promise<Message|Message[]>}
|
||||
* @example
|
||||
* // reply to a message
|
||||
* message.reply('Hey, I'm a reply!')
|
||||
* message.reply('Hey, I\'m a reply!')
|
||||
* .then(msg => console.log(`Sent a reply to ${msg.author}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
reply(content, options = {}) {
|
||||
content = this.client.resolver.resolveString(content);
|
||||
const prepend = this.guild ? `${this.author}, ` : '';
|
||||
content = `${prepend}${content}`;
|
||||
content = `${this.guild || this.channel.type === 'group' ? `${this.author}, ` : ''}${content}`;
|
||||
return this.channel.send(content, options);
|
||||
}
|
||||
|
||||
if (options.split) {
|
||||
if (typeof options.split !== 'object') options.split = {};
|
||||
if (!options.split.prepend) options.split.prepend = prepend;
|
||||
}
|
||||
|
||||
return this.client.rest.methods.sendMessage(this.channel, content, options);
|
||||
/**
|
||||
* Fetches the webhook used to create this message.
|
||||
* @returns {Promise<?Webhook>}
|
||||
*/
|
||||
fetchWebhook() {
|
||||
if (!this.webhookID) return Promise.reject(new Error('The message was not sent by a webhook.'));
|
||||
return this.client.fetchWebhook(this.webhookID);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -433,7 +518,7 @@ class Message {
|
||||
}
|
||||
|
||||
/**
|
||||
* When concatenated with a string, this automatically concatenates the Message's content instead of the object.
|
||||
* When concatenated with a string, this automatically concatenates the message's content instead of the object.
|
||||
* @returns {string}
|
||||
* @example
|
||||
* // logs: Message: This is a message!
|
||||
@@ -442,6 +527,42 @@ class Message {
|
||||
toString() {
|
||||
return this.content;
|
||||
}
|
||||
|
||||
_addReaction(emoji, user) {
|
||||
const emojiID = emoji.id ? `${emoji.name}:${emoji.id}` : emoji.name;
|
||||
let reaction;
|
||||
if (this.reactions.has(emojiID)) {
|
||||
reaction = this.reactions.get(emojiID);
|
||||
if (!reaction.me) reaction.me = user.id === this.client.user.id;
|
||||
} else {
|
||||
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++;
|
||||
return reaction;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
_removeReaction(emoji, user) {
|
||||
const emojiID = emoji.id || emoji;
|
||||
if (this.reactions.has(emojiID)) {
|
||||
const reaction = this.reactions.get(emojiID);
|
||||
if (reaction.users.has(user.id)) {
|
||||
reaction.users.delete(user.id);
|
||||
reaction.count--;
|
||||
if (user.id === this.client.user.id) reaction.me = false;
|
||||
return reaction;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
_clearReactions() {
|
||||
this.reactions.clear();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Message;
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
/**
|
||||
* Represents an Attachment in a Message
|
||||
* Represents an attachment in a message
|
||||
*/
|
||||
class MessageAttachment {
|
||||
constructor(message, data) {
|
||||
/**
|
||||
* The Client that instantiated this Message.
|
||||
* The Client that instantiated this MessageAttachment.
|
||||
* @name MessageAttachment#client
|
||||
* @type {Client}
|
||||
* @readonly
|
||||
*/
|
||||
this.client = message.client;
|
||||
Object.defineProperty(this, 'client', { enumerable: false, configurable: false });
|
||||
Object.defineProperty(this, 'client', { value: message.client });
|
||||
|
||||
/**
|
||||
* The message this attachment is part of.
|
||||
|
||||
@@ -16,7 +16,7 @@ class MessageCollector extends EventEmitter {
|
||||
* return false; // failed the filter test
|
||||
* }
|
||||
* ```
|
||||
* @typedef {function} CollectorFilterFunction
|
||||
* @typedef {Function} CollectorFilterFunction
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -54,7 +54,7 @@ class MessageCollector extends EventEmitter {
|
||||
this.options = options;
|
||||
|
||||
/**
|
||||
* Whether this collector has stopped collecting Messages.
|
||||
* Whether this collector has stopped collecting messages.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.ended = false;
|
||||
@@ -81,7 +81,7 @@ class MessageCollector extends EventEmitter {
|
||||
if (this.filter(message, this)) {
|
||||
this.collected.set(message.id, message);
|
||||
/**
|
||||
* Emitted whenever the Collector receives a Message that passes the filter test.
|
||||
* Emitted whenever the collector receives a message that passes the filter test.
|
||||
* @param {Message} message The received message
|
||||
* @param {MessageCollector} collector The collector the message passed through
|
||||
* @event MessageCollector#message
|
||||
@@ -138,7 +138,7 @@ class MessageCollector extends EventEmitter {
|
||||
/**
|
||||
* Emitted when the Collector stops collecting.
|
||||
* @param {Collection<string, Message>} collection A collection of messages collected
|
||||
* during the lifetime of the Collector, mapped by the ID of the Messages.
|
||||
* during the lifetime of the collector, mapped by the ID of the messages.
|
||||
* @param {string} reason The reason for the end of the collector. If it ended because it reached the specified time
|
||||
* limit, this would be `time`. If you invoke `.stop()` without specifying a reason, this would be `user`. If it
|
||||
* ended because it reached its message limit, it will be `limit`.
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
/**
|
||||
* Represents an embed in an image - e.g. preview of image
|
||||
* Represents an embed in a message (image/video preview, rich embed, etc.)
|
||||
*/
|
||||
class MessageEmbed {
|
||||
constructor(message, data) {
|
||||
/**
|
||||
* The client that instantiated this embed
|
||||
* @name MessageEmbed#client
|
||||
* @type {Client}
|
||||
* @readonly
|
||||
*/
|
||||
this.client = message.client;
|
||||
Object.defineProperty(this, 'client', { enumerable: false, configurable: false });
|
||||
Object.defineProperty(this, 'client', { value: message.client });
|
||||
|
||||
/**
|
||||
* The message this embed is part of
|
||||
@@ -20,18 +21,18 @@ class MessageEmbed {
|
||||
}
|
||||
|
||||
setup(data) {
|
||||
/**
|
||||
* The title of this embed, if there is one
|
||||
* @type {?string}
|
||||
*/
|
||||
this.title = data.title;
|
||||
|
||||
/**
|
||||
* The type of this embed
|
||||
* @type {string}
|
||||
*/
|
||||
this.type = data.type;
|
||||
|
||||
/**
|
||||
* The title of this embed, if there is one
|
||||
* @type {?string}
|
||||
*/
|
||||
this.title = data.title;
|
||||
|
||||
/**
|
||||
* The description of this embed, if there is one
|
||||
* @type {?string}
|
||||
@@ -44,6 +45,25 @@ class MessageEmbed {
|
||||
*/
|
||||
this.url = data.url;
|
||||
|
||||
/**
|
||||
* The color of the embed
|
||||
* @type {number}
|
||||
*/
|
||||
this.color = data.color;
|
||||
|
||||
/**
|
||||
* The fields of this embed
|
||||
* @type {MessageEmbedField[]}
|
||||
*/
|
||||
this.fields = [];
|
||||
if (data.fields) for (const field of data.fields) this.fields.push(new MessageEmbedField(this, field));
|
||||
|
||||
/**
|
||||
* The timestamp of this embed
|
||||
* @type {number}
|
||||
*/
|
||||
this.createdTimestamp = data.timestamp;
|
||||
|
||||
/**
|
||||
* The thumbnail of this embed, if there is one
|
||||
* @type {MessageEmbedThumbnail}
|
||||
@@ -61,11 +81,36 @@ class MessageEmbed {
|
||||
* @type {MessageEmbedProvider}
|
||||
*/
|
||||
this.provider = data.provider ? new MessageEmbedProvider(this, data.provider) : null;
|
||||
|
||||
/**
|
||||
* The footer of this embed
|
||||
* @type {MessageEmbedFooter}
|
||||
*/
|
||||
this.footer = data.footer ? new MessageEmbedFooter(this, data.footer) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The date this embed was created
|
||||
* @type {Date}
|
||||
*/
|
||||
get createdAt() {
|
||||
return new Date(this.createdTimestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* The hexadecimal version of the embed color, with a leading hash.
|
||||
* @type {string}
|
||||
* @readonly
|
||||
*/
|
||||
get hexColor() {
|
||||
let col = this.color.toString(16);
|
||||
while (col.length < 6) col = `0${col}`;
|
||||
return `#${col}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a thumbnail for a Message embed
|
||||
* Represents a thumbnail for a message embed
|
||||
*/
|
||||
class MessageEmbedThumbnail {
|
||||
constructor(embed, data) {
|
||||
@@ -106,7 +151,7 @@ class MessageEmbedThumbnail {
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a Provider for a Message embed
|
||||
* Represents a provider for a message embed
|
||||
*/
|
||||
class MessageEmbedProvider {
|
||||
constructor(embed, data) {
|
||||
@@ -135,7 +180,7 @@ class MessageEmbedProvider {
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a Author for a Message embed
|
||||
* Represents an author for a message embed
|
||||
*/
|
||||
class MessageEmbedAuthor {
|
||||
constructor(embed, data) {
|
||||
@@ -160,11 +205,89 @@ class MessageEmbedAuthor {
|
||||
* @type {string}
|
||||
*/
|
||||
this.url = data.url;
|
||||
|
||||
/**
|
||||
* The icon URL of this author
|
||||
* @type {string}
|
||||
*/
|
||||
this.iconURL = data.icon_url;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a field for a message embed
|
||||
*/
|
||||
class MessageEmbedField {
|
||||
constructor(embed, data) {
|
||||
/**
|
||||
* The embed this footer is part of
|
||||
* @type {MessageEmbed}
|
||||
*/
|
||||
this.embed = embed;
|
||||
|
||||
this.setup(data);
|
||||
}
|
||||
|
||||
setup(data) {
|
||||
/**
|
||||
* The name of this field
|
||||
* @type {string}
|
||||
*/
|
||||
this.name = data.name;
|
||||
|
||||
/**
|
||||
* The value of this field
|
||||
* @type {string}
|
||||
*/
|
||||
this.value = data.value;
|
||||
|
||||
/**
|
||||
* If this field is displayed inline
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.inline = data.inline;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the footer of a message embed
|
||||
*/
|
||||
class MessageEmbedFooter {
|
||||
constructor(embed, data) {
|
||||
/**
|
||||
* The embed this footer is part of
|
||||
* @type {MessageEmbed}
|
||||
*/
|
||||
this.embed = embed;
|
||||
|
||||
this.setup(data);
|
||||
}
|
||||
|
||||
setup(data) {
|
||||
/**
|
||||
* The text in this footer
|
||||
* @type {string}
|
||||
*/
|
||||
this.text = data.text;
|
||||
|
||||
/**
|
||||
* The icon URL of this footer
|
||||
* @type {string}
|
||||
*/
|
||||
this.iconURL = data.icon_url;
|
||||
|
||||
/**
|
||||
* The proxy icon URL of this footer
|
||||
* @type {string}
|
||||
*/
|
||||
this.proxyIconUrl = data.proxy_icon_url;
|
||||
}
|
||||
}
|
||||
|
||||
MessageEmbed.Thumbnail = MessageEmbedThumbnail;
|
||||
MessageEmbed.Provider = MessageEmbedProvider;
|
||||
MessageEmbed.Author = MessageEmbedAuthor;
|
||||
MessageEmbed.Field = MessageEmbedField;
|
||||
MessageEmbed.Footer = MessageEmbedFooter;
|
||||
|
||||
module.exports = MessageEmbed;
|
||||
|
||||
92
src/structures/MessageReaction.js
Normal file
92
src/structures/MessageReaction.js
Normal file
@@ -0,0 +1,92 @@
|
||||
const Collection = require('../util/Collection');
|
||||
const Emoji = require('./Emoji');
|
||||
const ReactionEmoji = require('./ReactionEmoji');
|
||||
|
||||
/**
|
||||
* Represents a reaction to a message
|
||||
*/
|
||||
class MessageReaction {
|
||||
constructor(message, emoji, count, me) {
|
||||
/**
|
||||
* The message that this reaction refers to
|
||||
* @type {Message}
|
||||
*/
|
||||
this.message = message;
|
||||
|
||||
/**
|
||||
* Whether the client has given this reaction
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.me = me;
|
||||
|
||||
/**
|
||||
* The number of people that have given the same reaction.
|
||||
* @type {number}
|
||||
*/
|
||||
this.count = count || 0;
|
||||
|
||||
/**
|
||||
* The users that have given this reaction, mapped by their ID.
|
||||
* @type {Collection<string, User>}
|
||||
*/
|
||||
this.users = new Collection();
|
||||
|
||||
this._emoji = new ReactionEmoji(this, emoji.name, emoji.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* The emoji of this reaction, either an Emoji object for known custom emojis, or a ReactionEmoji
|
||||
* object which has fewer properties. Whatever the prototype of the emoji, it will still have
|
||||
* `name`, `id`, `identifier` and `toString()`
|
||||
* @type {Emoji|ReactionEmoji}
|
||||
*/
|
||||
get emoji() {
|
||||
if (this._emoji instanceof Emoji) return this._emoji;
|
||||
// check to see if the emoji has become known to the client
|
||||
if (this._emoji.id) {
|
||||
const emojis = this.message.client.emojis;
|
||||
if (emojis.has(this._emoji.id)) {
|
||||
const emoji = emojis.get(this._emoji.id);
|
||||
this._emoji = emoji;
|
||||
return emoji;
|
||||
}
|
||||
}
|
||||
return this._emoji;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a user from this reaction.
|
||||
* @param {UserResolvable} [user=this.message.client.user] User to remove the reaction of
|
||||
* @returns {Promise<MessageReaction>}
|
||||
*/
|
||||
remove(user = this.message.client.user) {
|
||||
const message = this.message;
|
||||
user = this.message.client.resolver.resolveUserID(user);
|
||||
if (!user) return Promise.reject('Couldn\'t resolve the user ID to remove from the reaction.');
|
||||
return message.client.rest.methods.removeMessageReaction(
|
||||
message, this.emoji.identifier, user
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all the users that gave this reaction. Resolves with a collection of users, mapped by their IDs.
|
||||
* @param {number} [limit=100] the maximum amount of users to fetch, defaults to 100
|
||||
* @returns {Promise<Collection<string, User>>}
|
||||
*/
|
||||
fetchUsers(limit = 100) {
|
||||
const message = this.message;
|
||||
return message.client.rest.methods.getMessageReactionUsers(
|
||||
message, this.emoji.identifier, limit
|
||||
).then(users => {
|
||||
this.users = new Collection();
|
||||
for (const rawUser of users) {
|
||||
const user = this.message.client.dataManager.newUser(rawUser);
|
||||
this.users.set(user.id, user);
|
||||
}
|
||||
this.count = this.users.size;
|
||||
return users;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MessageReaction;
|
||||
82
src/structures/OAuth2Application.js
Normal file
82
src/structures/OAuth2Application.js
Normal file
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* Represents an OAuth2 Application
|
||||
*/
|
||||
class OAuth2Application {
|
||||
constructor(client, data) {
|
||||
/**
|
||||
* The client that instantiated the application
|
||||
* @name OAuth2Application#client
|
||||
* @type {Client}
|
||||
* @readonly
|
||||
*/
|
||||
Object.defineProperty(this, 'client', { value: client });
|
||||
|
||||
this.setup(data);
|
||||
}
|
||||
|
||||
setup(data) {
|
||||
/**
|
||||
* The ID of the app
|
||||
* @type {string}
|
||||
*/
|
||||
this.id = data.id;
|
||||
|
||||
/**
|
||||
* The name of the app
|
||||
* @type {string}
|
||||
*/
|
||||
this.name = data.name;
|
||||
|
||||
/**
|
||||
* The app's description
|
||||
* @type {string}
|
||||
*/
|
||||
this.description = data.description;
|
||||
|
||||
/**
|
||||
* The app's icon hash
|
||||
* @type {string}
|
||||
*/
|
||||
this.icon = data.icon;
|
||||
|
||||
/**
|
||||
* The app's icon URL
|
||||
* @type {string}
|
||||
*/
|
||||
this.iconURL = `https://cdn.discordapp.com/app-icons/${this.id}/${this.icon}.jpg`;
|
||||
|
||||
/**
|
||||
* The app's RPC origins
|
||||
* @type {Array<string>}
|
||||
*/
|
||||
this.rpcOrigins = data.rpc_origins;
|
||||
}
|
||||
|
||||
/**
|
||||
* The timestamp the app was created at
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
get createdTimestamp() {
|
||||
return (this.id / 4194304) + 1420070400000;
|
||||
}
|
||||
|
||||
/**
|
||||
* The time the app was created
|
||||
* @type {Date}
|
||||
* @readonly
|
||||
*/
|
||||
get createdAt() {
|
||||
return new Date(this.createdTimestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* When concatenated with a string, this automatically concatenates the app name rather than the app object.
|
||||
* @returns {string}
|
||||
*/
|
||||
toString() {
|
||||
return this.name;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = OAuth2Application;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user