mirror of
https://github.com/danbulant/discord.js
synced 2026-06-24 17:21:59 +00:00
Compare commits
140 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2eaafaf1d3 | ||
|
|
c513df444e | ||
|
|
4f5cdf2814 | ||
|
|
0ca50889ae | ||
|
|
53412f6b9f | ||
|
|
2b8a6294af | ||
|
|
8c65961a07 | ||
|
|
4599ef954f | ||
|
|
790b6b3b5c | ||
|
|
5be32161b9 | ||
|
|
db6d1b3ba9 | ||
|
|
21bfe3da7c | ||
|
|
a626dc8c41 | ||
|
|
c6f87aa32d | ||
|
|
2685b960d7 | ||
|
|
60e5a0e46f | ||
|
|
7365f40300 | ||
|
|
09d07553ab | ||
|
|
e272fd6909 | ||
|
|
90d458820b | ||
|
|
9f3c3e0918 | ||
|
|
ec560b8107 | ||
|
|
fbb1c93454 | ||
|
|
6b322f47a0 | ||
|
|
4fcb9ebf30 | ||
|
|
53529bd05d | ||
|
|
88625a5b7d | ||
|
|
e43ad1eea9 | ||
|
|
8d650a7250 | ||
|
|
12a096b5f1 | ||
|
|
6f3076325e | ||
|
|
8c8883ef26 | ||
|
|
4b555fdf4c | ||
|
|
863734aba4 | ||
|
|
1f4b9fe749 | ||
|
|
2a6c363a8a | ||
|
|
643f96c79b | ||
|
|
2b2994badc | ||
|
|
eaecd0e8b7 | ||
|
|
2e940e635d | ||
|
|
8b91ac5d7e | ||
|
|
7faa73a561 | ||
|
|
eadeeed72e | ||
|
|
939c495ebb | ||
|
|
042e071a64 | ||
|
|
065e89337e | ||
|
|
1028183c23 | ||
|
|
b61a367392 | ||
|
|
ff2dbfa52d | ||
|
|
3463acafca | ||
|
|
274ae9935e | ||
|
|
2eafeeca55 | ||
|
|
b8fd3f65d9 | ||
|
|
efd7849ed0 | ||
|
|
adf2e872f8 | ||
|
|
ed8b3cc9ea | ||
|
|
7ec0bd93b0 | ||
|
|
3d158f4448 | ||
|
|
250c3ae3c1 | ||
|
|
94c9cc2300 | ||
|
|
e9f36b5041 | ||
|
|
30808f9f0b | ||
|
|
af670fc718 | ||
|
|
4bbe716aa0 | ||
|
|
a7af4a8837 | ||
|
|
89feedad98 | ||
|
|
728b3f939c | ||
|
|
7db6978012 | ||
|
|
6261dd65d3 | ||
|
|
a45cc112e5 | ||
|
|
b8aa967226 | ||
|
|
6e4308bfde | ||
|
|
dd12912124 | ||
|
|
937153a92f | ||
|
|
c412cd7521 | ||
|
|
4a6fb9a7d4 | ||
|
|
824e92229d | ||
|
|
0b59141054 | ||
|
|
b9ad51049e | ||
|
|
08286459cb | ||
|
|
2e96b9a606 | ||
|
|
ef1856bb1f | ||
|
|
4f7c207c99 | ||
|
|
975b6cbd94 | ||
|
|
67c2e56647 | ||
|
|
ab0d6fc5c9 | ||
|
|
b8f50a09d2 | ||
|
|
9f3108052c | ||
|
|
a5ce6cfa9a | ||
|
|
ab5ee838a3 | ||
|
|
0ed281888d | ||
|
|
bc4dc22c1f | ||
|
|
d2341654fe | ||
|
|
169d4c3bff | ||
|
|
13d64e6fa6 | ||
|
|
f83b3d7fc1 | ||
|
|
f2bbad36d5 | ||
|
|
77c0788b2c | ||
|
|
4e79e39e22 | ||
|
|
32fe72f909 | ||
|
|
1e63f3756e | ||
|
|
8fa3a89482 | ||
|
|
9c76129a23 | ||
|
|
01ceda5b0c | ||
|
|
eeb4c14754 | ||
|
|
bcb7c721dc | ||
|
|
0da65becd3 | ||
|
|
422a4dda68 | ||
|
|
222137dcd1 | ||
|
|
372a405926 | ||
|
|
dfd63bdb6b | ||
|
|
5b39737d49 | ||
|
|
904aecfdb7 | ||
|
|
a28754b892 | ||
|
|
b43e742503 | ||
|
|
8ac25d37d9 | ||
|
|
77b6a7d5bd | ||
|
|
aa25608c52 | ||
|
|
b0ab37ddc0 | ||
|
|
3141f7cb04 | ||
|
|
7ba9440053 | ||
|
|
f97316319f | ||
|
|
405b487dc3 | ||
|
|
b48b782c87 | ||
|
|
74763ef3fb | ||
|
|
74ebb650df | ||
|
|
a363b90fa5 | ||
|
|
6aab9c3d64 | ||
|
|
2dc70af717 | ||
|
|
46acfac327 | ||
|
|
727b29c85d | ||
|
|
e0e271162c | ||
|
|
cfc68677ee | ||
|
|
43c4d80b12 | ||
|
|
05c9e30163 | ||
|
|
b6167d8c3b | ||
|
|
56e8ef2d38 | ||
|
|
db512d8f62 | ||
|
|
5249cf33e5 | ||
|
|
09bde74e43 |
101 changed files with 3446 additions and 1751 deletions
|
|
@ -1,11 +1,12 @@
|
||||||
{
|
{
|
||||||
|
"root": true,
|
||||||
"extends": ["eslint:recommended", "plugin:prettier/recommended"],
|
"extends": ["eslint:recommended", "plugin:prettier/recommended"],
|
||||||
"plugins": ["import"],
|
"plugins": ["import"],
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"ecmaVersion": 2019
|
"ecmaVersion": 2020
|
||||||
},
|
},
|
||||||
"env": {
|
"env": {
|
||||||
"es6": true,
|
"es2020": true,
|
||||||
"node": true
|
"node": true
|
||||||
},
|
},
|
||||||
"overrides": [{ "files": ["*.browser.js"], "env": { "browser": true } }],
|
"overrides": [{ "files": ["*.browser.js"], "env": { "browser": true } }],
|
||||||
|
|
|
||||||
2
.github/CONTRIBUTING.md
vendored
2
.github/CONTRIBUTING.md
vendored
|
|
@ -11,7 +11,7 @@ is a great boon to your development process.
|
||||||
To get ready to work on the codebase, please do the following:
|
To get ready to work on the codebase, please do the following:
|
||||||
|
|
||||||
1. Fork & clone the repository, and make sure you're on the **master** branch
|
1. Fork & clone the repository, and make sure you're on the **master** branch
|
||||||
2. Run `npm install`
|
2. Run `npm ci`
|
||||||
3. If you're working on voice, also run `npm install @discordjs/opus` or `npm install opusscript`
|
3. If you're working on voice, also run `npm install @discordjs/opus` or `npm install opusscript`
|
||||||
4. Code your heart out!
|
4. Code your heart out!
|
||||||
5. Run `npm test` to run ESLint and ensure any JSDoc changes are valid
|
5. Run `npm test` to run ESLint and ensure any JSDoc changes are valid
|
||||||
|
|
|
||||||
27
.github/workflows/codeql-analysis.yml
vendored
Normal file
27
.github/workflows/codeql-analysis.yml
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
name: "CodeQL"
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 */12 * * 4'
|
||||||
|
jobs:
|
||||||
|
analyze:
|
||||||
|
name: Analyze
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 2
|
||||||
|
|
||||||
|
- run: git checkout HEAD^2
|
||||||
|
if: ${{ github.event_name == 'pull_request' }}
|
||||||
|
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v1
|
||||||
|
with:
|
||||||
|
languages: javascript
|
||||||
|
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v1
|
||||||
12
.github/workflows/deploy.yml
vendored
12
.github/workflows/deploy.yml
vendored
|
|
@ -15,13 +15,13 @@ jobs:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@master
|
uses: actions/checkout@master
|
||||||
|
|
||||||
- name: Install Node v12
|
- name: Install Node v14
|
||||||
uses: actions/setup-node@master
|
uses: actions/setup-node@master
|
||||||
with:
|
with:
|
||||||
node-version: 12
|
node-version: 14
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm install
|
run: npm ci
|
||||||
|
|
||||||
- name: Build and deploy documentation
|
- name: Build and deploy documentation
|
||||||
uses: discordjs/action-docs@v1
|
uses: discordjs/action-docs@v1
|
||||||
|
|
@ -35,13 +35,13 @@ jobs:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@master
|
uses: actions/checkout@master
|
||||||
|
|
||||||
- name: Install Node v12
|
- name: Install Node v14
|
||||||
uses: actions/setup-node@master
|
uses: actions/setup-node@master
|
||||||
with:
|
with:
|
||||||
node-version: 12
|
node-version: 14
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm install
|
run: npm ci
|
||||||
|
|
||||||
- name: Build and deploy webpack
|
- name: Build and deploy webpack
|
||||||
uses: discordjs/action-webpack@v1
|
uses: discordjs/action-webpack@v1
|
||||||
|
|
|
||||||
24
.github/workflows/test-cron.yml
vendored
24
.github/workflows/test-cron.yml
vendored
|
|
@ -10,13 +10,13 @@ jobs:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v1
|
uses: actions/checkout@v1
|
||||||
|
|
||||||
- name: Install Node v12
|
- name: Install Node v14
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: 12
|
node-version: 14
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm install
|
run: npm ci
|
||||||
|
|
||||||
- name: Run ESLint
|
- name: Run ESLint
|
||||||
uses: icrawl/action-eslint@v1
|
uses: icrawl/action-eslint@v1
|
||||||
|
|
@ -28,13 +28,13 @@ jobs:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v1
|
uses: actions/checkout@v1
|
||||||
|
|
||||||
- name: Install Node v12
|
- name: Install Node v14
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: 12
|
node-version: 14
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm install
|
run: npm ci
|
||||||
|
|
||||||
- name: Run TSLint
|
- name: Run TSLint
|
||||||
run: npm run lint:typings
|
run: npm run lint:typings
|
||||||
|
|
@ -46,13 +46,13 @@ jobs:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Install Node v12
|
- name: Install Node v14
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: 12
|
node-version: 14
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm install
|
run: npm ci
|
||||||
|
|
||||||
- name: Register Problem Matcher
|
- name: Register Problem Matcher
|
||||||
run: echo "##[add-matcher].github/tsc.json"
|
run: echo "##[add-matcher].github/tsc.json"
|
||||||
|
|
@ -67,13 +67,13 @@ jobs:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v1
|
uses: actions/checkout@v1
|
||||||
|
|
||||||
- name: Install Node v12
|
- name: Install Node v14
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: 12
|
node-version: 14
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm install
|
run: npm ci
|
||||||
|
|
||||||
- name: Test documentation
|
- name: Test documentation
|
||||||
run: npm run docs:test
|
run: npm run docs:test
|
||||||
|
|
|
||||||
24
.github/workflows/test.yml
vendored
24
.github/workflows/test.yml
vendored
|
|
@ -8,13 +8,13 @@ jobs:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Install Node v12
|
- name: Install Node v14
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: 12
|
node-version: 14
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm install
|
run: npm ci
|
||||||
|
|
||||||
- name: Run ESLint
|
- name: Run ESLint
|
||||||
uses: icrawl/action-eslint@v1
|
uses: icrawl/action-eslint@v1
|
||||||
|
|
@ -26,13 +26,13 @@ jobs:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Install Node v12
|
- name: Install Node v14
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: 12
|
node-version: 14
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm install
|
run: npm ci
|
||||||
|
|
||||||
- name: Run TSLint
|
- name: Run TSLint
|
||||||
run: npm run lint:typings
|
run: npm run lint:typings
|
||||||
|
|
@ -44,13 +44,13 @@ jobs:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Install Node v12
|
- name: Install Node v14
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: 12
|
node-version: 14
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm install
|
run: npm ci
|
||||||
|
|
||||||
- name: Register Problem Matcher
|
- name: Register Problem Matcher
|
||||||
run: echo "##[add-matcher].github/tsc.json"
|
run: echo "##[add-matcher].github/tsc.json"
|
||||||
|
|
@ -65,13 +65,13 @@ jobs:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Install Node v12
|
- name: Install Node v14
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: 12
|
node-version: 14
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm install
|
run: npm ci
|
||||||
|
|
||||||
- name: Test documentation
|
- name: Test documentation
|
||||||
run: npm run docs:test
|
run: npm run docs:test
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@
|
||||||
|
|
||||||
## Table of contents
|
## Table of contents
|
||||||
|
|
||||||
|
- [Changes](#changes)
|
||||||
- [About](#about)
|
- [About](#about)
|
||||||
- [Installation](#installation)
|
- [Installation](#installation)
|
||||||
- [Audio engines](#audio-engines)
|
- [Audio engines](#audio-engines)
|
||||||
|
|
@ -29,6 +30,10 @@
|
||||||
- [Contributing](#contributing)
|
- [Contributing](#contributing)
|
||||||
- [Help](#help)
|
- [Help](#help)
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
This fork has the inline replies and slash commands for **testing** purposes.
|
||||||
|
|
||||||
## About
|
## About
|
||||||
|
|
||||||
discord.js is a powerful [Node.js](https://nodejs.org) module that allows you to easily interact with the
|
discord.js is a powerful [Node.js](https://nodejs.org) module that allows you to easily interact with the
|
||||||
|
|
@ -41,7 +46,7 @@ discord.js is a powerful [Node.js](https://nodejs.org) module that allows you to
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
**Node.js 12.0.0 or newer is required.**
|
**Node.js 14.0.0 or newer is required.**
|
||||||
Ignore any warnings about unmet peer dependencies, as they're all optional.
|
Ignore any warnings about unmet peer dependencies, as they're all optional.
|
||||||
|
|
||||||
Without voice support: `npm install discord.js`
|
Without voice support: `npm install discord.js`
|
||||||
|
|
@ -76,7 +81,7 @@ client.on('ready', () => {
|
||||||
|
|
||||||
client.on('message', msg => {
|
client.on('message', msg => {
|
||||||
if (msg.content === 'ping') {
|
if (msg.content === 'ping') {
|
||||||
msg.reply('pong');
|
msg.channel.send('pong');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ client.on('message', message => {
|
||||||
// If the message is "what is my avatar"
|
// If the message is "what is my avatar"
|
||||||
if (message.content === 'what is my avatar') {
|
if (message.content === 'what is my avatar') {
|
||||||
// Send the user's avatar URL
|
// Send the user's avatar URL
|
||||||
message.reply(message.author.displayAvatarURL());
|
message.channel.send(message.author.displayAvatarURL());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ client.on('message', message => {
|
||||||
// If we have a user mentioned
|
// If we have a user mentioned
|
||||||
if (user) {
|
if (user) {
|
||||||
// Now we get the member from the user
|
// Now we get the member from the user
|
||||||
const member = message.guild.member(user);
|
const member = message.guild.members.resolve(user);
|
||||||
// If the member is in the guild
|
// If the member is in the guild
|
||||||
if (member) {
|
if (member) {
|
||||||
/**
|
/**
|
||||||
|
|
@ -45,23 +45,23 @@ client.on('message', message => {
|
||||||
.kick('Optional reason that will display in the audit logs')
|
.kick('Optional reason that will display in the audit logs')
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// We let the message author know we were able to kick the person
|
// We let the message author know we were able to kick the person
|
||||||
message.reply(`Successfully kicked ${user.tag}`);
|
message.channel.send(`Successfully kicked ${user.tag}`);
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
// An error happened
|
// An error happened
|
||||||
// This is generally due to the bot not being able to kick the member,
|
// This is generally due to the bot not being able to kick the member,
|
||||||
// either due to missing permissions or role hierarchy
|
// either due to missing permissions or role hierarchy
|
||||||
message.reply('I was unable to kick the member');
|
message.channel.send('I was unable to kick the member');
|
||||||
// Log the error
|
// Log the error
|
||||||
console.error(err);
|
console.error(err);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// The mentioned user isn't in this guild
|
// The mentioned user isn't in this guild
|
||||||
message.reply("That user isn't in this guild!");
|
message.channel.send("That user isn't in this guild!");
|
||||||
}
|
}
|
||||||
// Otherwise, if no user was mentioned
|
// Otherwise, if no user was mentioned
|
||||||
} else {
|
} else {
|
||||||
message.reply("You didn't mention the user to kick!");
|
message.channel.send("You didn't mention the user to kick!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -105,7 +105,7 @@ client.on('message', message => {
|
||||||
// If we have a user mentioned
|
// If we have a user mentioned
|
||||||
if (user) {
|
if (user) {
|
||||||
// Now we get the member from the user
|
// Now we get the member from the user
|
||||||
const member = message.guild.member(user);
|
const member = message.guild.members.resolve(user);
|
||||||
// If the member is in the guild
|
// If the member is in the guild
|
||||||
if (member) {
|
if (member) {
|
||||||
/**
|
/**
|
||||||
|
|
@ -121,23 +121,23 @@ client.on('message', message => {
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// We let the message author know we were able to ban the person
|
// We let the message author know we were able to ban the person
|
||||||
message.reply(`Successfully banned ${user.tag}`);
|
message.channel.send(`Successfully banned ${user.tag}`);
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
// An error happened
|
// An error happened
|
||||||
// This is generally due to the bot not being able to ban the member,
|
// This is generally due to the bot not being able to ban the member,
|
||||||
// either due to missing permissions or role hierarchy
|
// either due to missing permissions or role hierarchy
|
||||||
message.reply('I was unable to ban the member');
|
message.channel.send('I was unable to ban the member');
|
||||||
// Log the error
|
// Log the error
|
||||||
console.error(err);
|
console.error(err);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// The mentioned user isn't in this guild
|
// The mentioned user isn't in this guild
|
||||||
message.reply("That user isn't in this guild!");
|
message.channel.send("That user isn't in this guild!");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, if no user was mentioned
|
// Otherwise, if no user was mentioned
|
||||||
message.reply("You didn't mention the user to ban!");
|
message.channel.send("You didn't mention the user to ban!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ These questions are some of the most frequently asked.
|
||||||
|
|
||||||
## No matter what, I get `SyntaxError: Block-scoped declarations (let, const, function, class) not yet supported outside strict mode`‽
|
## No matter what, I get `SyntaxError: Block-scoped declarations (let, const, function, class) not yet supported outside strict mode`‽
|
||||||
|
|
||||||
Update to Node.js 12.0.0 or newer.
|
Update to Node.js 14.0.0 or newer.
|
||||||
|
|
||||||
## How do I get voice working?
|
## How do I get voice working?
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ discord.js is a powerful [Node.js](https://nodejs.org) module that allows you to
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
**Node.js 12.0.0 or newer is required.**
|
**Node.js 14.0.0 or newer is required.**
|
||||||
Ignore any warnings about unmet peer dependencies, as they're all optional.
|
Ignore any warnings about unmet peer dependencies, as they're all optional.
|
||||||
|
|
||||||
Without voice support: `npm install discord.js`
|
Without voice support: `npm install discord.js`
|
||||||
|
|
@ -68,7 +68,7 @@ client.on('ready', () => {
|
||||||
|
|
||||||
client.on('message', msg => {
|
client.on('message', msg => {
|
||||||
if (msg.content === 'ping') {
|
if (msg.content === 'ping') {
|
||||||
msg.reply('pong');
|
msg.channel.send('pong');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ client.on('message', async message => {
|
||||||
if (message.member.voice.channel) {
|
if (message.member.voice.channel) {
|
||||||
const connection = await message.member.voice.channel.join();
|
const connection = await message.member.voice.channel.join();
|
||||||
} else {
|
} else {
|
||||||
message.reply('You need to join a voice channel first!');
|
message.channel.send('You need to join a voice channel first!');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,10 @@ export const {
|
||||||
SnowflakeUtil,
|
SnowflakeUtil,
|
||||||
Structures,
|
Structures,
|
||||||
SystemChannelFlags,
|
SystemChannelFlags,
|
||||||
|
UserFlags,
|
||||||
Util,
|
Util,
|
||||||
version,
|
version,
|
||||||
|
BaseGuildEmojiManager,
|
||||||
ChannelManager,
|
ChannelManager,
|
||||||
GuildChannelManager,
|
GuildChannelManager,
|
||||||
GuildEmojiManager,
|
GuildEmojiManager,
|
||||||
|
|
@ -34,6 +36,7 @@ export const {
|
||||||
GuildMemberManager,
|
GuildMemberManager,
|
||||||
GuildMemberRoleManager,
|
GuildMemberRoleManager,
|
||||||
GuildManager,
|
GuildManager,
|
||||||
|
ReactionManager,
|
||||||
ReactionUserManager,
|
ReactionUserManager,
|
||||||
MessageManager,
|
MessageManager,
|
||||||
PresenceManager,
|
PresenceManager,
|
||||||
|
|
@ -45,9 +48,11 @@ export const {
|
||||||
resolveColor,
|
resolveColor,
|
||||||
resolveString,
|
resolveString,
|
||||||
splitMessage,
|
splitMessage,
|
||||||
|
Application,
|
||||||
Base,
|
Base,
|
||||||
Activity,
|
Activity,
|
||||||
APIMessage,
|
APIMessage,
|
||||||
|
BaseGuildEmoji,
|
||||||
CategoryChannel,
|
CategoryChannel,
|
||||||
Channel,
|
Channel,
|
||||||
ClientApplication,
|
ClientApplication,
|
||||||
|
|
@ -60,6 +65,8 @@ export const {
|
||||||
GuildChannel,
|
GuildChannel,
|
||||||
GuildEmoji,
|
GuildEmoji,
|
||||||
GuildMember,
|
GuildMember,
|
||||||
|
GuildPreview,
|
||||||
|
GuildTemplate,
|
||||||
Integration,
|
Integration,
|
||||||
Invite,
|
Invite,
|
||||||
Message,
|
Message,
|
||||||
|
|
|
||||||
1638
package-lock.json
generated
1638
package-lock.json
generated
File diff suppressed because it is too large
Load diff
51
package.json
51
package.json
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "discord.js",
|
"name": "discord.js",
|
||||||
"version": "12.3.0",
|
"version": "12.5.0",
|
||||||
"description": "A powerful library for interacting with the Discord API",
|
"description": "A powerful library for interacting with the Discord API",
|
||||||
"main": "./src/index",
|
"main": "./src/index",
|
||||||
"types": "./typings/index.d.ts",
|
"types": "./typings/index.d.ts",
|
||||||
|
|
@ -22,7 +22,7 @@
|
||||||
"lint": "eslint src",
|
"lint": "eslint src",
|
||||||
"lint:fix": "eslint src --fix",
|
"lint:fix": "eslint src --fix",
|
||||||
"lint:typings": "tslint typings/index.d.ts",
|
"lint:typings": "tslint typings/index.d.ts",
|
||||||
"prettier": "prettier --write --single-quote --print-width 120 --trailing-comma all --end-of-line lf --arrow-parens avoid src/**/*.js typings/**/*.ts",
|
"prettier": "prettier --write src/**/*.js typings/**/*.ts",
|
||||||
"build:browser": "webpack",
|
"build:browser": "webpack",
|
||||||
"prepublishOnly": "npm run test && cross-env NODE_ENV=production npm run build:browser"
|
"prepublishOnly": "npm run test && cross-env NODE_ENV=production npm run build:browser"
|
||||||
},
|
},
|
||||||
|
|
@ -50,37 +50,37 @@
|
||||||
"@discordjs/collection": "^0.1.6",
|
"@discordjs/collection": "^0.1.6",
|
||||||
"@discordjs/form-data": "^3.0.1",
|
"@discordjs/form-data": "^3.0.1",
|
||||||
"abort-controller": "^3.0.0",
|
"abort-controller": "^3.0.0",
|
||||||
"node-fetch": "^2.6.0",
|
"node-fetch": "^2.6.1",
|
||||||
"prism-media": "^1.2.2",
|
"prism-media": "^1.2.2",
|
||||||
"setimmediate": "^1.0.5",
|
"setimmediate": "^1.0.5",
|
||||||
"tweetnacl": "^1.0.3",
|
"tweetnacl": "^1.0.3",
|
||||||
"ws": "^7.3.1"
|
"ws": "^7.3.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "^9.1.2",
|
"@commitlint/cli": "^11.0.0",
|
||||||
"@commitlint/config-angular": "^9.1.1",
|
"@commitlint/config-angular": "^11.0.0",
|
||||||
"@types/node": "^12.12.6",
|
"@types/node": "^12.12.6",
|
||||||
"@types/ws": "^7.2.6",
|
"@types/ws": "^7.2.7",
|
||||||
"cross-env": "^7.0.2",
|
"cross-env": "^7.0.2",
|
||||||
"discord.js-docgen": "discordjs/docgen",
|
"discord.js-docgen": "git+https://github.com/discordjs/docgen.git",
|
||||||
"dtslint": "^3.6.14",
|
"dtslint": "^4.0.4",
|
||||||
"eslint": "^7.6.0",
|
"eslint": "^7.11.0",
|
||||||
"eslint-config-prettier": "^6.11.0",
|
"eslint-config-prettier": "^6.13.0",
|
||||||
"eslint-plugin-import": "^2.22.0",
|
"eslint-plugin-import": "^2.22.1",
|
||||||
"eslint-plugin-prettier": "^3.1.4",
|
"eslint-plugin-prettier": "^3.1.4",
|
||||||
"husky": "^4.2.5",
|
"husky": "^4.3.0",
|
||||||
"jest": "^26.4.0",
|
"jest": "^26.6.0",
|
||||||
"json-filter-loader": "^1.0.0",
|
"json-filter-loader": "^1.0.0",
|
||||||
"lint-staged": "^10.2.11",
|
"lint-staged": "^10.4.2",
|
||||||
"prettier": "^2.0.5",
|
"prettier": "^2.1.2",
|
||||||
"terser-webpack-plugin": "^4.1.0",
|
"terser-webpack-plugin": "^4.2.3",
|
||||||
"tslint": "^6.1.3",
|
"tslint": "^6.1.3",
|
||||||
"typescript": "^3.9.7",
|
"typescript": "^4.0.3",
|
||||||
"webpack": "^4.44.1",
|
"webpack": "^4.44.2",
|
||||||
"webpack-cli": "^3.3.12"
|
"webpack-cli": "^3.3.12"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.0.0"
|
"node": ">=14.0.0"
|
||||||
},
|
},
|
||||||
"browser": {
|
"browser": {
|
||||||
"@discordjs/opus": false,
|
"@discordjs/opus": false,
|
||||||
|
|
@ -110,9 +110,9 @@
|
||||||
"src/client/voice/receiver/PacketHandler.js": false,
|
"src/client/voice/receiver/PacketHandler.js": false,
|
||||||
"src/client/voice/receiver/Receiver.js": false,
|
"src/client/voice/receiver/Receiver.js": false,
|
||||||
"src/client/voice/util/PlayInterface.js": false,
|
"src/client/voice/util/PlayInterface.js": false,
|
||||||
"src/client/voice/util/Secretbox.js": false,
|
|
||||||
"src/client/voice/util/Silence.js": false,
|
"src/client/voice/util/Silence.js": false,
|
||||||
"src/client/voice/util/VolumeInterface.js": false
|
"src/client/voice/util/VolumeInterface.js": false,
|
||||||
|
"src/util/Sodium.js": false
|
||||||
},
|
},
|
||||||
"husky": {
|
"husky": {
|
||||||
"hooks": {
|
"hooks": {
|
||||||
|
|
@ -122,7 +122,7 @@
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"*.js": "eslint --fix",
|
"*.js": "eslint --fix",
|
||||||
"*.ts": "prettier --write --single-quote --print-width 120 --trailing-comma all --end-of-line lf --arrow-parens avoid"
|
"*.ts": "prettier --write"
|
||||||
},
|
},
|
||||||
"commitlint": {
|
"commitlint": {
|
||||||
"extends": [
|
"extends": [
|
||||||
|
|
@ -152,5 +152,12 @@
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"prettier": {
|
||||||
|
"singleQuote": true,
|
||||||
|
"printWidth": 120,
|
||||||
|
"trailingComma": "all",
|
||||||
|
"endOfLine": "lf",
|
||||||
|
"arrowParens": "avoid"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,19 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const BaseClient = require('./BaseClient');
|
const BaseClient = require('./BaseClient');
|
||||||
|
const InteractionClient = require('./InteractionClient');
|
||||||
const ActionsManager = require('./actions/ActionsManager');
|
const ActionsManager = require('./actions/ActionsManager');
|
||||||
const ClientVoiceManager = require('./voice/ClientVoiceManager');
|
const ClientVoiceManager = require('./voice/ClientVoiceManager');
|
||||||
const WebSocketManager = require('./websocket/WebSocketManager');
|
const WebSocketManager = require('./websocket/WebSocketManager');
|
||||||
const { Error, TypeError, RangeError } = require('../errors');
|
const { Error, TypeError, RangeError } = require('../errors');
|
||||||
|
const BaseGuildEmojiManager = require('../managers/BaseGuildEmojiManager');
|
||||||
const ChannelManager = require('../managers/ChannelManager');
|
const ChannelManager = require('../managers/ChannelManager');
|
||||||
const GuildEmojiManager = require('../managers/GuildEmojiManager');
|
|
||||||
const GuildManager = require('../managers/GuildManager');
|
const GuildManager = require('../managers/GuildManager');
|
||||||
const UserManager = require('../managers/UserManager');
|
const UserManager = require('../managers/UserManager');
|
||||||
const ShardClientUtil = require('../sharding/ShardClientUtil');
|
const ShardClientUtil = require('../sharding/ShardClientUtil');
|
||||||
const ClientApplication = require('../structures/ClientApplication');
|
const ClientApplication = require('../structures/ClientApplication');
|
||||||
const GuildPreview = require('../structures/GuildPreview');
|
const GuildPreview = require('../structures/GuildPreview');
|
||||||
|
const GuildTemplate = require('../structures/GuildTemplate');
|
||||||
const Invite = require('../structures/Invite');
|
const Invite = require('../structures/Invite');
|
||||||
const VoiceRegion = require('../structures/VoiceRegion');
|
const VoiceRegion = require('../structures/VoiceRegion');
|
||||||
const Webhook = require('../structures/Webhook');
|
const Webhook = require('../structures/Webhook');
|
||||||
|
|
@ -102,6 +104,12 @@ class Client extends BaseClient {
|
||||||
? ShardClientUtil.singleton(this, process.env.SHARDING_MANAGER_MODE)
|
? ShardClientUtil.singleton(this, process.env.SHARDING_MANAGER_MODE)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The interaction client.
|
||||||
|
* @type {InteractionClient}
|
||||||
|
*/
|
||||||
|
this.interactionClient = new InteractionClient(options, this);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All of the {@link User} objects that have been cached at any point, mapped by their IDs
|
* All of the {@link User} objects that have been cached at any point, mapped by their IDs
|
||||||
* @type {UserManager}
|
* @type {UserManager}
|
||||||
|
|
@ -165,11 +173,11 @@ class Client extends BaseClient {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All custom emojis that the client has access to, mapped by their IDs
|
* All custom emojis that the client has access to, mapped by their IDs
|
||||||
* @type {GuildEmojiManager}
|
* @type {BaseGuildEmojiManager}
|
||||||
* @readonly
|
* @readonly
|
||||||
*/
|
*/
|
||||||
get emojis() {
|
get emojis() {
|
||||||
const emojis = new GuildEmojiManager({ client: this });
|
const emojis = new BaseGuildEmojiManager(this);
|
||||||
for (const guild of this.guilds.cache.values()) {
|
for (const guild of this.guilds.cache.values()) {
|
||||||
if (guild.available) for (const emoji of guild.emojis.cache.values()) emojis.cache.set(emoji.id, emoji);
|
if (guild.available) for (const emoji of guild.emojis.cache.values()) emojis.cache.set(emoji.id, emoji);
|
||||||
}
|
}
|
||||||
|
|
@ -254,6 +262,23 @@ class Client extends BaseClient {
|
||||||
.then(data => new Invite(this, data));
|
.then(data => new Invite(this, data));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains a template from Discord.
|
||||||
|
* @param {GuildTemplateResolvable} template Template code or URL
|
||||||
|
* @returns {Promise<GuildTemplate>}
|
||||||
|
* @example
|
||||||
|
* client.fetchGuildTemplate('https://discord.new/FKvmczH2HyUf')
|
||||||
|
* .then(template => console.log(`Obtained template with code: ${template.code}`))
|
||||||
|
* .catch(console.error);
|
||||||
|
*/
|
||||||
|
fetchGuildTemplate(template) {
|
||||||
|
const code = DataResolver.resolveGuildTemplateCode(template);
|
||||||
|
return this.api.guilds
|
||||||
|
.templates(code)
|
||||||
|
.get()
|
||||||
|
.then(data => new GuildTemplate(this, data));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtains a webhook from Discord.
|
* Obtains a webhook from Discord.
|
||||||
* @param {Snowflake} id ID of the webhook
|
* @param {Snowflake} id ID of the webhook
|
||||||
|
|
@ -356,28 +381,43 @@ class Client extends BaseClient {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a link that can be used to invite the bot to a guild.
|
* Generates a link that can be used to invite the bot to a guild.
|
||||||
* @param {PermissionResolvable} [permissions] Permissions to request
|
* @param {InviteGenerationOptions|PermissionResolvable} [options] Permissions to request
|
||||||
* @returns {Promise<string>}
|
* @returns {Promise<string>}
|
||||||
* @example
|
* @example
|
||||||
* client.generateInvite(['SEND_MESSAGES', 'MANAGE_GUILD', 'MENTION_EVERYONE'])
|
* client.generateInvite({
|
||||||
|
* permissions: ['SEND_MESSAGES', 'MANAGE_GUILD', 'MENTION_EVERYONE'],
|
||||||
|
* })
|
||||||
* .then(link => console.log(`Generated bot invite link: ${link}`))
|
* .then(link => console.log(`Generated bot invite link: ${link}`))
|
||||||
* .catch(console.error);
|
* .catch(console.error);
|
||||||
*/
|
*/
|
||||||
async generateInvite(permissions) {
|
async generateInvite(options = {}) {
|
||||||
permissions = Permissions.resolve(permissions);
|
if (Array.isArray(options) || ['string', 'number'].includes(typeof options) || options instanceof Permissions) {
|
||||||
|
process.emitWarning(
|
||||||
|
'Client#generateInvite: Generate invite with an options object instead of a PermissionResolvable',
|
||||||
|
'DeprecationWarning',
|
||||||
|
);
|
||||||
|
options = { permissions: options };
|
||||||
|
}
|
||||||
const application = await this.fetchApplication();
|
const application = await this.fetchApplication();
|
||||||
const query = new URLSearchParams({
|
const query = new URLSearchParams({
|
||||||
client_id: application.id,
|
client_id: application.id,
|
||||||
permissions: permissions,
|
permissions: Permissions.resolve(options.permissions),
|
||||||
scope: 'bot',
|
scope: 'bot',
|
||||||
});
|
});
|
||||||
|
if (typeof options.disableGuildSelect === 'boolean') {
|
||||||
|
query.set('disable_guild_select', options.disableGuildSelect.toString());
|
||||||
|
}
|
||||||
|
if (typeof options.guild !== 'undefined') {
|
||||||
|
const guildID = this.guilds.resolveID(options.guild);
|
||||||
|
if (!guildID) throw new TypeError('INVALID_TYPE', 'options.guild', 'GuildResolvable');
|
||||||
|
query.set('guild_id', guildID);
|
||||||
|
}
|
||||||
return `${this.options.http.api}${this.api.oauth2.authorize}?${query}`;
|
return `${this.options.http.api}${this.api.oauth2.authorize}?${query}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
toJSON() {
|
toJSON() {
|
||||||
return super.toJSON({
|
return super.toJSON({
|
||||||
readyAt: false,
|
readyAt: false,
|
||||||
presences: false,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -417,6 +457,13 @@ class Client extends BaseClient {
|
||||||
if (typeof options.messageSweepInterval !== 'number' || isNaN(options.messageSweepInterval)) {
|
if (typeof options.messageSweepInterval !== 'number' || isNaN(options.messageSweepInterval)) {
|
||||||
throw new TypeError('CLIENT_INVALID_OPTION', 'messageSweepInterval', 'a number');
|
throw new TypeError('CLIENT_INVALID_OPTION', 'messageSweepInterval', 'a number');
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
typeof options.messageEditHistoryMaxSize !== 'number' ||
|
||||||
|
isNaN(options.messageEditHistoryMaxSize) ||
|
||||||
|
options.messageEditHistoryMaxSize < -1
|
||||||
|
) {
|
||||||
|
throw new TypeError('CLIENT_INVALID_OPTION', 'messageEditHistoryMaxSize', 'a number greater than or equal to -1');
|
||||||
|
}
|
||||||
if (typeof options.fetchAllMembers !== 'boolean') {
|
if (typeof options.fetchAllMembers !== 'boolean') {
|
||||||
throw new TypeError('CLIENT_INVALID_OPTION', 'fetchAllMembers', 'a boolean');
|
throw new TypeError('CLIENT_INVALID_OPTION', 'fetchAllMembers', 'a boolean');
|
||||||
}
|
}
|
||||||
|
|
@ -443,6 +490,14 @@ class Client extends BaseClient {
|
||||||
|
|
||||||
module.exports = Client;
|
module.exports = Client;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options for {@link Client#generateInvite}.
|
||||||
|
* @typedef {Object} InviteGenerationOptions
|
||||||
|
* @property {PermissionResolvable} [permissions] Permissions to request
|
||||||
|
* @property {GuildResolvable} [guild] Guild to preselect
|
||||||
|
* @property {boolean} [disableGuildSelect] Whether to disable the guild selection
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emitted for general warnings.
|
* Emitted for general warnings.
|
||||||
* @event Client#warn
|
* @event Client#warn
|
||||||
|
|
|
||||||
207
src/client/InteractionClient.js
Normal file
207
src/client/InteractionClient.js
Normal file
|
|
@ -0,0 +1,207 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const BaseClient = require('./BaseClient');
|
||||||
|
const ApplicationCommand = require('../structures/ApplicationCommand');
|
||||||
|
const Interaction = require('../structures/Interaction');
|
||||||
|
const { Events, ApplicationCommandOptionType, InteractionType, InteractionResponseType } = require('../util/Constants');
|
||||||
|
|
||||||
|
let sodium;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interaction client is used for interactions.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const client = new InteractionClient({
|
||||||
|
* token: ABC,
|
||||||
|
* publicKey: XYZ,
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* client.on('interactionCreate', () => {
|
||||||
|
* // automatically handles long responses
|
||||||
|
* if (will take a long time) {
|
||||||
|
* doSomethingLong.then((d) => {
|
||||||
|
* interaction.reply({
|
||||||
|
* content: 'wow that took long',
|
||||||
|
* });
|
||||||
|
* });
|
||||||
|
* } else {
|
||||||
|
* interaction.reply('hi!');
|
||||||
|
* }
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
class InteractionClient extends BaseClient {
|
||||||
|
/**
|
||||||
|
* @param {Options} options Options for the client.
|
||||||
|
* @param {undefined} client For internal use.
|
||||||
|
*/
|
||||||
|
constructor(options, client) {
|
||||||
|
super(options);
|
||||||
|
|
||||||
|
Object.defineProperty(this, 'token', {
|
||||||
|
value: options.token,
|
||||||
|
writable: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.defineProperty(this, 'clientID', {
|
||||||
|
value: options.clientID,
|
||||||
|
writable: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.defineProperty(this, 'publicKey', {
|
||||||
|
value: options.publicKey ? Buffer.from(options.publicKey, 'hex') : undefined,
|
||||||
|
writable: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Compat for direct usage
|
||||||
|
this.client = client || this;
|
||||||
|
this.interactionClient = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get registered slash commands.
|
||||||
|
* @param {Snowflake} [guildID] Optional guild ID.
|
||||||
|
* @returns {Command[]}
|
||||||
|
*/
|
||||||
|
async getCommands(guildID) {
|
||||||
|
let path = this.client.api.applications('@me');
|
||||||
|
if (guildID) {
|
||||||
|
path = path.guilds(guildID);
|
||||||
|
}
|
||||||
|
const commands = await path.commands.get();
|
||||||
|
return commands.map(c => new ApplicationCommand(this, c, guildID));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a command.
|
||||||
|
* @param {Object} command The command description.
|
||||||
|
* @param {Snowflake?} guildID Optional guild ID.
|
||||||
|
* @returns {Promise<ApplicationCommand>} The created command.
|
||||||
|
*/
|
||||||
|
async createCommand(command, guildID) {
|
||||||
|
let path = this.client.api.applications(this.client.user.id);
|
||||||
|
if (guildID) {
|
||||||
|
path = path.guilds(guildID);
|
||||||
|
}
|
||||||
|
const c = await path.commands.post({
|
||||||
|
data: {
|
||||||
|
name: command.name,
|
||||||
|
description: command.description,
|
||||||
|
options: command.options.map(function m(o) {
|
||||||
|
return {
|
||||||
|
type: ApplicationCommandOptionType[o.type],
|
||||||
|
name: o.name,
|
||||||
|
description: o.description,
|
||||||
|
default: o.default,
|
||||||
|
required: o.required,
|
||||||
|
choices: o.choices,
|
||||||
|
options: o.options ? o.options.map(m) : undefined,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return new ApplicationCommand(this, c, guildID);
|
||||||
|
}
|
||||||
|
|
||||||
|
handle(data) {
|
||||||
|
switch (data.type) {
|
||||||
|
case InteractionType.PING:
|
||||||
|
return {
|
||||||
|
type: InteractionResponseType.PONG,
|
||||||
|
};
|
||||||
|
case InteractionType.APPLICATION_COMMAND: {
|
||||||
|
let timedOut = false;
|
||||||
|
let resolve;
|
||||||
|
const directPromise = new Promise(r => {
|
||||||
|
resolve = r;
|
||||||
|
this.client.setTimeout(() => {
|
||||||
|
timedOut = true;
|
||||||
|
r({
|
||||||
|
type: InteractionResponseType.ACKNOWLEDGE_WITH_SOURCE,
|
||||||
|
});
|
||||||
|
}, 250);
|
||||||
|
});
|
||||||
|
|
||||||
|
const syncHandle = {
|
||||||
|
acknowledge() {
|
||||||
|
if (!timedOut) {
|
||||||
|
resolve({
|
||||||
|
type: InteractionResponseType.ACKNOWLEDGE_WITH_SOURCE,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
reply(resolved) {
|
||||||
|
if (timedOut) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
resolve({
|
||||||
|
type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
|
||||||
|
data: resolved.data,
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const interaction = new Interaction(this.client, data, syncHandle);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emitted when an interaction is created.
|
||||||
|
* @event Client#interactionCreate
|
||||||
|
* @param {Interaction} interaction The interaction which was created.
|
||||||
|
*/
|
||||||
|
this.client.emit(Events.INTERACTION_CREATE, interaction);
|
||||||
|
|
||||||
|
return directPromise;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new RangeError('Invalid interaction data');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An express-like middleware factory which can be used
|
||||||
|
* with webhook interactions.
|
||||||
|
* @returns {Function} The middleware function.
|
||||||
|
*/
|
||||||
|
middleware() {
|
||||||
|
return async (req, res) => {
|
||||||
|
const timestamp = req.get('x-signature-timestamp');
|
||||||
|
const signature = req.get('x-signature-ed25519');
|
||||||
|
|
||||||
|
const chunks = [];
|
||||||
|
for await (const chunk of req) {
|
||||||
|
chunks.push(chunk);
|
||||||
|
}
|
||||||
|
const body = Buffer.concat(chunks);
|
||||||
|
|
||||||
|
if (sodium === undefined) {
|
||||||
|
sodium = require('../util/Sodium');
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
!sodium.methods.verify(
|
||||||
|
Buffer.from(signature, 'hex'),
|
||||||
|
Buffer.concat([Buffer.from(timestamp), body]),
|
||||||
|
this.publicKey,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
res.status(403).end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = JSON.parse(body.toString());
|
||||||
|
|
||||||
|
const result = await this.handle(data);
|
||||||
|
res.status(200).end(JSON.stringify(result));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleFromGateway(data) {
|
||||||
|
const result = await this.handle(data);
|
||||||
|
|
||||||
|
await this.client.api.interactions(data.id, data.token).callback.post({
|
||||||
|
data: result,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = InteractionClient;
|
||||||
|
|
@ -81,23 +81,25 @@ class GenericAction {
|
||||||
}
|
}
|
||||||
|
|
||||||
getMember(data, guild) {
|
getMember(data, guild) {
|
||||||
const id = data.user.id;
|
return this.getPayload(data, guild.members, data.user.id, PartialTypes.GUILD_MEMBER);
|
||||||
return this.getPayload(
|
|
||||||
{
|
|
||||||
user: {
|
|
||||||
id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
guild.members,
|
|
||||||
id,
|
|
||||||
PartialTypes.GUILD_MEMBER,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getUser(data) {
|
getUser(data) {
|
||||||
const id = data.user_id;
|
const id = data.user_id;
|
||||||
return data.user || this.getPayload({ id }, this.client.users, id, PartialTypes.USER);
|
return data.user || this.getPayload({ id }, this.client.users, id, PartialTypes.USER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getUserFromMember(data) {
|
||||||
|
if (data.guild_id && data.member && data.member.user) {
|
||||||
|
const guild = this.client.guilds.cache.get(data.guild_id);
|
||||||
|
if (guild) {
|
||||||
|
return guild.members.add(data.member).user;
|
||||||
|
} else {
|
||||||
|
return this.client.users.add(data.member.user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.getUser(data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = GenericAction;
|
module.exports = GenericAction;
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ class ActionsManager {
|
||||||
this.register(require('./InviteCreate'));
|
this.register(require('./InviteCreate'));
|
||||||
this.register(require('./InviteDelete'));
|
this.register(require('./InviteDelete'));
|
||||||
this.register(require('./GuildMemberRemove'));
|
this.register(require('./GuildMemberRemove'));
|
||||||
|
this.register(require('./GuildMemberUpdate'));
|
||||||
this.register(require('./GuildBanRemove'));
|
this.register(require('./GuildBanRemove'));
|
||||||
this.register(require('./GuildRoleCreate'));
|
this.register(require('./GuildRoleCreate'));
|
||||||
this.register(require('./GuildRoleDelete'));
|
this.register(require('./GuildRoleDelete'));
|
||||||
|
|
@ -35,6 +36,8 @@ class ActionsManager {
|
||||||
this.register(require('./GuildChannelsPositionUpdate'));
|
this.register(require('./GuildChannelsPositionUpdate'));
|
||||||
this.register(require('./GuildIntegrationsUpdate'));
|
this.register(require('./GuildIntegrationsUpdate'));
|
||||||
this.register(require('./WebhooksUpdate'));
|
this.register(require('./WebhooksUpdate'));
|
||||||
|
this.register(require('./TypingStart'));
|
||||||
|
this.register(require('./InteractionCreate'));
|
||||||
}
|
}
|
||||||
|
|
||||||
register(Action) {
|
register(Action) {
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,14 @@ const { Events } = require('../../util/Constants');
|
||||||
|
|
||||||
class GuildEmojiCreateAction extends Action {
|
class GuildEmojiCreateAction extends Action {
|
||||||
handle(guild, createdEmoji) {
|
handle(guild, createdEmoji) {
|
||||||
|
const already = guild.emojis.cache.has(createdEmoji.id);
|
||||||
const emoji = guild.emojis.add(createdEmoji);
|
const emoji = guild.emojis.add(createdEmoji);
|
||||||
/**
|
/**
|
||||||
* Emitted whenever a custom emoji is created in a guild.
|
* Emitted whenever a custom emoji is created in a guild.
|
||||||
* @event Client#emojiCreate
|
* @event Client#emojiCreate
|
||||||
* @param {GuildEmoji} emoji The emoji that was created
|
* @param {GuildEmoji} emoji The emoji that was created
|
||||||
*/
|
*/
|
||||||
this.client.emit(Events.GUILD_EMOJI_CREATE, emoji);
|
if (!already) this.client.emit(Events.GUILD_EMOJI_CREATE, emoji);
|
||||||
return { emoji };
|
return { emoji };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ class GuildMemberRemoveAction extends Action {
|
||||||
const guild = client.guilds.cache.get(data.guild_id);
|
const guild = client.guilds.cache.get(data.guild_id);
|
||||||
let member = null;
|
let member = null;
|
||||||
if (guild) {
|
if (guild) {
|
||||||
member = this.getMember(data, guild);
|
member = this.getMember({ user: data.user }, guild);
|
||||||
guild.memberCount--;
|
guild.memberCount--;
|
||||||
if (member) {
|
if (member) {
|
||||||
member.deleted = true;
|
member.deleted = true;
|
||||||
|
|
|
||||||
44
src/client/actions/GuildMemberUpdate.js
Normal file
44
src/client/actions/GuildMemberUpdate.js
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const Action = require('./Action');
|
||||||
|
const { Status, Events } = require('../../util/Constants');
|
||||||
|
|
||||||
|
class GuildMemberUpdateAction extends Action {
|
||||||
|
handle(data, shard) {
|
||||||
|
const { client } = this;
|
||||||
|
if (data.user.username) {
|
||||||
|
const user = client.users.cache.get(data.user.id);
|
||||||
|
if (!user) {
|
||||||
|
client.users.add(data.user);
|
||||||
|
} else if (!user.equals(data.user)) {
|
||||||
|
client.actions.UserUpdate.handle(data.user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const guild = client.guilds.cache.get(data.guild_id);
|
||||||
|
if (guild) {
|
||||||
|
const member = this.getMember({ user: data.user }, guild);
|
||||||
|
if (member) {
|
||||||
|
const old = member._update(data);
|
||||||
|
/**
|
||||||
|
* Emitted whenever a guild member changes - i.e. new role, removed role, nickname.
|
||||||
|
* Also emitted when the user's details (e.g. username) change.
|
||||||
|
* @event Client#guildMemberUpdate
|
||||||
|
* @param {GuildMember} oldMember The member before the update
|
||||||
|
* @param {GuildMember} newMember The member after the update
|
||||||
|
*/
|
||||||
|
if (shard.status === Status.READY) client.emit(Events.GUILD_MEMBER_UPDATE, old, member);
|
||||||
|
} else {
|
||||||
|
const newMember = guild.members.add(data);
|
||||||
|
/**
|
||||||
|
* Emitted whenever a member becomes available in a large guild.
|
||||||
|
* @event Client#guildMemberAvailable
|
||||||
|
* @param {GuildMember} member The member that became available
|
||||||
|
*/
|
||||||
|
this.client.emit(Events.GUILD_MEMBER_AVAILABLE, newMember);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = GuildMemberUpdateAction;
|
||||||
15
src/client/actions/InteractionCreate.js
Normal file
15
src/client/actions/InteractionCreate.js
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const Action = require('./Action');
|
||||||
|
|
||||||
|
class InteractionCreateAction extends Action {
|
||||||
|
handle(data) {
|
||||||
|
this.client.interactionClient.handleFromGateway(data).catch(e => {
|
||||||
|
this.client.emit('error', e);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = InteractionCreateAction;
|
||||||
|
|
@ -9,7 +9,7 @@ class InviteCreateAction extends Action {
|
||||||
const client = this.client;
|
const client = this.client;
|
||||||
const channel = client.channels.cache.get(data.channel_id);
|
const channel = client.channels.cache.get(data.channel_id);
|
||||||
const guild = client.guilds.cache.get(data.guild_id);
|
const guild = client.guilds.cache.get(data.guild_id);
|
||||||
if (!channel && !guild) return false;
|
if (!channel) return false;
|
||||||
|
|
||||||
const inviteData = Object.assign(data, { channel, guild });
|
const inviteData = Object.assign(data, { channel, guild });
|
||||||
const invite = new Invite(client, inviteData);
|
const invite = new Invite(client, inviteData);
|
||||||
|
|
|
||||||
|
|
@ -8,14 +8,17 @@ const { PartialTypes } = require('../../util/Constants');
|
||||||
{ user_id: 'id',
|
{ user_id: 'id',
|
||||||
message_id: 'id',
|
message_id: 'id',
|
||||||
emoji: { name: '<27>', id: null },
|
emoji: { name: '<27>', id: null },
|
||||||
channel_id: 'id' } }
|
channel_id: 'id',
|
||||||
|
// If originating from a guild
|
||||||
|
guild_id: 'id',
|
||||||
|
member: { ..., user: { ... } } }
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class MessageReactionAdd extends Action {
|
class MessageReactionAdd extends Action {
|
||||||
handle(data) {
|
handle(data) {
|
||||||
if (!data.emoji) return false;
|
if (!data.emoji) return false;
|
||||||
|
|
||||||
const user = this.getUser(data);
|
const user = this.getUserFromMember(data);
|
||||||
if (!user) return false;
|
if (!user) return false;
|
||||||
|
|
||||||
// Verify channel
|
// Verify channel
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@ const { Events } = require('../../util/Constants');
|
||||||
{ user_id: 'id',
|
{ user_id: 'id',
|
||||||
message_id: 'id',
|
message_id: 'id',
|
||||||
emoji: { name: '<27>', id: null },
|
emoji: { name: '<27>', id: null },
|
||||||
channel_id: 'id' } }
|
channel_id: 'id',
|
||||||
|
guild_id: 'id' }
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class MessageReactionRemove extends Action {
|
class MessageReactionRemove extends Action {
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,9 @@ class MessageUpdateAction extends Action {
|
||||||
const { id, channel_id, guild_id, author, timestamp, type } = data;
|
const { id, channel_id, guild_id, author, timestamp, type } = data;
|
||||||
const message = this.getMessage({ id, channel_id, guild_id, author, timestamp, type }, channel);
|
const message = this.getMessage({ id, channel_id, guild_id, author, timestamp, type }, channel);
|
||||||
if (message) {
|
if (message) {
|
||||||
message.patch(data);
|
const old = message.patch(data);
|
||||||
return {
|
return {
|
||||||
old: message._edits[0],
|
old,
|
||||||
updated: message,
|
updated: message,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
58
src/client/actions/TypingStart.js
Normal file
58
src/client/actions/TypingStart.js
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const Action = require('./Action');
|
||||||
|
const { Events } = require('../../util/Constants');
|
||||||
|
const textBasedChannelTypes = ['dm', 'text', 'news'];
|
||||||
|
|
||||||
|
class TypingStart extends Action {
|
||||||
|
handle(data) {
|
||||||
|
const channel = this.getChannel(data);
|
||||||
|
if (!channel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!textBasedChannelTypes.includes(channel.type)) {
|
||||||
|
this.client.emit(Events.WARN, `Discord sent a typing packet to a ${channel.type} channel ${channel.id}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = this.getUserFromMember(data);
|
||||||
|
const timestamp = new Date(data.timestamp * 1000);
|
||||||
|
|
||||||
|
if (channel && user) {
|
||||||
|
if (channel._typing.has(user.id)) {
|
||||||
|
const typing = channel._typing.get(user.id);
|
||||||
|
|
||||||
|
typing.lastTimestamp = timestamp;
|
||||||
|
typing.elapsedTime = Date.now() - typing.since;
|
||||||
|
this.client.clearTimeout(typing.timeout);
|
||||||
|
typing.timeout = this.tooLate(channel, user);
|
||||||
|
} else {
|
||||||
|
const since = new Date();
|
||||||
|
const lastTimestamp = new Date();
|
||||||
|
channel._typing.set(user.id, {
|
||||||
|
user,
|
||||||
|
since,
|
||||||
|
lastTimestamp,
|
||||||
|
elapsedTime: Date.now() - since,
|
||||||
|
timeout: this.tooLate(channel, user),
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emitted whenever a user starts typing in a channel.
|
||||||
|
* @event Client#typingStart
|
||||||
|
* @param {Channel} channel The channel the user started typing in
|
||||||
|
* @param {User} user The user that started typing
|
||||||
|
*/
|
||||||
|
this.client.emit(Events.TYPING_START, channel, user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tooLate(channel, user) {
|
||||||
|
return channel.client.setTimeout(() => {
|
||||||
|
channel._typing.delete(user.id);
|
||||||
|
}, 10000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = TypingStart;
|
||||||
|
|
@ -165,7 +165,7 @@ class VoiceConnection extends EventEmitter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The voice state of this connection
|
* The voice state of this connection
|
||||||
* @type {VoiceState}
|
* @type {?VoiceState}
|
||||||
*/
|
*/
|
||||||
get voice() {
|
get voice() {
|
||||||
return this.channel.guild.voice;
|
return this.channel.guild.voice;
|
||||||
|
|
@ -203,8 +203,8 @@ class VoiceConnection extends EventEmitter {
|
||||||
* Set the token and endpoint required to connect to the voice servers.
|
* Set the token and endpoint required to connect to the voice servers.
|
||||||
* @param {string} token The voice token
|
* @param {string} token The voice token
|
||||||
* @param {string} endpoint The voice endpoint
|
* @param {string} endpoint The voice endpoint
|
||||||
* @private
|
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
|
* @private
|
||||||
*/
|
*/
|
||||||
setTokenAndEndpoint(token, endpoint) {
|
setTokenAndEndpoint(token, endpoint) {
|
||||||
this.emit('debug', `Token "${token}" and endpoint "${endpoint}"`);
|
this.emit('debug', `Token "${token}" and endpoint "${endpoint}"`);
|
||||||
|
|
@ -473,7 +473,11 @@ class VoiceConnection extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
onStartSpeaking({ user_id, ssrc, speaking }) {
|
onStartSpeaking({ user_id, ssrc, speaking }) {
|
||||||
this.ssrcMap.set(+ssrc, { userID: user_id, speaking: speaking });
|
this.ssrcMap.set(+ssrc, {
|
||||||
|
...(this.ssrcMap.get(+ssrc) || {}),
|
||||||
|
userID: user_id,
|
||||||
|
speaking: speaking,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -501,7 +505,7 @@ class VoiceConnection extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (guild && user && !speaking.equals(old)) {
|
if (guild && user && !speaking.equals(old)) {
|
||||||
const member = guild.member(user);
|
const member = guild.members.resolve(user);
|
||||||
if (member) {
|
if (member) {
|
||||||
/**
|
/**
|
||||||
* Emitted once a guild member changes speaking state.
|
* Emitted once a guild member changes speaking state.
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const { Writable } = require('stream');
|
const { Writable } = require('stream');
|
||||||
const secretbox = require('../util/Secretbox');
|
const secretbox = require('../../../util/Sodium');
|
||||||
const Silence = require('../util/Silence');
|
const Silence = require('../util/Silence');
|
||||||
const VolumeInterface = require('../util/VolumeInterface');
|
const VolumeInterface = require('../util/VolumeInterface');
|
||||||
|
|
||||||
|
|
@ -56,7 +56,7 @@ class StreamDispatcher extends Writable {
|
||||||
* The broadcast controlling this dispatcher, if any
|
* The broadcast controlling this dispatcher, if any
|
||||||
* @type {?VoiceBroadcast}
|
* @type {?VoiceBroadcast}
|
||||||
*/
|
*/
|
||||||
this.broadcast = this.streams.broadcast;
|
this.broadcast = this.streams.broadcast || null;
|
||||||
|
|
||||||
this._pausedTime = 0;
|
this._pausedTime = 0;
|
||||||
this._silentPausedTime = 0;
|
this._silentPausedTime = 0;
|
||||||
|
|
|
||||||
|
|
@ -189,7 +189,11 @@ class VoiceWebSocket extends EventEmitter {
|
||||||
this.emit('sessionDescription', packet.d);
|
this.emit('sessionDescription', packet.d);
|
||||||
break;
|
break;
|
||||||
case VoiceOPCodes.CLIENT_CONNECT:
|
case VoiceOPCodes.CLIENT_CONNECT:
|
||||||
this.connection.ssrcMap.set(+packet.d.audio_ssrc, { userID: packet.d.user_id, speaking: 0 });
|
this.connection.ssrcMap.set(+packet.d.audio_ssrc, {
|
||||||
|
userID: packet.d.user_id,
|
||||||
|
speaking: 0,
|
||||||
|
hasVideo: Boolean(packet.d.video_ssrc),
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
case VoiceOPCodes.CLIENT_DISCONNECT:
|
case VoiceOPCodes.CLIENT_DISCONNECT:
|
||||||
const streamInfo = this.connection.receiver && this.connection.receiver.packets.streams.get(packet.d.user_id);
|
const streamInfo = this.connection.receiver && this.connection.receiver.packets.streams.get(packet.d.user_id);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const EventEmitter = require('events');
|
const EventEmitter = require('events');
|
||||||
const secretbox = require('../util/Secretbox');
|
const sodium = require('../../../util/Sodium');
|
||||||
|
const Speaking = require('../../../util/Speaking');
|
||||||
|
const { SILENCE_FRAME } = require('../util/Silence');
|
||||||
|
|
||||||
// The delay between packets when a user is considered to have stopped speaking
|
// The delay between packets when a user is considered to have stopped speaking
|
||||||
// https://github.com/discordjs/discord.js/issues/3524#issuecomment-540373200
|
// https://github.com/discordjs/discord.js/issues/3524#issuecomment-540373200
|
||||||
|
|
@ -56,7 +58,7 @@ class PacketHandler extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open packet
|
// Open packet
|
||||||
let packet = secretbox.methods.open(buffer.slice(12, end), this.nonce, secret_key);
|
let packet = sodium.methods.open(buffer.slice(12, end), this.nonce, secret_key);
|
||||||
if (!packet) return new Error('Failed to decrypt voice packet');
|
if (!packet) return new Error('Failed to decrypt voice packet');
|
||||||
packet = Buffer.from(packet);
|
packet = Buffer.from(packet);
|
||||||
|
|
||||||
|
|
@ -84,12 +86,30 @@ class PacketHandler extends EventEmitter {
|
||||||
const userStat = this.connection.ssrcMap.get(ssrc);
|
const userStat = this.connection.ssrcMap.get(ssrc);
|
||||||
if (!userStat) return;
|
if (!userStat) return;
|
||||||
|
|
||||||
|
let opusPacket;
|
||||||
|
const streamInfo = this.streams.get(userStat.userID);
|
||||||
|
// If the user is in video, we need to check if the packet is just silence
|
||||||
|
if (userStat.hasVideo) {
|
||||||
|
opusPacket = this.parseBuffer(buffer);
|
||||||
|
if (opusPacket instanceof Error) {
|
||||||
|
// Only emit an error if we were actively receiving packets from this user
|
||||||
|
if (streamInfo) {
|
||||||
|
this.emit('error', opusPacket);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (SILENCE_FRAME.equals(opusPacket)) {
|
||||||
|
// If this is a silence frame, pretend we never received it
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let speakingTimeout = this.speakingTimeouts.get(ssrc);
|
let speakingTimeout = this.speakingTimeouts.get(ssrc);
|
||||||
if (typeof speakingTimeout === 'undefined') {
|
if (typeof speakingTimeout === 'undefined') {
|
||||||
// Ensure at least the speaking bit is set.
|
// Ensure at least the speaking bit is set.
|
||||||
// As the object is by reference, it's only needed once per client re-connect.
|
// As the object is by reference, it's only needed once per client re-connect.
|
||||||
if (userStat.speaking === 0) {
|
if (userStat.speaking === 0) {
|
||||||
userStat.speaking = 1;
|
userStat.speaking = Speaking.FLAGS.SPEAKING;
|
||||||
}
|
}
|
||||||
this.connection.onSpeaking({ user_id: userStat.userID, ssrc: ssrc, speaking: userStat.speaking });
|
this.connection.onSpeaking({ user_id: userStat.userID, ssrc: ssrc, speaking: userStat.speaking });
|
||||||
speakingTimeout = this.receiver.connection.client.setTimeout(() => {
|
speakingTimeout = this.receiver.connection.client.setTimeout(() => {
|
||||||
|
|
@ -106,15 +126,17 @@ class PacketHandler extends EventEmitter {
|
||||||
speakingTimeout.refresh();
|
speakingTimeout.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
let stream = this.streams.get(userStat.userID);
|
if (streamInfo) {
|
||||||
if (!stream) return;
|
const { stream } = streamInfo;
|
||||||
stream = stream.stream;
|
if (!opusPacket) {
|
||||||
const opusPacket = this.parseBuffer(buffer);
|
opusPacket = this.parseBuffer(buffer);
|
||||||
if (opusPacket instanceof Error) {
|
if (opusPacket instanceof Error) {
|
||||||
this.emit('error', opusPacket);
|
this.emit('error', opusPacket);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stream.push(opusPacket);
|
||||||
}
|
}
|
||||||
stream.push(opusPacket);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,4 +10,6 @@ class Silence extends Readable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Silence.SILENCE_FRAME = SILENCE_FRAME;
|
||||||
|
|
||||||
module.exports = Silence;
|
module.exports = Silence;
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ class WebSocketManager extends EventEmitter {
|
||||||
* The gateway this manager uses
|
* The gateway this manager uses
|
||||||
* @type {?string}
|
* @type {?string}
|
||||||
*/
|
*/
|
||||||
this.gateway = undefined;
|
this.gateway = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The amount of shards this manager handles
|
* The amount of shards this manager handles
|
||||||
|
|
@ -76,7 +76,7 @@ class WebSocketManager extends EventEmitter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current status of this WebSocketManager
|
* The current status of this WebSocketManager
|
||||||
* @type {number}
|
* @type {Status}
|
||||||
*/
|
*/
|
||||||
this.status = Status.IDLE;
|
this.status = Status.IDLE;
|
||||||
|
|
||||||
|
|
@ -98,11 +98,11 @@ class WebSocketManager extends EventEmitter {
|
||||||
* The current session limit of the client
|
* The current session limit of the client
|
||||||
* @private
|
* @private
|
||||||
* @type {?Object}
|
* @type {?Object}
|
||||||
* @prop {number} total Total number of identifies available
|
* @property {number} total Total number of identifies available
|
||||||
* @prop {number} remaining Number of identifies remaining
|
* @property {number} remaining Number of identifies remaining
|
||||||
* @prop {number} reset_after Number of milliseconds after which the limit resets
|
* @property {number} reset_after Number of milliseconds after which the limit resets
|
||||||
*/
|
*/
|
||||||
this.sessionStartLimit = undefined;
|
this.sessionStartLimit = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -212,7 +212,7 @@ class WebSocketManager extends EventEmitter {
|
||||||
|
|
||||||
if (UNRESUMABLE_CLOSE_CODES.includes(event.code)) {
|
if (UNRESUMABLE_CLOSE_CODES.includes(event.code)) {
|
||||||
// These event codes cannot be resumed
|
// These event codes cannot be resumed
|
||||||
shard.sessionID = undefined;
|
shard.sessionID = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -56,10 +56,10 @@ class WebSocketShard extends EventEmitter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current session ID of the shard
|
* The current session ID of the shard
|
||||||
* @type {string}
|
* @type {?string}
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
this.sessionID = undefined;
|
this.sessionID = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The previous heartbeat ping of the shard
|
* The previous heartbeat ping of the shard
|
||||||
|
|
@ -121,10 +121,10 @@ class WebSocketShard extends EventEmitter {
|
||||||
/**
|
/**
|
||||||
* The HELLO timeout
|
* The HELLO timeout
|
||||||
* @name WebSocketShard#helloTimeout
|
* @name WebSocketShard#helloTimeout
|
||||||
* @type {?NodeJS.Timer}
|
* @type {?NodeJS.Timeout}
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
Object.defineProperty(this, 'helloTimeout', { value: undefined, writable: true });
|
Object.defineProperty(this, 'helloTimeout', { value: null, writable: true });
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the manager attached its event handlers on the shard
|
* If the manager attached its event handlers on the shard
|
||||||
|
|
@ -140,15 +140,15 @@ class WebSocketShard extends EventEmitter {
|
||||||
* @type {?Set<string>}
|
* @type {?Set<string>}
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
Object.defineProperty(this, 'expectedGuilds', { value: undefined, writable: true });
|
Object.defineProperty(this, 'expectedGuilds', { value: null, writable: true });
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ready timeout
|
* The ready timeout
|
||||||
* @name WebSocketShard#readyTimeout
|
* @name WebSocketShard#readyTimeout
|
||||||
* @type {?NodeJS.Timer}
|
* @type {?NodeJS.Timeout}
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
Object.defineProperty(this, 'readyTimeout', { value: undefined, writable: true });
|
Object.defineProperty(this, 'readyTimeout', { value: null, writable: true });
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Time when the WebSocket connection was opened
|
* Time when the WebSocket connection was opened
|
||||||
|
|
@ -428,7 +428,7 @@ class WebSocketShard extends EventEmitter {
|
||||||
// Reset the sequence
|
// Reset the sequence
|
||||||
this.sequence = -1;
|
this.sequence = -1;
|
||||||
// Reset the session ID as it's invalid
|
// Reset the session ID as it's invalid
|
||||||
this.sessionID = undefined;
|
this.sessionID = null;
|
||||||
// Set the status to reconnecting
|
// Set the status to reconnecting
|
||||||
this.status = Status.RECONNECTING;
|
this.status = Status.RECONNECTING;
|
||||||
// Finally, emit the INVALID_SESSION event
|
// Finally, emit the INVALID_SESSION event
|
||||||
|
|
@ -457,7 +457,7 @@ class WebSocketShard extends EventEmitter {
|
||||||
// Step 0. Clear the ready timeout, if it exists
|
// Step 0. Clear the ready timeout, if it exists
|
||||||
if (this.readyTimeout) {
|
if (this.readyTimeout) {
|
||||||
this.manager.client.clearTimeout(this.readyTimeout);
|
this.manager.client.clearTimeout(this.readyTimeout);
|
||||||
this.readyTimeout = undefined;
|
this.readyTimeout = null;
|
||||||
}
|
}
|
||||||
// Step 1. If we don't have any other guilds pending, we are ready
|
// Step 1. If we don't have any other guilds pending, we are ready
|
||||||
if (!this.expectedGuilds.size) {
|
if (!this.expectedGuilds.size) {
|
||||||
|
|
@ -480,7 +480,7 @@ class WebSocketShard extends EventEmitter {
|
||||||
this.debug(`Shard did not receive any more guild packets in 15 seconds.
|
this.debug(`Shard did not receive any more guild packets in 15 seconds.
|
||||||
Unavailable guild count: ${this.expectedGuilds.size}`);
|
Unavailable guild count: ${this.expectedGuilds.size}`);
|
||||||
|
|
||||||
this.readyTimeout = undefined;
|
this.readyTimeout = null;
|
||||||
|
|
||||||
this.status = Status.READY;
|
this.status = Status.READY;
|
||||||
|
|
||||||
|
|
@ -498,7 +498,7 @@ class WebSocketShard extends EventEmitter {
|
||||||
if (this.helloTimeout) {
|
if (this.helloTimeout) {
|
||||||
this.debug('Clearing the HELLO timeout.');
|
this.debug('Clearing the HELLO timeout.');
|
||||||
this.manager.client.clearTimeout(this.helloTimeout);
|
this.manager.client.clearTimeout(this.helloTimeout);
|
||||||
this.helloTimeout = undefined;
|
this.helloTimeout = null;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -519,7 +519,7 @@ class WebSocketShard extends EventEmitter {
|
||||||
if (this.heartbeatInterval) {
|
if (this.heartbeatInterval) {
|
||||||
this.debug('Clearing the heartbeat interval.');
|
this.debug('Clearing the heartbeat interval.');
|
||||||
this.manager.client.clearInterval(this.heartbeatInterval);
|
this.manager.client.clearInterval(this.heartbeatInterval);
|
||||||
this.heartbeatInterval = undefined;
|
this.heartbeatInterval = null;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -734,7 +734,7 @@ class WebSocketShard extends EventEmitter {
|
||||||
// Step 5: Reset the sequence and session ID if requested
|
// Step 5: Reset the sequence and session ID if requested
|
||||||
if (reset) {
|
if (reset) {
|
||||||
this.sequence = -1;
|
this.sequence = -1;
|
||||||
this.sessionID = undefined;
|
this.sessionID = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 6: reset the ratelimit data
|
// Step 6: reset the ratelimit data
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,5 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const { Status, Events } = require('../../../util/Constants');
|
module.exports = (client, packet, shard) => {
|
||||||
|
client.actions.GuildMemberUpdate.handle(packet.d, shard);
|
||||||
module.exports = (client, { d: data }, shard) => {
|
|
||||||
let user = client.users.cache.get(data.user.id);
|
|
||||||
if (!user && data.user.username) user = client.users.add(data.user);
|
|
||||||
if (user && data.user && data.user.username) {
|
|
||||||
if (!user.equals(data.user)) client.actions.UserUpdate.handle(data.user);
|
|
||||||
}
|
|
||||||
|
|
||||||
const guild = client.guilds.cache.get(data.guild_id);
|
|
||||||
if (guild) {
|
|
||||||
const member = guild.members.cache.get(data.user.id);
|
|
||||||
if (member) {
|
|
||||||
const old = member._update(data);
|
|
||||||
if (shard.status === Status.READY) {
|
|
||||||
/**
|
|
||||||
* Emitted whenever a guild member changes - i.e. new role, removed role, nickname.
|
|
||||||
* Also emitted when the user's details (e.g. username) change.
|
|
||||||
* @event Client#guildMemberUpdate
|
|
||||||
* @param {GuildMember} oldMember The member before the update
|
|
||||||
* @param {GuildMember} newMember The member after the update
|
|
||||||
*/
|
|
||||||
client.emit(Events.GUILD_MEMBER_UPDATE, old, member);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
5
src/client/websocket/handlers/INTERACTION_CREATE.js
Normal file
5
src/client/websocket/handlers/INTERACTION_CREATE.js
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = (client, packet) => {
|
||||||
|
client.actions.InteractionCreate.handle(packet.d);
|
||||||
|
};
|
||||||
|
|
@ -1,50 +1,5 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const { Events } = require('../../../util/Constants');
|
module.exports = (client, packet) => {
|
||||||
const textBasedChannelTypes = ['dm', 'text', 'news'];
|
client.actions.TypingStart.handle(packet.d);
|
||||||
|
|
||||||
module.exports = (client, { d: data }) => {
|
|
||||||
const channel = client.channels.cache.get(data.channel_id);
|
|
||||||
const user = client.users.cache.get(data.user_id);
|
|
||||||
const timestamp = new Date(data.timestamp * 1000);
|
|
||||||
|
|
||||||
if (channel && user) {
|
|
||||||
if (!textBasedChannelTypes.includes(channel.type)) {
|
|
||||||
client.emit(Events.WARN, `Discord sent a typing packet to a ${channel.type} channel ${channel.id}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (channel._typing.has(user.id)) {
|
|
||||||
const typing = channel._typing.get(user.id);
|
|
||||||
|
|
||||||
typing.lastTimestamp = timestamp;
|
|
||||||
typing.elapsedTime = Date.now() - typing.since;
|
|
||||||
client.clearTimeout(typing.timeout);
|
|
||||||
typing.timeout = tooLate(channel, user);
|
|
||||||
} else {
|
|
||||||
const since = new Date();
|
|
||||||
const lastTimestamp = new Date();
|
|
||||||
channel._typing.set(user.id, {
|
|
||||||
user,
|
|
||||||
since,
|
|
||||||
lastTimestamp,
|
|
||||||
elapsedTime: Date.now() - since,
|
|
||||||
timeout: tooLate(channel, user),
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Emitted whenever a user starts typing in a channel.
|
|
||||||
* @event Client#typingStart
|
|
||||||
* @param {Channel} channel The channel the user started typing in
|
|
||||||
* @param {User} user The user that started typing
|
|
||||||
*/
|
|
||||||
client.emit(Events.TYPING_START, channel, user);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function tooLate(channel, user) {
|
|
||||||
return channel.client.setTimeout(() => {
|
|
||||||
channel._typing.delete(user.id);
|
|
||||||
}, 10000);
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -21,11 +21,16 @@ const Messages = {
|
||||||
DISALLOWED_INTENTS: 'Privileged intent provided is not enabled or whitelisted.',
|
DISALLOWED_INTENTS: 'Privileged intent provided is not enabled or whitelisted.',
|
||||||
SHARDING_NO_SHARDS: 'No shards have been spawned.',
|
SHARDING_NO_SHARDS: 'No shards have been spawned.',
|
||||||
SHARDING_IN_PROCESS: 'Shards are still being spawned.',
|
SHARDING_IN_PROCESS: 'Shards are still being spawned.',
|
||||||
|
SHARDING_SHARD_NOT_FOUND: id => `Shard ${id} could not be found.`,
|
||||||
SHARDING_ALREADY_SPAWNED: count => `Already spawned ${count} shards.`,
|
SHARDING_ALREADY_SPAWNED: count => `Already spawned ${count} shards.`,
|
||||||
SHARDING_PROCESS_EXISTS: id => `Shard ${id} already has an active process.`,
|
SHARDING_PROCESS_EXISTS: id => `Shard ${id} already has an active process.`,
|
||||||
|
SHARDING_WORKER_EXISTS: id => `Shard ${id} already has an active worker.`,
|
||||||
SHARDING_READY_TIMEOUT: id => `Shard ${id}'s Client took too long to become ready.`,
|
SHARDING_READY_TIMEOUT: id => `Shard ${id}'s Client took too long to become ready.`,
|
||||||
SHARDING_READY_DISCONNECTED: id => `Shard ${id}'s Client disconnected before becoming ready.`,
|
SHARDING_READY_DISCONNECTED: id => `Shard ${id}'s Client disconnected before becoming ready.`,
|
||||||
SHARDING_READY_DIED: id => `Shard ${id}'s process exited before its Client became ready.`,
|
SHARDING_READY_DIED: id => `Shard ${id}'s process exited before its Client became ready.`,
|
||||||
|
SHARDING_NO_CHILD_EXISTS: id => `Shard ${id} has no active process or worker.`,
|
||||||
|
SHARDING_SHARD_MISCALCULATION: (shard, guild, count) =>
|
||||||
|
`Calculated invalid shard ${shard} for guild ${guild} with ${count} shards.`,
|
||||||
|
|
||||||
COLOR_RANGE: 'Color must be within the range 0 - 16777215 (0xFFFFFF).',
|
COLOR_RANGE: 'Color must be within the range 0 - 16777215 (0xFFFFFF).',
|
||||||
COLOR_CONVERT: 'Unable to convert color to a number.',
|
COLOR_CONVERT: 'Unable to convert color to a number.',
|
||||||
|
|
@ -66,7 +71,7 @@ const Messages = {
|
||||||
IMAGE_SIZE: size => `Invalid image size: ${size}`,
|
IMAGE_SIZE: size => `Invalid image size: ${size}`,
|
||||||
|
|
||||||
MESSAGE_BULK_DELETE_TYPE: 'The messages must be an Array, Collection, or number.',
|
MESSAGE_BULK_DELETE_TYPE: 'The messages must be an Array, Collection, or number.',
|
||||||
MESSAGE_NONCE_TYPE: 'Message nonce must fit in an unsigned 64-bit integer.',
|
MESSAGE_NONCE_TYPE: 'Message nonce must be an integer or a string.',
|
||||||
|
|
||||||
TYPING_COUNT: 'Count must be at least 1',
|
TYPING_COUNT: 'Count must be at least 1',
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ module.exports = {
|
||||||
// "Root" classes (starting points)
|
// "Root" classes (starting points)
|
||||||
BaseClient: require('./client/BaseClient'),
|
BaseClient: require('./client/BaseClient'),
|
||||||
Client: require('./client/Client'),
|
Client: require('./client/Client'),
|
||||||
|
InteractionClient: require('./client/InteractionClient'),
|
||||||
Shard: require('./sharding/Shard'),
|
Shard: require('./sharding/Shard'),
|
||||||
ShardClientUtil: require('./sharding/ShardClientUtil'),
|
ShardClientUtil: require('./sharding/ShardClientUtil'),
|
||||||
ShardingManager: require('./sharding/ShardingManager'),
|
ShardingManager: require('./sharding/ShardingManager'),
|
||||||
|
|
@ -33,6 +34,7 @@ module.exports = {
|
||||||
version: require('../package.json').version,
|
version: require('../package.json').version,
|
||||||
|
|
||||||
// Managers
|
// Managers
|
||||||
|
BaseGuildEmojiManager: require('./managers/BaseGuildEmojiManager'),
|
||||||
ChannelManager: require('./managers/ChannelManager'),
|
ChannelManager: require('./managers/ChannelManager'),
|
||||||
GuildChannelManager: require('./managers/GuildChannelManager'),
|
GuildChannelManager: require('./managers/GuildChannelManager'),
|
||||||
GuildEmojiManager: require('./managers/GuildEmojiManager'),
|
GuildEmojiManager: require('./managers/GuildEmojiManager'),
|
||||||
|
|
@ -56,6 +58,8 @@ module.exports = {
|
||||||
splitMessage: Util.splitMessage,
|
splitMessage: Util.splitMessage,
|
||||||
|
|
||||||
// Structures
|
// Structures
|
||||||
|
Application: require('./structures/interfaces/Application'),
|
||||||
|
ApplicationCommand: require('./structures/ApplicationCommand'),
|
||||||
Base: require('./structures/Base'),
|
Base: require('./structures/Base'),
|
||||||
Activity: require('./structures/Presence').Activity,
|
Activity: require('./structures/Presence').Activity,
|
||||||
APIMessage: require('./structures/APIMessage'),
|
APIMessage: require('./structures/APIMessage'),
|
||||||
|
|
@ -76,7 +80,9 @@ module.exports = {
|
||||||
GuildEmoji: require('./structures/GuildEmoji'),
|
GuildEmoji: require('./structures/GuildEmoji'),
|
||||||
GuildMember: require('./structures/GuildMember'),
|
GuildMember: require('./structures/GuildMember'),
|
||||||
GuildPreview: require('./structures/GuildPreview'),
|
GuildPreview: require('./structures/GuildPreview'),
|
||||||
|
GuildTemplate: require('./structures/GuildTemplate'),
|
||||||
Integration: require('./structures/Integration'),
|
Integration: require('./structures/Integration'),
|
||||||
|
Interaction: require('./structures/Interaction'),
|
||||||
Invite: require('./structures/Invite'),
|
Invite: require('./structures/Invite'),
|
||||||
Message: require('./structures/Message'),
|
Message: require('./structures/Message'),
|
||||||
MessageAttachment: require('./structures/MessageAttachment'),
|
MessageAttachment: require('./structures/MessageAttachment'),
|
||||||
|
|
|
||||||
80
src/managers/BaseGuildEmojiManager.js
Normal file
80
src/managers/BaseGuildEmojiManager.js
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const BaseManager = require('./BaseManager');
|
||||||
|
const GuildEmoji = require('../structures/GuildEmoji');
|
||||||
|
const ReactionEmoji = require('../structures/ReactionEmoji');
|
||||||
|
const { parseEmoji } = require('../util/Util');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds methods to resolve GuildEmojis and stores their cache.
|
||||||
|
* @extends {BaseManager}
|
||||||
|
*/
|
||||||
|
class BaseGuildEmojiManager extends BaseManager {
|
||||||
|
constructor(client, iterable) {
|
||||||
|
super(client, iterable, GuildEmoji);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The cache of GuildEmojis
|
||||||
|
* @type {Collection<Snowflake, GuildEmoji>}
|
||||||
|
* @name BaseGuildEmojiManager#cache
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data that can be resolved into a GuildEmoji object. This can be:
|
||||||
|
* * A custom emoji ID
|
||||||
|
* * A GuildEmoji object
|
||||||
|
* * A ReactionEmoji object
|
||||||
|
* @typedef {Snowflake|GuildEmoji|ReactionEmoji} EmojiResolvable
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves an EmojiResolvable to an Emoji object.
|
||||||
|
* @param {EmojiResolvable} emoji The Emoji resolvable to identify
|
||||||
|
* @returns {?GuildEmoji}
|
||||||
|
*/
|
||||||
|
resolve(emoji) {
|
||||||
|
if (emoji instanceof ReactionEmoji) return super.resolve(emoji.id);
|
||||||
|
return super.resolve(emoji);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves an EmojiResolvable to an Emoji ID string.
|
||||||
|
* @param {EmojiResolvable} emoji The Emoji resolvable to identify
|
||||||
|
* @returns {?Snowflake}
|
||||||
|
*/
|
||||||
|
resolveID(emoji) {
|
||||||
|
if (emoji instanceof ReactionEmoji) return emoji.id;
|
||||||
|
return super.resolveID(emoji);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data that can be resolved to give an emoji identifier. This can be:
|
||||||
|
* * The unicode representation of an emoji
|
||||||
|
* * The `<a:name:id>`, `<:name:id>`, `a:name:id` or `name:id` emoji identifier string of an emoji
|
||||||
|
* * An EmojiResolvable
|
||||||
|
* @typedef {string|EmojiResolvable} EmojiIdentifierResolvable
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves an EmojiResolvable to an emoji identifier.
|
||||||
|
* @param {EmojiIdentifierResolvable} emoji The emoji resolvable to resolve
|
||||||
|
* @returns {?string}
|
||||||
|
*/
|
||||||
|
resolveIdentifier(emoji) {
|
||||||
|
const emojiResolvable = this.resolve(emoji);
|
||||||
|
if (emojiResolvable) return emojiResolvable.identifier;
|
||||||
|
if (emoji instanceof ReactionEmoji) return emoji.identifier;
|
||||||
|
if (typeof emoji === 'string') {
|
||||||
|
const res = parseEmoji(emoji);
|
||||||
|
if (res && res.name.length) {
|
||||||
|
emoji = `${res.animated ? 'a:' : ''}${res.name}${res.id ? `:${res.id}` : ''}`;
|
||||||
|
}
|
||||||
|
if (!emoji.includes('%')) return encodeURIComponent(emoji);
|
||||||
|
return emoji;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = BaseGuildEmojiManager;
|
||||||
|
|
@ -1,19 +1,18 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const BaseManager = require('./BaseManager');
|
const BaseGuildEmojiManager = require('./BaseGuildEmojiManager');
|
||||||
const { TypeError } = require('../errors');
|
const { TypeError } = require('../errors');
|
||||||
const GuildEmoji = require('../structures/GuildEmoji');
|
|
||||||
const ReactionEmoji = require('../structures/ReactionEmoji');
|
|
||||||
const Collection = require('../util/Collection');
|
const Collection = require('../util/Collection');
|
||||||
const DataResolver = require('../util/DataResolver');
|
const DataResolver = require('../util/DataResolver');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages API methods for GuildEmojis and stores their cache.
|
* Manages API methods for GuildEmojis and stores their cache.
|
||||||
* @extends {BaseManager}
|
* @extends {BaseGuildEmojiManager}
|
||||||
*/
|
*/
|
||||||
class GuildEmojiManager extends BaseManager {
|
class GuildEmojiManager extends BaseGuildEmojiManager {
|
||||||
constructor(guild, iterable) {
|
constructor(guild, iterable) {
|
||||||
super(guild.client, iterable, GuildEmoji);
|
super(guild.client, iterable);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The guild this manager belongs to
|
* The guild this manager belongs to
|
||||||
* @type {Guild}
|
* @type {Guild}
|
||||||
|
|
@ -21,12 +20,6 @@ class GuildEmojiManager extends BaseManager {
|
||||||
this.guild = guild;
|
this.guild = guild;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* The cache of GuildEmojis
|
|
||||||
* @type {Collection<Snowflake, GuildEmoji>}
|
|
||||||
* @name GuildEmojiManager#cache
|
|
||||||
*/
|
|
||||||
|
|
||||||
add(data, cache) {
|
add(data, cache) {
|
||||||
return super.add(data, cache, { extras: [this.guild] });
|
return super.add(data, cache, { extras: [this.guild] });
|
||||||
}
|
}
|
||||||
|
|
@ -73,57 +66,6 @@ class GuildEmojiManager extends BaseManager {
|
||||||
.emojis.post({ data, reason })
|
.emojis.post({ data, reason })
|
||||||
.then(emoji => this.client.actions.GuildEmojiCreate.handle(this.guild, emoji).emoji);
|
.then(emoji => this.client.actions.GuildEmojiCreate.handle(this.guild, emoji).emoji);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Data that can be resolved into an GuildEmoji object. This can be:
|
|
||||||
* * A custom emoji ID
|
|
||||||
* * A GuildEmoji object
|
|
||||||
* * A ReactionEmoji object
|
|
||||||
* @typedef {Snowflake|GuildEmoji|ReactionEmoji} EmojiResolvable
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolves an EmojiResolvable to an Emoji object.
|
|
||||||
* @param {EmojiResolvable} emoji The Emoji resolvable to identify
|
|
||||||
* @returns {?GuildEmoji}
|
|
||||||
*/
|
|
||||||
resolve(emoji) {
|
|
||||||
if (emoji instanceof ReactionEmoji) return super.resolve(emoji.id);
|
|
||||||
return super.resolve(emoji);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolves an EmojiResolvable to an Emoji ID string.
|
|
||||||
* @param {EmojiResolvable} emoji The Emoji resolvable to identify
|
|
||||||
* @returns {?Snowflake}
|
|
||||||
*/
|
|
||||||
resolveID(emoji) {
|
|
||||||
if (emoji instanceof ReactionEmoji) return emoji.id;
|
|
||||||
return super.resolveID(emoji);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Data that can be resolved to give an emoji identifier. This can be:
|
|
||||||
* * The unicode representation of an emoji
|
|
||||||
* * An EmojiResolvable
|
|
||||||
* @typedef {string|EmojiResolvable} EmojiIdentifierResolvable
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolves an EmojiResolvable to an emoji identifier.
|
|
||||||
* @param {EmojiIdentifierResolvable} emoji The emoji resolvable to resolve
|
|
||||||
* @returns {?string}
|
|
||||||
*/
|
|
||||||
resolveIdentifier(emoji) {
|
|
||||||
const emojiResolvable = this.resolve(emoji);
|
|
||||||
if (emojiResolvable) return emojiResolvable.identifier;
|
|
||||||
if (emoji instanceof ReactionEmoji) return emoji.identifier;
|
|
||||||
if (typeof emoji === 'string') {
|
|
||||||
if (!emoji.includes('%')) return encodeURIComponent(emoji);
|
|
||||||
else return emoji;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = GuildEmojiManager;
|
module.exports = GuildEmojiManager;
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ const GuildMember = require('../structures/GuildMember');
|
||||||
const Invite = require('../structures/Invite');
|
const Invite = require('../structures/Invite');
|
||||||
const Role = require('../structures/Role');
|
const Role = require('../structures/Role');
|
||||||
const {
|
const {
|
||||||
|
ChannelTypes,
|
||||||
Events,
|
Events,
|
||||||
VerificationLevels,
|
VerificationLevels,
|
||||||
DefaultMessageNotifications,
|
DefaultMessageNotifications,
|
||||||
|
|
@ -129,6 +130,8 @@ class GuildManager extends BaseManager {
|
||||||
* <warn>This is only available to bots in fewer than 10 guilds.</warn>
|
* <warn>This is only available to bots in fewer than 10 guilds.</warn>
|
||||||
* @param {string} name The name of the guild
|
* @param {string} name The name of the guild
|
||||||
* @param {Object} [options] Options for the creating
|
* @param {Object} [options] Options for the creating
|
||||||
|
* @param {number} [options.afkChannelID] The ID of the AFK channel
|
||||||
|
* @param {number} [options.afkTimeout] The AFK timeout in seconds
|
||||||
* @param {PartialChannelData[]} [options.channels] The channels for this guild
|
* @param {PartialChannelData[]} [options.channels] The channels for this guild
|
||||||
* @param {DefaultMessageNotifications} [options.defaultMessageNotifications] The default message notifications
|
* @param {DefaultMessageNotifications} [options.defaultMessageNotifications] The default message notifications
|
||||||
* for the guild
|
* for the guild
|
||||||
|
|
@ -137,18 +140,22 @@ class GuildManager extends BaseManager {
|
||||||
* @param {string} [options.region] The region for the server, defaults to the closest one available
|
* @param {string} [options.region] The region for the server, defaults to the closest one available
|
||||||
* @param {PartialRoleData[]} [options.roles] The roles for this guild,
|
* @param {PartialRoleData[]} [options.roles] The roles for this guild,
|
||||||
* the first element of this array is used to change properties of the guild's everyone role.
|
* the first element of this array is used to change properties of the guild's everyone role.
|
||||||
|
* @param {number} [options.systemChannelID] The ID of the system channel
|
||||||
* @param {VerificationLevel} [options.verificationLevel] The verification level for the guild
|
* @param {VerificationLevel} [options.verificationLevel] The verification level for the guild
|
||||||
* @returns {Promise<Guild>} The guild that was created
|
* @returns {Promise<Guild>} The guild that was created
|
||||||
*/
|
*/
|
||||||
async create(
|
async create(
|
||||||
name,
|
name,
|
||||||
{
|
{
|
||||||
|
afkChannelID,
|
||||||
|
afkTimeout,
|
||||||
channels = [],
|
channels = [],
|
||||||
defaultMessageNotifications,
|
defaultMessageNotifications,
|
||||||
explicitContentFilter,
|
explicitContentFilter,
|
||||||
icon = null,
|
icon = null,
|
||||||
region,
|
region,
|
||||||
roles = [],
|
roles = [],
|
||||||
|
systemChannelID,
|
||||||
verificationLevel,
|
verificationLevel,
|
||||||
} = {},
|
} = {},
|
||||||
) {
|
) {
|
||||||
|
|
@ -163,6 +170,7 @@ class GuildManager extends BaseManager {
|
||||||
explicitContentFilter = ExplicitContentFilterLevels.indexOf(explicitContentFilter);
|
explicitContentFilter = ExplicitContentFilterLevels.indexOf(explicitContentFilter);
|
||||||
}
|
}
|
||||||
for (const channel of channels) {
|
for (const channel of channels) {
|
||||||
|
if (channel.type) channel.type = ChannelTypes[channel.type.toUpperCase()];
|
||||||
channel.parent_id = channel.parentID;
|
channel.parent_id = channel.parentID;
|
||||||
delete channel.parentID;
|
delete channel.parentID;
|
||||||
if (!channel.permissionOverwrites) continue;
|
if (!channel.permissionOverwrites) continue;
|
||||||
|
|
@ -187,8 +195,11 @@ class GuildManager extends BaseManager {
|
||||||
verification_level: verificationLevel,
|
verification_level: verificationLevel,
|
||||||
default_message_notifications: defaultMessageNotifications,
|
default_message_notifications: defaultMessageNotifications,
|
||||||
explicit_content_filter: explicitContentFilter,
|
explicit_content_filter: explicitContentFilter,
|
||||||
channels,
|
|
||||||
roles,
|
roles,
|
||||||
|
channels,
|
||||||
|
afk_channel_id: afkChannelID,
|
||||||
|
afk_timeout: afkTimeout,
|
||||||
|
system_channel_id: systemChannelID,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then(data => {
|
.then(data => {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ const { Error, TypeError, RangeError } = require('../errors');
|
||||||
const GuildMember = require('../structures/GuildMember');
|
const GuildMember = require('../structures/GuildMember');
|
||||||
const Collection = require('../util/Collection');
|
const Collection = require('../util/Collection');
|
||||||
const { Events, OPCodes } = require('../util/Constants');
|
const { Events, OPCodes } = require('../util/Constants');
|
||||||
|
const SnowflakeUtil = require('../util/Snowflake');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages API methods for GuildMembers and stores their cache.
|
* Manages API methods for GuildMembers and stores their cache.
|
||||||
|
|
@ -161,19 +162,22 @@ class GuildMemberManager extends BaseManager {
|
||||||
* .then(pruned => console.log(`I just pruned ${pruned} people!`))
|
* .then(pruned => console.log(`I just pruned ${pruned} people!`))
|
||||||
* .catch(console.error);
|
* .catch(console.error);
|
||||||
*/
|
*/
|
||||||
prune({ days = 7, dry = false, count = true, roles = [], reason } = {}) {
|
prune({ days = 7, dry = false, count: compute_prune_count = true, roles = [], reason } = {}) {
|
||||||
if (typeof days !== 'number') throw new TypeError('PRUNE_DAYS_TYPE');
|
if (typeof days !== 'number') throw new TypeError('PRUNE_DAYS_TYPE');
|
||||||
|
|
||||||
const query = new URLSearchParams();
|
const query = { days };
|
||||||
query.set('days', days);
|
const resolvedRoles = [];
|
||||||
query.set('compute_prune_count', count);
|
|
||||||
|
|
||||||
for (const role of roles) {
|
for (const role of roles) {
|
||||||
const resolvedRole = this.guild.roles.resolveID(role);
|
const resolvedRole = this.guild.roles.resolveID(role);
|
||||||
if (!resolvedRole) {
|
if (!resolvedRole) {
|
||||||
return Promise.reject(new TypeError('INVALID_TYPE', 'roles', 'Array of Roles or Snowflakes', true));
|
return Promise.reject(new TypeError('INVALID_TYPE', 'roles', 'Array of Roles or Snowflakes', true));
|
||||||
}
|
}
|
||||||
query.append('include_roles', role);
|
resolvedRoles.push(resolvedRole);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resolvedRoles.length) {
|
||||||
|
query.include_roles = dry ? resolvedRoles.join(',') : resolvedRoles;
|
||||||
}
|
}
|
||||||
|
|
||||||
const endpoint = this.client.api.guilds(this.guild.id).prune;
|
const endpoint = this.client.api.guilds(this.guild.id).prune;
|
||||||
|
|
@ -182,13 +186,12 @@ class GuildMemberManager extends BaseManager {
|
||||||
return endpoint.get({ query, reason }).then(data => data.pruned);
|
return endpoint.get({ query, reason }).then(data => data.pruned);
|
||||||
}
|
}
|
||||||
|
|
||||||
const body = [...query.entries()].reduce((acc, [k, v]) => {
|
return endpoint
|
||||||
if (k === 'include_roles') v = (acc[k] || []).concat(v);
|
.post({
|
||||||
acc[k] = v;
|
data: { ...query, compute_prune_count },
|
||||||
return acc;
|
reason,
|
||||||
}, {});
|
})
|
||||||
|
.then(data => data.pruned);
|
||||||
return endpoint.post({ data: body, reason }).then(data => data.pruned);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -207,6 +210,7 @@ class GuildMemberManager extends BaseManager {
|
||||||
* .catch(console.error);
|
* .catch(console.error);
|
||||||
*/
|
*/
|
||||||
ban(user, options = { days: 0 }) {
|
ban(user, options = { days: 0 }) {
|
||||||
|
if (typeof options !== 'object') return Promise.reject(new TypeError('INVALID_TYPE', 'options', 'object', true));
|
||||||
if (options.days) options.delete_message_days = options.days;
|
if (options.days) options.delete_message_days = options.days;
|
||||||
const id = this.client.users.resolveID(user);
|
const id = this.client.users.resolveID(user);
|
||||||
if (!id) return Promise.reject(new Error('BAN_RESOLVE_ID', true));
|
if (!id) return Promise.reject(new Error('BAN_RESOLVE_ID', true));
|
||||||
|
|
@ -263,7 +267,7 @@ class GuildMemberManager extends BaseManager {
|
||||||
user: user_ids,
|
user: user_ids,
|
||||||
query,
|
query,
|
||||||
time = 120e3,
|
time = 120e3,
|
||||||
nonce = Date.now().toString(16),
|
nonce = SnowflakeUtil.generate(),
|
||||||
force = false,
|
force = false,
|
||||||
} = {}) {
|
} = {}) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const BaseManager = require('./BaseManager');
|
const BaseManager = require('./BaseManager');
|
||||||
|
const { TypeError } = require('../errors');
|
||||||
const Message = require('../structures/Message');
|
const Message = require('../structures/Message');
|
||||||
const Collection = require('../util/Collection');
|
const Collection = require('../util/Collection');
|
||||||
const LimitedCollection = require('../util/LimitedCollection');
|
const LimitedCollection = require('../util/LimitedCollection');
|
||||||
|
|
@ -75,7 +76,7 @@ class MessageManager extends BaseManager {
|
||||||
* @returns {Promise<Collection<Snowflake, Message>>}
|
* @returns {Promise<Collection<Snowflake, Message>>}
|
||||||
* @example
|
* @example
|
||||||
* // Get pinned messages
|
* // Get pinned messages
|
||||||
* channel.fetchPinned()
|
* channel.messages.fetchPinned()
|
||||||
* .then(messages => console.log(`Received ${messages.size} messages`))
|
* .then(messages => console.log(`Received ${messages.size} messages`))
|
||||||
* .catch(console.error);
|
* .catch(console.error);
|
||||||
*/
|
*/
|
||||||
|
|
@ -120,9 +121,9 @@ class MessageManager extends BaseManager {
|
||||||
*/
|
*/
|
||||||
async delete(message, reason) {
|
async delete(message, reason) {
|
||||||
message = this.resolveID(message);
|
message = this.resolveID(message);
|
||||||
if (message) {
|
if (!message) throw new TypeError('INVALID_TYPE', 'message', 'MessageResolvable');
|
||||||
await this.client.api.channels(this.channel.id).messages(message).delete({ reason });
|
|
||||||
}
|
await this.client.api.channels(this.channel.id).messages(message).delete({ reason });
|
||||||
}
|
}
|
||||||
|
|
||||||
async _fetchId(messageID, cache, force) {
|
async _fetchId(messageID, cache, force) {
|
||||||
|
|
|
||||||
|
|
@ -48,16 +48,16 @@ class ReactionUserManager extends BaseManager {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes a user from this reaction.
|
* Removes a user from this reaction.
|
||||||
* @param {UserResolvable} [user=this.reaction.message.client.user] The user to remove the reaction of
|
* @param {UserResolvable} [user=this.client.user] The user to remove the reaction of
|
||||||
* @returns {Promise<MessageReaction>}
|
* @returns {Promise<MessageReaction>}
|
||||||
*/
|
*/
|
||||||
remove(user = this.reaction.message.client.user) {
|
remove(user = this.client.user) {
|
||||||
const message = this.reaction.message;
|
const userID = this.client.users.resolveID(user);
|
||||||
const userID = message.client.users.resolveID(user);
|
|
||||||
if (!userID) return Promise.reject(new Error('REACTION_RESOLVE_USER'));
|
if (!userID) return Promise.reject(new Error('REACTION_RESOLVE_USER'));
|
||||||
return message.client.api.channels[message.channel.id].messages[message.id].reactions[
|
const message = this.reaction.message;
|
||||||
this.reaction.emoji.identifier
|
return this.client.api.channels[message.channel.id].messages[message.id].reactions[this.reaction.emoji.identifier][
|
||||||
][userID === message.client.user.id ? '@me' : userID]
|
userID === this.client.user.id ? '@me' : userID
|
||||||
|
]
|
||||||
.delete()
|
.delete()
|
||||||
.then(() => this.reaction);
|
.then(() => this.reaction);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
const BaseManager = require('./BaseManager');
|
const BaseManager = require('./BaseManager');
|
||||||
const Role = require('../structures/Role');
|
const Role = require('../structures/Role');
|
||||||
|
const Collection = require('../util/Collection');
|
||||||
const Permissions = require('../util/Permissions');
|
const Permissions = require('../util/Permissions');
|
||||||
const { resolveColor } = require('../util/Util');
|
const { resolveColor } = require('../util/Util');
|
||||||
|
|
||||||
|
|
@ -31,10 +32,10 @@ class RoleManager extends BaseManager {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtains one or more roles from Discord, or the role cache if they're already available.
|
* Obtains one or more roles from Discord, or the role cache if they're already available.
|
||||||
* @param {Snowflake} [id] ID or IDs of the role(s)
|
* @param {Snowflake} [id] ID of the role to fetch
|
||||||
* @param {boolean} [cache=true] Whether to cache the new roles objects if it weren't already
|
* @param {boolean} [cache=true] Whether to cache the new role object(s) if they weren't already
|
||||||
* @param {boolean} [force=false] Whether to skip the cache check and request the API
|
* @param {boolean} [force=false] Whether to skip the cache check and request the API
|
||||||
* @returns {Promise<Role|RoleManager>}
|
* @returns {Promise<?Role|Collection<Snowflake, Role>>}
|
||||||
* @example
|
* @example
|
||||||
* // Fetch all roles from the guild
|
* // Fetch all roles from the guild
|
||||||
* message.guild.roles.fetch()
|
* message.guild.roles.fetch()
|
||||||
|
|
@ -53,9 +54,10 @@ class RoleManager extends BaseManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
// We cannot fetch a single role, as of this commit's date, Discord API throws with 405
|
// We cannot fetch a single role, as of this commit's date, Discord API throws with 405
|
||||||
const roles = await this.client.api.guilds(this.guild.id).roles.get();
|
const data = await this.client.api.guilds(this.guild.id).roles.get();
|
||||||
for (const role of roles) this.add(role, cache);
|
const roles = new Collection();
|
||||||
return id ? this.cache.get(id) || null : this;
|
for (const role of data) roles.set(role.id, this.add(role, cache));
|
||||||
|
return id ? roles.get(id) || null : roles;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ class APIRequest {
|
||||||
this.method = method;
|
this.method = method;
|
||||||
this.route = options.route;
|
this.route = options.route;
|
||||||
this.options = options;
|
this.options = options;
|
||||||
|
this.retries = 0;
|
||||||
|
|
||||||
let queryString = '';
|
let queryString = '';
|
||||||
if (options.query) {
|
if (options.query) {
|
||||||
|
|
|
||||||
95
src/rest/AsyncQueue.js
Normal file
95
src/rest/AsyncQueue.js
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
/**
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020 kyranet, discord.js
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// TODO(kyranet, vladfrangu): replace this with discord.js v13's core AsyncQueue.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An async queue that preserves the stack and prevents lock-ups.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
class AsyncQueue {
|
||||||
|
constructor() {
|
||||||
|
/**
|
||||||
|
* The promises array.
|
||||||
|
* @type {Array<{promise: Promise<void>, resolve: Function}>}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.promises = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The remaining amount of queued promises
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
get remaining() {
|
||||||
|
return this.promises.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Waits for last promise and queues a new one.
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
* @example
|
||||||
|
* const queue = new AsyncQueue();
|
||||||
|
* async function request(url, options) {
|
||||||
|
* await queue.wait();
|
||||||
|
* try {
|
||||||
|
* const result = await fetch(url, options);
|
||||||
|
* // Do some operations with 'result'
|
||||||
|
* } finally {
|
||||||
|
* // Remove first entry from the queue and resolve for the next entry
|
||||||
|
* queue.shift();
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* request(someUrl1, someOptions1); // Will call fetch() immediately
|
||||||
|
* request(someUrl2, someOptions2); // Will call fetch() after the first finished
|
||||||
|
* request(someUrl3, someOptions3); // Will call fetch() after the second finished
|
||||||
|
*/
|
||||||
|
wait() {
|
||||||
|
const next = this.promises.length ? this.promises[this.promises.length - 1].promise : Promise.resolve();
|
||||||
|
let resolve;
|
||||||
|
const promise = new Promise(res => {
|
||||||
|
resolve = res;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.promises.push({
|
||||||
|
resolve,
|
||||||
|
promise,
|
||||||
|
});
|
||||||
|
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frees the queue's lock for the next item to process.
|
||||||
|
*/
|
||||||
|
shift() {
|
||||||
|
const deferred = this.promises.shift();
|
||||||
|
if (typeof deferred !== 'undefined') deferred.resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = AsyncQueue;
|
||||||
|
|
@ -35,19 +35,6 @@ class RESTManager {
|
||||||
return Endpoints.CDN(this.client.options.http.cdn);
|
return Endpoints.CDN(this.client.options.http.cdn);
|
||||||
}
|
}
|
||||||
|
|
||||||
push(handler, apiRequest) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
handler
|
|
||||||
.push({
|
|
||||||
request: apiRequest,
|
|
||||||
resolve,
|
|
||||||
reject,
|
|
||||||
retries: 0,
|
|
||||||
})
|
|
||||||
.catch(reject);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
request(method, url, options = {}) {
|
request(method, url, options = {}) {
|
||||||
const apiRequest = new APIRequest(this, method, url, options);
|
const apiRequest = new APIRequest(this, method, url, options);
|
||||||
let handler = this.handlers.get(apiRequest.route);
|
let handler = this.handlers.get(apiRequest.route);
|
||||||
|
|
@ -57,7 +44,11 @@ class RESTManager {
|
||||||
this.handlers.set(apiRequest.route, handler);
|
this.handlers.set(apiRequest.route, handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.push(handler, apiRequest);
|
return handler.push(apiRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
get endpoint() {
|
||||||
|
return this.client.options.http.api;
|
||||||
}
|
}
|
||||||
|
|
||||||
set endpoint(endpoint) {
|
set endpoint(endpoint) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const AsyncQueue = require('./AsyncQueue');
|
||||||
const DiscordAPIError = require('./DiscordAPIError');
|
const DiscordAPIError = require('./DiscordAPIError');
|
||||||
const HTTPError = require('./HTTPError');
|
const HTTPError = require('./HTTPError');
|
||||||
const {
|
const {
|
||||||
|
|
@ -25,46 +26,31 @@ function calculateReset(reset, serverDate) {
|
||||||
class RequestHandler {
|
class RequestHandler {
|
||||||
constructor(manager) {
|
constructor(manager) {
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
this.busy = false;
|
this.queue = new AsyncQueue();
|
||||||
this.queue = [];
|
|
||||||
this.reset = -1;
|
this.reset = -1;
|
||||||
this.remaining = -1;
|
this.remaining = -1;
|
||||||
this.limit = -1;
|
this.limit = -1;
|
||||||
this.retryAfter = -1;
|
this.retryAfter = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
push(request) {
|
async push(request) {
|
||||||
if (this.busy) {
|
await this.queue.wait();
|
||||||
this.queue.push(request);
|
try {
|
||||||
return this.run();
|
return await this.execute(request);
|
||||||
} else {
|
} finally {
|
||||||
return this.execute(request);
|
this.queue.shift();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
run() {
|
|
||||||
if (this.queue.length === 0) return Promise.resolve();
|
|
||||||
return this.execute(this.queue.shift());
|
|
||||||
}
|
|
||||||
|
|
||||||
get limited() {
|
get limited() {
|
||||||
return Boolean(this.manager.globalTimeout) || (this.remaining <= 0 && Date.now() < this.reset);
|
return Boolean(this.manager.globalTimeout) || (this.remaining <= 0 && Date.now() < this.reset);
|
||||||
}
|
}
|
||||||
|
|
||||||
get _inactive() {
|
get _inactive() {
|
||||||
return this.queue.length === 0 && !this.limited && this.busy !== true;
|
return this.queue.remaining === 0 && !this.limited;
|
||||||
}
|
}
|
||||||
|
|
||||||
async execute(item) {
|
async execute(request) {
|
||||||
// Insert item back to the beginning if currently busy
|
|
||||||
if (this.busy) {
|
|
||||||
this.queue.unshift(item);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.busy = true;
|
|
||||||
const { reject, request, resolve } = item;
|
|
||||||
|
|
||||||
// After calculations and requests have been done, pre-emptively stop further requests
|
// After calculations and requests have been done, pre-emptively stop further requests
|
||||||
if (this.limited) {
|
if (this.limited) {
|
||||||
const timeout = this.reset + this.manager.client.options.restTimeOffset - Date.now();
|
const timeout = this.reset + this.manager.client.options.restTimeOffset - Date.now();
|
||||||
|
|
@ -102,9 +88,13 @@ class RequestHandler {
|
||||||
try {
|
try {
|
||||||
res = await request.make();
|
res = await request.make();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// NodeFetch error expected for all "operational" errors, such as 500 status code
|
// Retry the specified number of times for request abortions
|
||||||
this.busy = false;
|
if (request.retries === this.manager.client.options.retryLimit) {
|
||||||
return reject(new HTTPError(error.message, error.constructor.name, error.status, request.method, request.path));
|
throw new HTTPError(error.message, error.constructor.name, error.status, request.method, request.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
request.retries++;
|
||||||
|
return this.execute(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (res && res.headers) {
|
if (res && res.headers) {
|
||||||
|
|
@ -120,7 +110,7 @@ class RequestHandler {
|
||||||
this.retryAfter = retryAfter ? Number(retryAfter) : -1;
|
this.retryAfter = retryAfter ? Number(retryAfter) : -1;
|
||||||
|
|
||||||
// https://github.com/discordapp/discord-api-docs/issues/182
|
// https://github.com/discordapp/discord-api-docs/issues/182
|
||||||
if (item.request.route.includes('reactions')) {
|
if (request.route.includes('reactions')) {
|
||||||
this.reset = new Date(serverDate).getTime() - getAPIOffset(serverDate) + 250;
|
this.reset = new Date(serverDate).getTime() - getAPIOffset(serverDate) + 250;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -137,43 +127,46 @@ class RequestHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finished handling headers, safe to unlock manager
|
// Handle 2xx and 3xx responses
|
||||||
this.busy = false;
|
|
||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const success = await parseResponse(res);
|
|
||||||
// Nothing wrong with the request, proceed with the next one
|
// Nothing wrong with the request, proceed with the next one
|
||||||
resolve(success);
|
return parseResponse(res);
|
||||||
return this.run();
|
|
||||||
} else if (res.status === 429) {
|
|
||||||
// A ratelimit was hit - this should never happen
|
|
||||||
this.queue.unshift(item);
|
|
||||||
this.manager.client.emit('debug', `429 hit on route ${item.request.route}`);
|
|
||||||
await Util.delayFor(this.retryAfter);
|
|
||||||
return this.run();
|
|
||||||
} else if (res.status >= 500 && res.status < 600) {
|
|
||||||
// Retry the specified number of times for possible serverside issues
|
|
||||||
if (item.retries === this.manager.client.options.retryLimit) {
|
|
||||||
return reject(
|
|
||||||
new HTTPError(res.statusText, res.constructor.name, res.status, item.request.method, request.path),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
item.retries++;
|
|
||||||
this.queue.unshift(item);
|
|
||||||
return this.run();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Handle possible malformed requests
|
|
||||||
try {
|
|
||||||
const data = await parseResponse(res);
|
|
||||||
if (res.status >= 400 && res.status < 500) {
|
|
||||||
return reject(new DiscordAPIError(request.path, data, request.method, res.status));
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
} catch (err) {
|
|
||||||
return reject(new HTTPError(err.message, err.constructor.name, err.status, request.method, request.path));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle 4xx responses
|
||||||
|
if (res.status >= 400 && res.status < 500) {
|
||||||
|
// Handle ratelimited requests
|
||||||
|
if (res.status === 429) {
|
||||||
|
// A ratelimit was hit - this should never happen
|
||||||
|
this.manager.client.emit('debug', `429 hit on route ${request.route}`);
|
||||||
|
await Util.delayFor(this.retryAfter);
|
||||||
|
return this.execute(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle possible malformed requests
|
||||||
|
let data;
|
||||||
|
try {
|
||||||
|
data = await parseResponse(res);
|
||||||
|
} catch (err) {
|
||||||
|
throw new HTTPError(err.message, err.constructor.name, err.status, request.method, request.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new DiscordAPIError(request.path, data, request.method, res.status);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle 5xx responses
|
||||||
|
if (res.status >= 500 && res.status < 600) {
|
||||||
|
// Retry the specified number of times for possible serverside issues
|
||||||
|
if (request.retries === this.manager.client.options.retryLimit) {
|
||||||
|
throw new HTTPError(res.statusText, res.constructor.name, res.status, request.method, request.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
request.retries++;
|
||||||
|
return this.execute(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback in the rare case a status code outside the range 200..=599 is returned
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ class Shard extends EventEmitter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Arguments for the shard's process executable (only when {@link ShardingManager#mode} is `process`)
|
* Arguments for the shard's process executable (only when {@link ShardingManager#mode} is `process`)
|
||||||
* @type {?string[]}
|
* @type {string[]}
|
||||||
*/
|
*/
|
||||||
this.execArgv = manager.execArgv;
|
this.execArgv = manager.execArgv;
|
||||||
|
|
||||||
|
|
@ -115,7 +115,6 @@ class Shard extends EventEmitter {
|
||||||
.fork(path.resolve(this.manager.file), this.args, {
|
.fork(path.resolve(this.manager.file), this.args, {
|
||||||
env: this.env,
|
env: this.env,
|
||||||
execArgv: this.execArgv,
|
execArgv: this.execArgv,
|
||||||
silent: true,
|
|
||||||
})
|
})
|
||||||
.on('message', this._handleMessage.bind(this))
|
.on('message', this._handleMessage.bind(this))
|
||||||
.on('exit', this._exitListener);
|
.on('exit', this._exitListener);
|
||||||
|
|
@ -125,6 +124,9 @@ class Shard extends EventEmitter {
|
||||||
.on('exit', this._exitListener);
|
.on('exit', this._exitListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._evals.clear();
|
||||||
|
this._fetches.clear();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emitted upon the creation of the shard's child process/worker.
|
* Emitted upon the creation of the shard's child process/worker.
|
||||||
* @event Shard#spawn
|
* @event Shard#spawn
|
||||||
|
|
@ -226,6 +228,10 @@ class Shard extends EventEmitter {
|
||||||
* .catch(console.error);
|
* .catch(console.error);
|
||||||
*/
|
*/
|
||||||
fetchClientValue(prop) {
|
fetchClientValue(prop) {
|
||||||
|
// Shard is dead (maybe respawning), don't cache anything and error immediately
|
||||||
|
if (!this.process && !this.worker) return Promise.reject(new Error('SHARDING_NO_CHILD_EXISTS', this.id));
|
||||||
|
|
||||||
|
// Cached promise from previous call
|
||||||
if (this._fetches.has(prop)) return this._fetches.get(prop);
|
if (this._fetches.has(prop)) return this._fetches.get(prop);
|
||||||
|
|
||||||
const promise = new Promise((resolve, reject) => {
|
const promise = new Promise((resolve, reject) => {
|
||||||
|
|
@ -256,6 +262,10 @@ class Shard extends EventEmitter {
|
||||||
* @returns {Promise<*>} Result of the script execution
|
* @returns {Promise<*>} Result of the script execution
|
||||||
*/
|
*/
|
||||||
eval(script) {
|
eval(script) {
|
||||||
|
// Shard is dead (maybe respawning), don't cache anything and error immediately
|
||||||
|
if (!this.process && !this.worker) return Promise.reject(new Error('SHARDING_NO_CHILD_EXISTS', this.id));
|
||||||
|
|
||||||
|
// Cached promise from previous call
|
||||||
if (this._evals.has(script)) return this._evals.get(script);
|
if (this._evals.has(script)) return this._evals.get(script);
|
||||||
|
|
||||||
const promise = new Promise((resolve, reject) => {
|
const promise = new Promise((resolve, reject) => {
|
||||||
|
|
@ -324,18 +334,20 @@ class Shard extends EventEmitter {
|
||||||
|
|
||||||
// Shard is requesting a property fetch
|
// Shard is requesting a property fetch
|
||||||
if (message._sFetchProp) {
|
if (message._sFetchProp) {
|
||||||
this.manager.fetchClientValues(message._sFetchProp).then(
|
const resp = { _sFetchProp: message._sFetchProp, _sFetchPropShard: message._sFetchPropShard };
|
||||||
results => this.send({ _sFetchProp: message._sFetchProp, _result: results }),
|
this.manager.fetchClientValues(message._sFetchProp, message._sFetchPropShard).then(
|
||||||
err => this.send({ _sFetchProp: message._sFetchProp, _error: Util.makePlainError(err) }),
|
results => this.send({ ...resp, _result: results }),
|
||||||
|
err => this.send({ ...resp, _error: Util.makePlainError(err) }),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shard is requesting an eval broadcast
|
// Shard is requesting an eval broadcast
|
||||||
if (message._sEval) {
|
if (message._sEval) {
|
||||||
this.manager.broadcastEval(message._sEval).then(
|
const resp = { _sEval: message._sEval, _sEvalShard: message._sEvalShard };
|
||||||
results => this.send({ _sEval: message._sEval, _result: results }),
|
this.manager.broadcastEval(message._sEval, message._sEvalShard).then(
|
||||||
err => this.send({ _sEval: message._sEval, _error: Util.makePlainError(err) }),
|
results => this.send({ ...resp, _result: results }),
|
||||||
|
err => this.send({ ...resp, _error: Util.makePlainError(err) }),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -96,28 +96,29 @@ class ShardClientUtil {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches a client property value of each shard.
|
* Fetches a client property value of each shard, or a given shard.
|
||||||
* @param {string} prop Name of the client property to get, using periods for nesting
|
* @param {string} prop Name of the client property to get, using periods for nesting
|
||||||
* @returns {Promise<Array<*>>}
|
* @param {number} [shard] Shard to fetch property from, all if undefined
|
||||||
|
* @returns {Promise<*>|Promise<Array<*>>}
|
||||||
* @example
|
* @example
|
||||||
* client.shard.fetchClientValues('guilds.cache.size')
|
* client.shard.fetchClientValues('guilds.cache.size')
|
||||||
* .then(results => console.log(`${results.reduce((prev, val) => prev + val, 0)} total guilds`))
|
* .then(results => console.log(`${results.reduce((prev, val) => prev + val, 0)} total guilds`))
|
||||||
* .catch(console.error);
|
* .catch(console.error);
|
||||||
* @see {@link ShardingManager#fetchClientValues}
|
* @see {@link ShardingManager#fetchClientValues}
|
||||||
*/
|
*/
|
||||||
fetchClientValues(prop) {
|
fetchClientValues(prop, shard) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const parent = this.parentPort || process;
|
const parent = this.parentPort || process;
|
||||||
|
|
||||||
const listener = message => {
|
const listener = message => {
|
||||||
if (!message || message._sFetchProp !== prop) return;
|
if (!message || message._sFetchProp !== prop || message._sFetchPropShard !== shard) return;
|
||||||
parent.removeListener('message', listener);
|
parent.removeListener('message', listener);
|
||||||
if (!message._error) resolve(message._result);
|
if (!message._error) resolve(message._result);
|
||||||
else reject(Util.makeError(message._error));
|
else reject(Util.makeError(message._error));
|
||||||
};
|
};
|
||||||
parent.on('message', listener);
|
parent.on('message', listener);
|
||||||
|
|
||||||
this.send({ _sFetchProp: prop }).catch(err => {
|
this.send({ _sFetchProp: prop, _sFetchPropShard: shard }).catch(err => {
|
||||||
parent.removeListener('message', listener);
|
parent.removeListener('message', listener);
|
||||||
reject(err);
|
reject(err);
|
||||||
});
|
});
|
||||||
|
|
@ -125,29 +126,30 @@ class ShardClientUtil {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Evaluates a script or function on all shards, in the context of the {@link Clients}.
|
* Evaluates a script or function on all shards, or a given shard, in the context of the {@link Client}s.
|
||||||
* @param {string|Function} script JavaScript to run on each shard
|
* @param {string|Function} script JavaScript to run on each shard
|
||||||
* @returns {Promise<Array<*>>} Results of the script execution
|
* @param {number} [shard] Shard to run script on, all if undefined
|
||||||
|
* @returns {Promise<*>|Promise<Array<*>>} Results of the script execution
|
||||||
* @example
|
* @example
|
||||||
* client.shard.broadcastEval('this.guilds.cache.size')
|
* client.shard.broadcastEval('this.guilds.cache.size')
|
||||||
* .then(results => console.log(`${results.reduce((prev, val) => prev + val, 0)} total guilds`))
|
* .then(results => console.log(`${results.reduce((prev, val) => prev + val, 0)} total guilds`))
|
||||||
* .catch(console.error);
|
* .catch(console.error);
|
||||||
* @see {@link ShardingManager#broadcastEval}
|
* @see {@link ShardingManager#broadcastEval}
|
||||||
*/
|
*/
|
||||||
broadcastEval(script) {
|
broadcastEval(script, shard) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const parent = this.parentPort || process;
|
const parent = this.parentPort || process;
|
||||||
script = typeof script === 'function' ? `(${script})(this)` : script;
|
script = typeof script === 'function' ? `(${script})(this)` : script;
|
||||||
|
|
||||||
const listener = message => {
|
const listener = message => {
|
||||||
if (!message || message._sEval !== script) return;
|
if (!message || message._sEval !== script || message._sEvalShard !== shard) return;
|
||||||
parent.removeListener('message', listener);
|
parent.removeListener('message', listener);
|
||||||
if (!message._error) resolve(message._result);
|
if (!message._error) resolve(message._result);
|
||||||
else reject(Util.makeError(message._error));
|
else reject(Util.makeError(message._error));
|
||||||
};
|
};
|
||||||
parent.on('message', listener);
|
parent.on('message', listener);
|
||||||
|
|
||||||
this.send({ _sEval: script }).catch(err => {
|
this.send({ _sEval: script, _sEvalShard: shard }).catch(err => {
|
||||||
parent.removeListener('message', listener);
|
parent.removeListener('message', listener);
|
||||||
reject(err);
|
reject(err);
|
||||||
});
|
});
|
||||||
|
|
@ -224,6 +226,18 @@ class ShardClientUtil {
|
||||||
}
|
}
|
||||||
return this._singleton;
|
return this._singleton;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the shard ID for a given guild ID.
|
||||||
|
* @param {Snowflake} guildID Snowflake guild ID to get shard ID for
|
||||||
|
* @param {number} shardCount Number of shards
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
static shardIDForGuildID(guildID, shardCount) {
|
||||||
|
const shard = Number(BigInt(guildID) >> 22n) % shardCount;
|
||||||
|
if (shard < 0) throw new Error('SHARDING_SHARD_MISCALCULATION', shard, guildID, shardCount);
|
||||||
|
return shard;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = ShardClientUtil;
|
module.exports = ShardClientUtil;
|
||||||
|
|
|
||||||
|
|
@ -222,30 +222,48 @@ class ShardingManager extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Evaluates a script on all shards, in the context of the {@link Client}s.
|
* Evaluates a script on all shards, or a given shard, in the context of the {@link Client}s.
|
||||||
* @param {string} script JavaScript to run on each shard
|
* @param {string} script JavaScript to run on each shard
|
||||||
* @returns {Promise<Array<*>>} Results of the script execution
|
* @param {number} [shard] Shard to run on, all if undefined
|
||||||
|
* @returns {Promise<*>|Promise<Array<*>>} Results of the script execution
|
||||||
*/
|
*/
|
||||||
broadcastEval(script) {
|
broadcastEval(script, shard) {
|
||||||
const promises = [];
|
return this._performOnShards('eval', [script], shard);
|
||||||
for (const shard of this.shards.values()) promises.push(shard.eval(script));
|
|
||||||
return Promise.all(promises);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches a client property value of each shard.
|
* Fetches a client property value of each shard, or a given shard.
|
||||||
* @param {string} prop Name of the client property to get, using periods for nesting
|
* @param {string} prop Name of the client property to get, using periods for nesting
|
||||||
* @returns {Promise<Array<*>>}
|
* @param {number} [shard] Shard to fetch property from, all if undefined
|
||||||
|
* @returns {Promise<*>|Promise<Array<*>>}
|
||||||
* @example
|
* @example
|
||||||
* manager.fetchClientValues('guilds.cache.size')
|
* manager.fetchClientValues('guilds.cache.size')
|
||||||
* .then(results => console.log(`${results.reduce((prev, val) => prev + val, 0)} total guilds`))
|
* .then(results => console.log(`${results.reduce((prev, val) => prev + val, 0)} total guilds`))
|
||||||
* .catch(console.error);
|
* .catch(console.error);
|
||||||
*/
|
*/
|
||||||
fetchClientValues(prop) {
|
fetchClientValues(prop, shard) {
|
||||||
|
return this._performOnShards('fetchClientValue', [prop], shard);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs a method with given arguments on all shards, or a given shard.
|
||||||
|
* @param {string} method Method name to run on each shard
|
||||||
|
* @param {Array<*>} args Arguments to pass through to the method call
|
||||||
|
* @param {number} [shard] Shard to run on, all if undefined
|
||||||
|
* @returns {Promise<*>|Promise<Array<*>>} Results of the method execution
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_performOnShards(method, args, shard) {
|
||||||
if (this.shards.size === 0) return Promise.reject(new Error('SHARDING_NO_SHARDS'));
|
if (this.shards.size === 0) return Promise.reject(new Error('SHARDING_NO_SHARDS'));
|
||||||
if (this.shards.size !== this.shardList.length) return Promise.reject(new Error('SHARDING_IN_PROCESS'));
|
if (this.shards.size !== this.shardList.length) return Promise.reject(new Error('SHARDING_IN_PROCESS'));
|
||||||
|
|
||||||
|
if (typeof shard === 'number') {
|
||||||
|
if (this.shards.has(shard)) return this.shards.get(shard)[method](...args);
|
||||||
|
return Promise.reject(new Error('SHARDING_SHARD_NOT_FOUND', shard));
|
||||||
|
}
|
||||||
|
|
||||||
const promises = [];
|
const promises = [];
|
||||||
for (const shard of this.shards.values()) promises.push(shard.fetchClientValue(prop));
|
for (const sh of this.shards.values()) promises.push(sh[method](...args));
|
||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -74,13 +74,21 @@ class APIMessage {
|
||||||
return this.target instanceof Message;
|
return this.target instanceof Message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the target is an interaction
|
||||||
|
* @type {boolean}
|
||||||
|
* @readonly
|
||||||
|
*/
|
||||||
|
get isInteraction() {
|
||||||
|
const Interaction = require('./Interaction');
|
||||||
|
return this.target instanceof Interaction;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes the content of this message.
|
* Makes the content of this message.
|
||||||
* @returns {?(string|string[])}
|
* @returns {?(string|string[])}
|
||||||
*/
|
*/
|
||||||
makeContent() {
|
makeContent() {
|
||||||
const GuildMember = require('./GuildMember');
|
|
||||||
|
|
||||||
let content;
|
let content;
|
||||||
if (this.options.content === null) {
|
if (this.options.content === null) {
|
||||||
content = '';
|
content = '';
|
||||||
|
|
@ -110,25 +118,14 @@ class APIMessage {
|
||||||
const isCode = typeof this.options.code !== 'undefined' && this.options.code !== false;
|
const isCode = typeof this.options.code !== 'undefined' && this.options.code !== false;
|
||||||
const splitOptions = isSplit ? { ...this.options.split } : undefined;
|
const splitOptions = isSplit ? { ...this.options.split } : undefined;
|
||||||
|
|
||||||
let mentionPart = '';
|
if (content) {
|
||||||
if (this.options.reply && !this.isUser && this.target.type !== 'dm') {
|
|
||||||
const id = this.target.client.users.resolveID(this.options.reply);
|
|
||||||
mentionPart = `<@${this.options.reply instanceof GuildMember && this.options.reply.nickname ? '!' : ''}${id}>, `;
|
|
||||||
if (isSplit) {
|
|
||||||
splitOptions.prepend = `${mentionPart}${splitOptions.prepend || ''}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (content || mentionPart) {
|
|
||||||
if (isCode) {
|
if (isCode) {
|
||||||
const codeName = typeof this.options.code === 'string' ? this.options.code : '';
|
const codeName = typeof this.options.code === 'string' ? this.options.code : '';
|
||||||
content = `${mentionPart}\`\`\`${codeName}\n${Util.cleanCodeBlockContent(content)}\n\`\`\``;
|
content = `\`\`\`${codeName}\n${Util.cleanCodeBlockContent(content)}\n\`\`\``;
|
||||||
if (isSplit) {
|
if (isSplit) {
|
||||||
splitOptions.prepend = `${splitOptions.prepend || ''}\`\`\`${codeName}\n`;
|
splitOptions.prepend = `${splitOptions.prepend || ''}\`\`\`${codeName}\n`;
|
||||||
splitOptions.append = `\n\`\`\`${splitOptions.append || ''}`;
|
splitOptions.append = `\n\`\`\`${splitOptions.append || ''}`;
|
||||||
}
|
}
|
||||||
} else if (mentionPart) {
|
|
||||||
content = `${mentionPart}${content}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSplit) {
|
if (isSplit) {
|
||||||
|
|
@ -151,8 +148,11 @@ class APIMessage {
|
||||||
|
|
||||||
let nonce;
|
let nonce;
|
||||||
if (typeof this.options.nonce !== 'undefined') {
|
if (typeof this.options.nonce !== 'undefined') {
|
||||||
nonce = parseInt(this.options.nonce);
|
nonce = this.options.nonce;
|
||||||
if (isNaN(nonce) || nonce < 0) throw new RangeError('MESSAGE_NONCE_TYPE');
|
// eslint-disable-next-line max-len
|
||||||
|
if (typeof nonce === 'number' ? !Number.isInteger(nonce) : typeof nonce !== 'string') {
|
||||||
|
throw new RangeError('MESSAGE_NONCE_TYPE');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const embedLikes = [];
|
const embedLikes = [];
|
||||||
|
|
@ -176,25 +176,28 @@ class APIMessage {
|
||||||
if (this.isMessage) {
|
if (this.isMessage) {
|
||||||
// eslint-disable-next-line eqeqeq
|
// eslint-disable-next-line eqeqeq
|
||||||
flags = this.options.flags != null ? new MessageFlags(this.options.flags).bitfield : this.target.flags.bitfield;
|
flags = this.options.flags != null ? new MessageFlags(this.options.flags).bitfield : this.target.flags.bitfield;
|
||||||
|
} else if (this.isInteraction) {
|
||||||
|
flags = this.options.ephemeral ? MessageFlags.EPHEMERAL : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
let allowedMentions =
|
let allowedMentions =
|
||||||
typeof this.options.allowedMentions === 'undefined'
|
typeof this.options.allowedMentions === 'undefined'
|
||||||
? this.target.client.options.allowedMentions
|
? this.target.client.options.allowedMentions
|
||||||
: this.options.allowedMentions;
|
: this.options.allowedMentions;
|
||||||
if (this.options.reply) {
|
|
||||||
const id = this.target.client.users.resolveID(this.options.reply);
|
if (allowedMentions) {
|
||||||
if (allowedMentions) {
|
allowedMentions = Util.cloneObject(allowedMentions);
|
||||||
// Clone the object as to alter the ClientOptions object
|
allowedMentions.replied_user = allowedMentions.repliedUser;
|
||||||
allowedMentions = Util.cloneObject(allowedMentions);
|
delete allowedMentions.repliedUser;
|
||||||
const parsed = allowedMentions.parse && allowedMentions.parse.includes('users');
|
}
|
||||||
// Check if the mention won't be parsed, and isn't supplied in `users`
|
|
||||||
if (!parsed && !(allowedMentions.users && allowedMentions.users.includes(id))) {
|
let message_reference;
|
||||||
if (!allowedMentions.users) allowedMentions.users = [];
|
if (typeof this.options.replyTo !== 'undefined') {
|
||||||
allowedMentions.users.push(id);
|
const message_id = this.isMessage
|
||||||
}
|
? this.target.channel.messages.resolveID(this.options.replyTo)
|
||||||
} else {
|
: this.target.messages.resolveID(this.options.replyTo);
|
||||||
allowedMentions = { users: [id] };
|
if (message_id) {
|
||||||
|
message_reference = { message_id };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -206,8 +209,10 @@ class APIMessage {
|
||||||
embeds,
|
embeds,
|
||||||
username,
|
username,
|
||||||
avatar_url: avatarURL,
|
avatar_url: avatarURL,
|
||||||
allowed_mentions: typeof content === 'undefined' ? undefined : allowedMentions,
|
allowed_mentions:
|
||||||
|
typeof content === 'undefined' && typeof message_reference === 'undefined' ? undefined : allowedMentions,
|
||||||
flags,
|
flags,
|
||||||
|
message_reference,
|
||||||
};
|
};
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
102
src/structures/ApplicationCommand.js
Normal file
102
src/structures/ApplicationCommand.js
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const Base = require('./Base');
|
||||||
|
const { ApplicationCommandOptionType } = require('../util/Constants');
|
||||||
|
const Snowflake = require('../util/Snowflake');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an application command, see {@link InteractionClient}.
|
||||||
|
* @extends {Base}
|
||||||
|
*/
|
||||||
|
class ApplicationCommand extends Base {
|
||||||
|
constructor(client, data, guildID) {
|
||||||
|
super(client);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ID of the guild this command is part of, if any.
|
||||||
|
* @type {Snowflake?}
|
||||||
|
* @readonly
|
||||||
|
*/
|
||||||
|
this.guildID = guildID || null;
|
||||||
|
|
||||||
|
this._patch(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
_patch(data) {
|
||||||
|
/**
|
||||||
|
* The ID of this command.
|
||||||
|
* @type {Snowflake}
|
||||||
|
* @readonly
|
||||||
|
*/
|
||||||
|
this.id = data.id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ID of the application which owns this command.
|
||||||
|
* @type {Snowflake}
|
||||||
|
* @readonly
|
||||||
|
*/
|
||||||
|
this.appplicationID = data.application_id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of this command.
|
||||||
|
* @type {string}
|
||||||
|
* @readonly
|
||||||
|
*/
|
||||||
|
this.name = data.name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The description of this command.
|
||||||
|
* @type {string}
|
||||||
|
* @readonly
|
||||||
|
*/
|
||||||
|
this.description = data.description;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The options of this command.
|
||||||
|
* @type {Object[]}
|
||||||
|
* @readonly
|
||||||
|
*/
|
||||||
|
this.options = data.options?.map(function m(o) {
|
||||||
|
return {
|
||||||
|
type: ApplicationCommandOptionType[o.type],
|
||||||
|
name: o.name,
|
||||||
|
description: o.description,
|
||||||
|
default: o.default,
|
||||||
|
required: o.required,
|
||||||
|
choices: o.choices,
|
||||||
|
options: o.options ? o.options.map(m) : undefined,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The timestamp the command was created at.
|
||||||
|
* @type {number}
|
||||||
|
* @readonly
|
||||||
|
*/
|
||||||
|
get createdTimestamp() {
|
||||||
|
return Snowflake.deconstruct(this.id).timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The time the command was created at.
|
||||||
|
* @type {Date}
|
||||||
|
* @readonly
|
||||||
|
*/
|
||||||
|
get createdAt() {
|
||||||
|
return new Date(this.createdTimestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete this command.
|
||||||
|
*/
|
||||||
|
async delete() {
|
||||||
|
let path = this.client.api.applications('@me');
|
||||||
|
if (this.guildID) {
|
||||||
|
path = path.guilds(this.guildID);
|
||||||
|
}
|
||||||
|
await path.commands(this.id).delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = ApplicationCommand;
|
||||||
|
|
@ -4,6 +4,7 @@ const Util = require('../util/Util');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a data model that is identifiable by a Snowflake (i.e. Discord API data models).
|
* Represents a data model that is identifiable by a Snowflake (i.e. Discord API data models).
|
||||||
|
* @abstract
|
||||||
*/
|
*/
|
||||||
class Base {
|
class Base {
|
||||||
constructor(client) {
|
constructor(client) {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ const Emoji = require('./Emoji');
|
||||||
/**
|
/**
|
||||||
* Parent class for {@link GuildEmoji} and {@link GuildPreviewEmoji}.
|
* Parent class for {@link GuildEmoji} and {@link GuildPreviewEmoji}.
|
||||||
* @extends {Emoji}
|
* @extends {Emoji}
|
||||||
|
* @abstract
|
||||||
*/
|
*/
|
||||||
class BaseGuildEmoji extends Emoji {
|
class BaseGuildEmoji extends Emoji {
|
||||||
constructor(client, data, guild) {
|
constructor(client, data, guild) {
|
||||||
|
|
@ -16,6 +17,10 @@ class BaseGuildEmoji extends Emoji {
|
||||||
*/
|
*/
|
||||||
this.guild = guild;
|
this.guild = guild;
|
||||||
|
|
||||||
|
this.requiresColons = null;
|
||||||
|
this.managed = null;
|
||||||
|
this.available = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Array of role ids this emoji is active for
|
* Array of role ids this emoji is active for
|
||||||
* @name BaseGuildEmoji#_roles
|
* @name BaseGuildEmoji#_roles
|
||||||
|
|
@ -30,26 +35,29 @@ class BaseGuildEmoji extends Emoji {
|
||||||
_patch(data) {
|
_patch(data) {
|
||||||
if (data.name) this.name = data.name;
|
if (data.name) this.name = data.name;
|
||||||
|
|
||||||
/**
|
if (typeof data.require_colons !== 'undefined') {
|
||||||
* Whether or not this emoji requires colons surrounding it
|
/**
|
||||||
* @type {boolean}
|
* Whether or not this emoji requires colons surrounding it
|
||||||
* @name GuildEmoji#requiresColons
|
* @type {?boolean}
|
||||||
*/
|
*/
|
||||||
if (typeof data.require_colons !== 'undefined') this.requiresColons = data.require_colons;
|
this.requiresColons = data.require_colons;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
if (typeof data.managed !== 'undefined') {
|
||||||
* Whether this emoji is managed by an external service
|
/**
|
||||||
* @type {boolean}
|
* Whether this emoji is managed by an external service
|
||||||
* @name GuildEmoji#managed
|
* @type {?boolean}
|
||||||
*/
|
*/
|
||||||
if (typeof data.managed !== 'undefined') this.managed = data.managed;
|
this.managed = data.managed;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
if (typeof data.available !== 'undefined') {
|
||||||
* Whether this emoji is available
|
/**
|
||||||
* @type {boolean}
|
* Whether this emoji is available
|
||||||
* @name GuildEmoji#available
|
* @type {?boolean}
|
||||||
*/
|
*/
|
||||||
if (typeof data.available !== 'undefined') this.available = data.available;
|
this.available = data.available;
|
||||||
|
}
|
||||||
|
|
||||||
if (data.roles) this._roles = data.roles;
|
if (data.roles) this._roles = data.roles;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ const Snowflake = require('../util/Snowflake');
|
||||||
/**
|
/**
|
||||||
* Represents any channel on Discord.
|
* Represents any channel on Discord.
|
||||||
* @extends {Base}
|
* @extends {Base}
|
||||||
|
* @abstract
|
||||||
*/
|
*/
|
||||||
class Channel extends Base {
|
class Channel extends Base {
|
||||||
constructor(client, data) {
|
constructor(client, data) {
|
||||||
|
|
@ -97,6 +98,14 @@ class Channel extends Base {
|
||||||
return this.client.channels.fetch(this.id, true, force);
|
return this.client.channels.fetch(this.id, true, force);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether this channel is text-based.
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
isText() {
|
||||||
|
return 'messages' in this;
|
||||||
|
}
|
||||||
|
|
||||||
static create(client, data, guild) {
|
static create(client, data, guild) {
|
||||||
const Structures = require('../util/Structures');
|
const Structures = require('../util/Structures');
|
||||||
let channel;
|
let channel;
|
||||||
|
|
|
||||||
|
|
@ -1,46 +1,15 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const Base = require('./Base');
|
|
||||||
const Team = require('./Team');
|
const Team = require('./Team');
|
||||||
const { ClientApplicationAssetTypes, Endpoints } = require('../util/Constants');
|
const Application = require('./interfaces/Application');
|
||||||
const Snowflake = require('../util/Snowflake');
|
|
||||||
|
|
||||||
const AssetTypes = Object.keys(ClientApplicationAssetTypes);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a Client OAuth2 Application.
|
* Represents a Client OAuth2 Application.
|
||||||
* @extends {Base}
|
* @extends {Application}
|
||||||
*/
|
*/
|
||||||
class ClientApplication extends Base {
|
class ClientApplication extends Application {
|
||||||
constructor(client, data) {
|
|
||||||
super(client);
|
|
||||||
this._patch(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
_patch(data) {
|
_patch(data) {
|
||||||
/**
|
super._patch(data);
|
||||||
* The ID of the app
|
|
||||||
* @type {Snowflake}
|
|
||||||
*/
|
|
||||||
this.id = data.id;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The name of the app
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
this.name = data.name;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The app's description
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
this.description = data.description;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The app's icon hash
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
this.icon = data.icon;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The app's cover image
|
* The app's cover image
|
||||||
|
|
@ -72,85 +41,6 @@ class ClientApplication extends Base {
|
||||||
*/
|
*/
|
||||||
this.owner = data.team ? new Team(this.client, data.team) : data.owner ? this.client.users.add(data.owner) : null;
|
this.owner = data.team ? new Team(this.client, data.team) : data.owner ? this.client.users.add(data.owner) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* The timestamp the app was created at
|
|
||||||
* @type {number}
|
|
||||||
* @readonly
|
|
||||||
*/
|
|
||||||
get createdTimestamp() {
|
|
||||||
return Snowflake.deconstruct(this.id).timestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The time the app was created at
|
|
||||||
* @type {Date}
|
|
||||||
* @readonly
|
|
||||||
*/
|
|
||||||
get createdAt() {
|
|
||||||
return new Date(this.createdTimestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A link to the application's icon.
|
|
||||||
* @param {ImageURLOptions} [options={}] Options for the Image URL
|
|
||||||
* @returns {?string} URL to the icon
|
|
||||||
*/
|
|
||||||
iconURL({ format, size } = {}) {
|
|
||||||
if (!this.icon) return null;
|
|
||||||
return this.client.rest.cdn.AppIcon(this.id, this.icon, { format, size });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A link to this application's cover image.
|
|
||||||
* @param {ImageURLOptions} [options={}] Options for the Image URL
|
|
||||||
* @returns {?string} URL to the cover image
|
|
||||||
*/
|
|
||||||
coverImage({ format, size } = {}) {
|
|
||||||
if (!this.cover) return null;
|
|
||||||
return Endpoints.CDN(this.client.options.http.cdn).AppIcon(this.id, this.cover, { format, size });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Asset data.
|
|
||||||
* @typedef {Object} ClientAsset
|
|
||||||
* @property {Snowflake} id The asset ID
|
|
||||||
* @property {string} name The asset name
|
|
||||||
* @property {string} type The asset type
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the clients rich presence assets.
|
|
||||||
* @returns {Promise<Array<ClientAsset>>}
|
|
||||||
*/
|
|
||||||
fetchAssets() {
|
|
||||||
return this.client.api.oauth2
|
|
||||||
.applications(this.id)
|
|
||||||
.assets.get()
|
|
||||||
.then(assets =>
|
|
||||||
assets.map(a => ({
|
|
||||||
id: a.id,
|
|
||||||
name: a.name,
|
|
||||||
type: AssetTypes[a.type - 1],
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When concatenated with a string, this automatically returns the application's name instead of the
|
|
||||||
* ClientApplication object.
|
|
||||||
* @returns {string}
|
|
||||||
* @example
|
|
||||||
* // Logs: Application name: My App
|
|
||||||
* console.log(`Application name: ${application}`);
|
|
||||||
*/
|
|
||||||
toString() {
|
|
||||||
return this.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSON() {
|
|
||||||
return super.toJSON({ createdTimestamp: true });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = ClientApplication;
|
module.exports = ClientApplication;
|
||||||
|
|
|
||||||
|
|
@ -94,11 +94,9 @@ class ClientUser extends Structures.get('User') {
|
||||||
* @property {PresenceStatusData} [status] Status of the user
|
* @property {PresenceStatusData} [status] Status of the user
|
||||||
* @property {boolean} [afk] Whether the user is AFK
|
* @property {boolean} [afk] Whether the user is AFK
|
||||||
* @property {Object} [activity] Activity the user is playing
|
* @property {Object} [activity] Activity the user is playing
|
||||||
* @property {Object|string} [activity.application] An application object or application id
|
|
||||||
* @property {string} [activity.application.id] The id of the application
|
|
||||||
* @property {string} [activity.name] Name of the activity
|
* @property {string} [activity.name] Name of the activity
|
||||||
* @property {ActivityType|number} [activity.type] Type of the activity
|
* @property {ActivityType|number} [activity.type] Type of the activity
|
||||||
* @property {string} [activity.url] Stream url
|
* @property {string} [activity.url] Twitch / YouTube stream URL
|
||||||
* @property {?number|number[]} [shardID] Shard Id(s) to have the activity set on
|
* @property {?number|number[]} [shardID] Shard Id(s) to have the activity set on
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
@ -141,10 +139,10 @@ class ClientUser extends Structures.get('User') {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Options for setting an activity
|
* Options for setting an activity.
|
||||||
* @typedef ActivityOptions
|
* @typedef ActivityOptions
|
||||||
* @type {Object}
|
* @type {Object}
|
||||||
* @property {string} [url] Twitch stream URL
|
* @property {string} [url] Twitch / YouTube stream URL
|
||||||
* @property {ActivityType|number} [type] Type of the activity
|
* @property {ActivityType|number} [type] Type of the activity
|
||||||
* @property {?number|number[]} [shardID] Shard Id(s) to have the activity set on
|
* @property {?number|number[]} [shardID] Shard Id(s) to have the activity set on
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ class Emoji extends Base {
|
||||||
* @example
|
* @example
|
||||||
* // Send a custom emoji from a guild:
|
* // Send a custom emoji from a guild:
|
||||||
* const emoji = guild.emojis.cache.first();
|
* const emoji = guild.emojis.cache.first();
|
||||||
* msg.reply(`Hello! ${emoji}`);
|
* msg.channel.send(`Hello! ${emoji}`);
|
||||||
* @example
|
* @example
|
||||||
* // Send the emoji used in a reaction to the channel the reaction is part of
|
* // Send the emoji used in a reaction to the channel the reaction is part of
|
||||||
* reaction.message.channel.send(`The emoji used was: ${reaction.emoji}`);
|
* reaction.message.channel.send(`The emoji used was: ${reaction.emoji}`);
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ const { deprecate } = require('util');
|
||||||
const Base = require('./Base');
|
const Base = require('./Base');
|
||||||
const GuildAuditLogs = require('./GuildAuditLogs');
|
const GuildAuditLogs = require('./GuildAuditLogs');
|
||||||
const GuildPreview = require('./GuildPreview');
|
const GuildPreview = require('./GuildPreview');
|
||||||
|
const GuildTemplate = require('./GuildTemplate');
|
||||||
const Integration = require('./Integration');
|
const Integration = require('./Integration');
|
||||||
const Invite = require('./Invite');
|
const Invite = require('./Invite');
|
||||||
const VoiceRegion = require('./VoiceRegion');
|
const VoiceRegion = require('./VoiceRegion');
|
||||||
|
|
@ -17,6 +18,7 @@ const RoleManager = require('../managers/RoleManager');
|
||||||
const VoiceStateManager = require('../managers/VoiceStateManager');
|
const VoiceStateManager = require('../managers/VoiceStateManager');
|
||||||
const Collection = require('../util/Collection');
|
const Collection = require('../util/Collection');
|
||||||
const {
|
const {
|
||||||
|
browser,
|
||||||
ChannelTypes,
|
ChannelTypes,
|
||||||
DefaultMessageNotifications,
|
DefaultMessageNotifications,
|
||||||
PartialTypes,
|
PartialTypes,
|
||||||
|
|
@ -171,6 +173,7 @@ class Guild extends Base {
|
||||||
* * INVITE_SPLASH
|
* * INVITE_SPLASH
|
||||||
* * NEWS
|
* * NEWS
|
||||||
* * PARTNERED
|
* * PARTNERED
|
||||||
|
* * RELAY_ENABLED
|
||||||
* * VANITY_URL
|
* * VANITY_URL
|
||||||
* * VERIFIED
|
* * VERIFIED
|
||||||
* * VIP_REGIONS
|
* * VIP_REGIONS
|
||||||
|
|
@ -230,36 +233,38 @@ class Guild extends Base {
|
||||||
*/
|
*/
|
||||||
this.premiumTier = data.premium_tier;
|
this.premiumTier = data.premium_tier;
|
||||||
|
|
||||||
/**
|
|
||||||
* The total number of boosts for this server
|
|
||||||
* @type {?number}
|
|
||||||
* @name Guild#premiumSubscriptionCount
|
|
||||||
*/
|
|
||||||
if (typeof data.premium_subscription_count !== 'undefined') {
|
if (typeof data.premium_subscription_count !== 'undefined') {
|
||||||
|
/**
|
||||||
|
* The total number of boosts for this server
|
||||||
|
* @type {?number}
|
||||||
|
*/
|
||||||
this.premiumSubscriptionCount = data.premium_subscription_count;
|
this.premiumSubscriptionCount = data.premium_subscription_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
if (typeof data.widget_enabled !== 'undefined') {
|
||||||
* Whether widget images are enabled on this guild
|
/**
|
||||||
* @type {?boolean}
|
* Whether widget images are enabled on this guild
|
||||||
* @name Guild#widgetEnabled
|
* @type {?boolean}
|
||||||
*/
|
*/
|
||||||
if (typeof data.widget_enabled !== 'undefined') this.widgetEnabled = data.widget_enabled;
|
this.widgetEnabled = data.widget_enabled;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
if (typeof data.widget_channel_id !== 'undefined') {
|
||||||
* The widget channel ID, if enabled
|
/**
|
||||||
* @type {?string}
|
* The widget channel ID, if enabled
|
||||||
* @name Guild#widgetChannelID
|
* @type {?string}
|
||||||
*/
|
*/
|
||||||
if (typeof data.widget_channel_id !== 'undefined') this.widgetChannelID = data.widget_channel_id;
|
this.widgetChannelID = data.widget_channel_id;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
if (typeof data.embed_channel_id !== 'undefined') {
|
||||||
* The embed channel ID, if enabled
|
/**
|
||||||
* @type {?string}
|
* The embed channel ID, if enabled
|
||||||
* @name Guild#embedChannelID
|
* @type {?string}
|
||||||
* @deprecated
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
if (typeof data.embed_channel_id !== 'undefined') this.embedChannelID = data.embed_channel_id;
|
this.embedChannelID = data.embed_channel_id;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The verification level of the guild
|
* The verification level of the guild
|
||||||
|
|
@ -298,40 +303,47 @@ class Guild extends Base {
|
||||||
*/
|
*/
|
||||||
this.systemChannelFlags = new SystemChannelFlags(data.system_channel_flags).freeze();
|
this.systemChannelFlags = new SystemChannelFlags(data.system_channel_flags).freeze();
|
||||||
|
|
||||||
/**
|
if (typeof data.max_members !== 'undefined') {
|
||||||
* The maximum amount of members the guild can have
|
/**
|
||||||
* <info>You will need to fetch the guild using {@link Guild#fetch} if you want to receive this parameter</info>
|
* The maximum amount of members the guild can have
|
||||||
* @type {?number}
|
* @type {?number}
|
||||||
* @name Guild#maximumMembers
|
*/
|
||||||
*/
|
this.maximumMembers = data.max_members;
|
||||||
if (typeof data.max_members !== 'undefined') this.maximumMembers = data.max_members || 250000;
|
} else if (typeof this.maximumMembers === 'undefined') {
|
||||||
|
this.maximumMembers = null;
|
||||||
/**
|
}
|
||||||
* The maximum amount of presences the guild can have
|
|
||||||
* <info>You will need to fetch the guild using {@link Guild#fetch} if you want to receive this parameter</info>
|
if (typeof data.max_presences !== 'undefined') {
|
||||||
* @type {?number}
|
/**
|
||||||
* @name Guild#maximumPresences
|
* The maximum amount of presences the guild can have
|
||||||
*/
|
* <info>You will need to fetch the guild using {@link Guild#fetch} if you want to receive this parameter</info>
|
||||||
if (typeof data.max_presences !== 'undefined') this.maximumPresences = data.max_presences || 25000;
|
* @type {?number}
|
||||||
|
*/
|
||||||
/**
|
this.maximumPresences = data.max_presences || 25000;
|
||||||
* The approximate amount of members the guild has
|
} else if (typeof this.maximumPresences === 'undefined') {
|
||||||
* <info>You will need to fetch the guild using {@link Guild#fetch} if you want to receive this parameter</info>
|
this.maximumPresences = null;
|
||||||
* @type {?number}
|
}
|
||||||
* @name Guild#approximateMemberCount
|
|
||||||
*/
|
if (typeof data.approximate_member_count !== 'undefined') {
|
||||||
if (typeof data.approximate_member_count !== 'undefined') {
|
/**
|
||||||
this.approximateMemberCount = data.approximate_member_count;
|
* The approximate amount of members the guild has
|
||||||
|
* <info>You will need to fetch the guild using {@link Guild#fetch} if you want to receive this parameter</info>
|
||||||
|
* @type {?number}
|
||||||
|
*/
|
||||||
|
this.approximateMemberCount = data.approximate_member_count;
|
||||||
|
} else if (typeof this.approximateMemberCount === 'undefined') {
|
||||||
|
this.approximateMemberCount = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* The approximate amount of presences the guild has
|
|
||||||
* <info>You will need to fetch the guild using {@link Guild#fetch} if you want to receive this parameter</info>
|
|
||||||
* @type {?number}
|
|
||||||
* @name Guild#approximatePresenceCount
|
|
||||||
*/
|
|
||||||
if (typeof data.approximate_presence_count !== 'undefined') {
|
if (typeof data.approximate_presence_count !== 'undefined') {
|
||||||
|
/**
|
||||||
|
* The approximate amount of presences the guild has
|
||||||
|
* <info>You will need to fetch the guild using {@link Guild#fetch} if you want to receive this parameter</info>
|
||||||
|
* @type {?number}
|
||||||
|
*/
|
||||||
this.approximatePresenceCount = data.approximate_presence_count;
|
this.approximatePresenceCount = data.approximate_presence_count;
|
||||||
|
} else if (typeof this.approximatePresenceCount === 'undefined') {
|
||||||
|
this.approximatePresenceCount = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -625,18 +637,6 @@ class Guild extends Base {
|
||||||
return this.voiceStates.cache.get(this.client.user.id);
|
return this.voiceStates.cache.get(this.client.user.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the GuildMember form of a User object, if the user is present in the guild.
|
|
||||||
* @param {UserResolvable} user The user that you want to obtain the GuildMember of
|
|
||||||
* @returns {?GuildMember}
|
|
||||||
* @example
|
|
||||||
* // Get the guild member of a user
|
|
||||||
* const member = guild.member(message.author);
|
|
||||||
*/
|
|
||||||
member(user) {
|
|
||||||
return this.members.resolve(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches this guild.
|
* Fetches this guild.
|
||||||
* @returns {Promise<Guild>}
|
* @returns {Promise<Guild>}
|
||||||
|
|
@ -698,6 +698,8 @@ class Guild extends Base {
|
||||||
/**
|
/**
|
||||||
* Fetches a collection of integrations to this guild.
|
* Fetches a collection of integrations to this guild.
|
||||||
* Resolves with a collection mapping integrations by their ids.
|
* Resolves with a collection mapping integrations by their ids.
|
||||||
|
* @param {Object} [options] Options for fetching integrations
|
||||||
|
* @param {boolean} [options.includeApplications] Whether to include bot and Oauth2 webhook integrations
|
||||||
* @returns {Promise<Collection<string, Integration>>}
|
* @returns {Promise<Collection<string, Integration>>}
|
||||||
* @example
|
* @example
|
||||||
* // Fetch integrations
|
* // Fetch integrations
|
||||||
|
|
@ -705,10 +707,14 @@ class Guild extends Base {
|
||||||
* .then(integrations => console.log(`Fetched ${integrations.size} integrations`))
|
* .then(integrations => console.log(`Fetched ${integrations.size} integrations`))
|
||||||
* .catch(console.error);
|
* .catch(console.error);
|
||||||
*/
|
*/
|
||||||
fetchIntegrations() {
|
fetchIntegrations({ includeApplications = false } = {}) {
|
||||||
return this.client.api
|
return this.client.api
|
||||||
.guilds(this.id)
|
.guilds(this.id)
|
||||||
.integrations.get()
|
.integrations.get({
|
||||||
|
query: {
|
||||||
|
include_applications: includeApplications,
|
||||||
|
},
|
||||||
|
})
|
||||||
.then(data =>
|
.then(data =>
|
||||||
data.reduce(
|
data.reduce(
|
||||||
(collection, integration) => collection.set(integration.id, new Integration(this.client, integration, this)),
|
(collection, integration) => collection.set(integration.id, new Integration(this.client, integration, this)),
|
||||||
|
|
@ -717,6 +723,20 @@ class Guild extends Base {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches a collection of templates from this guild.
|
||||||
|
* Resolves with a collection mapping templates by their codes.
|
||||||
|
* @returns {Promise<Collection<string, GuildTemplate>>}
|
||||||
|
*/
|
||||||
|
fetchTemplates() {
|
||||||
|
return this.client.api
|
||||||
|
.guilds(this.id)
|
||||||
|
.templates.get()
|
||||||
|
.then(templates =>
|
||||||
|
templates.reduce((col, data) => col.set(data.code, new GuildTemplate(this.client, data)), new Collection()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The data for creating an integration.
|
* The data for creating an integration.
|
||||||
* @typedef {Object} IntegrationData
|
* @typedef {Object} IntegrationData
|
||||||
|
|
@ -737,6 +757,19 @@ class Guild extends Base {
|
||||||
.then(() => this);
|
.then(() => this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a template for the guild.
|
||||||
|
* @param {string} name The name for the template
|
||||||
|
* @param {string} [description] The description for the template
|
||||||
|
* @returns {Promise<GuildTemplate>}
|
||||||
|
*/
|
||||||
|
createTemplate(name, description) {
|
||||||
|
return this.client.api
|
||||||
|
.guilds(this.id)
|
||||||
|
.templates.post({ data: { name, description } })
|
||||||
|
.then(data => new GuildTemplate(this.client, data));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches a collection of invites to this guild.
|
* Fetches a collection of invites to this guild.
|
||||||
* Resolves with a collection mapping invites by their codes.
|
* Resolves with a collection mapping invites by their codes.
|
||||||
|
|
@ -883,13 +916,7 @@ class Guild extends Base {
|
||||||
* .catch(console.error);
|
* .catch(console.error);
|
||||||
*/
|
*/
|
||||||
fetchEmbed() {
|
fetchEmbed() {
|
||||||
return this.client.api
|
return this.fetchWidget();
|
||||||
.guilds(this.id)
|
|
||||||
.embed.get()
|
|
||||||
.then(data => ({
|
|
||||||
enabled: data.enabled,
|
|
||||||
channel: data.channel_id ? this.channels.cache.get(data.channel_id) : null,
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -901,14 +928,14 @@ class Guild extends Base {
|
||||||
* .then(widget => console.log(`The widget is ${widget.enabled ? 'enabled' : 'disabled'}`))
|
* .then(widget => console.log(`The widget is ${widget.enabled ? 'enabled' : 'disabled'}`))
|
||||||
* .catch(console.error);
|
* .catch(console.error);
|
||||||
*/
|
*/
|
||||||
fetchWidget() {
|
async fetchWidget() {
|
||||||
return this.client.api
|
const data = await this.client.api.guilds(this.id).widget.get();
|
||||||
.guilds(this.id)
|
this.widgetEnabled = this.embedEnabled = data.enabled;
|
||||||
.widget.get()
|
this.widgetChannelID = this.embedChannelID = data.channel_id;
|
||||||
.then(data => ({
|
return {
|
||||||
enabled: data.enabled,
|
enabled: data.enabled,
|
||||||
channel: data.channel_id ? this.channels.cache.get(data.channel_id) : null,
|
channel: data.channel_id ? this.channels.cache.get(data.channel_id) : null,
|
||||||
}));
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -955,29 +982,25 @@ class Guild extends Base {
|
||||||
* @param {boolean} [options.deaf] Whether the member should be deafened (requires `DEAFEN_MEMBERS`)
|
* @param {boolean} [options.deaf] Whether the member should be deafened (requires `DEAFEN_MEMBERS`)
|
||||||
* @returns {Promise<GuildMember>}
|
* @returns {Promise<GuildMember>}
|
||||||
*/
|
*/
|
||||||
addMember(user, options) {
|
async addMember(user, options) {
|
||||||
user = this.client.users.resolveID(user);
|
user = this.client.users.resolveID(user);
|
||||||
if (!user) return Promise.reject(new TypeError('INVALID_TYPE', 'user', 'UserResolvable'));
|
if (!user) throw new TypeError('INVALID_TYPE', 'user', 'UserResolvable');
|
||||||
if (this.members.cache.has(user)) return Promise.resolve(this.members.cache.get(user));
|
if (this.members.cache.has(user)) return this.members.cache.get(user);
|
||||||
options.access_token = options.accessToken;
|
options.access_token = options.accessToken;
|
||||||
if (options.roles) {
|
if (options.roles) {
|
||||||
const roles = [];
|
const roles = [];
|
||||||
for (let role of options.roles instanceof Collection ? options.roles.values() : options.roles) {
|
for (let role of options.roles instanceof Collection ? options.roles.values() : options.roles) {
|
||||||
role = this.roles.resolve(role);
|
role = this.roles.resolve(role);
|
||||||
if (!role) {
|
if (!role) {
|
||||||
return Promise.reject(
|
throw new TypeError('INVALID_TYPE', 'options.roles', 'Array or Collection of Roles or Snowflakes', true);
|
||||||
new TypeError('INVALID_TYPE', 'options.roles', 'Array or Collection of Roles or Snowflakes', true),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
roles.push(role.id);
|
roles.push(role.id);
|
||||||
}
|
}
|
||||||
options.roles = roles;
|
options.roles = roles;
|
||||||
}
|
}
|
||||||
return this.client.api
|
const data = await this.client.api.guilds(this.id).members(user).put({ data: options });
|
||||||
.guilds(this.id)
|
// Data is an empty buffer if the member is already part of the guild.
|
||||||
.members(user)
|
return data instanceof (browser ? ArrayBuffer : Buffer) ? this.members.fetch(user) : this.members.add(data);
|
||||||
.put({ data: options })
|
|
||||||
.then(data => this.members.add(data));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1106,7 +1129,7 @@ class Guild extends Base {
|
||||||
* @example
|
* @example
|
||||||
* // Edit the guild name
|
* // Edit the guild name
|
||||||
* guild.setName('Discord Guild')
|
* guild.setName('Discord Guild')
|
||||||
* .then(updated => console.log(`Updated guild name to ${guild}`))
|
* .then(updated => console.log(`Updated guild name to ${updated.name}`))
|
||||||
* .catch(console.error);
|
* .catch(console.error);
|
||||||
*/
|
*/
|
||||||
setName(name, reason) {
|
setName(name, reason) {
|
||||||
|
|
@ -1387,16 +1410,7 @@ class Guild extends Base {
|
||||||
* @deprecated
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
setEmbed(embed, reason) {
|
setEmbed(embed, reason) {
|
||||||
return this.client.api
|
return this.setWidget(embed, reason);
|
||||||
.guilds(this.id)
|
|
||||||
.embed.patch({
|
|
||||||
data: {
|
|
||||||
enabled: embed.enabled,
|
|
||||||
channel_id: this.channels.resolveID(embed.channel),
|
|
||||||
},
|
|
||||||
reason,
|
|
||||||
})
|
|
||||||
.then(() => this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1418,6 +1432,23 @@ class Guild extends Base {
|
||||||
.then(() => this);
|
.then(() => this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the commands associated with this guild.
|
||||||
|
* @returns {ApplicationCommand[]} A list of commands.
|
||||||
|
*/
|
||||||
|
getCommands() {
|
||||||
|
return this.client.interactionClient.getCommands(this.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a command. See {@link InteractionClient}.
|
||||||
|
* @param {Object} command The command description.
|
||||||
|
* @returns {ApplicationCommand} The created command.
|
||||||
|
*/
|
||||||
|
createCommand(command) {
|
||||||
|
return this.client.interactionClient.createCommand(command, this.id);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Leaves the guild.
|
* Leaves the guild.
|
||||||
* @returns {Promise<Guild>}
|
* @returns {Promise<Guild>}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ const Util = require('../util/Util');
|
||||||
/**
|
/**
|
||||||
* Key mirror of all available audit log targets.
|
* Key mirror of all available audit log targets.
|
||||||
* @name GuildAuditLogs.Targets
|
* @name GuildAuditLogs.Targets
|
||||||
* @type {AuditLogTargetType}
|
* @type {Object<string, string>}
|
||||||
*/
|
*/
|
||||||
const Targets = {
|
const Targets = {
|
||||||
ALL: 'ALL',
|
ALL: 'ALL',
|
||||||
|
|
@ -84,7 +84,7 @@ const Targets = {
|
||||||
/**
|
/**
|
||||||
* All available actions keyed under their names to their numeric values.
|
* All available actions keyed under their names to their numeric values.
|
||||||
* @name GuildAuditLogs.Actions
|
* @name GuildAuditLogs.Actions
|
||||||
* @type {AuditLogAction}
|
* @type {Object<string, number>}
|
||||||
*/
|
*/
|
||||||
const Actions = {
|
const Actions = {
|
||||||
ALL: null,
|
ALL: null,
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ const Util = require('../util/Util');
|
||||||
* - {@link NewsChannel}
|
* - {@link NewsChannel}
|
||||||
* - {@link StoreChannel}
|
* - {@link StoreChannel}
|
||||||
* @extends {Channel}
|
* @extends {Channel}
|
||||||
|
* @abstract
|
||||||
*/
|
*/
|
||||||
class GuildChannel extends Channel {
|
class GuildChannel extends Channel {
|
||||||
/**
|
/**
|
||||||
|
|
@ -52,7 +53,7 @@ class GuildChannel extends Channel {
|
||||||
* The ID of the category parent of this channel
|
* The ID of the category parent of this channel
|
||||||
* @type {?Snowflake}
|
* @type {?Snowflake}
|
||||||
*/
|
*/
|
||||||
this.parentID = data.parent_id;
|
this.parentID = data.parent_id || null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A map of permission overwrites in this channel for roles and users
|
* A map of permission overwrites in this channel for roles and users
|
||||||
|
|
@ -298,7 +299,7 @@ class GuildChannel extends Channel {
|
||||||
* @property {boolean} [nsfw] Whether the channel is NSFW
|
* @property {boolean} [nsfw] Whether the channel is NSFW
|
||||||
* @property {number} [bitrate] The bitrate of the voice channel
|
* @property {number} [bitrate] The bitrate of the voice channel
|
||||||
* @property {number} [userLimit] The user limit of the voice channel
|
* @property {number} [userLimit] The user limit of the voice channel
|
||||||
* @property {Snowflake} [parentID] The parent ID of the channel
|
* @property {?Snowflake} [parentID] The parent ID of the channel
|
||||||
* @property {boolean} [lockPermissions]
|
* @property {boolean} [lockPermissions]
|
||||||
* Lock the permissions of the channel to what the parent's permissions are
|
* Lock the permissions of the channel to what the parent's permissions are
|
||||||
* @property {OverwriteResolvable[]|Collection<Snowflake, OverwriteResolvable>} [permissionOverwrites]
|
* @property {OverwriteResolvable[]|Collection<Snowflake, OverwriteResolvable>} [permissionOverwrites]
|
||||||
|
|
@ -412,7 +413,7 @@ class GuildChannel extends Channel {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets a new topic for the guild channel.
|
* Sets a new topic for the guild channel.
|
||||||
* @param {string} topic The new topic for the guild channel
|
* @param {?string} topic The new topic for the guild channel
|
||||||
* @param {string} [reason] Reason for changing the guild channel's topic
|
* @param {string} [reason] Reason for changing the guild channel's topic
|
||||||
* @returns {Promise<GuildChannel>}
|
* @returns {Promise<GuildChannel>}
|
||||||
* @example
|
* @example
|
||||||
|
|
@ -513,7 +514,7 @@ class GuildChannel extends Channel {
|
||||||
* @param {boolean} [options.nsfw=this.nsfw] Whether the new channel is nsfw (only text)
|
* @param {boolean} [options.nsfw=this.nsfw] Whether the new channel is nsfw (only text)
|
||||||
* @param {number} [options.bitrate=this.bitrate] Bitrate of the new channel in bits (only voice)
|
* @param {number} [options.bitrate=this.bitrate] Bitrate of the new channel in bits (only voice)
|
||||||
* @param {number} [options.userLimit=this.userLimit] Maximum amount of users allowed in the new channel (only voice)
|
* @param {number} [options.userLimit=this.userLimit] Maximum amount of users allowed in the new channel (only voice)
|
||||||
* @param {number} [options.rateLimitPerUser=ThisType.rateLimitPerUser] Ratelimit per user for the new channel (only text)
|
* @param {number} [options.rateLimitPerUser=this.rateLimitPerUser] Ratelimit per user for the new channel (only text)
|
||||||
* @param {ChannelResolvable} [options.parent=this.parent] Parent of the new channel
|
* @param {ChannelResolvable} [options.parent=this.parent] Parent of the new channel
|
||||||
* @param {string} [options.reason] Reason for cloning this channel
|
* @param {string} [options.reason] Reason for cloning this channel
|
||||||
* @returns {Promise<GuildChannel>}
|
* @returns {Promise<GuildChannel>}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const Base = require('./Base');
|
const Base = require('./Base');
|
||||||
const { Presence } = require('./Presence');
|
|
||||||
const Role = require('./Role');
|
const Role = require('./Role');
|
||||||
const VoiceState = require('./VoiceState');
|
|
||||||
const TextBasedChannel = require('./interfaces/TextBasedChannel');
|
const TextBasedChannel = require('./interfaces/TextBasedChannel');
|
||||||
const { Error } = require('../errors');
|
const { Error } = require('../errors');
|
||||||
const GuildMemberRoleManager = require('../managers/GuildMemberRoleManager');
|
const GuildMemberRoleManager = require('../managers/GuildMemberRoleManager');
|
||||||
const Permissions = require('../util/Permissions');
|
const Permissions = require('../util/Permissions');
|
||||||
|
let Structures;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a member of a guild on Discord.
|
* Represents a member of a guild on Discord.
|
||||||
|
|
@ -62,7 +61,6 @@ class GuildMember extends Base {
|
||||||
/**
|
/**
|
||||||
* The nickname of this member, if they have one
|
* The nickname of this member, if they have one
|
||||||
* @type {?string}
|
* @type {?string}
|
||||||
* @name GuildMember#nickname
|
|
||||||
*/
|
*/
|
||||||
this.nickname = null;
|
this.nickname = null;
|
||||||
|
|
||||||
|
|
@ -75,7 +73,6 @@ class GuildMember extends Base {
|
||||||
/**
|
/**
|
||||||
* The user that this guild member instance represents
|
* The user that this guild member instance represents
|
||||||
* @type {User}
|
* @type {User}
|
||||||
* @name GuildMember#user
|
|
||||||
*/
|
*/
|
||||||
this.user = this.client.users.add(data.user, true);
|
this.user = this.client.users.add(data.user, true);
|
||||||
}
|
}
|
||||||
|
|
@ -126,6 +123,8 @@ class GuildMember extends Base {
|
||||||
* @readonly
|
* @readonly
|
||||||
*/
|
*/
|
||||||
get voice() {
|
get voice() {
|
||||||
|
if (!Structures) Structures = require('../util/Structures');
|
||||||
|
const VoiceState = Structures.get('VoiceState');
|
||||||
return this.guild.voiceStates.cache.get(this.id) || new VoiceState(this.guild, { user_id: this.id });
|
return this.guild.voiceStates.cache.get(this.id) || new VoiceState(this.guild, { user_id: this.id });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -153,6 +152,8 @@ class GuildMember extends Base {
|
||||||
* @readonly
|
* @readonly
|
||||||
*/
|
*/
|
||||||
get presence() {
|
get presence() {
|
||||||
|
if (!Structures) Structures = require('../util/Structures');
|
||||||
|
const Presence = Structures.get('Presence');
|
||||||
return (
|
return (
|
||||||
this.guild.presences.cache.get(this.id) ||
|
this.guild.presences.cache.get(this.id) ||
|
||||||
new Presence(this.client, {
|
new Presence(this.client, {
|
||||||
|
|
@ -266,7 +267,8 @@ class GuildMember extends Base {
|
||||||
*/
|
*/
|
||||||
hasPermission(permission, { checkAdmin = true, checkOwner = true } = {}) {
|
hasPermission(permission, { checkAdmin = true, checkOwner = true } = {}) {
|
||||||
if (checkOwner && this.user.id === this.guild.ownerID) return true;
|
if (checkOwner && this.user.id === this.guild.ownerID) return true;
|
||||||
return this.roles.cache.some(r => r.permissions.has(permission, checkAdmin));
|
const permissions = new Permissions(this.roles.cache.map(role => role.permissions));
|
||||||
|
return permissions.has(permission, checkAdmin);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ class GuildPreview extends Base {
|
||||||
* The description for this guild
|
* The description for this guild
|
||||||
* @type {?string}
|
* @type {?string}
|
||||||
*/
|
*/
|
||||||
this.description = data.description;
|
this.description = data.description || null;
|
||||||
|
|
||||||
if (!this.emojis) {
|
if (!this.emojis) {
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
225
src/structures/GuildTemplate.js
Normal file
225
src/structures/GuildTemplate.js
Normal file
|
|
@ -0,0 +1,225 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const Base = require('./Base');
|
||||||
|
const { Events } = require('../util/Constants');
|
||||||
|
const DataResolver = require('../util/DataResolver');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the template for a guild.
|
||||||
|
* @extends {Base}
|
||||||
|
*/
|
||||||
|
class GuildTemplate extends Base {
|
||||||
|
/**
|
||||||
|
* @param {Client} client The instantiating client
|
||||||
|
* @param {Object} data The raw data for the template
|
||||||
|
*/
|
||||||
|
constructor(client, data) {
|
||||||
|
super(client);
|
||||||
|
this._patch(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds or updates the template with the provided data.
|
||||||
|
* @param {Object} data The raw data for the template
|
||||||
|
* @returns {GuildTemplate}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_patch(data) {
|
||||||
|
/**
|
||||||
|
* The unique code of this template
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
this.code = data.code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of this template
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
this.name = data.name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The description of this template
|
||||||
|
* @type {?string}
|
||||||
|
*/
|
||||||
|
this.description = data.description;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The amount of times this template has been used
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
this.usageCount = data.usage_count;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ID of the user that created this template
|
||||||
|
* @type {Snowflake}
|
||||||
|
*/
|
||||||
|
this.creatorID = data.creator_id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The user that created this template
|
||||||
|
* @type {User}
|
||||||
|
*/
|
||||||
|
this.creator = this.client.users.add(data.creator);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The time of when this template was created at
|
||||||
|
* @type {Date}
|
||||||
|
*/
|
||||||
|
this.createdAt = new Date(data.created_at);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The time of when this template was last synced to the guild
|
||||||
|
* @type {Date}
|
||||||
|
*/
|
||||||
|
this.updatedAt = new Date(data.updated_at);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ID of the guild that this template belongs to
|
||||||
|
* @type {Snowflake}
|
||||||
|
*/
|
||||||
|
this.guildID = data.source_guild_id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The data of the guild that this template would create
|
||||||
|
* @type {Object}
|
||||||
|
* @see {@link https://discord.com/developers/docs/resources/guild#guild-resource}
|
||||||
|
*/
|
||||||
|
this.serializedGuild = data.serialized_source_guild;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this template has unsynced changes
|
||||||
|
* @type {?boolean}
|
||||||
|
*/
|
||||||
|
this.unSynced = 'is_dirty' in data ? Boolean(data.is_dirty) : null;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a guild based from this template.
|
||||||
|
* <warn>This is only available to bots in fewer than 10 guilds.</warn>
|
||||||
|
* @param {string} name The name of the guild
|
||||||
|
* @param {BufferResolvable|Base64Resolvable} [icon] The icon for the guild
|
||||||
|
* @returns {Promise<Guild>}
|
||||||
|
*/
|
||||||
|
async createGuild(name, icon) {
|
||||||
|
const { client } = this;
|
||||||
|
const data = await client.api.guilds.templates(this.code).post({
|
||||||
|
data: {
|
||||||
|
name,
|
||||||
|
icon: await DataResolver.resolveImage(icon),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// eslint-disable-next-line consistent-return
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const createdGuild = client.guilds.cache.get(data.id);
|
||||||
|
if (createdGuild) return resolve(createdGuild);
|
||||||
|
|
||||||
|
const resolveGuild = guild => {
|
||||||
|
client.off(Events.GUILD_CREATE, handleGuild);
|
||||||
|
client.decrementMaxListeners();
|
||||||
|
resolve(guild);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleGuild = guild => {
|
||||||
|
if (guild.id === data.id) {
|
||||||
|
client.clearTimeout(timeout);
|
||||||
|
resolveGuild(guild);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
client.incrementMaxListeners();
|
||||||
|
client.on(Events.GUILD_CREATE, handleGuild);
|
||||||
|
|
||||||
|
const timeout = client.setTimeout(() => resolveGuild(client.guilds.add(data)), 10000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the metadata on this template.
|
||||||
|
* @param {Object} options Options for the template
|
||||||
|
* @param {string} [options.name] The name of this template
|
||||||
|
* @param {string} [options.description] The description of this template
|
||||||
|
* @returns {Promise<GuildTemplate>}
|
||||||
|
*/
|
||||||
|
edit({ name, description } = {}) {
|
||||||
|
return this.client.api
|
||||||
|
.guilds(this.guildID)
|
||||||
|
.templates(this.code)
|
||||||
|
.patch({ data: { name, description } })
|
||||||
|
.then(data => this._patch(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes this template.
|
||||||
|
* @returns {Promise<GuildTemplate>}
|
||||||
|
*/
|
||||||
|
delete() {
|
||||||
|
return this.client.api
|
||||||
|
.guilds(this.guildID)
|
||||||
|
.templates(this.code)
|
||||||
|
.delete()
|
||||||
|
.then(() => this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Syncs this template to the current state of the guild.
|
||||||
|
* @returns {Promise<GuildTemplate>}
|
||||||
|
*/
|
||||||
|
sync() {
|
||||||
|
return this.client.api
|
||||||
|
.guilds(this.guildID)
|
||||||
|
.templates(this.code)
|
||||||
|
.put()
|
||||||
|
.then(data => this._patch(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The timestamp of when this template was created at
|
||||||
|
* @type {number}
|
||||||
|
* @readonly
|
||||||
|
*/
|
||||||
|
get createdTimestamp() {
|
||||||
|
return this.createdAt.getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The timestamp of when this template was last synced to the guild
|
||||||
|
* @type {number}
|
||||||
|
* @readonly
|
||||||
|
*/
|
||||||
|
get updatedTimestamp() {
|
||||||
|
return this.updatedAt.getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The guild that this template belongs to
|
||||||
|
* @type {?Guild}
|
||||||
|
* @readonly
|
||||||
|
*/
|
||||||
|
get guild() {
|
||||||
|
return this.client.guilds.cache.get(this.guildID) || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The URL of this template
|
||||||
|
* @type {string}
|
||||||
|
* @readonly
|
||||||
|
*/
|
||||||
|
get url() {
|
||||||
|
return `${this.client.options.http.template}/${this.code}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When concatenated with a string, this automatically returns the templates's code instead of the template object.
|
||||||
|
* @returns {string}
|
||||||
|
* @example
|
||||||
|
* // Logs: Template: FKvmczH2HyUf
|
||||||
|
* console.log(`Template: ${guildTemplate}!`);
|
||||||
|
*/
|
||||||
|
toString() {
|
||||||
|
return this.code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = GuildTemplate;
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const Base = require('./Base');
|
const Base = require('./Base');
|
||||||
|
const IntegrationApplication = require('./IntegrationApplication');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The information account for an integration
|
* The information account for an integration
|
||||||
|
|
@ -64,6 +65,8 @@ class Integration extends Base {
|
||||||
* @type {?User}
|
* @type {?User}
|
||||||
*/
|
*/
|
||||||
this.user = this.client.users.add(data.user);
|
this.user = this.client.users.add(data.user);
|
||||||
|
} else {
|
||||||
|
this.user = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -92,6 +95,20 @@ class Integration extends Base {
|
||||||
* @type {number}
|
* @type {number}
|
||||||
*/
|
*/
|
||||||
this.expireGracePeriod = data.expire_grace_period;
|
this.expireGracePeriod = data.expire_grace_period;
|
||||||
|
|
||||||
|
if ('application' in data) {
|
||||||
|
if (this.application) {
|
||||||
|
this.application._patch(data.application);
|
||||||
|
} else {
|
||||||
|
/**
|
||||||
|
* The application for this integration
|
||||||
|
* @type {?IntegrationApplication}
|
||||||
|
*/
|
||||||
|
this.application = new IntegrationApplication(this.client, data.application);
|
||||||
|
}
|
||||||
|
} else if (!this.application) {
|
||||||
|
this.application = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
25
src/structures/IntegrationApplication.js
Normal file
25
src/structures/IntegrationApplication.js
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const Application = require('./interfaces/Application');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an Integration's OAuth2 Application.
|
||||||
|
* @extends {Application}
|
||||||
|
*/
|
||||||
|
class IntegrationApplication extends Application {
|
||||||
|
_patch(data) {
|
||||||
|
super._patch(data);
|
||||||
|
|
||||||
|
if (typeof data.bot !== 'undefined') {
|
||||||
|
/**
|
||||||
|
* The bot {@link User user} for this application
|
||||||
|
* @type {?User}
|
||||||
|
*/
|
||||||
|
this.bot = this.client.users.add(data.bot);
|
||||||
|
} else if (!this.bot) {
|
||||||
|
this.bot = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = IntegrationApplication;
|
||||||
133
src/structures/Interaction.js
Normal file
133
src/structures/Interaction.js
Normal file
|
|
@ -0,0 +1,133 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const APIMessage = require('./APIMessage');
|
||||||
|
const Base = require('./Base');
|
||||||
|
const Snowflake = require('../util/Snowflake');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an interaction, see {@link InteractionClient}.
|
||||||
|
* @extends {Base}
|
||||||
|
*/
|
||||||
|
class Interaction extends Base {
|
||||||
|
constructor(client, data, syncHandle) {
|
||||||
|
super(client);
|
||||||
|
this.syncHandle = syncHandle;
|
||||||
|
this._patch(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
_patch(data) {
|
||||||
|
/**
|
||||||
|
* The ID of this interaction.
|
||||||
|
* @type {Snowflake}
|
||||||
|
* @readonly
|
||||||
|
*/
|
||||||
|
this.id = data.id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The token of this interaction.
|
||||||
|
* @type {string}
|
||||||
|
* @readonly
|
||||||
|
*/
|
||||||
|
this.token = data.token;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ID of the invoked command.
|
||||||
|
* @type {Snowflake}
|
||||||
|
* @readonly
|
||||||
|
*/
|
||||||
|
this.commandID = data.data.id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the invoked command.
|
||||||
|
* @type {string}
|
||||||
|
* @readonly
|
||||||
|
*/
|
||||||
|
this.commandName = data.data.name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The options passed to the command.
|
||||||
|
* @type {Object}
|
||||||
|
* @readonly
|
||||||
|
*/
|
||||||
|
this.options = data.data.options;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The channel this interaction was sent in.
|
||||||
|
* @type {?Channel}
|
||||||
|
* @readonly
|
||||||
|
*/
|
||||||
|
this.channel = this.client.channels?.cache.get(data.channel_id) || null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The guild this interaction was sent in, if any.
|
||||||
|
* @type {?Guild}
|
||||||
|
* @readonly
|
||||||
|
*/
|
||||||
|
this.guild = data.guild_id ? this.client.guilds?.cache.get(data.guild_id) : null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this interaction was sent in a guild, the member which sent it.
|
||||||
|
* @type {?Member}
|
||||||
|
* @readonly
|
||||||
|
*/
|
||||||
|
this.member = data.member ? this.guild?.members.add(data.member, false) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The timestamp the interaction was created at.
|
||||||
|
* @type {number}
|
||||||
|
* @readonly
|
||||||
|
*/
|
||||||
|
get createdTimestamp() {
|
||||||
|
return Snowflake.deconstruct(this.id).timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The time the interaction was created at.
|
||||||
|
* @type {Date}
|
||||||
|
* @readonly
|
||||||
|
*/
|
||||||
|
get createdAt() {
|
||||||
|
return new Date(this.createdTimestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Acknowledge this interaction without content.
|
||||||
|
*/
|
||||||
|
async acknowledge() {
|
||||||
|
await this.syncHandle.acknowledge();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reply to this interaction.
|
||||||
|
* @param {(StringResolvable | APIMessage)?} content The content for the message.
|
||||||
|
* @param {(MessageOptions | MessageAdditions)?} options The options to provide.
|
||||||
|
*/
|
||||||
|
async reply(content, options) {
|
||||||
|
let apiMessage;
|
||||||
|
|
||||||
|
if (content instanceof APIMessage) {
|
||||||
|
apiMessage = content.resolveData();
|
||||||
|
} else {
|
||||||
|
apiMessage = APIMessage.create(this, content, options).resolveData();
|
||||||
|
if (Array.isArray(apiMessage.data.content)) {
|
||||||
|
throw new Error('Message is too long');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolved = await apiMessage.resolveFiles();
|
||||||
|
|
||||||
|
if (!this.syncHandle.reply(resolved)) {
|
||||||
|
const clientID =
|
||||||
|
this.client.interactionClient.clientID || (await this.client.api.oauth2.applications('@me').get()).id;
|
||||||
|
|
||||||
|
await this.client.api.webhooks(clientID, this.token).post({
|
||||||
|
auth: false,
|
||||||
|
data: resolved.data,
|
||||||
|
files: resolved.files,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Interaction;
|
||||||
|
|
@ -205,11 +205,11 @@ class Message extends Base {
|
||||||
this.flags = new MessageFlags(data.flags).freeze();
|
this.flags = new MessageFlags(data.flags).freeze();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reference data sent in a crossposted message.
|
* Reference data sent in a crossposted message or inline reply.
|
||||||
* @typedef {Object} MessageReference
|
* @typedef {Object} MessageReference
|
||||||
* @property {string} channelID ID of the channel the message was crossposted from
|
* @property {string} channelID ID of the channel the message was referenced
|
||||||
* @property {?string} guildID ID of the guild the message was crossposted from
|
* @property {?string} guildID ID of the guild the message was referenced
|
||||||
* @property {?string} messageID ID of the message that was crossposted
|
* @property {?string} messageID ID of the message that was referenced
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -223,6 +223,10 @@ class Message extends Base {
|
||||||
messageID: data.message_reference.message_id,
|
messageID: data.message_reference.message_id,
|
||||||
}
|
}
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
if (data.referenced_message) {
|
||||||
|
this.channel.messages.add(data.referenced_message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -235,13 +239,18 @@ class Message extends Base {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the message.
|
* Updates the message and returns the old message.
|
||||||
* @param {Object} data Raw Discord message update data
|
* @param {Object} data Raw Discord message update data
|
||||||
|
* @returns {Message}
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
patch(data) {
|
patch(data) {
|
||||||
const clone = this._clone();
|
const clone = this._clone();
|
||||||
this._edits.unshift(clone);
|
const { messageEditHistoryMaxSize } = this.client.options;
|
||||||
|
if (messageEditHistoryMaxSize !== 0) {
|
||||||
|
const editsLimit = messageEditHistoryMaxSize === -1 ? Infinity : messageEditHistoryMaxSize;
|
||||||
|
if (this._edits.unshift(clone) > editsLimit) this._edits.pop();
|
||||||
|
}
|
||||||
|
|
||||||
if ('edited_timestamp' in data) this.editedTimestamp = new Date(data.edited_timestamp).getTime();
|
if ('edited_timestamp' in data) this.editedTimestamp = new Date(data.edited_timestamp).getTime();
|
||||||
if ('content' in data) this.content = data.content;
|
if ('content' in data) this.content = data.content;
|
||||||
|
|
@ -268,6 +277,8 @@ class Message extends Base {
|
||||||
);
|
);
|
||||||
|
|
||||||
this.flags = new MessageFlags('flags' in data ? data.flags : 0).freeze();
|
this.flags = new MessageFlags('flags' in data ? data.flags : 0).freeze();
|
||||||
|
|
||||||
|
return clone;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -277,7 +288,7 @@ class Message extends Base {
|
||||||
* @readonly
|
* @readonly
|
||||||
*/
|
*/
|
||||||
get member() {
|
get member() {
|
||||||
return this.guild ? this.guild.member(this.author) || null : null;
|
return this.guild ? this.guild.members.resolve(this.author) || null : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -418,6 +429,35 @@ class Message extends Base {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Message this crosspost/reply/pin-add references, if cached
|
||||||
|
* @type {?Message}
|
||||||
|
* @readonly
|
||||||
|
*/
|
||||||
|
get referencedMessage() {
|
||||||
|
if (!this.reference) return null;
|
||||||
|
const referenceChannel = this.client.channels.resolve(this.reference.channelID);
|
||||||
|
if (!referenceChannel) return null;
|
||||||
|
return referenceChannel.messages.resolve(this.reference.messageID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the message is crosspostable by the client user
|
||||||
|
* @type {boolean}
|
||||||
|
* @readonly
|
||||||
|
*/
|
||||||
|
get crosspostable() {
|
||||||
|
return (
|
||||||
|
this.channel.type === 'news' &&
|
||||||
|
!this.flags.has(MessageFlags.FLAGS.CROSSPOSTED) &&
|
||||||
|
this.type === 'DEFAULT' &&
|
||||||
|
this.channel.viewable &&
|
||||||
|
this.channel.permissionsFor(this.client.user).has(Permissions.FLAGS.SEND_MESSAGES) &&
|
||||||
|
(this.author.id === this.client.user.id ||
|
||||||
|
this.channel.permissionsFor(this.client.user).has(Permissions.FLAGS.MANAGE_MESSAGES))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Options that can be passed into editMessage.
|
* Options that can be passed into editMessage.
|
||||||
* @typedef {Object} MessageEditOptions
|
* @typedef {Object} MessageEditOptions
|
||||||
|
|
@ -448,6 +488,22 @@ class Message extends Base {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Publishes a message in an announcement channel to all channels following it.
|
||||||
|
* @returns {Promise<Message>}
|
||||||
|
* @example
|
||||||
|
* // Crosspost a message
|
||||||
|
* if (message.channel.type === 'news') {
|
||||||
|
* message.crosspost()
|
||||||
|
* .then(() => console.log('Crossposted message'))
|
||||||
|
* .catch(console.error);
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
async crosspost() {
|
||||||
|
await this.client.api.channels(this.channel.id).messages(this.id).crosspost.post();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pins this message to the channel's pinned messages.
|
* Pins this message to the channel's pinned messages.
|
||||||
* @param {Object} [options] Options for pinning
|
* @param {Object} [options] Options for pinning
|
||||||
|
|
@ -534,7 +590,7 @@ class Message extends Base {
|
||||||
* .catch(console.error);
|
* .catch(console.error);
|
||||||
*/
|
*/
|
||||||
delete(options = {}) {
|
delete(options = {}) {
|
||||||
if (typeof options !== 'object') throw new TypeError('INVALID_TYPE', 'options', 'object', true);
|
if (typeof options !== 'object') return Promise.reject(new TypeError('INVALID_TYPE', 'options', 'object', true));
|
||||||
const { timeout = 0, reason } = options;
|
const { timeout = 0, reason } = options;
|
||||||
if (timeout <= 0) {
|
if (timeout <= 0) {
|
||||||
return this.channel.messages.delete(this.id, reason).then(() => this);
|
return this.channel.messages.delete(this.id, reason).then(() => this);
|
||||||
|
|
@ -548,21 +604,19 @@ class Message extends Base {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replies to the message.
|
* Send an inline reply to this message.
|
||||||
* @param {StringResolvable|APIMessage} [content=''] The content for the message
|
* @param {StringResolvable|APIMessage} [content=''] The content for the message
|
||||||
* @param {MessageOptions|MessageAdditions} [options={}] The options to provide
|
* @param {MessageOptions|MessageAdditions} [options] The additional options to provide
|
||||||
|
* @param {MessageResolvable} [options.replyTo=this] The message to reply to
|
||||||
* @returns {Promise<Message|Message[]>}
|
* @returns {Promise<Message|Message[]>}
|
||||||
* @example
|
|
||||||
* // Reply to a message
|
|
||||||
* message.reply('Hey, I\'m a reply!')
|
|
||||||
* .then(() => console.log(`Sent a reply to ${message.author.username}`))
|
|
||||||
* .catch(console.error);
|
|
||||||
*/
|
*/
|
||||||
reply(content, options) {
|
reply(content, options) {
|
||||||
return this.channel.send(
|
return this.channel.send(
|
||||||
content instanceof APIMessage
|
content instanceof APIMessage
|
||||||
? content
|
? content
|
||||||
: APIMessage.transformOptions(content, options, { reply: this.member || this.author }),
|
: APIMessage.transformOptions(content, options, {
|
||||||
|
replyTo: this,
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,37 +29,37 @@ class MessageEmbed {
|
||||||
* * `link` - a link embed
|
* * `link` - a link embed
|
||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
this.type = data.type;
|
this.type = data.type || 'rich';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The title of this embed
|
* The title of this embed
|
||||||
* @type {?string}
|
* @type {?string}
|
||||||
*/
|
*/
|
||||||
this.title = data.title;
|
this.title = 'title' in data ? data.title : null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The description of this embed
|
* The description of this embed
|
||||||
* @type {?string}
|
* @type {?string}
|
||||||
*/
|
*/
|
||||||
this.description = data.description;
|
this.description = 'description' in data ? data.description : null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The URL of this embed
|
* The URL of this embed
|
||||||
* @type {?string}
|
* @type {?string}
|
||||||
*/
|
*/
|
||||||
this.url = data.url;
|
this.url = 'url' in data ? data.url : null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The color of this embed
|
* The color of this embed
|
||||||
* @type {?number}
|
* @type {?number}
|
||||||
*/
|
*/
|
||||||
this.color = Util.resolveColor(data.color);
|
this.color = 'color' in data ? Util.resolveColor(data.color) : null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The timestamp of this embed
|
* The timestamp of this embed
|
||||||
* @type {?number}
|
* @type {?number}
|
||||||
*/
|
*/
|
||||||
this.timestamp = data.timestamp ? new Date(data.timestamp).getTime() : null;
|
this.timestamp = 'timestamp' in data ? new Date(data.timestamp).getTime() : null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a field of a MessageEmbed
|
* Represents a field of a MessageEmbed
|
||||||
|
|
@ -331,7 +331,7 @@ class MessageEmbed {
|
||||||
*/
|
*/
|
||||||
setFooter(text, iconURL) {
|
setFooter(text, iconURL) {
|
||||||
text = Util.resolveString(text);
|
text = Util.resolveString(text);
|
||||||
this.footer = { text, iconURL, proxyIconURL: undefined };
|
this.footer = { text, iconURL };
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -138,7 +138,7 @@ class MessageMentions {
|
||||||
if (!this.guild) return null;
|
if (!this.guild) return null;
|
||||||
this._members = new Collection();
|
this._members = new Collection();
|
||||||
this.users.forEach(user => {
|
this.users.forEach(user => {
|
||||||
const member = this.guild.member(user);
|
const member = this.guild.members.resolve(user);
|
||||||
if (member) this._members.set(member.user.id, member);
|
if (member) this._members.set(member.user.id, member);
|
||||||
});
|
});
|
||||||
return this._members;
|
return this._members;
|
||||||
|
|
|
||||||
|
|
@ -28,12 +28,6 @@ class MessageReaction {
|
||||||
*/
|
*/
|
||||||
this.message = message;
|
this.message = message;
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the client has given this reaction
|
|
||||||
* @type {boolean}
|
|
||||||
*/
|
|
||||||
this.me = data.me;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A manager of the users that have given this reaction
|
* A manager of the users that have given this reaction
|
||||||
* @type {ReactionUserManager}
|
* @type {ReactionUserManager}
|
||||||
|
|
@ -46,13 +40,20 @@ class MessageReaction {
|
||||||
}
|
}
|
||||||
|
|
||||||
_patch(data) {
|
_patch(data) {
|
||||||
/**
|
|
||||||
* The number of people that have given the same reaction
|
|
||||||
* @type {?number}
|
|
||||||
* @name MessageReaction#count
|
|
||||||
*/
|
|
||||||
// eslint-disable-next-line eqeqeq
|
// eslint-disable-next-line eqeqeq
|
||||||
if (this.count == undefined) this.count = data.count;
|
if (this.count == undefined) {
|
||||||
|
/**
|
||||||
|
* The number of people that have given the same reaction
|
||||||
|
* @type {?number}
|
||||||
|
*/
|
||||||
|
this.count = data.count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the client has given this reaction
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
this.me = data.me;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const TextChannel = require('./TextChannel');
|
const TextChannel = require('./TextChannel');
|
||||||
|
const { Error } = require('../errors');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a guild news channel on Discord.
|
* Represents a guild news channel on Discord.
|
||||||
|
|
@ -13,6 +14,25 @@ class NewsChannel extends TextChannel {
|
||||||
// News channels don't have a rate limit per user, remove it
|
// News channels don't have a rate limit per user, remove it
|
||||||
this.rateLimitPerUser = undefined;
|
this.rateLimitPerUser = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the target to this channel's followers.
|
||||||
|
* @param {GuildChannelResolvable} channel The channel where the webhook should be created
|
||||||
|
* @param {string} [reason] Reason for creating the webhook
|
||||||
|
* @returns {Promise<NewsChannel>}
|
||||||
|
* @example
|
||||||
|
* if (channel.type === 'news') {
|
||||||
|
* channel.addFollower('222197033908436994', 'Important announcements')
|
||||||
|
* .then(() => console.log('Added follower'))
|
||||||
|
* .catch(console.error);
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
async addFollower(channel, reason) {
|
||||||
|
const channelID = this.guild.channels.resolveID(channel);
|
||||||
|
if (!channelID) throw new Error('GUILD_CHANNEL_RESOLVE');
|
||||||
|
await this.client.api.channels(this.id).followers.post({ data: { webhook_channel_id: channelID }, reason });
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = NewsChannel;
|
module.exports = NewsChannel;
|
||||||
|
|
|
||||||
|
|
@ -184,8 +184,8 @@ class Activity {
|
||||||
/**
|
/**
|
||||||
* Timestamps for the activity
|
* Timestamps for the activity
|
||||||
* @type {?Object}
|
* @type {?Object}
|
||||||
* @prop {?Date} start When the activity started
|
* @property {?Date} start When the activity started
|
||||||
* @prop {?Date} end When the activity will end
|
* @property {?Date} end When the activity will end
|
||||||
*/
|
*/
|
||||||
this.timestamps = data.timestamps
|
this.timestamps = data.timestamps
|
||||||
? {
|
? {
|
||||||
|
|
@ -197,8 +197,8 @@ class Activity {
|
||||||
/**
|
/**
|
||||||
* Party of the activity
|
* Party of the activity
|
||||||
* @type {?Object}
|
* @type {?Object}
|
||||||
* @prop {?string} id ID of the party
|
* @property {?string} id ID of the party
|
||||||
* @prop {number[]} size Size of the party as `[current, max]`
|
* @property {number[]} size Size of the party as `[current, max]`
|
||||||
*/
|
*/
|
||||||
this.party = data.party || null;
|
this.party = data.party || null;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,7 @@ class ReactionCollector extends Collector {
|
||||||
*/
|
*/
|
||||||
dispose(reaction, user) {
|
dispose(reaction, user) {
|
||||||
/**
|
/**
|
||||||
* Emitted whenever a reaction is disposed of and the `dispose` option is set to true.
|
* Emitted when the reaction had all the users removed and the `dispose` option is set to true.
|
||||||
* @event ReactionCollector#dispose
|
* @event ReactionCollector#dispose
|
||||||
* @param {MessageReaction} reaction The reaction that was disposed of
|
* @param {MessageReaction} reaction The reaction that was disposed of
|
||||||
* @param {User} user The user that removed the reaction
|
* @param {User} user The user that removed the reaction
|
||||||
|
|
@ -111,9 +111,7 @@ class ReactionCollector extends Collector {
|
||||||
if (reaction.message.id !== this.message.id) return null;
|
if (reaction.message.id !== this.message.id) return null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emitted whenever a reaction is removed from a message and the `dispose` option is set to true.
|
* Emitted when the reaction had one user removed and the `dispose` option is set to true.
|
||||||
* Will emit on all reaction removals, as opposed to {@link Collector#dispose} which will only
|
|
||||||
* be emitted when the entire reaction is removed.
|
|
||||||
* @event ReactionCollector#remove
|
* @event ReactionCollector#remove
|
||||||
* @param {MessageReaction} reaction The reaction that was removed
|
* @param {MessageReaction} reaction The reaction that was removed
|
||||||
* @param {User} user The user that removed the reaction
|
* @param {User} user The user that removed the reaction
|
||||||
|
|
|
||||||
|
|
@ -127,7 +127,7 @@ class Role extends Base {
|
||||||
*/
|
*/
|
||||||
get editable() {
|
get editable() {
|
||||||
if (this.managed) return false;
|
if (this.managed) return false;
|
||||||
const clientMember = this.guild.member(this.client.user);
|
const clientMember = this.guild.members.resolve(this.client.user);
|
||||||
if (!clientMember.permissions.has(Permissions.FLAGS.MANAGE_ROLES)) return false;
|
if (!clientMember.permissions.has(Permissions.FLAGS.MANAGE_ROLES)) return false;
|
||||||
return clientMember.roles.highest.comparePositionTo(this) > 0;
|
return clientMember.roles.highest.comparePositionTo(this) > 0;
|
||||||
}
|
}
|
||||||
|
|
@ -173,7 +173,7 @@ class Role extends Base {
|
||||||
* @example
|
* @example
|
||||||
* // Edit a role
|
* // Edit a role
|
||||||
* role.edit({ name: 'new role' })
|
* role.edit({ name: 'new role' })
|
||||||
* .then(updated => console.log(`Edited role ${updated.name} name to ${updated.name}`))
|
* .then(updated => console.log(`Edited role name to ${updated.name}`))
|
||||||
* .catch(console.error);
|
* .catch(console.error);
|
||||||
*/
|
*/
|
||||||
async edit(data, reason) {
|
async edit(data, reason) {
|
||||||
|
|
@ -232,7 +232,7 @@ class Role extends Base {
|
||||||
* @example
|
* @example
|
||||||
* // Set the name of the role
|
* // Set the name of the role
|
||||||
* role.setName('new role')
|
* role.setName('new role')
|
||||||
* .then(updated => console.log(`Edited name of role ${role.name} to ${updated.name}`))
|
* .then(updated => console.log(`Updated role name to ${updated.name}`))
|
||||||
* .catch(console.error);
|
* .catch(console.error);
|
||||||
*/
|
*/
|
||||||
setName(name, reason) {
|
setName(name, reason) {
|
||||||
|
|
@ -262,7 +262,7 @@ class Role extends Base {
|
||||||
* @example
|
* @example
|
||||||
* // Set the hoist of the role
|
* // Set the hoist of the role
|
||||||
* role.setHoist(true)
|
* role.setHoist(true)
|
||||||
* .then(r => console.log(`Role hoisted: ${r.hoist}`))
|
* .then(updated => console.log(`Role hoisted: ${updated.hoist}`))
|
||||||
* .catch(console.error);
|
* .catch(console.error);
|
||||||
*/
|
*/
|
||||||
setHoist(hoist, reason) {
|
setHoist(hoist, reason) {
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const Base = require('./Base');
|
const Base = require('./Base');
|
||||||
const { Presence } = require('./Presence');
|
|
||||||
const TextBasedChannel = require('./interfaces/TextBasedChannel');
|
const TextBasedChannel = require('./interfaces/TextBasedChannel');
|
||||||
const { Error } = require('../errors');
|
const { Error } = require('../errors');
|
||||||
const Snowflake = require('../util/Snowflake');
|
const Snowflake = require('../util/Snowflake');
|
||||||
const UserFlags = require('../util/UserFlags');
|
const UserFlags = require('../util/UserFlags');
|
||||||
|
|
||||||
|
let Structures;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a user on Discord.
|
* Represents a user on Discord.
|
||||||
* @implements {TextBasedChannel}
|
* @implements {TextBasedChannel}
|
||||||
|
|
@ -26,35 +27,35 @@ class User extends Base {
|
||||||
*/
|
*/
|
||||||
this.id = data.id;
|
this.id = data.id;
|
||||||
|
|
||||||
|
this.system = null;
|
||||||
|
this.flags = null;
|
||||||
|
|
||||||
this._patch(data);
|
this._patch(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
_patch(data) {
|
_patch(data) {
|
||||||
if (typeof this.bot !== 'boolean') {
|
|
||||||
/**
|
|
||||||
* Whether or not the user is a bot
|
|
||||||
* @type {?boolean}
|
|
||||||
* @name User#bot
|
|
||||||
*/
|
|
||||||
this.bot = 'bot' in data ? Boolean(data.bot) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('username' in data) {
|
if ('username' in data) {
|
||||||
/**
|
/**
|
||||||
* The username of the user
|
* The username of the user
|
||||||
* @type {?string}
|
* @type {?string}
|
||||||
* @name User#username
|
|
||||||
*/
|
*/
|
||||||
this.username = data.username;
|
this.username = data.username;
|
||||||
} else if (typeof this.username !== 'string') {
|
} else if (typeof this.username !== 'string') {
|
||||||
this.username = null;
|
this.username = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ('bot' in data || typeof this.bot !== 'boolean') {
|
||||||
|
/**
|
||||||
|
* Whether or not the user is a bot
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
this.bot = Boolean(data.bot);
|
||||||
|
}
|
||||||
|
|
||||||
if ('discriminator' in data) {
|
if ('discriminator' in data) {
|
||||||
/**
|
/**
|
||||||
* A discriminator based on username for the user
|
* A discriminator based on username for the user
|
||||||
* @type {?string}
|
* @type {?string}
|
||||||
* @name User#discriminator
|
|
||||||
*/
|
*/
|
||||||
this.discriminator = data.discriminator;
|
this.discriminator = data.discriminator;
|
||||||
} else if (typeof this.discriminator !== 'string') {
|
} else if (typeof this.discriminator !== 'string') {
|
||||||
|
|
@ -65,7 +66,6 @@ class User extends Base {
|
||||||
/**
|
/**
|
||||||
* The ID of the user's avatar
|
* The ID of the user's avatar
|
||||||
* @type {?string}
|
* @type {?string}
|
||||||
* @name User#avatar
|
|
||||||
*/
|
*/
|
||||||
this.avatar = data.avatar;
|
this.avatar = data.avatar;
|
||||||
} else if (typeof this.avatar !== 'string') {
|
} else if (typeof this.avatar !== 'string') {
|
||||||
|
|
@ -76,25 +76,14 @@ class User extends Base {
|
||||||
/**
|
/**
|
||||||
* Whether the user is an Official Discord System user (part of the urgent message system)
|
* Whether the user is an Official Discord System user (part of the urgent message system)
|
||||||
* @type {?boolean}
|
* @type {?boolean}
|
||||||
* @name User#system
|
|
||||||
*/
|
*/
|
||||||
this.system = Boolean(data.system);
|
this.system = Boolean(data.system);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('locale' in data) {
|
|
||||||
/**
|
|
||||||
* The locale of the user's client (ISO 639-1)
|
|
||||||
* @type {?string}
|
|
||||||
* @name User#locale
|
|
||||||
*/
|
|
||||||
this.locale = data.locale;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('public_flags' in data) {
|
if ('public_flags' in data) {
|
||||||
/**
|
/**
|
||||||
* The flags for this user
|
* The flags for this user
|
||||||
* @type {?UserFlags}
|
* @type {?UserFlags}
|
||||||
* @name User#flags
|
|
||||||
*/
|
*/
|
||||||
this.flags = new UserFlags(data.public_flags);
|
this.flags = new UserFlags(data.public_flags);
|
||||||
}
|
}
|
||||||
|
|
@ -158,6 +147,8 @@ class User extends Base {
|
||||||
for (const guild of this.client.guilds.cache.values()) {
|
for (const guild of this.client.guilds.cache.values()) {
|
||||||
if (guild.presences.cache.has(this.id)) return guild.presences.cache.get(this.id);
|
if (guild.presences.cache.has(this.id)) return guild.presences.cache.get(this.id);
|
||||||
}
|
}
|
||||||
|
if (!Structures) Structures = require('../util/Structures');
|
||||||
|
const Presence = Structures.get('Presence');
|
||||||
return new Presence(this.client, { user: { id: this.id } });
|
return new Presence(this.client, { user: { id: this.id } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -287,7 +278,7 @@ class User extends Base {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches this user's flags.
|
* Fetches this user's flags.
|
||||||
* @param {boolean} [force=false] Whether to skip the cache check and request the AP
|
* @param {boolean} [force=false] Whether to skip the cache check and request the API
|
||||||
* @returns {Promise<UserFlags>}
|
* @returns {Promise<UserFlags>}
|
||||||
*/
|
*/
|
||||||
async fetchFlags(force = false) {
|
async fetchFlags(force = false) {
|
||||||
|
|
@ -299,7 +290,7 @@ class User extends Base {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches this user.
|
* Fetches this user.
|
||||||
* @param {boolean} [force=false] Whether to skip the cache check and request the AP
|
* @param {boolean} [force=false] Whether to skip the cache check and request the API
|
||||||
* @returns {Promise<User>}
|
* @returns {Promise<User>}
|
||||||
*/
|
*/
|
||||||
fetch(force = false) {
|
fetch(force = false) {
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@ class VoiceChannel extends GuildChannel {
|
||||||
/**
|
/**
|
||||||
* The members in this voice channel
|
* The members in this voice channel
|
||||||
* @type {Collection<Snowflake, GuildMember>}
|
* @type {Collection<Snowflake, GuildMember>}
|
||||||
* @name VoiceChannel#members
|
|
||||||
* @readonly
|
* @readonly
|
||||||
*/
|
*/
|
||||||
get members() {
|
get members() {
|
||||||
|
|
|
||||||
|
|
@ -32,32 +32,32 @@ class VoiceState extends Base {
|
||||||
* Whether this member is deafened server-wide
|
* Whether this member is deafened server-wide
|
||||||
* @type {?boolean}
|
* @type {?boolean}
|
||||||
*/
|
*/
|
||||||
this.serverDeaf = data.deaf;
|
this.serverDeaf = 'deaf' in data ? data.deaf : null;
|
||||||
/**
|
/**
|
||||||
* Whether this member is muted server-wide
|
* Whether this member is muted server-wide
|
||||||
* @type {?boolean}
|
* @type {?boolean}
|
||||||
*/
|
*/
|
||||||
this.serverMute = data.mute;
|
this.serverMute = 'mute' in data ? data.mute : null;
|
||||||
/**
|
/**
|
||||||
* Whether this member is self-deafened
|
* Whether this member is self-deafened
|
||||||
* @type {?boolean}
|
* @type {?boolean}
|
||||||
*/
|
*/
|
||||||
this.selfDeaf = data.self_deaf;
|
this.selfDeaf = 'self_deaf' in data ? data.self_deaf : null;
|
||||||
/**
|
/**
|
||||||
* Whether this member is self-muted
|
* Whether this member is self-muted
|
||||||
* @type {?boolean}
|
* @type {?boolean}
|
||||||
*/
|
*/
|
||||||
this.selfMute = data.self_mute;
|
this.selfMute = 'self_mute' in data ? data.self_mute : null;
|
||||||
/**
|
/**
|
||||||
* Whether this member's camera is enabled
|
* Whether this member's camera is enabled
|
||||||
* @type {boolean}
|
* @type {?boolean}
|
||||||
*/
|
*/
|
||||||
this.selfVideo = data.self_video;
|
this.selfVideo = 'self_video' in data ? data.self_video : null;
|
||||||
/**
|
/**
|
||||||
* The session ID of this member's connection
|
* The session ID of this member's connection
|
||||||
* @type {?string}
|
* @type {?string}
|
||||||
*/
|
*/
|
||||||
this.sessionID = data.session_id;
|
this.sessionID = 'session_id' in data ? data.session_id : null;
|
||||||
/**
|
/**
|
||||||
* Whether this member is streaming using "Go Live"
|
* Whether this member is streaming using "Go Live"
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
|
|
@ -67,7 +67,7 @@ class VoiceState extends Base {
|
||||||
* The ID of the voice channel that this member is in
|
* The ID of the voice channel that this member is in
|
||||||
* @type {?Snowflake}
|
* @type {?Snowflake}
|
||||||
*/
|
*/
|
||||||
this.channelID = data.channel_id;
|
this.channelID = data.channel_id || null;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
125
src/structures/interfaces/Application.js
Normal file
125
src/structures/interfaces/Application.js
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { ClientApplicationAssetTypes, Endpoints } = require('../../util/Constants');
|
||||||
|
const Snowflake = require('../../util/Snowflake');
|
||||||
|
const Base = require('../Base');
|
||||||
|
|
||||||
|
const AssetTypes = Object.keys(ClientApplicationAssetTypes);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an OAuth2 Application.
|
||||||
|
* @abstract
|
||||||
|
*/
|
||||||
|
class Application extends Base {
|
||||||
|
constructor(client, data) {
|
||||||
|
super(client);
|
||||||
|
this._patch(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
_patch(data) {
|
||||||
|
/**
|
||||||
|
* The ID of the app
|
||||||
|
* @type {Snowflake}
|
||||||
|
*/
|
||||||
|
this.id = data.id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the app
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
this.name = data.name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The app's description
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
this.description = data.description;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The app's icon hash
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
this.icon = data.icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The timestamp the app was created at
|
||||||
|
* @type {number}
|
||||||
|
* @readonly
|
||||||
|
*/
|
||||||
|
get createdTimestamp() {
|
||||||
|
return Snowflake.deconstruct(this.id).timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The time the app was created at
|
||||||
|
* @type {Date}
|
||||||
|
* @readonly
|
||||||
|
*/
|
||||||
|
get createdAt() {
|
||||||
|
return new Date(this.createdTimestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A link to the application's icon.
|
||||||
|
* @param {ImageURLOptions} [options={}] Options for the Image URL
|
||||||
|
* @returns {?string} URL to the icon
|
||||||
|
*/
|
||||||
|
iconURL({ format, size } = {}) {
|
||||||
|
if (!this.icon) return null;
|
||||||
|
return this.client.rest.cdn.AppIcon(this.id, this.icon, { format, size });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A link to this application's cover image.
|
||||||
|
* @param {ImageURLOptions} [options={}] Options for the Image URL
|
||||||
|
* @returns {?string} URL to the cover image
|
||||||
|
*/
|
||||||
|
coverImage({ format, size } = {}) {
|
||||||
|
if (!this.cover) return null;
|
||||||
|
return Endpoints.CDN(this.client.options.http.cdn).AppIcon(this.id, this.cover, { format, size });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asset data.
|
||||||
|
* @typedef {Object} ApplicationAsset
|
||||||
|
* @property {Snowflake} id The asset ID
|
||||||
|
* @property {string} name The asset name
|
||||||
|
* @property {string} type The asset type
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the clients rich presence assets.
|
||||||
|
* @returns {Promise<Array<ApplicationAsset>>}
|
||||||
|
*/
|
||||||
|
fetchAssets() {
|
||||||
|
return this.client.api.oauth2
|
||||||
|
.applications(this.id)
|
||||||
|
.assets.get()
|
||||||
|
.then(assets =>
|
||||||
|
assets.map(a => ({
|
||||||
|
id: a.id,
|
||||||
|
name: a.name,
|
||||||
|
type: AssetTypes[a.type - 1],
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When concatenated with a string, this automatically returns the application's name instead of the
|
||||||
|
* Oauth2Application object.
|
||||||
|
* @returns {string}
|
||||||
|
* @example
|
||||||
|
* // Logs: Application name: My App
|
||||||
|
* console.log(`Application name: ${application}`);
|
||||||
|
*/
|
||||||
|
toString() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON() {
|
||||||
|
return super.toJSON({ createdTimestamp: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Application;
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const EventEmitter = require('events');
|
const EventEmitter = require('events');
|
||||||
|
const { TypeError } = require('../../errors');
|
||||||
const Collection = require('../../util/Collection');
|
const Collection = require('../../util/Collection');
|
||||||
const Util = require('../../util/Util');
|
const Util = require('../../util/Util');
|
||||||
|
|
||||||
|
|
@ -74,6 +75,10 @@ class Collector extends EventEmitter {
|
||||||
*/
|
*/
|
||||||
this._idletimeout = null;
|
this._idletimeout = null;
|
||||||
|
|
||||||
|
if (typeof filter !== 'function') {
|
||||||
|
throw new TypeError('INVALID_TYPE', 'filter', 'function');
|
||||||
|
}
|
||||||
|
|
||||||
this.handleCollect = this.handleCollect.bind(this);
|
this.handleCollect = this.handleCollect.bind(this);
|
||||||
this.handleDispose = this.handleDispose.bind(this);
|
this.handleDispose = this.handleDispose.bind(this);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ class TextBasedChannel {
|
||||||
* @property {string|boolean} [code] Language for optional codeblock formatting to apply
|
* @property {string|boolean} [code] Language for optional codeblock formatting to apply
|
||||||
* @property {boolean|SplitOptions} [split=false] Whether or not the message should be split into multiple messages if
|
* @property {boolean|SplitOptions} [split=false] Whether or not the message should be split into multiple messages if
|
||||||
* it exceeds the character limit. If an object is provided, these are the options for splitting the message
|
* it exceeds the character limit. If an object is provided, these are the options for splitting the message
|
||||||
* @property {UserResolvable} [reply] User to reply to (prefixes the message with a mention, except in DMs)
|
* @property {MessageResolvable} [replyTo] The message to reply to (must be in the same channel)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -74,6 +74,7 @@ class TextBasedChannel {
|
||||||
* @property {MessageMentionTypes[]} [parse] Types of mentions to be parsed
|
* @property {MessageMentionTypes[]} [parse] Types of mentions to be parsed
|
||||||
* @property {Snowflake[]} [users] Snowflakes of Users to be parsed as mentions
|
* @property {Snowflake[]} [users] Snowflakes of Users to be parsed as mentions
|
||||||
* @property {Snowflake[]} [roles] Snowflakes of Roles to be parsed as mentions
|
* @property {Snowflake[]} [roles] Snowflakes of Roles to be parsed as mentions
|
||||||
|
* @property {boolean} [repliedUser] Whether the author of the Message being replied to should be pinged
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ const browser = (exports.browser = typeof window !== 'undefined');
|
||||||
* sweepable (in seconds, 0 for forever)
|
* sweepable (in seconds, 0 for forever)
|
||||||
* @property {number} [messageSweepInterval=0] How frequently to remove messages from the cache that are older than
|
* @property {number} [messageSweepInterval=0] How frequently to remove messages from the cache that are older than
|
||||||
* the message cache lifetime (in seconds, 0 for never)
|
* the message cache lifetime (in seconds, 0 for never)
|
||||||
|
* @property {number} [messageEditHistoryMaxSize=-1] Maximum number of previous versions to hold for an edited message
|
||||||
|
* (-1 or Infinity for unlimited - don't do this without sweeping, otherwise memory usage may climb indefinitely.)
|
||||||
* @property {boolean} [fetchAllMembers=false] Whether to cache all guild members and users upon startup, as well as
|
* @property {boolean} [fetchAllMembers=false] Whether to cache all guild members and users upon startup, as well as
|
||||||
* upon joining a guild (should be avoided whenever possible)
|
* upon joining a guild (should be avoided whenever possible)
|
||||||
* @property {DisableMentionType} [disableMentions='none'] Default value for {@link MessageOptions#disableMentions}
|
* @property {DisableMentionType} [disableMentions='none'] Default value for {@link MessageOptions#disableMentions}
|
||||||
|
|
@ -28,7 +30,7 @@ const browser = (exports.browser = typeof window !== 'undefined');
|
||||||
* important usage information, as partials require you to put checks in place when handling data.
|
* important usage information, as partials require you to put checks in place when handling data.
|
||||||
* @property {number} [restWsBridgeTimeout=5000] Maximum time permitted between REST responses and their
|
* @property {number} [restWsBridgeTimeout=5000] Maximum time permitted between REST responses and their
|
||||||
* corresponding websocket events
|
* corresponding websocket events
|
||||||
* @property {number} [restTimeOffset=500] Extra time in millseconds to wait before continuing to make REST
|
* @property {number} [restTimeOffset=500] Extra time in milliseconds to wait before continuing to make REST
|
||||||
* requests (higher values will reduce rate-limiting errors on bad connections)
|
* requests (higher values will reduce rate-limiting errors on bad connections)
|
||||||
* @property {number} [restRequestTimeout=15000] Time to wait before cancelling a REST request, in milliseconds
|
* @property {number} [restRequestTimeout=15000] Time to wait before cancelling a REST request, in milliseconds
|
||||||
* @property {number} [restSweepInterval=60] How frequently to delete inactive request buckets, in seconds
|
* @property {number} [restSweepInterval=60] How frequently to delete inactive request buckets, in seconds
|
||||||
|
|
@ -43,6 +45,7 @@ exports.DefaultOptions = {
|
||||||
messageCacheMaxSize: 200,
|
messageCacheMaxSize: 200,
|
||||||
messageCacheLifetime: 0,
|
messageCacheLifetime: 0,
|
||||||
messageSweepInterval: 0,
|
messageSweepInterval: 0,
|
||||||
|
messageEditHistoryMaxSize: -1,
|
||||||
fetchAllMembers: false,
|
fetchAllMembers: false,
|
||||||
disableMentions: 'none',
|
disableMentions: 'none',
|
||||||
partials: [],
|
partials: [],
|
||||||
|
|
@ -74,16 +77,18 @@ exports.DefaultOptions = {
|
||||||
/**
|
/**
|
||||||
* HTTP options
|
* HTTP options
|
||||||
* @typedef {Object} HTTPOptions
|
* @typedef {Object} HTTPOptions
|
||||||
* @property {number} [version=7] API version to use
|
* @property {number} [version=8] API version to use
|
||||||
* @property {string} [api='https://discord.com/api'] Base url of the API
|
* @property {string} [api='https://discord.com/api'] Base url of the API
|
||||||
* @property {string} [cdn='https://cdn.discordapp.com'] Base url of the CDN
|
* @property {string} [cdn='https://cdn.discordapp.com'] Base url of the CDN
|
||||||
* @property {string} [invite='https://discord.gg'] Base url of invites
|
* @property {string} [invite='https://discord.gg'] Base url of invites
|
||||||
|
* @property {string} [template='https://discord.new'] Base url of templates
|
||||||
*/
|
*/
|
||||||
http: {
|
http: {
|
||||||
version: 7,
|
version: 8,
|
||||||
api: 'https://discord.com/api',
|
api: 'https://discord.com/api',
|
||||||
cdn: 'https://cdn.discordapp.com',
|
cdn: 'https://cdn.discordapp.com',
|
||||||
invite: 'https://discord.gg',
|
invite: 'https://discord.gg',
|
||||||
|
template: 'https://discord.new',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -277,6 +282,7 @@ exports.Events = {
|
||||||
SHARD_READY: 'shardReady',
|
SHARD_READY: 'shardReady',
|
||||||
SHARD_RESUME: 'shardResume',
|
SHARD_RESUME: 'shardResume',
|
||||||
INVALIDATED: 'invalidated',
|
INVALIDATED: 'invalidated',
|
||||||
|
INTERACTION_CREATE: 'interactionCreate',
|
||||||
RAW: 'raw',
|
RAW: 'raw',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -340,6 +346,7 @@ exports.PartialTypes = keyMirror(['USER', 'CHANNEL', 'GUILD_MEMBER', 'MESSAGE',
|
||||||
* * VOICE_STATE_UPDATE
|
* * VOICE_STATE_UPDATE
|
||||||
* * VOICE_SERVER_UPDATE
|
* * VOICE_SERVER_UPDATE
|
||||||
* * WEBHOOKS_UPDATE
|
* * WEBHOOKS_UPDATE
|
||||||
|
* * INTERACTION_CREATE
|
||||||
* @typedef {string} WSEventType
|
* @typedef {string} WSEventType
|
||||||
*/
|
*/
|
||||||
exports.WSEvents = keyMirror([
|
exports.WSEvents = keyMirror([
|
||||||
|
|
@ -379,6 +386,7 @@ exports.WSEvents = keyMirror([
|
||||||
'VOICE_STATE_UPDATE',
|
'VOICE_STATE_UPDATE',
|
||||||
'VOICE_SERVER_UPDATE',
|
'VOICE_SERVER_UPDATE',
|
||||||
'WEBHOOKS_UPDATE',
|
'WEBHOOKS_UPDATE',
|
||||||
|
'INTERACTION_CREATE',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -398,6 +406,7 @@ exports.WSEvents = keyMirror([
|
||||||
* * CHANNEL_FOLLOW_ADD
|
* * CHANNEL_FOLLOW_ADD
|
||||||
* * GUILD_DISCOVERY_DISQUALIFIED
|
* * GUILD_DISCOVERY_DISQUALIFIED
|
||||||
* * GUILD_DISCOVERY_REQUALIFIED
|
* * GUILD_DISCOVERY_REQUALIFIED
|
||||||
|
* * REPLY
|
||||||
* @typedef {string} MessageType
|
* @typedef {string} MessageType
|
||||||
*/
|
*/
|
||||||
exports.MessageTypes = [
|
exports.MessageTypes = [
|
||||||
|
|
@ -417,6 +426,10 @@ exports.MessageTypes = [
|
||||||
null,
|
null,
|
||||||
'GUILD_DISCOVERY_DISQUALIFIED',
|
'GUILD_DISCOVERY_DISQUALIFIED',
|
||||||
'GUILD_DISCOVERY_REQUALIFIED',
|
'GUILD_DISCOVERY_REQUALIFIED',
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
'REPLY',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -427,9 +440,10 @@ exports.MessageTypes = [
|
||||||
* * LISTENING
|
* * LISTENING
|
||||||
* * WATCHING
|
* * WATCHING
|
||||||
* * CUSTOM_STATUS
|
* * CUSTOM_STATUS
|
||||||
|
* * COMPETING
|
||||||
* @typedef {string} ActivityType
|
* @typedef {string} ActivityType
|
||||||
*/
|
*/
|
||||||
exports.ActivityTypes = ['PLAYING', 'STREAMING', 'LISTENING', 'WATCHING', 'CUSTOM_STATUS'];
|
exports.ActivityTypes = ['PLAYING', 'STREAMING', 'LISTENING', 'WATCHING', 'CUSTOM_STATUS', 'COMPETING'];
|
||||||
|
|
||||||
exports.ChannelTypes = {
|
exports.ChannelTypes = {
|
||||||
TEXT: 0,
|
TEXT: 0,
|
||||||
|
|
@ -515,17 +529,27 @@ exports.VerificationLevels = ['NONE', 'LOW', 'MEDIUM', 'HIGH', 'VERY_HIGH'];
|
||||||
* * UNKNOWN_USER
|
* * UNKNOWN_USER
|
||||||
* * UNKNOWN_EMOJI
|
* * UNKNOWN_EMOJI
|
||||||
* * UNKNOWN_WEBHOOK
|
* * UNKNOWN_WEBHOOK
|
||||||
|
* * UNKNOWN_BAN
|
||||||
|
* * UNKNOWN_GUILD_TEMPLATE
|
||||||
* * BOT_PROHIBITED_ENDPOINT
|
* * BOT_PROHIBITED_ENDPOINT
|
||||||
* * BOT_ONLY_ENDPOINT
|
* * BOT_ONLY_ENDPOINT
|
||||||
|
* * CHANNEL_HIT_WRITE_RATELIMIT
|
||||||
* * MAXIMUM_GUILDS
|
* * MAXIMUM_GUILDS
|
||||||
* * MAXIMUM_FRIENDS
|
* * MAXIMUM_FRIENDS
|
||||||
* * MAXIMUM_PINS
|
* * MAXIMUM_PINS
|
||||||
* * MAXIMUM_ROLES
|
* * MAXIMUM_ROLES
|
||||||
|
* * MAXIMUM_WEBHOOKS
|
||||||
* * MAXIMUM_REACTIONS
|
* * MAXIMUM_REACTIONS
|
||||||
* * MAXIMUM_CHANNELS
|
* * MAXIMUM_CHANNELS
|
||||||
|
* * MAXIMUM_ATTACHMENTS
|
||||||
* * MAXIMUM_INVITES
|
* * MAXIMUM_INVITES
|
||||||
|
* * GUILD_ALREADY_HAS_TEMPLATE
|
||||||
* * UNAUTHORIZED
|
* * UNAUTHORIZED
|
||||||
|
* * ACCOUNT_VERIFICATION_REQUIRED
|
||||||
|
* * REQUEST_ENTITY_TOO_LARGE
|
||||||
|
* * FEATURE_TEMPORARILY_DISABLED
|
||||||
* * USER_BANNED
|
* * USER_BANNED
|
||||||
|
* * ALREADY_CROSSPOSTED
|
||||||
* * MISSING_ACCESS
|
* * MISSING_ACCESS
|
||||||
* * INVALID_ACCOUNT_TYPE
|
* * INVALID_ACCOUNT_TYPE
|
||||||
* * CANNOT_EXECUTE_ON_DM
|
* * CANNOT_EXECUTE_ON_DM
|
||||||
|
|
@ -550,6 +574,7 @@ exports.VerificationLevels = ['NONE', 'LOW', 'MEDIUM', 'HIGH', 'VERY_HIGH'];
|
||||||
* * INVALID_FORM_BODY
|
* * INVALID_FORM_BODY
|
||||||
* * INVITE_ACCEPTED_TO_GUILD_NOT_CONTAINING_BOT
|
* * INVITE_ACCEPTED_TO_GUILD_NOT_CONTAINING_BOT
|
||||||
* * INVALID_API_VERSION
|
* * INVALID_API_VERSION
|
||||||
|
* * CANNOT_DELETE_COMMUNITY_REQUIRED_CHANNEL
|
||||||
* * REACTION_BLOCKED
|
* * REACTION_BLOCKED
|
||||||
* * RESOURCE_OVERLOADED
|
* * RESOURCE_OVERLOADED
|
||||||
* @typedef {string} APIError
|
* @typedef {string} APIError
|
||||||
|
|
@ -570,17 +595,27 @@ exports.APIErrors = {
|
||||||
UNKNOWN_USER: 10013,
|
UNKNOWN_USER: 10013,
|
||||||
UNKNOWN_EMOJI: 10014,
|
UNKNOWN_EMOJI: 10014,
|
||||||
UNKNOWN_WEBHOOK: 10015,
|
UNKNOWN_WEBHOOK: 10015,
|
||||||
|
UNKNOWN_BAN: 10026,
|
||||||
|
UNKNOWN_GUILD_TEMPLATE: 10057,
|
||||||
BOT_PROHIBITED_ENDPOINT: 20001,
|
BOT_PROHIBITED_ENDPOINT: 20001,
|
||||||
BOT_ONLY_ENDPOINT: 20002,
|
BOT_ONLY_ENDPOINT: 20002,
|
||||||
|
CHANNEL_HIT_WRITE_RATELIMIT: 20028,
|
||||||
MAXIMUM_GUILDS: 30001,
|
MAXIMUM_GUILDS: 30001,
|
||||||
MAXIMUM_FRIENDS: 30002,
|
MAXIMUM_FRIENDS: 30002,
|
||||||
MAXIMUM_PINS: 30003,
|
MAXIMUM_PINS: 30003,
|
||||||
MAXIMUM_ROLES: 30005,
|
MAXIMUM_ROLES: 30005,
|
||||||
|
MAXIMUM_WEBHOOKS: 30007,
|
||||||
MAXIMUM_REACTIONS: 30010,
|
MAXIMUM_REACTIONS: 30010,
|
||||||
MAXIMUM_CHANNELS: 30013,
|
MAXIMUM_CHANNELS: 30013,
|
||||||
|
MAXIMUM_ATTACHMENTS: 30015,
|
||||||
MAXIMUM_INVITES: 30016,
|
MAXIMUM_INVITES: 30016,
|
||||||
|
GUILD_ALREADY_HAS_TEMPLATE: 30031,
|
||||||
UNAUTHORIZED: 40001,
|
UNAUTHORIZED: 40001,
|
||||||
|
ACCOUNT_VERIFICATION_REQUIRED: 40002,
|
||||||
|
REQUEST_ENTITY_TOO_LARGE: 40005,
|
||||||
|
FEATURE_TEMPORARILY_DISABLED: 40006,
|
||||||
USER_BANNED: 40007,
|
USER_BANNED: 40007,
|
||||||
|
ALREADY_CROSSPOSTED: 40033,
|
||||||
MISSING_ACCESS: 50001,
|
MISSING_ACCESS: 50001,
|
||||||
INVALID_ACCOUNT_TYPE: 50002,
|
INVALID_ACCOUNT_TYPE: 50002,
|
||||||
CANNOT_EXECUTE_ON_DM: 50003,
|
CANNOT_EXECUTE_ON_DM: 50003,
|
||||||
|
|
@ -605,6 +640,7 @@ exports.APIErrors = {
|
||||||
INVALID_FORM_BODY: 50035,
|
INVALID_FORM_BODY: 50035,
|
||||||
INVITE_ACCEPTED_TO_GUILD_NOT_CONTAINING_BOT: 50036,
|
INVITE_ACCEPTED_TO_GUILD_NOT_CONTAINING_BOT: 50036,
|
||||||
INVALID_API_VERSION: 50041,
|
INVALID_API_VERSION: 50041,
|
||||||
|
CANNOT_DELETE_COMMUNITY_REQUIRED_CHANNEL: 50074,
|
||||||
REACTION_BLOCKED: 90001,
|
REACTION_BLOCKED: 90001,
|
||||||
RESOURCE_OVERLOADED: 130000,
|
RESOURCE_OVERLOADED: 130000,
|
||||||
};
|
};
|
||||||
|
|
@ -643,6 +679,33 @@ exports.WebhookTypes = [
|
||||||
'Channel Follower',
|
'Channel Follower',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
exports.ApplicationCommandOptionType = {
|
||||||
|
SUB_COMMAND: 1,
|
||||||
|
SUB_COMMAND_GROUP: 2,
|
||||||
|
STRING: 3,
|
||||||
|
INTEGER: 4,
|
||||||
|
BOOLEAN: 5,
|
||||||
|
USER: 6,
|
||||||
|
CHANNEL: 7,
|
||||||
|
ROLE: 8,
|
||||||
|
};
|
||||||
|
Object.entries(exports.ApplicationCommandOptionType).forEach(([k, v]) => {
|
||||||
|
exports.ApplicationCommandOptionType[v] = k;
|
||||||
|
});
|
||||||
|
|
||||||
|
exports.InteractionType = {
|
||||||
|
PING: 1,
|
||||||
|
APPLICATION_COMMAND: 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.InteractionResponseType = {
|
||||||
|
PONG: 1,
|
||||||
|
ACKNOWLEDGE: 2,
|
||||||
|
CHANNEL_MESSAGE: 3,
|
||||||
|
CHANNEL_MESSAGE_WITH_SOURCE: 4,
|
||||||
|
ACKNOWLEDGE_WITH_SOURCE: 5,
|
||||||
|
};
|
||||||
|
|
||||||
function keyMirror(arr) {
|
function keyMirror(arr) {
|
||||||
let tmp = Object.create(null);
|
let tmp = Object.create(null);
|
||||||
for (const value of arr) tmp[value] = value;
|
for (const value of arr) tmp[value] = value;
|
||||||
|
|
|
||||||
|
|
@ -24,16 +24,40 @@ class DataResolver {
|
||||||
* @typedef {string} InviteResolvable
|
* @typedef {string} InviteResolvable
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data that can be resolved to give an template code. This can be:
|
||||||
|
* * A template code
|
||||||
|
* * A template URL
|
||||||
|
* @typedef {string} GuildTemplateResolvable
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves the string to a code based on the passed regex.
|
||||||
|
* @param {string} data The string to resolve
|
||||||
|
* @param {RegExp} regex The RegExp used to extract the code
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
static resolveCode(data, regex) {
|
||||||
|
const match = regex.exec(data);
|
||||||
|
return match ? match[1] || data : data;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves InviteResolvable to an invite code.
|
* Resolves InviteResolvable to an invite code.
|
||||||
* @param {InviteResolvable} data The invite resolvable to resolve
|
* @param {InviteResolvable} data The invite resolvable to resolve
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
static resolveInviteCode(data) {
|
static resolveInviteCode(data) {
|
||||||
const inviteRegex = /discord(?:(?:app)?\.com\/invite|\.gg(?:\/invite)?)\/([\w-]{2,255})/i;
|
return this.resolveCode(data, /discord(?:(?:app)?\.com\/invite|\.gg(?:\/invite)?)\/([\w-]{2,255})/i);
|
||||||
const match = inviteRegex.exec(data);
|
}
|
||||||
if (match && match[1]) return match[1];
|
|
||||||
return data;
|
/**
|
||||||
|
* Resolves GuildTemplateResolvable to a template code.
|
||||||
|
* @param {GuildTemplateResolvable} data The template resolvable to resolve
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
static resolveGuildTemplateCode(data) {
|
||||||
|
return this.resolveCode(data, /discord(?:app)?\.(?:com\/template|new)\/([\w-]{2,255})/i);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ class MessageFlags extends BitField {}
|
||||||
* * `SUPPRESS_EMBEDS`
|
* * `SUPPRESS_EMBEDS`
|
||||||
* * `SOURCE_MESSAGE_DELETED`
|
* * `SOURCE_MESSAGE_DELETED`
|
||||||
* * `URGENT`
|
* * `URGENT`
|
||||||
|
* * `EPHEMERAL`
|
||||||
* @type {Object}
|
* @type {Object}
|
||||||
* @see {@link https://discord.com/developers/docs/resources/channel#message-object-message-flags}
|
* @see {@link https://discord.com/developers/docs/resources/channel#message-object-message-flags}
|
||||||
*/
|
*/
|
||||||
|
|
@ -31,6 +32,7 @@ MessageFlags.FLAGS = {
|
||||||
SUPPRESS_EMBEDS: 1 << 2,
|
SUPPRESS_EMBEDS: 1 << 2,
|
||||||
SOURCE_MESSAGE_DELETED: 1 << 3,
|
SOURCE_MESSAGE_DELETED: 1 << 3,
|
||||||
URGENT: 1 << 4,
|
URGENT: 1 << 4,
|
||||||
|
EPHEMERAL: 1 << 6,
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = MessageFlags;
|
module.exports = MessageFlags;
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,6 @@ class SnowflakeUtil {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (INCREMENT >= 4095) INCREMENT = 0;
|
if (INCREMENT >= 4095) INCREMENT = 0;
|
||||||
// eslint-disable-next-line max-len
|
|
||||||
const BINARY = `${(timestamp - EPOCH).toString(2).padStart(42, '0')}0000100000${(INCREMENT++)
|
const BINARY = `${(timestamp - EPOCH).toString(2).padStart(42, '0')}0000100000${(INCREMENT++)
|
||||||
.toString(2)
|
.toString(2)
|
||||||
.padStart(12, '0')}`;
|
.padStart(12, '0')}`;
|
||||||
|
|
@ -80,6 +79,15 @@ class SnowflakeUtil {
|
||||||
});
|
});
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Discord's epoch value (2015-01-01T00:00:00.000Z).
|
||||||
|
* @type {number}
|
||||||
|
* @readonly
|
||||||
|
*/
|
||||||
|
static get EPOCH() {
|
||||||
|
return EPOCH;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = SnowflakeUtil;
|
module.exports = SnowflakeUtil;
|
||||||
|
|
|
||||||
|
|
@ -5,16 +5,19 @@ const libs = {
|
||||||
open: sodium.api.crypto_secretbox_open_easy,
|
open: sodium.api.crypto_secretbox_open_easy,
|
||||||
close: sodium.api.crypto_secretbox_easy,
|
close: sodium.api.crypto_secretbox_easy,
|
||||||
random: n => sodium.randombytes_buf(n),
|
random: n => sodium.randombytes_buf(n),
|
||||||
|
verify: sodium.api.crypto_sign_verify_detached,
|
||||||
}),
|
}),
|
||||||
'libsodium-wrappers': sodium => ({
|
'libsodium-wrappers': sodium => ({
|
||||||
open: sodium.crypto_secretbox_open_easy,
|
open: sodium.crypto_secretbox_open_easy,
|
||||||
close: sodium.crypto_secretbox_easy,
|
close: sodium.crypto_secretbox_easy,
|
||||||
random: n => sodium.randombytes_buf(n),
|
random: n => sodium.randombytes_buf(n),
|
||||||
|
verify: sodium.crypto_sign_verify_detached,
|
||||||
}),
|
}),
|
||||||
tweetnacl: tweetnacl => ({
|
tweetnacl: tweetnacl => ({
|
||||||
open: tweetnacl.secretbox.open,
|
open: tweetnacl.secretbox.open,
|
||||||
close: tweetnacl.secretbox,
|
close: tweetnacl.secretbox,
|
||||||
random: n => tweetnacl.randomBytes(n),
|
random: n => tweetnacl.randomBytes(n),
|
||||||
|
verify: (s, d, p) => tweetnacl.sign.detached.verify(d, s, p),
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -17,7 +17,8 @@ class UserFlags extends BitField {}
|
||||||
/**
|
/**
|
||||||
* Numeric user flags. All available properties:
|
* Numeric user flags. All available properties:
|
||||||
* * `DISCORD_EMPLOYEE`
|
* * `DISCORD_EMPLOYEE`
|
||||||
* * `DISCORD_PARTNER`
|
* * `PARTNERED_SERVER_OWNER`
|
||||||
|
* * `DISCORD_PARTNER` **(deprecated)**
|
||||||
* * `HYPESQUAD_EVENTS`
|
* * `HYPESQUAD_EVENTS`
|
||||||
* * `BUGHUNTER_LEVEL_1`
|
* * `BUGHUNTER_LEVEL_1`
|
||||||
* * `HOUSE_BRAVERY`
|
* * `HOUSE_BRAVERY`
|
||||||
|
|
@ -28,12 +29,14 @@ class UserFlags extends BitField {}
|
||||||
* * `SYSTEM`
|
* * `SYSTEM`
|
||||||
* * `BUGHUNTER_LEVEL_2`
|
* * `BUGHUNTER_LEVEL_2`
|
||||||
* * `VERIFIED_BOT`
|
* * `VERIFIED_BOT`
|
||||||
* * `VERIFIED_DEVELOPER`
|
* * `EARLY_VERIFIED_BOT_DEVELOPER`
|
||||||
|
* * `VERIFIED_DEVELOPER` **(deprecated)**
|
||||||
* @type {Object}
|
* @type {Object}
|
||||||
* @see {@link https://discord.com/developers/docs/resources/user#user-object-user-flags}
|
* @see {@link https://discord.com/developers/docs/resources/user#user-object-user-flags}
|
||||||
*/
|
*/
|
||||||
UserFlags.FLAGS = {
|
UserFlags.FLAGS = {
|
||||||
DISCORD_EMPLOYEE: 1 << 0,
|
DISCORD_EMPLOYEE: 1 << 0,
|
||||||
|
PARTNERED_SERVER_OWNER: 1 << 1,
|
||||||
DISCORD_PARTNER: 1 << 1,
|
DISCORD_PARTNER: 1 << 1,
|
||||||
HYPESQUAD_EVENTS: 1 << 2,
|
HYPESQUAD_EVENTS: 1 << 2,
|
||||||
BUGHUNTER_LEVEL_1: 1 << 3,
|
BUGHUNTER_LEVEL_1: 1 << 3,
|
||||||
|
|
@ -45,6 +48,7 @@ UserFlags.FLAGS = {
|
||||||
SYSTEM: 1 << 12,
|
SYSTEM: 1 << 12,
|
||||||
BUGHUNTER_LEVEL_2: 1 << 14,
|
BUGHUNTER_LEVEL_2: 1 << 14,
|
||||||
VERIFIED_BOT: 1 << 16,
|
VERIFIED_BOT: 1 << 16,
|
||||||
|
EARLY_VERIFIED_DEVELOPER: 1 << 17,
|
||||||
VERIFIED_DEVELOPER: 1 << 17,
|
VERIFIED_DEVELOPER: 1 << 17,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -244,6 +244,7 @@ class Util {
|
||||||
})
|
})
|
||||||
.then(res => {
|
.then(res => {
|
||||||
if (res.ok) return res.json();
|
if (res.ok) return res.json();
|
||||||
|
if (res.status === 401) throw new DiscordError('TOKEN_INVALID');
|
||||||
throw res;
|
throw res;
|
||||||
})
|
})
|
||||||
.then(data => data.shards * (1000 / guildsPerShard));
|
.then(data => data.shards * (1000 / guildsPerShard));
|
||||||
|
|
|
||||||
31
test/createGuild.js
Normal file
31
test/createGuild.js
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const assert = require('assert');
|
||||||
|
const { token } = require('./auth');
|
||||||
|
const { Client } = require('../src');
|
||||||
|
|
||||||
|
const client = new Client();
|
||||||
|
|
||||||
|
client.on('ready', async () => {
|
||||||
|
try {
|
||||||
|
const guild = await client.guilds.create('testing', {
|
||||||
|
channels: [
|
||||||
|
{ name: 'afk channel', type: 'voice', id: 0 },
|
||||||
|
{ name: 'system-channel', id: 1 },
|
||||||
|
],
|
||||||
|
afkChannelID: 0,
|
||||||
|
afkTimeout: 60,
|
||||||
|
systemChannelID: 1,
|
||||||
|
});
|
||||||
|
console.log(guild.id);
|
||||||
|
assert.strictEqual(guild.afkChannel.name, 'afk channel');
|
||||||
|
assert.strictEqual(guild.afkTimeout, 60);
|
||||||
|
assert.strictEqual(guild.systemChannel.name, 'system-channel');
|
||||||
|
await guild.delete();
|
||||||
|
client.destroy();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
client.login(token).catch(console.error);
|
||||||
|
|
@ -36,9 +36,9 @@ client.on('message', message => {
|
||||||
// Clean content and log each character
|
// Clean content and log each character
|
||||||
console.log(Util.cleanContent(args.join(' '), message).split(''));
|
console.log(Util.cleanContent(args.join(' '), message).split(''));
|
||||||
|
|
||||||
if (command === 'test1') message.reply(tests[0]);
|
if (command === 'test1') message.channel.send(tests[0]);
|
||||||
else if (command === 'test2') message.reply(tests[1]);
|
else if (command === 'test2') message.channel.send(tests[1]);
|
||||||
else if (command === 'test3') message.reply(tests[2]);
|
else if (command === 'test3') message.channel.send(tests[2]);
|
||||||
});
|
});
|
||||||
|
|
||||||
client.login(token).catch(console.error);
|
client.login(token).catch(console.error);
|
||||||
|
|
|
||||||
|
|
@ -116,7 +116,8 @@ client.on('message', message => {
|
||||||
|
|
||||||
if (message.content.startsWith('kick')) {
|
if (message.content.startsWith('kick')) {
|
||||||
message.guild
|
message.guild
|
||||||
.member(message.mentions.users.first())
|
.members
|
||||||
|
.resolve(message.mentions.users.first())
|
||||||
.kick()
|
.kick()
|
||||||
.then(member => {
|
.then(member => {
|
||||||
console.log(member);
|
console.log(member);
|
||||||
|
|
@ -134,7 +135,7 @@ client.on('message', message => {
|
||||||
}
|
}
|
||||||
message.channel.send('last one...').then(m => {
|
message.channel.send('last one...').then(m => {
|
||||||
const diff = Date.now() - start;
|
const diff = Date.now() - start;
|
||||||
m.reply(`Each message took ${diff / 21}ms to send`);
|
m.channel.send(`Each message took ${diff / 21}ms to send`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -205,7 +206,7 @@ client.on('message', msg => {
|
||||||
.join()
|
.join()
|
||||||
.then(conn => {
|
.then(conn => {
|
||||||
con = conn;
|
con = conn;
|
||||||
msg.reply('done');
|
msg.channel.send('done');
|
||||||
const s = ytdl(song, { filter: 'audioonly' }, { passes: 3 });
|
const s = ytdl(song, { filter: 'audioonly' }, { passes: 3 });
|
||||||
s.on('error', e => console.log(`e w stream 2 ${e}`));
|
s.on('error', e => console.log(`e w stream 2 ${e}`));
|
||||||
disp = conn.playStream(s);
|
disp = conn.playStream(s);
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,6 @@ const tests = [
|
||||||
|
|
||||||
m => m.channel.send(fill('x'), { split: true }),
|
m => m.channel.send(fill('x'), { split: true }),
|
||||||
m => m.channel.send(fill('1'), { code: 'js', split: true }),
|
m => m.channel.send(fill('1'), { code: 'js', split: true }),
|
||||||
m => m.channel.send(fill('x'), { reply: m.author, code: 'js', split: true }),
|
|
||||||
m => m.channel.send(fill('xyz '), { split: { char: ' ' } }),
|
m => m.channel.send(fill('xyz '), { split: { char: ' ' } }),
|
||||||
|
|
||||||
m => m.channel.send('x', { embed: { description: 'a' } }),
|
m => m.channel.send('x', { embed: { description: 'a' } }),
|
||||||
|
|
@ -99,7 +98,6 @@ const tests = [
|
||||||
async m => m.channel.send({ files: [await read(fileA)] }),
|
async m => m.channel.send({ files: [await read(fileA)] }),
|
||||||
async m =>
|
async m =>
|
||||||
m.channel.send(fill('x'), {
|
m.channel.send(fill('x'), {
|
||||||
reply: m.author,
|
|
||||||
code: 'js',
|
code: 'js',
|
||||||
split: true,
|
split: true,
|
||||||
embed: embed().setImage('attachment://zero.png'),
|
embed: embed().setImage('attachment://zero.png'),
|
||||||
|
|
@ -111,7 +109,6 @@ const tests = [
|
||||||
m => m.channel.send({ files: [{ attachment: readStream(fileA) }] }),
|
m => m.channel.send({ files: [{ attachment: readStream(fileA) }] }),
|
||||||
async m =>
|
async m =>
|
||||||
m.channel.send(fill('xyz '), {
|
m.channel.send(fill('xyz '), {
|
||||||
reply: m.author,
|
|
||||||
code: 'js',
|
code: 'js',
|
||||||
split: { char: ' ', prepend: 'hello! ', append: '!!!' },
|
split: { char: ' ', prepend: 'hello! ', append: '!!!' },
|
||||||
embed: embed().setImage('attachment://zero.png'),
|
embed: embed().setImage('attachment://zero.png'),
|
||||||
|
|
|
||||||
|
|
@ -31,16 +31,13 @@ const commands = {
|
||||||
}
|
}
|
||||||
message.channel.send(res, { code: 'js' });
|
message.channel.send(res, { code: 'js' });
|
||||||
},
|
},
|
||||||
ping: message => message.reply('pong'),
|
ping: message => message.channel.send('pong'),
|
||||||
};
|
};
|
||||||
|
|
||||||
client.on('message', message => {
|
client.on('message', message => {
|
||||||
if (!message.content.startsWith(prefix) || message.author.bot) return;
|
if (!message.content.startsWith(prefix) || message.author.bot) return;
|
||||||
|
|
||||||
message.content = message.content
|
message.content = message.content.replace(prefix, '').trim().split(' ');
|
||||||
.replace(prefix, '')
|
|
||||||
.trim()
|
|
||||||
.split(' ');
|
|
||||||
const command = message.content.shift();
|
const command = message.content.shift();
|
||||||
message.content = message.content.join(' ');
|
message.content = message.content.join(' ');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,20 +43,15 @@ client.on('message', m => {
|
||||||
conn.receiver.createStream(m.author, true).on('data', b => console.log(b.toString()));
|
conn.receiver.createStream(m.author, true).on('data', b => console.log(b.toString()));
|
||||||
conn.player.on('error', (...e) => console.log('player', ...e));
|
conn.player.on('error', (...e) => console.log('player', ...e));
|
||||||
if (!connections.has(m.guild.id)) connections.set(m.guild.id, { conn, queue: [] });
|
if (!connections.has(m.guild.id)) connections.set(m.guild.id, { conn, queue: [] });
|
||||||
m.reply('ok!');
|
m.channel.send('ok!');
|
||||||
conn.play(ytdl('https://www.youtube.com/watch?v=_XXOSf0s2nk', { filter: 'audioonly' }, { passes: 3 }));
|
conn.play(ytdl('https://www.youtube.com/watch?v=_XXOSf0s2nk', { filter: 'audioonly' }, { passes: 3 }));
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
m.reply('Specify a voice channel!');
|
m.channel.send('Specify a voice channel!');
|
||||||
}
|
}
|
||||||
} else if (m.content.startsWith('#eval') && m.author.id === '66564597481480192') {
|
} else if (m.content.startsWith('#eval') && m.author.id === '66564597481480192') {
|
||||||
try {
|
try {
|
||||||
const com = eval(
|
const com = eval(m.content.split(' ').slice(1).join(' '));
|
||||||
m.content
|
|
||||||
.split(' ')
|
|
||||||
.slice(1)
|
|
||||||
.join(' '),
|
|
||||||
);
|
|
||||||
m.channel.send(com, { code: true });
|
m.channel.send(com, { code: true });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,6 @@ const tests = [
|
||||||
|
|
||||||
(m, hook) => hook.send(fill('x'), { split: true }),
|
(m, hook) => hook.send(fill('x'), { split: true }),
|
||||||
(m, hook) => hook.send(fill('1'), { code: 'js', split: true }),
|
(m, hook) => hook.send(fill('1'), { code: 'js', split: true }),
|
||||||
(m, hook) => hook.send(fill('x'), { reply: m.author, code: 'js', split: true }),
|
|
||||||
(m, hook) => hook.send(fill('xyz '), { split: { char: ' ' } }),
|
(m, hook) => hook.send(fill('xyz '), { split: { char: ' ' } }),
|
||||||
|
|
||||||
(m, hook) => hook.send({ embeds: [{ description: 'a' }] }),
|
(m, hook) => hook.send({ embeds: [{ description: 'a' }] }),
|
||||||
|
|
@ -96,7 +95,6 @@ const tests = [
|
||||||
async (m, hook) => hook.send({ files: [await read(fileA)] }),
|
async (m, hook) => hook.send({ files: [await read(fileA)] }),
|
||||||
async (m, hook) =>
|
async (m, hook) =>
|
||||||
hook.send(fill('x'), {
|
hook.send(fill('x'), {
|
||||||
reply: m.author,
|
|
||||||
code: 'js',
|
code: 'js',
|
||||||
split: true,
|
split: true,
|
||||||
embeds: [embed().setImage('attachment://zero.png')],
|
embeds: [embed().setImage('attachment://zero.png')],
|
||||||
|
|
@ -108,7 +106,6 @@ const tests = [
|
||||||
(m, hook) => hook.send({ files: [{ attachment: readStream(fileA) }] }),
|
(m, hook) => hook.send({ files: [{ attachment: readStream(fileA) }] }),
|
||||||
async (m, hook) =>
|
async (m, hook) =>
|
||||||
hook.send(fill('xyz '), {
|
hook.send(fill('xyz '), {
|
||||||
reply: m.author,
|
|
||||||
code: 'js',
|
code: 'js',
|
||||||
split: { char: ' ', prepend: 'hello! ', append: '!!!' },
|
split: { char: ' ', prepend: 'hello! ', append: '!!!' },
|
||||||
embeds: [embed().setImage('attachment://zero.png')],
|
embeds: [embed().setImage('attachment://zero.png')],
|
||||||
|
|
|
||||||
557
typings/index.d.ts
vendored
557
typings/index.d.ts
vendored
File diff suppressed because it is too large
Load diff
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue