Compare commits
1 Commits
feat/appli
...
@discordjs
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d63bcbb9d4 |
@@ -1,13 +1,11 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/commitlintrc.json",
|
||||
"extends": ["@commitlint/config-angular"],
|
||||
"rules": {
|
||||
"type-enum": [
|
||||
2,
|
||||
"always",
|
||||
["chore", "build", "ci", "docs", "feat", "fix", "perf", "refactor", "revert", "style", "test", "types"]
|
||||
["chore", "build", "ci", "docs", "feat", "fix", "perf", "refactor", "revert", "style", "test", "types", "typings"]
|
||||
],
|
||||
"scope-case": [0],
|
||||
"subject-exclamation-mark": [0]
|
||||
"scope-case": [0]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +1,31 @@
|
||||
# Packages
|
||||
**/node_modules
|
||||
node_modules/
|
||||
|
||||
# Log files
|
||||
**/logs
|
||||
**/*.log
|
||||
**/npm-debug.log*
|
||||
logs/
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Runtime data
|
||||
**/pids
|
||||
**/*.pid
|
||||
**/*.seed
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
|
||||
# Env
|
||||
**/.env
|
||||
.env
|
||||
|
||||
# Dist
|
||||
**/dist/
|
||||
**/dist-docs/
|
||||
dist/
|
||||
|
||||
# Miscellaneous
|
||||
**/.tmp
|
||||
**/.vscode
|
||||
**/.idea
|
||||
**/.DS_Store
|
||||
**/.turbo
|
||||
**/tsconfig.tsbuildinfo
|
||||
**/coverage
|
||||
**/__tests__
|
||||
**/out
|
||||
.tmp/
|
||||
.vscode/*
|
||||
.idea/
|
||||
.DS_Store
|
||||
.turbo
|
||||
tsconfig.tsbuildinfo
|
||||
coverage/
|
||||
__tests__/
|
||||
|
||||
# yarn
|
||||
.pnp.*
|
||||
@@ -39,29 +37,20 @@
|
||||
!.yarn/versions
|
||||
|
||||
# Cache
|
||||
**/.prettiercache
|
||||
**/.eslintcache
|
||||
**/.vercel
|
||||
.prettiercache
|
||||
.eslintcache
|
||||
|
||||
# Docker specific
|
||||
**/.cliff-jumperrc.json
|
||||
**/api-extractor.json
|
||||
**/api-extractor-docs.json
|
||||
**/.eslintignore
|
||||
**/.eslintrc.json
|
||||
**/.lintstagedrc.js
|
||||
**/.lintstagedrc.cjs
|
||||
**/.lintstagedrc.json
|
||||
**/.prettierignore
|
||||
**/.prettierrc.js
|
||||
**/.prettierrc.cjs
|
||||
**/.prettierrc.json
|
||||
**/cliff.toml
|
||||
**/CHANGELOG.md
|
||||
**/README.md
|
||||
**/LICENSE
|
||||
**/tsconfig.eslint.json
|
||||
**/tsconfig.docs.json
|
||||
**/docs/
|
||||
**/vitest.config.ts
|
||||
|
||||
.cliff-jumperrc.json
|
||||
api-extractor.json
|
||||
.eslintrc.json
|
||||
.lintstagedrc.cjs
|
||||
.lintstagedrc.cjs
|
||||
.prettierignore
|
||||
.prettierrc.js
|
||||
.prettierrc.cjs
|
||||
cliff.toml
|
||||
CHANGELOG.md
|
||||
README.md
|
||||
tsconfig.eslint.json
|
||||
docs/
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/eslintrc.json",
|
||||
"root": true,
|
||||
"extends": ["neon/common", "neon/node", "neon/typescript", "neon/prettier"],
|
||||
"parserOptions": {
|
||||
"project": ["./tsconfig.eslint.json"]
|
||||
"project": "./tsconfig.eslint.json"
|
||||
},
|
||||
"rules": {
|
||||
"@typescript-eslint/consistent-type-definitions": ["error", "interface"]
|
||||
},
|
||||
"ignorePatterns": ["**/dist/*"],
|
||||
"rules": {
|
||||
"import/extensions": 0
|
||||
"env": {
|
||||
"jest": true
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
# .git-blame-ignore-revs
|
||||
# switched to eslint-config-neon for mainlib discord.js
|
||||
b03c65c34c6e8bab7f97d507d6ccd7c441a14360
|
||||
1
.gitattributes
vendored
@@ -1,2 +1 @@
|
||||
* text=auto eol=lf
|
||||
pnpm-lock.yaml linguist-generated=true text=auto eol=lf
|
||||
|
||||
2
.github/.kodiak.toml
vendored
@@ -2,7 +2,7 @@ version = 1
|
||||
|
||||
[merge]
|
||||
require_automerge_label = false
|
||||
blocking_labels = ['blocked', 'in review', 'semver:major']
|
||||
blocking_labels = ['blocked']
|
||||
method = 'squash'
|
||||
|
||||
[merge.message]
|
||||
|
||||
34
.github/CODEOWNERS
vendored
@@ -1,34 +0,0 @@
|
||||
# Learn how to add code owners here:
|
||||
# https://help.github.com/articles/about-code-owners
|
||||
|
||||
* @iCrawl
|
||||
|
||||
package.json @discordjs/core
|
||||
pnpm-lock.yaml @discordjs/core
|
||||
|
||||
/apps/guide/ @discordjs/website @discordjs/guide
|
||||
/apps/guide/content/ @discordjs/guide
|
||||
/apps/website/ @discordjs/website
|
||||
|
||||
/packages/actions/ @discordjs/actions
|
||||
/packages/api-extractor/ @discordjs/api-extractor-utils
|
||||
/packages/api-extractor-model/ @discordjs/api-extractor-utils
|
||||
/packages/api-extractor-utils/ @discordjs/api-extractor-utils
|
||||
/packages/brokers/ @discordjs/brokers
|
||||
/packages/builders/ @discordjs/builders
|
||||
/packages/collection/ @discordjs/collection
|
||||
/packages/core/ @discordjs/core
|
||||
/packages/create-discord-bot/ @discordjs/guide
|
||||
/packages/discord.js/ @discordjs/core
|
||||
/packages/docgen/ @iCrawl
|
||||
/packages/formatters/ @discordjs/formatters
|
||||
/packages/next/ @discordjs/core
|
||||
/packages/proxy/ @discordjs/proxy
|
||||
/packages/proxy-container/ @discordjs/proxy
|
||||
/packages/rest/ @discordjs/rest
|
||||
/packages/scripts/ @discordjs/scripts
|
||||
/packages/structures/ @discordjs/structures
|
||||
/packages/ui/ @discordjs/ui
|
||||
/packages/util/ @discordjs/util
|
||||
/packages/voice/ @discordjs/core
|
||||
/packages/ws/ @discordjs/ws
|
||||
3
.github/COMMIT_CONVENTION.md
vendored
@@ -7,7 +7,7 @@
|
||||
Messages must be matched by the following regex:
|
||||
|
||||
```js
|
||||
/^(revert: )?(feat|fix|docs|style|refactor|perf|test|build|ci|chore|types)(\(.+\))?!?: .{1,72}/;
|
||||
/^(revert: )?(feat|fix|docs|style|refactor|perf|test|workflow|build|ci|chore|types|wip)(\(.+\))?: .{1,72}/;
|
||||
```
|
||||
|
||||
#### Examples
|
||||
@@ -55,7 +55,6 @@ A commit message consists of a **header**, **body** and **footer**. The header h
|
||||
```
|
||||
|
||||
The **header** is mandatory and the **scope** of the header is optional.
|
||||
If the commit contains **Breaking Changes**, a `!` can be added before the `:` as an indicator.
|
||||
|
||||
### Revert
|
||||
|
||||
|
||||
29
.github/CONTRIBUTING.md
vendored
@@ -11,20 +11,22 @@ is a great boon to your development process.
|
||||
To get ready to work on the codebase, please do the following:
|
||||
|
||||
1. Fork & clone the repository, and make sure you're on the **main** branch
|
||||
2. Run `pnpm install --frozen-lockfile` ([install](https://pnpm.io/installation))
|
||||
3. Run `pnpm run build` to build local packages
|
||||
2. Run `yarn --immutable` ([install](https://yarnpkg.com/getting-started/install))
|
||||
3. Run `yarn build` to build local packages
|
||||
4. Code your heart out!
|
||||
5. Run `pnpm run test` to run ESLint and ensure any JSDoc changes are valid
|
||||
5. Run `yarn test` to run ESLint and ensure any JSDoc changes are valid
|
||||
6. [Submit a pull request](https://github.com/discordjs/discord.js/compare) (Make sure you follow the [conventional commit format](https://github.com/discordjs/discord.js/blob/main/.github/COMMIT_CONVENTION.md))
|
||||
|
||||
## Testing changes locally
|
||||
|
||||
If you want to test changes you've made locally, you can do so by using `pnpm link <package-you-want-to-link-to-your-current-package>`. This will create a symlink to your local copy of the discord.js libraries.
|
||||
If you want to test changes you've made locally, you can do so by using `yarn link`. This will create a symlink to your local copy of the discord.js libraries.
|
||||
|
||||
1. Create a new directory `mkdir discordjs-test` and move into it `cd discordjs-test`
|
||||
2. Initialize a new pnpm project `pnpm init`
|
||||
3. Now link the discord.js package from the directory you cloned earlier `pnpm link {PATH_TO_DISCORDJS_REPO}/packages/<package>`. (e.g. `pnpm link ~/discord.js/packages/rest`)
|
||||
4. Import the package in your source code and test them out!
|
||||
2. Initialize a new yarn 3 project `yarn init -2`
|
||||
3. Disable pnp `yarn config set nodeLinker node-modules`
|
||||
4. Now link the local discord.js project you cloned earlier `yarn link -A {PATH_TO_DISCORDJS_REPO}`
|
||||
5. Install packages you'd like to test locally `yarn add discord.js@latest`, `yarn add @discordjs/rest@latest`, etc. **Note: Make sure you use `latest` tag or else yarn will try to install the remote package from npm**
|
||||
6. Import the package in your source code and test them out!
|
||||
|
||||
### Working with TypeScript packages
|
||||
|
||||
@@ -32,18 +34,15 @@ When testing local changes, you may notice you need to manually recompile TypeSc
|
||||
|
||||
To avoid this you can use the `--watch` parameter in the package build script to automatically recompile the project when changes are detected.
|
||||
|
||||
For example, to automatically recompile the `@discordjs/rest` project when changes are detected, run `pnpm turbo run build --filter='@discordjs/rest' -- --watch` in the root folder of where you cloned the discord.js repo.
|
||||
For example, to automatically recompile the `@discordjs/rest` project when changes are detected, run `yarn turbo run build --filter=@discordjs/rest -- --watch` in the root folder of where you cloned the discord.js repo.
|
||||
|
||||
## Adding new packages
|
||||
|
||||
If you'd like to create another package under the `@discordjs` organization run the following command:
|
||||
|
||||
```sh
|
||||
pnpm run create-package <package-name> [package-description]
|
||||
```bash
|
||||
yarn create-package <package-name> [package-description]
|
||||
```
|
||||
|
||||
This will create new package directory under `packages/` with the required configuration files. You may begin
|
||||
to make changes within the `src/` directory. You may also need to:
|
||||
|
||||
- Update workflows that utilize packages
|
||||
- Update the CODEOWNERS file
|
||||
This will create new package directory under `packages/` with the required configuration files. You can
|
||||
begin to make changes within the `src/` directory.
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
name: Websites bug report
|
||||
description: Report an issue with the documentation or guide websites.
|
||||
labels: [bug, need repro]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thank you for filing an issue! If you are here to ask a question, use Discord instead: https://discord.gg/djs
|
||||
|
||||
This issue form is for our documentation and guide websites.
|
||||
- type: dropdown
|
||||
id: application
|
||||
attributes:
|
||||
label: Which application is this bug report for?
|
||||
options:
|
||||
- Documentation
|
||||
- Guide
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Issue description
|
||||
description: Describe the issue in as much detail as possible.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: steps_to_reproduce
|
||||
attributes:
|
||||
label: Steps to Reproduce
|
||||
description: What steps must be taken to reproduce this issue?
|
||||
placeholder: |
|
||||
1. Visit a page
|
||||
2. Click a link
|
||||
3. ...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: versions
|
||||
attributes:
|
||||
label: Versions
|
||||
description: List necessary versions here. This includes your browser, operating system etc.
|
||||
placeholder: |
|
||||
- Safari 16.4 (18615.1.26.11.23)
|
||||
- macOS Ventura 13.3.1
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: priority
|
||||
attributes:
|
||||
label: Issue priority
|
||||
description: Please be realistic. If you need to elaborate on your reasoning, please use the issue description field above.
|
||||
options:
|
||||
- Low (slightly annoying)
|
||||
- Medium (should be fixed soon)
|
||||
- High (immediate attention needed)
|
||||
validations:
|
||||
required: true
|
||||
@@ -1,13 +1,11 @@
|
||||
name: Bug report
|
||||
description: Report an issue with discord.js or another package.
|
||||
description: Report incorrect or unexpected behavior of a package
|
||||
labels: [bug, need repro]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thank you for filing an issue! If you are here to ask a question, use Discord instead: https://discord.gg/djs
|
||||
|
||||
This issue form is for discord.js, including other packages.
|
||||
Use Discord for questions: https://discord.gg/djs
|
||||
- type: dropdown
|
||||
id: package
|
||||
attributes:
|
||||
@@ -18,15 +16,10 @@ body:
|
||||
- builders
|
||||
- collection
|
||||
- core
|
||||
- create-discord-bot
|
||||
- formatters
|
||||
- next
|
||||
- proxy
|
||||
- proxy-container
|
||||
- rest
|
||||
- structures
|
||||
- ui
|
||||
- util
|
||||
- voice
|
||||
- ws
|
||||
validations:
|
||||
@@ -35,44 +28,57 @@ body:
|
||||
id: description
|
||||
attributes:
|
||||
label: Issue description
|
||||
description: Describe the issue in as much detail as possible.
|
||||
description: |
|
||||
Describe the issue in as much detail as possible.
|
||||
|
||||
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files into it.
|
||||
placeholder: |
|
||||
Steps to reproduce with below code sample:
|
||||
1. Do thing
|
||||
2. Do thing in Discord client
|
||||
3. Observe behavior
|
||||
4. See error logs below
|
||||
1. do thing
|
||||
2. do thing in Discord client
|
||||
3. observe behavior
|
||||
4. see error logs below
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: code_sample
|
||||
id: codesample
|
||||
attributes:
|
||||
label: Code sample
|
||||
description: |
|
||||
Your code sample should be:
|
||||
1. Minimal - Use as little code as possible that still produces the same problem (and is understandable)
|
||||
2. Complete - Provide all parts someone else needs to reproduce your problem
|
||||
3. Reproducible - Test the code you're about to provide to make sure it reproduces the problem
|
||||
|
||||
This will be automatically formatted into code, so no need for backticks.
|
||||
description: Include a reproducible, minimal code sample. This will be automatically formatted into code, so no need for backticks.
|
||||
render: typescript
|
||||
- type: textarea
|
||||
id: versions
|
||||
attributes:
|
||||
label: Versions
|
||||
description: List necessary versions here. This includes your package version, runtime version, operating system etc.
|
||||
placeholder: |
|
||||
- discord.js 14.12.1 (`npm ls discord.js` or another package)
|
||||
- Node.js 22.12.0 (`node --version`)
|
||||
- TypeScript 5.1.6 (`npm ls typescript` if you use it)
|
||||
- macOS Ventura 13.3.1
|
||||
Your code sample should be...
|
||||
... Minimal - Use as little code as possible that still produces the same problem (and is understandable)
|
||||
... Complete - Provide all parts someone else needs to reproduce your problem
|
||||
... Reproducible - Test the code you're about to provide to make sure it reproduces the problem
|
||||
- type: input
|
||||
id: djs-version
|
||||
attributes:
|
||||
label: Package version
|
||||
description: Which version of the package are you using? Run `npm list <package>` in your project directory and paste the output.
|
||||
placeholder: We no longer support version 12 or earlier of discord.js
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: node-version
|
||||
attributes:
|
||||
label: Node.js version
|
||||
description: |
|
||||
Which version of Node.js are you using? Run `node --version` in your project directory and paste the output.
|
||||
If you are using TypeScript, please include its version (`npm list typescript`) as well.
|
||||
placeholder: Node.js version 16.9+ is required for version 14.0.0+
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: os
|
||||
attributes:
|
||||
label: Operating system
|
||||
description: Which OS does your application run on?
|
||||
- type: dropdown
|
||||
id: priority
|
||||
attributes:
|
||||
label: Issue priority
|
||||
description: Please be realistic. If you need to elaborate on your reasoning, please use the issue description field above.
|
||||
label: Priority this issue should have
|
||||
description: Please be realistic. If you need to elaborate on your reasoning, please use the Issue description field above.
|
||||
options:
|
||||
- Low (slightly annoying)
|
||||
- Medium (should be fixed soon)
|
||||
@@ -83,9 +89,12 @@ body:
|
||||
id: partials
|
||||
attributes:
|
||||
label: Which partials do you have configured?
|
||||
description: Check your `Client` constructor for the `partials` key.
|
||||
description: |
|
||||
Check your Client constructor for the `partials` key.
|
||||
|
||||
Tip: you can select multiple items
|
||||
options:
|
||||
- Not applicable
|
||||
- Not applicable (subpackage bug)
|
||||
- No Partials
|
||||
- User
|
||||
- Channel
|
||||
@@ -94,9 +103,6 @@ body:
|
||||
- Reaction
|
||||
- GuildScheduledEvent
|
||||
- ThreadMember
|
||||
- Poll
|
||||
- PollAnswer
|
||||
- SoundboardSound
|
||||
multiple: true
|
||||
validations:
|
||||
required: true
|
||||
@@ -104,14 +110,17 @@ body:
|
||||
id: intents
|
||||
attributes:
|
||||
label: Which gateway intents are you subscribing to?
|
||||
description: Check your `Client` constructor for the `intents` key.
|
||||
description: |
|
||||
Check your Client constructor options for the `intents` key.
|
||||
|
||||
Tip: you can select multiple items
|
||||
options:
|
||||
- Not applicable
|
||||
- Not applicable (subpackage bug)
|
||||
- No Intents
|
||||
- Guilds
|
||||
- GuildMembers
|
||||
- GuildModeration
|
||||
- GuildExpressions
|
||||
- GuildBans
|
||||
- GuildEmojisAndStickers
|
||||
- GuildIntegrations
|
||||
- GuildWebhooks
|
||||
- GuildInvites
|
||||
@@ -127,13 +136,11 @@ body:
|
||||
- GuildScheduledEvents
|
||||
- AutoModerationConfiguration
|
||||
- AutoModerationExecution
|
||||
- GuildMessagePolls
|
||||
- DirectMessagePolls
|
||||
multiple: true
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: dev_release
|
||||
id: dev-release
|
||||
attributes:
|
||||
label: I have tested this issue on a development release
|
||||
placeholder: d23280c (commit hash)
|
||||
@@ -9,26 +9,19 @@ body:
|
||||
We do not implement unreleased features.
|
||||
Use Discord for questions: https://discord.gg/djs
|
||||
- type: dropdown
|
||||
id: application_or_package
|
||||
id: package
|
||||
attributes:
|
||||
label: Which application or package is this feature request for?
|
||||
label: Which package is the feature request for?
|
||||
options:
|
||||
- discord.js
|
||||
- Documentation
|
||||
- Guide
|
||||
- brokers
|
||||
- builders
|
||||
- collection
|
||||
- core
|
||||
- create-discord-bot
|
||||
- formatters
|
||||
- next
|
||||
- proxy
|
||||
- proxy-container
|
||||
- rest
|
||||
- structures
|
||||
- ui
|
||||
- util
|
||||
- voice
|
||||
- ws
|
||||
validations:
|
||||
@@ -54,7 +47,7 @@ body:
|
||||
label: Alternative solutions or implementations
|
||||
description: A clear and concise description of any alternative solutions or features you have considered.
|
||||
- type: textarea
|
||||
id: additional_context
|
||||
id: additional-context
|
||||
attributes:
|
||||
label: Other context
|
||||
description: Any other context, screenshots, or file uploads that help us understand your feature request.
|
||||
8
.github/auto_assign.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
addReviewers: true
|
||||
reviewers:
|
||||
- iCrawl
|
||||
- SpaceEEC
|
||||
- kyranet
|
||||
- vladfrangu
|
||||
numberOfReviewers: 0
|
||||
runOnDraft: true
|
||||
54
.github/issue-labeler.yml
vendored
@@ -1,54 +0,0 @@
|
||||
apps:guide:
|
||||
- "### Which (application|package|application or package) is this (bug
|
||||
report|feature request) for\\?\\n\\nGuide\\n"
|
||||
apps:website:
|
||||
- "### Which (application|package|application or package) is this (bug
|
||||
report|feature request) for\\?\\n\\nDocumentation\\n"
|
||||
packages:brokers:
|
||||
- "### Which (application|package|application or package) is this (bug
|
||||
report|feature request) for\\?\\n\\nbrokers\\n"
|
||||
packages:builders:
|
||||
- "### Which (application|package|application or package) is this (bug
|
||||
report|feature request) for\\?\\n\\nbuilders\\n"
|
||||
packages:collection:
|
||||
- "### Which (application|package|application or package) is this (bug
|
||||
report|feature request) for\\?\\n\\ncollection\\n"
|
||||
packages:core:
|
||||
- "### Which (application|package|application or package) is this (bug
|
||||
report|feature request) for\\?\\n\\ncore\\n"
|
||||
packages:create-discord-bot:
|
||||
- "### Which (application|package|application or package) is this (bug
|
||||
report|feature request) for\\?\\n\\ncreate-discord-bot\\n"
|
||||
packages:discord.js:
|
||||
- "### Which (application|package|application or package) is this (bug
|
||||
report|feature request) for\\?\\n\\ndiscord.js\\n"
|
||||
packages:formatters:
|
||||
- "### Which (application|package|application or package) is this (bug
|
||||
report|feature request) for\\?\\n\\nformatters\\n"
|
||||
packages:next:
|
||||
- "### Which (application|package|application or package) is this (bug
|
||||
report|feature request) for\\?\\n\\nnext\\n"
|
||||
packages:proxy:
|
||||
- "### Which (application|package|application or package) is this (bug
|
||||
report|feature request) for\\?\\n\\nproxy\\n"
|
||||
packages:proxy-container:
|
||||
- "### Which (application|package|application or package) is this (bug
|
||||
report|feature request) for\\?\\n\\nproxy-container\\n"
|
||||
packages:rest:
|
||||
- "### Which (application|package|application or package) is this (bug
|
||||
report|feature request) for\\?\\n\\nrest\\n"
|
||||
packages:structures:
|
||||
- "### Which (application|package|application or package) is this (bug
|
||||
report|feature request) for\\?\\n\\nstructures\\n"
|
||||
packages:ui:
|
||||
- "### Which (application|package|application or package) is this (bug
|
||||
report|feature request) for\\?\\n\\ui\\n"
|
||||
packages:util:
|
||||
- "### Which (application|package|application or package) is this (bug
|
||||
report|feature request) for\\?\\n\\util\\n"
|
||||
packages:voice:
|
||||
- "### Which (application|package|application or package) is this (bug
|
||||
report|feature request) for\\?\\n\\nvoice\\n"
|
||||
packages:ws:
|
||||
- "### Which (application|package|application or package) is this (bug
|
||||
report|feature request) for\\?\\n\\nws\\n"
|
||||
121
.github/labeler.yml
vendored
@@ -1,105 +1,48 @@
|
||||
apps:guide:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- apps/guide/*
|
||||
- apps/guide/**/*
|
||||
- apps/guide/*
|
||||
- apps/guide/**/*
|
||||
apps:website:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- apps/website/*
|
||||
- apps/website/**/*
|
||||
packages:api-extractor:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- packages/api-extractor/*
|
||||
- packages/api-extractor/**/*
|
||||
packages:api-extractor-model:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- packages/api-extractor-model/*
|
||||
- packages/api-extractor-model/**/*
|
||||
- apps/website/*
|
||||
- apps/website/**/*
|
||||
packages:brokers:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- packages/brokers/*
|
||||
- packages/brokers/**/*
|
||||
- packages/brokers/*
|
||||
- packages/brokers/**/*
|
||||
packages:builders:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- packages/builders/*
|
||||
- packages/builders/**/*
|
||||
- packages/builders/*
|
||||
- packages/builders/**/*
|
||||
packages:collection:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- packages/collection/*
|
||||
- packages/collection/**/*
|
||||
- packages/collection/*
|
||||
- packages/collection/**/*
|
||||
packages:core:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- packages/core/*
|
||||
- packages/core/**/*
|
||||
packages:create-discord-bot:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- packages/create-discord-bot/*
|
||||
- packages/create-discord-bot/**/*
|
||||
- packages/core/*
|
||||
- packages/core/**/*
|
||||
packages:discord.js:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- packages/discord.js/*
|
||||
- packages/discord.js/**/*
|
||||
- packages/discord.js/*
|
||||
- packages/discord.js/**/*
|
||||
packages:docgen:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- packages/docgen/*
|
||||
- packages/docgen/**/*
|
||||
- packages/docgen/*
|
||||
- packages/docgen/**/*
|
||||
packages:formatters:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- packages/formatters/*
|
||||
- packages/formatters/**/*
|
||||
packages:next:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- packages/next/*
|
||||
- packages/next/**/*
|
||||
- packages/formatters/*
|
||||
- packages/formatters/**/*
|
||||
packages:proxy:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- packages/proxy/*
|
||||
- packages/proxy/**/*
|
||||
- packages/proxy/*
|
||||
- packages/proxy/**/*
|
||||
packages:proxy-container:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- packages/proxy-container/*
|
||||
- packages/proxy-container/**/*
|
||||
- packages/proxy-container/*
|
||||
- packages/proxy-container/**/*
|
||||
packages:rest:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- packages/rest/*
|
||||
- packages/rest/**/*
|
||||
packages:structures:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- packages/structures/*
|
||||
- packages/structures/**/*
|
||||
- packages/rest/*
|
||||
- packages/rest/**/*
|
||||
packages:ui:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- packages/ui/*
|
||||
- packages/ui/**/*
|
||||
- packages/ui/*
|
||||
- packages/ui/**/*
|
||||
packages:util:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- packages/util/*
|
||||
- packages/util/**/*
|
||||
- packages/util/*
|
||||
- packages/util/**/*
|
||||
packages:voice:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- packages/voice/*
|
||||
- packages/voice/**/*
|
||||
- packages/voice/*
|
||||
- packages/voice/**/*
|
||||
packages:ws:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- packages/ws/*
|
||||
- packages/ws/**/*
|
||||
- packages/ws/*
|
||||
- packages/ws/**/*
|
||||
|
||||
16
.github/labels.yml
vendored
@@ -8,10 +8,6 @@
|
||||
color: fbca04
|
||||
- name: backlog
|
||||
color: 7ef7ef
|
||||
- name: backport
|
||||
color: 88aabb
|
||||
- name: backport-candidate
|
||||
color: 0075ca
|
||||
- name: blocked
|
||||
color: fc1423
|
||||
- name: bug
|
||||
@@ -40,6 +36,8 @@
|
||||
color: 80c042
|
||||
- name: good first issue
|
||||
color: 7057ff
|
||||
- name: has PR
|
||||
color: 4b1f8e
|
||||
- name: help wanted
|
||||
color: '008672'
|
||||
- name: in progress
|
||||
@@ -52,10 +50,6 @@
|
||||
color: e4e669
|
||||
- name: need repro
|
||||
color: c66037
|
||||
- name: packages:api-extractor
|
||||
color: fbca04
|
||||
- name: packages:api-extractor-model
|
||||
color: fbca04
|
||||
- name: packages:brokers
|
||||
color: fbca04
|
||||
- name: packages:builders
|
||||
@@ -64,24 +58,18 @@
|
||||
color: fbca04
|
||||
- name: packages:core
|
||||
color: fbca04
|
||||
- name: packages:create-discord-bot
|
||||
color: fbca04
|
||||
- name: packages:discord.js
|
||||
color: fbca04
|
||||
- name: packages:docgen
|
||||
color: fbca04
|
||||
- name: packages:formatters
|
||||
color: fbca04
|
||||
- name: packages:next
|
||||
color: fbca04
|
||||
- name: packages:proxy
|
||||
color: fbca04
|
||||
- name: packages:proxy-container
|
||||
color: fbca04
|
||||
- name: packages:rest
|
||||
color: fbca04
|
||||
- name: packages:structures
|
||||
color: fbca04
|
||||
- name: packages:ui
|
||||
color: fbca04
|
||||
- name: packages:util
|
||||
|
||||
BIN
.github/powered-by-workers.png
vendored
|
Before Width: | Height: | Size: 16 KiB |
35
.github/workflows/cleanup-cache.yml
vendored
@@ -1,35 +0,0 @@
|
||||
# https://docs.github.com/actions/using-workflows/caching-dependencies-to-speed-up-workflows#force-deleting-cache-entries
|
||||
name: Cleanup caches
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- closed
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
cleanup:
|
||||
name: Cleanup caches
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Cleanup caches
|
||||
run: |
|
||||
gh extension install actions/gh-actions-cache
|
||||
|
||||
REPO=${{ github.repository }}
|
||||
BRANCH="refs/pull/${{ github.event.pull_request.number }}/merge"
|
||||
|
||||
echo "Fetching list of cache key"
|
||||
cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH | cut -f 1 )
|
||||
|
||||
## Setting this to not fail the workflow while deleting cache keys.
|
||||
set +e
|
||||
echo "Deleting caches..."
|
||||
for cacheKey in $cacheKeysForPR
|
||||
do
|
||||
gh actions-cache delete $cacheKey -R $REPO -B $BRANCH --confirm
|
||||
done
|
||||
echo "Done"
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
35
.github/workflows/deploy-website.yml
vendored
@@ -1,35 +0,0 @@
|
||||
name: Deploy website
|
||||
on:
|
||||
workflow_dispatch:
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
|
||||
cancel-in-progress: true
|
||||
jobs:
|
||||
deploy-website:
|
||||
name: Deploy website
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
|
||||
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
|
||||
if: github.repository_owner == 'discordjs'
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Install Node.js v22
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 22
|
||||
package-manager-cache: false
|
||||
|
||||
- name: Install dependencies
|
||||
uses: ./packages/actions/src/pnpmCache
|
||||
|
||||
- name: Pull vercel production environment
|
||||
run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
|
||||
|
||||
- name: Build website artifacts
|
||||
run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }}
|
||||
|
||||
- name: Deploy website artifacts to vercel
|
||||
run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }}
|
||||
53
.github/workflows/deprecate-version.yml
vendored
@@ -1,53 +0,0 @@
|
||||
name: Deprecate version
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
package:
|
||||
description: Package
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- '@discordjs/brokers'
|
||||
- '@discordjs/builders'
|
||||
- '@discordjs/collection'
|
||||
- '@discordjs/core'
|
||||
- 'create-discord-app'
|
||||
- 'create-discord-bot'
|
||||
- '@discordjs/formatters'
|
||||
- 'discord.js'
|
||||
- '@discordjs/next'
|
||||
- '@discordjs/proxy'
|
||||
- '@discordjs/rest'
|
||||
- '@discordjs/structures'
|
||||
- '@discordjs/util'
|
||||
- '@discordjs/voice'
|
||||
- '@discordjs/ws'
|
||||
version:
|
||||
description: Version(s)
|
||||
required: true
|
||||
type: string
|
||||
message:
|
||||
description: Deprecation message
|
||||
required: false
|
||||
type: string
|
||||
jobs:
|
||||
deprecate:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_owner == 'discordjs'
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Install Node.js v22
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 22
|
||||
package-manager-cache: false
|
||||
|
||||
- name: Install dependencies
|
||||
uses: ./packages/actions/src/pnpmCache
|
||||
|
||||
- name: Deprecate
|
||||
run: pnpm exec npm-deprecate --name "${{inputs.version}}" --message "${{inputs.message || 'This version is deprecated. Please use a newer version.'}}" --package ${{inputs.package}}
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
|
||||
308
.github/workflows/documentation.yml
vendored
@@ -3,11 +3,6 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
paths:
|
||||
- 'packages/*/src/**'
|
||||
- '!packages/create-discord-bot/**'
|
||||
- '!packages/proxy-container/**'
|
||||
- '!packages/ui/**'
|
||||
tags:
|
||||
- '**'
|
||||
workflow_dispatch:
|
||||
@@ -26,221 +21,137 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
|
||||
cancel-in-progress: true
|
||||
jobs:
|
||||
build-docs:
|
||||
name: Build & upload documentation
|
||||
build:
|
||||
name: Build documentation
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
REF_TYPE: ${{ inputs.ref_type || github.ref_type }}
|
||||
if: github.repository_owner == 'discordjs'
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ inputs.ref || '' }}
|
||||
|
||||
- name: Install Node.js v22
|
||||
uses: actions/setup-node@v5
|
||||
- name: Install node.js v16
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 22
|
||||
package-manager-cache: false
|
||||
node-version: 16
|
||||
|
||||
- name: Install dependencies
|
||||
uses: ./packages/actions/src/pnpmCache
|
||||
uses: ./packages/actions/src/yarnCache
|
||||
|
||||
- name: Build dependencies
|
||||
run: pnpm run build
|
||||
|
||||
- name: Checkout main repository
|
||||
if: ${{ inputs.ref && inputs.ref != 'main' }}
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
path: 'main'
|
||||
|
||||
- name: Build main
|
||||
if: ${{ inputs.ref && inputs.ref != 'main' }}
|
||||
shell: bash
|
||||
env:
|
||||
COREPACK_ENABLE_STRICT: 0
|
||||
run: |
|
||||
cd main
|
||||
pnpm self-update 10
|
||||
pnpm install --frozen-lockfile --prefer-offline --loglevel error
|
||||
pnpm run build
|
||||
cd ..
|
||||
|
||||
- name: Extract package and semver from tag
|
||||
if: ${{ env.REF_TYPE == 'tag' }}
|
||||
id: extract-tag
|
||||
uses: ./packages/actions/src/formatTag
|
||||
with:
|
||||
tag: ${{ inputs.ref || github.ref_name }}
|
||||
|
||||
- name: Apply tag to api-extractor config
|
||||
if: ${{ env.REF_TYPE == 'tag' && !inputs.ref }}
|
||||
run: sed -i 's!https://github.com/discordjs/discord.js/tree/main!https://github.com/discordjs/discord.js/tree/${{ github.ref_name }}!' "packages/${{ steps.extract-tag.outputs.package}}/api-extractor.json"
|
||||
run: yarn build
|
||||
|
||||
- name: Build docs
|
||||
run: pnpm run docs
|
||||
run: yarn docs
|
||||
|
||||
- name: Build docs with main api-extractor
|
||||
if: ${{ inputs.ref && inputs.ref != 'main' }}
|
||||
run: |
|
||||
declare -a PACKAGES=("brokers" "builders" "collection" "core" "discord.js" "formatters" "next" "proxy" "rest" "structures" "util" "voice" "ws")
|
||||
for PACKAGE in "${PACKAGES[@]}"; do
|
||||
if [ ! -d "packages/${PACKAGE}" ]; then
|
||||
echo "::notice::${PACKAGE} does not exist on this ref. Skipping..."
|
||||
continue
|
||||
fi
|
||||
- name: Upload docgen artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: docgen
|
||||
path: packages/*/docs/docs.json
|
||||
|
||||
cd "packages/${PACKAGE}"
|
||||
sed -i 's!https://github.com/discordjs/discord.js/tree/main!https://github.com/discordjs/discord.js/tree/${{ inputs.ref }}!' api-extractor.json
|
||||
../../main/packages/api-extractor/bin/api-extractor run --local --minify
|
||||
../../main/packages/scripts/bin/generateSplitDocumentation.js
|
||||
cd ../..
|
||||
done
|
||||
- name: Upload api-extractor artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: api-extractor
|
||||
path: packages/*/docs/docs.api.json
|
||||
|
||||
upload:
|
||||
name: Upload Documentation
|
||||
needs: build
|
||||
strategy:
|
||||
max-parallel: 1
|
||||
fail-fast: false
|
||||
matrix:
|
||||
package:
|
||||
[
|
||||
'brokers',
|
||||
'builders',
|
||||
'collection',
|
||||
'core',
|
||||
'discord.js',
|
||||
'formatters',
|
||||
'proxy',
|
||||
'rest',
|
||||
'util',
|
||||
'voice',
|
||||
'ws',
|
||||
]
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install node.js v16
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
- name: Install dependencies
|
||||
uses: ./packages/actions/src/yarnCache
|
||||
|
||||
- name: Build actions
|
||||
run: yarn workspace @discordjs/actions build
|
||||
|
||||
- name: Download docgen artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: docgen
|
||||
path: docs
|
||||
|
||||
- name: Download api-extractor artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: api-extractor
|
||||
path: docs
|
||||
|
||||
- name: Checkout docs repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: 'discordjs/docs'
|
||||
token: ${{ secrets.DJS_DOCS }}
|
||||
path: 'out'
|
||||
|
||||
- name: Upload documentation to database
|
||||
if: ${{ env.REF_TYPE == 'tag' && (!inputs.ref || inputs.ref == 'main') }}
|
||||
env:
|
||||
CF_D1_DOCS_API_KEY: ${{ secrets.CF_D1_DOCS_API_KEY }}
|
||||
CF_D1_DOCS_ID: ${{ secrets.CF_D1_DOCS_ID }}
|
||||
CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
CF_R2_DOCS_URL: ${{ secrets.CF_R2_DOCS_URL }}
|
||||
CF_R2_DOCS_ACCESS_KEY_ID: ${{ secrets.CF_R2_DOCS_ACCESS_KEY_ID }}
|
||||
CF_R2_DOCS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_DOCS_SECRET_ACCESS_KEY }}
|
||||
CF_R2_DOCS_BUCKET: ${{ secrets.CF_R2_DOCS_BUCKET }}
|
||||
CF_R2_DOCS_BUCKET_URL: ${{ secrets.CF_R2_DOCS_BUCKET_URL }}
|
||||
uses: ./packages/actions/src/uploadDocumentation
|
||||
- name: Extract package and semver from tag
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
id: extract-tag
|
||||
uses: ./packages/actions/src/formatTag
|
||||
with:
|
||||
package: ${{ steps.extract-tag.outputs.package }}
|
||||
version: ${{ steps.extract-tag.outputs.semver }}
|
||||
|
||||
- name: Upload documentation to database
|
||||
if: ${{ env.REF_TYPE == 'tag' && inputs.ref && inputs.ref != 'main' }}
|
||||
env:
|
||||
CF_D1_DOCS_API_KEY: ${{ secrets.CF_D1_DOCS_API_KEY }}
|
||||
CF_D1_DOCS_ID: ${{ secrets.CF_D1_DOCS_ID }}
|
||||
CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
CF_R2_DOCS_URL: ${{ secrets.CF_R2_DOCS_URL }}
|
||||
CF_R2_DOCS_ACCESS_KEY_ID: ${{ secrets.CF_R2_DOCS_ACCESS_KEY_ID }}
|
||||
CF_R2_DOCS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_DOCS_SECRET_ACCESS_KEY }}
|
||||
CF_R2_DOCS_BUCKET: ${{ secrets.CF_R2_DOCS_BUCKET }}
|
||||
CF_R2_DOCS_BUCKET_URL: ${{ secrets.CF_R2_DOCS_BUCKET_URL }}
|
||||
uses: ./main/packages/actions/src/uploadDocumentation
|
||||
with:
|
||||
package: ${{ steps.extract-tag.outputs.package }}
|
||||
version: ${{ steps.extract-tag.outputs.semver }}
|
||||
|
||||
- name: Upload split documentation to blob storage
|
||||
if: ${{ env.REF_TYPE == 'tag' && (!inputs.ref || inputs.ref == 'main') }}
|
||||
env:
|
||||
CF_R2_DOCS_URL: ${{ secrets.CF_R2_DOCS_URL }}
|
||||
CF_R2_DOCS_ACCESS_KEY_ID: ${{ secrets.CF_R2_DOCS_ACCESS_KEY_ID }}
|
||||
CF_R2_DOCS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_DOCS_SECRET_ACCESS_KEY }}
|
||||
CF_R2_DOCS_BUCKET: ${{ secrets.CF_R2_DOCS_BUCKET }}
|
||||
uses: ./packages/actions/src/uploadSplitDocumentation
|
||||
with:
|
||||
package: ${{ steps.extract-tag.outputs.package }}
|
||||
version: ${{ steps.extract-tag.outputs.semver }}
|
||||
|
||||
- name: Upload split documentation to blob storage
|
||||
if: ${{ env.REF_TYPE == 'tag' && inputs.ref && inputs.ref != 'main' }}
|
||||
env:
|
||||
CF_R2_DOCS_URL: ${{ secrets.CF_R2_DOCS_URL }}
|
||||
CF_R2_DOCS_ACCESS_KEY_ID: ${{ secrets.CF_R2_DOCS_ACCESS_KEY_ID }}
|
||||
CF_R2_DOCS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_DOCS_SECRET_ACCESS_KEY }}
|
||||
CF_R2_DOCS_BUCKET: ${{ secrets.CF_R2_DOCS_BUCKET }}
|
||||
uses: ./main/packages/actions/src/uploadSplitDocumentation
|
||||
with:
|
||||
package: ${{ steps.extract-tag.outputs.package }}
|
||||
version: ${{ steps.extract-tag.outputs.semver }}
|
||||
tag: ${{ github.ref_name }}
|
||||
|
||||
- name: Move docs to correct directory
|
||||
if: ${{ env.REF_TYPE == 'tag' }}
|
||||
if: ${{ github.ref_type == 'tag' && matrix.package == steps.extract-tag.outputs.package }}
|
||||
env:
|
||||
PACKAGE: ${{ steps.extract-tag.outputs.package }}
|
||||
SEMVER: ${{ steps.extract-tag.outputs.semver }}
|
||||
run: |
|
||||
mkdir -p "out/${PACKAGE}"
|
||||
if [[ "${PACKAGE}" == "discord.js" ]]; then
|
||||
mv "packages/${PACKAGE}/docs/docs.json" "out/${PACKAGE}/${SEMVER}.json"
|
||||
mv "packages/${PACKAGE}/docs/docs.api.json" "out/${PACKAGE}/${SEMVER}.api.json"
|
||||
else
|
||||
mv "packages/${PACKAGE}/docs/docs.api.json" "out/${PACKAGE}/${SEMVER}.api.json"
|
||||
mkdir -p out/${PACKAGE}
|
||||
if [[ $PACKAGE == "discord.js" ]]; then
|
||||
mv docs/${PACKAGE}/docs/docs.json out/${PACKAGE}/${SEMVER}.json
|
||||
fi
|
||||
if [[ $PACKAGE != "discord.js" ]]; then
|
||||
mv docs/${PACKAGE}/docs/docs.api.json out/${PACKAGE}/${SEMVER}.api.json
|
||||
fi
|
||||
|
||||
- name: Upload documentation to database
|
||||
if: ${{ env.REF_TYPE == 'branch' && (!inputs.ref || inputs.ref == 'main') }}
|
||||
env:
|
||||
CF_D1_DOCS_API_KEY: ${{ secrets.CF_D1_DOCS_API_KEY }}
|
||||
CF_D1_DOCS_ID: ${{ secrets.CF_D1_DOCS_ID }}
|
||||
CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
CF_R2_DOCS_URL: ${{ secrets.CF_R2_DOCS_URL }}
|
||||
CF_R2_DOCS_ACCESS_KEY_ID: ${{ secrets.CF_R2_DOCS_ACCESS_KEY_ID }}
|
||||
CF_R2_DOCS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_DOCS_SECRET_ACCESS_KEY }}
|
||||
CF_R2_DOCS_BUCKET: ${{ secrets.CF_R2_DOCS_BUCKET }}
|
||||
CF_R2_DOCS_BUCKET_URL: ${{ secrets.CF_R2_DOCS_BUCKET_URL }}
|
||||
uses: ./packages/actions/src/uploadDocumentation
|
||||
|
||||
- name: Upload documentation to database
|
||||
if: ${{ env.REF_TYPE == 'branch' && inputs.ref && inputs.ref != 'main' }}
|
||||
env:
|
||||
CF_D1_DOCS_API_KEY: ${{ secrets.CF_D1_DOCS_API_KEY }}
|
||||
CF_D1_DOCS_ID: ${{ secrets.CF_D1_DOCS_ID }}
|
||||
CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
CF_R2_DOCS_URL: ${{ secrets.CF_R2_DOCS_URL }}
|
||||
CF_R2_DOCS_ACCESS_KEY_ID: ${{ secrets.CF_R2_DOCS_ACCESS_KEY_ID }}
|
||||
CF_R2_DOCS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_DOCS_SECRET_ACCESS_KEY }}
|
||||
CF_R2_DOCS_BUCKET: ${{ secrets.CF_R2_DOCS_BUCKET }}
|
||||
CF_R2_DOCS_BUCKET_URL: ${{ secrets.CF_R2_DOCS_BUCKET_URL }}
|
||||
uses: ./main/packages/actions/src/uploadDocumentation
|
||||
|
||||
- name: Upload split documentation to blob storage
|
||||
if: ${{ env.REF_TYPE == 'branch' && (!inputs.ref || inputs.ref == 'main') }}
|
||||
env:
|
||||
CF_R2_DOCS_URL: ${{ secrets.CF_R2_DOCS_URL }}
|
||||
CF_R2_DOCS_ACCESS_KEY_ID: ${{ secrets.CF_R2_DOCS_ACCESS_KEY_ID }}
|
||||
CF_R2_DOCS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_DOCS_SECRET_ACCESS_KEY }}
|
||||
CF_R2_DOCS_BUCKET: ${{ secrets.CF_R2_DOCS_BUCKET }}
|
||||
uses: ./packages/actions/src/uploadSplitDocumentation
|
||||
|
||||
- name: Upload split documentation to blob storage
|
||||
if: ${{ env.REF_TYPE == 'branch' && inputs.ref && inputs.ref != 'main' }}
|
||||
env:
|
||||
CF_R2_DOCS_URL: ${{ secrets.CF_R2_DOCS_URL }}
|
||||
CF_R2_DOCS_ACCESS_KEY_ID: ${{ secrets.CF_R2_DOCS_ACCESS_KEY_ID }}
|
||||
CF_R2_DOCS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_DOCS_SECRET_ACCESS_KEY }}
|
||||
CF_R2_DOCS_BUCKET: ${{ secrets.CF_R2_DOCS_BUCKET }}
|
||||
uses: ./main/packages/actions/src/uploadSplitDocumentation
|
||||
|
||||
- name: Move docs to correct directory
|
||||
if: ${{ env.REF_TYPE == 'branch' }}
|
||||
if: ${{ github.ref_type == 'branch' }}
|
||||
env:
|
||||
PACKAGE: ${{ matrix.package }}
|
||||
run: |
|
||||
declare -a PACKAGES=("brokers" "builders" "collection" "core" "discord.js" "formatters" "next" "proxy" "rest" "structures" "util" "voice" "ws")
|
||||
for PACKAGE in "${PACKAGES[@]}"; do
|
||||
if [ ! -d "packages/${PACKAGE}" ]; then
|
||||
echo "::notice::${PACKAGE} does not exist on this ref. Skipping..."
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ "${PACKAGE}" == "discord.js" ]]; then
|
||||
mkdir -p "out/${PACKAGE}"
|
||||
mv "packages/${PACKAGE}/docs/docs.json" "out/${PACKAGE}/${GITHUB_REF_NAME}.json"
|
||||
mv "packages/${PACKAGE}/docs/docs.api.json" "out/${PACKAGE}/${GITHUB_REF_NAME}.api.json"
|
||||
else
|
||||
mkdir -p "out/${PACKAGE}"
|
||||
mv "packages/${PACKAGE}/docs/docs.api.json" "out/${PACKAGE}/${GITHUB_REF_NAME}.api.json"
|
||||
fi
|
||||
done
|
||||
mkdir -p out/${PACKAGE}
|
||||
if [[ $PACKAGE == "discord.js" ]]; then
|
||||
mv docs/${PACKAGE}/docs/docs.json out/${PACKAGE}/${GITHUB_REF_NAME}.json
|
||||
fi
|
||||
if [[ $PACKAGE != "discord.js" ]]; then
|
||||
mv docs/${PACKAGE}/docs/docs.api.json out/${PACKAGE}/${GITHUB_REF_NAME}.api.json
|
||||
fi
|
||||
|
||||
- name: Commit and push
|
||||
run: |
|
||||
@@ -250,34 +161,3 @@ jobs:
|
||||
git add .
|
||||
git commit -m "Docs build for ${GITHUB_REF_TYPE} ${GITHUB_REF_NAME}: ${GITHUB_SHA}" || true
|
||||
git push
|
||||
|
||||
build-indices:
|
||||
needs: build-docs
|
||||
name: Build & upload search indices
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
if: github.repository_owner == 'discordjs'
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Install Node.js v22
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 22
|
||||
package-manager-cache: false
|
||||
|
||||
- name: Install dependencies
|
||||
uses: ./packages/actions/src/pnpmCache
|
||||
|
||||
- name: Build dependencies
|
||||
run: pnpm run build
|
||||
|
||||
- name: Upload search indices to meilisearch
|
||||
env:
|
||||
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
||||
SEARCH_API_URL: ${{ secrets.SEARCH_API_URL }}
|
||||
SEARCH_API_KEY: ${{ secrets.SEARCH_API_KEY }}
|
||||
uses: ./packages/actions/src/uploadSearchIndices
|
||||
|
||||
14
.github/workflows/issue-triage.yml
vendored
@@ -1,14 +0,0 @@
|
||||
name: 'Issue Labeler'
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
jobs:
|
||||
issue-triage:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: github/issue-labeler@v3.4
|
||||
with:
|
||||
repo-token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
configuration-path: .github/issue-labeler.yml
|
||||
not-before: 2023-01-13T10:25:03.847Z
|
||||
enable-versioned-regex: 0
|
||||
4
.github/workflows/label-sync.yml
vendored
@@ -15,9 +15,9 @@ jobs:
|
||||
if: github.repository_owner == 'discordjs'
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Label sync
|
||||
uses: crazy-max/ghaction-github-labeler@v5
|
||||
uses: crazy-max/ghaction-github-labeler@v4
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
19
.github/workflows/lock.yml
vendored
@@ -1,19 +0,0 @@
|
||||
name: Lock Ancient Issues
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 16 * * *'
|
||||
workflow_dispatch:
|
||||
concurrency:
|
||||
group: lock
|
||||
jobs:
|
||||
action:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v5
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue-inactive-days: 365
|
||||
issue-lock-reason: resolved
|
||||
process-only: issues
|
||||
26
.github/workflows/npm-auto-deprecate.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
name: npm auto deprecate
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 1 * * *'
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
npm-auto-deprecate:
|
||||
name: npm auto deprecate
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_owner == 'discordjs'
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install node.js v16
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
- name: Install dependencies
|
||||
uses: ./packages/actions/src/yarnCache
|
||||
|
||||
- name: Deprecate versions
|
||||
run: 'yarn npm-deprecate --name "*dev*" --package @discordjs/brokers @discordjs/builders @discordjs/collection @discordjs/core @discordjs/formatters discord.js @discordjs/proxy @discordjs/rest @discordjs/util @discordjs/voice @discordjs/ws'
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
|
||||
31
.github/workflows/pr-triage.yml
vendored
@@ -1,34 +1,17 @@
|
||||
name: 'PR Triage'
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
- edited
|
||||
- reopened
|
||||
- synchronize
|
||||
jobs:
|
||||
label:
|
||||
name: Label
|
||||
if: github.event.action != 'edited'
|
||||
pr-triage:
|
||||
name: PR Triage
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Label pull request
|
||||
uses: actions/labeler@v5
|
||||
- name: Automatically label PR
|
||||
uses: actions/labeler@v4
|
||||
with:
|
||||
repo-token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
sync-labels: true
|
||||
validate-title:
|
||||
name: Validate title
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Validate pull request title
|
||||
env:
|
||||
TITLE: ${{ github.event.pull_request.title }}
|
||||
run: |
|
||||
REGEX="^(revert: )?(feat|fix|docs|style|refactor|perf|test|build|ci|chore|types)(\\(.+\\))?!?: .{1,72}$"
|
||||
|
||||
echo "Title: \"$TITLE\""
|
||||
|
||||
if [[ ! "$TITLE" =~ $REGEX ]]; then
|
||||
exit 1
|
||||
fi
|
||||
- name: Automatically assign reviewers
|
||||
if: github.event.action == 'opened'
|
||||
uses: kentaro-m/auto-assign-action@v1.2.3
|
||||
|
||||
20
.github/workflows/publish-dev-docker.yml
vendored
@@ -10,22 +10,16 @@ jobs:
|
||||
if: github.repository_owner == 'discordjs'
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Install Node.js v22
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 22
|
||||
package-manager-cache: false
|
||||
|
||||
- name: Install dependencies
|
||||
uses: ./packages/actions/src/pnpmCache
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Login to DockerHub
|
||||
run: echo ${{ secrets.DOCKER_ACCESS_TOKEN }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
|
||||
|
||||
- name: Build & push docker image
|
||||
run: docker build -f packages/proxy-container/Dockerfile -t discordjs/proxy:latest --push .
|
||||
- name: Build the image
|
||||
run: docker build -t discordjs/proxy:latest -f packages/proxy-container/Dockerfile .
|
||||
|
||||
- name: Push image to DockerHub
|
||||
run: docker push discordjs/proxy:latest
|
||||
|
||||
114
.github/workflows/publish-dev.yml
vendored
@@ -3,101 +3,59 @@ on:
|
||||
schedule:
|
||||
- cron: '0 */12 * * *'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
pull:
|
||||
description: 'The pull number to check out'
|
||||
required: false
|
||||
default: 'main'
|
||||
tag:
|
||||
description: 'The tag to use, generally a feature name'
|
||||
required: false
|
||||
type: string
|
||||
dry_run:
|
||||
description: 'Perform a dry run that skips publishing and outputs logs indicating what would have happened'
|
||||
type: boolean
|
||||
default: false
|
||||
jobs:
|
||||
npm-publish:
|
||||
name: npm publish
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- package: '@discordjs/brokers'
|
||||
folder: 'brokers'
|
||||
- package: '@discordjs/builders'
|
||||
folder: 'builders'
|
||||
- package: '@discordjs/collection'
|
||||
folder: 'collection'
|
||||
- package: '@discordjs/core'
|
||||
folder: 'core'
|
||||
- package: '@discordjs/formatters'
|
||||
folder: 'formatters'
|
||||
- package: 'discord.js'
|
||||
folder: 'discord.js'
|
||||
- package: '@discordjs/proxy'
|
||||
folder: 'proxy'
|
||||
- package: '@discordjs/rest'
|
||||
folder: 'rest'
|
||||
- package: '@discordjs/util'
|
||||
folder: 'util'
|
||||
- package: '@discordjs/voice'
|
||||
folder: 'voice'
|
||||
- package: '@discordjs/ws'
|
||||
folder: 'ws'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
env:
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
if: github.repository_owner == 'discordjs'
|
||||
steps:
|
||||
- uses: actions/create-github-app-token@v2
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ vars.DISCORDJS_APP_ID }}
|
||||
private-key: ${{ secrets.DISCORDJS_APP_KEY_RELEASE }}
|
||||
|
||||
- name: Decide ref
|
||||
id: ref
|
||||
run: |
|
||||
if [ -n "${{ github.event.inputs.pull }}" ]; then
|
||||
echo "ref=refs/pull/${{ github.event.inputs.pull }}/head" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "ref=refs/heads/main" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
ref: ${{ steps.ref.outputs.ref }}
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Node.js v22
|
||||
uses: actions/setup-node@v5
|
||||
- name: Install node.js v16
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 22
|
||||
package-manager-cache: false
|
||||
node-version: 16
|
||||
registry-url: https://registry.npmjs.org/
|
||||
|
||||
- name: Install dependencies
|
||||
uses: ./packages/actions/src/pnpmCache
|
||||
uses: ./packages/actions/src/yarnCache
|
||||
|
||||
- name: Build dependencies
|
||||
run: pnpm run build
|
||||
run: yarn build
|
||||
|
||||
- name: Checkout main repository (non-main ref)
|
||||
if: ${{ steps.ref.outputs.ref != 'refs/heads/main' }}
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
path: 'main'
|
||||
|
||||
- name: Install action deps (non-main ref)
|
||||
if: ${{ steps.ref.outputs.ref != 'refs/heads/main' }}
|
||||
shell: bash
|
||||
working-directory: ./main
|
||||
env:
|
||||
COREPACK_ENABLE_STRICT: 0
|
||||
- name: Publish package
|
||||
run: |
|
||||
pnpm self-update 10
|
||||
pnpm install --filter @discordjs/actions --frozen-lockfile --prefer-offline --loglevel error
|
||||
|
||||
- name: Publish packages (non-main ref)
|
||||
if: ${{ steps.ref.outputs.ref != 'refs/heads/main' }}
|
||||
uses: ./main/packages/actions/src/releasePackages
|
||||
with:
|
||||
exclude: '@discordjs/docgen'
|
||||
dry: ${{ inputs.dry_run }}
|
||||
dev: true
|
||||
tag: ${{ inputs.tag || 'dev' }}
|
||||
yarn workspace ${{ matrix.package }} release --preid "dev.$(date +%s)-$(git rev-parse --short HEAD)"
|
||||
yarn workspace ${{ matrix.package }} npm publish --tag dev || true
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Publish packages (main ref)
|
||||
if: ${{ steps.ref.outputs.ref == 'refs/heads/main' }}
|
||||
uses: ./packages/actions/src/releasePackages
|
||||
with:
|
||||
exclude: '@discordjs/docgen'
|
||||
dry: ${{ inputs.dry_run }}
|
||||
dev: true
|
||||
tag: ${{ inputs.tag || 'dev' }}
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
YARN_NPM_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
|
||||
|
||||
23
.github/workflows/publish-docker.yml
vendored
@@ -7,22 +7,19 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Install Node.js v22
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 22
|
||||
package-manager-cache: false
|
||||
|
||||
- name: Install dependencies
|
||||
uses: ./packages/actions/src/pnpmCache
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Login to DockerHub
|
||||
run: echo ${{ secrets.DOCKER_ACCESS_TOKEN }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
|
||||
|
||||
- name: Build & push docker image
|
||||
run: docker build -f packages/proxy-container/Dockerfile -t discordjs/proxy:$(cut -d '.' -f1 <<< $(jq --raw-output '.version' packages/proxy-container/package.json)) --push .
|
||||
- name: Build docker image
|
||||
run: docker build -t discordjs/proxy:latest -f packages/proxy-container/Dockerfile .
|
||||
|
||||
- name: Tag image with major
|
||||
run: docker tag discordjs/proxy discordjs/proxy:$(cut -d '.' -f1 <<< $(jq --raw-output '.version' packages/proxy-container/package.json))
|
||||
|
||||
- name: Push image to DockerHub
|
||||
run: docker push --all-tags discordjs/proxy
|
||||
|
||||
50
.github/workflows/publish-release.yml
vendored
@@ -1,50 +0,0 @@
|
||||
name: Publish Release
|
||||
on:
|
||||
release:
|
||||
types: [released]
|
||||
jobs:
|
||||
npm-publish:
|
||||
name: npm publish
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
env:
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
if: github.repository_owner == 'discordjs'
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Install Node.js v22
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 22
|
||||
package-manager-cache: false
|
||||
registry-url: https://registry.npmjs.org/
|
||||
|
||||
- name: Install dependencies
|
||||
uses: ./packages/actions/src/pnpmCache
|
||||
|
||||
- name: Build dependencies
|
||||
run: pnpm run build
|
||||
|
||||
- name: Extract package and semver from tag
|
||||
id: extract-tag
|
||||
uses: ./packages/actions/src/formatTag
|
||||
with:
|
||||
tag: ${{ github.ref_name }}
|
||||
|
||||
- name: Publish package
|
||||
run: |
|
||||
pnpm --filter=${{ steps.extract-tag.outputs.subpackage == 'true' && '@discordjs/' || '' }}${{ steps.extract-tag.outputs.package }} publish --provenance --no-git-checks
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
|
||||
|
||||
- name: create-discord-bot -> create-discord-app
|
||||
if: steps.extract-tag.outputs.package == 'create-discord-bot'
|
||||
run: |
|
||||
pnpm --filter=create-discord-bot run rename-to-app
|
||||
pnpm --filter=create-discord-app publish --provenance --no-git-checks
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
|
||||
112
.github/workflows/release.yml
vendored
@@ -1,112 +0,0 @@
|
||||
name: Release
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
ref:
|
||||
description: 'The branch, tag or SHA to checkout'
|
||||
required: true
|
||||
default: 'main'
|
||||
package:
|
||||
description: 'The published name of a single package to release'
|
||||
type: choice
|
||||
required: false
|
||||
options:
|
||||
- all
|
||||
- discord.js
|
||||
- '@discordjs/brokers'
|
||||
- '@discordjs/builders'
|
||||
- '@discordjs/collection'
|
||||
- '@discordjs/core'
|
||||
- 'create-discord-bot'
|
||||
- '@discordjs/docgen'
|
||||
- '@discordjs/formatters'
|
||||
- '@discordjs/next'
|
||||
- '@discordjs/proxy'
|
||||
- '@discordjs/rest'
|
||||
- '@discordjs/structures'
|
||||
- '@discordjs/util'
|
||||
- '@discordjs/voice'
|
||||
- '@discordjs/ws'
|
||||
exclude:
|
||||
description: 'Comma separated list of packages to exclude from release (if not depended upon)'
|
||||
required: false
|
||||
type: string
|
||||
default: '@discordjs/docgen,@discordjs/next'
|
||||
dry_run:
|
||||
description: Perform a dry run?
|
||||
type: boolean
|
||||
default: false
|
||||
jobs:
|
||||
npm-publish:
|
||||
name: npm publish
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
env:
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
if: github.repository_owner == 'discordjs'
|
||||
steps:
|
||||
- uses: actions/create-github-app-token@v2
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ vars.DISCORDJS_APP_ID }}
|
||||
private-key: ${{ secrets.DISCORDJS_APP_KEY_RELEASE }}
|
||||
permission-contents: write
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
ref: ${{ inputs.ref || '' }}
|
||||
|
||||
- name: Install Node.js v22
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 22
|
||||
package-manager-cache: false
|
||||
registry-url: https://registry.npmjs.org/
|
||||
|
||||
- name: Install dependencies
|
||||
uses: ./packages/actions/src/pnpmCache
|
||||
|
||||
- name: Build dependencies
|
||||
run: pnpm run build
|
||||
|
||||
- name: Checkout main repository
|
||||
if: ${{ inputs.ref && inputs.ref != 'main' }}
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
path: 'main'
|
||||
|
||||
- name: Install action deps (non-main ref only)
|
||||
if: ${{ inputs.ref && inputs.ref != 'main' }}
|
||||
shell: bash
|
||||
working-directory: ./main
|
||||
env:
|
||||
COREPACK_ENABLE_STRICT: 0
|
||||
run: |
|
||||
pnpm self-update 10
|
||||
pnpm install --filter @discordjs/actions --frozen-lockfile --prefer-offline --loglevel error
|
||||
|
||||
- name: Release packages (non-main ref)
|
||||
if: ${{ inputs.ref && inputs.ref != 'main' }}
|
||||
uses: ./main/packages/actions/src/releasePackages
|
||||
with:
|
||||
package: ${{ inputs.package }}
|
||||
exclude: ${{ inputs.exclude }}
|
||||
dry: ${{ inputs.dry_run }}
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
|
||||
- name: Release packages (main ref)
|
||||
if: ${{ !inputs.ref || inputs.ref == 'main' }}
|
||||
uses: ./packages/actions/src/releasePackages
|
||||
with:
|
||||
package: ${{ inputs.package }}
|
||||
exclude: ${{ inputs.exclude }}
|
||||
dry: ${{ inputs.dry_run }}
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
44
.github/workflows/tests.yml
vendored
@@ -3,8 +3,7 @@ on:
|
||||
push:
|
||||
pull_request:
|
||||
concurrency:
|
||||
# Group based on workflow name and PR if it exists, if no PR, let it run so carryforward flags work
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
|
||||
cancel-in-progress: true
|
||||
jobs:
|
||||
tests:
|
||||
@@ -15,53 +14,50 @@ jobs:
|
||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install Node.js v22
|
||||
uses: actions/setup-node@v5
|
||||
- name: Install node.js v16
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 22
|
||||
package-manager-cache: false
|
||||
node-version: 16
|
||||
|
||||
- name: Install dependencies
|
||||
uses: ./packages/actions/src/pnpmCache
|
||||
uses: ./packages/actions/src/yarnCache
|
||||
|
||||
- name: Build dependencies (PR)
|
||||
if: ${{ github.event_name != 'push' }}
|
||||
run: pnpm exec turbo run build --filter="...[origin/${{ github.event_name == 'pull_request' && github.event.pull_request.base.ref || 'main' }}]" --concurrency=4
|
||||
run: yarn build --filter="...[origin/${{ github.event_name == 'pull_request' && github.event.pull_request.base.ref || 'main' }}]"
|
||||
|
||||
- name: Build dependencies (Push)
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
run: pnpm exec turbo run build --filter="...[HEAD^1]" --concurrency=4
|
||||
|
||||
- name: Tests (PR)
|
||||
if: ${{ github.event_name != 'push' }}
|
||||
run: pnpm exec turbo run test --filter="...[origin/${{ github.event_name == 'pull_request' && github.event.pull_request.base.ref || 'main' }}]" --concurrency=4
|
||||
|
||||
- name: Tests (Push)
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
run: pnpm exec turbo run test --filter="...[HEAD^1]" --concurrency=4
|
||||
run: yarn build --filter="...[HEAD^1]"
|
||||
|
||||
- name: ESLint (PR)
|
||||
if: ${{ github.event_name != 'push' }}
|
||||
run: pnpm exec turbo run lint --filter="...[origin/${{ github.event_name == 'pull_request' && github.event.pull_request.base.ref || 'main' }}]" --concurrency=4 -- --format=compact
|
||||
run: yarn lint --filter="...[origin/${{ github.event_name == 'pull_request' && github.event.pull_request.base.ref || 'main' }}]" -- --format=compact
|
||||
|
||||
- name: ESLint (Push)
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
run: pnpm exec turbo run lint --filter="...[HEAD^1]" --concurrency=4 -- --format=compact
|
||||
run: yarn lint --filter="...[HEAD^1]" -- --format=compact
|
||||
|
||||
- name: Tests (PR)
|
||||
if: ${{ github.event_name != 'push' }}
|
||||
run: yarn test --filter="...[origin/${{ github.event_name == 'pull_request' && github.event.pull_request.base.ref || 'main' }}]"
|
||||
|
||||
- name: Tests (Push)
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
run: yarn test --filter="...[HEAD^1]"
|
||||
|
||||
- name: Docs (PR)
|
||||
if: ${{ github.event_name != 'push' }}
|
||||
run: pnpm exec turbo run docs --filter="...[origin/${{ github.event_name == 'pull_request' && github.event.pull_request.base.ref || 'main' }}]" --concurrency=4
|
||||
run: yarn docs --filter="...[origin/${{ github.event_name == 'pull_request' && github.event.pull_request.base.ref || 'main' }}]"
|
||||
|
||||
- name: Docs (Push)
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
run: pnpm exec turbo run docs --filter="...[HEAD^1]" --concurrency=4
|
||||
run: yarn docs --filter="...[HEAD^1]"
|
||||
|
||||
- name: Upload Coverage
|
||||
if: github.repository_owner == 'discordjs'
|
||||
uses: ./packages/actions/src/uploadCoverage
|
||||
with:
|
||||
codecov_token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
26
.gitignore
vendored
@@ -1,8 +1,8 @@
|
||||
# Packages
|
||||
node_modules
|
||||
node_modules/
|
||||
|
||||
# Log files
|
||||
logs
|
||||
logs/
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
@@ -15,30 +15,19 @@ pids
|
||||
.env
|
||||
|
||||
# Dist
|
||||
dist
|
||||
dist-docs
|
||||
packages/discord-api-types
|
||||
dist/
|
||||
|
||||
# Miscellaneous
|
||||
.tmp
|
||||
.tmp/
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
!.vscode/settings.json
|
||||
.idea
|
||||
.idea/
|
||||
.DS_Store
|
||||
.turbo
|
||||
tsconfig.tsbuildinfo
|
||||
coverage
|
||||
out
|
||||
package.tgz
|
||||
tsup.config.bundled*
|
||||
vitest.config.ts.timestamp*
|
||||
|
||||
# Deno
|
||||
deno.lock
|
||||
|
||||
# Bun
|
||||
bun.lockb
|
||||
coverage/
|
||||
out/
|
||||
|
||||
# yarn
|
||||
.pnp.*
|
||||
@@ -52,4 +41,3 @@ bun.lockb
|
||||
# Cache
|
||||
.prettiercache
|
||||
.eslintcache
|
||||
.vercel
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
pnpm exec commitlint --edit $1
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
yarn commitlint --edit $1
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
pnpm run build:affected && pnpm exec lint-staged
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
yarn build:affected && yarn lint-staged
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/lintstagedrc.schema.json",
|
||||
"*": "prettier --ignore-unknown --write",
|
||||
"{src/**,__tests__/**}.{mjs,js,cjs,ts,tsx}": "eslint --fix",
|
||||
"{src/**,__tests__/**}.{mjs,js,cjs,ts,tsx,astro}": "eslint --ext .mjs,.js,.cjs,.ts,.tsx,.astro --fix",
|
||||
"src/**.ts": "vitest related --run --config ../../vitest.config.ts"
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
CODEOWNERS
|
||||
CHANGELOG.md
|
||||
tsup.config.bundled*
|
||||
vitest.config.ts.timestamp*
|
||||
pnpm-lock.yaml
|
||||
@@ -1,5 +1,4 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/prettierrc.json",
|
||||
"printWidth": 120,
|
||||
"useTabs": true,
|
||||
"singleQuote": true,
|
||||
|
||||
2
.vscode/extensions.json
vendored
@@ -8,6 +8,8 @@
|
||||
"eamodio.gitlens",
|
||||
"christian-kohler.npm-intellisense",
|
||||
"christian-kohler.path-intellisense",
|
||||
"antfu.unocss",
|
||||
"astro-build.astro-vscode",
|
||||
"unifiedjs.vscode-mdx"
|
||||
]
|
||||
}
|
||||
|
||||
54
.vscode/settings.json
vendored
@@ -1,57 +1,19 @@
|
||||
{
|
||||
"eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"],
|
||||
"eslint.useESLintClass": true,
|
||||
"eslint.useFlatConfig": true,
|
||||
"eslint.workingDirectories": [{ "pattern": "./apps/*" }, { "pattern": "./packages/*" }],
|
||||
"eslint.validate": ["javascript", "javascriptreact", "astro", "typescript", "typescriptreact"],
|
||||
"prettier.documentSelectors": ["**/*.astro"],
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.organizeImports": "never",
|
||||
"source.fixAll.eslint": "always",
|
||||
"source.fixAll": "always"
|
||||
"source.fixAll": true,
|
||||
"source.organizeImports": false
|
||||
},
|
||||
"editor.trimAutoWhitespace": false,
|
||||
"files.associations": {
|
||||
"tsconfig.json": "jsonc",
|
||||
"tsconfig.eslint.json": "jsonc"
|
||||
"*.mdx": "markdown"
|
||||
},
|
||||
"files.insertFinalNewline": true,
|
||||
"files.eol": "\n",
|
||||
"search.exclude": {
|
||||
"**/.yarn": true,
|
||||
"**/.next": true,
|
||||
"**/dist": true,
|
||||
"**/coverage": true,
|
||||
"**/test-results": true
|
||||
},
|
||||
"search.followSymlinks": false,
|
||||
"search.useParentIgnoreFiles": true,
|
||||
"files.watcherExclude": {
|
||||
"**/.next/*/**": true,
|
||||
"**/.yarn/*/**": true,
|
||||
"**/coverage/*/**": true,
|
||||
"**/dist/*/**": true,
|
||||
"**/test-results/*/**": true
|
||||
},
|
||||
"unocss.disable": true,
|
||||
"npm.packageManager": "pnpm",
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"typescript.enablePromptUseWorkspaceTsdk": true,
|
||||
"tailwindCSS.experimental.classRegex": [
|
||||
["cva\\(((?:[^()]|\\([^()]*\\))*)\\)", "[\"'`]?([^\"'`]+)[\"'`]?"],
|
||||
["cx\\(((?:[^()]|\\([^()]*\\))*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"],
|
||||
["class:\\s*?[\"'`]([^\"'`]*).*?,"]
|
||||
],
|
||||
"workbench.editor.customLabels.patterns": {
|
||||
"**/app/**/page.tsx": "${dirname} (${filename}.${extname}) - Page",
|
||||
"**/app/**/layout.tsx": "${dirname} (${filename}.${extname}) - Layout",
|
||||
"**/app/**/template.tsx": "${dirname} (${filename}.${extname}) - Template",
|
||||
"**/app/**/error.tsx": "${dirname} (${filename}.${extname}) - Error",
|
||||
"**/app/**/not-found.tsx": "${dirname} (${filename}.${extname}) - Not Found",
|
||||
"**/components/**/page.tsx": "${dirname} (${filename}.${extname}) - Component"
|
||||
},
|
||||
"deno.enable": false,
|
||||
"deno.enablePaths": ["./packages/create-discord-bot/template/Deno"],
|
||||
"deno.lint": false,
|
||||
"deno.unstable": [],
|
||||
"deno.config": "./packages/create-discord-bot/template/Deno/deno.jsonc"
|
||||
"npm.packageManager": "yarn",
|
||||
"typescript.tsdk": "node_modules/typescript/lib"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
diff --git a/lib/TSDocConfigFile.js b/lib/TSDocConfigFile.js
|
||||
index caf3515d60fd386c5909db5a0aa8b4180b10d602..6fa4f1984b6ba6b3a7aecd05e54477ebf141af94 100644
|
||||
--- a/lib/TSDocConfigFile.js
|
||||
+++ b/lib/TSDocConfigFile.js
|
||||
@@ -31,8 +31,7 @@ const ajv_1 = __importDefault(require("ajv"));
|
||||
const jju = __importStar(require("jju"));
|
||||
const ajv = new ajv_1.default({ verbose: true });
|
||||
function initializeSchemaValidator() {
|
||||
- const jsonSchemaPath = resolve.sync('@microsoft/tsdoc/schemas/tsdoc.schema.json', { basedir: __dirname });
|
||||
- const jsonSchemaContent = fs.readFileSync(jsonSchemaPath).toString();
|
||||
+ const jsonSchemaContent = "{\"title\":\"TSDoc Configuration\",\"description\":\"Describes the TSDoc configuration for a TypeScript project\",\"type\":\"object\",\"properties\":{\"$schema\":{\"description\":\"Part of the JSON Schema standard, this optional keyword declares the URL of the schema that the file conforms to. Editors may download the schema and use it to perform syntax highlighting.\",\"type\":\"string\"},\"extends\":{\"description\":\"Optionally specifies one or more JSON config files that will be combined with this file. This provides a way for standard settings to be shared across multiple projects. Important: The \\\"extends\\\" paths are resolved using NodeJS module resolution, so a path to a local file MUST be prefixed with \\\"./\\\".\",\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"noStandardTags\":{\"description\":\"By default, the config file loader will predefine all of the standardized TSDoc tags. To disable this and start with a completely empty configuration, set \\\"noStandardTags\\\"=true.\",\"type\":\"boolean\"},\"tagDefinitions\":{\"description\":\"Additional tags to support when parsing documentation comments with TSDoc.\",\"type\":\"array\",\"items\":{\"$ref\":\"#/definitions/tsdocTagDefinition\"}},\"supportedHtmlElements\":{\"description\":\"The HTML element names that are supported in this configuration. Used in conjunction with the \\\"reportUnsupportedHtmlElements\\\" setting.\",\"type\":\"array\",\"items\":{\"type\":\"string\",\"pattern\":\"^[a-zA-Z0-9-]+$\"}},\"reportUnsupportedHtmlElements\":{\"description\":\"Whether an error should be reported when an unsupported HTML element is encountered in a doc comment. Defaults to \\\"true\\\" if the \\\"supportedHtmlElements\\\" field is present in this file, \\\"false\\\" if not.\",\"type\":\"boolean\"},\"supportForTags\":{\"description\":\"A collection of key/value pairs. The key is a TSDoc tag name (e.g. \\\"@myTag\\\") that must be defined in this configuration. The value is a boolean indicating whether the tag is supported. The TSDoc parser may report warnings when unsupported tags are encountered. If \\\"supportForTags\\\" is specified for at least one tag, then the \\\"reportUnsupportedTags\\\" validation check is enabled by default.\",\"type\":\"object\",\"patternProperties\":{\"@[a-zA-Z][a-zA-Z0-9]*$\":{\"type\":\"boolean\"}},\"additionalItems\":false}},\"required\":[\"$schema\"],\"additionalProperties\":false,\"definitions\":{\"tsdocTagDefinition\":{\"description\":\"Configuration for a custom supported TSDoc tag.\",\"type\":\"object\",\"properties\":{\"tagName\":{\"description\":\"Name of the custom tag. TSDoc tag names start with an at-sign (@) followed by ASCII letters using camelCase capitalization.\",\"type\":\"string\"},\"syntaxKind\":{\"description\":\"Syntax kind of the custom tag. \\\"inline\\\" means that this tag can appear inside other documentation sections (example: {@link}). \\\"block\\\" means that this tag starts a new documentation section (example: @remarks). \\\"modifier\\\" means that this tag's presence indicates an aspect of the associated API item (example: @internal).\",\"type\":\"string\",\"enum\":[\"inline\",\"block\",\"modifier\"]},\"allowMultiple\":{\"description\":\"If true, then this tag may appear multiple times in a doc comment. By default, a tag may only appear once.\",\"type\":\"boolean\"}},\"required\":[\"tagName\",\"syntaxKind\"],\"additionalProperties\":false}}}";
|
||||
const jsonSchema = jju.parse(jsonSchemaContent, { mode: 'cjson' });
|
||||
return ajv.compile(jsonSchema);
|
||||
}
|
||||
541
.yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
vendored
Normal file
550
.yarn/plugins/@yarnpkg/plugin-version.cjs
vendored
Normal file
28
.yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs
vendored
Normal file
801
.yarn/releases/yarn-3.2.4.cjs
vendored
Normal file
11
.yarnrc.yml
Normal file
@@ -0,0 +1,11 @@
|
||||
nodeLinker: node-modules
|
||||
|
||||
plugins:
|
||||
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
|
||||
spec: "@yarnpkg/plugin-interactive-tools"
|
||||
- path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs
|
||||
spec: "@yarnpkg/plugin-workspace-tools"
|
||||
- path: .yarn/plugins/@yarnpkg/plugin-version.cjs
|
||||
spec: "@yarnpkg/plugin-version"
|
||||
|
||||
yarnPath: .yarn/releases/yarn-3.2.4.cjs
|
||||
132
README.md
@@ -8,44 +8,108 @@
|
||||
<a href="https://discord.gg/djs"><img src="https://img.shields.io/discord/222078108977594368?color=5865F2&logo=discord&logoColor=white" alt="Discord server" /></a>
|
||||
<a href="https://www.npmjs.com/package/discord.js"><img src="https://img.shields.io/npm/v/discord.js.svg?maxAge=3600" alt="npm version" /></a>
|
||||
<a href="https://www.npmjs.com/package/discord.js"><img src="https://img.shields.io/npm/dt/discord.js.svg?maxAge=3600" alt="npm downloads" /></a>
|
||||
<a href="https://github.com/discordjs/discord.js/actions"><img src="https://github.com/discordjs/discord.js/actions/workflows/tests.yml/badge.svg" alt="Tests status" /></a>
|
||||
<a href="https://github.com/discordjs/discord.js/commits/main"><img src="https://img.shields.io/github/last-commit/discordjs/discord.js.svg?logo=github&logoColor=ffffff" alt="Last commit." /></a>
|
||||
<a href="https://github.com/discordjs/discord.js/graphs/contributors"><img src="https://img.shields.io/github/contributors/discordjs/discord.js.svg?maxAge=3600&logo=github&logoColor=fff&color=00c7be" alt="contributors" /></a>
|
||||
<a href="https://codecov.io/gh/discordjs/discord.js"><img src="https://codecov.io/gh/discordjs/discord.js/branch/main/graph/badge.svg?precision=2" alt="Code coverage" /></a>
|
||||
<a href="https://github.com/discordjs/discord.js/actions"><img src="https://github.com/discordjs/discord.js/actions/workflows/test.yml/badge.svg" alt="Tests status" /></a>
|
||||
<a href="https://codecov.io/gh/discordjs/discord.js" ><img src="https://codecov.io/gh/discordjs/discord.js/branch/main/graph/badge.svg?precision=2" alt="Code coverage" /></a>
|
||||
</p>
|
||||
<p>
|
||||
<a href="https://vercel.com/?utm_source=discordjs&utm_campaign=oss"><img src="https://raw.githubusercontent.com/discordjs/discord.js/main/.github/powered-by-vercel.svg" alt="Vercel" /></a>
|
||||
<a href="https://www.cloudflare.com"><img src="https://raw.githubusercontent.com/discordjs/discord.js/main/.github/powered-by-workers.png" alt="Cloudflare Workers" height="44" /></a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
## About
|
||||
|
||||
This repository contains multiple packages with separate [releases][github-releases]. You can find the assembled Discord API wrapper at [`discord.js`][source]. It is a powerful [Node.js](https://nodejs.org/en) module that allows you to easily interact with the [Discord API](https://discord.com/developers/docs/intro).
|
||||
discord.js is a powerful [Node.js](https://nodejs.org) module that allows you to easily interact with the
|
||||
[Discord API](https://discord.com/developers/docs/intro).
|
||||
|
||||
## Packages
|
||||
- Object-oriented
|
||||
- Predictable abstractions
|
||||
- Performant
|
||||
- 100% coverage of the Discord API
|
||||
|
||||
- `discord.js` ([source][source]) - A powerful Node.js module for interacting with the Discord API
|
||||
- `@discordjs/brokers` ([source][brokers-source]) - A collection of brokers for use with discord.js
|
||||
- `@discordjs/builders` ([source][builders-source]) - A utility package for easily building Discord API payloads
|
||||
- `@discordjs/collection` ([source][collection-source]) - A powerful utility data structure
|
||||
- `@discordjs/core` ([source][core-source]) - A thinly abstracted wrapper around the core components of the Discord API
|
||||
- `@discordjs/formatters` ([source][formatters-source]) - A collection of functions for formatting strings
|
||||
- `@discordjs/proxy` ([source][proxy-source]) - A wrapper around `@discordjs/rest` for running an HTTP proxy
|
||||
- `@discordjs/rest` ([source][rest-source]) - A module for interacting with the Discord REST API
|
||||
- `@discordjs/structures` ([source][structures-source]) - A wrapper around Discord's structures
|
||||
- `@discordjs/voice` ([source][voice-source]) - A module for interacting with the Discord Voice API
|
||||
- `@discordjs/util` ([source][util-source]) - A collection of utility functions
|
||||
- `@discordjs/ws` ([source][ws-source]) - A wrapper around Discord's gateway
|
||||
## Installation
|
||||
|
||||
**Node.js 16.9.0 or newer is required.**
|
||||
|
||||
```sh-session
|
||||
npm install discord.js
|
||||
yarn add discord.js
|
||||
pnpm add discord.js
|
||||
```
|
||||
|
||||
### Optional packages
|
||||
|
||||
- [zlib-sync](https://www.npmjs.com/package/zlib-sync) for WebSocket data compression and inflation (`npm install zlib-sync`)
|
||||
- [erlpack](https://github.com/discord/erlpack) for significantly faster WebSocket data (de)serialisation (`npm install discord/erlpack`)
|
||||
- [bufferutil](https://www.npmjs.com/package/bufferutil) for a much faster WebSocket connection (`npm install bufferutil`)
|
||||
- [utf-8-validate](https://www.npmjs.com/package/utf-8-validate) in combination with `bufferutil` for much faster WebSocket processing (`npm install utf-8-validate`)
|
||||
- [@discordjs/voice](https://www.npmjs.com/package/@discordjs/voice) for interacting with the Discord Voice API (`npm install @discordjs/voice`)
|
||||
|
||||
## Example usage
|
||||
|
||||
Install discord.js:
|
||||
|
||||
```sh-session
|
||||
npm install discord.js
|
||||
yarn add discord.js
|
||||
pnpm add discord.js
|
||||
```
|
||||
|
||||
Register a slash command against the Discord API:
|
||||
|
||||
```js
|
||||
const { REST, Routes } = require('discord.js');
|
||||
|
||||
const commands = [
|
||||
{
|
||||
name: 'ping',
|
||||
description: 'Replies with Pong!',
|
||||
},
|
||||
];
|
||||
|
||||
const rest = new REST({ version: '10' }).setToken(TOKEN);
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
console.log('Started refreshing application (/) commands.');
|
||||
|
||||
await rest.put(Routes.applicationCommands(CLIENT_ID), { body: commands });
|
||||
|
||||
console.log('Successfully reloaded application (/) commands.');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
})();
|
||||
```
|
||||
|
||||
Afterwards we can create a quite simple example bot:
|
||||
|
||||
```js
|
||||
const { Client, GatewayIntentBits } = require('discord.js');
|
||||
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
|
||||
|
||||
client.on('ready', () => {
|
||||
console.log(`Logged in as ${client.user.tag}!`);
|
||||
});
|
||||
|
||||
client.on('interactionCreate', async (interaction) => {
|
||||
if (!interaction.isChatInputCommand()) return;
|
||||
|
||||
if (interaction.commandName === 'ping') {
|
||||
await interaction.reply('Pong!');
|
||||
}
|
||||
});
|
||||
|
||||
client.login(TOKEN);
|
||||
```
|
||||
|
||||
## Links
|
||||
|
||||
- [Website][website] ([source][website-source])
|
||||
- [Documentation][documentation]
|
||||
- [Guide][guide] ([source][guide-source])
|
||||
Also see the v13 to v14 [Update Guide][guide-update], which includes updated and removed items from the library.
|
||||
See also the [Update Guide][guide-update], including updated and removed items in the library.
|
||||
- [discord.js Discord server][discord]
|
||||
- [Discord Developers Discord server][discord-developers]
|
||||
- [Discord API Discord server][discord-api]
|
||||
- [GitHub][source]
|
||||
- [npm][npm]
|
||||
- [Related libraries][related-libs]
|
||||
@@ -56,36 +120,26 @@ This repository contains multiple packages with separate [releases][github-relea
|
||||
|
||||
## Contributing
|
||||
|
||||
Please read through our [contribution guidelines][contributing] before starting a pull request. We welcome contributions of all kinds, not just code! If you're stuck for ideas, look for the [good first issue][good-first-issue] label on issues in the repository. If you have any questions about the project, feel free to ask them on [Discord][discord]. Before creating your own issue or pull request, always check to see if one already exists! Don't rush contributions, take your time and ensure you're doing it correctly.
|
||||
Before creating an issue, please ensure that it hasn't already been reported/suggested, and double-check the
|
||||
[documentation][documentation].
|
||||
See [the contribution guide][contributing] if you'd like to submit a PR.
|
||||
|
||||
## Help
|
||||
|
||||
If you don't understand something in the documentation, you are experiencing problems, or you just need a gentle nudge in the right direction, please join our [Discord server][discord].
|
||||
If you don't understand something in the documentation, you are experiencing problems, or you just need a gentle
|
||||
nudge in the right direction, please don't hesitate to join our official [discord.js Server][discord].
|
||||
|
||||
[website]: https://discord.js.org
|
||||
[website]: https://discord.js.org/
|
||||
[website-source]: https://github.com/discordjs/discord.js/tree/main/apps/website
|
||||
[documentation]: https://discord.js.org/docs
|
||||
[documentation]: https://discord.js.org/#/docs
|
||||
[guide]: https://discordjs.guide/
|
||||
[guide-source]: https://github.com/discordjs/guide
|
||||
[guide-update]: https://discordjs.guide/additional-info/changes-in-v14.html
|
||||
[discord]: https://discord.gg/djs
|
||||
[discord-developers]: https://discord.gg/discord-developers
|
||||
[discord-api]: https://discord.gg/discord-api
|
||||
[source]: https://github.com/discordjs/discord.js/tree/main/packages/discord.js
|
||||
[npm]: https://www.npmjs.com/package/discord.js
|
||||
[related-libs]: https://discord.com/developers/docs/topics/community-resources#libraries
|
||||
[rpc]: https://www.npmjs.com/package/discord-rpc
|
||||
[rpc-source]: https://github.com/discordjs/RPC
|
||||
[contributing]: https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md
|
||||
[github-releases]: https://github.com/discordjs/discord.js/releases
|
||||
[brokers-source]: https://github.com/discordjs/discord.js/tree/main/packages/brokers
|
||||
[builders-source]: https://github.com/discordjs/discord.js/tree/main/packages/builders
|
||||
[collection-source]: https://github.com/discordjs/discord.js/tree/main/packages/collection
|
||||
[core-source]: https://github.com/discordjs/discord.js/tree/main/packages/core
|
||||
[formatters-source]: https://github.com/discordjs/discord.js/tree/main/packages/formatters
|
||||
[proxy-source]: https://github.com/discordjs/discord.js/tree/main/packages/proxy
|
||||
[rest-source]: https://github.com/discordjs/discord.js/tree/main/packages/rest
|
||||
[structures-source]: https://github.com/discordjs/discord.js/tree/main/packages/structures
|
||||
[voice-source]: https://github.com/discordjs/discord.js/tree/main/packages/voice
|
||||
[util-source]: https://github.com/discordjs/discord.js/tree/main/packages/util
|
||||
[ws-source]: https://github.com/discordjs/discord.js/tree/main/packages/ws
|
||||
[good-first-issue]: https://github.com/discordjs/discord.js/contribute
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
*
|
||||
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
|
||||
*/
|
||||
"mainEntryPointFilePath": "<projectFolder>/dist-docs/index.d.ts",
|
||||
"mainEntryPointFilePath": "<projectFolder>/dist/index.d.ts",
|
||||
|
||||
/**
|
||||
* A list of NPM package names whose exports should be treated as part of this package.
|
||||
@@ -88,52 +88,9 @@
|
||||
*
|
||||
* DEFAULT VALUE: no overrideTsconfig section
|
||||
*/
|
||||
"overrideTsconfig": {
|
||||
"compilerOptions": {
|
||||
// Type Checking
|
||||
"allowUnreachableCode": false,
|
||||
"allowUnusedLabels": false,
|
||||
"exactOptionalPropertyTypes": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noImplicitOverride": true,
|
||||
"noImplicitReturns": true,
|
||||
"noPropertyAccessFromIndexSignature": false,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"strict": true,
|
||||
|
||||
// Modules
|
||||
"allowArbitraryExtensions": false,
|
||||
"allowImportingTsExtensions": false,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "nodenext",
|
||||
"resolveJsonModule": true,
|
||||
"resolvePackageJsonExports": false,
|
||||
"resolvePackageJsonImports": false,
|
||||
|
||||
// Emit
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"importHelpers": false,
|
||||
"newLine": "lf",
|
||||
"noEmitHelpers": true,
|
||||
"outDir": "dist",
|
||||
"removeComments": false,
|
||||
"sourceMap": true,
|
||||
|
||||
// Interop Constraints
|
||||
"esModuleInterop": false,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"isolatedModules": true,
|
||||
|
||||
// Language and Environment
|
||||
"experimentalDecorators": true,
|
||||
"lib": ["ESNext"],
|
||||
"target": "ES2022",
|
||||
"useDefineForClassFields": true
|
||||
}
|
||||
}
|
||||
// "overrideTsconfig": {
|
||||
// . . .
|
||||
// }
|
||||
/**
|
||||
* This option causes the compiler to be invoked with the --skipLibCheck option. This option is not recommended
|
||||
* and may cause API Extractor to produce incomplete or incorrect declarations, but it may be required when
|
||||
@@ -224,7 +181,7 @@
|
||||
/**
|
||||
* (REQUIRED) Whether to generate the .d.ts rollup file.
|
||||
*/
|
||||
"enabled": false,
|
||||
"enabled": false
|
||||
|
||||
/**
|
||||
* Specifies the output path for a .d.ts rollup file to be generated without any trimming.
|
||||
@@ -238,7 +195,7 @@
|
||||
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
|
||||
* DEFAULT VALUE: "<projectFolder>/dist/<unscopedPackageName>.d.ts"
|
||||
*/
|
||||
"untrimmedFilePath": "<projectFolder>/dist-docs/index.d.ts"
|
||||
// "untrimmedFilePath": "<projectFolder>/dist/<unscopedPackageName>.d.ts",
|
||||
|
||||
/**
|
||||
* Specifies the output path for a .d.ts rollup file to be generated with trimming for an "alpha" release.
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
NEXT_PUBLIC_LOCAL_DEV=true
|
||||
11
apps/guide/.eslintrc.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"extends": ["../../.eslintrc.json", "neon/react", "neon/astro", "neon/prettier"],
|
||||
"settings": {
|
||||
"react": {
|
||||
"version": "detect"
|
||||
}
|
||||
},
|
||||
"rules": {
|
||||
"react/jsx-filename-extension": [1, { "extensions": [".tsx", ".astro"] }]
|
||||
}
|
||||
}
|
||||
20
apps/guide/.gitignore
vendored
@@ -1,8 +1,8 @@
|
||||
# Packages
|
||||
node_modules
|
||||
node_modules/
|
||||
|
||||
# Log files
|
||||
logs
|
||||
logs/
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
@@ -13,14 +13,18 @@ pids
|
||||
|
||||
# Env
|
||||
.env
|
||||
.env*.local
|
||||
|
||||
# Dist
|
||||
.next
|
||||
.source
|
||||
dist/
|
||||
typings/
|
||||
.cache/
|
||||
build/
|
||||
api/
|
||||
src/styles/unocss.css
|
||||
.next/
|
||||
|
||||
# Miscellaneous
|
||||
.tmp
|
||||
.vscode
|
||||
.tmp/
|
||||
coverage/
|
||||
.vercel
|
||||
next-env.d.ts
|
||||
public/searchIndex
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
/** @type {import('lint-staged').Config} */
|
||||
module.exports = require('../../.lintstagedrc.json');
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
.next
|
||||
# Autogenerated
|
||||
CHANGELOG.md
|
||||
.turbo
|
||||
.vscode
|
||||
coverage
|
||||
src/assets/readme
|
||||
dist/
|
||||
docs/**/*
|
||||
!docs/index.yml
|
||||
!docs/README.md
|
||||
coverage/
|
||||
.cache
|
||||
build/
|
||||
src/styles/unocss.css
|
||||
next-env.d.ts
|
||||
.source
|
||||
api/
|
||||
.next/
|
||||
.vercel/
|
||||
.cache/
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
/** @type {import('prettier').Config} */
|
||||
module.exports = {
|
||||
...require('../../.prettierrc.json'),
|
||||
plugins: ['prettier-plugin-tailwindcss'],
|
||||
tailwindFunctions: ['cva', 'cx'],
|
||||
plugins: [
|
||||
'prettier-plugin-astro',
|
||||
'prettier-plugin-tailwindcss', // MUST come last
|
||||
],
|
||||
pluginSearchDirs: false,
|
||||
};
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
# Contributing
|
||||
|
||||
## Local development
|
||||
|
||||
Clone the code base into a local folder, `cd` into it, and install the dependencies:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/discordjs/discord.js.git
|
||||
cd discord.js/apps/guide
|
||||
pnpm --filter guide install
|
||||
```
|
||||
|
||||
You can and should use `pnpm dev` to check your changes out locally before pushing them for review.
|
||||
|
||||
## Adding pages
|
||||
|
||||
To add a new page to the guide, create a `filename.mdx` file in the folder of your choice located under `/content`. Fumadocs will pick it up and route appropriately. To list the section in the sidebar, make sure it is listed in the `meta.json` file of the directory you placed it in under the `pages` key. The order in the `pages` array determines the order pages have in the sidebar.
|
||||
|
||||
## Framework and components
|
||||
|
||||
The guide uses the fumadocs documentation framework for Next.js. You can find examples as well as documentation for the components you can use at <https://fumadocs.dev/docs/ui>.
|
||||
|
||||
## General guidelines
|
||||
|
||||
Please try your best to follow the guidelines below. They help to make the guide appear as a coherent piece of work rather than a collection of disconnected pieces with different writing styles.
|
||||
|
||||
### Spelling, grammar, and wording
|
||||
|
||||
Improper grammar, strange wording, and incorrect spelling are all things that may lead to confusion when a user reads a guide page. It's important to attempt to keep the content clear and consistent. Re-read what you've written and place yourself in the shoes of someone else for a moment to see if you can fully understand everything without any confusion.
|
||||
|
||||
Don't worry if you aren't super confident with your grammar/spelling/wording skills; all pull requests get thoroughly reviewed, and comments are left in areas that need to be fixed or could be done better/differently.
|
||||
|
||||
#### "You"/"your" instead of "we"/"our"
|
||||
|
||||
When explaining parts of the guide, we recommend to use "you" instead of "we" when referring to the read or things they can do:
|
||||
|
||||
```diff
|
||||
- To check our Node version, we can run `node -v`.
|
||||
+ To check your Node version, you can run `node -v`.
|
||||
|
||||
- To delete a message, we can do: `message.delete();`
|
||||
+ To delete a message, you can do: `message.delete();`
|
||||
|
||||
- Our final code should look like this: ...
|
||||
+ Your final code should look like this: ...
|
||||
|
||||
- Before we can actually do this, we need to update our configuration file.
|
||||
+ Before you can actually do this, you need to update your configuration file.
|
||||
```
|
||||
|
||||
#### "We" instead of "I"
|
||||
|
||||
When referring to yourself, use "we" (as in "the writers of this guide") instead of "I". For example:
|
||||
|
||||
```diff
|
||||
- If you don't already have this package installed, I would highly recommend doing so.
|
||||
+ If you don't already have this package installed, we would highly recommend doing so.
|
||||
# Valid alternative:
|
||||
+ If you don't already have this package installed, it's highly recommended that you do so.
|
||||
|
||||
- In this section, I'll be covering how to do that really cool thing everyone's asking about.
|
||||
+ In this section, we'll be covering how to do that really cool thing everyone's asking about.
|
||||
```
|
||||
|
||||
#### Inclusive language
|
||||
|
||||
Try to avoid using gendered and otherwise non-inclusive language. The following are just examples to give you an idea of what we expect. Don't understand this as a complete list of "banned terms":
|
||||
|
||||
- Use they/them/their instead of gendered pronouns (he/him/his, she/her/hers).
|
||||
- Avoid using "master" and "slave", you can use "primary" and "replica" or "secondary" instead.
|
||||
- Avoid gendered terms like "guys", "folks" and "people" work just as well.
|
||||
- Avoid ableist terms "sanity check", use "confidence check" or "coherence check" instead.
|
||||
- Avoid talking about "dummy" values, call them "placeholder" or "sample value" instead.
|
||||
|
||||
### Paragraph structure
|
||||
|
||||
Try to keep guide articles formatted nicely and easy to read. If paragraphs get too long, you can usually split them up where they introduce a new concept or facet. Adding a bit of spacing can make the guide easier to digest and follow! Try to avoid run-on sentences with many embedded clauses.
|
||||
|
||||
## Semantic components
|
||||
|
||||
You can find the full documentation for the guide framework at <https://fumadocs.dev/docs/ui/>. If you are unsure what to use when, consider looking through the existing guide pages and how they approach things.
|
||||
|
||||
### Callouts
|
||||
|
||||
You can use [Callouts](https://fumadocs.dev/docs/ui/markdown#callouts) to describe additional context that doesn't fully fit into the flow of the paragraph or requires special attention. Prefer to MDX syntax `<Callout />` over Remark `:::` admonitions.
|
||||
|
||||
### Code
|
||||
|
||||
Fumadocs integrates [Shiki transformers](https://fumadocs.dev/docs/ui/markdown#shiki-transformers) for visual highlighting through the use of [Rhype Code](https://fumadocs.dev/docs/headless/mdx/rehype-code).
|
||||
|
||||
When describing changes or additions to code, prefer using the appropriate language (`js` in most cases for this guide) with diff transformers over `diff` highlights:
|
||||
|
||||
```js
|
||||
console.log('Hello'); // [!code --]
|
||||
console.log('Hello World'); // [!code ++]
|
||||
```
|
||||
|
||||
You can put the transformer syntax above the respective line and declare ranges instead of repeating formatting intsructions. You can also combine different transformers on the same line. Note that word highlights highlight the word across the code block by default, but do respect ranges.
|
||||
|
||||
```js
|
||||
console.log('Hello'); // [!code --:2]
|
||||
console.log('World');
|
||||
// [!code ++]
|
||||
console.log('Hello World');
|
||||
```
|
||||
|
||||
```js
|
||||
// ...
|
||||
// [!code focus:2] [!code word:log:1]
|
||||
console.log('Hello World!'); // this instance of "log" is highlighted
|
||||
console.log('Foobar'); // this one is not
|
||||
// ...
|
||||
```
|
||||
|
||||
When introducing new functions in a paragraph, consider highlighting them in the following code snippet to draw additional attention to their use. For example, if you just described the `log` function:
|
||||
|
||||
```js
|
||||
console.log('Hello World'); // [!code word:log]
|
||||
```
|
||||
|
||||
Make sure to denote the file names or paths if you describe progress in a specific code sample. When descrbing multiple files, use [tab groups](https://fumadocs.dev/docs/ui/markdown#tab-groups).
|
||||
|
||||
````md
|
||||
```json title="package.json" tab="Configuration"
|
||||
{ ... }
|
||||
```
|
||||
|
||||
```js tab="Usage"
|
||||
// code showing how to use what is being configured
|
||||
```
|
||||
````
|
||||
|
||||
### Directory Structure
|
||||
|
||||
You can use the [Files](https://fumadocs.dev/docs/ui/components/files) component to visualize the expected directory structure, if it is relevant to the approach you describe.
|
||||
@@ -1,190 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
Copyright 2022 Noel Buechler
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -6,11 +6,10 @@
|
||||
<br />
|
||||
<p>
|
||||
<a href="https://discord.gg/djs"><img src="https://img.shields.io/discord/222078108977594368?color=5865F2&logo=discord&logoColor=white" alt="Discord server" /></a>
|
||||
<a href="https://github.com/discordjs/discord.js/actions"><img src="https://github.com/discordjs/discord.js/actions/workflows/tests.yml/badge.svg" alt="Build status" /></a>
|
||||
<a href="https://github.com/discordjs/discord.js/actions"><img src="https://github.com/discordjs/discord.js/actions/workflows/test.yml/badge.svg" alt="Build status" /></a>
|
||||
</p>
|
||||
<p>
|
||||
<a href="https://vercel.com/?utm_source=discordjs&utm_campaign=oss"><img src="https://raw.githubusercontent.com/discordjs/discord.js/main/.github/powered-by-vercel.svg" alt="Vercel" /></a>
|
||||
<a href="https://www.cloudflare.com"><img src="https://raw.githubusercontent.com/discordjs/discord.js/main/.github/powered-by-workers.png" alt="Cloudflare Workers" height="44" /></a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -19,28 +18,29 @@
|
||||
- [Website][website] ([source][website-source])
|
||||
- [Documentation][documentation]
|
||||
- [Guide][guide] ([source][guide-source])
|
||||
See also the [Update Guide][guide-update], including updated and removed items in the library.
|
||||
- [discord.js Discord server][discord]
|
||||
- [Discord API Discord server][discord-api]
|
||||
- [GitHub][source]
|
||||
- [Related libraries][related-libs]
|
||||
|
||||
## Contributing
|
||||
|
||||
Before creating an issue, please ensure that it hasn't already been reported/suggested, and double-check the existing guide.
|
||||
See [the contribution guide][./contributing] if you'd like to submit a PR.
|
||||
|
||||
## Local Development
|
||||
|
||||
To install and run just the guide portion of the repository for development, you can install dependencies with `pnpm --filter guide install` and serve a development version of the guide on localhost with `pnpm dev`.
|
||||
See [the contribution guide][contributing] if you'd like to submit a PR.
|
||||
|
||||
## Help
|
||||
|
||||
If you don't understand something in the documentation, you are experiencing problems, or you just need a gentle nudge in the right direction, please don't hesitate to join our official [discord.js Server][discord].
|
||||
If you don't understand something in the documentation, you are experiencing problems, or you just need a gentle
|
||||
nudge in the right direction, please don't hesitate to join our official [discord.js Server][discord].
|
||||
|
||||
[website]: https://discord.js.org
|
||||
[website]: https://discord.js.org/
|
||||
[website-source]: https://github.com/discordjs/discord.js/tree/main/apps/website
|
||||
[documentation]: https://discord.js.org/docs
|
||||
[guide]: https://discord.js/guide
|
||||
[guide-source]: https://github.com/discordjs/discord.js/tree/main/apps/guide
|
||||
[documentation]: https://discord.js.org/
|
||||
[guide]: https://discordjs.guide/
|
||||
[guide-source]: https://github.com/discordjs/guide
|
||||
[guide-update]: https://discordjs.guide/additional-info/changes-in-v14.html
|
||||
[discord]: https://discord.gg/djs
|
||||
[discord-api]: https://discord.gg/discord-api
|
||||
[source]: https://github.com/discordjs/discord.js/tree/main/apps/guide
|
||||
[related-libs]: https://discord.com/developers/docs/topics/community-resources#libraries
|
||||
[contributing]: https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md
|
||||
|
||||
117
apps/guide/astro.config.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import { fileURLToPath, URL } from 'node:url';
|
||||
import image from '@astrojs/image';
|
||||
import mdx from '@astrojs/mdx';
|
||||
import prefetch from '@astrojs/prefetch';
|
||||
import react from '@astrojs/react';
|
||||
import { remarkCodeHike } from '@code-hike/mdx';
|
||||
import { defineConfig } from 'astro/config';
|
||||
import compress from 'astro-compress';
|
||||
import critters from 'astro-critters';
|
||||
import { type Node, toString } from 'hast-util-to-string';
|
||||
import { h } from 'hastscript';
|
||||
import { escape } from 'html-escaper';
|
||||
import rehypeAutolinkHeadings from 'rehype-autolink-headings';
|
||||
import rehypeSlug from 'rehype-slug';
|
||||
import shikiThemeDarkPlus from 'shiki/themes/dark-plus.json' assert { type: 'json' };
|
||||
import Unocss from 'unocss/astro';
|
||||
|
||||
const LinkIcon = h(
|
||||
'svg',
|
||||
{
|
||||
width: '1rem',
|
||||
height: '1rem',
|
||||
viewBox: '0 0 24 24',
|
||||
fill: 'none',
|
||||
stroke: 'currentColor',
|
||||
strokeWidth: '2',
|
||||
strokeLinecap: 'round',
|
||||
strokeLinejoin: 'round',
|
||||
},
|
||||
h('path', {
|
||||
// eslint-disable-next-line id-length
|
||||
d: 'M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71',
|
||||
}),
|
||||
h('path', {
|
||||
// eslint-disable-next-line id-length
|
||||
d: 'M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71',
|
||||
}),
|
||||
);
|
||||
|
||||
const createSROnlyLabel = (text: string) => {
|
||||
const node = h('span.sr-only', `Section titled ${escape(text)}`);
|
||||
node.properties!['is:raw'] = true;
|
||||
return node;
|
||||
};
|
||||
|
||||
const rootDir = new URL('../../', import.meta.url);
|
||||
|
||||
export default defineConfig({
|
||||
integrations: [
|
||||
react(),
|
||||
mdx({
|
||||
remarkPlugins: [[remarkCodeHike, { autoImport: false, theme: shikiThemeDarkPlus, lineNumbers: true }]],
|
||||
rehypePlugins: [
|
||||
rehypeSlug,
|
||||
[
|
||||
rehypeAutolinkHeadings,
|
||||
{
|
||||
properties: {
|
||||
class:
|
||||
'relative inline-flex w-6 h-6 place-items-center place-content-center outline-0 text-black dark:text-white ml-2',
|
||||
},
|
||||
behavior: 'after',
|
||||
group: ({ tagName }: { tagName: string }) =>
|
||||
h('div', {
|
||||
class: `[&>*]:inline-block [&>h1]:m-0 [&>h2]:m-0 [&>h3]:m-0 [&>h4]:m-0 level-${tagName}`,
|
||||
tabIndex: -1,
|
||||
}),
|
||||
content: (heading: Node) => [
|
||||
h(
|
||||
`span.anchor-icon`,
|
||||
{
|
||||
ariaHidden: 'true',
|
||||
},
|
||||
LinkIcon,
|
||||
),
|
||||
createSROnlyLabel(toString(heading)),
|
||||
],
|
||||
},
|
||||
],
|
||||
],
|
||||
}),
|
||||
image({
|
||||
serviceEntryPoint: '@astrojs/image/sharp',
|
||||
}),
|
||||
prefetch({
|
||||
throttle: 3,
|
||||
}),
|
||||
Unocss({
|
||||
configFile: fileURLToPath(new URL('unocss.config.ts', rootDir)),
|
||||
}),
|
||||
critters(),
|
||||
compress(),
|
||||
],
|
||||
markdown: {
|
||||
extendDefaultPlugins: true,
|
||||
syntaxHighlight: false,
|
||||
},
|
||||
vite: {
|
||||
resolve: {
|
||||
alias: {
|
||||
'ariakit/button': fileURLToPath(new URL('node_modules/ariakit/esm/button/index.js', rootDir)),
|
||||
'ariakit/disclosure': fileURLToPath(new URL('node_modules/ariakit/esm/disclosure/index.js', rootDir)),
|
||||
'ariakit/separator': fileURLToPath(new URL('node_modules/ariakit/esm/separator/index.js', rootDir)),
|
||||
'ariakit-utils/dom': fileURLToPath(new URL('node_modules/ariakit-utils/esm/dom.js', rootDir)),
|
||||
'ariakit-utils/events': fileURLToPath(new URL('node_modules/ariakit-utils/esm/events.js', rootDir)),
|
||||
'ariakit-utils/focus': fileURLToPath(new URL('node_modules/ariakit-utils/esm/focus.js', rootDir)),
|
||||
'ariakit-utils/hooks': fileURLToPath(new URL('node_modules/ariakit-utils/esm/hooks.js', rootDir)),
|
||||
'ariakit-utils/misc': fileURLToPath(new URL('node_modules/ariakit-utils/esm/misc.js', rootDir)),
|
||||
'ariakit-utils/platform': fileURLToPath(new URL('node_modules/ariakit-utils/esm/platform.js', rootDir)),
|
||||
'ariakit-utils/system': fileURLToPath(new URL('node_modules/ariakit-utils/esm/system.js', rootDir)),
|
||||
'react-icons/fi': fileURLToPath(new URL('node_modules/react-icons/fi/index.esm.js', rootDir)),
|
||||
'react-icons/vsc': fileURLToPath(new URL('node_modules/react-icons/vsc/index.esm.js', rootDir)),
|
||||
'react-use': fileURLToPath(new URL('node_modules/react-use/esm/index.js', rootDir)),
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1,103 +0,0 @@
|
||||
---
|
||||
title: Cooldowns
|
||||
---
|
||||
|
||||
Spam is something you generally want to avoid, especially if one of your commands require calls to other APIs or takes a bit of time to build/send.
|
||||
|
||||
<Callout>
|
||||
This section assumes you followed the [Command Handling](../app-creation/handling-commands) part of the guide.
|
||||
</Callout>
|
||||
|
||||
First, add a cooldown property to your command. This will determine how long the user would have to wait (in seconds) before using the command again.
|
||||
|
||||
```js title="commands/utility/ping.js"
|
||||
const { SlashCommandBuilder } = require('discord.js');
|
||||
|
||||
module.exports = {
|
||||
cooldown: 5, // [!code ++]
|
||||
data: new SlashCommandBuilder().setName('ping').setDescription('Replies with Pong!'),
|
||||
async execute(interaction) {
|
||||
// ...
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
In your main file, initialize a [Collection](../additional-info/collections) to store cooldowns of commands:
|
||||
|
||||
```js
|
||||
client.cooldowns = new Collection();
|
||||
```
|
||||
|
||||
The key will be the command names, and the values will be Collections associating the user's id (key) to the last time (value) this user used this command. Overall the logical path to get a user's last usage of a command will be `cooldowns > command > user > timestamp`.
|
||||
|
||||
In your `InteractionCreate` event handler, add the following code:
|
||||
|
||||
```js title="index.js / interactionCreate.js (if you followed the event handler section)"
|
||||
// ...
|
||||
// [!code ++:14]
|
||||
const { cooldowns } = interaction.client;
|
||||
|
||||
if (!cooldowns.has(command.data.name)) {
|
||||
cooldowns.set(command.data.name, new Collection());
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
const timestamps = cooldowns.get(command.data.name);
|
||||
const defaultCooldownDuration = 3;
|
||||
const cooldownAmount = (command.cooldown ?? defaultCooldownDuration) * 1_000;
|
||||
|
||||
if (timestamps.has(interaction.user.id)) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
You check if the `cooldowns` Collection already has an entry for the command being used. If this is not the case, you can add a new entry, where the value is initialized as an empty Collection. Next, create the following variables:
|
||||
|
||||
1. `now`: The current timestamp.
|
||||
2. `timestamps`: A reference to the Collection of user ids and timestamp key/value pairs for the triggered command.
|
||||
3. `cooldownAmount`: The specified cooldown for the command, converted to milliseconds for straightforward calculation. If none is specified, this defaults to three seconds.
|
||||
|
||||
If the user has already used this command in this session, get the timestamp, calculate the expiration time, and inform the user of the amount of time they need to wait before using this command again. Note the use of the `return` statement here, causing the code below this snippet to execute only if the user has not used this command in this session or the wait has already expired.
|
||||
|
||||
Continuing with your current setup, this is the complete `if` statement:
|
||||
|
||||
```js title="index.js / interactionCreate.js (if you followed the event handler section)"
|
||||
const defaultCooldownDuration = 3;
|
||||
const cooldownAmount = (command.cooldown ?? defaultCooldownDuration) * 1_000;
|
||||
|
||||
// [!code focus:13]
|
||||
if (timestamps.has(interaction.user.id)) {
|
||||
// ... // [!code --]
|
||||
// [!code ++:9]
|
||||
const expirationTime = timestamps.get(interaction.user.id) + cooldownAmount;
|
||||
|
||||
if (now < expirationTime) {
|
||||
const expiredTimestamp = Math.round(expirationTime / 1_000);
|
||||
return interaction.reply({
|
||||
content: `Please wait, you are on a cooldown for \`${command.data.name}\`. You can use it again <t:${expiredTimestamp}:R>.`,
|
||||
flags: MessageFlags.Ephemeral,
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Since the `timestamps` Collection has the user's id as the key, you can use the `get()` method on it to get the value and sum it up with the `cooldownAmount` variable to get the correct expiration timestamp and further check to see if it's expired or not.
|
||||
|
||||
The previous user check serves as a precaution in case the user leaves the guild. You can now use the `setTimeout` method, which will allow you to execute a function after a specified amount of time and remove the timeout.
|
||||
|
||||
```js
|
||||
// [!code focus]
|
||||
if (timestamps.has(interaction.user.id)) {
|
||||
const expiredTimestamp = Math.round(expirationTime / 1_000);
|
||||
return interaction.reply({
|
||||
content: `Please wait, you are on a cooldown for \`${command.data.name}\`. You can use it again <t:${expiredTimestamp}:R>.`,
|
||||
flags: MessageFlags.Ephemeral,
|
||||
});
|
||||
} // [!code focus]
|
||||
|
||||
// [!code ++:2] [!code focus:2]
|
||||
timestamps.set(interaction.user.id, now);
|
||||
setTimeout(() => timestamps.delete(interaction.user.id), cooldownAmount);
|
||||
```
|
||||
|
||||
This line causes the entry for the user under the specified command to be deleted after the command's cooldown time has expired for them.
|
||||
@@ -1,74 +0,0 @@
|
||||
---
|
||||
title: Reloading Commands
|
||||
---
|
||||
|
||||
When writing your commands, you may find it tedious to restart your bot every time for testing the smallest changes. With a command handler, you can eliminate this issue and reload your commands while your bot is running.
|
||||
|
||||
<Callout>
|
||||
ESM does not support require and clearing import cache. You can use [hot-esm](https://www.npmjs.com/package/hot-esm)
|
||||
to import files without cache. Windows support is experimental per [this
|
||||
issue](https://github.com/vinsonchuong/hot-esm/issues/33).
|
||||
</Callout>
|
||||
|
||||
<Callout>
|
||||
This section assumes you followed the [Command Handling](../app-creation/handling-commands) part of the guide.
|
||||
</Callout>
|
||||
|
||||
```js title="commands/utility/reload.js"
|
||||
const { SlashCommandBuilder } = require('discord.js');
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('reload')
|
||||
.setDescription('Reloads a command.')
|
||||
.addStringOption((option) => option.setName('command').setDescription('The command to reload.').setRequired(true)),
|
||||
async execute(interaction) {
|
||||
// ...
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
First off, you need to check if the command you want to reload exists. You can do this check similarly to getting a command.
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
// ...
|
||||
// [!code focus:10]
|
||||
async execute(interaction) {
|
||||
// ... // [!code --]
|
||||
// [!code ++:6]
|
||||
const commandName = interaction.options.getString('command', true).toLowerCase();
|
||||
const command = interaction.client.commands.get(commandName);
|
||||
|
||||
if (!command) {
|
||||
return interaction.reply(`There is no command with name \`${commandName}\`!`);
|
||||
}
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
<Callout type="warn">
|
||||
The reload command ideally should not be used by every user. You should deploy it as a guild command in a private
|
||||
guild.
|
||||
</Callout>
|
||||
|
||||
To build the correct file path, you will need the file name. You can use `command.data.name` for doing that.
|
||||
|
||||
In theory, all there is to do is delete the previous command from `client.commands` and require the file again. In practice, you cannot do this easily as `require()` caches the file. If you were to require it again, you would load the previously cached file without any changes. You first need to delete the file from `require.cache`, and only then should you require and set the command file to `client.commands`:
|
||||
|
||||
```js
|
||||
delete require.cache[require.resolve(`./${command.data.name}.js`)];
|
||||
|
||||
try {
|
||||
const newCommand = require(`./${command.data.name}.js`);
|
||||
interaction.client.commands.set(newCommand.data.name, newCommand);
|
||||
await interaction.reply(`Command \`${newCommand.data.name}\` was reloaded!`);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
await interaction.reply(
|
||||
`There was an error while reloading a command \`${command.data.name}\`:\n\`${error.message}\``,
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
The snippet above uses a `try...catch` block to load the command file and add it to `client.commands`. In case of an error, it will log the full error to the console and notify the user about it with the error's message component `error.message`. Note that you never actually delete the command from the commands Collection and instead overwrite it. This behavior prevents you from deleting a command and ending up with no command at all after a failed `require()` call, as each use of the reload command checks that Collection again.
|
||||
@@ -1,674 +0,0 @@
|
||||
---
|
||||
title: Updating to v14
|
||||
---
|
||||
|
||||
## Before you start
|
||||
|
||||
Make sure you're using the latest LTS version of Node. To check your Node version, use `node -v` in your terminal or command prompt, and if it's not high enough, update it! There are many resources online to help you with this step based on your host system.
|
||||
|
||||
### Various packages are now included in v14
|
||||
|
||||
If you previously had `@discordjs/builders`, `@discordjs/formatters`, `@discordjs/rest`, or `discord-api-types` manually installed, it's _highly_ recommended that you uninstall the packages to avoid package version conflicts.
|
||||
|
||||
```sh tab="npm"
|
||||
npm uninstall @discordjs/builders @discordjs/formatters @discordjs/rest discord-api-types
|
||||
```
|
||||
|
||||
```sh tab="yarn"
|
||||
yarn remove @discordjs/builders @discordjs/formatters @discordjs/rest discord-api-types
|
||||
```
|
||||
|
||||
```sh tab="pnpm"
|
||||
pnpm remove @discordjs/builders @discordjs/formatters @discordjs/rest discord-api-types
|
||||
```
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
### API version
|
||||
|
||||
discord.js v14 makes the switch to Discord API v10!
|
||||
|
||||
### Common Breakages
|
||||
|
||||
### Enum Values
|
||||
|
||||
Any areas that used to accept a `string` or `number` type for an enum parameter will now only accept exclusively `number`s.
|
||||
|
||||
In addition, the old enums exported by discord.js v13 and lower are replaced with new enums from [discord-api-types](https://discord-api-types.dev/api/discord-api-types-v10).
|
||||
|
||||
#### New enum differences
|
||||
|
||||
Most of the difference between enums from discord.js and discord-api-types can be summarized as so:
|
||||
|
||||
1. Enums are singular, i.e., `ApplicationCommandOptionTypes` -> `ApplicationCommandOptionType`
|
||||
2. Enums that are prefixed with `Message` no longer have the `Message` prefix, i.e., `MessageButtonStyles` -> `ButtonStyle`
|
||||
3. Enum values are `PascalCase` rather than `SCREAMING_SNAKE_CASE`, i.e., `.CHAT_INPUT` -> `.ChatInput`
|
||||
|
||||
<Callout>
|
||||
You might be inclined to use raw `number`s (most commonly referred to as [magic numbers](<https://en.wikipedia.org/wiki/Magic_number_(programming)>)) instead of enum values. This is highly discouraged. Enums provide more readability and are more resistant to changes in the API. Magic numbers can obscure the meaning of your code in many ways, check out this [blog post](https://blog.webdevsimplified.com/2020-02/magic-numbers/) if you want more context on as to why they shouldn't be used.
|
||||
</Callout>
|
||||
|
||||
#### Common enum breakages
|
||||
|
||||
Areas like `Client` initialization, JSON slash commands and JSON message components will likely need to be modified to accommodate these changes:
|
||||
|
||||
##### Common Client Initialization Changes
|
||||
|
||||
```js
|
||||
const { Client, Intents } = require('discord.js'); // [!code --]
|
||||
const { Client, GatewayIntentBits, Partials } = require('discord.js'); // [!code ++]
|
||||
|
||||
const client = new Client({ intents: [Intents.FLAGS.GUILDS], partials: ['CHANNEL'] }); // [!code --]
|
||||
const client = new Client({ intents: [GatewayIntentBits.Guilds], partials: [Partials.Channel] }); // [!code ++]
|
||||
```
|
||||
|
||||
##### Common Application Command Data changes
|
||||
|
||||
```js
|
||||
const { ApplicationCommandType, ApplicationCommandOptionType } = require('discord.js'); // [!code ++]
|
||||
|
||||
const command = {
|
||||
name: 'ping',
|
||||
type: 'CHAT_INPUT', // [!code --]
|
||||
type: ApplicationCommandType.ChatInput, // [!code ++]
|
||||
options: [
|
||||
{
|
||||
name: 'option',
|
||||
description: 'A sample option',
|
||||
type: 'STRING', // [!code --]
|
||||
type: ApplicationCommandOptionType.String, // [!code ++]
|
||||
},
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
##### Common Button Data changes
|
||||
|
||||
```js
|
||||
const { ButtonStyle } = require('discord.js'); // [!code ++]
|
||||
|
||||
const button = {
|
||||
label: 'test',
|
||||
style: 'PRIMARY', // [!code --]
|
||||
style: ButtonStyle.Primary, // [!code ++]
|
||||
customId: '1234',
|
||||
};
|
||||
```
|
||||
|
||||
### Removal of method-based type guards
|
||||
|
||||
#### Channels
|
||||
|
||||
Some channel type guard methods that narrowed to one channel type have been removed. Instead compare the `type` property against a [ChannelType](https://discord-api-types.dev/api/discord-api-types-v10/enum/ChannelType) enum member to narrow channels.
|
||||
|
||||
```js
|
||||
const { ChannelType } = require('discord.js'); // [!code ++]
|
||||
|
||||
channel.isText(); // [!code --]
|
||||
channel.type === ChannelType.GuildText; // [!code ++]
|
||||
|
||||
channel.isVoice(); // [!code --]
|
||||
channel.type === ChannelType.GuildVoice; // [!code ++]
|
||||
|
||||
channel.isDM(); // [!code --]
|
||||
channel.type === ChannelType.DM; // [!code ++]
|
||||
```
|
||||
|
||||
### Builders
|
||||
|
||||
Builders are no longer returned by the API like they were previously. For example you send the API an `EmbedBuilder` but you receive an `Embed` of the same data from the API. This may affect how your code handles received structures such as components. Refer to [message component changes section](#messagecomponent) for more details.
|
||||
|
||||
Added `disableValidators()` and `enableValidators()` as top-level exports which disable or enable validation (enabled by default).
|
||||
|
||||
### Consolidation of `create()` & `edit()` parameters
|
||||
|
||||
Various `create()` and `edit()` methods on managers and objects have had their parameters consolidated. The changes are below:
|
||||
|
||||
- `Guild#edit()` now takes `reason` in the `data` parameter
|
||||
- `GuildChannel#edit()` now takes `reason` in the `data` parameter
|
||||
- `GuildEmoji#edit()` now takes `reason` in the `data` parameter
|
||||
- `Role#edit()` now takes `reason` in the `data` parameter
|
||||
- `Sticker#edit()` now takes `reason` in the `data` parameter
|
||||
- `ThreadChannel#edit()` now takes `reason` in the `data` parameter
|
||||
- `GuildChannelManager#create()` now takes `name` in the `options` parameter
|
||||
- `GuildChannelManager#createWebhook()` (and other text-based channels) now takes `channel` and `name` in the `options` parameter
|
||||
- `GuildChannelManager#edit()` now takes `reason` as a part of `data`
|
||||
- `GuildEmojiManager#edit()` now takes `reason` as a part of `data`
|
||||
- `GuildManager#create()` now takes `name` as a part of `options`
|
||||
- `GuildMemberManager#edit()` now takes `reason` as a part of `data`
|
||||
- `GuildMember#edit()` now takes `reason` as a part of `data`
|
||||
- `GuildStickerManager#edit()` now takes `reason` as a part of `data`
|
||||
- `RoleManager#edit()` now takes `reason` as a part of `options`
|
||||
- `Webhook#edit()` now takes `reason` as a part of `options`
|
||||
- `GuildEmojiManager#create()` now takes `attachment` and `name` as a part of `options`
|
||||
- `GuildStickerManager#create()` now takes `file`, `name`, and `tags` as a part of `options`
|
||||
|
||||
### Activity
|
||||
|
||||
The following properties have been removed as they are not documented by Discord:
|
||||
|
||||
- `Activity#id`
|
||||
- `Activity#platform`
|
||||
- `Activity#sessionId`
|
||||
- `Activity#syncId`
|
||||
|
||||
### Application
|
||||
|
||||
`Application#fetchAssets()` has been removed as it is no longer supported by the API.
|
||||
|
||||
### BitField
|
||||
|
||||
- BitField constituents now have a `BitField` suffix to avoid naming conflicts with the enum names:
|
||||
|
||||
```js
|
||||
new Permissions(); // [!code --]
|
||||
new PermissionsBitField(); // [!code ++]
|
||||
|
||||
new MessageFlags(); // [!code --]
|
||||
new MessageFlagsBitField(); // [!code ++]
|
||||
|
||||
new ThreadMemberFlags(); // [!code --]
|
||||
new ThreadMemberFlagsBitField(); // [!code ++]
|
||||
|
||||
new UserFlags(); // [!code --]
|
||||
new UserFlagsBitField(); // [!code ++]
|
||||
|
||||
new SystemChannelFlags(); // [!code --]
|
||||
new SystemChannelFlagsBitField(); // [!code ++]
|
||||
|
||||
new ApplicationFlags(); // [!code --]
|
||||
new ApplicationFlagsBitField(); // [!code ++]
|
||||
|
||||
new Intents(); // [!code --]
|
||||
new IntentsBitField(); // [!code ++]
|
||||
|
||||
new ActivityFlags(); // [!code --]
|
||||
new ActivityFlagsBitField(); // [!code ++]
|
||||
```
|
||||
|
||||
- `#FLAGS` has been renamed to `#Flags`
|
||||
|
||||
### CDN
|
||||
|
||||
The methods that return CDN URLs have changed. Here is an example on a User:
|
||||
|
||||
```js
|
||||
const url = user.displayAvatarURL({ dynamic: true, format: 'png', size: 1_024 }); // [!code --]
|
||||
const url = user.displayAvatarURL({ extension: 'png', size: 1_024 }); // [!code ++]
|
||||
```
|
||||
|
||||
Dynamic URLs use `ImageURLOptions` and static URLs use `BaseImageURLOptions`. Since dynamic URLs are returned by default, this option has been renamed to `forceStatic` which forces the return of a static URL. Additionally, `format` has been renamed to `extension`.
|
||||
|
||||
### CategoryChannel
|
||||
|
||||
`CategoryChannel#children` is no longer a `Collection` of channels the category contains. It is now a manager (`CategoryChannelChildManager`). This also means `CategoryChannel#createChannel()` has been moved to the `CategoryChannelChildManager`.
|
||||
|
||||
### Channel
|
||||
|
||||
The following type guards have been removed:
|
||||
|
||||
- `Channel#isText()`
|
||||
- `Channel#isVoice()`
|
||||
- `Channel#isDirectory()`
|
||||
- `Channel#isDM()`
|
||||
- `Channel#isGroupDM()`
|
||||
- `Channel#isCategory()`
|
||||
- `Channel#isNews()`
|
||||
|
||||
Refer to [this section](#channels) for more context.
|
||||
|
||||
The base channel class is now `BaseChannel`.
|
||||
|
||||
### Client
|
||||
|
||||
The `restWsBridgeTimeout` client option has been removed.
|
||||
|
||||
### CommandInteractionOptionResolver
|
||||
|
||||
`CommandInteractionOptionResolver#getMember()` no longer has a parameter for `required`. See [this pull request](https://github.com/discordjs/discord.js/pull/7188) for more information.
|
||||
|
||||
### Constants
|
||||
|
||||
- Many constant objects and key arrays are now top-level exports for example:
|
||||
|
||||
```js
|
||||
const { Constants } = require('discord.js'); // [!code --]
|
||||
const { Colors } = Constants; // [!code --]
|
||||
const { Colors } = require('discord.js'); // [!code ++]
|
||||
```
|
||||
|
||||
- The refactored constants structures have `PascalCase` member names as opposed to `SCREAMING_SNAKE_CASE` member names.
|
||||
|
||||
- Many of the exported constants structures have been replaced and renamed:
|
||||
|
||||
```js
|
||||
Opcodes; // [!code --]
|
||||
GatewayOpcodes; // [!code ++]
|
||||
|
||||
WSEvents; // [!code --]
|
||||
GatewayDispatchEvents; // [!code ++]
|
||||
|
||||
WSCodes; // [!code --]
|
||||
GatewayCloseCodes; // [!code ++]
|
||||
|
||||
InviteScopes; // [!code --]
|
||||
OAuth2Scopes; // [!code ++]
|
||||
```
|
||||
|
||||
### Events
|
||||
|
||||
The `message` and `interaction` events are now removed. Use `messageCreate` and `interactionCreate` instead.
|
||||
|
||||
`applicationCommandCreate`, `applicationCommandDelete` and `applicationCommandUpdate` have all been removed. See [this pull request](https://github.com/discordjs/discord.js/pull/6492) for more information.
|
||||
|
||||
The `threadMembersUpdate` event now emits the users who were added, the users who were removed, and the thread respectively.
|
||||
|
||||
### GuildBanManager
|
||||
|
||||
Developers should utilise `deleteMessageSeconds` instead of `days` and `deleteMessageDays`:
|
||||
|
||||
```js
|
||||
<GuildBanManager>.create('123456789', {
|
||||
days: 3 // [!code --]
|
||||
deleteMessageDays: 3 // [!code --]
|
||||
deleteMessageSeconds: 3 * 24 * 60 * 60 // [!code ++]
|
||||
});
|
||||
```
|
||||
|
||||
`deleteMessageDays` (introduced with version 14) and `days` are both deprecated and will be removed in the future.
|
||||
|
||||
### Guild
|
||||
|
||||
`Guild#setRolePositions()` and `Guild#setChannelPositions()` have been removed. Use `RoleManager#setPositions()` and `GuildChannelManager#setPositions()` instead respectively.
|
||||
|
||||
`Guild#maximumPresences` no longer has a default value of 25,000.
|
||||
|
||||
`Guild#me` has been moved to `GuildMemberManager#me`. See [this pull request](https://github.com/discordjs/discord.js/pull/7669) for more information.
|
||||
|
||||
### GuildAuditLogs & GuildAuditLogsEntry
|
||||
|
||||
`GuildAuditLogs.build()` has been removed as it has been deemed defunct. There is no alternative.
|
||||
|
||||
The following properties & methods have been moved to the `GuildAuditLogsEntry` class:
|
||||
|
||||
- `GuildAuditLogs.Targets`
|
||||
- `GuildAuditLogs.actionType()`
|
||||
- `GuildAuditLogs.targetType()`
|
||||
|
||||
### GuildMember
|
||||
|
||||
`GuildMember#pending` is now nullable to account for partial guild members. See [this issue](https://github.com/discordjs/discord.js/issues/6546) for more information.
|
||||
|
||||
### IntegrationApplication
|
||||
|
||||
`IntegrationApplication#summary` has been removed as it is no longer supported by the API.
|
||||
|
||||
### Interaction
|
||||
|
||||
Whenever an interaction is replied to and one fetches the reply, it could possibly give an `APIMessage` if the guild was not cached. However, interaction replies now always return an `InteractionCallbackResponse` with `withResponse` set to `true`.
|
||||
|
||||
The base interaction class is now `BaseInteraction`.
|
||||
|
||||
### Invite
|
||||
|
||||
`Invite#inviter` is now a getter and resolves structures from the cache.
|
||||
|
||||
### MessageAttachment
|
||||
|
||||
`MessageAttachment` has now been renamed to `AttachmentBuilder`. // [!code --]
|
||||
|
||||
```js
|
||||
new MessageAttachment(buffer, 'image.png'); // [!code --]
|
||||
new AttachmentBuilder(buffer, { name: 'image.png' }); // [!code ++]
|
||||
```
|
||||
|
||||
### MessageComponent
|
||||
|
||||
- MessageComponents have been renamed as well. They no longer have the `Message` prefix, and now have a `Builder` suffix:
|
||||
|
||||
```js
|
||||
const button = new MessageButton(); // [!code --]
|
||||
const button = new ButtonBuilder(); // [!code ++]
|
||||
|
||||
const selectMenu = new MessageSelectMenu(); // [!code --]
|
||||
const selectMenu = new StringSelectMenuBuilder(); // [!code ++]
|
||||
|
||||
const actionRow = new MessageActionRow(); // [!code --]
|
||||
const actionRow = new ActionRowBuilder(); // [!code ++]
|
||||
|
||||
const textInput = new TextInputComponent(); // [!code --]
|
||||
const textInput = new TextInputBuilder(); // [!code ++]
|
||||
```
|
||||
|
||||
- Components received from the API are no longer directly mutable. If you wish to mutate a component from the API, use `ComponentBuilder#from`. For example, if you want to make a button mutable:
|
||||
|
||||
```js
|
||||
const editedButton = receivedButton // [!code --]
|
||||
.setDisabled(true); // [!code --]
|
||||
const { ButtonBuilder } = require('discord.js'); // [!code ++]
|
||||
const editedButton = ButtonBuilder.from(receivedButton) // [!code ++]
|
||||
.setDisabled(true); // [!code ++]
|
||||
```
|
||||
|
||||
### MessageManager
|
||||
|
||||
`MessageManager#fetch()`'s second parameter has been removed. The `BaseFetchOptions` the second parameter once was is now merged into the first parameter.
|
||||
|
||||
```js
|
||||
messageManager.fetch('1234567890', { cache: false, force: true }); // [!code --]
|
||||
messageManager.fetch({ message: '1234567890', cache: false, force: true }); // [!code ++]
|
||||
```
|
||||
|
||||
### MessageSelectMenu
|
||||
|
||||
- `MessageSelectMenu` has been renamed to `StringSelectMenuBuilder`
|
||||
- `StringSelectMenuBuilder#addOption()` has been removed. Use `StringSelectMenuBuilder#addOptions()` instead.
|
||||
|
||||
### MessageEmbed
|
||||
|
||||
- `MessageEmbed` has now been renamed to `EmbedBuilder`.
|
||||
- `EmbedBuilder#setAuthor()` now accepts a sole `EmbedAuthorOptions` object.
|
||||
- `EmbedBuilder#setFooter()` now accepts a sole `EmbedFooterOptions` object.
|
||||
- `EmbedBuilder#addField()` has been removed. Use `EmbedBuilder#addFields()` instead.
|
||||
|
||||
```js
|
||||
new MessageEmbed().addField('Inline field title', 'Some value here', true); // [!code --]
|
||||
new EmbedBuilder().addFields([ // [!code ++]
|
||||
{ name: 'one', value: 'one', inline: true }, // [!code ++]
|
||||
{ name: 'two', value: 'two', inline: true }, // [!code ++]
|
||||
+]);
|
||||
```
|
||||
|
||||
### Modal
|
||||
|
||||
- `Modal` has been renamed as well and now has a `Builder` suffix:
|
||||
|
||||
```js
|
||||
const modal = new Modal(); // [!code --]
|
||||
const modal = new ModalBuilder(); // [!code ++]
|
||||
```
|
||||
|
||||
### PartialTypes
|
||||
|
||||
The `PartialTypes` string array has been removed. Use the `Partials` enum instead.
|
||||
|
||||
In addition to this, there is now a new partial: `Partials.ThreadMember`.
|
||||
|
||||
### Permissions
|
||||
|
||||
Thread permissions `USE_PUBLIC_THREADS` and `USE_PRIVATE_THREADS` have been removed as they are deprecated in the API. Use `CREATE_PUBLIC_THREADS` and `CREATE_PRIVATE_THREADS` respectively.
|
||||
|
||||
`ManageEmojisAndStickers` has been deprecated due to API changes. Its replacement is `ManageGuildExpressions`. See [this pull request](https://github.com/discord/discord-api-docs/pull/6017) for more information.
|
||||
|
||||
### PermissionOverwritesManager
|
||||
|
||||
Overwrites are now keyed by the `PascalCase` permission key rather than the `SCREAMING_SNAKE_CASE` permission key.
|
||||
|
||||
### REST Events
|
||||
|
||||
#### apiRequest
|
||||
|
||||
This REST event has been removed as discord.js now uses [Undici](https://github.com/nodejs/undici) as the underlying request handler. You must now use a [Diagnostics Channel](https://undici.nodejs.org/#/docs/api/DiagnosticsChannel). Here is a simple example:
|
||||
|
||||
```js
|
||||
import diagnosticsChannel from 'node:diagnostics_channel';
|
||||
|
||||
diagnosticsChannel.channel('undici:request:create').subscribe((data) => {
|
||||
// If you use TypeScript, `data` may be casted as
|
||||
// `DiagnosticsChannel.RequestCreateMessage`
|
||||
// from Undici to receive type definitions.
|
||||
const { request } = data;
|
||||
console.log(request.method); // Log the method
|
||||
console.log(request.path); // Log the path
|
||||
console.log(request.headers); // Log the headers
|
||||
console.log(request); // Or just log everything!
|
||||
});
|
||||
```
|
||||
|
||||
You can find further examples at the [Undici Diagnostics Channel documentation](https://undici.nodejs.org/#/docs/api/DiagnosticsChannel).
|
||||
|
||||
#### apiResponse
|
||||
|
||||
This REST event has been renamed to `response` and moved to `Client#rest`:
|
||||
|
||||
```js
|
||||
client.on('apiResponse', ...); // [!code --]
|
||||
client.rest.on('response', ...); // [!code ++]
|
||||
```
|
||||
|
||||
#### invalidRequestWarning
|
||||
|
||||
This REST event has been moved to `Client#rest`:
|
||||
|
||||
```js
|
||||
client.on('invalidRequestWarning', ...); // [!code --]
|
||||
client.rest.on('invalidRequestWarning', ...); // [!code ++]
|
||||
```
|
||||
|
||||
#### rateLimit
|
||||
|
||||
This REST event has been renamed to `rateLimited` and moved to `Client#rest`:
|
||||
|
||||
```js
|
||||
client.on('rateLimit', ...); // [!code --]
|
||||
client.rest.on('rateLimited', ...); // [!code ++]
|
||||
```
|
||||
|
||||
### RoleManager
|
||||
|
||||
`Role.comparePositions()` has been removed. Use `RoleManager#comparePositions()` instead.
|
||||
|
||||
### Sticker
|
||||
|
||||
`Sticker#tags` is now a nullable string (`string | null`). Previously, it was a nullable array of strings (`string[] | null`). See [this pull request](https://github.com/discordjs/discord.js/pull/8010) for more information.
|
||||
|
||||
### ThreadChannel
|
||||
|
||||
The `MAX` helper used in `ThreadAutoArchiveDuration` has been removed. Discord has since allowed any guild to use any auto archive time which makes this helper redundant.
|
||||
|
||||
### ThreadMemberManager
|
||||
|
||||
`ThreadMemberManager#fetch()`'s second parameter has been removed. The `BaseFetchOptions` the second parameter once was is now merged into the first parameter. In addition, the boolean helper to specify `cache` has been removed.
|
||||
|
||||
Usage is now as follows:
|
||||
|
||||
```js
|
||||
// The second parameter is merged into the first parameter.
|
||||
threadMemberManager.fetch('1234567890', { cache: false, force: true }); // [!code --]
|
||||
threadMemberManager.fetch({ member: '1234567890', cache: false, force: true }); // [!code ++]
|
||||
|
||||
// The lone boolean has been removed. One must be explicit here.
|
||||
threadMemberManager.fetch(false); // [!code --]
|
||||
threadMemberManager.fetch({ cache: false }); // [!code ++]
|
||||
```
|
||||
|
||||
### Util
|
||||
|
||||
`Util.removeMentions()` has been removed. To control mentions, you should use `allowedMentions` on `BaseMessageOptions` instead.
|
||||
|
||||
`Util.splitMessage()` has been removed. This utility method is something the developer themselves should do.
|
||||
|
||||
`Util.resolveAutoArchiveMaxLimit()` has been removed. Discord has since allowed any guild to use any auto archive time which makes this method redundant.
|
||||
|
||||
Other functions in `Util` have been moved to top-level exports so you can directly import them from `discord.js`.
|
||||
|
||||
```js
|
||||
import { Util } from 'discord.js'; // [!code --]
|
||||
Util.escapeMarkdown(message); // [!code --]
|
||||
import { escapeMarkdown } from 'discord.js'; // [!code ++]
|
||||
escapeMarkdown(message); // [!code ++]
|
||||
```
|
||||
|
||||
### `.deleted` Field(s) have been removed
|
||||
|
||||
You can no longer use the `deleted` property to check if a structure was deleted. See [this issue](https://github.com/discordjs/discord.js/issues/7091) for more information.
|
||||
|
||||
### VoiceChannel
|
||||
|
||||
`VoiceChannel#editable` has been removed. You should use `GuildChannel#manageable` instead.
|
||||
|
||||
### VoiceRegion
|
||||
|
||||
`VoiceRegion#vip` has been removed as it is no longer part of the API.
|
||||
|
||||
### Webhook
|
||||
|
||||
`Webhook#fetchMessage()`'s second parameter no longer allows a boolean to be passed. The `cache` option in `WebhookFetchMessageOptions` should be used instead.
|
||||
|
||||
## Features
|
||||
|
||||
### ApplicationCommand
|
||||
|
||||
NFSW commands are supported.
|
||||
|
||||
### Attachment
|
||||
|
||||
Added support for voice message metadata fields.
|
||||
|
||||
### AutocompleteInteraction
|
||||
|
||||
`AutocompleteInteraction#commandGuildId` has been added which is the id of the guild the invoked application command is registered to.
|
||||
|
||||
### BaseChannel
|
||||
|
||||
Added support for `BaseChannel#flags`.
|
||||
|
||||
Store channels have been removed as they are no longer part of the API.
|
||||
|
||||
`BaseChannel#url` has been added which is a link to a channel, just like in the client.
|
||||
|
||||
Additionally, new typeguards have been added:
|
||||
|
||||
- `BaseChannel#isDMBased()`
|
||||
- `BaseChannel#isTextBased()`
|
||||
- `BaseChannel#isVoiceBased()`
|
||||
|
||||
### BaseInteraction
|
||||
|
||||
Added `BaseInteraction#isRepliable()` to check whether a given interaction can be replied to.
|
||||
|
||||
### ClientApplication
|
||||
|
||||
Added support for role connection metadata.
|
||||
|
||||
### Collection
|
||||
|
||||
- Added `Collection#merge()` and `Collection#combineEntries()`.
|
||||
- New type: `ReadonlyCollection` which indicates an immutable `Collection`.
|
||||
|
||||
### Collector
|
||||
|
||||
A new `ignore` event has been added which is emitted whenever an element is not collected by the collector.
|
||||
|
||||
Component collector options now use the `ComponentType` enum values:
|
||||
|
||||
```js
|
||||
const { ComponentType } = require('discord.js'); // [!code ++]
|
||||
|
||||
const collector = interaction.channel.createMessageComponentCollector({
|
||||
filter: collectorFilter,
|
||||
componentType: 'BUTTON', // [!code --]
|
||||
componentType: ComponentType.Button, // [!code ++]
|
||||
time: 20_000,
|
||||
});
|
||||
```
|
||||
|
||||
### CommandInteraction
|
||||
|
||||
`CommandInteraction#commandGuildId` has been added which is the id of the guild the invoked application command is registered to.
|
||||
|
||||
### CommandInteractionOptionResolver
|
||||
|
||||
`CommandInteractionOptionResolver#getChannel()` now has a third parameter which narrows the channel type.
|
||||
|
||||
### Events
|
||||
|
||||
Added support for `guildAuditLogEntryCreate` event.
|
||||
|
||||
### ForumChannel
|
||||
|
||||
Added support for forum channels.
|
||||
|
||||
Added support for `ForumChannel#defaultForumLayout`.
|
||||
|
||||
### Guild
|
||||
|
||||
Added `Guild#setMFALevel()` which sets the guild's MFA level.
|
||||
|
||||
Added `Guild#maxVideoChannelUsers` which indicates the maximum number of video channel users.
|
||||
|
||||
Added `Guild#maxStageVideoChannelUsers` which indicates the maximum number of video channel users for stage channels.
|
||||
|
||||
Added `Guild#disableInvites()` which disables the guild's invites.
|
||||
|
||||
Added support for the `after` parameter in `Guild#fetchAuditLogs()`.
|
||||
|
||||
### GuildChannelManager
|
||||
|
||||
`videoQualityMode` may be used whilst creating a channel to initially set the camera video quality mode.
|
||||
|
||||
### GuildEmojiManager
|
||||
|
||||
Added `GuildEmojiManager#delete()` and `GuildEmojiManager#edit()` for managing existing guild emojis.
|
||||
|
||||
### GuildForumThreadManager
|
||||
|
||||
Added `GuildForumThreadManager` as manager for threads in forum channels.
|
||||
|
||||
### GuildMember
|
||||
|
||||
Added support for `GuildMember#flags`.
|
||||
|
||||
### GuildMembersChunk
|
||||
|
||||
This object now supports the `GuildMembersChunk#notFound` property.
|
||||
|
||||
### GuildMemberManager
|
||||
|
||||
Added `GuildMemberManager#fetchMe()` to fetch the client user in the guild.
|
||||
|
||||
Added `GuildMemberManager#addRole()` and `GuildMemberManager#removeRole()`. These methods allow a single addition or removal of a role respectively to a guild member, even if uncached.
|
||||
|
||||
### GuildTextThreadManager
|
||||
|
||||
Added `GuildTextThreadManager` as manager for threads in text channels and announcement channels.
|
||||
|
||||
### Message
|
||||
|
||||
`Message#position` has been added as an approximate position in a thread.
|
||||
|
||||
Added support for role subscription data.
|
||||
|
||||
### MessageReaction
|
||||
|
||||
Added `MessageReaction#react()` to make the client user react with the reaction the class belongs to.
|
||||
|
||||
### Role
|
||||
|
||||
Added support for role subscriptions.
|
||||
|
||||
Added support for `Role#tags#guildConnections`.
|
||||
|
||||
### StageChannel
|
||||
|
||||
Stage channels now allow messages to be sent in them, much like voice channels.
|
||||
|
||||
### Sticker
|
||||
|
||||
Added support for GIF stickers.
|
||||
|
||||
### ThreadMemberManager
|
||||
|
||||
The new `withMember` options returns the associated guild member with the thread member.
|
||||
|
||||
When fetching multiple thread members alongside `withMember`, paginated results will be returned. The `after` and `limit` option are supported in this scenario.
|
||||
|
||||
### Webhook
|
||||
|
||||
Added `Webhook#applicationId`.
|
||||
|
||||
Added the `threadName` property in `Webhook#send()` options which allows a webhook to create a post in a forum channel.
|
||||
|
||||
### WebSocketManager
|
||||
|
||||
discord.js uses `@discordjs/ws` internally
|
||||
@@ -1,107 +0,0 @@
|
||||
---
|
||||
title: Collections
|
||||
---
|
||||
|
||||
discord.js comes with a utility class known as `Collection`.
|
||||
It extends JavaScript's native `Map` class, so it has all the `Map` features and more!
|
||||
|
||||
<Callout type="warn">
|
||||
If you're not familiar with `Map`, read [MDN's page on
|
||||
it](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) before continuing. You
|
||||
should be familiar with `Array`
|
||||
[methods](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) as well. We will
|
||||
also use some ES6 features, so read up [here](./es6-syntax) if you do not know what they are.
|
||||
</Callout>
|
||||
|
||||
A `Map` allows for an association between unique keys and their values.
|
||||
For example, how can you transform every value or filter the entries in a `Map` easily?
|
||||
This is the point of the `Collection` class!
|
||||
|
||||
## Array-like Methods
|
||||
|
||||
Many of the methods on `Collection` correspond to their namesake in `Array`. One of them is `find`:
|
||||
|
||||
```js
|
||||
// Assume we have an array of users and a collection of the same users.
|
||||
array.find((u) => u.discriminator === '1000'); // [!code word:find]
|
||||
collection.find((u) => u.discriminator === '1000');
|
||||
```
|
||||
|
||||
The interface of the callback function is very similar between the two.
|
||||
For arrays, callbacks usually pass the parameters `(value, index, array)`, where `value` is the value iterated to,
|
||||
`index` is the current index, and `array` is the array. For collections, you would have `(value, key, collection)`.
|
||||
Here, `value` is the same, but `key` is the key of the value, and `collection` is the collection itself instead.
|
||||
|
||||
Methods that follow this philosophy of staying close to the `Array` interface are as follows:
|
||||
|
||||
- `find`
|
||||
- `filter` - Note that this returns a `Collection` rather than an `Array`.
|
||||
- `map` - Yet this returns an `Array` of values instead of a `Collection`!
|
||||
- `every`
|
||||
- `some`
|
||||
- `reduce`
|
||||
- `concat`
|
||||
- `sort`
|
||||
|
||||
## Converting to Array
|
||||
|
||||
Since `Collection` extends `Map`, it is an [iterable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols), and can be converted to an `Array` through either `Array.from()` or spread syntax (`...collection`).
|
||||
|
||||
```js
|
||||
// For values.
|
||||
Array.from(collection.values());
|
||||
[...collection.values()];
|
||||
|
||||
// For keys.
|
||||
Array.from(collection.keys());
|
||||
[...collection.keys()];
|
||||
|
||||
// For [key, value] pairs.
|
||||
Array.from(collection);
|
||||
[...collection];
|
||||
```
|
||||
|
||||
<Callout>
|
||||
Many people convert Collections to Arrays way too much!
|
||||
|
||||
This can lead to unnecessary and confusing code. Before you use `Array.from()` or similar, ask yourself if whatever you are trying to do can't be done with the given `Map` or `Collection` methods or with a for-of loop. Not being familiar with a new data structure should not mean you default to transforming it into the other.
|
||||
|
||||
There is usually a reason, why a `Map` or `Collection` is used. Most structures in Discord can be identified with an `id`, which lends itself well to `key -> value` associations like in `Map`s.
|
||||
|
||||
</Callout>
|
||||
|
||||
## Extra Utilities
|
||||
|
||||
Some methods are not from `Array` and are instead entirely new to standard JavaScript.
|
||||
|
||||
```js
|
||||
// A random value.
|
||||
collection.random();
|
||||
|
||||
// The first value.
|
||||
collection.first();
|
||||
|
||||
// The first 5 values.
|
||||
collection.first(5);
|
||||
|
||||
// Similar to `first`, but from the end.
|
||||
collection.last();
|
||||
collection.last(2);
|
||||
|
||||
// Removes anything that meets the condition from the collection.
|
||||
// Sort of like `filter`, but in-place.
|
||||
collection.sweep((user) => user.username === 'Bob');
|
||||
```
|
||||
|
||||
A more complicated method is `partition`, which splits a single Collection into two new Collections based on the provided function.
|
||||
You can think of it as two `filter`s, but done at the same time (and because of that much more performant):
|
||||
|
||||
```js
|
||||
// `bots` is a Collection of users where their `bot` property was true.
|
||||
// `humans` is a Collection where the property was false instead!
|
||||
const [bots, humans] = collection.partition((u) => u.bot); // [!code word:partition]
|
||||
|
||||
// Both return true.
|
||||
bots.every((b) => b.bot);
|
||||
humans.every((h) => !h.bot); // note the "not" ! operator
|
||||
```
|
||||
@@ -1,243 +0,0 @@
|
||||
---
|
||||
title: ES6 Syntax
|
||||
---
|
||||
|
||||
If you've used JavaScript for only a (relatively) small amount of time or don't have much experience with it, you might not be aware of what ES6 is and what beneficial features it includes. Since this is a guide primarily for Discord bots, we'll be using some discord.js code as an example of what you might have versus what you could do to benefit from ES6.
|
||||
|
||||
Here's the startup code we'll be using:
|
||||
|
||||
```js title="index.js" lineNumbers
|
||||
const { Client, Events, GatewayIntentBits } = require('discord.js'); // [!code word:const]
|
||||
const config = require('./config.json');
|
||||
|
||||
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
|
||||
|
||||
// [!code word:=>]
|
||||
client.once(Events.ClientReady, () => {
|
||||
console.log('Ready!');
|
||||
});
|
||||
|
||||
client.on(Events.InteractionCreate, (interaction) => {
|
||||
if (!interaction.isChatInputCommand()) return;
|
||||
|
||||
const { commandName } = interaction;
|
||||
|
||||
if (commandName === 'ping') {
|
||||
interaction.reply('Pong.');
|
||||
} else if (commandName === 'beep') {
|
||||
interaction.reply('Boop.');
|
||||
} else if (commandName === 'server') {
|
||||
interaction.reply('Guild name: ' + interaction.guild.name + '\nTotal members: ' + interaction.guild.memberCount);
|
||||
} else if (commandName === 'user-info') {
|
||||
interaction.reply('Your username: ' + interaction.user.username + '\nYour ID: ' + interaction.user.id);
|
||||
}
|
||||
});
|
||||
|
||||
client.login(config.token);
|
||||
```
|
||||
|
||||
If you haven't noticed, this piece of code is already using a bit of ES6 here! The `const` keyword and arrow function declaration (`() => ...`) is ES6 syntax, and we recommend using it whenever possible.
|
||||
|
||||
As for the code above, there are a few places where things can be done better. Let's look at them.
|
||||
|
||||
## Template literals
|
||||
|
||||
If you check the code above, it's currently doing things like `'Guild name: ' + interaction.guild.name` and `'Your username: ' + interaction.user.username`, which is perfectly valid. It is a bit hard to read, though, and it's not too fun to constantly type out. Fortunately, there's a better alternative.
|
||||
|
||||
```js title="index.js" lineNumbers=19
|
||||
} else if (commandName === 'server') {
|
||||
interaction.reply('Guild name: ' + interaction.guild.name + '\nTotal members: ' + interaction.guild.memberCount); // [!code --]
|
||||
interaction.reply(`Guild name: ${interaction.guild.name}\nTotal members: ${interaction.guild.memberCount}`); // [!code ++]
|
||||
}
|
||||
else if (commandName === 'user-info') {
|
||||
interaction.reply('Your username: ' + interaction.user.username + '\nYour ID: ' + interaction.user.id); // [!code --]
|
||||
interaction.reply(`Your username: ${interaction.user.username}\nYour ID: ${interaction.user.id}`); // [!code ++]
|
||||
}
|
||||
```
|
||||
|
||||
Easier to read and write! The best of both worlds.
|
||||
|
||||
### Template literals vs string concatenation
|
||||
|
||||
If you've used other programming languages, you might be familiar with the term "string interpolation". Template literals would be JavaScript's implementation of string interpolation. If you're familiar with the heredoc syntax, it's very much like that; it allows for string interpolation, as well as multiline strings.
|
||||
|
||||
The example below won't go too much into detail about it, but if you're interested in reading more, you can [read about them on MDN](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Template_literals).
|
||||
|
||||
```js
|
||||
const username = 'Sanctuary';
|
||||
const password = 'pleasedonthackme';
|
||||
|
||||
function letsPretendThisDoesSomething() {
|
||||
return 'Yay for sample data.';
|
||||
}
|
||||
|
||||
console.log('Your username is: **' + username + '**.'); // [!code --:2]
|
||||
console.log('Your password is: **' + password + '**.');
|
||||
console.log(`Your username is: **${username}**.`); // [!code ++:2]
|
||||
console.log(`Your password is: **${password}**.`);
|
||||
|
||||
console.log('1 + 1 = ' + (1 + 1)); // [!code --]
|
||||
console.log(`1 + 1 = ${1 + 1}`); // [!code ++]
|
||||
|
||||
console.log("And here's a function call: " + letsPretendThisDoesSomething()); // [!code --]
|
||||
console.log(`And here's a function call: ${letsPretendThisDoesSomething()}`); // [!code ++]
|
||||
|
||||
console.log('Putting strings on new lines\n' + 'can be a bit painful\n' + 'with string concatenation.'); // [!code --]
|
||||
// [!code ++:5]
|
||||
console.log(`
|
||||
Putting strings on new lines
|
||||
is a breeze
|
||||
with template literals!
|
||||
`);
|
||||
```
|
||||
|
||||
<Callout>
|
||||
As you will notice, template literals will also render the white space inside them, including the indentation! There
|
||||
are ways around this, which we will discuss in another section.
|
||||
</Callout>
|
||||
|
||||
You can see how it makes things easier and more readable. In some cases, it can even make your code shorter! This one is something you'll want to take advantage of as much as possible.
|
||||
|
||||
## Arrow functions
|
||||
|
||||
Arrow functions are shorthand for regular functions, with the addition that they use a lexical `this` context inside of their own. If you don't know what the `this` keyword is referring to, don't worry about it; you'll learn more about it as you advance.
|
||||
|
||||
Here are some examples of ways you can benefit from arrow functions over regular functions:
|
||||
|
||||
```js
|
||||
// [!code --:3]
|
||||
client.once(Events.ClientReady, function () {
|
||||
console.log('Ready!');
|
||||
});
|
||||
client.once(Events.ClientReady, () => console.log('Ready!')); // [!code ++]
|
||||
|
||||
// [!code --:3]
|
||||
client.on(Events.TypingStart, function (typing) {
|
||||
console.log(typing.user.tag + ' started typing in #' + typing.channel.name);
|
||||
});
|
||||
client.on(Events.TypingStart, (typing) => console.log(`${typing.user.tag} started typing in #${typing.channel.name}`)); // [!code ++]
|
||||
|
||||
// [!code --:3]
|
||||
client.on(Events.MessageCreate, function (message) {
|
||||
console.log(message.author.tag + ' sent: ' + message.content);
|
||||
});
|
||||
client.on(Events.MessageCreate, (message) => console.log(`${message.author.tag} sent: ${message.content}`)); // [!code ++]
|
||||
|
||||
// [!code --:3]
|
||||
var doubleAge = function (age) {
|
||||
return 'Your age doubled is: ' + age * 2;
|
||||
};
|
||||
const doubleAge = (age) => `Your age doubled is: ${age * 2}`; // [!code ++]
|
||||
|
||||
// [!code --:4]
|
||||
var collectorFilter = function (m) {
|
||||
return m.content === 'I agree' && !m.author.bot;
|
||||
};
|
||||
var collector = message.createMessageCollector({ filter: collectorFilter, time: 15_000 });
|
||||
const collectorFilter = (m) => m.content === 'I agree' && !m.author.bot; // [!code ++:2]
|
||||
const collector = message.createMessageCollector({ filter: collectorFilter, time: 15_000 });
|
||||
```
|
||||
|
||||
There are a few important things you should note here:
|
||||
|
||||
- The parentheses around function parameters are optional when you have only one parameter but are required otherwise. If you feel like this will confuse you, it may be a good idea to use parentheses.
|
||||
- You can cleanly put what you need on a single line without curly braces.
|
||||
- Omitting curly braces will make arrow functions use **implicit return**, but only if you have a single-line expression. The `doubleAge` and `filter` variables are a good example of this.
|
||||
- Unlike the `function someFunc() { ... }` declaration, arrow functions cannot be used to create functions with such syntax. You can create a variable and give it an anonymous arrow function as the value, though (as seen with the `doubleAge` and `filter` variables).
|
||||
|
||||
We won't be covering the lexical `this` scope with arrow functions in here, but you can Google around if you're still curious. Again, if you aren't sure what `this` is or when you need it, reading about lexical `this` first may only confuse you.
|
||||
|
||||
## Destructuring
|
||||
|
||||
Destructuring is an easy way to extract items from an object or array. If you've never seen the syntax for it before, it can be a bit confusing, but it's straightforward to understand once explained!
|
||||
|
||||
### Object destructuring
|
||||
|
||||
Here's a common example where object destructuring would come in handy:
|
||||
|
||||
```js
|
||||
const config = require('./config.json');
|
||||
const prefix = config.prefix;
|
||||
const token = config.token;
|
||||
```
|
||||
|
||||
This code is a bit verbose and not the most fun to write out each time. Object destructuring simplifies this, making it easier to both read and write. Take a look:
|
||||
|
||||
```js
|
||||
const config = require('./config.json'); // [!code --:3]
|
||||
const prefix = config.prefix;
|
||||
const token = config.token;
|
||||
const { prefix, token } = require('./config.json'); // [!code ++]
|
||||
```
|
||||
|
||||
Object destructuring takes those properties from the object and stores them in variables. If the property doesn't exist, it'll still create a variable but with the value of `undefined`. So instead of using `config.token` in your `client.login()` method, you'd simply use `token`. And since destructuring creates a variable for each item, you don't even need that `const prefix = config.prefix` line. Pretty cool!
|
||||
|
||||
Additionally, you could do this for your commands:
|
||||
|
||||
```js
|
||||
client.on(Events.InteractionCreate, (interaction) => {
|
||||
const { commandName } = interaction;
|
||||
|
||||
if (commandName === 'ping') {
|
||||
// ping command here...
|
||||
} else if (commandName === 'beep') {
|
||||
// beep command here...
|
||||
}
|
||||
// other commands here...
|
||||
});
|
||||
```
|
||||
|
||||
The code is shorter and looks cleaner, but it shouldn't be necessary if you follow along with the [command handler](../app-creation/handling-commands) part of the guide.
|
||||
|
||||
You can also rename variables when destructuring, if necessary. A good example is when you're extracting a property with a name already being used or conflicts with a reserved keyword. The syntax is as follows:
|
||||
|
||||
```js
|
||||
// `default` is a reserved keyword
|
||||
const { default: defaultValue } = someObject;
|
||||
|
||||
console.log(defaultValue);
|
||||
// 'Some default value here'
|
||||
```
|
||||
|
||||
### Array destructuring
|
||||
|
||||
Array destructuring syntax is very similar to object destructuring, except that you use brackets instead of curly braces. In addition, since you're using it on an array, you destructure the items in the same order the array is. Without array destructuring, this is how you'd extract items from an array:
|
||||
|
||||
```js
|
||||
// assuming we're in a `profile` command and have an `args` variable
|
||||
const name = args[0];
|
||||
const age = args[1];
|
||||
const location = args[2];
|
||||
```
|
||||
|
||||
Like the first example with object destructuring, this is a bit verbose and not fun to write out. Array destructuring eases this pain.
|
||||
|
||||
```js
|
||||
const name = args[0]; // [!code --:3]
|
||||
const age = args[1];
|
||||
const location = args[2];
|
||||
const [name, age, location] = args; // [!code ++]
|
||||
```
|
||||
|
||||
A single line of code that makes things much cleaner! In some cases, you may not even need all the array's items (e.g., when using `string.match(regex)`). Array destructuring still allows you to operate in the same sense.
|
||||
|
||||
```js
|
||||
const [, username, id] = message.content.match(someRegex);
|
||||
```
|
||||
|
||||
In this snippet, we use a comma without providing a name for the item in the array we don't need. You can also give it a placeholder name (`_match` or similar) if you prefer, of course; it's entirely preference at that point.
|
||||
|
||||
<Callout>
|
||||
The underscore `_` prefix is a convention for unused variables. Some lint rules will error or warn if you define
|
||||
identifiers without using them in your code but ignore identifiers starting with `_`.
|
||||
</Callout>
|
||||
|
||||
## var, let, and const
|
||||
|
||||
Since there are many, many articles out there that can explain this part more in-depth, we'll only be giving you a TL;DR and an article link if you choose to read more about it.
|
||||
|
||||
1. The `var` keyword is what was (and can still be) used in JavaScript before `let` and `const` came to surface. There are many issues with `var`, though, such as it being function-scoped, hoisting related issues, and allowing redeclaration.
|
||||
2. The `let` keyword is essentially the new `var`; it addresses many of the issues `var` has, but its most significant factor would be that it's block-scoped and disallows redeclaration (_not_ reassignment).
|
||||
3. The `const` keyword is for giving variables a constant value that is unable to be reassigned. `const`, like `let`, is also block-scoped.
|
||||
|
||||
The general rule of thumb recommended by this guide is to use `const` wherever possible, `let` otherwise, and avoid using `var`. Here's a [helpful article](https://madhatted.com/2016/1/25/let-it-be) if you want to read more about this subject.
|
||||
|
Before Width: | Height: | Size: 9.6 KiB |
|
Before Width: | Height: | Size: 38 KiB |
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"pages": ["async-await", "collections", "es6-syntax", "notation", "rest-api", "proxy"]
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
---
|
||||
title: Understanding Notation
|
||||
---
|
||||
|
||||
Throughout the discord.js docs and when asking for help on the official server, you will run into many different kinds of notations. To help you understand the texts that you read, we will be going over some standard notations.
|
||||
|
||||
<Callout>
|
||||
Always keep in mind that notation is not always rigorous. There will be typos, misunderstandings, or contexts that
|
||||
will cause notation to differ from the usual meanings.
|
||||
</Callout>
|
||||
|
||||
## Classes
|
||||
|
||||
Some common notations refer to a class or the properties, methods, or events of a class. There are many variations on these notations, and they are very flexible depending on the person, so use your best judgment when reading them.
|
||||
|
||||
The notation `<Class>` means an instance of the `Class` class. For example, a snippet like `<BaseInteraction>.reply('Hello')` is asking you to replace `<BaseInteraction>` with some value that is an instance of `BaseInteraction`, e.g. `interaction.reply('Hello')`. It could also just be a placeholder, e.g., `<id>` would mean a placeholder for some ID.
|
||||
|
||||
The notation `Class#foo` can refer to the `foo` property, method, or event of the `Class` class. Which one the writer meant needs to be determined from context. For example:
|
||||
|
||||
- `BaseInteraction#user` means that you should refer to the `user` property on a `BaseInteraction`.
|
||||
- `TextChannel#send` means that you should refer to the `send` method on a `TextChannel`.
|
||||
- `Client#interactionCreate` means that you should refer to the `interactionCreate` event on a `Client`.
|
||||
|
||||
<Callout>
|
||||
Remember that this notation is not valid JavaScript; it is a shorthand to refer to a specific piece of code.
|
||||
</Callout>
|
||||
|
||||
Sometimes, the notation is extended, which can help you determine which one the writer meant. For example, `TextChannel#send(options)` is definitely a method of `TextChannel`, since it uses function notation. `Client#event:messageCreate` is an event since it says it is an event.
|
||||
|
||||
The vital thing to take away from this notation is that the `#` symbol signifies that the property, method, or event can only be accessed through an instance of the class. Unfortunately, many abuse this notation, e.g., `<Message>#send` or `Util#resolveColor`. `<Message>` is already an instance, so this makes no sense, and `resolveColor` is a static method–you should write it as `Util.resolveColor`. Always refer back to the docs if you are confused.
|
||||
|
||||
As an example, the documentation's search feature uses this notation.
|
||||
|
||||

|
||||
|
||||
Notice the use of the `.` operator for the static method, `Role.comparePositions` and the `#` notation for the method, `Role#comparePositionsTo`.
|
||||
|
||||
## Types
|
||||
|
||||
In the discord.js docs, there are type signatures everywhere, such as in properties, parameters, or return values. If you do not come from a statically typed language, you may not know what specific notations mean.
|
||||
|
||||
The symbol `*` means any type. For example, methods that return `*` mean that they can return anything, and a parameter of type `*` can be anything.
|
||||
|
||||
The symbol `?` means that the type is nullable. You can see it before or after the type (e.g. `?T` or `T?`). This symbol means that the value can be of the type `T` or `null`. An example is `GuildMember#nickname`; its type is `?string` since a member may or may not have a nickname.
|
||||
|
||||
The expression `T[]` means an array of `T`. You can sometimes see multiple brackets `[]`, indicating that the array is multi-dimensional, e.g., `string[][]`.
|
||||
|
||||
The expression `...T` signifies a rest parameter of type `T`. This means that the function can take any amount of arguments, and all those arguments must be of the type `T`.
|
||||
|
||||
The operator `|`, which can read as "or", creates a union type, e.g. `A|B|C`. Simply, it means the value can be of any one of the types given.
|
||||
|
||||
The angle brackets `<>` are used for generic types or parameterized types, signifying a type that uses another type(s). The notation looks like `A<B>` where `A` is the type and `B` is a type parameter. If this is hard to follow, it is enough to keep in mind that whenever you see `A<B>`, you can think of an `A` containing `B`. Examples:
|
||||
|
||||
- `Array<String>` means an array of strings.
|
||||
- `Promise<User>` means a `Promise` that contains a `User`.
|
||||
- `Array<Promise<User|GuildMember>>` would be an array of `Promise`s, each containing a `User` or a `GuildMember`.
|
||||
- `Collection<Snowflake, User>` would be a `Collection`, containing key-value pairs where the keys are `Snowflake`s, and the values are `User`s.
|
||||
|
||||

|
||||
|
||||
In this piece of the docs, you can see two type signatures, `string`, `MessagePayload`, or `MessageOptions`, and `Promise<(Message|Array<Message>)>`. The meaning of the word "or" here is the same as `|`.
|
||||
@@ -1,73 +0,0 @@
|
||||
---
|
||||
title: Using a proxy
|
||||
---
|
||||
|
||||
This guide will show you how to set up a proxy with discord.js. This may be necessary if you are deploying your bot to a server with a firewall only allowing outside traffic through the proxy.
|
||||
|
||||
Proxying discord.js requires two components: a REST proxy and a WebSocket proxy.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
To achieve these two components you can utilize the `undici` and `global-agent` packages:
|
||||
|
||||
```sh tab="npm"
|
||||
npm install undici global-agent
|
||||
```
|
||||
|
||||
```sh tab="yarn"
|
||||
yarn add undici global-agent
|
||||
```
|
||||
|
||||
```sh tab="pnpm"
|
||||
pnpm add undici global-agent
|
||||
```
|
||||
|
||||
```sh tab="bun"
|
||||
bun add undici global-agent
|
||||
```
|
||||
|
||||
## Setting up the proxy for REST calls
|
||||
|
||||
The `@discordjs/rest` package handling HTTP requests in discord.js uses the `undici` package. Accordingly, you can provide a custom `ProxyAgent` configuration to the client constructor:
|
||||
|
||||
```js title="index.js" lineNumbers
|
||||
const { ProxyAgent } = require('undici'); // [!code word:ProxyAgent]
|
||||
const { Client } = require('discord.js');
|
||||
|
||||
const client = new Client({
|
||||
// ...other client options
|
||||
rest: {
|
||||
agent: new ProxyAgent('http://my-proxy-server:port'),
|
||||
},
|
||||
});
|
||||
|
||||
client.login('your-token-goes-here');
|
||||
```
|
||||
|
||||
<Callout>
|
||||
For further information on the `undici` `ProxyAgent`, please refer to the [undici
|
||||
documentation](https://undici.nodejs.org/#/docs/api/ProxyAgent.md).
|
||||
</Callout>
|
||||
|
||||
## Setting up the proxy for the WebSocket connection
|
||||
|
||||
To set up a proxy for WebSocket, you can use the `global-agent` package. You will need to import and call the `bootstrap()` function and set the required `GLOBAL_AGENT` globals as shown below:
|
||||
|
||||
```js title="index.js" lineNumbers
|
||||
const { ProxyAgent } = require('undici');
|
||||
const { Client } = require('discord.js');
|
||||
const { bootstrap } = require('global-agent'); // [!code ++:5]
|
||||
|
||||
bootstrap(); // [!code word:bootstrap]
|
||||
global.GLOBAL_AGENT.HTTP_PROXY = 'http://my-proxy-server:port';
|
||||
global.GLOBAL_AGENT.HTTPS_PROXY = 'https://my-proxy-server:port';
|
||||
|
||||
const client = new Client({
|
||||
// ...other client options
|
||||
rest: {
|
||||
agent: new ProxyAgent('http://my-proxy-server:port'),
|
||||
},
|
||||
});
|
||||
|
||||
client.login('your-token-goes-here');
|
||||
```
|
||||
@@ -1,176 +0,0 @@
|
||||
---
|
||||
title: REST APIs
|
||||
---
|
||||
|
||||
REST APIs are extremely popular on the web and allow you to freely grab a site's data if it has an available API over an HTTP connection.
|
||||
|
||||
## Making HTTP requests with Node
|
||||
|
||||
In these examples, we will be using [undici](https://www.npmjs.com/package/undici), an excellent library for making HTTP requests.
|
||||
|
||||
To install undici, run the following command:
|
||||
|
||||
```sh tab="npm"
|
||||
npm i install undici
|
||||
```
|
||||
|
||||
```sh tab="yarn"
|
||||
yarn add undici
|
||||
```
|
||||
|
||||
```sh tab="pnpm"
|
||||
pnpm add undici
|
||||
```
|
||||
|
||||
## Skeleton code
|
||||
|
||||
To start off, you will be using the following skeleton code. Since both the commands you will be adding in this section require an interaction with external APIs, you will defer the reply, so your application responds with a "thinking..." state. You can then edit the reply once you got the data you need:
|
||||
|
||||
```js title="rest-examples.js" lineNumbers
|
||||
const { Client, EmbedBuilder, Events, GatewayIntentBits } = require('discord.js');
|
||||
|
||||
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
|
||||
|
||||
client.once(Events.ClientReady, (readyClient) => {
|
||||
console.log(`Ready! Logged in as ${readyClient.user.tag}`);
|
||||
});
|
||||
|
||||
client.on(Events.InteractionCreate, async (interaction) => {
|
||||
if (!interaction.isChatInputCommand()) return;
|
||||
|
||||
const { commandName } = interaction;
|
||||
await interaction.deferReply();
|
||||
// ...
|
||||
});
|
||||
|
||||
client.login('your-token-goes-here');
|
||||
```
|
||||
|
||||
<Callout>
|
||||
We're taking advantage of [destructuring](./es6-syntax#destructuring) in this tutorial to maintain readability.
|
||||
</Callout>
|
||||
|
||||
## Using undici
|
||||
|
||||
Undici is a Promise-based HTTP/1.1 client, written from scratch for Node.js. If you aren't already familiar with Promises, you should read up on them [here](./async-await).
|
||||
|
||||
In this tutorial, you will be making a bot with two API-based commands using the [random.cat](https://aws.random.cat) and [Urban Dictionary](https://www.urbandictionary.com) APIs.
|
||||
|
||||
On top of your file, import the library function you will be using:
|
||||
|
||||
```js
|
||||
const { request } = require('undici');
|
||||
```
|
||||
|
||||
### Random Cat
|
||||
|
||||
<Callout title="No more cats :(" type="error">
|
||||
Unfortunately, the `aws.random.cat` API doesn't work anymore. We will keep the example as-is until we find a better
|
||||
showcase!
|
||||
</Callout>
|
||||
|
||||
Random cat's API is available at [https://aws.random.cat/meow](https://aws.random.cat/meow) and returns a [JSON](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON) response. To actually fetch data from the API, you're going to do the following:
|
||||
|
||||
```js
|
||||
const catResult = await request('https://aws.random.cat/meow');
|
||||
const { file } = await catResult.body.json();
|
||||
```
|
||||
|
||||
If you just add this code, it will seem like nothing happens. What you do not see, is that you are launching a request to the random.cat server, which responds some JSON data. The helper function parses the response data to a JavaScript object you can work with. The object will have a `file` property with the value of a link to a random cat image.
|
||||
|
||||
Next, you will implement this approach into an application command:
|
||||
|
||||
```js
|
||||
client.on(Events.InteractionCreate, async (interaction) => {
|
||||
// ...
|
||||
if (commandName === 'cat') {
|
||||
const catResult = await request('https://aws.random.cat/meow');
|
||||
const { file } = await catResult.body.json();
|
||||
interaction.editReply({ files: [file] });
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
So, here's what's happening in this code:
|
||||
|
||||
1. Your application sends a `GET` request to random.cat.
|
||||
2. random.cat sees the request and gets a random file url from their database.
|
||||
3. random.cat then sends that file's URL as a JSON object in a stringified form that contains a link to the image.
|
||||
4. undici receives the response and you parse the body to a JSON object.
|
||||
5. Your application then attaches the image and sends it in Discord.
|
||||
|
||||
### Urban Dictionary
|
||||
|
||||
Urban Dictionary's API is available at [https://api.urbandictionary.com/v0/define](https://api.urbandictionary.com/v0/define), accepts a `term` parameter, and returns a JSON response.
|
||||
|
||||
The following code will fetch data from this api:
|
||||
|
||||
```js
|
||||
// ...
|
||||
client.on(Events.InteractionCreate, async (interaction) => {
|
||||
// ...
|
||||
if (commandName === 'urban') {
|
||||
const term = interaction.options.getString('term');
|
||||
const query = new URLSearchParams({ term }); // [!code word:URLSearchParams]
|
||||
|
||||
const dictResult = await request(`https://api.urbandictionary.com/v0/define?${query}`);
|
||||
const { list } = await dictResult.body.json();
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Here, you are using JavaScript's native [URLSearchParams class](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) to create a [query string](https://en.wikipedia.org/wiki/Query_string) for the URL so that the Urban Dictionary server can parse it and know what you want to look up.
|
||||
|
||||
If you were to do `/urban hello world`, then the URL would become https://api.urbandictionary.com/v0/define?term=hello%20world since the string `"hello world"` is encoded.
|
||||
|
||||
You can get the respective properties from the returned JSON. If you were to view it in your browser, it usually looks like a bunch of mumbo jumbo. If it doesn't, great! If it does, then you should get a JSON formatter/viewer. If you're using Chrome, [JSON Formatter](https://chrome.google.com/webstore/detail/json-formatter/bcjindcccaagfpapjjmafapmmgkkhgoa) is one of the more popular extensions. If you're not using Chrome, search for "JSON formatter/viewer <your browser>" and get one.
|
||||
|
||||
Now, if you look at the JSON, you can see that it has a `list` property, which is an array of objects containing various definitions for the term (maximum 10). Something you always want to do when making API-based commands is to handle the case when no results are available. So, if you throw a random term in there (e.g. `njaksdcas`) and then look at the response the `list` array should be empty. Now you are ready to start writing!
|
||||
|
||||
As explained above, you'll want to check if the API returned any answers for your query, and send back the definition if that's the case:
|
||||
|
||||
```js
|
||||
if (commandName === 'urban') {
|
||||
// ...
|
||||
if (!list.length) {
|
||||
return interaction.editReply(`No results found for **${term}**.`);
|
||||
}
|
||||
|
||||
interaction.editReply(`**${term}**: ${list[0].definition}`);
|
||||
}
|
||||
```
|
||||
|
||||
Here, you are only getting the first object from the array of objects called `list` and grabbing its `definition` property.
|
||||
|
||||
If you've followed the tutorial, you should have something like this:
|
||||
|
||||
Now, you can make it an [embed](../popular-topics/embeds) for easier formatting.
|
||||
|
||||
You can define the following helper function at the top of your file. In the code below, you can use this function to truncate the returned data and make sure the embed doesn't error, because field values exceed 1024 characters.
|
||||
|
||||
```js
|
||||
const trim = (str, max) => (str.length > max ? `${str.slice(0, max - 3)}...` : str);
|
||||
```
|
||||
|
||||
And here is how you can build the embed from the API data:
|
||||
|
||||
```js
|
||||
const [answer] = list;
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor(0xefff00)
|
||||
.setTitle(answer.word)
|
||||
.setURL(answer.permalink)
|
||||
.addFields(
|
||||
{ name: 'Definition', value: trim(answer.definition, 1_024) },
|
||||
{ name: 'Example', value: trim(answer.example, 1_024) },
|
||||
{ name: 'Rating', value: `${answer.thumbs_up} thumbs up. ${answer.thumbs_down} thumbs down.` },
|
||||
);
|
||||
|
||||
interaction.editReply({ embeds: [embed] });
|
||||
```
|
||||
|
||||
<Callout>
|
||||
Check out display components for a newer approach to message formatting! You can read the [display
|
||||
components](../popular-topics/display-components) section of this guide to learn more about using them!
|
||||
</Callout>
|
||||
@@ -1,141 +0,0 @@
|
||||
---
|
||||
title: Creating slash commands
|
||||
---
|
||||
|
||||
## Creating slash commands
|
||||
|
||||
import { Step, Steps } from 'fumadocs-ui/components/steps';
|
||||
import { File, Folder, Files } from 'fumadocs-ui/components/files';
|
||||
|
||||
Discord allows developers to register [slash commands](https://discord.com/developers/docs/interactions/application-commands), which provide users a first-class way of interacting directly with your application.
|
||||
|
||||
Slash commands provide a huge number of benefits over manual message parsing, including:
|
||||
|
||||
- Integration with the Discord client interface.
|
||||
- Automatic command detection and parsing of the associated options/arguments.
|
||||
- Typed argument inputs for command options, e.g. "String", "User", or "Role".
|
||||
- Validated or dynamic choices for command options.
|
||||
- In-channel private responses (ephemeral messages).
|
||||
- Pop-up form-style inputs for capturing additional information.
|
||||
|
||||
...and many more!
|
||||
|
||||
## Before you continue
|
||||
|
||||
Assuming you've followed the guide so far, your project directory should look something like this:
|
||||
|
||||
<Files>
|
||||
<Folder name="discord-bot" defaultOpen>
|
||||
<Folder name="node_modules" defaultOpen />
|
||||
<File name="config.json" />
|
||||
<File name="index.js" />
|
||||
<File name="package-lock.json" />
|
||||
<File name="package.json" />
|
||||
</Folder>
|
||||
</Files>
|
||||
|
||||
<Steps>
|
||||
<Step>
|
||||
|
||||
### Command Files
|
||||
|
||||
The individual command files, containing slash command definitions and functionality.
|
||||
|
||||
</Step>
|
||||
<Step>
|
||||
|
||||
### Command Handler
|
||||
|
||||
The [command handler](./handling-commands), dynamically reads the command files and executes commands.
|
||||
|
||||
</Step>
|
||||
<Step>
|
||||
|
||||
### Command Deployment
|
||||
|
||||
The command [deployment script](./deploying-commands) to register your slash commands with Discord.
|
||||
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
These steps can be followed in any order, but are all required to make your bot work. This page details step **1**. Make sure you also check out the other linked pages.
|
||||
|
||||
## Individual command files
|
||||
|
||||
Create a new folder named `commands` and a subfolder named `utility` inside it, which is where you'll store all of your utility command files. You'll be using the class to construct the command definitions.
|
||||
|
||||
At a minimum, the definition of a slash command must have a name and a description. Slash command names must be between 1-32 characters and contain no capital letters, spaces, or symbols other than `-` and `_`. Using the builder, a simple `ping` command definition would look like this:
|
||||
|
||||
```js
|
||||
new SlashCommandBuilder().setName('ping').setDescription('Replies with Pong!');
|
||||
```
|
||||
|
||||
A slash command also requires a function to run when the command is used, to respond to the interaction. Using an interaction response method confirms to Discord that your bot successfully received the interaction, and has responded to the user. Discord enforces this to ensure that all slash commands provide a good user experience (UX). Failing to respond will cause Discord to show that the command failed, even if your bot is performing other actions as a result.
|
||||
|
||||
The simplest way to acknowledge and respond to an interaction is the `interaction.reply()` method. Other methods of replying are covered on the [Response methods](../slash-commands/response-methods) page later in this section.
|
||||
|
||||
```js
|
||||
async execute(interaction) {
|
||||
await interaction.reply('Pong!')
|
||||
}
|
||||
```
|
||||
|
||||
Put these two together by creating a `ping.js` file in the `commands/utility` folder for your first command. Inside this file, you're going to define and export two items.
|
||||
|
||||
- The `data` property, which will provide the command definition shown above for registering to Discord.
|
||||
- The `execute` method, which will contain the functionality to run from our event handler when the command is used.
|
||||
|
||||
These are placed inside `module.exports` so they can be read by other files; namely the command loader and command deployment scripts mentioned earlier.
|
||||
|
||||
```js title="commands/utility/ping.js"
|
||||
const { SlashCommandBuilder } = require('discord.js');
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder().setName('ping').setDescription('Replies with Pong!'),
|
||||
async execute(interaction) {
|
||||
await interaction.reply('Pong!');
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
<Callout>
|
||||
[`module.exports`](https://nodejs.org/api/modules.html#modules_module_exports) is how you export data in Node.js so that you can [`require()`](https://nodejs.org/api/modules.html#modules_require_id) it in other files.
|
||||
|
||||
If you need to access your client instance from inside a command file, you can access it via `interaction.client`. If you need to access external files, packages, etc., you should `require()` them at the top of the file.
|
||||
|
||||
</Callout>
|
||||
|
||||
That's it for your basic ping command. Below are examples of two more commands we're going to build upon throughout the guide, so create two more files for these before you continue reading.
|
||||
|
||||
```js tab="User" title="commands/utility/user.js"
|
||||
const { SlashCommandBuilder } = require('discord.js');
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder().setName('user').setDescription('Provides information about the user.'),
|
||||
async execute(interaction) {
|
||||
// interaction.user is the object representing the User who ran the command
|
||||
// interaction.member is the GuildMember object, which represents the user in the specific guild
|
||||
await interaction.reply(
|
||||
`This command was run by ${interaction.user.username}, who joined on ${interaction.member.joinedAt}.`,
|
||||
);
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
```js tab="Server" title="commands/utility/server.js"
|
||||
const { SlashCommandBuilder } = require('discord.js');
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder().setName('server').setDescription('Provides information about the server.'),
|
||||
async execute(interaction) {
|
||||
// interaction.guild is the object representing the Guild in which the command was run
|
||||
await interaction.reply(
|
||||
`This server is ${interaction.guild.name} and has ${interaction.guild.memberCount} members.`,
|
||||
);
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
#### Next steps
|
||||
|
||||
You can implement additional commands by creating new files within a dedicated subfolder in the `commands` folder, but these three are the ones we're going to use for the examples as we go on. For now let's move on to the code you'll need for command handling, to load the files and respond to incoming interactions.
|
||||
@@ -1,153 +0,0 @@
|
||||
---
|
||||
title: Registering Commands
|
||||
---
|
||||
|
||||
import { Step, Steps } from 'fumadocs-ui/components/steps';
|
||||
|
||||
For fully functional slash commands, you need three important pieces of code:
|
||||
|
||||
<Steps>
|
||||
<Step>
|
||||
|
||||
### Command Files
|
||||
|
||||
The individual command files, containing [slash command](./creating-commands) definitions and functionality.
|
||||
|
||||
</Step>
|
||||
<Step>
|
||||
|
||||
### Command Handler
|
||||
|
||||
The [command handler](./handling-commands), dynamically reads the command files and executes commands.
|
||||
|
||||
</Step>
|
||||
<Step>
|
||||
|
||||
### Command Deployment
|
||||
|
||||
The command deployment script to register your slash commands with Discord.
|
||||
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
These steps can be followed in any order, but are all required to make your bot work. This page details step **3**. Make sure you also check out the other linked pages.
|
||||
|
||||
## Command registration
|
||||
|
||||
Slash commands can be registered in two ways; in one specific guild, or for every guild the bot is in. We're going to look at single-guild registration first, as this is a good way to develop and test your commands before a global deployment.
|
||||
|
||||
Your application will need the `applications.commands` scope authorized in a guild for any of its slash commands to appear, and to be able to register them in a specific guild without error.
|
||||
|
||||
Slash commands only need to be registered once, and updated when the definition (description, options etc) is changed. As there is a daily limit on command creations, it's not necessary nor desirable to connect a whole client to the gateway or do this on every `ready` event. As such, a standalone script using the lighter REST manager is preferred.
|
||||
|
||||
This script is intended to be run separately, only when you need to make changes to your slash command **definitions** - you're free to modify parts such as the execute function as much as you like without redeployment.
|
||||
|
||||
### Guild commands
|
||||
|
||||
Create a `deploy-commands.js` file in your project directory. This file will be used to register and update the slash commands for your bot application.
|
||||
|
||||
Add two more properties to your `config.json` file, which we'll need in the deployment script:
|
||||
|
||||
- `clientId`: Your application's client id ([Discord Developer Portal](https://discord.com/developers/applications) > "General Information" > application id)
|
||||
- `guildId`: Your development server's id ([Enable developer mode](https://support.discord.com/hc/en-us/articles/206346498) > Right-click the server title > "Copy ID")
|
||||
|
||||
```json title="config.json"
|
||||
{
|
||||
"token": "your-token-goes-here",
|
||||
// [!code ++:2]
|
||||
"clientId": "your-application-id-goes-here",
|
||||
"guildId": "your-server-id-goes-here"
|
||||
}
|
||||
```
|
||||
|
||||
With these defined, you can use the deployment script below:
|
||||
|
||||
```js title="deploy-commands.js" lineNumbers
|
||||
const { REST, Routes } = require('discord.js');
|
||||
const { clientId, guildId, token } = require('./config.json');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const commands = [];
|
||||
// Grab all the command folders from the commands directory you created earlier
|
||||
const foldersPath = path.join(__dirname, 'commands');
|
||||
const commandFolders = fs.readdirSync(foldersPath);
|
||||
|
||||
for (const folder of commandFolders) {
|
||||
// Grab all the command files from the commands directory you created earlier
|
||||
const commandsPath = path.join(foldersPath, folder);
|
||||
const commandFiles = fs.readdirSync(commandsPath).filter((file) => file.endsWith('.js'));
|
||||
// Grab the SlashCommandBuilder#toJSON() output of each command's data for deployment
|
||||
for (const file of commandFiles) {
|
||||
const filePath = path.join(commandsPath, file);
|
||||
const command = require(filePath);
|
||||
if ('data' in command && 'execute' in command) {
|
||||
commands.push(command.data.toJSON());
|
||||
} else {
|
||||
console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Construct and prepare an instance of the REST module
|
||||
const rest = new REST().setToken(token);
|
||||
|
||||
// and deploy your commands!
|
||||
(async () => {
|
||||
try {
|
||||
console.log(`Started refreshing ${commands.length} application (/) commands.`);
|
||||
|
||||
// [!code word:Guild]
|
||||
// The put method is used to fully refresh all commands in the guild with the current set
|
||||
const data = await rest.put(Routes.applicationGuildCommands(clientId, guildId), { body: commands });
|
||||
|
||||
console.log(`Successfully reloaded ${data.length} application (/) commands.`);
|
||||
} catch (error) {
|
||||
// And of course, make sure you catch and log any errors!
|
||||
console.error(error);
|
||||
}
|
||||
})();
|
||||
```
|
||||
|
||||
Once you fill in these values, run `node deploy-commands.js` in your project directory to register your commands to the guild specified. If you see the success message, check for the commands in the server by typing `/`! If all goes well, you should be able to run them and see your bot's response in Discord!
|
||||
|
||||
### Global commands
|
||||
|
||||
Global application commands will be available in all the guilds your application has the `applications.commands` scope authorized in, and in direct messages by default.
|
||||
|
||||
To deploy global commands, you can use the same script from the guild commands section above and simply adjust the route in the script to `.applicationCommands(clientId)`
|
||||
|
||||
```js
|
||||
await rest.put(Routes.applicationCommands(clientId), { body: commands });
|
||||
```
|
||||
|
||||
### Where to deploy
|
||||
|
||||
<Callout>
|
||||
Guild-based deployment of commands is best suited for development and testing in your own personal server. Once you're satisfied that it's ready, deploy the command globally to publish it to all guilds that your bot is in.
|
||||
|
||||
You may wish to have a separate application and token in the Discord Dev Portal for your dev application, to avoid duplication between your guild-based commands and the global deployment.
|
||||
|
||||
</Callout>
|
||||
|
||||
#### Further reading
|
||||
|
||||
You've successfully sent a response to a slash command! However, this is only the most basic of command event and response functionality. Much more is available to enhance the user experience including:
|
||||
|
||||
<Cards>
|
||||
<Card title="Event Handling" href="./handling-events">
|
||||
Apply a similar dynamic, modular handling approach to client events.
|
||||
</Card>
|
||||
<Card title="Response Methods" href="../slash-commands/response-methods">
|
||||
Utilize different response methods for slash commands.
|
||||
</Card>
|
||||
<Card title="Advanced Command Creation" href="../slash-commands/advanced-creation">
|
||||
Expand on the command examples with additional, validated, option types.
|
||||
</Card>
|
||||
<Card title="Display Components" href="../popular-topics/display-components">
|
||||
Level up command responses with formatted content and display components.
|
||||
</Card>
|
||||
<Card title="Interactive Components" href="../interactive-components/action-rows">
|
||||
Enhance your commands with more input methods using Buttons, Select Menus, and Modals!
|
||||
</Card>
|
||||
</Cards>
|
||||
@@ -1,171 +0,0 @@
|
||||
---
|
||||
title: Command Handling
|
||||
---
|
||||
|
||||
import { Step, Steps } from 'fumadocs-ui/components/steps';
|
||||
|
||||
Unless your bot project is small, it's not a very good idea to have a single file with a giant `if`/`else if` chain for commands. If you want to implement features into your bot and make your development process a lot less painful, you'll want to implement a command handler. Let's get started on that!
|
||||
|
||||
For fully functional slash commands, you need three important pieces of code:
|
||||
|
||||
<Steps>
|
||||
<Step>
|
||||
|
||||
### Command Files
|
||||
|
||||
The individual command files, containing [slash command](./creating-commands) definitions and functionality.
|
||||
|
||||
</Step>
|
||||
<Step>
|
||||
|
||||
### Command Handler
|
||||
|
||||
The command handler, dynamically reads the command files and executes commands.
|
||||
|
||||
</Step>
|
||||
<Step>
|
||||
|
||||
### Command Deployment
|
||||
|
||||
The command [deployment script](./deploying-commands) to register your slash commands with Discord.
|
||||
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
These steps can be followed in any order, but are all required to make your bot work. This page details step **2**. Make sure you also check out the other linked pages.
|
||||
|
||||
## Loading command files
|
||||
|
||||
Now that your command files have been created, your bot needs to load these files on startup.
|
||||
|
||||
In your `index.js` file, make these additions to the base template:
|
||||
|
||||
```js title="index.js" lineNumbers
|
||||
// [!code ++:4]
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const { Client, Collection, Events, GatewayIntentBits, MessageFlags } = require('discord.js');
|
||||
const { token } = require('./config.json');
|
||||
|
||||
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
|
||||
|
||||
client.once(Events.ClientReady, (readyClient) => {
|
||||
console.log(`Ready! Logged in as ${readyClient.user.tag}`);
|
||||
});
|
||||
|
||||
client.commands = new Collection(); // [!code ++]
|
||||
```
|
||||
|
||||
We recommend attaching a `.commands` property to your client instance so that you can access your commands in other files. The rest of the examples in this guide will follow this convention. For TypeScript users, we recommend extending the base Client class to add this property, [casting](https://www.typescripttutorial.net/typescript-tutorial/type-casting/), or [augmenting the module type](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation).
|
||||
|
||||
<Callout>
|
||||
- The [`fs`](https://nodejs.org/api/fs.html) module is Node's native file system module. `fs` is used to read the
|
||||
`commands` directory and identify our command files. - The [`path`](https://nodejs.org/api/path.html) module is Node's
|
||||
native path utility module. `path` helps construct paths to access files and directories. One of the advantages of the
|
||||
`path` module is that it automatically detects the operating system and uses the appropriate joiners. - The
|
||||
`Collection` class extends JavaScript's native
|
||||
[`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) class, and includes more
|
||||
extensive, useful functionality. `Collection` is used to store and efficiently retrieve commands for execution.
|
||||
</Callout>
|
||||
|
||||
Next, using the modules imported above, dynamically retrieve your command files with a few more additions to the `index.js` file:
|
||||
|
||||
```js title="index.js" lineNumbers=12
|
||||
client.commands = new Collection();
|
||||
|
||||
const foldersPath = path.join(__dirname, 'commands');
|
||||
const commandFolders = fs.readdirSync(foldersPath);
|
||||
|
||||
for (const folder of commandFolders) {
|
||||
const commandsPath = path.join(foldersPath, folder);
|
||||
const commandFiles = fs.readdirSync(commandsPath).filter((file) => file.endsWith('.js'));
|
||||
for (const file of commandFiles) {
|
||||
const filePath = path.join(commandsPath, file);
|
||||
const command = require(filePath);
|
||||
// Set a new item in the Collection with the key as the command name and the value as the exported module
|
||||
if ('data' in command && 'execute' in command) {
|
||||
client.commands.set(command.data.name, command);
|
||||
} else {
|
||||
console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
First, [`path.join()`](https://nodejs.org/api/path.html#pathjoinpaths) helps to construct a path to the `commands` directory. The first [`fs.readdirSync()`](https://nodejs.org/api/fs.html#fs_fs_readdirsync_path_options) method then reads the path to the directory and returns an array of all the folder names it contains, currently `['utility']`. The second `fs.readdirSync()` method reads the path to this directory and returns an array of all the file names they contain, currently `['ping.js', 'server.js', 'user.js']`. To ensure only command files get processed, `Array.filter()` removes any non-JavaScript files from the array.
|
||||
|
||||
With the correct files identified, the last step is dynamically set each command into the `client.commands` Collection. For each file being loaded, check that it has at least the `data` and `execute` properties. This helps to prevent errors resulting from loading empty, unfinished, or otherwise incorrect command files while you're still developing.
|
||||
|
||||
## Receiving command interactions
|
||||
|
||||
You will receive an interaction for every slash command executed. To respond to a command, you need to create a listener for the `interactionCreate` event that will execute code when your application receives an interaction. Place the code below in the `index.js` file you created earlier.
|
||||
|
||||
```js title="index.js" lineNumbers=32
|
||||
// [!code ++:3]
|
||||
client.on(Events.InteractionCreate, (interaction) => {
|
||||
console.log(interaction);
|
||||
});
|
||||
```
|
||||
|
||||
Not every interaction is a slash command (e.g. `MessageComponent` interactions). Make sure to only handle slash commands in this function by making use of the `BaseInteraction#isChatInputCommand` method to exit the handler if another type is encountered. This method also provides typeguarding for TypeScript users, narrowing the type from `BaseInteraction` to a `ChatInputCommandInteraction`.
|
||||
|
||||
```js title="index.js" lineNumbers=32
|
||||
client.on(Events.InteractionCreate, (interaction) => {
|
||||
if (!interaction.isChatInputCommand()) return; // [!code ++]
|
||||
console.log(interaction);
|
||||
});
|
||||
```
|
||||
|
||||
## Executing commands
|
||||
|
||||
When your bot receives a `interactionCreate` event, the interaction object contains all the information you need to dynamically retrieve and execute your commands!
|
||||
|
||||
Let's take a look at the `ping` command again. Note the `execute()` function that will reply to the interaction with "Pong!".
|
||||
|
||||
```js title="commands/utility/ping.js"
|
||||
// [!code word:execute]
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder().setName('ping').setDescription('Replies with Pong!'),
|
||||
async execute(interaction) {
|
||||
await interaction.reply('Pong!');
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
First, you need to get the matching command from the `client.commands` Collection based on the `interaction.commandName`. Your `Client` instance is always available via `interaction.client`. If no matching command is found, log an error to the console and ignore the event.
|
||||
|
||||
With the right command identified, all that's left to do is call the command's `.execute()` method and pass in the `interaction` variable as its argument. In case something goes wrong, catch and log any error to the console.
|
||||
|
||||
```js title="index.js" lineNumbers=32
|
||||
client.on(Events.InteractionCreate, async (interaction) => {
|
||||
if (!interaction.isChatInputCommand()) return;
|
||||
// [!code ++:23]
|
||||
const command = interaction.client.commands.get(interaction.commandName);
|
||||
|
||||
if (!command) {
|
||||
console.error(`No command matching ${interaction.commandName} was found.`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await command.execute(interaction);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
if (interaction.replied || interaction.deferred) {
|
||||
await interaction.followUp({
|
||||
content: 'There was an error while executing this command!',
|
||||
flags: MessageFlags.Ephemeral,
|
||||
});
|
||||
} else {
|
||||
await interaction.reply({
|
||||
content: 'There was an error while executing this command!',
|
||||
flags: MessageFlags.Ephemeral,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
#### Next steps
|
||||
|
||||
Your command files are now loaded into your bot, and the event listener is prepared and ready to respond. In the next section, we cover the final step: a command deployment script you'll need to register your commands so they appear in the Discord client.
|
||||
@@ -1,215 +0,0 @@
|
||||
---
|
||||
title: Event Handling
|
||||
---
|
||||
|
||||
import { File, Folder, Files } from 'fumadocs-ui/components/files';
|
||||
|
||||
Node.js uses an event-driven architecture, making it possible to execute code when a specific event occurs. The discord.js library takes full advantage of this. You can visit the `Client` documentation to see the full list of events.
|
||||
|
||||
<Callout>
|
||||
This page assumes you've followed the guide up to this point, and created your `index.js` and individual slash
|
||||
commands according to those pages.
|
||||
</Callout>
|
||||
|
||||
At this point, your `index.js` file has listeners for two events: `ClientReady` and `InteractionCreate`.
|
||||
|
||||
```js title="index.js"
|
||||
// [!code word:ClientReady]
|
||||
client.once(Events.ClientReady, (readyClient) => {
|
||||
console.log(`Ready! Logged in as ${readyClient.user.tag}`);
|
||||
|
||||
// [!code word:InteractionCreate]
|
||||
client.on(Events.InteractionCreate, async (interaction) => {
|
||||
if (!interaction.isChatInputCommand()) return;
|
||||
|
||||
const command = interaction.client.commands.get(interaction.commandName);
|
||||
|
||||
if (!command) {
|
||||
console.error(`No command matching ${interaction.commandName} was found.`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await command.execute(interaction);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
if (interaction.replied || interaction.deferred) {
|
||||
await interaction.followUp({
|
||||
content: 'There was an error while executing this command!',
|
||||
flags: MessageFlags.Ephemeral,
|
||||
});
|
||||
} else {
|
||||
await interaction.reply({
|
||||
content: 'There was an error while executing this command!',
|
||||
flags: MessageFlags.Ephemeral,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Currently, the event listeners are in the `index.js` file. `Client#ready`emits once when the `Client` becomes ready for use, and `Client#interactionCreate` emits whenever an interaction is received. Moving the event listener code into individual files is simple, and we'll be taking a similar approach to the command handler.
|
||||
|
||||
<Callout type="warn">
|
||||
You're only going to move these two events from `index.js`. The code for [loading command
|
||||
files](./handling-commands#loading-command-files) will stay here!
|
||||
</Callout>
|
||||
|
||||
## Individual event files
|
||||
|
||||
Your project directory should look something like this:
|
||||
|
||||
<Files>
|
||||
<Folder name="discord-bot" defaultOpen>
|
||||
<Folder name="commands" defaultOpen />
|
||||
<Folder name="node_modules" defaultOpen />
|
||||
<File name="config.json" />
|
||||
<File name="deploy-commands.js" />
|
||||
<File name="index.js" />
|
||||
<File name="package-lock.json" />
|
||||
<File name="package.json" />
|
||||
</Folder>
|
||||
</Files>
|
||||
|
||||
Create an `events` folder in the same directory. You can then move the code from your event listeners in `index.js` to separate files: `events/ready.js` and `events/interactionCreate.js`.
|
||||
|
||||
```js tab="Ready Handler" title="events/ready.js"
|
||||
const { Events } = require('discord.js');
|
||||
|
||||
module.exports = {
|
||||
name: Events.ClientReady,
|
||||
once: true,
|
||||
execute(client) {
|
||||
console.log(`Ready! Logged in as ${client.user.tag}`);
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
```js tab="Interaction Handler" title="events/interactionCreate.js"
|
||||
const { Events, MessageFlags } = require('discord.js');
|
||||
|
||||
module.exports = {
|
||||
name: Events.InteractionCreate,
|
||||
async execute(interaction) {
|
||||
if (!interaction.isChatInputCommand()) return;
|
||||
|
||||
const command = interaction.client.commands.get(interaction.commandName);
|
||||
|
||||
if (!command) {
|
||||
console.error(`No command matching ${interaction.commandName} was found.`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await command.execute(interaction);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
if (interaction.replied || interaction.deferred) {
|
||||
await interaction.followUp({
|
||||
content: 'There was an error while executing this command!',
|
||||
flags: MessageFlags.Ephemeral,
|
||||
});
|
||||
} else {
|
||||
await interaction.reply({
|
||||
content: 'There was an error while executing this command!',
|
||||
flags: MessageFlags.Ephemeral,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
```js title="index.js" tab="Main File (after removing events)"
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const { Client, Collection, GatewayIntentBits } = require('discord.js');
|
||||
const { token } = require('./config.json');
|
||||
|
||||
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
|
||||
|
||||
client.commands = new Collection();
|
||||
const foldersPath = path.join(__dirname, 'commands');
|
||||
const commandFolders = fs.readdirSync(foldersPath);
|
||||
|
||||
for (const folder of commandFolders) {
|
||||
const commandsPath = path.join(foldersPath, folder);
|
||||
const commandFiles = fs.readdirSync(commandsPath).filter((file) => file.endsWith('.js'));
|
||||
for (const file of commandFiles) {
|
||||
const filePath = path.join(commandsPath, file);
|
||||
const command = require(filePath);
|
||||
if ('data' in command && 'execute' in command) {
|
||||
client.commands.set(command.data.name, command);
|
||||
} else {
|
||||
console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
client.login(token);
|
||||
```
|
||||
|
||||
The `name` property states which event this file is for, and the `once` property holds a boolean value that specifies if the event should run only once. You don't need to specify this in `interactionCreate.js` as the default behavior will be to run on every event instance. The `execute` function holds your event logic, which will be called by the event handler whenever the event emits.
|
||||
|
||||
## Reading event files
|
||||
|
||||
Next, let's write the code for dynamically retrieving all the event files in the `events` folder. We'll be taking a similar approach to our [command handler](./handling-commands). Place the new code highlighted below in your `index.js`.
|
||||
|
||||
`fs.readdirSync().filter()` returns an array of all the file names in the given directory and filters for only `.js` files, i.e. `['ready.js', 'interactionCreate.js']`.
|
||||
|
||||
```js title="index.js"
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const { Client, Collection, GatewayIntentBits } = require('discord.js');
|
||||
const { token } = require('./config.json');
|
||||
|
||||
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
|
||||
|
||||
client.commands = new Collection();
|
||||
const foldersPath = path.join(__dirname, 'commands');
|
||||
const commandFolders = fs.readdirSync(foldersPath);
|
||||
|
||||
for (const folder of commandFolders) {
|
||||
const commandsPath = path.join(foldersPath, folder);
|
||||
const commandFiles = fs.readdirSync(commandsPath).filter((file) => file.endsWith('.js'));
|
||||
for (const file of commandFiles) {
|
||||
const filePath = path.join(commandsPath, file);
|
||||
const command = require(filePath);
|
||||
if ('data' in command && 'execute' in command) {
|
||||
client.commands.set(command.data.name, command);
|
||||
} else {
|
||||
console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// [!code ++:12]
|
||||
const eventsPath = path.join(__dirname, 'events');
|
||||
const eventFiles = fs.readdirSync(eventsPath).filter((file) => file.endsWith('.js'));
|
||||
|
||||
for (const file of eventFiles) {
|
||||
const filePath = path.join(eventsPath, file);
|
||||
const event = require(filePath);
|
||||
if (event.once) {
|
||||
client.once(event.name, (...args) => event.execute(...args));
|
||||
} else {
|
||||
client.on(event.name, (...args) => event.execute(...args));
|
||||
}
|
||||
}
|
||||
|
||||
client.login(token);
|
||||
```
|
||||
|
||||
You'll notice the code looks very similar to the command loading above it - read the files in the events folder and load each one individually.
|
||||
|
||||
The `Client` class in discord.js extends the [`EventEmitter`](https://nodejs.org/api/events.html#events_class_eventemitter) class. Therefore, the `client` object exposes the [`.on()`](https://nodejs.org/api/events.html#events_emitter_on_eventname_listener) and [`.once()`](https://nodejs.org/api/events.html#events_emitter_once_eventname_listener) methods that you can use to register event listeners. These methods take two arguments: the event name and a callback function. These are defined in your separate event files as `name` and `execute`.
|
||||
|
||||
The callback function passed takes argument(s) returned by its respective event, collects them in an `args` array using the `...` [rest parameter syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters), then calls `event.execute()` while passing in the `args` array using the `...` [spread syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax). They are used here because different events in discord.js have different numbers of arguments. The rest parameter collects these variable number of arguments into a single array, and the spread syntax then takes these elements and passes them to the `execute` function.
|
||||
|
||||
After this, listening for other events is as easy as creating a new file in the `events` folder. The event handler will automatically retrieve and register it whenever you restart your bot.
|
||||
|
||||
<Callout>
|
||||
In most cases, you can access your `client` instance in other files by obtaining it from one of the other discord.js
|
||||
structures, e.g. `interaction.client` in the `interactionCreate` event. You do not need to manually pass it to your
|
||||
events.
|
||||
</Callout>
|
||||
@@ -1,50 +0,0 @@
|
||||
---
|
||||
title: The Main File
|
||||
---
|
||||
|
||||
<Callout>
|
||||
This page assumes you've already prepared the [configuration files](../app-creation/project-setup#configuration-files)
|
||||
from the previous page. We're using the `config.json` approach, however feel free to substitute your own!
|
||||
</Callout>
|
||||
|
||||
## Creating the main file
|
||||
|
||||
Open your code editor and create a new file. We suggest that you save the file as `index.js`, but you may name it whatever you wish.
|
||||
|
||||
Here's the base code to get you started:
|
||||
|
||||
```js title="index.js"
|
||||
// Require the necessary discord.js classes
|
||||
const { Client, Events, GatewayIntentBits } = require('discord.js');
|
||||
const { token } = require('./config.json');
|
||||
|
||||
// Create a new client instance
|
||||
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
|
||||
|
||||
// When the client is ready, run this code (only once).
|
||||
// The distinction between `client: Client<boolean>` and `readyClient: Client<true>` is important for TypeScript developers.
|
||||
// It makes some properties non-nullable.
|
||||
client.once(Events.ClientReady, (readyClient) => {
|
||||
console.log(`Ready! Logged in as ${readyClient.user.tag}`);
|
||||
});
|
||||
|
||||
// Log in to Discord with your client's token
|
||||
client.login(token);
|
||||
```
|
||||
|
||||
This is how you create a client instance for your Discord bot and log in to Discord. The `GatewayIntentBits.Guilds` intents option is necessary for the discord.js client to work as you expect it to, as it ensures that the caches for guilds, channels, and roles are populated and available for internal use.
|
||||
|
||||
<Callout>The term "guild" is used by the Discord API and in discord.js to refer to a Discord server.</Callout>
|
||||
|
||||
Intents also define which events Discord should send to your bot, and you may wish to enable more than just the minimum. You can read more about the other intents in the [Intents topic](../popular-topics/intents).
|
||||
|
||||
## Running your application
|
||||
|
||||
Open your terminal and run `node index.js` to start the process. If you see "Ready!" after a few seconds, you're good to go! The next step is to start adding slash commands to develop your app's functionality.
|
||||
|
||||
<Callout>
|
||||
You can open your `package.json` file and edit the `"main": "index.js"` field to point to your main file. You can then run `node .` in your terminal to start the process!
|
||||
|
||||
After closing the process with <kbd>Ctrl</kbd> <kbd>C</kbd>, you can press the up arrow on your keyboard to bring up the latest commands you've run. Pressing up and then enter after closing the process is a quick way to start it up again.
|
||||
|
||||
</Callout>
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"title": "Creating Your App",
|
||||
"pages": [
|
||||
"project-setup",
|
||||
"main-file",
|
||||
"creating-commands",
|
||||
"handling-commands",
|
||||
"deploying-commands",
|
||||
"handling-events"
|
||||
]
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
---
|
||||
title: Project Setup
|
||||
---
|
||||
|
||||
## Configuration files
|
||||
|
||||
Once you [add your bot to a server](../preparations/adding-your-app), the next step is to start coding and get it online! Let's start by creating a config file for your client token and a main file for your bot application.
|
||||
|
||||
As explained in the ["What is a token, anyway?"](../preparations/app-setup#what-is-a-token-anyway) section, your token is essentially your bot's password, and you should protect it as best as possible. This can be done through a `config.json` file or by using environment variables.
|
||||
|
||||
Open your application in the [Discord Developer Portal](https://discord.com/developers/applications) and go to the "Bot" page to copy your token.
|
||||
|
||||
## Using `config.json`
|
||||
|
||||
Storing data in a `config.json` file is a common way of keeping your sensitive values safe. Create a `config.json` file in your project directory and paste in your token. You can access your token inside other files by using `require()`.
|
||||
|
||||
```json tab="Config" title="config.json"
|
||||
{
|
||||
"token": "your-token-goes-here"
|
||||
}
|
||||
```
|
||||
|
||||
```js tab="Usage"
|
||||
const { token } = require('./config.json');
|
||||
|
||||
console.log(token);
|
||||
```
|
||||
|
||||
<Callout title="Danger" type="error">
|
||||
If you're using Git, you should not commit files containing secrets. Read on to find out how to [exclude them from
|
||||
versioning by using `.gitignore`](#git-and-gitignore).
|
||||
</Callout>
|
||||
|
||||
## Using environment variables
|
||||
|
||||
Environment variables are, as the name suggets, values you can pass to your environment (e.g. terminal session, Docker container, node process). This has the benefit that you can keep your code the same for different execution contexts.
|
||||
|
||||
```txt title=".env"
|
||||
A=Hello World
|
||||
B=123
|
||||
DISCORD_TOKEN=MTI3NDMxMjA3PDQ3ODIxNzIzNg.G6uEbl.IpA3-9YeScYr9pu9K1utMlpP4p-KJwNxcIAbi8
|
||||
```
|
||||
|
||||
<Callout title="Danger" type="error">
|
||||
If you're using Git, you should not commit `.env` or other environment files containing secrets. Read on to find out
|
||||
how to [exclude them from versioning by using `.gitignore`](#git-and-gitignore).
|
||||
</Callout>
|
||||
|
||||
To use environment variables in Node.js, we recommend you use the command line interface flag `--env-file` to point to the `.env` file you want to use. Note that the file name `.env` is just a convention. You could for example have a `.env.development` and `.env.production` file with different values depending on the Discord application you want to run your code.
|
||||
|
||||
You can also read multiple environment files by using the `--env-file` flag multiple times.
|
||||
|
||||
```sh
|
||||
node --env-file=.env index.js
|
||||
```
|
||||
|
||||
<Callout>You don't need to pass any special flags when using Bun! Bun reads `.env` files automatically.</Callout>
|
||||
|
||||
The values you specify in `.env` files this way are exposed through the `process.env` global variable in any file. Note that values passed this way will always be strings. If you want to do calculations on environment numbers, you will have to parse them:
|
||||
|
||||
```js title="index.js"
|
||||
// [!code word:env]
|
||||
console.log(process.env.A);
|
||||
console.log(process.env.B + 9); // 1239 (this concatenates the number to the string!)
|
||||
console.log(Number(process.env.C) + 9); // 132
|
||||
console.log(process.env.DISCORD_TOKEN);
|
||||
```
|
||||
|
||||
## Online editors
|
||||
|
||||
While we generally do not recommend using online editors as hosting solutions, but rather invest in a proper virtual private server, these services do offer ways to keep your credentials safe as well! Please see the respective service's documentation and help articles for more information on how to keep sensitive values safe:
|
||||
|
||||
<Cards>
|
||||
<Card title="Glitch" href="https://help.glitch.com/s/article/Adding-Private-Data">
|
||||
Learn more about storing secrets in `.env` files using Glitch
|
||||
</Card>
|
||||
<Card title="Heroku" href="https://devcenter.heroku.com/articles/config-vars">
|
||||
Learn more about configuration variables in Heroku
|
||||
</Card>
|
||||
<Card title="Replit" href="https://docs.replit.com/replit-workspace/workspace-features/secrets#secrets">
|
||||
Learn more about secrets and environment variables in Replit
|
||||
</Card>
|
||||
</Cards>
|
||||
|
||||
## Git and `.gitignore`
|
||||
|
||||
Git is a fantastic tool to keep track of your code changes and allows you to upload progress to services like [GitHub](https://github.com/), [GitLab](https://about.gitlab.com/), or [Bitbucket](https://bitbucket.org/product). While this is super useful to share code with other developers, it also bears the risk of uploading your configuration files with sensitive values!
|
||||
|
||||
You can specify files that Git should ignore in its versioning systems with a `.gitignore` file. Create a `.gitignore` file in your project directory and add the names of the files and folders you want to ignore. The following example ignores the `config.json` and `.env` files as well as the `node_modules` directory:
|
||||
|
||||
```txt title=".gitignore"
|
||||
node_modules
|
||||
.env
|
||||
config.json
|
||||
```
|
||||
|
||||
<Callout>
|
||||
`.gitignore` files can specify intricate patterns and help with your general development flow. Apart from keeping your
|
||||
credentials safe, you should exclude `node_modules` from version control as well, its contents can be restored from
|
||||
the entries in your `package.json` and `package-lock.json` files.
|
||||
</Callout>
|
||||
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 21 KiB |
@@ -1,200 +0,0 @@
|
||||
---
|
||||
title: Package scripts
|
||||
---
|
||||
|
||||
## Setting up package.json scripts
|
||||
|
||||
An easy way to run scripts like a script to start your bot, a script to lint your bot's files, or whatever scripts you use is by storing them in your `package.json` file. After you store these scripts in your `package.json` file, you can run the `start` script to start your bot or the `lint` script to lint your code for errors.
|
||||
|
||||
```sh tab="npm"
|
||||
npm run start
|
||||
npm run lint
|
||||
```
|
||||
|
||||
```sh tab="yarn"
|
||||
yarn run start
|
||||
yarn run lint
|
||||
```
|
||||
|
||||
```sh tab="pnpm"
|
||||
pnpm run start
|
||||
pnpm run lint
|
||||
```
|
||||
|
||||
```sh tab="bun"
|
||||
bun run start
|
||||
bun run lint
|
||||
```
|
||||
|
||||
## Getting started
|
||||
|
||||
<Callout>
|
||||
Before getting started, you'll need to have a `package.json` file. If you don't have a `package.json` file yet, you can run the following command in the console to generate one.
|
||||
|
||||
```sh tab="npm"
|
||||
npm init -y
|
||||
```
|
||||
|
||||
```sh tab="yarn"
|
||||
yarn init -y
|
||||
```
|
||||
|
||||
```sh tab="pnpm"
|
||||
pnpm init
|
||||
```
|
||||
|
||||
```sh tab="bun"
|
||||
bun init -y
|
||||
```
|
||||
|
||||
</Callout>
|
||||
|
||||
If you haven't touched your `package.json` file yet (excluding installing dependencies), your `package.json` file should look similar to the following:
|
||||
|
||||
```json title="package.json"
|
||||
{
|
||||
"name": "my-bot",
|
||||
"version": "1.0.0",
|
||||
"description": "A Discord bot!",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
||||
```
|
||||
|
||||
Let's zoom in more. Below `main`, you'll see `scripts`. You can specify your scripts there. In this guide, we'll show how to start and lint your bot using a `package.json` script.
|
||||
|
||||
## Adding your first script
|
||||
|
||||
<Callout>
|
||||
We'll assume you have finished the [creating your app](../app-creation/project-setup) section of the guide. If you
|
||||
haven't, ensure to follow it first!
|
||||
</Callout>
|
||||
|
||||
Over at your `package.json` file, add the following line to the `scripts`:
|
||||
|
||||
```json title="package.json"
|
||||
{
|
||||
"name": "my-bot",
|
||||
"version": "1.0.0",
|
||||
"description": "A Discord bot!",
|
||||
"main": "index.js",
|
||||
"scripts": { // [!code focus:5]
|
||||
"test": "echo \"Error: no test specified\" && exit 1" // needs a comma // [!code --]
|
||||
"test": "echo \"Error: no test specified\" && exit 1", // [!code ++]
|
||||
"start": "node ." // [!code ++]
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
||||
```
|
||||
|
||||
<Callout>
|
||||
The `node .` script will run the file you have specified at the `main` entry in your `package.json` file. If you don't
|
||||
have it set yet, make sure to select your bot's main file as `main`!
|
||||
</Callout>
|
||||
|
||||
Now, whenever you run the `start` script in your bot's directory, it will run the `node .` command.
|
||||
|
||||
```sh tab="npm"
|
||||
npm run start
|
||||
```
|
||||
|
||||
```sh tab="yarn"
|
||||
yarn run start
|
||||
```
|
||||
|
||||
```sh tab="pnpm"
|
||||
pnpm run start
|
||||
```
|
||||
|
||||
```sh tab="bun"
|
||||
bun run start
|
||||
```
|
||||
|
||||
Let's create another script to lint your code via the command line. Add the following line to your scripts:
|
||||
|
||||
```json title="package.json"
|
||||
{
|
||||
"name": "my-bot",
|
||||
"version": "1.0.0",
|
||||
"description": "A Discord bot!",
|
||||
"main": "index.js",
|
||||
"scripts": { // [!code focus:6]
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "node ." // needs a comma // [!code --]
|
||||
"start": "node .", // [!code ++]
|
||||
"lint": "eslint ." // [!code ++]
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
||||
```
|
||||
|
||||
Now, whenever you run the `lint` script, ESLint will lint your `index.js` file.
|
||||
|
||||
```sh tab="npm"
|
||||
npm run lint
|
||||
```
|
||||
|
||||
```sh tab="yarn"
|
||||
yarn run lint
|
||||
```
|
||||
|
||||
```sh tab="pnpm"
|
||||
pnpm run lint
|
||||
```
|
||||
|
||||
```sh tab="bun"
|
||||
bun run lint
|
||||
```
|
||||
|
||||
Your `package.json` file should now look similar to the following:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "my-bot",
|
||||
"version": "1.0.0",
|
||||
"description": "A Discord bot!",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "node .",
|
||||
"lint": "eslint ."
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
||||
```
|
||||
|
||||
And that's it! You can always add more scripts now, running them with:
|
||||
|
||||
```sh tab="npm"
|
||||
npm run <script-name>
|
||||
```
|
||||
|
||||
```sh tab="yarn"
|
||||
yarn run <script-name>
|
||||
```
|
||||
|
||||
```sh tab="pnpm"
|
||||
pnpm run <script-name>
|
||||
```
|
||||
|
||||
```sh tab="bun"
|
||||
bun run <script-name>
|
||||
```
|
||||
|
||||
<Cards>
|
||||
<Card title="Package Scripts" href="https://docs.npmjs.com/cli/v7/using-npm/scripts">
|
||||
Package scripts allow some more configuration (like pre-, post- and lifecycle scripts) than we can cover in this
|
||||
guide. Check out the official documentation on for more information.
|
||||
</Card>
|
||||
</Cards>
|
||||
@@ -1,113 +0,0 @@
|
||||
---
|
||||
title: PM2
|
||||
---
|
||||
|
||||
PM2 is a process manager. It manages your applications' states, so you can start, stop, restart, and delete processes. It offers features such as monitoring running processes and setting up a "start with operating system" (be that Windows, Linux, or Mac) so your processes start when you boot your system.
|
||||
|
||||
## Installation
|
||||
|
||||
You can install PM2 via the following command:
|
||||
|
||||
```sh tab="npm"
|
||||
npm install --global pm2
|
||||
```
|
||||
|
||||
```sh tab="yarn"
|
||||
yarn global add pm2
|
||||
```
|
||||
|
||||
```sh tab="pnpm"
|
||||
pnpm add --global pm2
|
||||
```
|
||||
|
||||
```sh tab="bun"
|
||||
bun add --global pm2
|
||||
```
|
||||
|
||||
## Starting your app
|
||||
|
||||
After you install PM2, the easiest way you can start your app is by going to the directory your bot is in and then run the following:
|
||||
|
||||
```sh
|
||||
pm2 start your-app-name.js
|
||||
```
|
||||
|
||||
### Additional notes
|
||||
|
||||
The `pm2 start` script allows for more optional command-line arguments.
|
||||
|
||||
- `--name`: This allows you to set the name of your process when listing it up with `pm2 list` or `pm2 monit`:
|
||||
|
||||
```sh
|
||||
pm2 start your-app-name.js --name "Some cool name"
|
||||
```
|
||||
|
||||
- `--watch`: This option will automatically restart your process as soon as a file change is detected, which can be useful for development environments:
|
||||
|
||||
```bash
|
||||
pm2 start your-app-name.js --watch
|
||||
```
|
||||
|
||||
<Callout>
|
||||
The `pm2 start` command can take more optional parameters, but only these two are relevant. If you want to see all the
|
||||
parameters available, you can check the documentation of pm2
|
||||
[here](https://pm2.keymetrics.io/docs/usage/pm2-doc-single-page/).
|
||||
</Callout>
|
||||
|
||||
Once the process launches with pm2, you can run `pm2 monit` to monitor all console outputs from the processes started by pm2. This accounts for any `console.log()` in your code or outputted errors.
|
||||
|
||||
In a similar fashion to how you start the process, running `pm2 stop` will stop the current process without removing it from PM2's interface:
|
||||
|
||||
```sh
|
||||
pm2 stop your-app-name.js
|
||||
```
|
||||
|
||||
## Setting up booting with your system
|
||||
|
||||
Perhaps one of the more useful features of PM2 is being able to boot up with your Operating System. This feature will ensure that your bot processes will always be started after an (unexpected) reboot (e.g., after a power outage).
|
||||
|
||||
The initial steps differ per OS. In this guide, we'll cover those for Windows and Linux/macOS.
|
||||
|
||||
### Initial steps for Windows
|
||||
|
||||
It is recommended to use `pm2-installer`. Follow the steps over at their [`GitHub`](https://github.com/jessety/pm2-installer).
|
||||
|
||||
### Initial steps for Linux/macOS
|
||||
|
||||
You'll need a start script, which you can get by running the following command:
|
||||
|
||||
```sh
|
||||
# Detects the available init system, generates the config, and enables startup system
|
||||
pm2 startup
|
||||
```
|
||||
|
||||
Or, if you want to specify your machine manually, select one of the options with the command:
|
||||
|
||||
```sh
|
||||
pm2 startup [ubuntu | ubuntu14 | ubuntu12 | centos | centos6 | arch | oracle | amazon | macos | darwin | freesd | systemd | systemv | upstart | launchd | rcd | openrc]
|
||||
```
|
||||
|
||||
The output of running one of the commands listed above will output a command for you to run with all environment variables and options configured.
|
||||
|
||||
**Example output for an Ubuntu user:**
|
||||
|
||||
```
|
||||
[PM2] You have to run this command as root. Execute the following command:
|
||||
sudo su -c "env PATH=$PATH:/home/user/.nvm/versions/node/v8.9/bin pm2 startup ubuntu -u user --hp /home/user
|
||||
```
|
||||
|
||||
After running that command, you can continue to the next step.
|
||||
|
||||
### Saving the current process list
|
||||
|
||||
To save the current process list so it will automatically get started after a restart, run the following command:
|
||||
|
||||
```sh
|
||||
pm2 save
|
||||
```
|
||||
|
||||
To disable this, you can run the following command:
|
||||
|
||||
```sh
|
||||
pm2 unstartup
|
||||
```
|
||||
@@ -1,52 +0,0 @@
|
||||
---
|
||||
title: Context Menus
|
||||
---
|
||||
|
||||
Context Menus are application commands which appear when right clicking or tapping a user or a message, in the Apps submenu.
|
||||
|
||||
<Callout>
|
||||
This page is a follow-up to the [slash commands](../slash-commands/advanced-creation) section. Please carefully read
|
||||
those pages first so that you can understand the methods used in this section.
|
||||
</Callout>
|
||||
|
||||
## Registering context menu commands
|
||||
|
||||
To create a context menu command, use the `ContextMenuCommandBuilder` class. You can then set the type of the context menu (user or message) using the `setType()` method.
|
||||
|
||||
```js
|
||||
const { ContextMenuCommandBuilder, ApplicationCommandType } = require('discord.js');
|
||||
|
||||
const data = new ContextMenuCommandBuilder().setName('User Information').setType(ApplicationCommandType.User);
|
||||
```
|
||||
|
||||
## Receiving context menu command interactions
|
||||
|
||||
Context menus commands, just like slash commands, are received via an interaction. You can check if a given interaction is a context menu by invoking the `isContextMenuCommand()` method, or the `isMessageContextMenuCommand()` and `isUserContextMenuCommand()` methods to check for the specific type of context menu interaction:
|
||||
|
||||
```js
|
||||
client.on(Events.InteractionCreate, (interaction) => {
|
||||
if (!interaction.isUserContextMenuCommand()) return;
|
||||
console.log(interaction);
|
||||
});
|
||||
```
|
||||
|
||||
## Extracting data from context menus
|
||||
|
||||
For user context menus, you can get the targeted user by accessing the `targetUser` or `targetMember` property from the `UserContextMenuCommandInteraction`.
|
||||
|
||||
For message context menus, you can get the targeted message by accessing the `targetMessage` property from the `MessageContextMenuCommandInteraction`.
|
||||
|
||||
```js
|
||||
client.on(Events.InteractionCreate, (interaction) => {
|
||||
if (!interaction.isUserContextMenuCommand()) return;
|
||||
// Get the User's username from context menu
|
||||
const { username } = interaction.targetUser;
|
||||
console.log(username);
|
||||
});
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Context menu commands cannot have subcommands or any options.
|
||||
- Responding to context menu commands functions the same as slash commands. Refer to our [slash command responses](../slash-commands/response-methods) guide for more information.
|
||||
- Context menu command permissions also function the same as slash commands. Refer to our [slash command permissions](../slash-commands/permissions) guide for more information.
|
||||
|
Before Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 25 KiB |
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"title": "Other Interactions"
|
||||
}
|
||||
@@ -1,192 +0,0 @@
|
||||
---
|
||||
title: Modals
|
||||
---
|
||||
|
||||
With modals you can create pop-up forms that allow users to provide you with formatted inputs through submissions. We'll cover how to create, show, and receive modal forms using discord.js!
|
||||
|
||||
<Callout>
|
||||
This page is a follow-up to the [interactions (slash commands) page](../slash-commands/advanced-creation). Please
|
||||
carefully read that section first, so that you can understand the methods used in this section.
|
||||
</Callout>
|
||||
|
||||
## Building and responding with modals
|
||||
|
||||
Unlike message components, modals aren't strictly components themselves. They're a callback structure used to respond to interactions.
|
||||
|
||||
<Callout>
|
||||
You can have a maximum of five `ActionRowBuilder`s per modal builder, and one `TextInputBuilder` within an
|
||||
`ActionRowBuilder`. Currently, you can only use `TextInputBuilder`s in modal action rows builders.
|
||||
</Callout>
|
||||
|
||||
To create a modal you construct a new `ModalBuilder`. You can then use the setters to add the custom id and title.
|
||||
|
||||
```js
|
||||
const { Events, ModalBuilder } = require('discord.js');
|
||||
|
||||
client.on(Events.InteractionCreate, async (interaction) => {
|
||||
if (!interaction.isChatInputCommand()) return;
|
||||
|
||||
if (interaction.commandName === 'ping') {
|
||||
const modal = new ModalBuilder().setCustomId('myModal').setTitle('My Modal');
|
||||
|
||||
// TODO: Add components to modal...
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
<Callout>
|
||||
The custom id is a developer-defined string of up to 100 characters. Use this field to ensure you can uniquely define
|
||||
all incoming interactions from your modals!
|
||||
</Callout>
|
||||
|
||||
The next step is to add the input fields in which users responding can enter free-text. Adding inputs is similar to adding components to messages.
|
||||
|
||||
At the end, we then call `ChatInputCommandInteraction#showModal` to display the modal to the user.
|
||||
|
||||
<Callout type="warn">
|
||||
If you're using typescript you'll need to specify the type of components your action row holds. This can be done by specifying the generic parameter in `ActionRowBuilder`:
|
||||
|
||||
```diff
|
||||
- new ActionRowBuilder()
|
||||
+ new ActionRowBuilder<ModalActionRowComponentBuilder>()
|
||||
```
|
||||
|
||||
</Callout>
|
||||
|
||||
```js
|
||||
const { ActionRowBuilder, Events, ModalBuilder, TextInputBuilder, TextInputStyle } = require('discord.js');
|
||||
|
||||
client.on(Events.InteractionCreate, async (interaction) => {
|
||||
if (!interaction.isChatInputCommand()) return;
|
||||
|
||||
if (interaction.commandName === 'ping') {
|
||||
// Create the modal
|
||||
const modal = new ModalBuilder().setCustomId('myModal').setTitle('My Modal');
|
||||
|
||||
// Add components to modal
|
||||
|
||||
// Create the text input components
|
||||
const favoriteColorInput = new TextInputBuilder()
|
||||
.setCustomId('favoriteColorInput')
|
||||
// The label is the prompt the user sees for this input
|
||||
.setLabel("What's your favorite color?")
|
||||
// Short means only a single line of text
|
||||
.setStyle(TextInputStyle.Short);
|
||||
|
||||
const hobbiesInput = new TextInputBuilder()
|
||||
.setCustomId('hobbiesInput')
|
||||
.setLabel("What's some of your favorite hobbies?")
|
||||
// Paragraph means multiple lines of text.
|
||||
.setStyle(TextInputStyle.Paragraph);
|
||||
|
||||
// An action row only holds one text input,
|
||||
// so you need one action row per text input.
|
||||
const firstActionRow = new ActionRowBuilder().addComponents(favoriteColorInput);
|
||||
const secondActionRow = new ActionRowBuilder().addComponents(hobbiesInput);
|
||||
|
||||
// Add inputs to the modal
|
||||
modal.addComponents(firstActionRow, secondActionRow);
|
||||
|
||||
// Show the modal to the user
|
||||
await interaction.showModal(modal); // [!code word:showModal]
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Restart your bot and invoke the `/ping` command again. You should see a popup form resembling the image below:
|
||||
|
||||

|
||||
|
||||
<Callout type="warn">
|
||||
Showing a modal must be the first response to an interaction. You cannot `defer()` or `deferUpdate()` then show a
|
||||
modal later.
|
||||
</Callout>
|
||||
|
||||
### Input styles
|
||||
|
||||
Currently there are two different input styles available:
|
||||
|
||||
- `Short`, a single-line text entry;
|
||||
- `Paragraph`, a multi-line text entry similar to the HTML `<textarea>`;
|
||||
|
||||
### Input properties
|
||||
|
||||
In addition to the `customId`, `label` and `style`, a text input can be customised in a number of ways to apply validation, prompt the user, or set default values via the `TextInputBuilder` methods:
|
||||
|
||||
```js
|
||||
const input = new TextInputBuilder()
|
||||
// set the maximum number of characters to allow
|
||||
.setMaxLength(1_000)
|
||||
// set the minimum number of characters required for submission
|
||||
.setMinLength(10)
|
||||
// set a placeholder string to prompt the user
|
||||
.setPlaceholder('Enter some text!')
|
||||
// set a default value to pre-fill the input
|
||||
.setValue('Default')
|
||||
// require a value in this input field
|
||||
.setRequired(true);
|
||||
```
|
||||
|
||||
## Receiving modal submissions
|
||||
|
||||
### Interaction collectors
|
||||
|
||||
Modal submissions can be collected within the scope of the interaction that showed it by utilising an `InteractionCollector`, or the `ChatInputCommandInteraction#awaitModalSubmit` promisified method. These both provide instances of the `ModalSubmitInteraction` class as collected items.
|
||||
|
||||
For a detailed guide on receiving message components via collectors, please refer to the [collectors guide](../popular-topics/collectors#interaction-collectors).
|
||||
|
||||
### The interactionCreate event
|
||||
|
||||
To receive a `ModalSubmitInteraction` event, attach an `Client#interactionCreate` event listener to your client and use the `BaseInteraction#isModalSubmit` type guard to make sure you only receive modals:
|
||||
|
||||
```js
|
||||
client.on(Events.InteractionCreate, (interaction) => {
|
||||
if (!interaction.isModalSubmit()) return;
|
||||
console.log(interaction);
|
||||
});
|
||||
```
|
||||
|
||||
## Responding to modal submissions
|
||||
|
||||
The `ModalSubmitInteraction` class provides the same methods as the `ChatInputCommandInteraction` class. These methods behave equally:
|
||||
|
||||
- `reply()`
|
||||
- `editReply()`
|
||||
- `deferReply()`
|
||||
- `fetchReply()`
|
||||
- `deleteReply()`
|
||||
- `followUp()`
|
||||
|
||||
If the modal was shown from a `ButtonInteraction` or `StringSelectMenuInteraction`, it will also provide these methods, which behave equally:
|
||||
|
||||
- `update()`
|
||||
- `deferUpdate()`
|
||||
|
||||
```js
|
||||
client.on(Events.InteractionCreate, async (interaction) => {
|
||||
if (!interaction.isModalSubmit()) return;
|
||||
if (interaction.customId === 'myModal') {
|
||||
await interaction.reply({ content: 'Your submission was received successfully!' });
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
<Callout>
|
||||
If you're using typescript, you can use the `ModalSubmitInteraction#isFromMessage` typeguard, to make sure the
|
||||
received interaction was from a `MessageComponentInteraction`.
|
||||
</Callout>
|
||||
|
||||
## Extracting data from modal submissions
|
||||
|
||||
You'll most likely need to read the data sent by the user in the modal. You can do this by accessing the `ModalSubmitInteraction#fields`. From there you can call `ModalSubmitFields#getTextInputValue` with the custom id of the text input to get the value.
|
||||
|
||||
```js
|
||||
client.on(Events.InteractionCreate, (interaction) => {
|
||||
if (!interaction.isModalSubmit()) return;
|
||||
|
||||
// Get the data entered by the user
|
||||
const favoriteColor = interaction.fields.getTextInputValue('favoriteColorInput');
|
||||
const hobbies = interaction.fields.getTextInputValue('hobbiesInput');
|
||||
console.log({ favoriteColor, hobbies });
|
||||
});
|
||||
```
|
||||
@@ -1,40 +0,0 @@
|
||||
---
|
||||
title: Action rows
|
||||
---
|
||||
|
||||
Action rows are a layout component with five "slots" that can be filled with other components. At the time of writing this guide, buttons take up one slot and select menus take up five "slots". Accordingly, each `ActionRow` can hold up to 5 buttons or a single select menu. A message can have up to five rows without providing the `IsComponentsV2` message flag. If you want to place buttons or action rows into the message body, they have to be wrapped inside rows.
|
||||
|
||||
<Callout>
|
||||
Read our guide section on [display components](../popular-topics/display-components) if you want to learn more about
|
||||
those.
|
||||
</Callout>
|
||||
|
||||
## Building action rows
|
||||
|
||||
To create an action row, use the `ActionRowBuilder` class and the `ActionRowBuilder#addComponents` method to add buttons or a select menu.
|
||||
|
||||
```js
|
||||
const row = new ActionRowBuilder().addComponents(component);
|
||||
```
|
||||
|
||||
<Callout>
|
||||
If you're using TypeScript, you'll need to specify the type of components your action row holds. This can be done by specifying the component builder you will add to it using a generic parameter in `ActionRowBuilder`.
|
||||
|
||||
```js
|
||||
new ActionRowBuilder() // [!code --]
|
||||
new ActionRowBuilder<ButtonBuilder>() // [!code ++]
|
||||
```
|
||||
|
||||
</Callout>
|
||||
|
||||
## Sending action rows
|
||||
|
||||
Once one or many components are inside your row(s), send them in the `components` property of your `InteractionReplyOptions` (extends `BaseMessageOptions`).
|
||||
|
||||
```js
|
||||
const row = new ActionRowBuilder().addComponents(component);
|
||||
|
||||
await interaction.reply({ components: [row] });
|
||||
```
|
||||
|
||||
To learn how to create the buttons and select menus that will go inside your row, including more detailed examples on how you might use them, continue on to the other pages in this section.
|
||||
@@ -1,122 +0,0 @@
|
||||
---
|
||||
title: Buttons
|
||||
---
|
||||
|
||||
The first type of interactive component we'll cover creating is a Button. Buttons are available in a variety of styles and can be used to provide permanent interfaces, temporary confirmation workflows, and other forms of additional interaction with your bot.
|
||||
|
||||
<Callout>
|
||||
This page is a follow-up to the [slash commands](../slash-commands/advanced-creation) section and [action
|
||||
rows](./action-rows) page. Please carefully read those pages first so that you can understand the methods used here.
|
||||
</Callout>
|
||||
|
||||
## Building buttons
|
||||
|
||||
Buttons are one of the `MessageComponent` classes, which can be sent via messages or interaction responses.
|
||||
|
||||
For this example, you're going to expand on the `ban` command that was previously covered on the [parsing options](../slash-commands/parsing-options) page with a confirmation workflow.
|
||||
|
||||
To create your buttons, use the `ButtonBuilder` class, defining at least the `customId`, `style` and `label`.
|
||||
|
||||
```js
|
||||
const { ButtonBuilder, ButtonStyle, SlashCommandBuilder } = require('discord.js');
|
||||
|
||||
module.exports = {
|
||||
// data: new SlashCommandBuilder()...
|
||||
async execute(interaction) {
|
||||
const target = interaction.options.getUser('target');
|
||||
const reason = interaction.options.getString('reason') ?? 'No reason provided';
|
||||
|
||||
const confirm = new ButtonBuilder().setCustomId('confirm').setLabel('Confirm Ban').setStyle(ButtonStyle.Danger);
|
||||
|
||||
const cancel = new ButtonBuilder().setCustomId('cancel').setLabel('Cancel').setStyle(ButtonStyle.Secondary);
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
<Callout>
|
||||
The custom id is a developer-defined string of up to 100 characters. Use this field to ensure you can uniquely define
|
||||
all incoming interactions.
|
||||
</Callout>
|
||||
|
||||
## Sending buttons
|
||||
|
||||
To send your buttons, create an action row and add the buttons as components. Then, send the row in the `components` property of `InteractionReplyOptions` (extends `BaseMessageOptions`).
|
||||
|
||||
```js
|
||||
const { ActionRowBuilder, ButtonBuilder, ButtonStyle, SlashCommandBuilder } = require('discord.js');
|
||||
|
||||
module.exports = {
|
||||
// data: new SlashCommandBuilder()...
|
||||
async execute(interaction) {
|
||||
const target = interaction.options.getUser('target');
|
||||
const reason = interaction.options.getString('reason') ?? 'No reason provided';
|
||||
|
||||
const confirm = new ButtonBuilder().setCustomId('confirm').setLabel('Confirm Ban').setStyle(ButtonStyle.Danger);
|
||||
|
||||
const cancel = new ButtonBuilder().setCustomId('cancel').setLabel('Cancel').setStyle(ButtonStyle.Secondary);
|
||||
|
||||
const row = new ActionRowBuilder().addComponents(cancel, confirm);
|
||||
|
||||
await interaction.reply({
|
||||
content: `Are you sure you want to ban ${target} for reason: ${reason}?`,
|
||||
components: [row],
|
||||
});
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
Restart your bot and then send the command to a channel your bot has access to. If all goes well, you should see something like this:
|
||||
|
||||
## Button styles
|
||||
|
||||
You'll notice in the above example that two different styles of buttons have been used, the grey Secondary style and the red Danger style. These were chosen specifically to support good UI/UX principles. In total, there are five button styles that can be used as appropriate to the action of the button:
|
||||
|
||||
- `Primary` style buttons are blue. These are suitable for most general purpose actions, where it's the primary or most significant action expected.
|
||||
- `Secondary` style buttons are grey. Use these for less important actions like the "Cancel" button in the example above.
|
||||
- `Success` style buttons are green. Similar to the Primary button, these are a good choice for "positive" confirmation actions.
|
||||
- `Danger` style buttons are red. Where the action being confirmed is "destructive", such a ban or delete, using a red button helps alert the user to the risk of the action.
|
||||
- `Link` style buttons are also grey, but are tagged with the "external link" symbol. These buttons will open the provided link in the browser without sending an interaction to the bot.
|
||||
|
||||
## Link buttons
|
||||
|
||||
Link buttons are a little different to the other styles. `Link` buttons **must** have a `url`, **cannot** have a `customId` and **do not** send an interaction event when clicked.
|
||||
|
||||
```js
|
||||
const button = new ButtonBuilder()
|
||||
.setLabel('discord.js docs')
|
||||
.setURL('https://discord.js.org') // [!code word:setURL]
|
||||
.setStyle(ButtonStyle.Link); // [!code word:Link]
|
||||
```
|
||||
|
||||
## Disabled buttons
|
||||
|
||||
If you want to prevent a button from being used, but not remove it from the message, you can disable it with the `ButtonBuilder#setDisabled` method:
|
||||
|
||||
```js
|
||||
const button = new ButtonBuilder()
|
||||
.setCustomId('disabled')
|
||||
.setLabel('Click me?')
|
||||
.setStyle(ButtonStyle.Primary)
|
||||
.setDisabled(true); // [!code word:setDisabled]
|
||||
```
|
||||
|
||||
## Emoji buttons
|
||||
|
||||
If you want to use a guild emoji within a `ButtonBuilder`, you can use the `ButtonBuilder#setEmoji` method:
|
||||
|
||||
```js
|
||||
const button = new ButtonBuilder()
|
||||
.setCustomId('primary')
|
||||
.setLabel('Primary')
|
||||
.setStyle(ButtonStyle.Primary)
|
||||
.setEmoji('123456789012345678'); // [!code word:setEmoji]
|
||||
```
|
||||
|
||||
#### Next steps
|
||||
|
||||
<Callout>
|
||||
Buttons can also be set as an accessory in section components. Check the guide section on [display
|
||||
components](../popular-topics/display-components) if you want to find out more about this.
|
||||
</Callout>
|
||||
|
||||
That's everything you need to know about building and sending buttons! From here you can learn about the other type of message component, [select menus](./select-menus), or have your bot start listening to [component interactions](./interactions) from your buttons.
|
||||
|
Before Width: | Height: | Size: 16 KiB |
@@ -1,213 +0,0 @@
|
||||
---
|
||||
title: Interactions
|
||||
---
|
||||
|
||||
## Component interactions
|
||||
|
||||
Every button click or select menu selection on a component sent by your bot fires an `interaction`, triggering the `Client#interactionCreate` event. How you decide to handle this will likely depend on the purpose of the components. Options include:
|
||||
|
||||
- Waiting for a single interaction via `awaitMessageComponent()` on a channel or a message.
|
||||
- Listening for multiple interactions over a period of time using an `InteractionCollector`.
|
||||
- Creating a permanent component handler in the `Client#interactionCreate` event.
|
||||
|
||||
<Callout>
|
||||
This page is a follow-up to the [slash commands](../slash-commands/advanced-creation) section, and assumes you have
|
||||
created either [buttons](./buttons) or [select menus](./select-menus) as detailed in this guide. Please carefully read
|
||||
those pages first so that you can understand the methods used here.
|
||||
</Callout>
|
||||
|
||||
## Responding to component interactions
|
||||
|
||||
As with all other interactions message components interactions require a response within 3 seconds, else Discord will treat them as failed.
|
||||
|
||||
Like slash commands, all types of message component interactions support the `reply()`, `deferReply()`, `editReply()` and `followUp()` methods, with the option for these responses to be ephemeral. These function identically to how they do for slash commands, so refer to the page on [slash command response methods](../slash-commands/response-methods) for information on those.
|
||||
|
||||
Component interactions also support two additional methods of response, detailed below and demonstrated in examples later on the page.
|
||||
|
||||
### Updates
|
||||
|
||||
Responding to a component interaction via the `update()` method acknowledges the interaction by editing the message on which the component was attached. This method should be preferred to calling `editReply()` on the original interaction which sent the components. Like `editReply()`, `update()` cannot be used to change the ephemeral state of a message.
|
||||
|
||||
Once `update()` has been called, future messages can be sent by calling `followUp()` or edits can be made by calling `editReply()` on the component interaction.
|
||||
|
||||
### Deferred updates
|
||||
|
||||
Responding to a component interaction via the `deferUpdate()` method acknowledges the interaction and resets the message state. This method can be used to suppress the need for further responses, however it's encouraged to provide meaningful feedback to users via an `update()` or ephemeral `reply()` at least.
|
||||
|
||||
Once `deferUpdate()` has been called, future messages can be sent by calling `followUp()` or edits can be made by calling `editReply()` on the component interaction.
|
||||
|
||||
## Awaiting components
|
||||
|
||||
If you followed our [buttons](./buttons) guide, the confirmation workflow for the `ban` command is a good example of a situation where your bot is expecting to receive a single response, from either the Confirm or Cancel button.
|
||||
|
||||
Begin by adding `withResponse` to the options, and then calling `Message#awaitMessageComponent` on the message. This method returns a [Promise](../additional-info/async-await) that resolves when any interaction passes its filter (if one is provided), or throws if none are received before the timeout. If this happens, remove the components and notify the user.
|
||||
|
||||
```js title="commands/moderation/ban.js"
|
||||
const response = await interaction.reply({
|
||||
content: `Are you sure you want to ban ${target.username} for reason: ${reason}?`,
|
||||
components: [row],
|
||||
// [!code ++]
|
||||
withResponse: true,
|
||||
});
|
||||
|
||||
// [!code ++:7]
|
||||
const collectorFilter = (i) => i.user.id === interaction.user.id;
|
||||
|
||||
try {
|
||||
const confirmation = await response.resource.message.awaitMessageComponent({ filter: collectorFilter, time: 60_000 });
|
||||
} catch {
|
||||
await interaction.editReply({ content: 'Confirmation not received within 1 minute, cancelling', components: [] });
|
||||
}
|
||||
```
|
||||
|
||||
<Callout>
|
||||
The filter applied here ensures that only the user who triggered the original interaction can use the buttons.
|
||||
</Callout>
|
||||
|
||||
With the confirmation collected, check which button was clicked and perform the appropriate action.
|
||||
|
||||
```js
|
||||
const response = await interaction.reply({
|
||||
content: `Are you sure you want to ban ${target.username} for reason: ${reason}?`,
|
||||
components: [row],
|
||||
withResponse: true,
|
||||
});
|
||||
|
||||
const collectorFilter = (i) => i.user.id === interaction.user.id;
|
||||
try {
|
||||
// [!code focus:8] [!code word:customId]
|
||||
const confirmation = await response.resource.message.awaitMessageComponent({ filter: collectorFilter, time: 60_000 });
|
||||
|
||||
if (confirmation.customId === 'confirm') {
|
||||
await interaction.guild.members.ban(target);
|
||||
await confirmation.update({ content: `${target.username} has been banned for reason: ${reason}`, components: [] });
|
||||
} else if (confirmation.customId === 'cancel') {
|
||||
await confirmation.update({ content: 'Action cancelled', components: [] });
|
||||
}
|
||||
} catch {
|
||||
await interaction.editReply({ content: 'Confirmation not received within 1 minute, cancelling', components: [] });
|
||||
}
|
||||
```
|
||||
|
||||
## Component collectors
|
||||
|
||||
For situations where you want to collect multiple interactions, the Collector approach is better suited than awaiting singular interactions. Following on from the [select menus](./select-menus) guide, you're going to extend that example to use an `InteractionCollector` to listen for multiple `StringSelectMenuInteraction`.
|
||||
|
||||
Begin by adding `withResponse` to the options, and then calling `Message#createMessageComponentCollector` on this instance. This method returns an InteractionCollector that will fire its `InteractionCollector#collect` event whenever an interaction passes its filter (if one is provided).
|
||||
|
||||
In the `collect` event, each interaction is a `StringSelectMenuInteraction` thanks to the `componentType: ComponentType.StringSelect` option provided to the collector (and because that was the only type of component in the message). The selected value(s) are available via the `StringSelectMenuInteraction#values` property.
|
||||
|
||||
```js
|
||||
const response = await interaction.reply({
|
||||
content: 'Choose your starter!',
|
||||
components: [row],
|
||||
withResponse: true, // [!code ++]
|
||||
});
|
||||
|
||||
// [!code ++:9]
|
||||
const collector = response.resource.message.createMessageComponentCollector({
|
||||
componentType: ComponentType.StringSelect,
|
||||
time: 3_600_000, // 1 hour
|
||||
});
|
||||
|
||||
collector.on('collect', async (i) => {
|
||||
const selection = i.values[0];
|
||||
await i.reply(`${i.user} has selected ${selection}!`);
|
||||
});
|
||||
```
|
||||
|
||||
## The Client#interactionCreate event
|
||||
|
||||
Third and finally, you may wish to have a listener setup to respond to permanent button or select menu features of your guild. For this, returning to the `Client#interactionCreate` event is the best approach.
|
||||
|
||||
If your event handling has been setup in multiple files as per our [event handling](../app-creation/handling-events) guide, you should already have an `events/interactionCreate.js` file that looks something like this.
|
||||
|
||||
```js title="events/interactionCreate.js"
|
||||
const { Events } = require('discord.js');
|
||||
|
||||
module.exports = {
|
||||
name: Events.InteractionCreate,
|
||||
async execute(interaction) {
|
||||
if (!interaction.isChatInputCommand()) return;
|
||||
|
||||
const command = interaction.client.commands.get(interaction.commandName);
|
||||
|
||||
if (!command) {
|
||||
console.error(`No command matching ${interaction.commandName} was found.`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await command.execute(interaction);
|
||||
} catch (error) {
|
||||
console.error(`Error executing ${interaction.commandName}`);
|
||||
console.error(error);
|
||||
}
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
The way this was previously set up returns from the `execute` function whenever it encounters an interaction that is not a `ChatInputCommandInteraction`, as shown on the highlighted line above. The first change that needs to be made is to invert this logic, without actually changing the functionality.
|
||||
|
||||
```js title="events/interactionCreate.js"
|
||||
const { Events } = require('discord.js');
|
||||
|
||||
// [!code focus:22]
|
||||
module.exports = {
|
||||
name: Events.InteractionCreate,
|
||||
async execute(interaction) {
|
||||
if (!interaction.isChatInputCommand()) return; // [!code --]
|
||||
// [!code ++:15]
|
||||
if (interaction.isChatInputCommand()) {
|
||||
const command = interaction.client.commands.get(interaction.commandName);
|
||||
|
||||
if (!command) {
|
||||
console.error(`No command matching ${interaction.commandName} was found.`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await command.execute(interaction);
|
||||
} catch (error) {
|
||||
console.error(`Error executing ${interaction.commandName}`);
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
Now that the logic is setup to run code when something _is_ a `ChatInputCommandInteraction`, rather than to stop and exit when it isn't, you can add handling for additional interaction types via simple `if...else` logic.
|
||||
|
||||
```js
|
||||
const { Events } = require('discord.js');
|
||||
|
||||
module.exports = {
|
||||
name: Events.InteractionCreate,
|
||||
// [!code focus]
|
||||
async execute(interaction) {
|
||||
// [!code focus]
|
||||
if (interaction.isChatInputCommand()) {
|
||||
const command = interaction.client.commands.get(interaction.commandName);
|
||||
|
||||
if (!command) {
|
||||
console.error(`No command matching ${interaction.commandName} was found.`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await command.execute(interaction);
|
||||
} catch (error) {
|
||||
console.error(`Error executing ${interaction.commandName}`);
|
||||
console.error(error);
|
||||
}
|
||||
// [!code focus:5] [!code ++:5]
|
||||
} else if (interaction.isButton()) {
|
||||
// respond to the button
|
||||
} else if (interaction.isStringSelectMenu()) {
|
||||
// respond to the select menu
|
||||
}
|
||||
// [!code focus]
|
||||
},
|
||||
};
|
||||
```
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"pages": ["action-rows", "buttons", "select-menus", "interactions"]
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
---
|
||||
title: Select Menus
|
||||
---
|
||||
|
||||
Select menus are one of the `MessageComponent` classes, which can be sent via messages or interaction responses.
|
||||
|
||||
<Callout>
|
||||
This page is a follow-up to the [slash commands](../slash-commands/advanced-creation) section and [action
|
||||
rows](../interactive-components/action-rows) page. Please carefully read those pages first so that you can understand
|
||||
the methods used here.
|
||||
</Callout>
|
||||
|
||||
## Building string select menus
|
||||
|
||||
The "standard" and most customizable type of select menu is the string select menu. To create a string select menu, use the `StringSelectMenuBuilder` and `StringSelectMenuOptionBuilder` classes.
|
||||
|
||||
If you're a Pokémon fan, you've probably made a selection pretty similar to this example at some point in your life!
|
||||
|
||||
```js
|
||||
const { StringSelectMenuBuilder, StringSelectMenuOptionBuilder, SlashCommandBuilder } = require('discord.js');
|
||||
|
||||
module.exports = {
|
||||
// data: new SlashCommandBuilder()...
|
||||
async execute(interaction) {
|
||||
const select = new StringSelectMenuBuilder()
|
||||
.setCustomId('starter')
|
||||
.setPlaceholder('Make a selection!')
|
||||
.addOptions(
|
||||
new StringSelectMenuOptionBuilder()
|
||||
.setLabel('Bulbasaur')
|
||||
.setDescription('The dual-type Grass/Poison Seed Pokémon.')
|
||||
.setValue('bulbasaur'),
|
||||
new StringSelectMenuOptionBuilder()
|
||||
.setLabel('Charmander')
|
||||
.setDescription('The Fire-type Lizard Pokémon.')
|
||||
.setValue('charmander'),
|
||||
new StringSelectMenuOptionBuilder()
|
||||
.setLabel('Squirtle')
|
||||
.setDescription('The Water-type Tiny Turtle Pokémon.')
|
||||
.setValue('squirtle'),
|
||||
);
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
<Callout>
|
||||
The custom id is a developer-defined string of up to 100 characters. Use this field to ensure you can uniquely define
|
||||
all incoming interactions from your select menus!
|
||||
</Callout>
|
||||
|
||||
## Sending select menus
|
||||
|
||||
To send your select menu, create an action row and add the buttons as components. Then, send the row in the `components` property of `InteractionReplyOptions` (extends `BaseMessageOptions`).
|
||||
|
||||
```js
|
||||
const {
|
||||
ActionRowBuilder,
|
||||
StringSelectMenuBuilder,
|
||||
StringSelectMenuOptionBuilder,
|
||||
SlashCommandBuilder,
|
||||
} = require('discord.js');
|
||||
// [!code focus:30]
|
||||
module.exports = {
|
||||
// data: new SlashCommandBuilder()...
|
||||
async execute(interaction) {
|
||||
const select = new StringSelectMenuBuilder()
|
||||
.setCustomId('starter')
|
||||
.setPlaceholder('Make a selection!')
|
||||
.addOptions(
|
||||
new StringSelectMenuOptionBuilder()
|
||||
.setLabel('Bulbasaur')
|
||||
.setDescription('The dual-type Grass/Poison Seed Pokémon.')
|
||||
.setValue('bulbasaur'),
|
||||
new StringSelectMenuOptionBuilder()
|
||||
.setLabel('Charmander')
|
||||
.setDescription('The Fire-type Lizard Pokémon.')
|
||||
.setValue('charmander'),
|
||||
new StringSelectMenuOptionBuilder()
|
||||
.setLabel('Squirtle')
|
||||
.setDescription('The Water-type Tiny Turtle Pokémon.')
|
||||
.setValue('squirtle'),
|
||||
);
|
||||
|
||||
// [!code ++:6]
|
||||
const row = new ActionRowBuilder().addComponents(select);
|
||||
|
||||
await interaction.reply({
|
||||
content: 'Choose your starter!',
|
||||
components: [row],
|
||||
});
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
<Callout>Remember that if you have more than one select menu, each one will need its own action row.</Callout>
|
||||
|
||||
<Callout>
|
||||
You can also send action rows inside containers or between other components, when using the [display
|
||||
components](../popular-topics/display-components) system.
|
||||
</Callout>
|
||||
|
||||
### String select menu options
|
||||
|
||||
String select menu options are custom-defined by the user, as shown in the example above. At a minimum, each option must have it's `label` and `value` defined. The label is shown to the user, while the value is included in the interaction sent to the bot.
|
||||
|
||||
In addition to these, each option can be enhanced with a `description` or `emoji`, or can be set to be selected by default.
|
||||
|
||||
```js
|
||||
const select = new StringSelectMenuBuilder().setCustomId('select').addOptions(
|
||||
// [!code focus:6]
|
||||
new StringSelectMenuOptionBuilder()
|
||||
.setLabel('Option')
|
||||
.setValue('option')
|
||||
.setDescription('A selectable option') // [!code word:setDescription]
|
||||
.setEmoji('123456789012345678') // [!code word:setEmoji]
|
||||
.setDefault(true), // [!code word:setDefault]
|
||||
);
|
||||
```
|
||||
|
||||
## Auto-populating select menus
|
||||
|
||||
Although the String select menu with its user-defined options is the most customizable, there are four other types of select menu that auto-populate with their corresponding Discord entities, much like slash command options. These are:
|
||||
|
||||
- `UserSelectMenuBuilder`
|
||||
- `RoleSelectMenuBuilder`
|
||||
- `MentionableSelectMenuBuilder`
|
||||
- `ChannelSelectMenuBuilder`
|
||||
|
||||
The `ChannelSelectMenuBuilder` can be configured to only show specific channel types using `ChannelSelectMenuBuilder#setChannelTypes`.
|
||||
|
||||
## Multi-selects
|
||||
|
||||
Where slash command options fall behind is in their single-select limitation on User, Role and Channel option types. Select menus can support this use case via `BaseSelectMenuBuilder#setMinValues` and `BaseSelectMenuBuilder#setMaxValues`. When these values are set, users can select multiple items, and the interaction will be sent with all selected values only when the user clicks outside the select menu.
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
// data: new SlashCommandBuilder()...
|
||||
async execute(interaction) {
|
||||
// [!code focus:5]
|
||||
const userSelect = new UserSelectMenuBuilder()
|
||||
.setCustomId('users')
|
||||
.setPlaceholder('Select multiple users.')
|
||||
.setMinValues(1) // [!code word:setMinValues]
|
||||
.setMaxValues(10); // [!code word:setMaxValues]
|
||||
|
||||
const row1 = new ActionRowBuilder().addComponents(userSelect);
|
||||
|
||||
await interaction.reply({
|
||||
content: 'Select users:',
|
||||
components: [row1],
|
||||
});
|
||||
},
|
||||
};
|
||||
```
|
||||
@@ -1,194 +0,0 @@
|
||||
---
|
||||
title: Keyv
|
||||
---
|
||||
|
||||
[Keyv](https://www.npmjs.com/package/keyv) is a simple key-value store that works with multiple backends. It's fully scalable for [sharding](../sharding/) and supports JSON storage.
|
||||
|
||||
## Installation
|
||||
|
||||
```sh tab="npm"
|
||||
npm install keyv
|
||||
```
|
||||
|
||||
```sh tab="yarn"
|
||||
yarn add keyv
|
||||
```
|
||||
|
||||
```sh tab="pnpm"
|
||||
pnpm add keyv
|
||||
```
|
||||
|
||||
```sh tab="bun"
|
||||
bun add keyv
|
||||
```
|
||||
|
||||
Keyv requires an additional package depending on which persistent backend you would prefer to use. If you want to keep everything in memory, you can skip this part. Otherwise, install one of the below.
|
||||
|
||||
```sh tab="npm"
|
||||
npm install @keyv/redis
|
||||
npm install @keyv/mongo
|
||||
npm install @keyv/sqlite
|
||||
npm install @keyv/postgres
|
||||
npm install @keyv/mysql
|
||||
```
|
||||
|
||||
```sh tab="yarn"
|
||||
yarn add @keyv/redis
|
||||
yarn add @keyv/mongo
|
||||
yarn add @keyv/sqlite
|
||||
yarn add @keyv/postgres
|
||||
yarn add @keyv/mysql
|
||||
```
|
||||
|
||||
```sh tab="pnpm"
|
||||
pnpm add @keyv/redis
|
||||
pnpm add @keyv/mongo
|
||||
pnpm add @keyv/sqlite
|
||||
pnpm add @keyv/postgres
|
||||
pnpm add @keyv/mysql
|
||||
```
|
||||
|
||||
```sh tab="bun"
|
||||
bun add @keyv/redis
|
||||
bun add @keyv/mongo
|
||||
bun add @keyv/sqlite
|
||||
bun add @keyv/postgres
|
||||
bun add @keyv/mysql
|
||||
```
|
||||
|
||||
Create an instance of Keyv once you've installed Keyv and any necessary drivers.
|
||||
|
||||
```js
|
||||
const { Keyv } = require('keyv');
|
||||
|
||||
// One of the following
|
||||
const keyv = new Keyv(); // for in-memory storage
|
||||
const keyv = new Keyv('redis://user:pass@localhost:6379');
|
||||
const keyv = new Keyv('mongodb://user:pass@localhost:27017/dbname');
|
||||
const keyv = new Keyv('sqlite://path/to/database.sqlite');
|
||||
const keyv = new Keyv('postgresql://user:pass@localhost:5432/dbname');
|
||||
const keyv = new Keyv('mysql://user:pass@localhost:3306/dbname');
|
||||
```
|
||||
|
||||
Make sure to handle connection errors.
|
||||
|
||||
```js
|
||||
keyv.on('error', (err) => console.error('Keyv connection error:', err));
|
||||
```
|
||||
|
||||
For a more detailed setup, check out the [Keyv readme](https://github.com/jaredwray/keyv/tree/main/packages/keyv).
|
||||
|
||||
## Usage
|
||||
|
||||
Keyv exposes a familiar [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map)-like API. However, it only has `set`, `get`, `delete`, and `clear` methods. Additionally, instead of immediately returning data, these methods return [Promises](../additional-info/async-await) that resolve with the data.
|
||||
|
||||
```js
|
||||
(async () => {
|
||||
// true
|
||||
await keyv.set('foo', 'bar');
|
||||
|
||||
// bar
|
||||
await keyv.get('foo');
|
||||
|
||||
// undefined
|
||||
await keyv.clear();
|
||||
|
||||
// undefined
|
||||
await keyv.get('foo');
|
||||
})();
|
||||
```
|
||||
|
||||
## Application
|
||||
|
||||
Although Keyv can assist in any scenario where you need key-value data, we will focus on setting up a per-guild prefix configuration using [Sqlite](https://www.sqlite.org/).
|
||||
|
||||
<Callout>
|
||||
This section will still work with any provider supported by Keyv. We recommend PostgreSQL for larger applications.
|
||||
|
||||
Do note that "large" here should be interpreted as absolutely massive. Sqlite is used at scale by many companies and products you use every single day. The slight overhead should not be noticable for the application of a Discord bot at all unless you are dealing with super complicated queries or are using specific features that do not exist in sqlite.
|
||||
|
||||
You can find out if sqlite might be a good choice for your project (it very likely is) by reading [their own article](https://www.sqlite.org/whentouse.html) on the topic.
|
||||
|
||||
</Callout>
|
||||
|
||||
### Setup
|
||||
|
||||
```js
|
||||
const { Keyv } = require('keyv');
|
||||
const { Client, Events, GatewayIntentBits } = require('discord.js');
|
||||
const { globalPrefix, token } = require('./config.json');
|
||||
|
||||
const client = new Client({
|
||||
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent],
|
||||
});
|
||||
const prefixes = new Keyv('sqlite://path/to.sqlite');
|
||||
```
|
||||
|
||||
### Command handler
|
||||
|
||||
<Callout type="warn">
|
||||
This guide example is from a time where parsing the message content of Discord messages was the only option for
|
||||
commands. We have since moved on from this and recommend you do the same with [slash
|
||||
commands](../app-creation/handling-commands). It can still serve as an example for using databases in a more
|
||||
integrated example. We hope it can help you understand the core idea and apply it to your own use cases!
|
||||
</Callout>
|
||||
|
||||
```js
|
||||
client.on(Events.MessageCreate, async (message) => {
|
||||
if (message.author.bot) return;
|
||||
|
||||
let args;
|
||||
// handle messages in a guild
|
||||
if (message.guild) {
|
||||
let prefix;
|
||||
|
||||
if (message.content.startsWith(globalPrefix)) {
|
||||
prefix = globalPrefix;
|
||||
} else {
|
||||
// check the guild-level prefix
|
||||
const guildPrefix = await prefixes.get(message.guild.id);
|
||||
if (message.content.startsWith(guildPrefix)) prefix = guildPrefix;
|
||||
}
|
||||
|
||||
// if we found a prefix, setup args; otherwise, this isn't a command
|
||||
if (!prefix) return;
|
||||
args = message.content.slice(prefix.length).trim().split(/\s+/);
|
||||
} else {
|
||||
// handle DMs
|
||||
const slice = message.content.startsWith(globalPrefix) ? globalPrefix.length : 0;
|
||||
args = message.content.slice(slice).split(/\s+/);
|
||||
}
|
||||
|
||||
// get the first space-delimited argument after the prefix as the command
|
||||
const command = args.shift().toLowerCase();
|
||||
});
|
||||
```
|
||||
|
||||
### Prefix command
|
||||
|
||||
Now that you have a command handler, you can make a command to allow people to use your prefix system.
|
||||
|
||||
```js
|
||||
client.on(Events.MessageCreate, async (message) => {
|
||||
// ...
|
||||
if (command === 'prefix') {
|
||||
// if there's at least one argument, set the prefix
|
||||
if (args.length) {
|
||||
await prefixes.set(message.guild.id, args[0]);
|
||||
return message.channel.send(`Successfully set prefix to \`${args[0]}\``);
|
||||
}
|
||||
|
||||
return message.channel.send(`Prefix is \`${(await prefixes.get(message.guild.id)) || globalPrefix}\``);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
<Callout>
|
||||
You will probably want to set up additional validation, such as required permissions and maximum prefix length.
|
||||
</Callout>
|
||||
|
||||
## Next steps
|
||||
|
||||
Various other applications can use Keyv, such as guild settings; create another instance with a different [namespace](https://github.com/jaredwray/keyv/tree/main/packages/keyv#namespaces) for each setting. Additionally, it can be [extended](https://github.com/jaredwray/keyv/tree/main/packages/keyv#third-party-storage-adapters) to work with whatever storage backend you prefer.
|
||||
|
||||
Check out the [Keyv repository](https://github.com/jaredwray/keyv/tree/main/packages/keyv) for more information.
|
||||