├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ ├── feature_request.yml │ └── question.yml ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── test.yml ├── .gitignore ├── .vscode └── guide.code-snippets ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── code-samples ├── .eslintrc.js ├── additional-features │ ├── cooldowns │ │ ├── commands │ │ │ └── utility │ │ │ │ ├── ping.js │ │ │ │ ├── server.js │ │ │ │ └── user.js │ │ └── index.js │ └── reloading-commands │ │ ├── commands │ │ └── utility │ │ │ ├── ping.js │ │ │ ├── reload.js │ │ │ ├── server.js │ │ │ └── user.js │ │ └── index.js ├── additional-info │ └── rest-api │ │ └── 14 │ │ └── index.js ├── creating-your-bot │ ├── command-deployment │ │ ├── commands │ │ │ └── utility │ │ │ │ ├── ping.js │ │ │ │ ├── server.js │ │ │ │ └── user.js │ │ ├── config.json │ │ ├── deploy-commands.js │ │ └── index.js │ ├── command-handling │ │ ├── commands │ │ │ └── utility │ │ │ │ ├── ping.js │ │ │ │ ├── server.js │ │ │ │ └── user.js │ │ ├── config.json │ │ └── index.js │ ├── event-handling │ │ ├── commands │ │ │ └── utility │ │ │ │ ├── ping.js │ │ │ │ ├── server.js │ │ │ │ └── user.js │ │ ├── config.json │ │ ├── events │ │ │ ├── interactionCreate.js │ │ │ └── ready.js │ │ └── index.js │ ├── initial-files │ │ ├── config.json │ │ └── index.js │ └── slash-commands │ │ ├── commands │ │ └── utility │ │ │ ├── ping.js │ │ │ ├── server.js │ │ │ └── user.js │ │ ├── config.json │ │ └── index.js ├── keyv │ ├── config.json │ └── index.js ├── oauth │ └── simple-oauth-webserver │ │ ├── config.json │ │ ├── index.html │ │ └── index.js ├── popular-topics │ ├── canvas │ │ └── 14 │ │ │ ├── index.js │ │ │ └── wallpaper.jpg │ ├── permissions │ │ └── 14 │ │ │ └── index.js │ ├── reactions │ │ └── 14 │ │ │ ├── awaiting-reactions.js │ │ │ ├── basic-reacting.js │ │ │ └── uncached-messages.js │ └── webhooks │ │ └── 14 │ │ ├── using-Webhook.js │ │ └── using-WebhookClient.js ├── sequelize │ ├── currency │ │ └── 14 │ │ │ ├── app.js │ │ │ ├── dbInit.js │ │ │ ├── dbObjects.js │ │ │ └── models │ │ │ ├── CurrencyShop.js │ │ │ ├── UserItems.js │ │ │ └── Users.js │ └── tags │ │ └── sequelize.js └── sharding │ ├── extended │ └── 14 │ │ ├── bot.js │ │ └── index.js │ └── getting-started │ └── 14 │ ├── bot.js │ └── index.js ├── guide ├── .eslintrc.js ├── .vuepress │ ├── assets │ │ └── discord-avatar-djs.png │ ├── clientAppEnhance.ts │ ├── components │ │ ├── DocsLink.vue │ │ └── ResultingCode.vue │ ├── config.ts │ ├── public │ │ ├── favicon.png │ │ └── meta-image.png │ ├── sidebar.ts │ ├── styles │ │ ├── content.scss │ │ ├── custom-blocks.scss │ │ ├── home.scss │ │ ├── index.scss │ │ ├── layout.scss │ │ ├── mixins │ │ │ └── scrollbar.scss │ │ ├── navbar.scss │ │ ├── palette.scss │ │ ├── search.scss │ │ ├── sidebar.scss │ │ └── theme-variables.scss │ ├── templates │ │ ├── index.dev.html │ │ └── index.ssr.html │ └── theme │ │ ├── components │ │ ├── Navbar.vue │ │ ├── Notification.vue │ │ ├── Notifications.vue │ │ ├── Sidebar.vue │ │ ├── ThemeOptions.vue │ │ ├── ToggleDarkModeButton.vue │ │ ├── ToggleSidebarButton.vue │ │ ├── UserSettings.vue │ │ └── icons │ │ │ ├── Bars.vue │ │ │ ├── ChevronDown.vue │ │ │ ├── Close.vue │ │ │ └── PartyPopper.vue │ │ ├── composables │ │ ├── useColorTheme.ts │ │ └── useDarkMode.ts │ │ ├── index.ts │ │ └── layouts │ │ └── Layout.vue ├── README.md ├── additional-features │ ├── cooldowns.md │ └── reloading-commands.md ├── additional-info │ ├── async-await.md │ ├── changes-in-v13.md │ ├── changes-in-v14.md │ ├── collections.md │ ├── es6-syntax.md │ ├── images │ │ ├── search.png │ │ └── send.png │ ├── notation.md │ └── rest-api.md ├── creating-your-bot │ ├── README.md │ ├── command-deployment.md │ ├── command-handling.md │ ├── event-handling.md │ ├── main-file.md │ └── slash-commands.md ├── images │ └── branding │ │ ├── banner-blurple-small.png │ │ ├── banner-blurple.png │ │ ├── banner-small.png │ │ ├── banner.png │ │ ├── logo-blurple-favicon.png │ │ ├── logo-blurple-small.png │ │ ├── logo-blurple.png │ │ ├── logo-favicon.png │ │ ├── logo-small.png │ │ └── logo.png ├── improving-dev-environment │ ├── package-json-scripts.md │ └── pm2.md ├── interactions │ ├── context-menus.md │ ├── images │ │ ├── modal-example.png │ │ └── selectephem.png │ └── modals.md ├── keyv │ └── README.md ├── message-components │ ├── action-rows.md │ ├── buttons.md │ ├── images │ │ └── select.png │ ├── interactions.md │ └── select-menus.md ├── miscellaneous │ ├── cache-customization.md │ ├── images │ │ ├── chalk-red.png │ │ ├── chalk-ugly.png │ │ └── winston.png │ └── useful-packages.md ├── oauth2 │ ├── README.md │ └── images │ │ ├── add-redirects.png │ │ ├── authorize-app-page.png │ │ ├── generate-url.png │ │ └── oauth2-app-page.png ├── popular-topics │ ├── audit-logs.md │ ├── canvas.md │ ├── collectors.md │ ├── embeds.md │ ├── errors.md │ ├── faq.md │ ├── formatters.md │ ├── images │ │ ├── canvas-add-name.png │ │ ├── canvas-after-text-wrap.png │ │ ├── canvas-before-text-wrap.png │ │ ├── canvas-circle-avatar.png │ │ ├── canvas-final-result.png │ │ ├── canvas-plain.png │ │ ├── canvas-preview.png │ │ ├── canvas-square-avatar.png │ │ ├── canvas-stretched-avatar.png │ │ ├── canvas.jpg │ │ ├── creating-webhooks-1.png │ │ ├── creating-webhooks-2.png │ │ ├── creating-webhooks-3.png │ │ └── wallpaper.jpg │ ├── intents.md │ ├── partials.md │ ├── permissions-extended.md │ ├── permissions.md │ ├── reactions.md │ ├── threads.md │ └── webhooks.md ├── preparations │ ├── README.md │ ├── adding-your-bot-to-servers.md │ ├── images │ │ ├── bot-auth-page.png │ │ ├── bot-authorized.png │ │ ├── bot-in-memberlist.png │ │ ├── create-app.png │ │ └── created-bot.png │ ├── setting-up-a-bot-application.md │ └── setting-up-a-linter.md ├── requesting-more-content.md ├── sequelize │ ├── README.md │ ├── currency.md │ └── images │ │ └── currency_er_diagram.svg ├── sharding │ ├── README.md │ ├── additional-information.md │ └── extended.md ├── slash-commands │ ├── advanced-creation.md │ ├── autocomplete.md │ ├── deleting-commands.md │ ├── images │ │ ├── bots-and-apps.png │ │ └── commands-copy-id.png │ ├── parsing-options.md │ ├── permissions.md │ └── response-methods.md ├── voice │ ├── README.md │ ├── audio-player.md │ ├── audio-resources.md │ ├── life-cycles.md │ └── voice-connections.md └── whats-new.md ├── netlify.toml ├── package-lock.json └── package.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.{css,js,jsx,md,scss,ts,tsx,vue}] 4 | end_of_line = lf 5 | indent_size = 4 6 | indent_style = tab 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | [*.md] 11 | trim_trailing_whitespace = false 12 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | !/guide/.vuepress 2 | guide/.vuepress/dist 3 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | module.exports = { 3 | 'extends': 'sora/vue-3', 4 | plugins: ['markdown'], 5 | rules: { 6 | indent: ['error', 'tab'], 7 | semi: ['error', 'always'], 8 | 'no-trailing-spaces': 'error', 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: Create a report to help us improve 3 | labels: ['type: bug'] 4 | 5 | body: 6 | - type: textarea 7 | id: description 8 | attributes: 9 | label: Describe the bug 10 | description: A clear and concise description of what the bug is. 11 | validations: 12 | required: true 13 | 14 | - type: textarea 15 | id: steps_to_reproduce 16 | attributes: 17 | label: To Reproduce 18 | description: | 19 | 1. Go to '...' 20 | 2. Click on '....' 21 | 3. Scroll down to '....' 22 | 4. See error 23 | validations: 24 | required: true 25 | 26 | - type: textarea 27 | id: screenshots 28 | attributes: 29 | label: Screenshots 30 | description: If applicable, add screenshots to help explain your problem. 31 | 32 | - type: textarea 33 | id: device 34 | attributes: 35 | label: Device (please complete the following information) 36 | description: | 37 | - Device/OS: [e.g. iOS] 38 | - Browser: [e.g. chrome, safari] 39 | - Version: [e.g. 22] 40 | validations: 41 | required: true 42 | 43 | - type: textarea 44 | attributes: 45 | label: Additional notes 46 | description: Add any other notes about the problem here. 47 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Discord server 4 | url: https://discord.gg/djs 5 | about: Visit our Discord server for questions and support requests. 6 | - name: Guide discussion boards 7 | url: https://github.com/discordjs/discord.js/discussions 8 | about: Ask questions and receive answers. 9 | - name: discord.js issues 10 | url: https://github.com/discordjs/discord.js/issues/new/choose 11 | about: Issue tracker for the library discord.js. 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Suggest an idea for this project 3 | labels: ['t: request'] 4 | 5 | body: 6 | - type: textarea 7 | id: description 8 | attributes: 9 | label: Is your feature request related to a problem? Please describe. 10 | description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 11 | validations: 12 | required: true 13 | 14 | - type: textarea 15 | id: solution 16 | attributes: 17 | label: Describe the solution you'd like 18 | description: A clear and concise description of what you want to be addressed in the guide. 19 | validations: 20 | required: true 21 | 22 | - type: textarea 23 | id: alternatives 24 | attributes: 25 | label: Describe alternatives you've considered 26 | description: A clear and concise description of any alternative solutions or features you've considered. 27 | 28 | - type: textarea 29 | id: additional_notes 30 | attributes: 31 | label: Additional notes 32 | description: Add any other notes about the problem here. 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.yml: -------------------------------------------------------------------------------- 1 | name: Question/General support request 2 | description: Ask questions about the guide. 3 | labels: ['question'] 4 | 5 | body: 6 | - type: textarea 7 | id: scope 8 | attributes: 9 | label: Part of the guide or code sample the question is about 10 | description: URL or path to the file or section in question. If none please select "feature request" instead. 11 | validations: 12 | required: true 13 | 14 | - type: textarea 15 | id: question 16 | attributes: 17 | label: Question 18 | description: Your question about the referenced part of the guide. 19 | validations: 20 | required: true 21 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **Please describe the changes this PR makes and why it should be merged:** 2 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Testing 2 | on: [push, pull_request] 3 | jobs: 4 | lint: 5 | name: ESLint 6 | runs-on: ubuntu-latest 7 | steps: 8 | - name: Checkout repository 9 | uses: actions/checkout@v2 10 | 11 | - name: Install Node v16 12 | uses: actions/setup-node@v2 13 | with: 14 | node-version: 16 15 | 16 | - name: Install dependencies 17 | run: npm ci 18 | 19 | - name: Run ESLint 20 | run: npm run lint 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | guide/.vuepress/.cache 3 | guide/.vuepress/.temp 4 | guide/.vuepress/dist 5 | .vscode/settings.json 6 | .DS_Store 7 | -------------------------------------------------------------------------------- /.vscode/guide.code-snippets: -------------------------------------------------------------------------------- 1 | // https://code.visualstudio.com/docs/editor/userdefinedsnippets#_project-snippet-scope 2 | // custom element explanations: https://github.com/discordjs/guide/blob/main/CONTRIBUTING.md 3 | // mocking discord messages: https://github.com/Danktuary/vue-discord-message 4 | 5 | { 6 | "tip": { 7 | "prefix": "tip", 8 | "description": "insert a tip element", 9 | "body": [ 10 | "::: tip", 11 | "$1", 12 | ":::" 13 | ] 14 | }, 15 | "warning": { 16 | "prefix": "warning", 17 | "description": "insert a warning element", 18 | "body": [ 19 | "::: warning", 20 | "$1", 21 | ":::" 22 | ] 23 | }, 24 | "danger": { 25 | "prefix": "danger", 26 | "description": "insert a danger element", 27 | "body": [ 28 | "::: danger", 29 | "$1", 30 | ":::" 31 | ] 32 | }, 33 | "image": { 34 | "prefix": ["image", "img"], 35 | "description": "image tag", 36 | "body": "![${1:some alt text}](~@${2:/images/AfFp7pu.png})" 37 | }, 38 | "messages": { 39 | "prefix": ["messages", "discord-messages"], 40 | "description": "mock discord message wrapper", 41 | "body": [ 42 | "", 43 | "\t$1", 44 | "" 45 | ] 46 | }, 47 | "message": { 48 | "prefix": ["message", "discord-message"], 49 | "description": "mock discord message", 50 | "body": [ 51 | "", 52 | "\t${3:You can mock mentions with someone and mark the message as highlighted (mentioning the logged in user) with someone}$0", 53 | "" 54 | ] 55 | }, 56 | "docs": { 57 | "prefix": ["docs", "djs-docs"], 58 | "description": "version sensitive link to the discord.js documentation", 59 | "body": [ 60 | "" 61 | ] 62 | }, 63 | "docs-collection": { 64 | "prefix": ["docs-collection", "coll-docs"], 65 | "description": "link to the discord.js collections", 66 | "body": [ 67 | "" 68 | ] 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at sanc.pw@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 - 2022 Sanctuary 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | discord.js Guide 3 |
4 | 5 | # Discord.js Guide 6 | 7 | Imagine a guide... that explores the many possibilities for your [discord.js](https://github.com/discordjs/discord.js) bot. 8 | 9 | ## About 10 | 11 | This guide is aimed at users who are either unfamiliar or inexperienced with Node.js and creating Discord bots. It assumes you have a basic understanding of JavaScript. 12 | 13 | There are many different subjects covered, such as: 14 | 15 | - How to get a bot [up and running](https://discordjs.guide/preparations/) from scratch; 16 | - How to properly [create](https://discordjs.guide/creating-your-bot/), [organize](https://discordjs.guide/creating-your-bot/command-handling.html), and expand on your commands; 17 | - In-depth explanations and examples regarding popular topics (e.g. [reactions](https://discordjs.guide/popular-topics/reactions.html), [embeds](https://discordjs.guide/popular-topics/embeds.html), [canvas](https://discordjs.guide/popular-topics/canvas.html)); 18 | - Working with databases (e.g. [sequelize](https://discordjs.guide/sequelize/) and [keyv](https://discordjs.guide/keyv/)); 19 | - Getting started with [sharding](https://discordjs.guide/sharding/); 20 | - And much more. 21 | 22 | ## Contributing 23 | 24 | If you're interested in contributing to the guide, you should check out our [GitHub Projects](https://github.com/discordjs/guide/projects) page or [open issues](https://github.com/discordjs/guide/issues). There's a [contribution guide](https://github.com/discordjs/guide/blob/main/CONTRIBUTING.md) you should read once you decide on what you want to contribute. 25 | -------------------------------------------------------------------------------- /code-samples/.eslintrc.js: -------------------------------------------------------------------------------- 1 | const path = require('node:path'); 2 | 3 | module.exports = { 4 | 'extends': path.join(__dirname, '..', '.eslintrc.js'), 5 | env: { 6 | node: true, 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /code-samples/additional-features/cooldowns/commands/utility/ping.js: -------------------------------------------------------------------------------- 1 | const { SlashCommandBuilder } = require('discord.js'); 2 | 3 | module.exports = { 4 | cooldown: 5, 5 | data: new SlashCommandBuilder() 6 | .setName('ping') 7 | .setDescription('Replies with Pong!'), 8 | async execute(interaction) { 9 | await interaction.reply('Pong!'); 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /code-samples/additional-features/cooldowns/commands/utility/server.js: -------------------------------------------------------------------------------- 1 | const { SlashCommandBuilder } = require('discord.js'); 2 | 3 | module.exports = { 4 | cooldown: 5, 5 | data: new SlashCommandBuilder() 6 | .setName('server') 7 | .setDescription('Provides information about the server.'), 8 | async execute(interaction) { 9 | await interaction.reply(`This server is ${interaction.guild.name} and has ${interaction.guild.memberCount} members.`); 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /code-samples/additional-features/cooldowns/commands/utility/user.js: -------------------------------------------------------------------------------- 1 | const { SlashCommandBuilder } = require('discord.js'); 2 | 3 | module.exports = { 4 | cooldown: 5, 5 | data: new SlashCommandBuilder() 6 | .setName('user') 7 | .setDescription('Provides information about the user.'), 8 | async execute(interaction) { 9 | await interaction.reply(`This command was run by ${interaction.user.username}, who joined on ${interaction.member.joinedAt}.`); 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /code-samples/additional-features/cooldowns/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('node:fs'); 2 | const path = require('node:path'); 3 | const { Client, Collection, Events, GatewayIntentBits, MessageFlags } = require('discord.js'); 4 | const { token } = require('./config.json'); 5 | 6 | const client = new Client({ intents: [GatewayIntentBits.Guilds] }); 7 | 8 | client.cooldowns = new Collection(); 9 | client.commands = new Collection(); 10 | const foldersPath = path.join(__dirname, 'commands'); 11 | const commandFolders = fs.readdirSync(foldersPath); 12 | 13 | for (const folder of commandFolders) { 14 | const commandsPath = path.join(foldersPath, folder); 15 | const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js')); 16 | for (const file of commandFiles) { 17 | const filePath = path.join(commandsPath, file); 18 | const command = require(filePath); 19 | if ('data' in command && 'execute' in command) { 20 | client.commands.set(command.data.name, command); 21 | } else { 22 | console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`); 23 | } 24 | } 25 | } 26 | 27 | client.once(Events.ClientReady, readyClient => { 28 | console.log(`Ready! Logged in as ${readyClient.user.tag}`); 29 | }); 30 | 31 | client.on(Events.InteractionCreate, async interaction => { 32 | if (!interaction.isChatInputCommand()) return; 33 | const command = client.commands.get(interaction.commandName); 34 | 35 | if (!command) { 36 | console.error(`No command matching ${interaction.commandName} was found.`); 37 | return; 38 | } 39 | 40 | const { cooldowns } = interaction.client; 41 | 42 | if (!cooldowns.has(command.data.name)) { 43 | cooldowns.set(command.data.name, new Collection()); 44 | } 45 | 46 | const now = Date.now(); 47 | const timestamps = cooldowns.get(command.data.name); 48 | const defaultCooldownDuration = 3; 49 | const cooldownAmount = (command.cooldown ?? defaultCooldownDuration) * 1000; 50 | 51 | if (timestamps.has(interaction.user.id)) { 52 | const expirationTime = timestamps.get(interaction.user.id) + cooldownAmount; 53 | 54 | if (now < expirationTime) { 55 | const expiredTimestamp = Math.round(expirationTime / 1000); 56 | return interaction.reply({ content: `Please wait, you are on a cooldown for \`${command.data.name}\`. You can use it again .`, flags: MessageFlags.Ephemeral }); 57 | } 58 | } 59 | 60 | timestamps.set(interaction.user.id, now); 61 | setTimeout(() => timestamps.delete(interaction.user.id), cooldownAmount); 62 | 63 | try { 64 | await command.execute(interaction); 65 | } catch (error) { 66 | console.error(error); 67 | if (interaction.replied || interaction.deferred) { 68 | await interaction.followUp({ content: 'There was an error while executing this command!', flags: MessageFlags.Ephemeral }); 69 | } else { 70 | await interaction.reply({ content: 'There was an error while executing this command!', flags: MessageFlags.Ephemeral }); 71 | } 72 | } 73 | }); 74 | 75 | client.login(token); 76 | -------------------------------------------------------------------------------- /code-samples/additional-features/reloading-commands/commands/utility/ping.js: -------------------------------------------------------------------------------- 1 | const { SlashCommandBuilder } = require('discord.js'); 2 | 3 | module.exports = { 4 | category: 'utility', 5 | data: new SlashCommandBuilder() 6 | .setName('ping') 7 | .setDescription('Replies with Pong!'), 8 | async execute(interaction) { 9 | await interaction.reply('Pong!'); 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /code-samples/additional-features/reloading-commands/commands/utility/reload.js: -------------------------------------------------------------------------------- 1 | const { SlashCommandBuilder } = require('discord.js'); 2 | 3 | module.exports = { 4 | category: 'utility', 5 | data: new SlashCommandBuilder() 6 | .setName('reload') 7 | .setDescription('Reloads a command.') 8 | .addStringOption(option => 9 | option.setName('command') 10 | .setDescription('The command to reload.') 11 | .setRequired(true)), 12 | async execute(interaction) { 13 | const commandName = interaction.options.getString('command', true).toLowerCase(); 14 | const command = interaction.client.commands.get(commandName); 15 | 16 | if (!command) { 17 | return interaction.reply(`There is no command with name \`${commandName}\`!`); 18 | } 19 | 20 | delete require.cache[require.resolve(`../${command.category}/${command.data.name}.js`)]; 21 | 22 | try { 23 | const newCommand = require(`../${command.category}/${command.data.name}.js`); 24 | interaction.client.commands.set(newCommand.data.name, newCommand); 25 | await interaction.reply(`Command \`${newCommand.data.name}\` was reloaded!`); 26 | } catch (error) { 27 | console.error(error); 28 | await interaction.reply(`There was an error while reloading a command \`${command.data.name}\`:\n\`${error.message}\``); 29 | } 30 | }, 31 | }; 32 | -------------------------------------------------------------------------------- /code-samples/additional-features/reloading-commands/commands/utility/server.js: -------------------------------------------------------------------------------- 1 | const { SlashCommandBuilder } = require('discord.js'); 2 | 3 | module.exports = { 4 | category: 'utility', 5 | data: new SlashCommandBuilder() 6 | .setName('server') 7 | .setDescription('Provides information about the server.'), 8 | async execute(interaction) { 9 | await interaction.reply(`This server is ${interaction.guild.name} and has ${interaction.guild.memberCount} members.`); 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /code-samples/additional-features/reloading-commands/commands/utility/user.js: -------------------------------------------------------------------------------- 1 | const { SlashCommandBuilder } = require('discord.js'); 2 | 3 | module.exports = { 4 | category: 'utility', 5 | data: new SlashCommandBuilder() 6 | .setName('user') 7 | .setDescription('Provides information about the user.'), 8 | async execute(interaction) { 9 | await interaction.reply(`This command was run by ${interaction.user.username}, who joined on ${interaction.member.joinedAt}.`); 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /code-samples/additional-features/reloading-commands/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('node:fs'); 2 | const path = require('node:path'); 3 | const { Client, Collection, Events, GatewayIntentBits, MessageFlags } = require('discord.js'); 4 | const { token } = require('./config.json'); 5 | 6 | const client = new Client({ intents: [GatewayIntentBits.Guilds] }); 7 | 8 | client.cooldowns = new Collection(); 9 | client.commands = new Collection(); 10 | const foldersPath = path.join(__dirname, 'commands'); 11 | const commandFolders = fs.readdirSync(foldersPath); 12 | 13 | for (const folder of commandFolders) { 14 | const commandsPath = path.join(foldersPath, folder); 15 | const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js')); 16 | for (const file of commandFiles) { 17 | const filePath = path.join(commandsPath, file); 18 | const command = require(filePath); 19 | if ('data' in command && 'execute' in command) { 20 | client.commands.set(command.data.name, command); 21 | } else { 22 | console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`); 23 | } 24 | } 25 | } 26 | 27 | client.once(Events.ClientReady, c => { 28 | console.log(`Ready! Logged in as ${c.user.tag}`); 29 | }); 30 | 31 | client.on(Events.InteractionCreate, async interaction => { 32 | if (!interaction.isChatInputCommand()) return; 33 | const command = client.commands.get(interaction.commandName); 34 | 35 | if (!command) { 36 | console.error(`No command matching ${interaction.commandName} was found.`); 37 | return; 38 | } 39 | 40 | const { cooldowns } = interaction.client; 41 | 42 | if (!cooldowns.has(command.data.name)) { 43 | cooldowns.set(command.data.name, new Collection()); 44 | } 45 | 46 | const now = Date.now(); 47 | const timestamps = cooldowns.get(command.data.name); 48 | const defaultCooldownDuration = 3; 49 | const cooldownAmount = (command.cooldown ?? defaultCooldownDuration) * 1000; 50 | 51 | if (timestamps.has(interaction.user.id)) { 52 | const expirationTime = timestamps.get(interaction.user.id) + cooldownAmount; 53 | 54 | if (now < expirationTime) { 55 | const expiredTimestamp = Math.round(expirationTime / 1000); 56 | return interaction.reply({ content: `Please wait, you are on a cooldown for \`${command.data.name}\`. You can use it again .`, flags: MessageFlags.Ephemeral }); 57 | } 58 | } 59 | 60 | timestamps.set(interaction.user.id, now); 61 | setTimeout(() => timestamps.delete(interaction.user.id), cooldownAmount); 62 | 63 | try { 64 | await command.execute(interaction); 65 | } catch (error) { 66 | console.error(error); 67 | if (interaction.replied || interaction.deferred) { 68 | await interaction.followUp({ content: 'There was an error while executing this command!', flags: MessageFlags.Ephemeral }); 69 | } else { 70 | await interaction.reply({ content: 'There was an error while executing this command!', flags: MessageFlags.Ephemeral }); 71 | } 72 | } 73 | }); 74 | 75 | client.login(token); 76 | -------------------------------------------------------------------------------- /code-samples/additional-info/rest-api/14/index.js: -------------------------------------------------------------------------------- 1 | const { Client, EmbedBuilder, Events, GatewayIntentBits } = require('discord.js'); 2 | const { request } = require('undici'); 3 | 4 | const client = new Client({ intents: [GatewayIntentBits.Guilds] }); 5 | 6 | const trim = (str, max) => (str.length > max ? `${str.slice(0, max - 3)}...` : str); 7 | 8 | client.once(Events.ClientReady, readyClient => { 9 | console.log(`Ready! Logged in as ${readyClient.user.tag}`); 10 | }); 11 | 12 | client.on(Events.InteractionCreate, async interaction => { 13 | if (!interaction.isChatInputCommand()) return; 14 | 15 | const { commandName } = interaction; 16 | await interaction.deferReply(); 17 | 18 | if (commandName === 'cat') { 19 | const catResult = await request('https://aws.random.cat/meow'); 20 | const { file } = await catResult.body.json(); 21 | interaction.editReply({ files: [{ attachment: file, name: 'cat.png' }] }); 22 | } else if (commandName === 'urban') { 23 | const term = interaction.options.getString('term'); 24 | const query = new URLSearchParams({ term }); 25 | 26 | const dictResult = await request(`https://api.urbandictionary.com/v0/define?${query}`); 27 | const { list } = await dictResult.body.json(); 28 | 29 | if (!list.length) { 30 | return interaction.editReply(`No results found for **${term}**.`); 31 | } 32 | 33 | const [answer] = list; 34 | 35 | const embed = new EmbedBuilder() 36 | .setColor(0xEFFF00) 37 | .setTitle(answer.word) 38 | .setURL(answer.permalink) 39 | .addFields( 40 | { name: 'Definition', value: trim(answer.definition, 1024) }, 41 | { name: 'Example', value: trim(answer.example, 1024) }, 42 | { 43 | name: 'Rating', 44 | value: `${answer.thumbs_up} thumbs up. ${answer.thumbs_down} thumbs down.`, 45 | }, 46 | ); 47 | interaction.editReply({ embeds: [embed] }); 48 | } 49 | }); 50 | 51 | client.login('your-token-goes-here'); 52 | -------------------------------------------------------------------------------- /code-samples/creating-your-bot/command-deployment/commands/utility/ping.js: -------------------------------------------------------------------------------- 1 | const { SlashCommandBuilder } = require('discord.js'); 2 | 3 | module.exports = { 4 | data: new SlashCommandBuilder() 5 | .setName('ping') 6 | .setDescription('Replies with Pong!'), 7 | async execute(interaction) { 8 | await interaction.reply('Pong!'); 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /code-samples/creating-your-bot/command-deployment/commands/utility/server.js: -------------------------------------------------------------------------------- 1 | const { SlashCommandBuilder } = require('discord.js'); 2 | 3 | module.exports = { 4 | data: new SlashCommandBuilder() 5 | .setName('server') 6 | .setDescription('Provides information about the server.'), 7 | async execute(interaction) { 8 | await interaction.reply(`This server is ${interaction.guild.name} and has ${interaction.guild.memberCount} members.`); 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /code-samples/creating-your-bot/command-deployment/commands/utility/user.js: -------------------------------------------------------------------------------- 1 | const { SlashCommandBuilder } = require('discord.js'); 2 | 3 | module.exports = { 4 | data: new SlashCommandBuilder() 5 | .setName('user') 6 | .setDescription('Provides information about the user.'), 7 | async execute(interaction) { 8 | await interaction.reply(`This command was run by ${interaction.user.username}, who joined on ${interaction.member.joinedAt}.`); 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /code-samples/creating-your-bot/command-deployment/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "clientId": "123456789012345678", 3 | "guildId": "876543210987654321", 4 | "token": "your-token-goes-here" 5 | } 6 | -------------------------------------------------------------------------------- /code-samples/creating-your-bot/command-deployment/deploy-commands.js: -------------------------------------------------------------------------------- 1 | const { REST, Routes } = require('discord.js'); 2 | const { clientId, guildId, token } = require('./config.json'); 3 | const fs = require('node:fs'); 4 | const path = require('node:path'); 5 | 6 | const commands = []; 7 | // Grab all the command folders from the commands directory you created earlier 8 | const foldersPath = path.join(__dirname, 'commands'); 9 | const commandFolders = fs.readdirSync(foldersPath); 10 | 11 | for (const folder of commandFolders) { 12 | // Grab all the command files from the commands directory you created earlier 13 | const commandsPath = path.join(foldersPath, folder); 14 | const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js')); 15 | // Grab the SlashCommandBuilder#toJSON() output of each command's data for deployment 16 | for (const file of commandFiles) { 17 | const filePath = path.join(commandsPath, file); 18 | const command = require(filePath); 19 | if ('data' in command && 'execute' in command) { 20 | commands.push(command.data.toJSON()); 21 | } else { 22 | console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`); 23 | } 24 | } 25 | } 26 | 27 | // Construct and prepare an instance of the REST module 28 | const rest = new REST().setToken(token); 29 | 30 | // and deploy your commands! 31 | (async () => { 32 | try { 33 | console.log(`Started refreshing ${commands.length} application (/) commands.`); 34 | 35 | // The put method is used to fully refresh all commands in the guild with the current set 36 | const data = await rest.put( 37 | Routes.applicationGuildCommands(clientId, guildId), 38 | { body: commands }, 39 | ); 40 | 41 | console.log(`Successfully reloaded ${data.length} application (/) commands.`); 42 | } catch (error) { 43 | // And of course, make sure you catch and log any errors! 44 | console.error(error); 45 | } 46 | })(); 47 | -------------------------------------------------------------------------------- /code-samples/creating-your-bot/command-deployment/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('node:fs'); 2 | const path = require('node:path'); 3 | const { Client, Collection, Events, GatewayIntentBits, MessageFlags } = require('discord.js'); 4 | const { token } = require('./config.json'); 5 | 6 | const client = new Client({ intents: [GatewayIntentBits.Guilds] }); 7 | 8 | client.commands = new Collection(); 9 | const foldersPath = path.join(__dirname, 'commands'); 10 | const commandFolders = fs.readdirSync(foldersPath); 11 | 12 | for (const folder of commandFolders) { 13 | const commandsPath = path.join(foldersPath, folder); 14 | const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js')); 15 | for (const file of commandFiles) { 16 | const filePath = path.join(commandsPath, file); 17 | const command = require(filePath); 18 | if ('data' in command && 'execute' in command) { 19 | client.commands.set(command.data.name, command); 20 | } else { 21 | console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`); 22 | } 23 | } 24 | } 25 | 26 | client.once(Events.ClientReady, readyClient => { 27 | console.log(`Ready! Logged in as ${readyClient.user.tag}`); 28 | }); 29 | 30 | client.on(Events.InteractionCreate, async interaction => { 31 | if (!interaction.isChatInputCommand()) return; 32 | const command = interaction.client.commands.get(interaction.commandName); 33 | 34 | if (!command) { 35 | console.error(`No command matching ${interaction.commandName} was found.`); 36 | return; 37 | } 38 | 39 | try { 40 | await command.execute(interaction); 41 | } catch (error) { 42 | console.error(error); 43 | if (interaction.replied || interaction.deferred) { 44 | await interaction.followUp({ content: 'There was an error while executing this command!', flags: MessageFlags.Ephemeral }); 45 | } else { 46 | await interaction.reply({ content: 'There was an error while executing this command!', flags: MessageFlags.Ephemeral }); 47 | } 48 | } 49 | }); 50 | 51 | client.login(token); 52 | -------------------------------------------------------------------------------- /code-samples/creating-your-bot/command-handling/commands/utility/ping.js: -------------------------------------------------------------------------------- 1 | const { SlashCommandBuilder } = require('discord.js'); 2 | 3 | module.exports = { 4 | data: new SlashCommandBuilder() 5 | .setName('ping') 6 | .setDescription('Replies with Pong!'), 7 | async execute(interaction) { 8 | await interaction.reply('Pong!'); 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /code-samples/creating-your-bot/command-handling/commands/utility/server.js: -------------------------------------------------------------------------------- 1 | const { SlashCommandBuilder } = require('discord.js'); 2 | 3 | module.exports = { 4 | data: new SlashCommandBuilder() 5 | .setName('server') 6 | .setDescription('Provides information about the server.'), 7 | async execute(interaction) { 8 | await interaction.reply(`This server is ${interaction.guild.name} and has ${interaction.guild.memberCount} members.`); 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /code-samples/creating-your-bot/command-handling/commands/utility/user.js: -------------------------------------------------------------------------------- 1 | const { SlashCommandBuilder } = require('discord.js'); 2 | 3 | module.exports = { 4 | data: new SlashCommandBuilder() 5 | .setName('user') 6 | .setDescription('Provides information about the user.'), 7 | async execute(interaction) { 8 | await interaction.reply(`This command was run by ${interaction.user.username}, who joined on ${interaction.member.joinedAt}.`); 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /code-samples/creating-your-bot/command-handling/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "token": "your-token-goes-here" 3 | } 4 | -------------------------------------------------------------------------------- /code-samples/creating-your-bot/command-handling/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('node:fs'); 2 | const path = require('node:path'); 3 | const { Client, Collection, Events, GatewayIntentBits, MessageFlags } = require('discord.js'); 4 | const { token } = require('./config.json'); 5 | 6 | const client = new Client({ intents: [GatewayIntentBits.Guilds] }); 7 | 8 | client.commands = new Collection(); 9 | const foldersPath = path.join(__dirname, 'commands'); 10 | const commandFolders = fs.readdirSync(foldersPath); 11 | 12 | for (const folder of commandFolders) { 13 | const commandsPath = path.join(foldersPath, folder); 14 | const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js')); 15 | for (const file of commandFiles) { 16 | const filePath = path.join(commandsPath, file); 17 | const command = require(filePath); 18 | if ('data' in command && 'execute' in command) { 19 | client.commands.set(command.data.name, command); 20 | } else { 21 | console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`); 22 | } 23 | } 24 | } 25 | 26 | client.once(Events.ClientReady, readyClient => { 27 | console.log(`Ready! Logged in as ${readyClient.user.tag}`); 28 | }); 29 | 30 | client.on(Events.InteractionCreate, async interaction => { 31 | if (!interaction.isChatInputCommand()) return; 32 | const command = interaction.client.commands.get(interaction.commandName); 33 | 34 | if (!command) { 35 | console.error(`No command matching ${interaction.commandName} was found.`); 36 | return; 37 | } 38 | 39 | try { 40 | await command.execute(interaction); 41 | } catch (error) { 42 | console.error(error); 43 | if (interaction.replied || interaction.deferred) { 44 | await interaction.followUp({ content: 'There was an error while executing this command!', flags: MessageFlags.Ephemeral }); 45 | } else { 46 | await interaction.reply({ content: 'There was an error while executing this command!', flags: MessageFlags.Ephemeral }); 47 | } 48 | } 49 | }); 50 | 51 | client.login(token); 52 | -------------------------------------------------------------------------------- /code-samples/creating-your-bot/event-handling/commands/utility/ping.js: -------------------------------------------------------------------------------- 1 | const { SlashCommandBuilder } = require('discord.js'); 2 | 3 | module.exports = { 4 | data: new SlashCommandBuilder() 5 | .setName('ping') 6 | .setDescription('Replies with Pong!'), 7 | async execute(interaction) { 8 | await interaction.reply('Pong!'); 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /code-samples/creating-your-bot/event-handling/commands/utility/server.js: -------------------------------------------------------------------------------- 1 | const { SlashCommandBuilder } = require('discord.js'); 2 | 3 | module.exports = { 4 | data: new SlashCommandBuilder() 5 | .setName('server') 6 | .setDescription('Provides information about the server.'), 7 | async execute(interaction) { 8 | await interaction.reply(`This server is ${interaction.guild.name} and has ${interaction.guild.memberCount} members.`); 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /code-samples/creating-your-bot/event-handling/commands/utility/user.js: -------------------------------------------------------------------------------- 1 | const { SlashCommandBuilder } = require('discord.js'); 2 | 3 | module.exports = { 4 | data: new SlashCommandBuilder() 5 | .setName('user') 6 | .setDescription('Provides information about the user.'), 7 | async execute(interaction) { 8 | await interaction.reply(`This command was run by ${interaction.user.username}, who joined on ${interaction.member.joinedAt}.`); 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /code-samples/creating-your-bot/event-handling/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "clientId": "123456789012345678", 3 | "guildId": "876543210987654321", 4 | "token": "your-token-goes-here" 5 | } 6 | -------------------------------------------------------------------------------- /code-samples/creating-your-bot/event-handling/events/interactionCreate.js: -------------------------------------------------------------------------------- 1 | const { Events, MessageFlags } = require('discord.js'); 2 | 3 | module.exports = { 4 | name: Events.InteractionCreate, 5 | async execute(interaction) { 6 | if (!interaction.isChatInputCommand()) return; 7 | 8 | const command = interaction.client.commands.get(interaction.commandName); 9 | 10 | if (!command) { 11 | console.error(`No command matching ${interaction.commandName} was found.`); 12 | return; 13 | } 14 | 15 | try { 16 | await command.execute(interaction); 17 | } catch (error) { 18 | console.error(error); 19 | if (interaction.replied || interaction.deferred) { 20 | await interaction.followUp({ content: 'There was an error while executing this command!', flags: MessageFlags.Ephemeral }); 21 | } else { 22 | await interaction.reply({ content: 'There was an error while executing this command!', flags: MessageFlags.Ephemeral }); 23 | } 24 | } 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /code-samples/creating-your-bot/event-handling/events/ready.js: -------------------------------------------------------------------------------- 1 | const { Events } = require('discord.js'); 2 | 3 | module.exports = { 4 | name: Events.ClientReady, 5 | once: true, 6 | execute(client) { 7 | console.log(`Ready! Logged in as ${client.user.tag}`); 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /code-samples/creating-your-bot/event-handling/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('node:fs'); 2 | const path = require('node:path'); 3 | const { Client, Collection, GatewayIntentBits } = require('discord.js'); 4 | const { token } = require('./config.json'); 5 | 6 | const client = new Client({ intents: [GatewayIntentBits.Guilds] }); 7 | 8 | client.commands = new Collection(); 9 | const foldersPath = path.join(__dirname, 'commands'); 10 | const commandFolders = fs.readdirSync(foldersPath); 11 | 12 | for (const folder of commandFolders) { 13 | const commandsPath = path.join(foldersPath, folder); 14 | const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js')); 15 | for (const file of commandFiles) { 16 | const filePath = path.join(commandsPath, file); 17 | const command = require(filePath); 18 | if ('data' in command && 'execute' in command) { 19 | client.commands.set(command.data.name, command); 20 | } else { 21 | console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`); 22 | } 23 | } 24 | } 25 | 26 | const eventsPath = path.join(__dirname, 'events'); 27 | const eventFiles = fs.readdirSync(eventsPath).filter(file => file.endsWith('.js')); 28 | 29 | for (const file of eventFiles) { 30 | const filePath = path.join(eventsPath, file); 31 | const event = require(filePath); 32 | if (event.once) { 33 | client.once(event.name, (...args) => event.execute(...args)); 34 | } else { 35 | client.on(event.name, (...args) => event.execute(...args)); 36 | } 37 | } 38 | 39 | client.login(token); 40 | -------------------------------------------------------------------------------- /code-samples/creating-your-bot/initial-files/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "token": "your-token-goes-here" 3 | } 4 | -------------------------------------------------------------------------------- /code-samples/creating-your-bot/initial-files/index.js: -------------------------------------------------------------------------------- 1 | const { Client, Events, GatewayIntentBits } = require('discord.js'); 2 | const { token } = require('./config.json'); 3 | 4 | const client = new Client({ intents: [GatewayIntentBits.Guilds] }); 5 | 6 | client.once(Events.ClientReady, readyClient => { 7 | console.log(`Ready! Logged in as ${readyClient.user.tag}`); 8 | }); 9 | 10 | client.login(token); 11 | -------------------------------------------------------------------------------- /code-samples/creating-your-bot/slash-commands/commands/utility/ping.js: -------------------------------------------------------------------------------- 1 | const { SlashCommandBuilder } = require('discord.js'); 2 | 3 | module.exports = { 4 | data: new SlashCommandBuilder() 5 | .setName('ping') 6 | .setDescription('Replies with Pong!'), 7 | async execute(interaction) { 8 | await interaction.reply('Pong!'); 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /code-samples/creating-your-bot/slash-commands/commands/utility/server.js: -------------------------------------------------------------------------------- 1 | const { SlashCommandBuilder } = require('discord.js'); 2 | 3 | module.exports = { 4 | data: new SlashCommandBuilder() 5 | .setName('server') 6 | .setDescription('Provides information about the server.'), 7 | async execute(interaction) { 8 | // interaction.guild is the object representing the Guild in which the command was run 9 | await interaction.reply(`This server is ${interaction.guild.name} and has ${interaction.guild.memberCount} members.`); 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /code-samples/creating-your-bot/slash-commands/commands/utility/user.js: -------------------------------------------------------------------------------- 1 | const { SlashCommandBuilder } = require('discord.js'); 2 | 3 | module.exports = { 4 | data: new SlashCommandBuilder() 5 | .setName('user') 6 | .setDescription('Provides information about the user.'), 7 | async execute(interaction) { 8 | // interaction.user is the object representing the User who ran the command 9 | // interaction.member is the GuildMember object, which represents the user in the specific guild 10 | await interaction.reply(`This command was run by ${interaction.user.username}, who joined on ${interaction.member.joinedAt}.`); 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /code-samples/creating-your-bot/slash-commands/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "clientId": "123456789012345678", 3 | "guildId": "876543210987654321", 4 | "token": "your-token-goes-here" 5 | } 6 | -------------------------------------------------------------------------------- /code-samples/creating-your-bot/slash-commands/index.js: -------------------------------------------------------------------------------- 1 | const { Client, Events, GatewayIntentBits } = require('discord.js'); 2 | const { token } = require('./config.json'); 3 | 4 | // Create a new client instance 5 | const client = new Client({ intents: [GatewayIntentBits.Guilds] }); 6 | 7 | // When the client is ready, run this code (only once) 8 | client.once(Events.ClientReady, readyClient => { 9 | console.log(`Ready! Logged in as ${readyClient.user.tag}`); 10 | }); 11 | 12 | // Log in to Discord with your client's token 13 | client.login(token); 14 | -------------------------------------------------------------------------------- /code-samples/keyv/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "globalPrefix": ".", 3 | "token": "your-token-goes-here" 4 | } 5 | -------------------------------------------------------------------------------- /code-samples/keyv/index.js: -------------------------------------------------------------------------------- 1 | const { Keyv } = require('keyv'); 2 | const { Client, Events, GatewayIntentBits } = require('discord.js'); 3 | const { globalPrefix, token } = require('./config.json'); 4 | 5 | const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent] }); 6 | const prefixes = new Keyv('sqlite://path/to.sqlite'); 7 | 8 | client.once(Events.ClientReady, readyClient => { 9 | console.log(`Ready! Logged in as ${readyClient.user.tag}`); 10 | }); 11 | 12 | client.on(Events.MessageCreate, async message => { 13 | if (message.author.bot) return; 14 | 15 | let args; 16 | if (message.guild) { 17 | let prefix; 18 | 19 | if (message.content.startsWith(globalPrefix)) { 20 | prefix = globalPrefix; 21 | } else { 22 | const guildPrefix = await prefixes.get(message.guild.id); 23 | if (message.content.startsWith(guildPrefix)) prefix = guildPrefix; 24 | } 25 | 26 | if (!prefix) return; 27 | args = message.content.slice(prefix.length).trim().split(/\s+/); 28 | } else { 29 | const slice = message.content.startsWith(globalPrefix) ? globalPrefix.length : 0; 30 | args = message.content.slice(slice).split(/\s+/); 31 | } 32 | 33 | const command = args.shift().toLowerCase(); 34 | 35 | if (command === 'prefix') { 36 | if (args.length) { 37 | await prefixes.set(message.guild.id, args[0]); 38 | return message.channel.send(`Successfully set prefix to \`${args[0]}\``); 39 | } 40 | 41 | return message.channel.send(`Prefix is \`${await prefixes.get(message.guild.id) || globalPrefix}\``); 42 | } 43 | }); 44 | 45 | client.login(token); 46 | -------------------------------------------------------------------------------- /code-samples/oauth/simple-oauth-webserver/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "clientId": "", 3 | "clientSecret": "", 4 | "port": 53134 5 | } 6 | -------------------------------------------------------------------------------- /code-samples/oauth/simple-oauth-webserver/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | My Discord OAuth2 App 5 | 6 | 7 |
8 | Hoi! 9 |
10 | 11 | 12 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /code-samples/oauth/simple-oauth-webserver/index.js: -------------------------------------------------------------------------------- 1 | const { request } = require('undici'); 2 | const express = require('express'); 3 | const { clientId, clientSecret, port } = require('./config.json'); 4 | 5 | const app = express(); 6 | 7 | app.get('/', async ({ query }, response) => { 8 | const { code } = query; 9 | 10 | if (code) { 11 | try { 12 | const tokenResponseData = await request('https://discord.com/api/oauth2/token', { 13 | method: 'POST', 14 | body: new URLSearchParams({ 15 | client_id: clientId, 16 | client_secret: clientSecret, 17 | code, 18 | grant_type: 'authorization_code', 19 | redirect_uri: `http://localhost:${port}`, 20 | scope: 'identify', 21 | }).toString(), 22 | headers: { 23 | 'Content-Type': 'application/x-www-form-urlencoded', 24 | }, 25 | }); 26 | 27 | const oauthData = await tokenResponseData.body.json(); 28 | 29 | const userResult = await request('https://discord.com/api/users/@me', { 30 | headers: { 31 | authorization: `${oauthData.token_type} ${oauthData.access_token}`, 32 | }, 33 | }); 34 | 35 | console.log(await userResult.body.json()); 36 | } catch (error) { 37 | // NOTE: An unauthorized token will not throw an error 38 | // tokenResponseData.statusCode will be 401 39 | console.error(error); 40 | } 41 | } 42 | 43 | return response.sendFile('index.html', { root: '.' }); 44 | }); 45 | 46 | app.listen(port, () => console.log(`App listening at http://localhost:${port}`)); 47 | -------------------------------------------------------------------------------- /code-samples/popular-topics/canvas/14/index.js: -------------------------------------------------------------------------------- 1 | const { AttachmentBuilder, Client, Events, GatewayIntentBits } = require('discord.js'); 2 | const { createCanvas, Image } = require('@napi-rs/canvas'); 3 | const { readFile } = require('fs/promises'); 4 | const { request } = require('undici'); 5 | 6 | const client = new Client({ intents: [GatewayIntentBits.Guilds] }); 7 | 8 | client.once(Events.ClientReady, readyClient => { 9 | console.log(`Ready! Logged in as ${readyClient.user.tag}`); 10 | }); 11 | 12 | const applyText = (canvas, text) => { 13 | const context = canvas.getContext('2d'); 14 | let fontSize = 70; 15 | 16 | do { 17 | context.font = `${fontSize -= 10}px sans-serif`; 18 | } while (context.measureText(text).width > canvas.width - 300); 19 | 20 | return context.font; 21 | }; 22 | 23 | client.on(Events.InteractionCreate, async interaction => { 24 | if (!interaction.isChatInputCommand()) return; 25 | 26 | if (interaction.commandName === 'profile') { 27 | const canvas = createCanvas(700, 250); 28 | const context = canvas.getContext('2d'); 29 | 30 | const background = await readFile('./wallpaper.jpg'); 31 | const backgroundImage = new Image(); 32 | backgroundImage.src = background; 33 | context.drawImage(backgroundImage, 0, 0, canvas.width, canvas.height); 34 | 35 | context.strokeStyle = '#0099ff'; 36 | context.strokeRect(0, 0, canvas.width, canvas.height); 37 | 38 | context.font = '28px sans-serif'; 39 | context.fillStyle = '#ffffff'; 40 | context.fillText('Profile', canvas.width / 2.5, canvas.height / 3.5); 41 | 42 | context.font = applyText(canvas, `${interaction.member.displayName}!`); 43 | context.fillStyle = '#ffffff'; 44 | context.fillText(`${interaction.member.displayName}!`, canvas.width / 2.5, canvas.height / 1.8); 45 | 46 | context.beginPath(); 47 | context.arc(125, 125, 100, 0, Math.PI * 2, true); 48 | context.closePath(); 49 | context.clip(); 50 | 51 | const { body } = await request(interaction.user.displayAvatarURL({ format: 'jpg' })); 52 | const avatar = new Image(); 53 | avatar.src = Buffer.from(await body.arrayBuffer()); 54 | context.drawImage(avatar, 25, 25, 200, 200); 55 | 56 | const attachment = new AttachmentBuilder(canvas.toBuffer('image/png'), { name: 'profile-image.png' }); 57 | 58 | interaction.reply({ files: [attachment] }); 59 | } 60 | }); 61 | 62 | client.login('your-token-goes-here'); 63 | -------------------------------------------------------------------------------- /code-samples/popular-topics/canvas/14/wallpaper.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discordjs/guide/1fa24806d4e0c24095f3b2cf21fa746d2fc66d50/code-samples/popular-topics/canvas/14/wallpaper.jpg -------------------------------------------------------------------------------- /code-samples/popular-topics/reactions/14/awaiting-reactions.js: -------------------------------------------------------------------------------- 1 | const { Client, Events, GatewayIntentBits } = require('discord.js'); 2 | 3 | const client = new Client({ 4 | intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessageReactions], 5 | }); 6 | 7 | client.once(Events.ClientReady, readyClient => { 8 | console.log(`Ready! Logged in as ${readyClient.user.tag}`); 9 | }); 10 | 11 | client.on(Events.InteractionCreate, async interaction => { 12 | if (!interaction.isChatInputCommand()) return; 13 | 14 | if (interaction.commandName === 'react-await') { 15 | const response = await interaction.reply({ content: 'Awaiting emojis...', withResponse: true }); 16 | const { message } = response.resource; 17 | message.react('👍').then(() => message.react('👎')); 18 | 19 | const collectorFilter = (reaction, user) => { 20 | return ['👍', '👎'].includes(reaction.emoji.name) && user.id === interaction.user.id; 21 | }; 22 | 23 | message.awaitReactions({ filter: collectorFilter, max: 1, time: 60000, errors: ['time'] }) 24 | .then(collected => { 25 | const reaction = collected.first(); 26 | 27 | if (reaction.emoji.name === '👍') { 28 | interaction.followUp('You reacted with a thumbs up.'); 29 | } else { 30 | interaction.followUp('You reacted with a thumbs down.'); 31 | } 32 | }) 33 | .catch(collected => { 34 | console.log(`After a minute, only ${collected.size} out of 4 reacted.`); 35 | interaction.followUp('You didn\'t react with neither a thumbs up, nor a thumbs down.'); 36 | }); 37 | } 38 | }); 39 | 40 | client.login('your-token-goes-here'); 41 | -------------------------------------------------------------------------------- /code-samples/popular-topics/reactions/14/basic-reacting.js: -------------------------------------------------------------------------------- 1 | const { Client, Events, GatewayIntentBits } = require('discord.js'); 2 | 3 | const client = new Client({ 4 | intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessageReactions], 5 | }); 6 | 7 | client.once(Events.ClientReady, readyClient => { 8 | console.log(`Ready! Logged in as ${readyClient.user.tag}`); 9 | }); 10 | 11 | client.on(Events.InteractionCreate, async interaction => { 12 | if (!interaction.isChatInputCommand()) return; 13 | 14 | const { commandName } = interaction; 15 | 16 | if (commandName === 'react') { 17 | const response = await interaction.reply({ content: 'You can react with Unicode emojis!', withResponse: true }); 18 | const { message } = response.resource; 19 | message.react('😄'); 20 | } else if (commandName === 'react-custom') { 21 | const response = await interaction.reply({ content: 'You can react with custom emojis!', withResponse: true }); 22 | const { message } = response.resource; 23 | message.react('123456789012345678'); 24 | } else if (commandName === 'fruits') { 25 | const response = await interaction.reply({ content: 'Reacting with fruits!', withResponse: true }); 26 | const { message } = response.resource; 27 | 28 | message.react('🍎') 29 | .then(() => message.react('🍊')) 30 | .then(() => message.react('🍇')) 31 | .catch(() => console.error('One of the emojis failed to react.')); 32 | } 33 | }); 34 | 35 | client.login('your-token-goes-here'); 36 | -------------------------------------------------------------------------------- /code-samples/popular-topics/reactions/14/uncached-messages.js: -------------------------------------------------------------------------------- 1 | const { Client, Events, GatewayIntentBits, Partials } = require('discord.js'); 2 | 3 | const client = new Client({ 4 | intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.GuildMessageReactions], 5 | partials: [Partials.Message, Partials.Channel, Partials.Reaction], 6 | }); 7 | 8 | client.once(Events.ClientReady, readyClient => { 9 | console.log(`Ready! Logged in as ${readyClient.user.tag}`); 10 | }); 11 | 12 | client.on(Events.MessageReactionAdd, async (reaction, user) => { 13 | if (reaction.message.partial) { 14 | try { 15 | await reaction.message.fetch(); 16 | } catch (error) { 17 | console.error('Something went wrong when fetching the message: ', error); 18 | } 19 | } 20 | 21 | console.log(`${user.username} reacted with "${reaction.emoji.name}".`); 22 | }); 23 | 24 | client.on(Events.MessageReactionRemove, async (reaction, user) => { 25 | if (reaction.message.partial) { 26 | try { 27 | await reaction.message.fetch(); 28 | } catch (error) { 29 | console.error('Something went wrong when fetching the message: ', error); 30 | } 31 | } 32 | 33 | console.log(`${user.username} removed their "${reaction.emoji.name}" reaction.`); 34 | }); 35 | 36 | client.login('your-token-goes-here'); 37 | -------------------------------------------------------------------------------- /code-samples/popular-topics/webhooks/14/using-Webhook.js: -------------------------------------------------------------------------------- 1 | const { Client, EmbedBuilder, Events, GatewayIntentBits } = require('discord.js'); 2 | const { token } = require('./config.json'); 3 | 4 | const client = new Client({ intents: [GatewayIntentBits.Guilds] }); 5 | 6 | const embed = new EmbedBuilder() 7 | .setTitle('Some Title') 8 | .setColor(0x00FFFF); 9 | 10 | client.once(Events.ClientReady, async () => { 11 | const channel = client.channels.cache.get('222197033908436994'); 12 | try { 13 | const webhooks = await channel.fetchWebhooks(); 14 | const webhook = webhooks.find(wh => wh.token); 15 | 16 | if (!webhook) { 17 | return console.log('No webhook was found that I can use!'); 18 | } 19 | 20 | await webhook.send({ 21 | content: 'Webhook test', 22 | username: 'some-username', 23 | avatarURL: 'https://i.imgur.com/AfFp7pu.png', 24 | embeds: [embed], 25 | }); 26 | } catch (error) { 27 | console.error('Error trying to send: ', error); 28 | } 29 | }); 30 | 31 | client.login(token); 32 | -------------------------------------------------------------------------------- /code-samples/popular-topics/webhooks/14/using-WebhookClient.js: -------------------------------------------------------------------------------- 1 | const { EmbedBuilder, WebhookClient } = require('discord.js'); 2 | const { webhookId, webhookToken } = require('./config.json'); 3 | 4 | const webhookClient = new WebhookClient({ id: webhookId, token: webhookToken }); 5 | 6 | const embed = new EmbedBuilder() 7 | .setTitle('Some Title') 8 | .setColor(0x00FFFF); 9 | 10 | webhookClient.send({ 11 | content: 'Webhook test', 12 | username: 'some-username', 13 | avatarURL: 'https://i.imgur.com/AfFp7pu.png', 14 | embeds: [embed], 15 | }); 16 | -------------------------------------------------------------------------------- /code-samples/sequelize/currency/14/app.js: -------------------------------------------------------------------------------- 1 | const { Op } = require('sequelize'); 2 | const { Client, codeBlock, Collection, Events, GatewayIntentBits } = require('discord.js'); 3 | const { Users, CurrencyShop } = require('./dbObjects.js'); 4 | 5 | const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages] }); 6 | const currency = new Collection(); 7 | 8 | /* 9 | * Make sure you are on at least version 5 of Sequelize! Version 4 as used in this guide will pose a security threat. 10 | * You can read more about this issue On the [Sequelize issue tracker](https://github.com/sequelize/sequelize/issues/7310). 11 | */ 12 | 13 | async function addBalance(id, amount) { 14 | const user = currency.get(id); 15 | 16 | if (user) { 17 | user.balance += Number(amount); 18 | return user.save(); 19 | } 20 | 21 | const newUser = await Users.create({ user_id: id, balance: amount }); 22 | currency.set(id, newUser); 23 | 24 | return newUser; 25 | } 26 | 27 | function getBalance(id) { 28 | const user = currency.get(id); 29 | return user ? user.balance : 0; 30 | } 31 | 32 | client.once(Events.Ready, async () => { 33 | const storedBalances = await Users.findAll(); 34 | storedBalances.forEach(b => currency.set(b.user_id, b)); 35 | 36 | console.log(`Logged in as ${client.user.tag}!`); 37 | }); 38 | 39 | client.on(Events.MessageCreate, async message => { 40 | if (message.author.bot) return; 41 | addBalance(message.author.id, 1); 42 | }); 43 | 44 | client.on(Events.InteractionCreate, async interaction => { 45 | if (!interaction.isChatInputCommand()) return; 46 | 47 | const { commandName } = interaction; 48 | 49 | if (commandName === 'balance') { 50 | const target = interaction.options.getUser('user') || interaction.user; 51 | 52 | return interaction.reply(`${target.tag} has ${getBalance(target.id)}💰`); 53 | } else if (commandName === 'inventory') { 54 | const target = interaction.options.getUser('user') || interaction.user; 55 | const user = await Users.findOne({ where: { user_id: target.id } }); 56 | const items = await user.getItems(); 57 | 58 | if (!items.length) return interaction.reply(`${target.tag} has nothing!`); 59 | 60 | return interaction.reply(`${target.tag} currently has ${items.map(t => `${t.amount} ${t.item.name}`).join(', ')}`); 61 | } else if (commandName === 'transfer') { 62 | const currentAmount = getBalance(interaction.user.id); 63 | const transferAmount = interaction.options.getInteger('amount'); 64 | const transferTarget = interaction.options.getUser('user'); 65 | 66 | if (transferAmount > currentAmount) return interaction.reply(`Sorry ${interaction.user} you don't have that much.`); 67 | if (transferAmount <= 0) return interaction.reply(`Please enter an amount greater than zero, ${interaction.user}`); 68 | 69 | addBalance(interaction.user.id, -transferAmount); 70 | addBalance(transferTarget.id, transferAmount); 71 | 72 | return interaction.reply(`Successfully transferred ${transferAmount}💰 to ${transferTarget.tag}. Your current balance is ${getBalance(interaction.user.id)}💰`); 73 | } else if (commandName === 'buy') { 74 | const itemName = interaction.options.getString('item'); 75 | const item = await CurrencyShop.findOne({ where: { name: { [Op.like]: itemName } } }); 76 | 77 | if (!item) return interaction.reply('That item doesn\'t exist.'); 78 | if (item.cost > getBalance(interaction.user.id)) { 79 | return interaction.reply(`You don't have enough currency, ${interaction.user}`); 80 | } 81 | 82 | const user = await Users.findOne({ where: { user_id: interaction.user.id } }); 83 | addBalance(interaction.user.id, -item.cost); 84 | await user.addItem(item); 85 | 86 | return interaction.reply(`You've bought a ${item.name}`); 87 | } else if (commandName === 'shop') { 88 | const items = await CurrencyShop.findAll(); 89 | return interaction.reply(codeBlock(items.map(i => `${i.name}: ${i.cost}💰`).join('\n'))); 90 | } else if (commandName === 'leaderboard') { 91 | return interaction.reply( 92 | codeBlock( 93 | currency.sort((a, b) => b.balance - a.balance) 94 | .filter(user => client.users.cache.has(user.user_id)) 95 | .first(10) 96 | .map((user, position) => `(${position + 1}) ${(client.users.cache.get(user.user_id).tag)}: ${user.balance}💰`) 97 | .join('\n'), 98 | ), 99 | ); 100 | } 101 | }); 102 | 103 | client.login('your-token-goes-here'); 104 | -------------------------------------------------------------------------------- /code-samples/sequelize/currency/14/dbInit.js: -------------------------------------------------------------------------------- 1 | const Sequelize = require('sequelize'); 2 | 3 | /* 4 | * Make sure you are on at least version 5 of Sequelize! Version 4 as used in this guide will pose a security threat. 5 | * You can read more about this issue on the [Sequelize issue tracker](https://github.com/sequelize/sequelize/issues/7310). 6 | */ 7 | 8 | const sequelize = new Sequelize('database', 'username', 'password', { 9 | host: 'localhost', 10 | dialect: 'sqlite', 11 | logging: false, 12 | storage: 'database.sqlite', 13 | }); 14 | 15 | const CurrencyShop = require('./models/CurrencyShop.js')(sequelize, Sequelize.DataTypes); 16 | require('./models/Users.js')(sequelize, Sequelize.DataTypes); 17 | require('./models/UserItems.js')(sequelize, Sequelize.DataTypes); 18 | 19 | const force = process.argv.includes('--force') || process.argv.includes('-f'); 20 | 21 | sequelize.sync({ force }).then(async () => { 22 | const shop = [ 23 | CurrencyShop.upsert({ name: 'Tea', cost: 1 }), 24 | CurrencyShop.upsert({ name: 'Coffee', cost: 2 }), 25 | CurrencyShop.upsert({ name: 'Cake', cost: 5 }), 26 | ]; 27 | 28 | await Promise.all(shop); 29 | console.log('Database synced'); 30 | 31 | sequelize.close(); 32 | }).catch(console.error); 33 | -------------------------------------------------------------------------------- /code-samples/sequelize/currency/14/dbObjects.js: -------------------------------------------------------------------------------- 1 | const Sequelize = require('sequelize'); 2 | 3 | /* 4 | * Make sure you are on at least version 5 of Sequelize! Version 4 as used in this guide will pose a security threat. 5 | * You can read more about this issue on the [Sequelize issue tracker](https://github.com/sequelize/sequelize/issues/7310). 6 | */ 7 | 8 | const sequelize = new Sequelize('database', 'username', 'password', { 9 | host: 'localhost', 10 | dialect: 'sqlite', 11 | logging: false, 12 | storage: 'database.sqlite', 13 | }); 14 | 15 | const Users = require('./models/Users.js')(sequelize, Sequelize.DataTypes); 16 | const CurrencyShop = require('./models/CurrencyShop.js')(sequelize, Sequelize.DataTypes); 17 | const UserItems = require('./models/UserItems.js')(sequelize, Sequelize.DataTypes); 18 | 19 | UserItems.belongsTo(CurrencyShop, { foreignKey: 'item_id', as: 'item' }); 20 | 21 | Reflect.defineProperty(Users.prototype, 'addItem', { 22 | /* eslint-disable-next-line func-name-matching */ 23 | value: async function addItem(item) { 24 | const userItem = await UserItems.findOne({ 25 | where: { user_id: this.user_id, item_id: item.id }, 26 | }); 27 | 28 | if (userItem) { 29 | userItem.amount += 1; 30 | return userItem.save(); 31 | } 32 | 33 | return UserItems.create({ user_id: this.user_id, item_id: item.id, amount: 1 }); 34 | }, 35 | }); 36 | 37 | Reflect.defineProperty(Users.prototype, 'getItems', { 38 | /* eslint-disable-next-line func-name-matching */ 39 | value: function getItems() { 40 | return UserItems.findAll({ 41 | where: { user_id: this.user_id }, 42 | include: ['item'], 43 | }); 44 | }, 45 | }); 46 | 47 | module.exports = { Users, CurrencyShop, UserItems }; 48 | -------------------------------------------------------------------------------- /code-samples/sequelize/currency/14/models/CurrencyShop.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, DataTypes) => { 2 | return sequelize.define('currency_shop', { 3 | name: { 4 | type: DataTypes.STRING, 5 | unique: true, 6 | }, 7 | cost: { 8 | type: DataTypes.INTEGER, 9 | allowNull: false, 10 | }, 11 | }, { 12 | timestamps: false, 13 | }); 14 | }; 15 | -------------------------------------------------------------------------------- /code-samples/sequelize/currency/14/models/UserItems.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, DataTypes) => { 2 | return sequelize.define('user_item', { 3 | user_id: DataTypes.STRING, 4 | item_id: DataTypes.INTEGER, 5 | amount: { 6 | type: DataTypes.INTEGER, 7 | allowNull: false, 8 | 'default': 0, 9 | }, 10 | }, { 11 | timestamps: false, 12 | }); 13 | }; 14 | -------------------------------------------------------------------------------- /code-samples/sequelize/currency/14/models/Users.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, DataTypes) => { 2 | return sequelize.define('users', { 3 | user_id: { 4 | type: DataTypes.STRING, 5 | primaryKey: true, 6 | }, 7 | balance: { 8 | type: DataTypes.INTEGER, 9 | defaultValue: 0, 10 | allowNull: false, 11 | }, 12 | }, { 13 | timestamps: false, 14 | }); 15 | }; 16 | -------------------------------------------------------------------------------- /code-samples/sequelize/tags/sequelize.js: -------------------------------------------------------------------------------- 1 | const Sequelize = require('sequelize'); 2 | const { Client, Events, GatewayIntentBits } = require('discord.js'); 3 | 4 | const client = new Client({ intents: [GatewayIntentBits.Guilds] }); 5 | 6 | /* 7 | * Make sure you are on at least version 5 of Sequelize! Version 4 as used in this guide will pose a security threat. 8 | * You can read more about this issue on the [Sequelize issue tracker](https://github.com/sequelize/sequelize/issues/7310). 9 | */ 10 | 11 | const sequelize = new Sequelize('database', 'username', 'password', { 12 | host: 'localhost', 13 | dialect: 'sqlite', 14 | logging: false, 15 | // SQLite only 16 | storage: 'database.sqlite', 17 | }); 18 | 19 | const Tags = sequelize.define('tags', { 20 | name: { 21 | type: Sequelize.STRING, 22 | unique: true, 23 | }, 24 | description: Sequelize.TEXT, 25 | username: Sequelize.STRING, 26 | usage_count: { 27 | type: Sequelize.INTEGER, 28 | defaultValue: 0, 29 | allowNull: false, 30 | }, 31 | }); 32 | 33 | client.once(Events.ClientReady, readyClient => { 34 | /* 35 | * equivalent to: CREATE TABLE tags( 36 | * name VARCHAR(255), 37 | * description TEXT, 38 | * username VARCHAR(255), 39 | * usage_count INT NOT NULL DEFAULT 0 40 | * ); 41 | */ 42 | Tags.sync(); 43 | 44 | console.log(`Logged in as ${readyClient.user.tag}!`); 45 | }); 46 | 47 | client.on(Events.InteractionCreate, async interaction => { 48 | if (!interaction.isChatInputCommand()) return; 49 | 50 | const { commandName } = interaction; 51 | 52 | if (commandName === 'addtag') { 53 | const tagName = interaction.options.getString('name'); 54 | const tagDescription = interaction.options.getString('description'); 55 | 56 | try { 57 | // equivalent to: INSERT INTO tags (name, description, username) values (?, ?, ?); 58 | const tag = await Tags.create({ 59 | name: tagName, 60 | description: tagDescription, 61 | username: interaction.author.username, 62 | }); 63 | 64 | return interaction.reply(`Tag ${tag.name} added.`); 65 | } catch (error) { 66 | if (error.name === 'SequelizeUniqueConstraintError') { 67 | return interaction.reply('That tag already exists.'); 68 | } 69 | 70 | return interaction.reply('Something went wrong with adding a tag.'); 71 | } 72 | } else if (commandName === 'tag') { 73 | const tagName = interaction.options.getString('name'); 74 | 75 | // equivalent to: SELECT * FROM tags WHERE name = 'tagName' LIMIT 1; 76 | const tag = await Tags.findOne({ where: { name: tagName } }); 77 | 78 | if (tag) { 79 | // equivalent to: UPDATE tags SET usage_count = usage_count + 1 WHERE name = 'tagName'; 80 | tag.increment('usage_count'); 81 | return interaction.reply(tag.get('description')); 82 | } 83 | 84 | return interaction.reply(`Could not find tag: ${tagName}`); 85 | } else if (commandName === 'edittag') { 86 | const tagName = interaction.options.getString('name'); 87 | const tagDescription = interaction.options.getString('description'); 88 | 89 | // equivalent to: UPDATE tags (description) values (?) WHERE name = ?; 90 | const affectedRows = await Tags.update({ description: tagDescription }, { where: { name: tagName } }); 91 | 92 | if (affectedRows > 0) { 93 | return interaction.reply(`Tag ${tagName} was edited.`); 94 | } 95 | 96 | return interaction.reply(`Could not find a tag with name ${tagName}.`); 97 | } else if (commandName === 'taginfo') { 98 | const tagName = interaction.options.getString('name'); 99 | 100 | // equivalent to: SELECT * FROM tags WHERE name = 'tagName' LIMIT 1; 101 | const tag = await Tags.findOne({ where: { name: tagName } }); 102 | 103 | if (tag) { 104 | return interaction.reply(`${tagName} was created by ${tag.username} at ${tag.createdAt} and has been used ${tag.usage_count} times.`); 105 | } 106 | 107 | return interaction.reply(`Could not find tag: ${tagName}`); 108 | } else if (commandName === 'showtags') { 109 | const tagList = await Tags.findAll({ attributes: ['name'] }); 110 | const tagString = tagList.map(t => t.name).join(', ') || 'No tags set.'; 111 | 112 | return interaction.reply(`List of tags: ${tagString}`); 113 | } else if (commandName === 'removetag') { 114 | // equivalent to: DELETE from tags WHERE name = ?; 115 | const tagName = interaction.options.getString('name'); 116 | const rowCount = await Tags.destroy({ where: { name: tagName } }); 117 | 118 | if (!rowCount) return interaction.reply('That tag did not exist.'); 119 | 120 | return interaction.reply('Tag deleted.'); 121 | } 122 | }); 123 | 124 | client.login('your-token-goes-here'); 125 | -------------------------------------------------------------------------------- /code-samples/sharding/extended/14/bot.js: -------------------------------------------------------------------------------- 1 | const { Client, Events, GatewayIntentBits } = require('discord.js'); 2 | 3 | const client = new Client({ intents: [GatewayIntentBits.Guilds] }); 4 | 5 | function findEmoji(c, { nameOrId }) { 6 | return c.emojis.cache.get(nameOrId) || c.emojis.cache.find(e => e.name.toLowerCase() === nameOrId.toLowerCase()); 7 | } 8 | 9 | client.on(Events.InteractionCreate, interaction => { 10 | if (!interaction.isChatInputCommand()) return; 11 | 12 | const { commandName } = interaction; 13 | 14 | if (commandName === 'send') { 15 | const id = interaction.options.getString('destination'); 16 | 17 | return client.shard.broadcastEval(async (c, { channelId }) => { 18 | const channel = c.channels.cache.get(channelId); 19 | if (channel) { 20 | await channel.send(`This is a message from shard ${client.shard.ids.join(',')}!`); 21 | return true; 22 | } 23 | return false; 24 | }, { context: { channelId: id } }) 25 | .then(sentArray => { 26 | if (!sentArray.includes(true)) { 27 | return interaction.reply('I could not find such a channel.'); 28 | } 29 | 30 | return interaction.reply(`I have sent a message to channel: \`${id}\`!`); 31 | }); 32 | } 33 | 34 | if (commandName === 'emoji') { 35 | const emojiNameOrId = interaction.options.getString('emoji'); 36 | 37 | return client.shard.broadcastEval(findEmoji, { context: { nameOrId: emojiNameOrId } }) 38 | .then(emojiArray => { 39 | // Locate a non falsy result, which will be the emoji in question 40 | const foundEmoji = emojiArray.find(emoji => emoji); 41 | if (!foundEmoji) return interaction.reply('I could not find such an emoji.'); 42 | return interaction.reply(`I have found the ${foundEmoji.animated ? `<${foundEmoji.identifier}>` : `<:${foundEmoji.identifier}> emoji!`}!`); 43 | }); 44 | } 45 | }); 46 | 47 | client.login('your-token-goes-here'); 48 | -------------------------------------------------------------------------------- /code-samples/sharding/extended/14/index.js: -------------------------------------------------------------------------------- 1 | const { ShardingManager } = require('discord.js'); 2 | 3 | const manager = new ShardingManager('./bot.js', { token: 'your-token-goes-here' }); 4 | 5 | manager.on('shardCreate', shard => console.log(`Launched shard ${shard.id}`)); 6 | 7 | manager.spawn(); 8 | -------------------------------------------------------------------------------- /code-samples/sharding/getting-started/14/bot.js: -------------------------------------------------------------------------------- 1 | // bot.js 2 | const { Client, Events, GatewayIntentBits } = require('discord.js'); 3 | 4 | const client = new Client({ intents: [GatewayIntentBits.Guilds] }); 5 | 6 | client.on(Events.InteractionCreate, interaction => { 7 | if (!interaction.isChatInputCommand()) return; 8 | 9 | const { commandName } = interaction; 10 | 11 | if (commandName === 'stats') { 12 | return interaction.reply(`Server count: ${client.guilds.cache.size}.`); 13 | } 14 | }); 15 | 16 | client.login('your-token-goes-here'); 17 | -------------------------------------------------------------------------------- /code-samples/sharding/getting-started/14/index.js: -------------------------------------------------------------------------------- 1 | const { ShardingManager } = require('discord.js'); 2 | 3 | const manager = new ShardingManager('./bot.js', { token: 'your-token-goes-here' }); 4 | 5 | manager.on('shardCreate', shard => console.log(`Launched shard ${shard.id}`)); 6 | 7 | manager.spawn(); 8 | -------------------------------------------------------------------------------- /guide/.eslintrc.js: -------------------------------------------------------------------------------- 1 | const path = require('node:path'); 2 | 3 | module.exports = { 4 | 'extends': path.join(__dirname, '..', '.eslintrc.js'), 5 | rules: { 6 | 'no-undef': 'off', 7 | 'no-unused-vars': 'off', 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /guide/.vuepress/assets/discord-avatar-djs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discordjs/guide/1fa24806d4e0c24095f3b2cf21fa746d2fc66d50/guide/.vuepress/assets/discord-avatar-djs.png -------------------------------------------------------------------------------- /guide/.vuepress/clientAppEnhance.ts: -------------------------------------------------------------------------------- 1 | import { defineClientAppEnhance } from '@vuepress/client'; 2 | import { 3 | DiscordButton, 4 | DiscordButtons, 5 | DiscordEmbed, 6 | DiscordEmbedField, 7 | DiscordEmbedFields, 8 | DiscordInteraction, 9 | DiscordMarkdown, 10 | DiscordMention, 11 | DiscordMessage, 12 | DiscordMessages, 13 | DiscordReaction, 14 | DiscordReactions, 15 | install as DiscordMessageComponents, 16 | } from '@discord-message-components/vue'; 17 | import DocsLink from './components/DocsLink.vue'; 18 | import ResultingCode from './components/ResultingCode.vue'; 19 | import djsAvatar from './assets/discord-avatar-djs.png'; 20 | import '@discord-message-components/vue/dist/style.css'; 21 | 22 | export default defineClientAppEnhance(({ app }) => { 23 | app.use(DiscordMessageComponents, { 24 | avatars: { 25 | djs: djsAvatar, 26 | }, 27 | profiles: { 28 | user: { 29 | author: 'User', 30 | avatar: 'djs', 31 | }, 32 | bot: { 33 | author: 'Guide Bot', 34 | avatar: 'green', 35 | bot: true, 36 | }, 37 | }, 38 | }); 39 | 40 | app.component('DiscordButton', DiscordButton); 41 | app.component('DiscordButtons', DiscordButtons); 42 | app.component('DiscordEmbed', DiscordEmbed); 43 | app.component('DiscordEmbedField', DiscordEmbedField); 44 | app.component('DiscordEmbedFields', DiscordEmbedFields); 45 | app.component('DiscordInteraction', DiscordInteraction); 46 | app.component('DiscordMarkdown', DiscordMarkdown); 47 | app.component('DiscordMention', DiscordMention); 48 | app.component('DiscordMessage', DiscordMessage); 49 | app.component('DiscordMessages', DiscordMessages); 50 | app.component('DiscordReaction', DiscordReaction); 51 | app.component('DiscordReactions', DiscordReactions); 52 | 53 | app.component('DocsLink', DocsLink); 54 | app.component('ResultingCode', ResultingCode); 55 | }); 56 | -------------------------------------------------------------------------------- /guide/.vuepress/components/DocsLink.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 54 | -------------------------------------------------------------------------------- /guide/.vuepress/components/ResultingCode.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 27 | -------------------------------------------------------------------------------- /guide/.vuepress/config.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { defineUserConfig } from 'vuepress-vite'; 3 | import type { DefaultThemeOptions, ViteBundlerOptions } from 'vuepress-vite'; 4 | import sidebar from './sidebar'; 5 | 6 | const config = defineUserConfig({ 7 | bundler: '@vuepress/vite', 8 | templateDev: path.join(__dirname, 'templates', 'index.dev.html'), 9 | templateSSR: path.join(__dirname, 'templates', 'index.ssr.html'), 10 | lang: 'en-US', 11 | title: 'discord.js Guide', 12 | description: 'Imagine a guide... that explores the many possibilities for your discord.js bot.', 13 | head: [ 14 | ['meta', { charset: 'utf-8' }], 15 | ['meta', { name: 'viewport', content: 'width=device-width, initial-scale=1.0' }], 16 | ['link', { rel: 'icon', href: '/favicon.png' }], 17 | ['meta', { name: 'theme-color', content: '#3eaf7c' }], 18 | ['meta', { name: 'twitter:card', content: 'summary' }], 19 | ['meta', { property: 'og:title', content: 'discord.js Guide' }], 20 | ['meta', { property: 'og:description', content: 'Imagine a guide... that explores the many possibilities for your discord.js bot.' }], 21 | ['meta', { property: 'og:type', content: 'website' }], 22 | ['meta', { property: 'og:url', content: 'https://discordjs.guide/' }], 23 | ['meta', { property: 'og:locale', content: 'en_US' }], 24 | ['meta', { property: 'og:image', content: '/meta-image.png' }], 25 | ], 26 | theme: path.join(__dirname, 'theme', 'index.ts'), 27 | themeConfig: { 28 | contributors: false, 29 | sidebar, 30 | repo: 'discordjs/guide', 31 | docsDir: 'guide', 32 | sidebarDepth: 3, 33 | editLinks: true, 34 | lastUpdated: true, 35 | navbar: [ 36 | { 37 | text: 'Voice', 38 | link: '/voice/', 39 | }, 40 | { 41 | text: 'Documentation', 42 | link: 'https://discord.js.org/docs/packages/discord.js/stable', 43 | }, 44 | ], 45 | themePlugins: { 46 | mediumZoom: false, 47 | }, 48 | }, 49 | plugins: [], 50 | }); 51 | 52 | const { ALGOLIA_DOCSEARCH_API_KEY, ALGOLIA_DOCSEARCH_APP_ID, GOOGLE_ANALYTICS_ID, NODE_ENV } = process.env; 53 | 54 | if (NODE_ENV === 'production' && ALGOLIA_DOCSEARCH_API_KEY && GOOGLE_ANALYTICS_ID) { 55 | config.plugins.push( 56 | [ 57 | '@vuepress/plugin-docsearch', 58 | { 59 | appId: ALGOLIA_DOCSEARCH_APP_ID, 60 | apiKey: ALGOLIA_DOCSEARCH_API_KEY, 61 | indexName: 'discordjs', 62 | placeholder: 'Search guide', 63 | }, 64 | ], 65 | [ 66 | '@vuepress/plugin-google-analytics', 67 | { id: GOOGLE_ANALYTICS_ID }, 68 | ], 69 | ); 70 | } 71 | 72 | export default config; 73 | -------------------------------------------------------------------------------- /guide/.vuepress/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discordjs/guide/1fa24806d4e0c24095f3b2cf21fa746d2fc66d50/guide/.vuepress/public/favicon.png -------------------------------------------------------------------------------- /guide/.vuepress/public/meta-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discordjs/guide/1fa24806d4e0c24095f3b2cf21fa746d2fc66d50/guide/.vuepress/public/meta-image.png -------------------------------------------------------------------------------- /guide/.vuepress/sidebar.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | '/voice/': [ 3 | { 4 | text: 'Home', 5 | children: [ 6 | '/', 7 | '/requesting-more-content.md', 8 | '/whats-new.md', 9 | ], 10 | }, 11 | { 12 | text: 'Getting Started', 13 | children: [ 14 | '/voice/', 15 | ], 16 | }, 17 | { 18 | text: 'Library', 19 | children: [ 20 | '/voice/life-cycles.md', 21 | '/voice/voice-connections.md', 22 | '/voice/audio-player.md', 23 | '/voice/audio-resources.md', 24 | ], 25 | }, 26 | ], 27 | '/': [ 28 | { 29 | text: 'Home', 30 | children: [ 31 | '/', 32 | '/requesting-more-content.md', 33 | '/whats-new.md', 34 | ], 35 | }, 36 | { 37 | text: 'Installations & Preparations', 38 | children: [ 39 | '/preparations/', 40 | '/preparations/setting-up-a-linter.md', 41 | '/preparations/setting-up-a-bot-application.md', 42 | '/preparations/adding-your-bot-to-servers.md', 43 | ], 44 | }, 45 | { 46 | text: 'Creating Your Bot', 47 | children: [ 48 | '/creating-your-bot/', 49 | '/creating-your-bot/main-file.md', 50 | '/creating-your-bot/slash-commands.md', 51 | '/creating-your-bot/command-handling.md', 52 | '/creating-your-bot/command-deployment.md', 53 | '/creating-your-bot/event-handling.md', 54 | ], 55 | }, 56 | { 57 | text: 'Additional Features', 58 | children: [ 59 | '/additional-features/cooldowns.md', 60 | '/additional-features/reloading-commands.md', 61 | ] 62 | }, 63 | { 64 | text: 'Slash Commands', 65 | children: [ 66 | '/slash-commands/response-methods.md', 67 | '/slash-commands/advanced-creation.md', 68 | '/slash-commands/parsing-options.md', 69 | '/slash-commands/permissions.md', 70 | '/slash-commands/autocomplete.md', 71 | '/slash-commands/deleting-commands.md' 72 | ] 73 | }, 74 | { 75 | text: 'Message Components', 76 | children: [ 77 | '/message-components/action-rows.md', 78 | '/message-components/buttons.md', 79 | '/message-components/select-menus.md', 80 | '/message-components/interactions.md', 81 | ] 82 | }, 83 | { 84 | text: 'Other Interactions', 85 | children: [ 86 | '/interactions/modals.md', 87 | '/interactions/context-menus.md', 88 | ] 89 | }, 90 | { 91 | text: 'Popular Topics', 92 | children: [ 93 | '/popular-topics/faq.md', 94 | '/popular-topics/audit-logs.md', 95 | '/popular-topics/canvas.md', 96 | '/popular-topics/collectors.md', 97 | '/popular-topics/embeds.md', 98 | '/popular-topics/errors.md', 99 | '/popular-topics/formatters.md', 100 | '/popular-topics/intents.md', 101 | '/popular-topics/partials.md', 102 | '/popular-topics/permissions.md', 103 | '/popular-topics/permissions-extended.md', 104 | '/popular-topics/reactions.md', 105 | '/popular-topics/threads.md', 106 | '/popular-topics/webhooks.md', 107 | ], 108 | }, 109 | { 110 | text: 'Miscellaneous', 111 | children: [ 112 | '/miscellaneous/cache-customization.md', 113 | '/miscellaneous/useful-packages.md', 114 | ], 115 | }, 116 | { 117 | text: 'Databases', 118 | children: [ 119 | '/sequelize/', 120 | '/sequelize/currency.md', 121 | '/keyv/', 122 | ], 123 | }, 124 | { 125 | text: 'Sharding', 126 | children: [ 127 | '/sharding/', 128 | '/sharding/additional-information.md', 129 | '/sharding/extended.md', 130 | ], 131 | }, 132 | { 133 | text: 'OAuth2', 134 | children: [ 135 | '/oauth2/', 136 | ], 137 | }, 138 | { 139 | text: 'Improving Your Dev Environment', 140 | children: [ 141 | '/improving-dev-environment/pm2.md', 142 | '/improving-dev-environment/package-json-scripts.md', 143 | ], 144 | }, 145 | { 146 | text: 'Additional Information', 147 | children: [ 148 | '/additional-info/notation.md', 149 | '/additional-info/es6-syntax.md', 150 | '/additional-info/collections.md', 151 | '/additional-info/async-await.md', 152 | '/additional-info/rest-api.md', 153 | '/additional-info/changes-in-v13.md', 154 | '/additional-info/changes-in-v14.md', 155 | ], 156 | }, 157 | ], 158 | }; 159 | -------------------------------------------------------------------------------- /guide/.vuepress/styles/content.scss: -------------------------------------------------------------------------------- 1 | .theme-default-content { 2 | & > h1 { 3 | font-weight: 800; 4 | font-size: 2.25rem; 5 | } 6 | 7 | & > h2 { 8 | font-weight: 700; 9 | font-size: 1.5rem; 10 | padding-bottom: 0; 11 | border-bottom-width: 0; 12 | } 13 | 14 | & > h3 { 15 | font-weight: 600; 16 | } 17 | 18 | & > h1, 19 | & > h2, 20 | & > h3, 21 | & > h4, 22 | & > h5, 23 | & > h6 { 24 | color: var(--c-headers); 25 | overflow-wrap: anywhere; 26 | } 27 | 28 | code { 29 | color: #2f3136; 30 | } 31 | 32 | a code { 33 | color: var(--c-text-accent); 34 | } 35 | 36 | pre, 37 | pre[class*=language-] { 38 | word-wrap: normal; 39 | } 40 | } 41 | 42 | html.dark .theme-default-content { 43 | code { 44 | color: #f3f4f6; 45 | } 46 | 47 | a code { 48 | color: var(--c-text-accent); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /guide/.vuepress/styles/custom-blocks.scss: -------------------------------------------------------------------------------- 1 | .custom-container.danger, 2 | details.custom-container.details, 3 | .custom-container.tip, 4 | .custom-container.warning { 5 | overflow-wrap: break-word; 6 | } 7 | 8 | .custom-container.danger, 9 | details.custom-container.details, 10 | .custom-container.tip, 11 | .custom-container.warning { 12 | border-radius: 0.375rem; 13 | } 14 | 15 | .custom-container { 16 | &.tip a { 17 | color: var(--c-tip-link); 18 | } 19 | 20 | &.warning a { 21 | color: var(--c-warning-title); 22 | } 23 | 24 | &.danger a { 25 | color: var(--c-danger-title); 26 | } 27 | 28 | &.details summary:focus-visible { 29 | outline: 1px solid; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /guide/.vuepress/styles/home.scss: -------------------------------------------------------------------------------- 1 | .home { 2 | max-width: unset; 3 | padding: 4rem 0 0; 4 | 5 | .hero { 6 | background-color: var(--theme-500); 7 | padding: 2rem 0; 8 | 9 | img { 10 | margin: 0 auto; 11 | } 12 | 13 | .description { 14 | color: var(--text-gray); 15 | max-width: unset; 16 | } 17 | } 18 | 19 | .theme-default-content { 20 | margin: 0 auto; 21 | padding: 0 2rem; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /guide/.vuepress/styles/index.scss: -------------------------------------------------------------------------------- 1 | @import './theme-variables.scss'; 2 | @import './layout.scss'; 3 | @import './navbar.scss'; 4 | @import './search.scss'; 5 | @import './sidebar.scss'; 6 | @import './home.scss'; 7 | @import './content.scss'; 8 | @import './custom-blocks.scss'; 9 | @import './mixins/scrollbar.scss'; 10 | 11 | html { 12 | @include customScrollbar(false); 13 | } 14 | 15 | html, body { 16 | transition: none; 17 | } 18 | 19 | h2 { 20 | padding-bottom: 0; 21 | border-bottom: 0; 22 | } 23 | 24 | .theme-default-content:not(.custom) a:hover { 25 | text-decoration: underline; 26 | } 27 | 28 | div[class*=language-].line-numbers-mode::after { 29 | border-right-color: rgba(#000, 0.25); 30 | } 31 | 32 | .theme-container { 33 | .fade-slide-y-enter-active { 34 | transition: all 0.2s ease; 35 | } 36 | 37 | .fade-slide-y-leave-active { 38 | transition: all 0.2s cubic-bezier(1, 0.5, 0.8, 1); 39 | } 40 | 41 | .page { 42 | .page-meta .meta-item { 43 | &.edit-link .meta-item-label { 44 | color: var(--c-text-accent); 45 | } 46 | 47 | &.last-updated .meta-item-label { 48 | color: var(--c-brand); 49 | } 50 | } 51 | } 52 | } 53 | 54 | #nprogress { 55 | .bar { 56 | --nprogress-color: var(--c-brand-light); 57 | height: 0.375rem !important; 58 | 59 | .peg { 60 | box-shadow: none; 61 | } 62 | } 63 | } 64 | 65 | .discord-messages { 66 | border-radius: 0.375rem; 67 | 68 | .discord-message .discord-message-content .discord-markdown-content > code { 69 | color: inherit; 70 | font-size: 0.9em; 71 | padding: 0.2em; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /guide/.vuepress/styles/layout.scss: -------------------------------------------------------------------------------- 1 | .content-wrapper { 2 | display: flex; 3 | max-width: var(--wrapper-max-width); 4 | position: relative; 5 | margin: 0 auto; 6 | 7 | @media (max-width: 719px) { 8 | display: block; 9 | } 10 | 11 | &::before { 12 | content: ''; 13 | background-color: var(--c-bg-sidebar); 14 | width: 100%; 15 | position: absolute; 16 | top: 0; 17 | left: -100%; 18 | bottom: -4rem; 19 | z-index: 1; 20 | pointer-events: none; 21 | } 22 | 23 | .sidebar-wrapper { 24 | position: relative; 25 | top: 4rem; 26 | z-index: 10; 27 | 28 | @media (max-width: 719px) { 29 | position: fixed; 30 | transform: translateX(-100%); 31 | transition: transform 0.2s ease; 32 | } 33 | 34 | .sidebar { 35 | position: sticky; 36 | height: calc(100vh - 4rem); 37 | padding-top: 0; 38 | overflow-y: auto; 39 | } 40 | } 41 | 42 | .page { 43 | width: calc(100% - var(--sidebar-width)); 44 | padding-left: 0; 45 | 46 | @media (max-width: 959px) { 47 | width: calc(100% - var(--sidebar-width-mobile)); 48 | } 49 | 50 | @media (max-width: 719px) { 51 | width: 100%; 52 | } 53 | } 54 | } 55 | 56 | .theme-container.sidebar-open .content-wrapper { 57 | .sidebar-wrapper { 58 | @media (max-width: 719px) { 59 | transform: translateX(0); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /guide/.vuepress/styles/mixins/scrollbar.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | --c-bg-scrollbar: var(--c-bg); 3 | --c-bg-scrollbar-thumb: var(--theme-460); 4 | --c-bg-scrollbar-thumb-hover: var(--theme-430); 5 | overflow-y: auto; 6 | } 7 | 8 | @mixin customScrollbar($thin: true) { 9 | @if $thin { 10 | scrollbar-width: thin; 11 | } 12 | scrollbar-color: var(--c-bg-scrollbar-thumb) var(--c-bg-scrollbar); 13 | 14 | &::-webkit-scrollbar { 15 | @if $thin { 16 | width: 8px; 17 | height: 8px; 18 | } @else { 19 | width: 17px; 20 | height: 17px; 21 | } 22 | } 23 | 24 | &::-webkit-scrollbar-thumb { 25 | background-color: var(--c-bg-scrollbar-thumb); 26 | 27 | &:hover { 28 | background-color: var(--c-bg-scrollbar-thumb-hover); 29 | } 30 | } 31 | 32 | &::-webkit-scrollbar-track { 33 | background-color: var(--c-bg-scrollbar); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /guide/.vuepress/styles/navbar.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | --nav-background-color: var(--theme-530); 3 | --nav-links-color: var(--theme-600); 4 | --nav-links-hover-color: var(--theme-630); 5 | } 6 | 7 | html.guide-theme-blurple { 8 | --nav-background-color: var(--theme-560); 9 | } 10 | 11 | @mixin navLink() { 12 | color: var(--text-gray); 13 | font-size: 1em; 14 | margin: 0; 15 | padding: 0.875rem 0.75rem; 16 | border-width: 0; 17 | border-radius: 0.375rem; 18 | 19 | &:hover, 20 | &.router-link-active:hover { 21 | color: var(--text-gray); 22 | background-color: var(--nav-links-hover-color); 23 | } 24 | 25 | &.router-link-active { 26 | background-color: var(--nav-links-color); 27 | } 28 | } 29 | 30 | .navbar { 31 | background-color: var(--nav-background-color); 32 | display: flex; 33 | align-items: center; 34 | font-size: 0.875rem; 35 | padding: 0.5rem 0; 36 | border-width: 0; 37 | transition: none; 38 | 39 | .navbar-wrapper { 40 | display: flex; 41 | justify-content: space-between; 42 | width: 100%; 43 | max-width: var(--wrapper-max-width); 44 | margin: 0 auto; 45 | padding-left: 2rem; 46 | padding-right: 2rem; 47 | box-sizing: border-box; 48 | 49 | @media (max-width: 719px) { 50 | padding-left: 0.5rem; 51 | padding-right: 0.5rem; 52 | } 53 | } 54 | 55 | .toggle-sidebar-button { 56 | @include navLink(); 57 | position: unset; 58 | top: unset; 59 | left: unset; 60 | margin-left: 0.5rem; 61 | 62 | .sidebar-menu-icon { 63 | display: block; 64 | } 65 | 66 | .sidebar-close-icon { 67 | display: none; 68 | } 69 | } 70 | 71 | .toggle-dark-button { 72 | @include navLink(); 73 | margin-left: 2rem; 74 | opacity: 1; 75 | 76 | @media (max-width: 719px) { 77 | margin-left: 0.5rem; 78 | } 79 | 80 | .dark-icon { 81 | display: none; 82 | } 83 | 84 | .light-icon { 85 | display: block; 86 | } 87 | } 88 | 89 | .navbar-links-home { 90 | > a { 91 | display: inline-block; 92 | line-height: 1.4rem; 93 | } 94 | } 95 | 96 | a { 97 | @include navLink(); 98 | 99 | .icon.outbound { 100 | color: var(--text-gray); 101 | } 102 | } 103 | 104 | .navbar-links-wrapper { 105 | background-color: var(--nav-background-color); 106 | display: flex; 107 | align-items: center; 108 | justify-content: space-between; 109 | width: 100%; 110 | position: relative; 111 | top: 0; 112 | right: 0; 113 | padding-left: 0; 114 | 115 | .navbar-links-container { 116 | display: inline-flex; 117 | align-items: center; 118 | } 119 | 120 | .navbar-links { 121 | @media (max-width: 959px) { 122 | display: none; 123 | } 124 | 125 | .navbar-links-item { 126 | margin-left: 2rem; 127 | 128 | a { 129 | @include navLink(); 130 | } 131 | } 132 | } 133 | } 134 | } 135 | 136 | .theme-container.sidebar-open .navbar { 137 | .toggle-sidebar-button { 138 | background-color: var(--nav-links-color); 139 | 140 | &:hover { 141 | background-color: var(--nav-links-hover-color); 142 | } 143 | 144 | .sidebar-menu-icon { 145 | display: none; 146 | } 147 | 148 | .sidebar-close-icon { 149 | display: block; 150 | } 151 | } 152 | } 153 | 154 | html.dark .navbar { 155 | .toggle-dark-button { 156 | .dark-icon { 157 | display: block; 158 | } 159 | 160 | .light-icon { 161 | display: none; 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /guide/.vuepress/styles/palette.scss: -------------------------------------------------------------------------------- 1 | // code blocks 2 | $codeLang: 'html' 'js' 'json' 'md' 'sh' 'ts'; 3 | -------------------------------------------------------------------------------- /guide/.vuepress/styles/search.scss: -------------------------------------------------------------------------------- 1 | :root .DocSearch { 2 | --docsearch-text-color: #fff; 3 | --docsearch-searchbox-background: var(--theme-600); 4 | --docsearch-searchbox-focus-background: var(--theme-630); 5 | 6 | --docsearch-hit-active-color: var(--docsearch-text-color); 7 | 8 | --docsearch-key-shadow: 0 0 0 1px var(--theme-560); 9 | } 10 | 11 | html.dark .DocSearch { 12 | --docsearch-key-shadow: 0 0 0 1px var(--theme-560); 13 | } 14 | 15 | .navbar .navbar-search { 16 | display: inline-flex; 17 | } 18 | 19 | .DocSearch { 20 | &.DocSearch-Button { 21 | color: var(--text-gray); 22 | height: unset; 23 | width: 100%; 24 | font-size: 1em; 25 | margin-left: 2rem; 26 | padding: 0.875rem 0.75rem; 27 | border-width: 0; 28 | border-radius: 0.375rem; 29 | box-shadow: none; 30 | 31 | @media (max-width: 719px) { 32 | background-color: unset; 33 | width: unset; 34 | margin-left: 0.5rem; 35 | } 36 | 37 | &:hover { 38 | background-color: var(--docsearch-searchbox-focus-background); 39 | } 40 | 41 | &:hover, 42 | &:active, 43 | &:focus { 44 | box-shadow: none; 45 | } 46 | 47 | .DocSearch-Button-Container { 48 | .DocSearch-Search-Icon { 49 | width: 1rem; 50 | height: 1rem; 51 | 52 | @media (max-width: 719px) { 53 | width: 1.25rem; 54 | height: 1.25rem; 55 | } 56 | } 57 | 58 | .DocSearch-Button-Placeholder { 59 | padding: 0 1rem; 60 | 61 | @media (max-width: 750px) { 62 | display: inline-block; 63 | } 64 | 65 | @media (max-width: 719px) { 66 | display: none; 67 | } 68 | } 69 | } 70 | 71 | .DocSearch-Button-Keys { 72 | @media (max-width: 719px) { 73 | display: none; 74 | } 75 | 76 | .DocSearch-Button-Key { 77 | @media (max-width: 750px) { 78 | display: flex; 79 | } 80 | 81 | color: #fff; 82 | background: unset; 83 | height: unset; 84 | top: 0; 85 | padding-bottom: 0; 86 | 87 | &:last-of-type { 88 | margin-right: 0; 89 | } 90 | } 91 | } 92 | } 93 | 94 | .DocSearch-Modal { 95 | .DocSearch-Dropdown { 96 | overflow-y: auto; 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /guide/.vuepress/styles/sidebar.scss: -------------------------------------------------------------------------------- 1 | @import './mixins/scrollbar.scss'; 2 | 3 | :root { 4 | --active-sidebar-link: var(--theme-530); 5 | --active-sidebar-link-hover: var(--theme-500); 6 | } 7 | 8 | .sidebar { 9 | --c-bg-scrollbar: #ededed; 10 | top: 4rem; 11 | font-size: 14px; 12 | border-right-width: 0; 13 | transition: transform 0.2s ease; 14 | @include customScrollbar(); 15 | 16 | @media (max-width: 719px) { 17 | top: 0; 18 | padding-top: 4rem; 19 | } 20 | 21 | .navbar-links { 22 | @media (max-width: 959px) { 23 | display: block; 24 | padding: 0.5rem; 25 | } 26 | 27 | .navbar-links-item { 28 | @media (max-width: 959px) { 29 | margin-left: 0; 30 | padding-left: 1.25rem; 31 | 32 | & > a { 33 | margin-bottom: 0; 34 | border-bottom-width: 0; 35 | 36 | &:hover, 37 | &.router-link-active { 38 | color: var(--c-text-accent); 39 | border-bottom-width: 0; 40 | } 41 | } 42 | } 43 | } 44 | } 45 | 46 | & > .sidebar-links { 47 | padding: 1.25rem 0.5rem; 48 | 49 | @media (max-width: 719px) { 50 | padding: 0.5rem; 51 | } 52 | 53 | .sidebar-group { 54 | &.sidebar-group-collapsed { 55 | .sidebar-heading .menu-icon { 56 | transform: rotate(-90deg); 57 | } 58 | } 59 | } 60 | 61 | .sidebar-heading { 62 | font-weight: 700; 63 | font-size: 1em; 64 | border-left-width: 0; 65 | text-transform: uppercase; 66 | } 67 | 68 | button.sidebar-heading { 69 | background: none; 70 | display: inline-flex; 71 | align-items: center; 72 | border: none; 73 | cursor: pointer; 74 | 75 | .menu-icon { 76 | width: 1.5em; 77 | height: 1.5em; 78 | } 79 | } 80 | 81 | .sidebar-item:not(.sidebar-heading) { 82 | border-radius: 0.125rem 0 0 0.125rem; 83 | 84 | &.active { 85 | color: var(--active-sidebar-link); 86 | border-left-width: 0.25rem; 87 | border-left-color: var(--active-sidebar-link); 88 | } 89 | 90 | &:hover, 91 | &.active:hover { 92 | color: var(--active-sidebar-link-hover); 93 | border-left-color: var(--active-sidebar-link-hover); 94 | } 95 | } 96 | } 97 | } 98 | 99 | html.dark .sidebar { 100 | --c-bg-scrollbar: #171717; 101 | } 102 | -------------------------------------------------------------------------------- /guide/.vuepress/styles/theme-variables.scss: -------------------------------------------------------------------------------- 1 | // base colors 2 | :root { 3 | // Guide green colors 4 | --green-430: #5bc595; 5 | --green-460: #48be89; 6 | --green-500: #3eaf7c; 7 | --green-530: #379c6f; 8 | --green-560: #318961; 9 | --green-600: #2a7754; 10 | --green-630: #236447; 11 | 12 | // Discord colors 13 | --blurple-430: #707bf4; 14 | --blurple-460: #6571f3; 15 | --blurple-500: #5865f2; 16 | --blurple-530: #505cdc; 17 | --blurple-560: #4752c4; 18 | --blurple-600: #3c45a5; 19 | --blurple-630: #343b8f; 20 | 21 | // Theme colors 22 | --theme-430: var(--green-430); 23 | --theme-460: var(--green-460); 24 | --theme-500: var(--green-500); 25 | --theme-530: var(--green-530); 26 | --theme-560: var(--green-560); 27 | --theme-600: var(--green-600); 28 | --theme-630: var(--green-630); 29 | 30 | --text-gray: #e5e7eb; 31 | --c-headers: #111827; 32 | 33 | // Overrides: 34 | 35 | // brand colors 36 | --c-brand: var(--theme-560); 37 | --c-brand-light: var(--theme-460); 38 | 39 | // text colors 40 | --c-text: #374151; 41 | --c-text-accent: var(--c-brand-light); 42 | 43 | // custom container colors 44 | --c-tip: #42b983; 45 | --c-tip-bg: #e2f5ec; 46 | --c-tip-text: #215d42; 47 | --c-tip-title: #359469; 48 | --c-tip-link: var(--green-460); 49 | --c-warning: #e7c000; 50 | --c-warning-bg: rgba(255, 229, 100, 0.25); 51 | --c-warning-text: #6b5900; 52 | --c-warning-title: #b29400; 53 | --c-danger: #c00; 54 | --c-danger-bg: #ffe0e0; 55 | --c-danger-text: #600; 56 | --c-danger-title: #c00; 57 | 58 | // code blocks 59 | --code-hl-bg-color: #353b45; 60 | 61 | // font 62 | --font-family: system-ui, -apple-system, 'Segoe UI', Roboto, 63 | Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji'; 64 | 65 | // layout 66 | --navbar-height: 4rem; 67 | --content-width: 100%; 68 | --wrapper-max-width: 96rem; 69 | 70 | // miscellaneous 71 | --c-border: rgba(0, 0, 0, 0.05); 72 | } 73 | 74 | html.guide-theme-blurple { 75 | --theme-430: var(--blurple-430); 76 | --theme-460: var(--blurple-460); 77 | --theme-500: var(--blurple-500); 78 | --theme-530: var(--blurple-530); 79 | --theme-560: var(--blurple-560); 80 | --theme-600: var(--blurple-600); 81 | --theme-630: var(--blurple-630); 82 | 83 | // Overrides 84 | 85 | // brand colors 86 | --c-brand: var(--theme-560); 87 | --c-brand-light: var(--theme-460); 88 | 89 | // text colors 90 | --c-text-accent: var(--c-brand-light); 91 | } 92 | 93 | html.dark { 94 | --c-headers: #fff; 95 | --c-bg-scrollbar: var(--c-bg); 96 | 97 | // Overrides: 98 | 99 | // brand colors 100 | --c-brand: var(--theme-560); 101 | --c-brand-light: var(--theme-460); 102 | 103 | // background colors 104 | --c-bg: #1d1d1d; 105 | --c-bg-sidebar: #191919; 106 | 107 | // text colors 108 | --c-text: #d1d5db; 109 | --c-text-accent: var(--c-brand-light); 110 | 111 | // custom container colors 112 | --c-tip: #42b983; 113 | --c-tip-bg: rgba(66, 185, 131, 0.25); 114 | --c-tip-text: #a0ddc1; 115 | --c-tip-title: #66c99c; 116 | --c-tip-link: var(--green-530); 117 | --c-warning: #e7c000; 118 | --c-warning-bg: rgba(255, 229, 100, 0.25); 119 | --c-warning-text: #ffefa2; 120 | --c-warning-title: #ffd91d; 121 | --c-danger: #f66; 122 | --c-danger-bg: rgba(204, 0, 0, 0.25); 123 | --c-danger-text: #f66; 124 | --c-danger-title: #ff2929; 125 | 126 | // code blocks 127 | --code-hl-bg-color: #353b45; 128 | 129 | // miscellaneous 130 | --c-border: rgba(255, 255, 255, 0.05); 131 | } 132 | -------------------------------------------------------------------------------- /guide/.vuepress/templates/index.dev.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 37 | 38 | 39 |
40 | 41 | 42 | -------------------------------------------------------------------------------- /guide/.vuepress/templates/index.ssr.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 37 | 38 | 39 | 40 | 41 | 42 |
43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /guide/.vuepress/theme/components/Navbar.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 42 | -------------------------------------------------------------------------------- /guide/.vuepress/theme/components/Notification.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 37 | 38 | 93 | -------------------------------------------------------------------------------- /guide/.vuepress/theme/components/Notifications.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 38 | -------------------------------------------------------------------------------- /guide/.vuepress/theme/components/Sidebar.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 25 | -------------------------------------------------------------------------------- /guide/.vuepress/theme/components/ThemeOptions.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 28 | 29 | 70 | -------------------------------------------------------------------------------- /guide/.vuepress/theme/components/ToggleDarkModeButton.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 46 | -------------------------------------------------------------------------------- /guide/.vuepress/theme/components/ToggleSidebarButton.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 14 | -------------------------------------------------------------------------------- /guide/.vuepress/theme/components/UserSettings.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 24 | -------------------------------------------------------------------------------- /guide/.vuepress/theme/components/icons/Bars.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /guide/.vuepress/theme/components/icons/ChevronDown.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /guide/.vuepress/theme/components/icons/Close.vue: -------------------------------------------------------------------------------- 1 | 2 | 5 | -------------------------------------------------------------------------------- /guide/.vuepress/theme/components/icons/PartyPopper.vue: -------------------------------------------------------------------------------- 1 | 2 | 5 | -------------------------------------------------------------------------------- /guide/.vuepress/theme/composables/useColorTheme.ts: -------------------------------------------------------------------------------- 1 | export default function useColorTheme() { 2 | const setTheme = ({ colorTheme = 'default', persist = false }) => { 3 | const themes = ['blurple']; 4 | const { classList } = document.documentElement; 5 | const themesClasses = themes.map(theme => `guide-theme-${theme}`); 6 | 7 | if (colorTheme !== 'default' && !themes.includes(colorTheme)) { 8 | const oldTheme = localStorage.getItem('guide-color-theme'); 9 | colorTheme = themes.includes(oldTheme) ? oldTheme : 'default'; 10 | } 11 | 12 | if (persist) { 13 | localStorage.setItem('guide-color-theme', colorTheme); 14 | } 15 | 16 | if (colorTheme === 'default') return classList.remove(...themesClasses); 17 | classList.remove(...themesClasses.filter(themeClass => themeClass !== `guide-theme-${colorTheme}`)); 18 | classList.add(`guide-theme-${colorTheme}`); 19 | }; 20 | 21 | return { 22 | setTheme, 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /guide/.vuepress/theme/composables/useDarkMode.ts: -------------------------------------------------------------------------------- 1 | import { onMounted, onUnmounted, ref, watch } from 'vue'; 2 | import type { Ref } from 'vue'; 3 | 4 | export default function useDarkMode(): { isDarkMode: Ref } { 5 | const isDarkMode = ref(false); 6 | 7 | const updateDarkModeClass = (value = isDarkMode.value): void => { 8 | // set `class="dark"` on `` element 9 | const htmlEl = window?.document.querySelector('html'); 10 | htmlEl?.classList.toggle('dark', value); 11 | 12 | const systemDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; 13 | 14 | if ((value && systemDarkMode) || (!value && !systemDarkMode)) { 15 | localStorage.removeItem('guide-color-scheme'); 16 | } else if (value && !systemDarkMode) { 17 | localStorage.setItem('guide-color-scheme', 'dark'); 18 | } else if (!value && systemDarkMode) { 19 | localStorage.setItem('guide-color-scheme', 'light'); 20 | } 21 | } 22 | 23 | const mediaQuery = ref(null) 24 | const onMediaQueryChange = (event: MediaQueryListEvent): void => { 25 | isDarkMode.value = event.matches; 26 | }; 27 | 28 | onMounted(() => { 29 | // get stored preference and `prefers-color-scheme` media query and set the initial mode 30 | const userMode = localStorage.getItem('guide-color-scheme'); 31 | mediaQuery.value = window.matchMedia('(prefers-color-scheme: dark)'); 32 | isDarkMode.value = userMode === 'dark' || (userMode !== 'light' && mediaQuery.value.matches); 33 | 34 | // watch changes 35 | mediaQuery.value.addEventListener('change', onMediaQueryChange); 36 | watch(isDarkMode, updateDarkModeClass, { immediate: true }); 37 | }); 38 | 39 | onUnmounted(() => { 40 | mediaQuery.value?.removeEventListener('change', onMediaQueryChange); 41 | }); 42 | 43 | return { 44 | isDarkMode, 45 | }; 46 | } 47 | -------------------------------------------------------------------------------- /guide/.vuepress/theme/index.ts: -------------------------------------------------------------------------------- 1 | import { path } from '@vuepress/utils'; 2 | 3 | export default { 4 | name: 'vuepress-theme-discordjs-guide', 5 | 'extends': '@vuepress/theme-default', 6 | layouts: { 7 | Layout: path.resolve(__dirname, 'layouts/Layout.vue'), 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /guide/.vuepress/theme/layouts/Layout.vue: -------------------------------------------------------------------------------- 1 | 57 | 58 | 132 | -------------------------------------------------------------------------------- /guide/README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | If you're reading this, it probably means you want to learn how to make a bot with discord.js. Awesome! You've come to the right place. 4 | This guide will teach you things such as: 5 | - How to get a bot [up and running](/preparations/) from scratch; 6 | - How to properly [create](/creating-your-bot/), [organize](/creating-your-bot/command-handling.md), and expand on your commands; 7 | - In-depth explanations and examples regarding popular topics (e.g. [reactions](/popular-topics/reactions.md), [embeds](/popular-topics/embeds.md), [canvas](/popular-topics/canvas.md)); 8 | - Working with databases (e.g. [sequelize](/sequelize/) and [keyv](/keyv/)); 9 | - Getting started with [sharding](/sharding/); 10 | - And much more. 11 | 12 | This guide will also cover subjects like common errors and how to solve them, keeping your code clean, setting up a proper development environment, etc. 13 | Sounds good? Great! Let's get started, then. 14 | 15 | ## Before you begin... 16 | 17 | Alright, making a bot is cool and all, but there are some prerequisites to it. To create a bot with discord.js, you should have a fairly decent grasp of JavaScript itself. 18 | While you _can_ make a bot with very little JavaScript and programming knowledge, trying to do so without understanding the language first will only hinder you. You may get stuck on many uncomplicated issues, struggle with solutions to incredibly easy problems, and all-in-all end up frustrated. Sounds pretty annoying. 19 | 20 | If you don't know JavaScript but would like to learn about it, here are a few links to help get you started: 21 | 22 | * [Eloquent JavaScript, a free online book](http://eloquentjavascript.net/) 23 | * [JavaScript.info, a modern javascript tutorial](https://javascript.info/) 24 | * [Codecademy's interactive JavaScript course](https://www.codecademy.com/learn/introduction-to-javascript) 25 | * [Nodeschool, for both JavaScript and Node.js lessons](https://nodeschool.io/) 26 | * [MDN's JavaScript guide and full documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript) 27 | * [Google, your best friend](https://google.com) 28 | 29 | Take your pick, learn some JavaScript, and once you feel like you're confident enough to make a bot, come back and get started! 30 | 31 | 32 | Deploys by Netlify 33 | 34 | -------------------------------------------------------------------------------- /guide/additional-features/cooldowns.md: -------------------------------------------------------------------------------- 1 | # Cooldowns 2 | 3 | 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. 4 | 5 | ::: tip 6 | This section assumes you followed the [Command Handling](/creating-your-bot/command-handling.md) part. 7 | ::: 8 | 9 | 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. 10 | 11 | ```js {4} 12 | const { SlashCommandBuilder } = require('discord.js'); 13 | 14 | module.exports = { 15 | cooldown: 5, 16 | data: new SlashCommandBuilder() 17 | .setName('ping') 18 | .setDescription('Replies with Pong!'), 19 | async execute(interaction) { 20 | // ... 21 | }, 22 | }; 23 | ``` 24 | 25 | In your main file, initialize a [Collection](/additional-info/collections.md) to store cooldowns of commands: 26 | 27 | ```js 28 | client.cooldowns = new Collection(); 29 | ``` 30 | 31 | 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`. 32 | 33 | In your `InteractionCreate` event, add the following code: 34 | 35 | ```js {1,3-5,7-10,12-14} 36 | const { cooldowns } = interaction.client; 37 | 38 | if (!cooldowns.has(command.data.name)) { 39 | cooldowns.set(command.data.name, new Collection()); 40 | } 41 | 42 | const now = Date.now(); 43 | const timestamps = cooldowns.get(command.data.name); 44 | const defaultCooldownDuration = 3; 45 | const cooldownAmount = (command.cooldown ?? defaultCooldownDuration) * 1_000; 46 | 47 | if (timestamps.has(interaction.user.id)) { 48 | // ... 49 | } 50 | 51 | try { 52 | // ... 53 | } catch (error) { 54 | // ... 55 | } 56 | ``` 57 | 58 | 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: 59 | 60 | 1. `now`: The current timestamp. 61 | 2. `timestamps`: A reference to the Collection of user ids and timestamp key/value pairs for the triggered command. 62 | 3. `cooldownAmount`: The specified cooldown for the command, converted to milliseconds for straightforward calculation. If none is specified, this defaults to three seconds. 63 | 64 | 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. 65 | 66 | Continuing with your current setup, this is the complete `if` statement: 67 | 68 | ```js {2-7} 69 | if (timestamps.has(interaction.user.id)) { 70 | const expirationTime = timestamps.get(interaction.user.id) + cooldownAmount; 71 | 72 | if (now < expirationTime) { 73 | const expiredTimestamp = Math.round(expirationTime / 1_000); 74 | return interaction.reply({ content: `Please wait, you are on a cooldown for \`${command.data.name}\`. You can use it again .`, flags: MessageFlags.Ephemeral }); 75 | } 76 | } 77 | ``` 78 | 79 | 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. 80 | 81 | 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. 82 | 83 | ```js {5-6} 84 | if (timestamps.has(interaction.user.id)) { 85 | // ... 86 | } 87 | 88 | timestamps.set(interaction.user.id, now); 89 | setTimeout(() => timestamps.delete(interaction.user.id), cooldownAmount); 90 | ``` 91 | 92 | 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. 93 | 94 | ## Resulting code 95 | 96 | -------------------------------------------------------------------------------- /guide/additional-features/reloading-commands.md: -------------------------------------------------------------------------------- 1 | # Reloading Commands 2 | 3 | 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. 4 | 5 | ::: warning 6 | 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). 7 | ::: 8 | 9 | ::: tip 10 | This section assumes you followed the [Command Handling](/creating-your-bot/command-handling.md) part. 11 | ::: 12 | 13 | ::: warning 14 | The reload command ideally should not be used by every user. You should deploy it as a guild command in a private guild. 15 | ::: 16 | 17 | ```js 18 | const { SlashCommandBuilder } = require('discord.js'); 19 | 20 | module.exports = { 21 | data: new SlashCommandBuilder() 22 | .setName('reload') 23 | .setDescription('Reloads a command.') 24 | .addStringOption(option => 25 | option.setName('command') 26 | .setDescription('The command to reload.') 27 | .setRequired(true)), 28 | async execute(interaction) { 29 | // ... 30 | }, 31 | }; 32 | ``` 33 | 34 | First off, you need to check if the command you want to reload exists. You can do this check similarly to getting a command. 35 | 36 | ```js {4-9} 37 | module.exports = { 38 | // ... 39 | async execute(interaction) { 40 | const commandName = interaction.options.getString('command', true).toLowerCase(); 41 | const command = interaction.client.commands.get(commandName); 42 | 43 | if (!command) { 44 | return interaction.reply(`There is no command with name \`${commandName}\`!`); 45 | } 46 | }, 47 | }; 48 | ``` 49 | 50 | To build the correct file path, you will need the file name. You can use `command.data.name` for doing that. 51 | 52 | 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`: 53 | 54 | ```js {1,4-6} 55 | delete require.cache[require.resolve(`./${command.data.name}.js`)]; 56 | 57 | try { 58 | const newCommand = require(`./${command.data.name}.js`); 59 | interaction.client.commands.set(newCommand.data.name, newCommand); 60 | await interaction.reply(`Command \`${newCommand.data.name}\` was reloaded!`); 61 | } catch (error) { 62 | console.error(error); 63 | await interaction.reply(`There was an error while reloading a command \`${command.data.name}\`:\n\`${error.message}\``); 64 | } 65 | ``` 66 | 67 | 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. 68 | 69 | ## Resulting code 70 | 71 | 72 | -------------------------------------------------------------------------------- /guide/additional-info/collections.md: -------------------------------------------------------------------------------- 1 | # Collections 2 | 3 | discord.js comes with a utility class known as `Collection`. 4 | It extends JavaScript's native `Map` class, so it has all the `Map` features and more! 5 | 6 | ::: warning 7 | 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](/additional-info/es6-syntax.md) if you do not know what they are. 8 | ::: 9 | 10 | A `Map` allows for an association between unique keys and their values. 11 | For example, how can you transform every value or filter the entries in a `Map` easily? 12 | This is the point of the `Collection` class! 13 | 14 | ## Array-like Methods 15 | 16 | Many of the methods on `Collection` correspond to their namesake in `Array`. One of them is `find`: 17 | 18 | ```js 19 | // Assume we have an array of users and a collection of the same users. 20 | array.find(u => u.discriminator === '1000'); 21 | collection.find(u => u.discriminator === '1000'); 22 | ``` 23 | 24 | The interface of the callback function is very similar between the two. 25 | For arrays, callbacks usually pass the parameters `(value, index, array)`, where `value` is the value iterated to, 26 | `index` is the current index, and `array` is the array. For collections, you would have `(value, key, collection)`. 27 | Here, `value` is the same, but `key` is the key of the value, and `collection` is the collection itself instead. 28 | 29 | Methods that follow this philosophy of staying close to the `Array` interface are as follows: 30 | 31 | - `find` 32 | - `filter` - Note that this returns a `Collection` rather than an `Array`. 33 | - `map` - Yet this returns an `Array` of values instead of a `Collection`! 34 | - `every` 35 | - `some` 36 | - `reduce` 37 | - `concat` 38 | - `sort` 39 | 40 | ## Converting to Array 41 | 42 | 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`). 43 | 44 | ```js 45 | // For values. 46 | Array.from(collection.values()); 47 | [...collection.values()]; 48 | 49 | // For keys. 50 | Array.from(collection.keys()); 51 | [...collection.keys()]; 52 | 53 | // For [key, value] pairs. 54 | Array.from(collection); 55 | [...collection]; 56 | ``` 57 | 58 | ::: warning 59 | 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. 60 | ::: 61 | 62 | ## Extra Utilities 63 | 64 | Some methods are not from `Array` and are instead entirely new to standard JavaScript. 65 | 66 | ```js 67 | // A random value. 68 | collection.random(); 69 | 70 | // The first value. 71 | collection.first(); 72 | 73 | // The first 5 values. 74 | collection.first(5); 75 | 76 | // Similar to `first`, but from the end. 77 | collection.last(); 78 | collection.last(2); 79 | 80 | // Removes anything that meets the condition from the collection. 81 | // Sort of like `filter`, but in-place. 82 | collection.sweep(user => user.username === 'Bob'); 83 | ``` 84 | 85 | A more complicated method is `partition`, which splits a single Collection into two new Collections based on the provided function. 86 | You can think of it as two `filter`s, but done at the same time: 87 | 88 | ```js 89 | // `bots` is a Collection of users where their `bot` property was true. 90 | // `humans` is a Collection where the property was false instead! 91 | const [bots, humans] = collection.partition(u => u.bot); 92 | 93 | // Both return true. 94 | bots.every(b => b.bot); 95 | humans.every(h => !h.bot); 96 | ``` 97 | -------------------------------------------------------------------------------- /guide/additional-info/images/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discordjs/guide/1fa24806d4e0c24095f3b2cf21fa746d2fc66d50/guide/additional-info/images/search.png -------------------------------------------------------------------------------- /guide/additional-info/images/send.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discordjs/guide/1fa24806d4e0c24095f3b2cf21fa746d2fc66d50/guide/additional-info/images/send.png -------------------------------------------------------------------------------- /guide/additional-info/notation.md: -------------------------------------------------------------------------------- 1 | # Understanding notation 2 | 3 | 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. 4 | 5 | ::: tip 6 | 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. 7 | ::: 8 | 9 | ## Classes 10 | 11 | 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. 12 | 13 | The notation `` means an instance of the `Class` class. For example, a snippet like `.reply('Hello')` is asking you to replace `` with some value that is an instance of `BaseInteraction`, e.g. `interaction.reply('Hello')`. It could also just be a placeholder, e.g., `` would mean a placeholder for some ID. 14 | 15 | 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: 16 | 17 | - `BaseInteraction#user` means that you should refer to the `user` property on a `BaseInteraction`. 18 | - `TextChannel#send` means that you should refer to the `send` method on a `TextChannel`. 19 | - `Client#interactionCreate` means that you should refer to the `interactionCreate` event on a `Client`. 20 | 21 | ::: tip 22 | Remember that this notation is not valid JavaScript; it is a shorthand to refer to a specific piece of code. 23 | ::: 24 | 25 | 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. 26 | 27 | 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., `#send` or `Util#resolveColor`. `` 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. 28 | 29 | As an example, the documentation's search feature uses this notation. 30 | 31 | ![Docs search](./images/search.png) 32 | 33 | Notice the use of the `.` operator for the static method, `Role.comparePositions` and the `#` notation for the method, `Role#comparePositionsTo`. 34 | 35 | ## Types 36 | 37 | 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. 38 | 39 | The symbol `*` means any type. For example, methods that return `*` mean that they can return anything, and a parameter of type `*` can be anything. 40 | 41 | 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. 42 | 43 | The expression `T[]` means an array of `T`. You can sometimes see multiple brackets `[]`, indicating that the array is multi-dimensional, e.g., `string[][]`. 44 | 45 | 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`. 46 | 47 | 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. 48 | 49 | The angle brackets `<>` are used for generic types or parameterized types, signifying a type that uses another type(s). The notation looks like `A` 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`, you can think of an `A` containing `B`. Examples: 50 | 51 | - `Array` means an array of strings. 52 | - `Promise` means a `Promise` that contains a `User`. 53 | - `Array>` would be an array of `Promise`s, each containing a `User` or a `GuildMember`. 54 | - `Collection` would be a `Collection`, containing key-value pairs where the keys are `Snowflake`s, and the values are `User`s. 55 | 56 | ![TextChannel#send on the docs](./images/send.png) 57 | 58 | In this piece of the docs, you can see two type signatures, `string`, `MessagePayload`, or `MessageOptions`, and `Promise<(Message|Array)>`. The meaning of the word "or" here is the same as `|`. 59 | -------------------------------------------------------------------------------- /guide/creating-your-bot/main-file.md: -------------------------------------------------------------------------------- 1 | # Creating the main file 2 | 3 | ::: tip 4 | This page assumes you've already prepared the [configuration files](/creating-your-bot/#creating-configuration-files) from the previous page. We're using the `config.json` approach, however feel free to substitute your own! 5 | ::: 6 | 7 | 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. 8 | 9 | Here's the base code to get you started: 10 | 11 | ```js 12 | // Require the necessary discord.js classes 13 | const { Client, Events, GatewayIntentBits } = require('discord.js'); 14 | const { token } = require('./config.json'); 15 | 16 | // Create a new client instance 17 | const client = new Client({ intents: [GatewayIntentBits.Guilds] }); 18 | 19 | // When the client is ready, run this code (only once). 20 | // The distinction between `client: Client` and `readyClient: Client` is important for TypeScript developers. 21 | // It makes some properties non-nullable. 22 | client.once(Events.ClientReady, readyClient => { 23 | console.log(`Ready! Logged in as ${readyClient.user.tag}`); 24 | }); 25 | 26 | // Log in to Discord with your client's token 27 | client.login(token); 28 | ``` 29 | 30 | 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. 31 | 32 | ::: tip 33 | The term "guild" is used by the Discord API and in discord.js to refer to a Discord server. 34 | ::: 35 | 36 | 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 on the [Intents topic](/popular-topics/intents). 37 | 38 | ## Running your application 39 | 40 | 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](/creating-your-bot/slash-commands.md) to develop your bot's functionality. 41 | 42 | ::: tip 43 | 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! 44 | 45 | After closing the process with `Ctrl + C`, 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. 46 | ::: 47 | 48 | #### Resulting code 49 | 50 | -------------------------------------------------------------------------------- /guide/images/branding/banner-blurple-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discordjs/guide/1fa24806d4e0c24095f3b2cf21fa746d2fc66d50/guide/images/branding/banner-blurple-small.png -------------------------------------------------------------------------------- /guide/images/branding/banner-blurple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discordjs/guide/1fa24806d4e0c24095f3b2cf21fa746d2fc66d50/guide/images/branding/banner-blurple.png -------------------------------------------------------------------------------- /guide/images/branding/banner-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discordjs/guide/1fa24806d4e0c24095f3b2cf21fa746d2fc66d50/guide/images/branding/banner-small.png -------------------------------------------------------------------------------- /guide/images/branding/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discordjs/guide/1fa24806d4e0c24095f3b2cf21fa746d2fc66d50/guide/images/branding/banner.png -------------------------------------------------------------------------------- /guide/images/branding/logo-blurple-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discordjs/guide/1fa24806d4e0c24095f3b2cf21fa746d2fc66d50/guide/images/branding/logo-blurple-favicon.png -------------------------------------------------------------------------------- /guide/images/branding/logo-blurple-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discordjs/guide/1fa24806d4e0c24095f3b2cf21fa746d2fc66d50/guide/images/branding/logo-blurple-small.png -------------------------------------------------------------------------------- /guide/images/branding/logo-blurple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discordjs/guide/1fa24806d4e0c24095f3b2cf21fa746d2fc66d50/guide/images/branding/logo-blurple.png -------------------------------------------------------------------------------- /guide/images/branding/logo-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discordjs/guide/1fa24806d4e0c24095f3b2cf21fa746d2fc66d50/guide/images/branding/logo-favicon.png -------------------------------------------------------------------------------- /guide/images/branding/logo-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discordjs/guide/1fa24806d4e0c24095f3b2cf21fa746d2fc66d50/guide/images/branding/logo-small.png -------------------------------------------------------------------------------- /guide/images/branding/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discordjs/guide/1fa24806d4e0c24095f3b2cf21fa746d2fc66d50/guide/images/branding/logo.png -------------------------------------------------------------------------------- /guide/improving-dev-environment/pm2.md: -------------------------------------------------------------------------------- 1 | # Managing your bot process with PM2 2 | 3 | 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. 4 | 5 | ## Installation 6 | 7 | You can install PM2 via the following command: 8 | 9 | :::: code-group 10 | ::: code-group-item npm 11 | ```sh:no-line-numbers 12 | npm install --global pm2 13 | ``` 14 | ::: 15 | ::: code-group-item yarn 16 | ```sh:no-line-numbers 17 | yarn global add pm2 18 | ``` 19 | ::: 20 | ::: code-group-item pnpm 21 | ```sh:no-line-numbers 22 | pnpm add --global pm2 23 | ``` 24 | ::: 25 | ::: code-group-item bun 26 | ```sh:no-line-numbers 27 | bun add --global pm2 28 | ``` 29 | ::: 30 | :::: 31 | 32 | ## Starting your app 33 | 34 | 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: 35 | 36 | ```sh:no-line-numbers 37 | pm2 start your-app-name.js 38 | ``` 39 | 40 | ### Additional notes 41 | 42 | The `pm2 start` script allows for more optional command-line arguments. 43 | 44 | - `--name`: This allows you to set the name of your process when listing it up with `pm2 list` or `pm2 monit`: 45 | 46 | ```sh:no-line-numbers 47 | pm2 start your-app-name.js --name "Some cool name" 48 | ``` 49 | 50 | - `--watch`: This option will automatically restart your process as soon as a file change is detected, which can be useful for development environments: 51 | 52 | ```bash 53 | pm2 start your-app-name.js --watch 54 | ``` 55 | 56 | ::: tip 57 | 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/). 58 | ::: 59 | 60 | 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. 61 | 62 | 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: 63 | 64 | ```sh:no-line-numbers 65 | pm2 stop your-app-name.js 66 | ``` 67 | 68 | ## Setting up booting with your system 69 | 70 | 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). 71 | 72 | The initial steps differ per OS. In this guide, we'll cover those for Windows and Linux/macOS. 73 | 74 | ### Initial steps for Windows 75 | 76 | It is recommended to use `pm2-installer`. Follow the steps over at their [`GitHub`](https://github.com/jessety/pm2-installer). 77 | 78 | ### Initial steps for Linux/macOS 79 | 80 | You'll need a start script, which you can get by running the following command: 81 | 82 | ```sh:no-line-numbers 83 | # Detects the available init system, generates the config, and enables startup system 84 | pm2 startup 85 | ``` 86 | 87 | Or, if you want to specify your machine manually, select one of the options with the command: 88 | 89 | ```sh:no-line-numbers 90 | pm2 startup [ubuntu | ubuntu14 | ubuntu12 | centos | centos6 | arch | oracle | amazon | macos | darwin | freesd | systemd | systemv | upstart | launchd | rcd | openrc] 91 | ``` 92 | 93 | 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. 94 | 95 | **Example output for an Ubuntu user:** 96 | 97 | ```sh:no-line-numbers 98 | [PM2] You have to run this command as root. Execute the following command: 99 | sudo su -c "env PATH=$PATH:/home/user/.nvm/versions/node/v8.9/bin pm2 startup ubuntu -u user --hp /home/user 100 | ``` 101 | 102 | After running that command, you can continue to the next step. 103 | 104 | ### Saving the current process list 105 | 106 | To save the current process list so it will automatically get started after a restart, run the following command: 107 | 108 | ```sh:no-line-numbers 109 | pm2 save 110 | ``` 111 | 112 | To disable this, you can run the following command: 113 | 114 | ```sh:no-line-numbers 115 | pm2 unstartup 116 | ``` 117 | -------------------------------------------------------------------------------- /guide/interactions/context-menus.md: -------------------------------------------------------------------------------- 1 | # Context Menus 2 | 3 | Context Menus are application commands which appear when right clicking or tapping a user or a message, in the Apps submenu. 4 | 5 | ::: tip 6 | This page is a follow-up to the [slash commands](/slash-commands/advanced-creation.md) section. Please carefully read those pages first so that you can understand the methods used in this section. 7 | ::: 8 | 9 | ## Registering context menu commands 10 | 11 | To create a context menu command, use the class. You can then set the type of the context menu (user or message) using the `setType()` method. 12 | 13 | ```js 14 | const { ContextMenuCommandBuilder, ApplicationCommandType } = require('discord.js'); 15 | 16 | const data = new ContextMenuCommandBuilder() 17 | .setName('User Information') 18 | .setType(ApplicationCommandType.User); 19 | ``` 20 | 21 | ## Receiving context menu command interactions 22 | 23 | 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: 24 | 25 | ```js {2} 26 | client.on(Events.InteractionCreate, interaction => { 27 | if (!interaction.isUserContextMenuCommand()) return; 28 | console.log(interaction); 29 | }); 30 | ``` 31 | 32 | ## Extracting data from context menus 33 | 34 | For user context menus, you can get the targeted user by accessing the `targetUser` or `targetMember` property from the . 35 | 36 | For message context menus, you can get the targeted message by accessing the `targetMessage` property from the . 37 | 38 | ```js {4} 39 | client.on(Events.InteractionCreate, interaction => { 40 | if (!interaction.isUserContextMenuCommand()) return; 41 | // Get the User's username from context menu 42 | const { username } = interaction.targetUser; 43 | console.log(username); 44 | }); 45 | ``` 46 | 47 | ## Notes 48 | 49 | - Context menu commands cannot have subcommands or any options. 50 | - 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. 51 | - Context menu command permissions also function the same as slash commands. Refer to our [slash command permissions](/slash-commands/permissions) guide for more information. 52 | -------------------------------------------------------------------------------- /guide/interactions/images/modal-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discordjs/guide/1fa24806d4e0c24095f3b2cf21fa746d2fc66d50/guide/interactions/images/modal-example.png -------------------------------------------------------------------------------- /guide/interactions/images/selectephem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discordjs/guide/1fa24806d4e0c24095f3b2cf21fa746d2fc66d50/guide/interactions/images/selectephem.png -------------------------------------------------------------------------------- /guide/message-components/action-rows.md: -------------------------------------------------------------------------------- 1 | # Action rows 2 | 3 | With the components API, you can create interactive message components to enhance the functionality of your slash commands. To get started with this, the first component type you'll need to understand is the action row. To send any type of component, it **must** be placed in an action row. 4 | 5 | Action rows are a fairly simple form of layout component. A message may contain up to five rows, each of which has a "width" of five units. This can be thought of as a flexible 5x5 grid. A button will consume one unit of width in a row, while a select menu will consume the whole five units of width. At this time, these are the only types of components that can be sent in a message. 6 | 7 | :::warning 8 | The "width units" referred to are not fixed - the actual width of each individual button will be dynamic based on its label contents. 9 | ::: 10 | 11 | ## Building action rows 12 | 13 | To create an action row, use the class and the method to add buttons or a select menu. 14 | 15 | ```js 16 | const row = new ActionRowBuilder() 17 | .addComponents(component); 18 | ``` 19 | 20 | ::: warning 21 | 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 . 22 | 23 | ```diff 24 | - new ActionRowBuilder() 25 | + new ActionRowBuilder() 26 | ``` 27 | ::: 28 | 29 | ## Sending action rows 30 | 31 | Once one or many components are inside your row(s), send them in the `components` property of your (extends ). 32 | 33 | ```js {4} 34 | const row = new ActionRowBuilder() 35 | .addComponents(component); 36 | 37 | await interaction.reply({ components: [row] }); 38 | ``` 39 | 40 | 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. 41 | -------------------------------------------------------------------------------- /guide/message-components/images/select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discordjs/guide/1fa24806d4e0c24095f3b2cf21fa746d2fc66d50/guide/message-components/images/select.png -------------------------------------------------------------------------------- /guide/miscellaneous/cache-customization.md: -------------------------------------------------------------------------------- 1 | # Cache customization 2 | 3 | Sometimes, you would like to be able to customize discord.js's caching behavior in order to reduce memory usage. 4 | To this end, discord.js provides you with two ways to do so: 5 | 6 | 1. Limiting the size of caches. 7 | 2. Periodically removing old items from caches. 8 | 9 | ::: danger 10 | Customization of caching behavior is an advanced topic. 11 | It is very easy to introduce errors if your custom cache is not working as expected. 12 | ::: 13 | 14 | ## Limiting caches 15 | 16 | When creating a new , you can limit the size of caches, which are specific to certain managers, using the `makeCache` option. Generally speaking, almost all your customizations can be done via the helper functions from the class. 17 | 18 | Below is the default settings, which will limit message caches. 19 | 20 | ```js 21 | const { Client, Options } = require('discord.js'); 22 | 23 | const client = new Client({ 24 | makeCache: Options.cacheWithLimits(Options.DefaultMakeCacheSettings), 25 | }); 26 | ``` 27 | 28 | To change the cache behaviors for a type of manager, add it on top of the default settings. For example, you can make caches for reactions limited to 0 items i.e. the client won't cache reactions at all: 29 | 30 | ```js 31 | const client = new Client({ 32 | makeCache: Options.cacheWithLimits({ 33 | ...Options.DefaultMakeCacheSettings, 34 | ReactionManager: 0, 35 | }), 36 | }); 37 | ``` 38 | 39 | ::: danger 40 | As noted in the documentation, customizing `GuildManager`, `ChannelManager`, `GuildChannelManager`, `RoleManager`, or `PermissionOverwriteManager` is unsupported! Functionality will break with any kind of customization. 41 | ::: 42 | 43 | We can further customize this by passing options to , a special kind of collection that limits the number of items. In the example below, the client is configured so that: 44 | 45 | 1. Only 200 guild members maximum may be cached per `GuildMemberManager` (essentially, per guild). 46 | 2. We never remove a member if it is the client. This is especially important, so that the client can function correctly in guilds. 47 | 48 | ```js 49 | const client = new Client({ 50 | makeCache: Options.cacheWithLimits({ 51 | ...Options.DefaultMakeCacheSettings, 52 | ReactionManager: 0, 53 | GuildMemberManager: { 54 | maxSize: 200, 55 | keepOverLimit: member => member.id === member.client.user.id, 56 | }, 57 | }), 58 | }); 59 | ``` 60 | 61 | ## Sweeping caches 62 | 63 | In addition to limiting caches, you can also periodically sweep and remove old items from caches. When creating a new , you can customize the `sweepers` option. 64 | 65 | Below is the default settings, which will occasionally sweep threads. 66 | 67 | ```js 68 | const { Client, Options } = require('discord.js'); 69 | 70 | const client = new Client({ 71 | sweepers: Options.DefaultSweeperSettings, 72 | }); 73 | ``` 74 | 75 | To change the sweep behavior, you specify the type of cache to sweep () and the options for sweeping (). If the type of cache has a lifetime associated with it, such as invites, messages, or threads, then you can set the `lifetime` option to sweep items older than specified. Otherwise, you can set the `filter` option for any type of cache, which will select the items to sweep. 76 | 77 | ```js 78 | const client = new Client({ 79 | sweepers: { 80 | ...Options.DefaultSweeperSettings, 81 | messages: { 82 | interval: 3_600, // Every hour. 83 | lifetime: 1_800, // Remove messages older than 30 minutes. 84 | }, 85 | users: { 86 | interval: 3_600, // Every hour. 87 | filter: () => user => user.bot && user.id !== user.client.user.id, // Remove all bots. 88 | }, 89 | }, 90 | }); 91 | ``` 92 | 93 | ::: tip 94 | Take a look at the documentation for which types of cache you can sweep. 95 | Also look to see exactly what lifetime means for invites, messages, and threads! 96 | ::: 97 | -------------------------------------------------------------------------------- /guide/miscellaneous/images/chalk-red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discordjs/guide/1fa24806d4e0c24095f3b2cf21fa746d2fc66d50/guide/miscellaneous/images/chalk-red.png -------------------------------------------------------------------------------- /guide/miscellaneous/images/chalk-ugly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discordjs/guide/1fa24806d4e0c24095f3b2cf21fa746d2fc66d50/guide/miscellaneous/images/chalk-ugly.png -------------------------------------------------------------------------------- /guide/miscellaneous/images/winston.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discordjs/guide/1fa24806d4e0c24095f3b2cf21fa746d2fc66d50/guide/miscellaneous/images/winston.png -------------------------------------------------------------------------------- /guide/oauth2/images/add-redirects.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discordjs/guide/1fa24806d4e0c24095f3b2cf21fa746d2fc66d50/guide/oauth2/images/add-redirects.png -------------------------------------------------------------------------------- /guide/oauth2/images/authorize-app-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discordjs/guide/1fa24806d4e0c24095f3b2cf21fa746d2fc66d50/guide/oauth2/images/authorize-app-page.png -------------------------------------------------------------------------------- /guide/oauth2/images/generate-url.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discordjs/guide/1fa24806d4e0c24095f3b2cf21fa746d2fc66d50/guide/oauth2/images/generate-url.png -------------------------------------------------------------------------------- /guide/oauth2/images/oauth2-app-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discordjs/guide/1fa24806d4e0c24095f3b2cf21fa746d2fc66d50/guide/oauth2/images/oauth2-app-page.png -------------------------------------------------------------------------------- /guide/popular-topics/audit-logs.md: -------------------------------------------------------------------------------- 1 | # Working with Audit Logs 2 | 3 | ## A Quick Background 4 | 5 | Audit logs are an excellent moderation tool offered by Discord to know what happened in a server and usually by whom. Making use of audit logs requires the `ViewAuditLog` permission. Audit logs may be fetched on a server, or they may be received via the gateway event `guildAuditLogEntryCreate` which requires the `GuildModeration` intent. 6 | 7 | There are quite a few cases where you may use audit logs. This guide will limit itself to the most common use cases. Feel free to consult the [relevant Discord API page](https://discord.com/developers/docs/resources/audit-log) for more information. 8 | 9 | Keep in mind that these examples explore a straightforward case and are by no means exhaustive. Their purpose is to teach you how audit logs work, and expansion of these examples is likely needed to suit your specific use case. 10 | 11 | ## Fetching Audit Logs 12 | 13 | Let's start by glancing at the method and how to work with it. Like many discord.js methods, it returns a [`Promise`](/additional-info/async-await.md) containing the object. This object has one property, `entries`, which holds a [`Collection`](/additional-info/collections.md) of objects, and consequently, the information you want to retrieve. 14 | 15 | Here is the most basic fetch to look at some entries. 16 | 17 | ```js 18 | const fetchedLogs = await guild.fetchAuditLogs(); 19 | const firstEntry = fetchedLogs.entries.first(); 20 | ``` 21 | 22 | Simple, right? Now, let's look at utilizing its options: 23 | 24 | ```js 25 | const { AuditLogEvent } = require('discord.js'); 26 | 27 | const fetchedLogs = await guild.fetchAuditLogs({ 28 | type: AuditLogEvent.InviteCreate, 29 | limit: 1, 30 | }); 31 | 32 | const firstEntry = fetchedLogs.entries.first(); 33 | ``` 34 | 35 | This will return the first entry where an invite was created. You used `limit: 1` here to specify only one entry. 36 | 37 | ## Receiving Audit Logs 38 | 39 | Audit logs may be received via the gateway event `guildAuditLogEntryCreate`. This is the best way to receive audit logs if you want to monitor them. As soon as a message is deleted, or an invite or emoji is created, your application will receive an instance of this event. A common use case is to find out _who_ did the action that caused the audit log event to happen. 40 | 41 | ### Who deleted a message? 42 | 43 | One of the most common use cases for audit logs is understanding who deleted a message in a Discord server. If a user deleted another user's message, you can find out who did that as soon as you receive the corresponding audit log event. 44 | 45 | ```js 46 | const { AuditLogEvent, Events } = require('discord.js'); 47 | 48 | client.on(Events.GuildAuditLogEntryCreate, async auditLog => { 49 | // Define your variables. 50 | // The extra information here will be the channel. 51 | const { action, extra: channel, executorId, targetId } = auditLog; 52 | 53 | // Check only for deleted messages. 54 | if (action !== AuditLogEvent.MessageDelete) return; 55 | 56 | // Ensure the executor is cached. 57 | const executor = await client.users.fetch(executorId); 58 | 59 | // Ensure the author whose message was deleted is cached. 60 | const target = await client.users.fetch(targetId); 61 | 62 | // Log the output. 63 | console.log(`A message by ${target.tag} was deleted by ${executor.tag} in ${channel}.`); 64 | }); 65 | ``` 66 | 67 | With this, you now have a very simple logger telling you who deleted a message authored by another person. 68 | 69 | ### Who kicked a user? 70 | 71 | This is very similar to the example above. 72 | 73 | ```js 74 | const { AuditLogEvent, Events } = require('discord.js'); 75 | 76 | client.on(Events.GuildAuditLogEntryCreate, async auditLog => { 77 | // Define your variables. 78 | const { action, executorId, targetId } = auditLog; 79 | 80 | // Check only for kicked users. 81 | if (action !== AuditLogEvent.MemberKick) return; 82 | 83 | // Ensure the executor is cached. 84 | const executor = await client.users.fetch(executorId); 85 | 86 | // Ensure the kicked guild member is cached. 87 | const kickedUser = await client.users.fetch(targetId); 88 | 89 | // Now you can log the output! 90 | console.log(`${kickedUser.tag} was kicked by ${executor.tag}.`); 91 | }); 92 | ``` 93 | 94 | If you want to check who banned a user, it's the same example as above except the `action` should be `AuditLogEvent.MemberBanAdd`. You can check the rest of the types over at the [discord-api-types documentation](https://discord-api-types.dev/api/discord-api-types-v10/enum/AuditLogEvent). 95 | -------------------------------------------------------------------------------- /guide/popular-topics/formatters.md: -------------------------------------------------------------------------------- 1 | # Formatters 2 | 3 | discord.js provides the package which contains a variety of utilities you can use when writing your Discord bot. 4 | 5 | ## Basic Markdown 6 | 7 | These functions format strings into all the different Markdown styles supported by Discord. 8 | 9 | ```js 10 | const { blockQuote, bold, italic, quote, spoiler, strikethrough, underline, subtext } = require('discord.js'); 11 | const string = 'Hello!'; 12 | 13 | const boldString = bold(string); 14 | const italicString = italic(string); 15 | const strikethroughString = strikethrough(string); 16 | const underlineString = underline(string); 17 | const spoilerString = spoiler(string); 18 | const quoteString = quote(string); 19 | const blockquoteString = blockQuote(string); 20 | const subtextString = subtext(string); 21 | ``` 22 | 23 | ## Links 24 | 25 | There are also two functions to format hyperlinks. `hyperlink()` will format the URL into a masked markdown link, and `hideLinkEmbed()` will wrap the URL in `<>`, preventing it from embedding. 26 | 27 | ```js 28 | const { hyperlink, hideLinkEmbed } = require('discord.js'); 29 | const url = 'https://discord.js.org/'; 30 | 31 | const link = hyperlink('discord.js', url); 32 | const hiddenEmbed = hideLinkEmbed(url); 33 | ``` 34 | 35 | ## Code blocks 36 | 37 | You can use `inlineCode()` and `codeBlock()` to turn a string into an inline code block or a regular code block with or without syntax highlighting. 38 | 39 | ```js 40 | const { inlineCode, codeBlock } = require('discord.js'); 41 | const jsString = 'const value = true;'; 42 | 43 | const inline = inlineCode(jsString); 44 | const codeblock = codeBlock(jsString); 45 | const highlighted = codeBlock('js', jsString); 46 | ``` 47 | 48 | ## Timestamps 49 | 50 | With `time()`, you can format Unix timestamps and dates into a Discord time string. 51 | 52 | ```js 53 | const { time, TimestampStyles } = require('discord.js'); 54 | const date = new Date(); 55 | 56 | const timeString = time(date); 57 | const relative = time(date, TimestampStyles.RelativeTime); 58 | ``` 59 | 60 | ## Mentions 61 | 62 | `userMention()`, `channelMention()`, and `roleMention()` all exist to format Snowflakes into mentions. 63 | 64 | ```js 65 | const { channelMention, roleMention, userMention } = require('discord.js'); 66 | const id = '123456789012345678'; 67 | 68 | const channel = channelMention(id); 69 | const role = roleMention(id); 70 | const user = userMention(id); 71 | ``` 72 | -------------------------------------------------------------------------------- /guide/popular-topics/images/canvas-add-name.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discordjs/guide/1fa24806d4e0c24095f3b2cf21fa746d2fc66d50/guide/popular-topics/images/canvas-add-name.png -------------------------------------------------------------------------------- /guide/popular-topics/images/canvas-after-text-wrap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discordjs/guide/1fa24806d4e0c24095f3b2cf21fa746d2fc66d50/guide/popular-topics/images/canvas-after-text-wrap.png -------------------------------------------------------------------------------- /guide/popular-topics/images/canvas-before-text-wrap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discordjs/guide/1fa24806d4e0c24095f3b2cf21fa746d2fc66d50/guide/popular-topics/images/canvas-before-text-wrap.png -------------------------------------------------------------------------------- /guide/popular-topics/images/canvas-circle-avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discordjs/guide/1fa24806d4e0c24095f3b2cf21fa746d2fc66d50/guide/popular-topics/images/canvas-circle-avatar.png -------------------------------------------------------------------------------- /guide/popular-topics/images/canvas-final-result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discordjs/guide/1fa24806d4e0c24095f3b2cf21fa746d2fc66d50/guide/popular-topics/images/canvas-final-result.png -------------------------------------------------------------------------------- /guide/popular-topics/images/canvas-plain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discordjs/guide/1fa24806d4e0c24095f3b2cf21fa746d2fc66d50/guide/popular-topics/images/canvas-plain.png -------------------------------------------------------------------------------- /guide/popular-topics/images/canvas-preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discordjs/guide/1fa24806d4e0c24095f3b2cf21fa746d2fc66d50/guide/popular-topics/images/canvas-preview.png -------------------------------------------------------------------------------- /guide/popular-topics/images/canvas-square-avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discordjs/guide/1fa24806d4e0c24095f3b2cf21fa746d2fc66d50/guide/popular-topics/images/canvas-square-avatar.png -------------------------------------------------------------------------------- /guide/popular-topics/images/canvas-stretched-avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discordjs/guide/1fa24806d4e0c24095f3b2cf21fa746d2fc66d50/guide/popular-topics/images/canvas-stretched-avatar.png -------------------------------------------------------------------------------- /guide/popular-topics/images/canvas.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discordjs/guide/1fa24806d4e0c24095f3b2cf21fa746d2fc66d50/guide/popular-topics/images/canvas.jpg -------------------------------------------------------------------------------- /guide/popular-topics/images/creating-webhooks-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discordjs/guide/1fa24806d4e0c24095f3b2cf21fa746d2fc66d50/guide/popular-topics/images/creating-webhooks-1.png -------------------------------------------------------------------------------- /guide/popular-topics/images/creating-webhooks-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discordjs/guide/1fa24806d4e0c24095f3b2cf21fa746d2fc66d50/guide/popular-topics/images/creating-webhooks-2.png -------------------------------------------------------------------------------- /guide/popular-topics/images/creating-webhooks-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discordjs/guide/1fa24806d4e0c24095f3b2cf21fa746d2fc66d50/guide/popular-topics/images/creating-webhooks-3.png -------------------------------------------------------------------------------- /guide/popular-topics/images/wallpaper.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discordjs/guide/1fa24806d4e0c24095f3b2cf21fa746d2fc66d50/guide/popular-topics/images/wallpaper.jpg -------------------------------------------------------------------------------- /guide/popular-topics/partials.md: -------------------------------------------------------------------------------- 1 | # Partial Structures 2 | 3 | Partial Structures were introduced to the library in version 12 and are optionally received whenever there is insufficient data to emit the client event with a fully intact discord.js structure. They are (as the name suggests) incomplete, and you cannot expect them to have any information besides their ID. All other properties and methods on this object should be considered invalid and defunct. Before this feature, discord.js client events would not emit if one of the necessary structures could not be built with sufficient data to guarantee a fully functional structure. If you do not opt into partials, this is still the case. 4 | 5 | One example leveraging partials is the handling of reactions on uncached messages, which is explained on [this page](/popular-topics/reactions.md#listening-for-reactions-on-old-messages). 6 | 7 | Prior you had to either handle the undocumented `raw` event or fetch the respective messages on startup. The first approach was prone to errors and unexpected internal behavior. The second was not fully fail-proof either, as the messages could still be uncached if cache size was exceeded in busy channels. 8 | 9 | ## Enabling Partials 10 | 11 | As we said earlier, partials do not have all the information necessary to make them fully functional discord.js structures, so it would not be a good idea to enable the functionality by default. Users should know how to handle them before opting into this feature. 12 | 13 | You choose which structures you want to emit as partials as client options when instantiating your bot client. Available structures are: `User`, `Channel` (only DM channels can be uncached, server channels will always be available), `GuildMember`, `Message`, `Reaction`, `GuildScheduledEvent` and `ThreadMember`. 14 | 15 | ```js 16 | const { Client, Partials } = require('discord.js'); 17 | 18 | const client = new Client({ partials: [Partials.Message, Partials.Channel, Partials.Reaction] }); 19 | ``` 20 | 21 | ::: warning 22 | Make sure you enable all partials you need for your use case! If you miss one, the event does not get emitted. 23 | ::: 24 | 25 | ::: warning 26 | Partial structures are enabled globally. You cannot make them work for only a specific event or cache, and you very likely need to adapt other parts of your code that are accessing data from the relevant caches. All caches holding the respective structure type might return partials as well! 27 | ::: 28 | 29 | ## Handling Partial data 30 | 31 | All structures you can choose to use partials for have a new property, fittingly called `.partial`, indicating if it is a fully functional or partial instance of its class. The value is `true` if partial, `false` if fully functional. 32 | 33 | ::: warning 34 | Partial data is only ever guaranteed to contain an ID! Do not assume any property or method to work when dealing with a partial structure! 35 | ::: 36 | 37 | ```js 38 | if (message.partial) { 39 | console.log('The message is partial.'); 40 | } else { 41 | console.log('The message is not partial.'); 42 | } 43 | ``` 44 | 45 | ## Obtaining the full structure 46 | 47 | Along with `.partial` to check if the structure you call it on is partial or not, the library also introduced a `.fetch()` method to retrieve the missing data from the API and complete the structure. The method returns a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) you need to handle. After the Promise resolves (and with it, the missing data arrived), you can use the structure as you would before. 48 | 49 | ```js {2-8,10} 50 | if (message.partial) { 51 | message.fetch() 52 | .then(fullMessage => { 53 | console.log(fullMessage.content); 54 | }) 55 | .catch(error => { 56 | console.log('Something went wrong when fetching the message: ', error); 57 | }); 58 | } else { 59 | console.log(message.content); 60 | } 61 | ``` 62 | 63 | ::: warning 64 | You cannot fetch deleted data from the API. For message deletions, `messageDelete` will only emit with the ID, which you cannot use to fetch the complete message containing content, author, or other information, as it is already inaccessible by the time you receive the event. 65 | ::: 66 | -------------------------------------------------------------------------------- /guide/preparations/README.md: -------------------------------------------------------------------------------- 1 | # Installing Node.js and discord.js 2 | 3 | ## Installing Node.js 4 | 5 | To use discord.js, you'll need to install the latest LTS version of [Node.js](https://nodejs.org/). 6 | 7 | ::: tip 8 | To check if you already have Node installed on your machine \(e.g., if you're using a VPS\), run `node -v` in your terminal. It is recommended to use the latest LTS version of Node. 9 | ::: 10 | 11 | On Windows, it's as simple as installing any other program. Download the latest version from [the Node.js website](https://nodejs.org/), open the downloaded file, and follow the steps from the installer. 12 | 13 | On macOS, either: 14 | 15 | - Download the latest version from [the Node.js website](https://nodejs.org/), open the package installer, and follow the instructions 16 | - Use a package manager like [Homebrew](https://brew.sh/) with the command `brew install node` 17 | 18 | On Linux, you can consult [this page](https://nodejs.org/en/download/package-manager/) to determine how you should install Node. 19 | 20 | ## Preparing the essentials 21 | 22 | To use discord.js, you'll need to install it via npm \(Node's package manager\). npm comes with every Node installation, so you don't have to worry about installing that. However, before you install anything, you should set up a new project folder. 23 | 24 | Navigate to a suitable place on your machine and create a new folder named `discord-bot` (or whatever you want). Next you'll need to open your terminal. 25 | 26 | ### Opening the terminal 27 | 28 | ::: tip 29 | If you use [Visual Studio Code](https://code.visualstudio.com/), you can press Ctrl + ` (backtick) to open its integrated terminal. 30 | ::: 31 | 32 | On Windows, either: 33 | 34 | - `Shift + Right-click` inside your project directory and choose the "Open command window here" option 35 | - Press `Win + R` and run `cmd.exe`, and then `cd` into your project directory 36 | 37 | On macOS, either: 38 | - Open Launchpad or Spotlight and search for "Terminal" 39 | - In your "Applications" folder, under "Utilities", open the Terminal app 40 | 41 | On Linux, you can quickly open the terminal with `Ctrl + Alt + T`. 42 | 43 | With the terminal open, run the `node -v` command to make sure you've successfully installed Node.js. 44 | 45 | ### Initiating a project folder 46 | 47 | :::: code-group 48 | ::: code-group-item npm 49 | ```sh:no-line-numbers 50 | npm init 51 | ``` 52 | ::: 53 | ::: code-group-item yarn 54 | ```sh:no-line-numbers 55 | yarn init 56 | ``` 57 | ::: 58 | ::: code-group-item pnpm 59 | ```sh:no-line-numbers 60 | pnpm init 61 | ``` 62 | ::: 63 | ::: code-group-item bun 64 | ```sh:no-line-numbers 65 | bun init 66 | ``` 67 | ::: 68 | :::: 69 | 70 | This is the next command you'll be running. This command creates a `package.json` file for you, which will keep track of the dependencies your project uses, as well as other info. 71 | 72 | This command will ask you a sequence of questions–you should fill them out as you see fit. If you're not sure of something or want to skip it as a whole, leave it blank and press enter. 73 | 74 | ::: tip 75 | To get started quickly, you can run the following command to have it fill out everything for you. 76 | 77 | 78 | 79 | 80 | ```sh:no-line-numbers 81 | npm init -y 82 | ``` 83 | 84 | 85 | 86 | 87 | ```sh:no-line-numbers 88 | yarn init -y 89 | ``` 90 | 91 | 92 | 93 | 94 | ```sh:no-line-numbers 95 | pnpm init 96 | ``` 97 | 98 | 99 | 100 | 101 | ```sh:no-line-numbers 102 | bun init -y 103 | ``` 104 | 105 | 106 | 107 | ::: 108 | 109 | Once you're done with that, you're ready to install discord.js! 110 | 111 | ## Installing discord.js 112 | 113 | Now that you've installed Node.js and know how to open your console and run commands, you can finally install discord.js! Run the following command in your terminal: 114 | 115 | :::: code-group 116 | ::: code-group-item npm 117 | ```sh:no-line-numbers 118 | npm install discord.js 119 | ``` 120 | ::: 121 | ::: code-group-item yarn 122 | ```sh:no-line-numbers 123 | yarn add discord.js 124 | ``` 125 | ::: 126 | ::: code-group-item pnpm 127 | ```sh:no-line-numbers 128 | pnpm add discord.js 129 | ``` 130 | ::: 131 | ::: code-group-item bun 132 | ```sh:no-line-numbers 133 | bun add discord.js 134 | ``` 135 | ::: 136 | :::: 137 | 138 | And that's it! With all the necessities installed, you're almost ready to start coding your bot. 139 | 140 | ## Installing a linter 141 | 142 | While you are coding, it's possible to run into numerous syntax errors or code in an inconsistent style. You should [install a linter](/preparations/setting-up-a-linter.md) to ease these troubles. While code editors generally can point out syntax errors, linters coerce your code into a specific style as defined by the configuration. While this is not required, it is advised. 143 | -------------------------------------------------------------------------------- /guide/preparations/adding-your-bot-to-servers.md: -------------------------------------------------------------------------------- 1 | # Adding your bot to servers 2 | 3 | After you [set up a bot application](/preparations/setting-up-a-bot-application.md), you'll notice that it's not in any servers yet. So how does that work? 4 | 5 | Before you're able to see your bot in your own (or other) servers, you'll need to add it by creating and using a unique invite link using your bot application's client id. 6 | 7 | ## Bot invite links 8 | 9 | The basic version of one such link looks like this: 10 | 11 | ```:no-line-numbers 12 | https://discord.com/api/oauth2/authorize?client_id=123456789012345678&permissions=0&scope=bot%20applications.commands 13 | ``` 14 | 15 | The structure of the URL is quite simple: 16 | 17 | * `https://discord.com/api/oauth2/authorize` is Discord's standard structure for authorizing an OAuth2 application (such as your bot application) for entry to a Discord server. 18 | * `client_id=...` is to specify _which_ application you want to authorize. You'll need to replace this part with your client's id to create a valid invite link. 19 | * `permissions=...` describes what permissions your bot will have on the server you are adding it to. 20 | * `scope=bot%20applications.commands` specifies that you want to add this application as a Discord bot, with the ability to create slash commands. 21 | 22 | ::: warning 23 | If you get an error message saying "Bot requires a code grant", head over to your application's settings and disable the "Require OAuth2 Code Grant" option. You shouldn't enable this option unless you know why you need to. 24 | ::: 25 | 26 | ## Creating and using your invite link 27 | 28 | To create an invite link, head back to the [My Apps](https://discord.com/developers/applications/me) page under the "Applications" section, click on your bot application, and open the OAuth2 page. 29 | 30 | In the sidebar, you'll find the OAuth2 URL generator. Select the `bot` and `applications.commands` options. Once you select the `bot` option, a list of permissions will appear, allowing you to configure the permissions your bot needs. 31 | 32 | Grab the link via the "Copy" button and enter it in your browser. You should see something like this (with your bot's username and avatar): 33 | 34 | ![Bot Authorization page](./images/bot-auth-page.png) 35 | 36 | Choose the server you want to add it to and click "Authorize". Do note that you'll need the "Manage Server" permission on a server to add your bot there. This should then present you a nice confirmation message: 37 | 38 | ![Bot authorized](./images/bot-authorized.png) 39 | 40 | Congratulations! You've successfully added your bot to your Discord server. It should show up in your server's member list somewhat like this: 41 | 42 | ![Bot in server's member list](./images/bot-in-memberlist.png) 43 | -------------------------------------------------------------------------------- /guide/preparations/images/bot-auth-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discordjs/guide/1fa24806d4e0c24095f3b2cf21fa746d2fc66d50/guide/preparations/images/bot-auth-page.png -------------------------------------------------------------------------------- /guide/preparations/images/bot-authorized.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discordjs/guide/1fa24806d4e0c24095f3b2cf21fa746d2fc66d50/guide/preparations/images/bot-authorized.png -------------------------------------------------------------------------------- /guide/preparations/images/bot-in-memberlist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discordjs/guide/1fa24806d4e0c24095f3b2cf21fa746d2fc66d50/guide/preparations/images/bot-in-memberlist.png -------------------------------------------------------------------------------- /guide/preparations/images/create-app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discordjs/guide/1fa24806d4e0c24095f3b2cf21fa746d2fc66d50/guide/preparations/images/create-app.png -------------------------------------------------------------------------------- /guide/preparations/images/created-bot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discordjs/guide/1fa24806d4e0c24095f3b2cf21fa746d2fc66d50/guide/preparations/images/created-bot.png -------------------------------------------------------------------------------- /guide/preparations/setting-up-a-bot-application.md: -------------------------------------------------------------------------------- 1 | # Setting up a bot application 2 | 3 | ## Creating your bot 4 | 5 | Now that you've installed Node, discord.js, and hopefully a linter, you're almost ready to start coding! The next step you need to take is setting up an actual Discord bot application via Discord's website. 6 | 7 | It's effortless to create one. The steps you need to take are as follows: 8 | 9 | 1. Open the [Discord developer portal](https://discord.com/developers/applications) and log into your account. 10 | 2. Click on the "New Application" button. 11 | 3. Enter a name and confirm the pop-up window by clicking the "Create" button. 12 | 13 | You should see a page like this: 14 | 15 | ![Successfully created application](./images/create-app.png) 16 | 17 | You can edit your application's name, description, and avatar here. Once you've done that, then congratulations—you're now the proud owner of a shiny new Discord bot! You're not entirely done, though. 18 | 19 | ## Your bot's token 20 | 21 | ::: danger 22 | This section is critical, so pay close attention. It explains what your bot token is, as well as the security aspects of it. 23 | ::: 24 | 25 | After creating a bot user, you'll see a section like this: 26 | 27 | ![Bot application](./images/created-bot.png) 28 | 29 | In this panel, you can give your bot a snazzy avatar, set its username, and make it public or private. Your bot's token will be revealed when you press the "Reset Token" button and confirm. When we ask you to paste your bot's token somewhere, this is the value that you need to put in. If you happen to lose your bot's token at some point, you need to come back to this page and reset your bot's token again which will reveal the new token, invalidating all old ones. 30 | 31 | ### What is a token, anyway? 32 | 33 | A token is essentially your bot's password; it's what your bot uses to login to Discord. With that said, **it is vital that you do not ever share this token with anybody, purposely or accidentally**. If someone does manage to get a hold of your bot's token, they can use your bot as if it were theirs—this means they can perform malicious acts with it. 34 | 35 | Tokens look like this: `NzkyNzE1NDU0MTk2MDg4ODQy.X-hvzA.Ovy4MCQywSkoMRRclStW4xAYK7I` (don't worry, we immediately reset this token before even posting it here!). If it's any shorter and looks more like this: `kxbsDRU5UfAaiO7ar9GFMHSlmTwYaIYn`, you copied your client secret instead. Make sure to copy the token if you want your bot to work! 36 | 37 | ### Token leak scenario 38 | 39 | Let's imagine that you have a bot on over 1,000 servers, and it took you many, many months of coding and patience to get it on that amount. Your bot's token gets leaked somewhere, and now someone else has it. That person can: 40 | 41 | * Spam every server your bot is on; 42 | * DM spam as many users as possible; 43 | * Delete as many channels as possible; 44 | * Kick or ban as many server members as possible; 45 | * Make your bot leave all of the servers it has joined; 46 | 47 | All that and much, much more. Sounds pretty terrible, right? So make sure to keep your bot's token as safe as possible! 48 | 49 | In the [initial files](/creating-your-bot/) page of the guide, we cover how to safely store your bot's token in a configuration file. 50 | 51 | ::: danger 52 | If your bot token has been compromised by committing it to a public repository, posting it in discord.js support etc. or otherwise see your bot's token in danger, return to this page and press "Reset Token". This will invalidate all old tokens belonging to your bot. Keep in mind that you will need to update your bot's token where you used it before. 53 | ::: 54 | -------------------------------------------------------------------------------- /guide/requesting-more-content.md: -------------------------------------------------------------------------------- 1 | # Requesting more content 2 | 3 | Since this guide is made specifically for the discord.js community, we want to be sure to provide the most relevant and up-to-date content. We will, of course, make additions to the current pages and add new ones as we see fit, but fulfilling requests is how we know we're providing content you all want the most. 4 | 5 | Requests may be as simple as "add an example to the [frequently asked questions](/popular-topics/faq.html) page", or as elaborate as "add a page regarding [sharding](/sharding/)". We'll do our best to fulfill all requests, as long as they're reasonable. 6 | 7 | To make a request, simply head over to [the repo's issue tracker](https://github.com/discordjs/guide/issues) and [create a new issue](https://github.com/discordjs/guide/issues/new)! Title it appropriately, and let us know exactly what you mean inside the issue description. Make sure that you've looked around the site before making a request; what you want to request might already exist! 8 | 9 | ::: tip 10 | Remember that you can always [fork the repo](https://github.com/discordjs/guide) and [make a pull request](https://github.com/discordjs/guide/pulls) if you want to add anything to the guide yourself! 11 | ::: 12 | -------------------------------------------------------------------------------- /guide/sharding/additional-information.md: -------------------------------------------------------------------------------- 1 | # Additional information 2 | 3 | ::: tip 4 | This page is a follow-up and bases its code on [the previous page](/sharding/). 5 | ::: 6 | 7 | Here are some extra topics covered about sharding that might have raised concerns. 8 | 9 | ## Legend 10 | 11 | * `manager` is an instance of `ShardingManager`, e.g. `const manager = new ShardingManager(file, options);` 12 | * `client.shard` refers to the current shard. 13 | 14 | ## Shard messages 15 | 16 | For shards to communicate, they have to send messages to one another, as they each have another process. You must wait for each shard to finish spawning before you can listen to their events, otherwise `ShardingManager#shards` will be an empty `Collection`. You can listen for these messages on the individual shards by adding the following lines in your `index.js` file: 17 | 18 | ```js 19 | manager.spawn() 20 | .then(shards => { 21 | shards.forEach(shard => { 22 | shard.on('message', message => { 23 | console.log(`Shard[${shard.id}] : ${message._eval} : ${message._result}`); 24 | }); 25 | }); 26 | }) 27 | .catch(console.error); 28 | ``` 29 | 30 | As the property names imply, the `_eval` property is what the shard is attempting to evaluate, and the `_result` property is the output of said evaluation. However, these properties are only guaranteed if a _shard_ is sending a message. There will also be an `_error` property, should the evaluation have thrown an error. 31 | 32 | You can also send messages via `process.send('hello')`, which would not contain the same information. This is why the `.message` property's type is declared as `*` in the documentation. 33 | 34 | ## Specific shards 35 | 36 | There might be times where you want to target a specific shard. An example would be to kill a specific shard that isn't working as intended. You can achieve this by taking the following snippet (in a command, preferably): 37 | 38 | ::: tip 39 | In discord.js v13, `Client#shard` can hold multiple ids. If you use the default sharding manager, the `.ids` array will only have one entry. 40 | ::: 41 | 42 | ```js 43 | client.shard.broadcastEval(c => { 44 | if (c.shard.ids.includes(0)) process.exit(); 45 | }); 46 | ``` 47 | 48 | If you're using something like [PM2](http://pm2.keymetrics.io/) or [Forever](https://github.com/foreverjs/forever), this is an easy way to restart a specific shard. Remember, sends a message to **all** shards, so you have to check if it's on the shard you want. 49 | 50 | ## `ShardingManager#shardArgs` and `ShardingManager#execArgv` 51 | 52 | Consider the following example of creating a new `ShardingManager` instance: 53 | 54 | ```js 55 | const manager = new ShardingManager('./bot.js', { 56 | execArgv: ['--trace-warnings'], 57 | shardArgs: ['--ansi', '--color'], 58 | token: 'your-token-goes-here', 59 | }); 60 | ``` 61 | 62 | The `execArgv` property is what you would usually pass to Node without sharding, e.g.: 63 | 64 | ```sh:no-line-numbers 65 | node --trace-warnings bot.js 66 | ``` 67 | 68 | You can find a list of command-line options for Node [here](https://nodejs.org/api/cli.html). 69 | 70 | The `shardArgs` property is what you would usually pass to your bot without sharding, e.g.: 71 | 72 | ```sh:no-line-numbers 73 | node bot.js --ansi --color 74 | ``` 75 | 76 | You can access them later as usual via `process.argv`, which contains an array of executables, your main file, and the command-line arguments used to execute the script. 77 | 78 | ## Eval arguments 79 | 80 | There may come the point where you will want to pass arguments from the outer scope into a `.broadcastEval()` call. 81 | 82 | ```js 83 | function funcName(c, { arg }) { 84 | // ... 85 | } 86 | 87 | client.shard.broadcastEval(funcName, { context: { arg: 'arg' } }); 88 | ``` 89 | 90 | The `BroadcastEvalOptions` typedef was introduced in discord.js v13 as the second parameter in `.broadcastEval()`. It accepts two properties: `shard` and `context`. The `context` property will be sent as the second argument to your function. 91 | 92 | In this small snippet, an argument is passed to the `funcName` function through this parameter. 93 | The function will receive the arguments as an object as the second parameter. 94 | 95 | ::: warning 96 | The `context` option only accepts properties which are JSON-serializable. This means you cannot pass complex data types in the context directly. 97 | For example, if you sent a `User` instance, the function would receive the raw data object. 98 | ::: 99 | -------------------------------------------------------------------------------- /guide/slash-commands/deleting-commands.md: -------------------------------------------------------------------------------- 1 | # Deleting commands 2 | 3 | ::: tip 4 | This page is a follow-up to [command deployment](/creating-your-bot/command-deployment.md). To delete commands, you need to register them in the first place. 5 | ::: 6 | 7 | You may have decided that you don't need a command anymore and don't want your users to be confused when they encounter a removed command. 8 | 9 | ## Deleting specific commands 10 | 11 | To delete a specific command, you will need its id. Head to **Server Settings -> Integrations -> Bots and Apps** and choose your bot. Then, right click a command and click **Copy ID**. 12 | 13 | ::: tip 14 | You need to have [Developer Mode](https://support.discord.com/hc/articles/206346498) enabled for this to show up! 15 | ::: 16 | 17 | ![bots-and-apps](./images/bots-and-apps.png) 18 | 19 | ![commands-copy-id](./images/commands-copy-id.png) 20 | 21 | Edit your `deploy-commands.js` as shown below, or put it into its own file to clearly discern it from the deploy workflow: 22 | 23 | ```js {9-17} 24 | const { REST, Routes } = require('discord.js'); 25 | const { clientId, guildId, token } = require('./config.json'); 26 | 27 | const rest = new REST().setToken(token); 28 | 29 | // ... 30 | 31 | // for guild-based commands 32 | rest.delete(Routes.applicationGuildCommand(clientId, guildId, 'commandId')) 33 | .then(() => console.log('Successfully deleted guild command')) 34 | .catch(console.error); 35 | 36 | // for global commands 37 | rest.delete(Routes.applicationCommand(clientId, 'commandId')) 38 | .then(() => console.log('Successfully deleted application command')) 39 | .catch(console.error); 40 | ``` 41 | 42 | Where `'commandId'` is the id of the command you want to delete. Run your deploy script and it will delete the command. 43 | 44 | ## Deleting all commands 45 | 46 | To delete all commands in the respective scope (one guild, all global commands) you can pass an empty array when setting commands. 47 | 48 | ```js {9-18} 49 | const { REST, Routes } = require('discord.js'); 50 | const { clientId, guildId, token } = require('./config.json'); 51 | 52 | const rest = new REST().setToken(token); 53 | 54 | // ... 55 | 56 | // for guild-based commands 57 | rest.put(Routes.applicationGuildCommands(clientId, guildId), { body: [] }) 58 | .then(() => console.log('Successfully deleted all guild commands.')) 59 | .catch(console.error); 60 | 61 | // for global commands 62 | rest.put(Routes.applicationCommands(clientId), { body: [] }) 63 | .then(() => console.log('Successfully deleted all application commands.')) 64 | .catch(console.error); 65 | ``` 66 | 67 | Discord's API doesn't currently provide an easy way to delete guild-based commands that occur on multiple guilds from all places at once. Each will need a call of the above endpoint, while specifying the respective guild and command id. Note, that the same command will have a different id, if deployed to a different guild! 68 | -------------------------------------------------------------------------------- /guide/slash-commands/images/bots-and-apps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discordjs/guide/1fa24806d4e0c24095f3b2cf21fa746d2fc66d50/guide/slash-commands/images/bots-and-apps.png -------------------------------------------------------------------------------- /guide/slash-commands/images/commands-copy-id.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/discordjs/guide/1fa24806d4e0c24095f3b2cf21fa746d2fc66d50/guide/slash-commands/images/commands-copy-id.png -------------------------------------------------------------------------------- /guide/slash-commands/permissions.md: -------------------------------------------------------------------------------- 1 | # Slash command permissions 2 | 3 | Slash commands have their own permissions system. This system allows you to set the default level of permissions required for a user to execute a command when it is first deployed or your bot is added to a new server. 4 | 5 | The slash command permissions for guilds are defaults only and can be altered by guild administrators, allowing them to configure access however best suits their moderation and server roles. Your code should not try to enforce its own permission management, as this can result in a conflict between the server-configured permissions and your bot's code. 6 | 7 | ::: warning 8 | It is not possible to prevent users with Administrator permissions from using any commands deployed globally or specifically to their guild. Think twice before creating "dev-only" commands such as `eval`. 9 | ::: 10 | 11 | ## Member permissions 12 | 13 | You can use to set the default permissions required for a member to run the command. Setting it to `0` will prohibit anyone in a guild from using the command unless a specific overwrite is configured or the user has the Administrator permission flag. 14 | 15 | For this, we'll introduce two common and simple moderation commands: `ban` and `kick`. For a ban command, a sensible default is to ensure that users already have the Discord permission `BanMembers` in order to use it. 16 | 17 | ```js {11} 18 | const { SlashCommandBuilder, PermissionFlagsBits } = require('discord.js'); 19 | 20 | const data = new SlashCommandBuilder() 21 | .setName('ban') 22 | .setDescription('Select a member and ban them.') 23 | .addUserOption(option => 24 | option 25 | .setName('target') 26 | .setDescription('The member to ban') 27 | .setRequired(true)) 28 | .setDefaultMemberPermissions(PermissionFlagsBits.BanMembers); 29 | ``` 30 | 31 | For a kick command however, we can allow members with the `KickMembers` permission to execute the command, so we'll list that flag here. 32 | 33 | ::: tip 34 | You can require the user to have all of multiple permissions by merging them with the `|` bitwise OR operator (for example `PermissionFlagsBits.BanMembers | PermissionFlagsBits.KickMembers`). 35 | You cannot require any of multiple permissions. Discord evaluates against the combined permission bitfield! 36 | 37 | If you want to learn more about the `|` bitwise OR operator you can check the [Wikipedia](https://en.wikipedia.org/wiki/Bitwise_operation#OR) and [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_OR) articles on the topic. 38 | ::: 39 | 40 | ```js {11} 41 | const { SlashCommandBuilder, PermissionFlagsBits } = require('discord.js'); 42 | 43 | const data = new SlashCommandBuilder() 44 | .setName('kick') 45 | .setDescription('Select a member and kick them.') 46 | .addUserOption(option => 47 | option 48 | .setName('target') 49 | .setDescription('The member to kick') 50 | .setRequired(true)) 51 | .setDefaultMemberPermissions(PermissionFlagsBits.KickMembers); 52 | ``` 53 | 54 | In reality, you'll probably want to have an additional confirmation step before a ban actually executes. Check out the [button components section](/message-components/buttons) of the guide to see how to add confirmation buttons to your command responses, and listen to button clicks. 55 | 56 | ## Contexts 57 | 58 | By default, globally-deployed commands are also available for use in DMs. You can pass in [InteractionContextType](https://discord-api-types.dev/api/discord-api-types-v10/enum/InteractionContextType) to the `setContexts` method of the builder to restrict the command to only be available in guilds or DMs. 59 | 60 | It doesn't make much sense for your `ban` command to be available in DMs, so you can add `setContexts(InteractionContextType.Guild)` to the builder so that it is only available in guilds: 61 | 62 | ```js {11-12} 63 | const { InteractionContextType, PermissionFlagsBits, SlashCommandBuilder } = require('discord.js'); 64 | 65 | const data = new SlashCommandBuilder() 66 | .setName('ban') 67 | .setDescription('Select a member and ban them.') 68 | .addUserOption(option => 69 | option 70 | .setName('target') 71 | .setDescription('The member to ban') 72 | .setRequired(true)) 73 | .setDefaultMemberPermissions(PermissionFlagsBits.BanMembers) 74 | .setContexts(InteractionContextType.Guild); 75 | ``` 76 | 77 | And that's all you need to know on slash command permissions and contexts! 78 | -------------------------------------------------------------------------------- /guide/voice/life-cycles.md: -------------------------------------------------------------------------------- 1 | # Life cycles 2 | 3 | Two of the main components that you'll interact with when using `@discordjs/voice` are: 4 | 5 | - **VoiceConnection** – maintains a network connection to a Discord voice server 6 | - **AudioPlayer** – plays audio resources across a voice connection 7 | 8 | Both voice connections and audio players are _stateful_, and you can subscribe to changes in their state as they progress through their respective life cycles. 9 | 10 | It's important to listen for state change events, as they will likely require you to take some action. For example, a voice connection entering the `Disconnected` state will probably require you to attempt to reconnect it. 11 | 12 | Their individual life cycles with descriptions of their states are documented on their respective pages. 13 | 14 | Listening to changes in the life cycles of voice connections and audio players can be done in two ways: 15 | 16 | ## Subscribing to individual events 17 | 18 | ```js 19 | const { VoiceConnectionStatus, AudioPlayerStatus } = require('@discordjs/voice'); 20 | 21 | connection.on(VoiceConnectionStatus.Ready, (oldState, newState) => { 22 | console.log('Connection is in the Ready state!'); 23 | }); 24 | 25 | player.on(AudioPlayerStatus.Playing, (oldState, newState) => { 26 | console.log('Audio player is in the Playing state!'); 27 | }); 28 | ``` 29 | 30 | :::tip 31 | One advantage of listening for transitions to individual states is that it becomes a lot easier to write sequential logic. This is made easy using our [state transition helper](https://github.com/discordjs/discord.js/blob/main/packages/voice/src/util/entersState.ts). An example is shown below. 32 | 33 | ```js 34 | const { AudioPlayerStatus, entersState } = require('@discordjs/voice'); 35 | 36 | async function start() { 37 | player.play(resource); 38 | try { 39 | await entersState(player, AudioPlayerStatus.Playing, 5_000); 40 | // The player has entered the Playing state within 5 seconds 41 | console.log('Playback has started!'); 42 | } catch (error) { 43 | // The player has not entered the Playing state and either: 44 | // 1) The 'error' event has been emitted and should be handled 45 | // 2) 5 seconds have passed 46 | console.error(error); 47 | } 48 | } 49 | 50 | void start(); 51 | ``` 52 | ::: 53 | 54 | ## Subscribing to all state transitions 55 | 56 | If you would prefer to keep a single event listener for all possible state transitions, then you can also listen to the `stateChange` event: 57 | 58 | ```js 59 | connection.on('stateChange', (oldState, newState) => { 60 | console.log(`Connection transitioned from ${oldState.status} to ${newState.status}`); 61 | }); 62 | 63 | player.on('stateChange', (oldState, newState) => { 64 | console.log(`Audio player transitioned from ${oldState.status} to ${newState.status}`); 65 | }); 66 | ``` 67 | -------------------------------------------------------------------------------- /guide/whats-new.md: -------------------------------------------------------------------------------- 1 | 12 | 13 | # What's new 14 | 15 | 16 | 17 | 24 | discord.js v14 has released and the guide has been updated! 25 | 26 | :tada: 27 | 28 |
29 | This includes additions and changes made in Discord, such as slash commands and message components. 30 |
31 |
32 | 33 | ## Site 34 | 35 | - Upgraded to [VuePress v2](https://v2.vuepress.vuejs.org/) 36 | - New theme made to match the [discord.js documentation site](https://discord.js.org/) 37 | - Discord message components upgraded to [@discord-message-components/vue](https://github.com/Danktuary/discord-message-components/blob/main/packages/vue/README.md) 38 | - Many fixes in code blocks, grammar, consistency, etc. 39 | 40 | ## Pages 41 | 42 | All content has been updated to use discord.js v14 syntax. The v13 version of the guide can be found at [https://v13.discordjs.guide/](https://v13.discordjs.guide/). 43 | 44 | ### New 45 | 46 | - [Updating from v13 to v14](/additional-info/changes-in-v14.md): A list of the changes from discord.js v13 to v14 47 | - [Slash commands](/slash-commands/advanced-creation.md): Registering, replying to slash commands and permissions 48 | - [Buttons](/message-components/buttons): Building, sending, and receiving buttons 49 | - [Select menus](/message-components/select-menus): Building, sending, and receiving select menus 50 | - [Threads](/popular-topics/threads.md): Creating and managing threads 51 | - [Formatters](/popular-topics/formatters.md): A collection of formatters to use with your bot 52 | 53 | ### Updated 54 | 55 | - Commando: Replaced with [Sapphire](https://sapphirejs.dev/docs/Guide/getting-started/getting-started-with-sapphire) 56 | - [Voice](/voice/): Rewritten to use the [`@discordjs/voice`](https://github.com/discordjs/discord.js/tree/main/packages/voice) package 57 | - [Command handling](/creating-your-bot/command-handling.md/): Updated to use slash commands 58 | - Obsolete sections removed 59 | - `client.on('message')` snippets updated to `client.on('interactionCreate')` 60 | - [Message content will become a new privileged intent on August 31, 2022](https://support-dev.discord.com/hc/articles/6207308062871) 61 | 62 | 63 | 64 | Thank you to all of those that contributed to the development of discord.js and the guide! 65 | 66 | :heart: 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [[headers]] 2 | for = "/*" 3 | [headers.values] 4 | Permissions-Policy = "interest-cohort=()" 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "discord.js-guide", 3 | "description": "Imagine a guide... that explores the many possibilities for your discord.js bot.", 4 | "version": "3.0.0", 5 | "private": true, 6 | "scripts": { 7 | "lint": "eslint . --ext js,md,vue", 8 | "dev": "vuepress dev guide", 9 | "build": "NODE_ENV=production vuepress build guide" 10 | }, 11 | "repository": "https://github.com/discordjs/guide.git", 12 | "keywords": [ 13 | "discord", 14 | "bot", 15 | "guide", 16 | "tutorial", 17 | "javascript", 18 | "node" 19 | ], 20 | "author": "Sanctuary ", 21 | "contributors": [ 22 | "dragonfire535 ", 23 | "Drahcirius", 24 | "Lewdcario", 25 | "Souji " 26 | ], 27 | "license": "MIT", 28 | "bugs": { 29 | "url": "https://github.com/discordjs/guide/issues" 30 | }, 31 | "homepage": "https://discordjs.guide", 32 | "devDependencies": { 33 | "@vuepress/plugin-docsearch": "^2.0.0-beta.24", 34 | "@vuepress/plugin-google-analytics": "^2.0.0-beta.24", 35 | "eslint": "^7.18.0", 36 | "eslint-config-sora": "^3.1.0", 37 | "eslint-plugin-markdown": "^1.0.0", 38 | "eslint-plugin-vue": "^7.5.0", 39 | "@babel/eslint-parser": "^7.17.0", 40 | "vuepress-vite": "^2.0.0-beta.24" 41 | }, 42 | "dependencies": { 43 | "@discord-message-components/vue": "^0.2.1" 44 | } 45 | } 46 | --------------------------------------------------------------------------------