├── .gitbook └── assets │ ├── becomeAPatronBanner.png │ ├── cleverbot-results.png │ ├── editorhelp.png │ ├── favicon.ico │ ├── first-bot-embed-example.png │ ├── gs-add-bot-modal.png │ ├── gs-application-page.png │ ├── gs-bot-created.png │ ├── gs-copy-token.png │ ├── gs-created-application.png │ ├── gs-installing-discordjs.gif │ ├── gs-invite-bot.png │ ├── gs-making-bot.png │ ├── gs-new-application.png │ ├── gs-ping-pong.png │ ├── starboard.png │ ├── wh01.png │ ├── wh02.png │ ├── wh03.png │ ├── wh04.png │ ├── wh05.png │ ├── wh06.png │ ├── wh07.png │ ├── wh08.png │ ├── wh09.png │ ├── wh12.png │ ├── wh13.png │ └── wh14.png ├── .github └── workflows │ └── retype.yml ├── .gitignore ├── .markdownlint.json ├── .vscode └── settings.json ├── README.md ├── SUMMARY.md ├── book.json ├── coding-guides ├── README.md ├── making-your-own-starboard.md ├── sqlite-based-points-system.md ├── tracking-used-invites.md ├── using-audit-logs.md └── using-emojis.md ├── common-errors.md ├── discord-webhooks ├── README.md ├── discord-webhooks-part-1.md ├── discord-webhooks-part-2.md └── discord-webhooks-part-3.md ├── examples ├── README.md ├── making-an-eval-command.md └── miscellaneous-examples.md ├── first-bot ├── README.md ├── a-basic-command-handler.md ├── adding-a-config-file.md ├── better-basic-handler.md ├── command-with-arguments.md ├── using-embeds-in-messages.md └── your-first-bot.md ├── frequently-asked-questions.md ├── getting-started ├── README.md ├── getting-started-long-version.md └── getting-started-tl-dr.md ├── other-guides ├── README.md ├── async-await.md ├── env-files.md ├── installing-and-using-a-proper-editor.md └── using-git-to-share-and-update-code.md ├── package.json ├── retype.yml ├── understanding ├── README.md ├── collections.md ├── events-and-handlers.md ├── roles.md └── sharding.md └── video-guides └── README.md /.gitbook/assets/becomeAPatronBanner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnIdiotsGuide/discordjs-bot-guide/25db371744384edb1ad228e245319f5dc62021c8/.gitbook/assets/becomeAPatronBanner.png -------------------------------------------------------------------------------- /.gitbook/assets/cleverbot-results.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnIdiotsGuide/discordjs-bot-guide/25db371744384edb1ad228e245319f5dc62021c8/.gitbook/assets/cleverbot-results.png -------------------------------------------------------------------------------- /.gitbook/assets/editorhelp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnIdiotsGuide/discordjs-bot-guide/25db371744384edb1ad228e245319f5dc62021c8/.gitbook/assets/editorhelp.png -------------------------------------------------------------------------------- /.gitbook/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnIdiotsGuide/discordjs-bot-guide/25db371744384edb1ad228e245319f5dc62021c8/.gitbook/assets/favicon.ico -------------------------------------------------------------------------------- /.gitbook/assets/first-bot-embed-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnIdiotsGuide/discordjs-bot-guide/25db371744384edb1ad228e245319f5dc62021c8/.gitbook/assets/first-bot-embed-example.png -------------------------------------------------------------------------------- /.gitbook/assets/gs-add-bot-modal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnIdiotsGuide/discordjs-bot-guide/25db371744384edb1ad228e245319f5dc62021c8/.gitbook/assets/gs-add-bot-modal.png -------------------------------------------------------------------------------- /.gitbook/assets/gs-application-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnIdiotsGuide/discordjs-bot-guide/25db371744384edb1ad228e245319f5dc62021c8/.gitbook/assets/gs-application-page.png -------------------------------------------------------------------------------- /.gitbook/assets/gs-bot-created.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnIdiotsGuide/discordjs-bot-guide/25db371744384edb1ad228e245319f5dc62021c8/.gitbook/assets/gs-bot-created.png -------------------------------------------------------------------------------- /.gitbook/assets/gs-copy-token.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnIdiotsGuide/discordjs-bot-guide/25db371744384edb1ad228e245319f5dc62021c8/.gitbook/assets/gs-copy-token.png -------------------------------------------------------------------------------- /.gitbook/assets/gs-created-application.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnIdiotsGuide/discordjs-bot-guide/25db371744384edb1ad228e245319f5dc62021c8/.gitbook/assets/gs-created-application.png -------------------------------------------------------------------------------- /.gitbook/assets/gs-installing-discordjs.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnIdiotsGuide/discordjs-bot-guide/25db371744384edb1ad228e245319f5dc62021c8/.gitbook/assets/gs-installing-discordjs.gif -------------------------------------------------------------------------------- /.gitbook/assets/gs-invite-bot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnIdiotsGuide/discordjs-bot-guide/25db371744384edb1ad228e245319f5dc62021c8/.gitbook/assets/gs-invite-bot.png -------------------------------------------------------------------------------- /.gitbook/assets/gs-making-bot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnIdiotsGuide/discordjs-bot-guide/25db371744384edb1ad228e245319f5dc62021c8/.gitbook/assets/gs-making-bot.png -------------------------------------------------------------------------------- /.gitbook/assets/gs-new-application.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnIdiotsGuide/discordjs-bot-guide/25db371744384edb1ad228e245319f5dc62021c8/.gitbook/assets/gs-new-application.png -------------------------------------------------------------------------------- /.gitbook/assets/gs-ping-pong.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnIdiotsGuide/discordjs-bot-guide/25db371744384edb1ad228e245319f5dc62021c8/.gitbook/assets/gs-ping-pong.png -------------------------------------------------------------------------------- /.gitbook/assets/starboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnIdiotsGuide/discordjs-bot-guide/25db371744384edb1ad228e245319f5dc62021c8/.gitbook/assets/starboard.png -------------------------------------------------------------------------------- /.gitbook/assets/wh01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnIdiotsGuide/discordjs-bot-guide/25db371744384edb1ad228e245319f5dc62021c8/.gitbook/assets/wh01.png -------------------------------------------------------------------------------- /.gitbook/assets/wh02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnIdiotsGuide/discordjs-bot-guide/25db371744384edb1ad228e245319f5dc62021c8/.gitbook/assets/wh02.png -------------------------------------------------------------------------------- /.gitbook/assets/wh03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnIdiotsGuide/discordjs-bot-guide/25db371744384edb1ad228e245319f5dc62021c8/.gitbook/assets/wh03.png -------------------------------------------------------------------------------- /.gitbook/assets/wh04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnIdiotsGuide/discordjs-bot-guide/25db371744384edb1ad228e245319f5dc62021c8/.gitbook/assets/wh04.png -------------------------------------------------------------------------------- /.gitbook/assets/wh05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnIdiotsGuide/discordjs-bot-guide/25db371744384edb1ad228e245319f5dc62021c8/.gitbook/assets/wh05.png -------------------------------------------------------------------------------- /.gitbook/assets/wh06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnIdiotsGuide/discordjs-bot-guide/25db371744384edb1ad228e245319f5dc62021c8/.gitbook/assets/wh06.png -------------------------------------------------------------------------------- /.gitbook/assets/wh07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnIdiotsGuide/discordjs-bot-guide/25db371744384edb1ad228e245319f5dc62021c8/.gitbook/assets/wh07.png -------------------------------------------------------------------------------- /.gitbook/assets/wh08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnIdiotsGuide/discordjs-bot-guide/25db371744384edb1ad228e245319f5dc62021c8/.gitbook/assets/wh08.png -------------------------------------------------------------------------------- /.gitbook/assets/wh09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnIdiotsGuide/discordjs-bot-guide/25db371744384edb1ad228e245319f5dc62021c8/.gitbook/assets/wh09.png -------------------------------------------------------------------------------- /.gitbook/assets/wh12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnIdiotsGuide/discordjs-bot-guide/25db371744384edb1ad228e245319f5dc62021c8/.gitbook/assets/wh12.png -------------------------------------------------------------------------------- /.gitbook/assets/wh13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnIdiotsGuide/discordjs-bot-guide/25db371744384edb1ad228e245319f5dc62021c8/.gitbook/assets/wh13.png -------------------------------------------------------------------------------- /.gitbook/assets/wh14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnIdiotsGuide/discordjs-bot-guide/25db371744384edb1ad228e245319f5dc62021c8/.gitbook/assets/wh14.png -------------------------------------------------------------------------------- /.github/workflows/retype.yml: -------------------------------------------------------------------------------- 1 | name: Publish Retype powered website to GitHub Pages 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | publish: 10 | name: Publish to retype branch 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | 17 | - uses: retypeapp/action-build@v1 18 | 19 | - uses: retypeapp/action-github-pages@v1 20 | with: 21 | update-branch: true 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Node rules: 2 | ## Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 3 | .grunt 4 | 5 | ## Dependency directory 6 | ## Commenting this out is preferred by some people, see 7 | ## https://docs.npmjs.com/misc/faq#should-i-check-my-node_modules-folder-into-git 8 | node_modules 9 | 10 | # Book build output 11 | _book 12 | 13 | # eBook build output 14 | *.epub 15 | *.mobi 16 | *.pdf -------------------------------------------------------------------------------- /.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "MD001": false, 3 | "MD013": false, 4 | "MD024": false, 5 | "MD025": false, 6 | "MD026": false, 7 | "MD040": false 8 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "bootup", 4 | "bot", 5 | "bot's", 6 | "botception", 7 | "broadcasteval", 8 | "clbot", 9 | "commandname", 10 | "discordjs", 11 | "distro", 12 | "endhint", 13 | "enmap", 14 | "evie", 15 | "evie's", 16 | "fetchclientvalues", 17 | "gitbook", 18 | "hastebin", 19 | "mkdir", 20 | "pastebin", 21 | "sharded", 22 | "syncronous" 23 | ], 24 | "cSpell.ignoreWords": [ 25 | "addrole", 26 | "ayyy", 27 | "botapi", 28 | "chonker", 29 | "cleverbot", 30 | "evaled", 31 | "ezpz", 32 | "halp", 33 | "Heya", 34 | "iamkey", 35 | "leaderboard", 36 | "listemojis", 37 | "mycommand", 38 | "nadda", 39 | "newtag", 40 | "noppers", 41 | "npmjs", 42 | "rolename", 43 | "twemoji", 44 | "typeof", 45 | "woah", 46 | "wooh" 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 'The unofficial Discord.js beginner''s guide, written by idiots for beginners.' 3 | --- 4 | 5 | # Welcome 6 | 7 | ## Introduction 8 | 9 | This guide makes a best effort attempt at humanizing the use of the Discord.js library, in its current version 13 iteration. It is maintained by York\#0001 and Discordaholic\#0001 \("Evie.Codes"\), with the help of many precious contributors that enable us to keep updating and enhancing this guide. 10 | 11 | It was born out of necessity - the first version of this guide for Discord.js version 7, more than 5 years ago, was the first of its kind because I \(Evie\) could not find any such beginner's guide and the documentation was \(and still is\) unfathomably difficult to navigate for anyone without a sufficiently advanced knowledge of JavaScript concepts. 12 | 13 | In this guide we make every effort to _teach_ you how to use the library and how to extend it with your own code. We do, however expect you to have a basic understanding of JavaScript before attempting to write bots. Discord.js \(and other discord libraries\) require some median-to-advanced concepts that might be hard to grasp for anyone that doesn't have a good footing with the language. 14 | 15 | ## Get Support 16 | 17 | If you have any questions after reading this guide, please don't hesitate to join us on our Discord server, "An Idiot's Guide". The link is located at the top of this page. If you wish to consult with the discord.js developers and official community, the Discord.js official discord is right next to ours, still at the top of the page! 18 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Table of contents 2 | 3 | * [Welcome](README.md) 4 | * [Frequently Asked Questions](frequently-asked-questions.md) 5 | * [Common Errors](common-errors.md) 6 | * [Getting Started](getting-started/README.md) 7 | * [Getting Started - Long Version](getting-started/getting-started-long-version.md) 8 | * [Getting Started - TL;DR](getting-started/getting-started-tl-dr.md) 9 | * [First Bot](first-bot/README.md) 10 | * [Your First Bot](first-bot/your-first-bot.md) 11 | * [Adding a Config File](first-bot/adding-a-config-file.md) 12 | * [Command with arguments](first-bot/command-with-arguments.md) 13 | * [A Basic Command Handler](first-bot/a-basic-command-handler.md) 14 | * [A Better Basic Command Handler](first-bot/better-basic-handler.md) 15 | * [Using Embeds in messages](first-bot/using-embeds-in-messages.md) 16 | * [Coding Guides](coding-guides/README.md) 17 | * [SQLite-Based Points System](coding-guides/sqlite-based-points-system.md) 18 | * [Using Emojis](coding-guides/using-emojis.md) 19 | * [Making a Starboard](coding-guides/making-your-own-starboard.md) 20 | * [Tracking Used Invites](coding-guides/tracking-used-invites.md) 21 | * [Using Audit Logs](coding-guides/using-audit-logs.md) 22 | * [Understanding](understanding/README.md) 23 | * [Events and Handlers](understanding/events-and-handlers.md) 24 | * [Collections](understanding/collections.md) 25 | * [Roles and Permissions](understanding/roles.md) 26 | * [Sharding](understanding/sharding.md) 27 | * [Examples](examples/README.md) 28 | * [Making an Eval command](examples/making-an-eval-command.md) 29 | * [Miscellaneous Examples](examples/miscellaneous-examples.md) 30 | * [Discord Webhooks](discord-webhooks/README.md) 31 | * [Webhooks (Part 1)](discord-webhooks/discord-webhooks-part-1.md) 32 | * [Webhooks (Part 2)](discord-webhooks/discord-webhooks-part-2.md) 33 | * [Webhooks (Part 3)](discord-webhooks/discord-webhooks-part-3.md) 34 | * [Video Guides](video-guides/README.md) 35 | * [Other Guides](other-guides/README.md) 36 | * [Installing and Using Atom](other-guides/installing-and-using-a-proper-editor.md) 37 | * [Using Git to share and update code](other-guides/using-git-to-share-and-update-code.md) 38 | * [Using Environment Variables](other-guides/env-files.md) 39 | * [Async / Await](other-guides/async-await.md) 40 | -------------------------------------------------------------------------------- /book.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["noembed"] 3 | } 4 | -------------------------------------------------------------------------------- /coding-guides/README.md: -------------------------------------------------------------------------------- 1 | # Coding Guides 2 | -------------------------------------------------------------------------------- /coding-guides/making-your-own-starboard.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: >- 3 | Starboards: Your premium source of memes, out of context messages, and funny 4 | quotes. Let's make one, shall we! 5 | --- 6 | 7 | # Making a Starboard 8 | 9 | {% hint style="info" %} 10 | In order to receive the events used in this code `messageReactionAdd` and `messageReactionRemove`, you need to enable the `GUILD_MESSAGE_REACTIONS` intent, along with `GUILD_MESSAGES` and `GUILDS`. 11 | {% endhint %} 12 | 13 | This is a long awaited feature, requested by many people. 14 | 15 | Let's begin by talking about what a starboard is. This is an example taken from the Discord.js Official Server. 16 | 17 | ![Starboard](../.gitbook/assets/starboard.png) 18 | 19 | A starboard is a popular feature in bots that serve as a channel of messages that users of the server find funny, stupid, or both! To make a functioning starboard, we need to monitor for a reaction being added to a message, and we'll do this with the `messageReactionAdd` and `messageReactionRemove` events. 20 | 21 | {% hint style="info" %} 22 | Before we start, there is one thing you need to know. This tutorial is only immediately compatible with Guidebot Class. Unless you're using it, you'll need to modify the code to your needs! 23 | {% endhint %} 24 | 25 | So, let's begin! 26 | 27 | In this block, we just do some simple setup for later on. For ease, I personally define the message object as `message`, but this is completely optional. Next, we grab the starboardChannel key from the guilds settings. 28 | 29 | {% hint style="info" %} 30 | Reminder: GuideBot and GuideBot-Class use [Enmap](https://www.npmjs.com/package/enmap) for settings! 31 | {% endhint %} 32 | 33 | Then, we preform a couple of checks on the reaction and the message. First, we check if the reaction is **NOT** the unicode star emote. Next, we preform two checks on the message, checking to see if the user who added the reaction authored the message, if the user who sent the message is the person who reacted to it, and if the message author is a bot. If none of these checks return true, we're good to move on. 34 | 35 | {% hint style="info" %} 36 | Now, it's very important that you have a starboardChannel key in your servers settings before you attempt to use this. If you're using Guidebot, you can simply run `conf add starboardChannel starboard` and apply the change to all guilds! 37 | {% endhint %} 38 | 39 | ```javascript 40 | module.exports = class { 41 | constructor(client) { 42 | this.client = client; 43 | } 44 | 45 | // This is where all the action happens. 46 | async run(reaction, user) { 47 | const message = reaction.message; 48 | // This is the first check where we check to see if the reaction is not the unicode star emote. 49 | if (reaction.emoji.name !== "⭐") return; 50 | // Here we check to see if the person who reacted is the person who sent the original message. 51 | if (message.author.id === user.id) return message.channel.send(`${user}, you cannot star your own messages.`); 52 | // This is our final check, checking to see if message was sent by a bot. 53 | if (message.author.bot) return message.channel.send(`${user}, you cannot star bot messages.`); 54 | // Here we get the starboard channel from the guilds settings. 55 | const { starboardChannel } = this.client.settings.get(message.guild.id); 56 | // Here we will find the channel 57 | const starChannel = message.guild.channels.cache.find(channel => channel.name === starboardChannel) 58 | // If there's no starboard channel, we stop the event from running any further, and tell them that they don't have a starboard channel. 59 | if (!starChannel) return message.channel.send(`It appears that you do not have a \`${starboardChannel}\` channel.`); 60 | } 61 | } 62 | ``` 63 | 64 | Now, this next block may look real complex, but it really isn't. Let's break it down. 65 | 66 | First, we declare 6 variables here, fetch, stars, star, foundStar, image, and embed. `fetch` will fetch the last 100 messages in the starboard channel, `stars` attempts to find an embed who's footer ends with the message's id. 67 | 68 | Next, we'll start an if statement for if there is already an embed in the starboard channel for this message, and we also define our final 4 variables. `star` is a simple regex to check how many stars the embed already has, `foundStar` allows us to use the color of the pre-existing embed, `image` checks if there is anything attached to the message, and `embed` is our new embed! 69 | 70 | We also use a function that hasn't been talked about yet, and that is `this.extension`. It checks the message to see if it has any images. You'll see that later, as it's not _too_ important. 71 | 72 | I told you it wasn't that complicated. Let's keep going. 73 | 74 | ```javascript 75 | // Here we fetch 100 messages from the starboard channel. 76 | const fetch = await starChannel.messages.fetch({ limit: 100 }); 77 | // We check the messages within the fetch object to see if the message that was reacted to is already a message in the starboard, 78 | const stars = fetch.find(m => m.embeds[0].footer.text.startsWith("⭐") && m.embeds[0].footer.text.endsWith(message.id)); 79 | // Now we setup an if statement for if the message is found within the starboard. 80 | if (stars) { 81 | // Regex to check how many stars the embed has. 82 | const star = /^\⭐\s([0-9]{1,3})\s\|\s([0-9]{17,20})/.exec(stars.embeds[0].footer.text); 83 | // A variable that allows us to use the color of the pre-existing embed. 84 | const foundStar = stars.embeds[0]; 85 | // We use the this.extension function to see if there is anything attached to the message. 86 | const image = message.attachments.size > 0 ? await this.extension(reaction, message.attachments.array()[0].url) : ""; 87 | const embed = new MessageEmbed() 88 | .setColor(foundStar.color) 89 | .setDescription(foundStar.description) 90 | .setAuthor(message.author.tag, message.author.displayAvatarURL()) 91 | .setTimestamp() 92 | .setFooter(`⭐ ${parseInt(star[1])+1} | ${message.id}`) 93 | .setImage(image); 94 | // We fetch the ID of the message already on the starboard. 95 | const starMsg = await starChannel.messages.fetch(stars.id); 96 | // And now we edit the message with the new embed! 97 | await starMsg.edit({ embeds: [embed] }); 98 | } 99 | ``` 100 | 101 | {% hint style="info" %} 102 | Confused with what /^\⭐\s([0-9]{1,3})\s\|\s([0-9]{17,20})/ is? It is Regex or Regular Expressions, which is used for parsing and finding text. [Read up more here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions) 103 | {% endhint %} 104 | 105 | Now, if you were to just use the code above, your starboard would only function if there was already a message in the starboard channel. Let's take care of that. 106 | 107 | Here we add an if statement that mimics and is placed after the previous block, but this time manually setting the color of the embed, and also manually setting the amount of stars the embed will have. 108 | 109 | ```javascript 110 | // Now we use an if statement for if a message isn't found in the starboard for the message. 111 | if (!stars) { 112 | // We use the this.extension function to see if there is anything attached to the message. 113 | const image = message.attachments.size > 0 ? await this.extension(reaction, message.attachments.array()[0].url) : ""; 114 | // If the message is empty, we don't allow the user to star the message. 115 | if (image === "" && message.cleanContent.length < 1) return message.channel.send(`${user}, you cannot star an empty message.`); 116 | const embed = new MessageEmbed() 117 | // We set the color to a nice yellow here. 118 | .setColor(15844367) 119 | // Here we use cleanContent, which replaces all mentions in the message with their 120 | // equivalent text. For example, an @everyone ping will just display as @everyone, without tagging you! 121 | // At the date of this edit (09/06/18) embeds do not mention yet. 122 | // But nothing is stopping Discord from enabling mentions from embeds in a future update. 123 | .setDescription(message.cleanContent) 124 | .setAuthor(message.author.tag, message.author.displayAvatarURL) 125 | .setTimestamp(new Date()) 126 | .setFooter(`⭐ 1 | ${message.id}`) 127 | .setImage(image); 128 | await starChannel.send({ embeds: [embed] }); 129 | } 130 | ``` 131 | 132 | Now, if you've been following along exactly, you have a working starboard! But if you like a TL;DR, here ya go. 133 | 134 | ```javascript 135 | module.exports = class { 136 | constructor(client) { 137 | this.client = client; 138 | } 139 | 140 | async run(reaction, user) { 141 | const message = reaction.message; 142 | if (reaction.emoji.name !== "⭐") return; 143 | if (message.author.id === user.id) return message.channel.send(`${user}, you cannot star your own messages.`); 144 | if (message.author.bot) return message.channel.send(`${user}, you cannot star bot messages.`); 145 | const { starboardChannel } = this.client.settings.get(message.guild.id); 146 | const starChannel = message.guild.channels.cache.find(channel => channel.name === starboardChannel) 147 | if (!starChannel) return message.channel.send(`It appears that you do not have a \`${starboardChannel}\` channel.`); 148 | const fetchedMessages = await starChannel.messages.fetch({ limit: 100 }); 149 | const stars = fetchedMessages.find(m => m.embeds[0].footer.text.startsWith("⭐") && m.embeds[0].footer.text.endsWith(message.id)); 150 | if (stars) { 151 | const star = /^\⭐\s([0-9]{1,3})\s\|\s([0-9]{17,20})/.exec(stars.embeds[0].footer.text); 152 | const foundStar = stars.embeds[0]; 153 | const image = message.attachments.size > 0 ? await this.extension(reaction, message.attachments.array()[0].url) : ""; 154 | const embed = new MessageEmbed() 155 | .setColor(foundStar.color) 156 | .setDescription(foundStar.description) 157 | .setAuthor(message.author.tag, message.author.displayAvatarURL) 158 | .setTimestamp() 159 | .setFooter(`⭐ ${parseInt(star[1])+1} | ${message.id}`) 160 | .setImage(image); 161 | const starMsg = await starChannel.messages.fetch(stars.id); 162 | await starMsg.edit({ embeds: [embed] }); 163 | } 164 | if (!stars) { 165 | const image = message.attachments.size > 0 ? await this.extension(reaction, message.attachments.array()[0].url) : ""; 166 | if (image === "" && message.cleanContent.length < 1) return message.channel.send(`${user}, you cannot star an empty message.`); 167 | const embed = new MessageEmbed() 168 | .setColor(15844367) 169 | .setDescription(message.cleanContent) 170 | .setAuthor(message.author.tag, message.author.displayAvatarURL) 171 | .setTimestamp(new Date()) 172 | .setFooter(`⭐ 1 | ${message.id}`) 173 | .setImage(image); 174 | await starChannel.send({ embeds: [embed] }); 175 | } 176 | } 177 | 178 | // Here we add the this.extension function to check if there's anything attached to the message. 179 | extension(reaction, attachment) { 180 | const imageLink = attachment.split("."); 181 | const typeOfImage = imageLink[imageLink.length - 1]; 182 | const image = /(jpg|jpeg|png|gif)/gi.test(typeOfImage); 183 | if (!image) return ""; 184 | return attachment; 185 | } 186 | }; 187 | ``` 188 | 189 | So, all that code up there is only for if a reaction is added. Now, we'll handle if a reaction is removed. 190 | 191 | Here, we very closely mimic the messageReactionAdd event, only this time we'll **subtract** 1 from the total amount of stars that are on the embed.' 192 | 193 | There's only a slight difference between the `messageReactionAdd` and the `messageReactionRemove` event, and that is the `messageReactionAdd` event fires when a reaction is added, and the `messageReactionRemove` event fires when a reaction is removed. 194 | 195 | ```javascript 196 | // Here we need to check if the user who removed the reaction is not the message author, as the star would then only remove one star as we did return when he added it 197 | if (message.author.id === user.id) return; 198 | if (stars) { 199 | const star = /^\⭐\s([0-9]{1,3})\s\|\s([0-9]{17,20})/.exec(stars.embeds[0].footer.text); 200 | const foundStar = stars.embeds[0]; 201 | const image = message.attachments.size > 0 ? await this.extension(reaction, message.attachments.array()[0].url) : ""; 202 | const embed = new MessageEmbed() 203 | .setColor(foundStar.color) 204 | .setDescription(foundStar.description) 205 | .setAuthor(message.author.tag, message.author.displayAvatarURL) 206 | .setTimestamp() 207 | .setFooter(`⭐ ${parseInt(star[1])-1} | ${message.id}`) 208 | .setImage(image); 209 | const starMsg = await starChannel.messages.fetch(stars.id); 210 | await starMsg.edit({ embeds: [embed] }); 211 | // Here we want to check if the message now has 0 Stars 212 | if (parseInt(star[1]) - 1 == 0) return setTimeout(() => starMsg.delete(), 1000); 213 | } 214 | ``` 215 | 216 | Here's the finalized code block for the `messageReactionRemove` event. 217 | 218 | ```javascript 219 | module.exports = class { 220 | constructor(client) { 221 | this.client = client; 222 | } 223 | 224 | async run(reaction, user) { 225 | const { message } = reaction; 226 | if (message.author.id === user.id) return; 227 | if (reaction.emoji.name !== "⭐") return; 228 | const { starboardChannel } = this.client.settings.get(message.guild.id); 229 | const starChannel = message.guild.channels.cache.find(channel => channel.name == starboardChannel) 230 | if (!starChannel) return message.channel.send(`It appears that you do not have a \`${starboardChannel}\` channel.`); 231 | const fetchedMessages = await starChannel.messages.fetch({ limit: 100 }); 232 | const stars = fetchedMessages.find(m => m.embeds[0].footer.text.startsWith("⭐") && m.embeds[0].footer.text.endsWith(reaction.message.id)); 233 | if (stars) { 234 | const star = /^\⭐\s([0-9]{1,3})\s\|\s([0-9]{17,20})/.exec(stars.embeds[0].footer.text); 235 | const foundStar = stars.embeds[0]; 236 | const image = message.attachments.size > 0 ? await this.extension(reaction, message.attachments.array()[0].url) : ""; 237 | const embed = new MessageEmbed() 238 | .setColor(foundStar.color) 239 | .setDescription(foundStar.description) 240 | .setAuthor(message.author.tag, message.author.displayAvatarURL) 241 | .setTimestamp() 242 | .setFooter(`⭐ ${parseInt(star[1])-1} | ${message.id}`) 243 | .setImage(image); 244 | const starMsg = await starChannel.messages.fetch(stars.id); 245 | await starMsg.edit({ embeds: [embed] }); 246 | if (parseInt(star[1]) - 1 == 0) return setTimeout(() => starMsg.delete(), 1000); 247 | } 248 | } 249 | 250 | // Now, it may seem weird that we use this in the messageReactionRemove event, but we still need to check if there's an image so that we can set it, if necessary. 251 | extension(reaction, attachment) { 252 | const imageLink = attachment.split("."); 253 | const typeOfImage = imageLink[imageLink.length - 1]; 254 | const image = /(jpg|jpeg|png|gif)/gi.test(typeOfImage); 255 | if (!image) return ""; 256 | return attachment; 257 | }; 258 | }; 259 | ``` 260 | -------------------------------------------------------------------------------- /coding-guides/sqlite-based-points-system.md: -------------------------------------------------------------------------------- 1 | # SQLite-Based Points System 2 | 3 | That is the focus of this guide: we'll be creating the points system with SQLite. The core of this system is using the `better-sqlite3` package that you can get from [npmjs.com](https://npmjs.com/package/better-sqlite3). 4 | 5 | ## Installation 6 | 7 | {% hint style="warning" %} 8 | **Pre-Requisites**: `better-sqlite3`, similarly to a lot of modules, gets compiled using `node-gyp` which has 2 very important requirements: Python 2.7 and the C++ Build Tools. For windows, open up an Elevated \(Administrator\) command prompt and run the following FIRST, before installing better-sqlite3: `npm i --vs2015 -g windows-build-tools`. For linux, you need `sudo apt-get install build-essential` and you need to figure out how to install Python 2.7 \(NOT Python 3!\) on your system. 9 | {% endhint %} 10 | 11 | For this guide to work, you first need to make sure you have the proper modules installed. Let's assume you already have `discord.js` installed, and go straight to installing the sqlite one and its node-gyp dependency: 12 | 13 | ```text 14 | npm i node-gyp better-sqlite3 15 | ``` 16 | 17 | ## Setting the table 18 | 19 | You've got SQLite installed, now what do we want in our table? I'll tell ya! 20 | 21 | For this example points system we want the user's ID, points and level to be compliant with Discord's Developer Terms of Service. I'm not going to go to deep in to the jargon surrounding SQL and SQLite, but the tables are made from rows and columns of data. Got it? Good, moving on! 22 | 23 | Our starting point is a very basic message handler with pre-existing commands - such as what we see in the [Command with Arguments](../first-bot/command-with-arguments.md) page of this guide. The code is as such: 24 | 25 | ```javascript 26 | const { Client, Intents } = require("discord.js"); 27 | const client = new Client({ 28 | intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MESSAGES] 29 | }); 30 | const config = require("./config.json"); // Contains the prefix, and token! 31 | 32 | client.on("ready", () => { 33 | console.log("I am ready!"); 34 | }); 35 | 36 | client.on("messageCreate", message => { 37 | if (message.author.bot) return; 38 | // This is where we'll put our code. 39 | if (message.content.indexOf(config.prefix) !== 0) return; 40 | 41 | const args = message.content.slice(config.prefix.length).trim().split(/ +/g); 42 | const command = args.shift().toLowerCase(); 43 | 44 | // Command-specific code here! 45 | }); 46 | 47 | client.login(config.token); 48 | ``` 49 | 50 | Now we've got that we should `require` sqlite and make use of it, put the following under `const client`: 51 | 52 | ```javascript 53 | const SQLite = require("better-sqlite3"); 54 | const sql = new SQLite("./scores.sqlite"); 55 | ``` 56 | 57 | We do have a small caveat - we really don't want to react on Direct Messages, so our whole code will be in a block that checks for that. We don't just want to ignore DMs because our bot itself might have DM commands! 58 | 59 | ```javascript 60 | client.on("messageCreate", message => { 61 | if (message.author.bot) return; 62 | if (message.guild) { 63 | // This is where we'll put our code. 64 | } 65 | // Rest of message handler 66 | }); 67 | ``` 68 | 69 | Your code should look like this now: 70 | 71 | ```javascript 72 | const { Client } = require("discord.js"); 73 | const client = new Client(); 74 | const config = require("./config.json"); 75 | const SQLite = require("better-sqlite3"); 76 | const sql = new SQLite("./scores.sqlite"); 77 | 78 | client.on("ready", () => { 79 | console.log("I am ready!"); 80 | }); 81 | 82 | client.on("messageCreate", message => { 83 | if (message.author.bot) return; 84 | if (message.guild) { 85 | // This is where we'll put our code. 86 | } 87 | if (message.content.indexOf(config.prefix) !== 0) return; 88 | 89 | const args = message.content.slice(config.prefix.length).trim().split(/ +/g); 90 | const command = args.shift().toLowerCase(); 91 | 92 | // Command-specific code here! 93 | }); 94 | 95 | client.login(config.token); 96 | ``` 97 | 98 | ## Dressing the Table 99 | 100 | One important thing with SQLite is that it will only create tables if we ask it to. That means, we have to make sure that the table exists. We'll do this in our `ready` event, so it will execute only once when we start the bot. Now, I'm doing _a little bit of magic_ here, which includes setting some database toggles that make SQLite faster. If you want to look up "pragma syncronous" and "pragma journal mode wal", by all means go learn what they are, but these are good _production_ settings to have. 101 | 102 | And, another bit of magic here, is that we can _prepare_ some statements beforehand, and simply _run_ them with specific values later on. This is a more advanced concept of SQL, but it should be easy to follow even if you're not familiar with it. So here's the code for the ready event: 103 | 104 | ```javascript 105 | client.on("ready", () => { 106 | // Check if the table "points" exists. 107 | const table = sql.prepare("SELECT count(*) FROM sqlite_master WHERE type='table' AND name = 'scores';").get(); 108 | if (!table['count(*)']) { 109 | // If the table isn't there, create it and setup the database correctly. 110 | sql.prepare("CREATE TABLE scores (id TEXT PRIMARY KEY, user TEXT, guild TEXT, points INTEGER, level INTEGER);").run(); 111 | // Ensure that the "id" row is always unique and indexed. 112 | sql.prepare("CREATE UNIQUE INDEX idx_scores_id ON scores (id);").run(); 113 | sql.pragma("synchronous = 1"); 114 | sql.pragma("journal_mode = wal"); 115 | } 116 | 117 | // And then we have two prepared statements to get and set the score data. 118 | client.getScore = sql.prepare("SELECT * FROM scores WHERE user = ? AND guild = ?"); 119 | client.setScore = sql.prepare("INSERT OR REPLACE INTO scores (id, user, guild, points, level) VALUES (@id, @user, @guild, @points, @level);"); 120 | }); 121 | ``` 122 | 123 | ## You get points, and you get points and EVERYBODY GETS POINTS. 124 | 125 | Now we can go right ahead and start using the database to retrieve and store points data. We'll be doing this inside the Message handler, and our very first step is to try to retrieve the existing points for a user inside the points table, which would look like this: 126 | 127 | ```javascript 128 | let score = client.getScore.get(message.author.id, message.guild.id); 129 | ``` 130 | 131 | Importantly, if the bot has never seen this user before they won't have any data, which means we have to "define" their initial values. This can be done with a simple condition, though: 132 | 133 | ```javascript 134 | if (!score) { 135 | score = { 136 | id: `${message.guild.id}-${message.author.id}`, 137 | user: message.author.id, 138 | guild: message.guild.id, 139 | points: 0, 140 | level: 1 141 | } 142 | } 143 | ``` 144 | 145 | ## Keeping Score 146 | 147 | Now that we have our initial "scores" value, we can do two things: first, increment the points. And second, calculate the level of the user. 148 | 149 | ```javascript 150 | // Increment the score 151 | score.points++; 152 | 153 | // Calculate the current level through MATH OMG HALP. 154 | const curLevel = Math.floor(0.1 * Math.sqrt(score.points)); 155 | 156 | // Check if the user has leveled up, and let them know if they have: 157 | if (score.level < curLevel) { 158 | // Level up! 159 | score.level++; 160 | message.reply(`You've leveled up to level **${curLevel}**! Ain't that dandy?`); 161 | } 162 | ``` 163 | 164 | And finally, we need to save all this back to the database. SQLite has a great "secret" feature called "INSERT OR REPLACE" and we've already created a prepared statement for this, called `client.setScore`. This will basically _update an existing row with the same_ `id`_, or create a new row if the_ `id` _isn't found_. This explains why we have the `id` field there, in case you were wondering. 165 | 166 | ```javascript 167 | // This looks super simple because it's calling upon the prepared statement! 168 | client.setScore.run(score); 169 | ``` 170 | 171 | Let's put it all together. Your code should now look like this. 172 | 173 | ```javascript 174 | const { Client, MessageEmbed } = require("discord.js"); 175 | const client = new Client(); 176 | const config = require("./config.json"); 177 | const SQLite = require("better-sqlite3"); 178 | const sql = new SQLite("./scores.sqlite"); 179 | 180 | client.on("ready", () => { 181 | // Check if the table "points" exists. 182 | const table = sql.prepare("SELECT count(*) FROM sqlite_master WHERE type='table' AND name = 'scores';").get(); 183 | if (!table['count(*)']) { 184 | // If the table isn't there, create it and setup the database correctly. 185 | sql.prepare("CREATE TABLE scores (id TEXT PRIMARY KEY, user TEXT, guild TEXT, points INTEGER, level INTEGER);").run(); 186 | // Ensure that the "id" row is always unique and indexed. 187 | sql.prepare("CREATE UNIQUE INDEX idx_scores_id ON scores (id);").run(); 188 | sql.pragma("synchronous = 1"); 189 | sql.pragma("journal_mode = wal"); 190 | } 191 | 192 | // And then we have two prepared statements to get and set the score data. 193 | client.getScore = sql.prepare("SELECT * FROM scores WHERE user = ? AND guild = ?"); 194 | client.setScore = sql.prepare("INSERT OR REPLACE INTO scores (id, user, guild, points, level) VALUES (@id, @user, @guild, @points, @level);"); 195 | }); 196 | 197 | client.on("messageCreate", message => { 198 | if (message.author.bot) return; 199 | let score; 200 | if (message.guild) { 201 | score = client.getScore.get(message.author.id, message.guild.id); 202 | if (!score) { 203 | score = { id: `${message.guild.id}-${message.author.id}`, user: message.author.id, guild: message.guild.id, points: 0, level: 1 } 204 | } 205 | score.points++; 206 | const curLevel = Math.floor(0.1 * Math.sqrt(score.points)); 207 | if (score.level < curLevel) { 208 | score.level++; 209 | message.reply(`You've leveled up to level **${curLevel}**! Ain't that dandy?`); 210 | } 211 | client.setScore.run(score); 212 | } 213 | if (message.content.indexOf(config.prefix) !== 0) return; 214 | 215 | const args = message.content.slice(config.prefix.length).trim().split(/ +/g); 216 | const command = args.shift().toLowerCase(); 217 | 218 | // Command-specific code here! 219 | }); 220 | 221 | client.login(config.token); 222 | ``` 223 | 224 | ## Let a user view their level & points 225 | 226 | Now we've got the core of this code done, we need to add a few commands, which we can add below all our code in the message handler. We've already separated the arguments and commands, so this will be pretty easy, especially since we've already loaded the `score`, calculated the points, and the level! 227 | 228 | ```javascript 229 | if (command === "points") { 230 | return message.reply(`You currently have ${score.points} points and are level ${score.level}!`); 231 | } 232 | ``` 233 | 234 | ### Addendum: Leader board and Give commands! 235 | 236 | Here are some quick & easy commands you can use, assuming the above code is used and this is still happening in the same file: 237 | 238 | ```javascript 239 | // You can modify the code below to remove points from the mentioned user as well! 240 | if (command === "give") { 241 | // Limited to guild owner - adjust to your own preference! 242 | if (!message.author.id === message.guild.ownerId) return message.reply("You're not the boss of me, you can't do that!"); 243 | 244 | const user = message.mentions.users.first() || client.users.cache.get(args[0]); 245 | if (!user) return message.reply("You must mention someone or give their ID!"); 246 | 247 | const pointsToAdd = parseInt(args[1], 10); 248 | if (!pointsToAdd) return message.reply("You didn't tell me how many points to give..."); 249 | 250 | // Get their current points. 251 | let userScore = client.getScore.get(user.id, message.guild.id); 252 | 253 | // It's possible to give points to a user we haven't seen, so we need to initiate defaults here too! 254 | if (!userScore) { 255 | userScore = { id: `${message.guild.id}-${user.id}`, user: user.id, guild: message.guild.id, points: 0, level: 1 } 256 | } 257 | userScore.points += pointsToAdd; 258 | 259 | // We also want to update their level (but we won't notify them if it changes) 260 | let userLevel = Math.floor(0.1 * Math.sqrt(score.points)); 261 | userScore.level = userLevel; 262 | 263 | // And we save it! 264 | client.setScore.run(userScore); 265 | 266 | return message.channel.send(`${user.tag} has received ${pointsToAdd} points and now stands at ${userScore.points} points.`); 267 | } 268 | 269 | if (command === "leaderboard") { 270 | const top10 = sql.prepare("SELECT * FROM scores WHERE guild = ? ORDER BY points DESC LIMIT 10;").all(message.guild.id); 271 | 272 | // Now shake it and show it! (as a nice embed, too!) 273 | const embed = new MessageEmbed() 274 | .setTitle("Leader board") 275 | .setAuthor(client.user.username, client.user.avatarURL()) 276 | .setDescription("Our top 10 points leaders!") 277 | .setColor(0x00AE86); 278 | 279 | for (const data of top10) { 280 | embed.addFields({ name: client.users.cache.get(data.user).tag, value: `${data.points} points (level ${data.level})` }); 281 | } 282 | return message.channel.send({ embed: [embed] }); 283 | } 284 | ``` 285 | -------------------------------------------------------------------------------- /coding-guides/tracking-used-invites.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: >- 3 | Using a simple cache to track invites and know which invite was used when a 4 | new member joins a guild. 5 | --- 6 | 7 | # Tracking Used Invites 8 | 9 | A relatively frequent thing people would love to know, is "what invite someone used to join". Unfortunately, the Discord API does not provide the information about the invite used to join a server. 10 | 11 | To get around this, we need to do two separate steps. The first one is to fetch all of the invites for each guild and store it in a temporary [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map). The second is to fetch a guild's invite whenever someone joins, and find which one has a new use on it. Thankfully, that's actually pretty simple! 12 | 13 | {% hint style="info" %} 14 | For this code to work, you need to have your privileged `GUILD_MEMBERS` intent enabled in your Bot's Application Page. You also need to make sure to initialize that the Intents property in your Client Options has this intent mentioned. These two vital steps will give you access to the privileged `guildMemberAdd` and `guildMemberRemove` events. 15 | {% endhint %} 16 | 17 | While the below is a fair approximation of invite tracking, it's still not perfect. There are 2 things it doesn't track: 18 | 19 | * Temporary one-use invites \(when right-clicking someone, and doing Invite To => Server\). Those exist only for a few moments and cannot be tracked at all. 20 | 21 | ## Caching Invites 22 | 23 | The first part is to fetch all the invites and keep them cached in a [Collection](https://anidiots.guide/understanding/collections/). This is done in the `ready` event. First, however, we must ensure that the cache is initialized _outside_ of the ready event. 24 | 25 | ```javascript 26 | // Initialize the invite cache 27 | const invites = new Collection(); 28 | 29 | // A pretty useful method to create a delay without blocking the whole script. 30 | const wait = require("timers/promises").setTimeout; 31 | 32 | client.on("ready", async () => { 33 | // "ready" isn't really ready. We need to wait a spell. 34 | await wait(1000); 35 | 36 | // Loop over all the guilds 37 | client.guilds.cache.forEach(async (guild) => { 38 | // Fetch all Guild Invites 39 | const firstInvites = await guild.invites.fetch(); 40 | // Set the key as Guild ID, and create a map which has the invite code, and the number of uses 41 | invites.set(guild.id, new Collection(firstInvites.map((invite) => [invite.code, invite.uses]))); 42 | }); 43 | }); 44 | ``` 45 | 46 | Moving on to the second part, we need to listen to the `inviteCreate` and `inviteDelete` event provided by the API, and update our cache accordingly. 47 | 48 | ```javascript 49 | client.on("inviteDelete", (invite) => { 50 | // Delete the Invite from Cache 51 | invites.get(invite.guild.id).delete(invite.code); 52 | }); 53 | 54 | client.on("inviteCreate", (invite) => { 55 | // Update cache on new invites 56 | invites.get(invite.guild.id).set(invite.code, invite.uses); 57 | }); 58 | 59 | ``` 60 | 61 | ## Caching Guilds that have been added/removed 62 | 63 | Since we already have the Guilds and their Invites cached, we need to check if we've been added or removed from any of the Guilds. If we're added, we need to fetch and cache all invites. If we're removed, we can just delete the data from our cache. It is as simple as this 64 | 65 | ```javascript 66 | client.on("guildCreate", (guild) => { 67 | // We've been added to a new Guild. Let's fetch all the invites, and save it to our cache 68 | guild.invites.fetch().then(guildInvites => { 69 | // This is the same as the ready event 70 | invites.set(guild.id, new Map(guildInvites.map((invite) => [invite.code, invite.uses]))); 71 | }) 72 | }); 73 | 74 | client.on("guildDelete", (guild) => { 75 | // We've been removed from a Guild. Let's delete all their invites 76 | invites.delete(guild.id); 77 | }); 78 | ``` 79 | 80 | ## Catching New Members 81 | 82 | So now that we have our `invites` object, we're ready to listen to the `guildMemberAdd` event. When a new member joins, we need to fetch all of the guild's invites once again. Then, we look through our _cached_ invites and see which one has been used, by comparing the current invite's use with the cached ones. 83 | 84 | ```javascript 85 | client.on("guildMemberAdd", async (member) => { 86 | // To compare, we need to load the current invite list. 87 | const newInvites = await member.guild.invites.fetch() 88 | // This is the *existing* invites for the guild. 89 | const oldInvites = invites.get(member.guild.id); 90 | // Look through the invites, find the one for which the uses went up. 91 | const invite = newInvites.find(i => i.uses > oldInvites.get(i.code)); 92 | // This is just to simplify the message being sent below (inviter doesn't have a tag property) 93 | const inviter = await client.users.fetch(invite.inviter.id); 94 | // Get the log channel (change to your liking) 95 | const logChannel = member.guild.channels.cache.find(channel => channel.name === "join-logs"); 96 | // A real basic message with the information we need. 97 | inviter 98 | ? logChannel.send(`${member.user.tag} joined using invite code ${invite.code} from ${inviter.tag}. Invite was used ${invite.uses} times since its creation.`) 99 | : logChannel.send(`${member.user.tag} joined but I couldn't find through which invite.`); 100 | }); 101 | ``` 102 | 103 | And... well, that's pretty much it. But.... 104 | 105 | ## There's a Catch 106 | 107 | So here's the problem. Each time you fetch invites, you're hitting the Discord API with a request for information. While that's not an issue for small bots, it might as the bot grows. I'm not saying that using this code would get you banned from the API - there is no inherent problem with querying the API if it's not abuse. However, there are a few technical issues with performance especially. 108 | 109 | The more guilds you have, the more invites are in each guild, the more data you're receiving from the API. Remember, because of the way the `ready` event works, you need to _wait_ a bit before running the fetch method, and the more guilds you have, the more time you need to wait. During this time, member joins won't correctly register because the cache doesn't exist. 110 | 111 | Furthermore, the cache itself grows in size, so you're using more memory. On top of which, it takes more time to sort through the invites the more invites exist. It might make the bot appear as being slower to respond to members joining. 112 | 113 | So to conclude, the above code works perfectly well and it _should_ not get you in trouble with Discord, but I wouldn't recommend implementing this on larger bots, especially if you're worried about memory and performance. 114 | -------------------------------------------------------------------------------- /coding-guides/using-audit-logs.md: -------------------------------------------------------------------------------- 1 | # Using Audit Logs 2 | 3 | From time to time, users in "An Idiot's Guide Official Server" need to reference audit logs for whatever reason, let it be viewing them for certain actions to adding things into the audit logs. This guide will explain everything about audit logs and how to use them. 4 | 5 | The first thing that you will need is a working Discord Bot. If you do not have one, please visit [Your First Bot](../first-bot/your-first-bot.md) to get started. Now, some things to take note of. This guide is using discord.js. The bot will need some permissions. The main permission the bot will need is `'VIEW_AUDIT_LOGS'`. This permission allows the bot to view the audit logs. 6 | 7 | Now that the permission has been established. Lets get started! 8 | 9 | Firstly, we need to know what we are doing with the audit logs. Let's log who deleted a message using the messageDelete event. This event will fire whenever a cached message is deleted in a server. 10 | 11 | ```javascript 12 | const { Permissions } = require("discord.js"); 13 | 14 | client.on("messageDelete", async (message) => { 15 | // Firstly, we need a logs channel. 16 | let logs = message.guild.channels.cache.find(channel => channel.name === "logs"); 17 | 18 | // If there is no logs channel, we can create it if we have the 'MANAGE_CHANNELS' permission 19 | // Remember, this is completely options. Use to your best judgement. 20 | if (message.guild.me.permissions.has(Permissions.FLAGS.MANAGE_CHANNELS) && !logs) { 21 | logs = await message.guild.channels.create("logs", { type: "GUILD_TEXT" }).catch(console.error); 22 | } 23 | 24 | // If we do not have permissions, console log both errors 25 | if (!logs) { 26 | return console.log("The logs channel does not exist and cannot be created"); 27 | } 28 | 29 | /* 30 | Lets establish who deleted the message. This is the actual audit logs part, yay! 31 | The "type" is how you will be searching through the audit logs, like role 32 | updates or members banned. A complete list of types can be found at the end of this page. 33 | Keep in mind the following line uses some advanced async/await promise manipulation. 34 | Explaining exactly how this works is beyond the scope of this guide. 35 | */ 36 | const entry = await message.guild.fetchAuditLogs({ type: "MESSAGE_DELETE" }).then(audit => audit.entries.first()) 37 | 38 | // Define an empty user for now. This will be used later in the guide. 39 | let user; 40 | 41 | }) 42 | ``` 43 | 44 | This is what is returned from `entry`. This information allows us to compare the time stamp, the user, the channel, and the executor. 45 | 46 | ```javascript 47 | GuildAuditLogsEntry { 48 | targetType: 'MESSAGE', 49 | actionType: 'DELETE', 50 | action: 'MESSAGE_DELETE', 51 | reason: null, 52 | executor: [Object], 53 | changes: null, 54 | id: '434116792567201792', 55 | extra: [Object], 56 | target: [Object] } 57 | ``` 58 | 59 | Notice the `reason` field. Some audit logs, like kicking and banning, can provide a reason. You can probably make the bot log when a user is banned and for whatever reason. What we want is the executor of the action. We do that by going to the `executor` target as that is where the user is stored. Below is showing what the information that the `executor` property provides us: 60 | 61 | ```javascript 62 | User { 63 | id: '286270602820452353', 64 | username: 'Zoth The Wumpus', 65 | discriminator: '6066', 66 | avatar: '57361ef0f8e23e02a44069c3dd5f5f47', 67 | bot: false, 68 | lastMessageID: '435165896198062091', 69 | lastMessage: [Object] } 70 | ``` 71 | 72 | With all the information above, we can start creating comparisons to narrow down on who really deleted the message, whether it was the author of the message or someone else. 73 | 74 | ```javascript 75 | // Please keep in mind: Discord's audit logs will not log the information if the author of that message deleted it. 76 | // I did this with a series of checks: 77 | ​ 78 | //we defined entry above, so we can use it here to check the channel id 79 | if (entry.extra.channel.id === message.channel.id 80 | ​ 81 | //Then we are checking if the target is the same as the author id 82 | && (entry.target.id === message.author.id) 83 | ​ 84 | // We are comparing time as audit logs are sometimes slow. 85 | && (entry.createdTimestamp > (Date.now() - 5000) 86 | 87 | // We want to check the count as audit logs stores the amount deleted in a channel 88 | && entry.extra.count >= 1) { 89 | user = entry.executor.username 90 | } 91 | 92 | else { 93 | // When all else fails, we can assume that the author has deleted their message. 94 | user = message.author.username 95 | } 96 | ``` 97 | 98 | Let's take a break to explain exactly whats going on in the above code block. The `Date.now()` is getting the current time \(in milliseconds\). We want to take away 5 seconds for the potential delay in the audit logs. The `entry` will be retrieving the very latest audit log entry and all of its information that goes with it. What does this information contain? Everything we need for logging. With all the given information above, let's start sending it all to a channel. 99 | 100 | ```javascript 101 | // We defined the logs channel earlier in this guide, so now we can send it to the channel! 102 | logs.send(`A message was deleted in ${message.channel.name} by ${user}`;); 103 | ``` 104 | 105 | The final code should look like this: 106 | 107 | ```javascript 108 | const { Permissions } = require('discord.js'); 109 | 110 | client.on("messageDelete", async (message) => { 111 | const logs = message.guild.channels.cache.find(channel => channel.name === "logs"); 112 | if (message.guild.me.permissions.has(Permissions.FLAGS.MANAGE_CHANNELS) && !logs) { 113 | message.guild.channels.create("logs", { type: "GUILD_TEXT" }); 114 | } 115 | if (!message.guild.me.permissions.has(Permissions.FLAGS.MANAGE_CHANNELS) && !logs) { 116 | console.log("The logs channel does not exist and tried to create the channel but I am lacking permissions") 117 | } 118 | const entry = await message.guild.fetchAuditLogs({ type: "MESSAGE_DELETE" }).then(audit => audit.entries.first()) 119 | let user = "" 120 | if (entry.extra.channel.id === message.channel.id 121 | && (entry.target.id === message.author.id) 122 | && (entry.createdTimestamp > (Date.now() - 5000)) 123 | && (entry.extra.count >= 1)) { 124 | user = entry.executor.username 125 | } 126 | 127 | else { 128 | user = message.author.username 129 | } 130 | logs.send(`A message was deleted in ${message.channel.name} by ${user}`); 131 | }) 132 | ``` 133 | 134 | And there you have it. Thats how you can view audit logs as most of them, if not all, of them work the same. 135 | 136 | [Types of Audit Logs:](https://discord.js.org/#/docs/main/master/typedef/AuditLogAction) 137 | 138 | ```javascript 139 | ALL: null 140 | GUILD_UPDATE: 1 141 | CHANNEL_CREATE: 10 142 | CHANNEL_UPDATE: 11 143 | CHANNEL_DELETE: 12 144 | CHANNEL_OVERWRITE_CREATE: 13 145 | CHANNEL_OVERWRITE_UPDATE: 14 146 | CHANNEL_OVERWRITE_DELETE: 15 147 | MEMBER_KICK: 20 148 | MEMBER_PRUNE: 21 149 | MEMBER_BAN_ADD: 22 150 | MEMBER_BAN_REMOVE: 23 151 | MEMBER_UPDATE: 24 152 | MEMBER_ROLE_UPDATE: 25 153 | MEMBER_MOVE: 26 154 | MEMBER_DISCONNECT: 27 155 | BOT_ADD: 28 156 | ROLE_CREATE: 30 157 | ROLE_UPDATE: 31 158 | ROLE_DELETE: 32 159 | INVITE_CREATE: 40 160 | INVITE_UPDATE: 41 161 | INVITE_DELETE: 42 162 | WEBHOOK_CREATE: 50 163 | WEBHOOK_UPDATE: 51 164 | WEBHOOK_DELETE: 52 165 | EMOJI_CREATE: 60 166 | EMOJI_UPDATE: 61 167 | EMOJI_DELETE: 62 168 | MESSAGE_DELETE: 72 169 | MESSAGE_BULK_DELETE: 73 170 | MESSAGE_PIN: 74 171 | MESSAGE_UNPIN: 75 172 | INTEGRATION_CREATE: 80 173 | INTEGRATION_UPDATE: 81 174 | INTEGRATION_DELETE: 82 175 | ``` 176 | -------------------------------------------------------------------------------- /coding-guides/using-emojis.md: -------------------------------------------------------------------------------- 1 | # Using Emojis 2 | 3 | Here's a fun fact you might not know about bots on Discord: They have access to every single "custom emoji" of every single guild they're in - for free. That's right, you have a feature of Nitro, free in your bot, right now! In this page we'll be taking a look at how to take advantage of these emojis, how to access them and how to display them. 4 | 5 | ## What's an Emoji? 6 | 7 | Let's start by tearing apart exactly what an Emoji is, how they're configured and how they're accessed. So here, we have an emoji: 8 | 9 | ![ayy](https://cdn.discordapp.com/emojis/305818615712579584.png) 10 | 11 | When I want to write this emoji in my chat, I simply type `:ayy:` and it turns into the above \(smaller, of course, but still\). But behind the scenes, 2 things happen for this emoji to show: 12 | 13 | * Discord looks up the emoji in my list , finds the one with the name `ayy` and looks up its ID. 14 | * It then sends the _actual_ emoji code to the server, which looks like this: `<:ayy:305818615712579584>`. This is the code that makes up the emoji. 15 | * When a client receives the above, it looks up the URL for the Emoji from its ID, to get the image location. In this case, it's: `https://cdn.discordapp.com/emojis/305818615712579584.png`. 16 | * As you can see the ID is the only thing that really matters in the URL. This ID is unique to each emoji. 17 | 18 | ## How does Discord.js store emojis? 19 | 20 | There are two places where you can grab emojis using discord.js: in the client, and in the guilds. `client.emojis.cache` is a collection of every emoji the client has access to, and `guild.emojis.cache` is a collection of the emojis of a specific guild. 21 | 22 | {% hint style="info" %} 23 | In order for the emojis to be cached, you need to have the `GUILD_EMOJIS_AND_STICKERS` intents enabled 24 | {% endhint %} 25 | 26 | If you've learned anything from [Understanding Collections](../understanding/collections.md), you might already know how to get something by ID from a collection: 27 | 28 | ```javascript 29 | const ayy = client.emojis.cache.get("305818615712579584"); 30 | ``` 31 | 32 | You might also know how to use `find` to get something with another property - so here, I can get `ayy` through its name: 33 | 34 | ```javascript 35 | const ayy = client.emojis.cache.find(emoji => emoji.name === "ayy"); 36 | ``` 37 | 38 | ## Outputting Emoji in chat 39 | 40 | But how does one output that emoji to the chat? Well, just like users and roles, emojis have a special `.toString()` method that converts them to the appropriate format. So, `ayy.toString()` will actually output the `<:ayy:305818615712579584>` we saw above, which the client turns into a proper emoji. 41 | 42 | You can also take advantage of concatenation and template literals to simplify the task, since they will automatically do the conversion for you: 43 | 44 | ```javascript 45 | if (message.content === "ayy") { 46 | const ayy = client.emojis.cache.find(emoji => emoji.name === "ayy"); 47 | message.reply(`${ayy} LMAO`); 48 | } 49 | ``` 50 | 51 | If you wanted to list all the emojis in a guild, a simple map operation on the collection should give you proper results: 52 | 53 | ```javascript 54 | if (message.content === "listemojis") { 55 | const emojiList = message.guild.emojis.cache.map(emoji => emoji.toString()).join(" "); 56 | message.channel.send(emojiList); 57 | } 58 | ``` 59 | 60 | In this example, you can list all custom emojis with \(emoji.id, emoji.image and emoji.name\). 61 | 62 | ```javascript 63 | if (message.content === "listemojis") { 64 | const emojiList = message.guild.emojis.cache.map((e, x) => `${x} = ${e} | ${e.name}`).join("\n"); 65 | message.channel.send(emojiList); 66 | } 67 | 68 | example: 69 | 450661466287112204 = :image: | name 70 | ``` 71 | 72 | ## Reacting with Emojis 73 | 74 | You can also use custom emojis as reactions to messages, using `message.react(emoji)`. In the case of custom emojis, you must use the emoji's ID, so you could do something like `message.react(ayy.id)` or `message.react("305818615712579584")` to add the `ayy` emoji as a reaction. 75 | 76 | ## But what about Unicode Emoji? 77 | 78 | Don't forget there is a very extensive collection of emojis that are built into Discord that you can have access to. Discord uses Twemoji, provided by Twitter. You can use those emojis to react to messages directly. 79 | 80 | The way that Discord expects those emojis however is that they have to be the _unicode_ character, not the "text". Meaning, you can't just do `message.send(":poop:")` and expect to see 💩 appear. You actually need to get the unicode value. How do you do that? Just escape the emoji in chat: `\:poop:` will show as 💩. You can copy/paste that inside your bot's code either in a message string, or as an emoji reaction such as `message.react("💩")`. 81 | -------------------------------------------------------------------------------- /common-errors.md: -------------------------------------------------------------------------------- 1 | # Common Errors 2 | 3 | ## Cannot find module `discord.js` 4 | 5 | ### Problem: 6 | 7 | You didn't install Discord.js or installed it in the wrong folder. 8 | 9 | ### Solution: 10 | 11 | * Make sure you are in the **correct** folder where you have your bot's files 12 | * SHIFT+Right-Click in the folder and select **Open command window here** 13 | * Run `npm init -y`, and hit enter until the wizard is complete 14 | * Run `npm i discord.js` again to install Discord. 15 | 16 | ## `Error: getaddrinfo ENOTFOUND gateway.discord.gg gateway.discord.gg:443` 17 | 18 | ### Problem: 19 | 20 | Your internet went down. 21 | 22 | ### Solution: 23 | 24 | Get better, more stable internet, or host your bot on a VPS. 25 | 26 | ## Unexpected End of Input 27 | 28 | ### Problem: 29 | 30 | ```text 31 | }); 32 | ^ 33 | SyntaxError: Unexpected end of input 34 | ``` 35 | 36 | ### Solution: 37 | 38 | Your code has an error somewhere. This is _impossible_ to troubleshoot without the **complete** code, since the error can be anywhere \(in fact the error stack often tells you it's at the end of your code\). 39 | 40 | The following trick is a lifesaver, so pay attention: Your code editor is trying to help you. Whatever editor you're using \(except notepad++.exe. Don't use notepad++!\), clicking on any \(and I mean any\) special character such as parentheses, square brackets, curly braces, double and single quotes, will automatically highlight the one that matches it. The screenshot below shows this: I clicked on the curly brace at the bottom, it shows me the one on top by highlighting it. Learn this, and how different functions and event handlers "look" like. 41 | 42 | ![They have friends](.gitbook/assets/editorhelp.png) 43 | 44 | You can check out [Installing and Using a Proper Editor](other-guides/installing-and-using-a-proper-editor.md) to help in at least knowing there are errors _before_ running your bot code. 45 | -------------------------------------------------------------------------------- /discord-webhooks/README.md: -------------------------------------------------------------------------------- 1 | # Discord Webhooks 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /discord-webhooks/discord-webhooks-part-1.md: -------------------------------------------------------------------------------- 1 | # Webhooks \(Part 1\) 2 | 3 | This has been a rather demanded topic recently, everyone wants to know how to use the webhooks, so here I am with this guide to explain the basic coverage of the webhooks. 4 | 5 | As per usual let's grab the example source code. 6 | 7 | ```javascript 8 | const { Client, Intents } = require("discord.js"); 9 | const client = new Client({ 10 | intents: [Intents.FLAGS.GUILD, Intents.FLAGS.GUILD_MESSAGES] 11 | }); 12 | 13 | client.on("ready", () => { 14 | console.log("I am ready!"); 15 | }); 16 | 17 | client.on("messageCreate", (message) => { 18 | if (message.content.startsWith("ping")) { 19 | message.channel.send("pong!"); 20 | } 21 | }); 22 | 23 | client.login("SuperSecretBotTokenHere"); 24 | ``` 25 | 26 | Right, we'll start off slow, we need to create a webhook first, if we look at the [documentation](https://discord.js.org/#/docs/main/stable/class/TextChannel?scrollTo=createWebhook) it comes with an example, that is basically all we need to create a webhook, but we'll add some polish to it and throw it into a basic command. 27 | 28 | ```javascript 29 | // This will create the webhook with the name "Example Webhook" and an example avatar. 30 | message.channel.createWebhook("Example Webhook", { avatar: "https://i.imgur.com/p2qNFag.png" }) 31 | // This will get the bot to DM you the webhook, if you use this in a selfbot, 32 | // change it to a console.log as you cannot DM yourself 33 | .then(wb => message.author.send(`Here is your webhook ${wb.url}`)) 34 | .catch(console.error); 35 | ``` 36 | 37 | This is what it should look like if you test the code. 38 | 39 | ![Created the webhook](../.gitbook/assets/wh01.png) 40 | 41 | ![Successfully created webhook](../.gitbook/assets/wh02.png) 42 | 43 | Now, that's all well and good, we can create the webhooks and get our bot to DM us, but the values are _hardcoded_, which means if we run that command, we'd get webhooks by the same name / avatar all the time, let's fix that shall we? we'll be looking at the [command arguments](../first-bot/command-with-arguments.md) page. 44 | 45 | You should have a message handler that looks something like this. 46 | 47 | ```javascript 48 | let prefix = "~"; 49 | client.on("messageCreate", message => { 50 | let args = message.content.split(" ").slice(1); 51 | if (message.content.startsWith(`${prefix}createHook`)) { 52 | message.channel.createWebhook("Example Webhook", { avatar: "https://i.imgur.com/p2qNFag.png" }) 53 | .then(wb => message.author.send(`Here is your webhook ${wb.url}`)) 54 | .catch(console.error); 55 | } 56 | }); 57 | ``` 58 | 59 | So far so good, but we're going to run into a problem, what if you want to give your webhook a name that contains spaces? Right now you'd end up with the avatar url in the name, so we're going to have to use some _regex_, [Regular Expressions](https://developer.mozilla.org/en/docs/Web/JavaScript/Guide/Regular_Expressions) is very powerful, and very daunting to start out with, but don't worry, the regex I'm going to supply for this example works, just drop it in your code and you're good. 60 | 61 | Here's the regex on it's own 62 | 63 | ```javascript 64 | /https?:\/\/.+\.(?:png|jpg|jpeg)/gi 65 | ``` 66 | 67 | Using the above regex with `match`, `replace` and `test` will allow you to isolate the image url in the string and leave the remaining string to be used as the webhook's name, there's an amazing online tool called [regex101.com](https://regex101.com/), with that tool I was able to create the above regex, here's an image of it in action. 68 | 69 | ![Regex in action.](../.gitbook/assets/wh03.png) 70 | 71 | I'm not going to go into much detail, but the fact that both of the test strings are highlighted, and it's saying there's 2 matches is all we need to know, it works with links starting with `http` and `https`, and it looks for valid extensions, which are jpg, jpeg and png. 72 | 73 | let's do the rest of the command shall we? we've got our regex, and we know we need to use `match`, `replace` and `test`, so we should `test` for a link first using our regex, if it returns false we need to notify the user, if it returns true we need to `match` and `replace` for the rest, the code can look like this. 74 | 75 | ```javascript 76 | const nameAvatar = args.join(" "); 77 | const linkCheck = /https?:\/\/.+\.(?:png|jpg|jpeg)/gi; 78 | if (!linkCheck.test(nameAvatar)) return message.reply("You must supply an image link."); 79 | const avatar = nameAvatar.match(linkCheck)[0]; 80 | const name = nameAvatar.replace(linkCheck, ""); 81 | message.channel.createWebhook(name, { avatar }) 82 | .then(wb => message.author.send(`Here is your webhook ${wb.url}. \n\nPlease keep this safe, as you could be exploited.`)) 83 | .catch(error => console.log(error)); 84 | ``` 85 | 86 | Alright, now let's throw that together with our bot code and issue the command! 87 | 88 | ![Command usage.](../.gitbook/assets/wh04.png) 89 | 90 | And let's check the channel webhooks! 91 | 92 | ![Channel Webhooks](../.gitbook/assets/wh05.png) 93 | 94 | Wooo! we did it! 95 | 96 | Now we can create webhooks on the fly via our bot code, but in the next [_chapter_](discord-webhooks-part-2.md) we'll see what we can do with them! 97 | -------------------------------------------------------------------------------- /discord-webhooks/discord-webhooks-part-2.md: -------------------------------------------------------------------------------- 1 | # Webhooks \(Part 2\) 2 | 3 | In the [last chapter](discord-webhooks-part-1.md) we covered how to create the webhooks via code, which to be honest isn't very useful, in this chapter we will continue where we left off and we will actually use the webhooks we create in some bot code. 4 | 5 | Now, one way we could use this, is to grab mentions... For some reason people think it's acceptable to mention me then shortly afterwards remove the mention if I don't respond within X seconds or minutes. 6 | 7 | We have two choices, either create a stand-alone bot, or throw it in an existing bot... For the purposes of this guide I will throw the code in a stand alone bot, but it should be pretty self-explanatory how to add this to an existing bot. 8 | 9 | Let's grab some example code... 10 | 11 | ```javascript 12 | const { Client, Intents } = require("discord.js"); 13 | const client = new Client({ 14 | intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MESSAGES] 15 | }); 16 | 17 | client.on("ready", () => { 18 | console.log("I am ready!"); 19 | }); 20 | 21 | let prefix = "~"; 22 | client.on("messageCreate", (message) => { 23 | if (message.author.id === client.user.id || message.author.bot) return; 24 | let args = message.content.split(" ").slice(1); 25 | if (message.content.startsWith(`${prefix}ping`)) { 26 | message.channel.send("pong!"); 27 | } 28 | }); 29 | 30 | client.login("SuperSecretBotTokenHere"); 31 | ``` 32 | 33 | Now, we've got the example code, we want to take our previously made webhook and grab the `id` and `token` from the URL, let's get it together! 34 | 35 | You want to start off by defining your webhook at the top of your code, don't forget to replace `Webhook ID` and `Webhook Token` with their respective values. 36 | 37 | ```javascript 38 | const { Client, Intents, WebhookClient } = require("discord.js"); 39 | const client = new Client({ 40 | intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MESSAGES] 41 | }); 42 | const mentionHook = new WebhookClient({ id: "Webhook ID", token: "Webhook Token" }); 43 | 44 | client.on("ready", () => { 45 | console.log("I am ready!"); 46 | }); 47 | 48 | let prefix = "~"; 49 | client.on("messageCreate", (message) => { 50 | if (message.author.id === client.user.id || message.author.bot) return; 51 | let args = message.content.split(" ").slice(1); 52 | if (message.content.startsWith(`${prefix}ping`)) { 53 | message.channel.send("pong!"); 54 | } 55 | }); 56 | 57 | client.login("SuperSecretBotTokenHere"); 58 | ``` 59 | 60 | Now, this bit will be a little long winded; but inside the message event you want to check for mentions, now the more mentions you can capture the better, for example there's the `@everyone` and `@here` mentions, role mentions and the direct mentions. 61 | 62 | The official documentation has the wonderful `Message.mentions.has(data)` boolean, that data can be a `GuildChannel`, `User` or `Role` Object, or a `string` representing the ID of any of the previously mentioned things, so inside the message event create a new `if statement`. 63 | 64 | ```javascript 65 | client.on("messageCreate", (message) => { 66 | if (message.author.id === client.user.id || message.author.bot) return; 67 | if (message.mentions.has("YOUR USER ID")) { 68 | // Additional Code 69 | } 70 | let args = message.content.split(" ").slice(1); 71 | if (message.content.startsWith(`${prefix}ping`)) { 72 | message.channel.send("pong!"); 73 | } 74 | }); 75 | ``` 76 | 77 | Okay, that covers direct mentions, but what about the mentioned `@everyone`, `@here` and role mentions? 78 | 79 | Well the `message` object has `mentions` which has both `everyone` and `roles`, so this is what the code will look like so far... 80 | 81 | ```javascript 82 | client.on("messageCreate", (message) => { 83 | if (message.author.id === client.user.id || message.author.bot) return; 84 | if (message.mentions.has("YOUR USER ID") || message.mentions.everyone || (message.guild && message.mentions.roles.filter(r => message.guild.members.cache.get("YOUR USER ID").roles.cache.has(r.id)).size > 0)) { 85 | // Additional Code 86 | } 87 | let args = message.content.split(" ").slice(1); 88 | if (message.content.startsWith(`${prefix}ping`)) { 89 | message.channel.send("pong!"); 90 | } 91 | }); 92 | ``` 93 | 94 | Alright, that's the mention detection stuff finished, but let me cover that last bit... 95 | 96 | ```javascript 97 | (message.guild && message.mentions.roles.filter(r => message.guild.members.cache.get("YOUR USER ID").roles.cache.has(r.id)).size > 0) 98 | ``` 99 | 100 | The `message.guild` check will make sure we're being mentioned inside a guild channel, now the next bit is a little more complex basically the code is checking for any roles that were mentioned and filtering them against our own roles if any of them match \(making the size greater than 0\) it'll return true. 101 | 102 | So far so good, we're almost half way there... We've set up the conditions to check for mentions, now we just need to ignore a few things, namely ourselves, and bots. 103 | 104 | ```javascript 105 | client.on("messageCreate", (message) => { 106 | if (message.author.id === client.user.id || message.author.bot) return; 107 | if (message.mentions.has("YOUR USER ID") || message.mentions.everyone || (message.guild && message.mentions.roles.filter(r => message.guild.members.cache.get("YOUR USER ID").roles.cache.has(r.id)).size > 0)) { 108 | if (message.author.id === "YOUR USER ID") return; 109 | // Additional Code 110 | } 111 | let args = message.content.split(" ").slice(1); 112 | if (message.content.startsWith(`${prefix}ping`)) { 113 | message.channel.send("pong!"); 114 | } 115 | }); 116 | ``` 117 | 118 | This code may look familiar, and you would be right. It's what we use to get bots to ignore themselves and other bots, but we've changed it slightly so if the `message.author` is the target user \(you\), then ignore it. 119 | 120 | Alright, now we're done with the conditions for the webhook, let's actually use the webhook! Take your code so far \(or copy the code from below\)... 121 | 122 | ```javascript 123 | const { Client, WebhookClient } = require("discord.js"); 124 | const client = new Client(); 125 | const mentionHook = new WebhookClient({ id: 'Webhook ID', token: 'Webhook Token' }); 126 | 127 | client.on("ready", () => { 128 | console.log("I am ready!"); 129 | }); 130 | 131 | let prefix = "~"; 132 | client.on("messageCreate", (message) => { 133 | if (message.author.id === client.user.id || message.author.bot) return; 134 | if (message.mentions.has("YOUR USER ID") || message.mentions.everyone || (message.guild && message.mentions.roles.filter(r => message.guild.members.cache.get("YOUR USER ID").roles.cache.has(r.id)).size > 0)) { 135 | if (message.author.id === "YOUR USER ID") return; 136 | // Additional Code 137 | } 138 | let args = message.content.split(" ").slice(1); 139 | if (message.content.startsWith(`${prefix}ping`)) { 140 | message.channel.send("pong!"); 141 | } 142 | }); 143 | 144 | client.login("SuperSecretBotTokenHere"); 145 | ``` 146 | 147 | ... and add the following line below where it says `// Additional Code` 148 | 149 | ```javascript 150 | mentionHook.send("You were mentioned!"); 151 | ``` 152 | 153 | Now, let's fill in all of the details we need to get this working \(webhook `id` and `token`, and your user `id`\) 154 | 155 | {% hint style="info" %} 156 | This webhook has long since been deleted. 157 | {% endhint %} 158 | 159 | ```javascript 160 | const { Client, WebhookClient } = require("discord.js"); 161 | const client = new Client(); 162 | const mentionHook = new WebhookClient({ id: "WEBHOOK_ID", token: "WEBHOOK_TOKEN" }); 163 | 164 | client.on("ready", () => { 165 | console.log("I am ready!"); 166 | }); 167 | 168 | let prefix = "~"; 169 | client.on("messageCreate", (message) => { 170 | if (message.author.id === client.user.id || message.author.bot) return; 171 | if (message.mentions.has("146048938242211840") || message.mentions.everyone || (message.guild && message.mentions.roles.filter(r => message.guild.members.cache.get("146048938242211840").roles.cache.has(r.id)).size > 0)) { 172 | if (message.author.id === "146048938242211840") return; 173 | // Additional Code 174 | mentionHook.send("You were mentioned!"); 175 | } 176 | let args = message.content.split(" ").slice(1); 177 | if (message.content.startsWith(`${prefix}ping`)) { 178 | message.channel.send("pong!"); 179 | } 180 | }); 181 | 182 | client.login("SuperSecretBotTokenHere"); 183 | ``` 184 | 185 | Now, just get someone to mention you! 186 | 187 | ![Getting Mentioned](../.gitbook/assets/wh06.png) 188 | 189 | If all goes well, check the channel you set the webhook for and you should see something like this... 190 | 191 | ![You got Mentioned!](../.gitbook/assets/wh07.png) 192 | 193 | That's basically it for this guide, but you can spice up the webhook notification by grabbing who mentioned you, what channel/guild you were mentioned in and what the content was \(in cases of deletion.\) 194 | 195 | In _Chapter 3_ I'll be covering third party websites such as [Zapier](https://zapier.com/) and [IFTT](https://ifttt.com/), which allows you to expand your webhook reach to things like Facebook, Twitter, GMail, and more! 196 | -------------------------------------------------------------------------------- /discord-webhooks/discord-webhooks-part-3.md: -------------------------------------------------------------------------------- 1 | # Webhooks \(Part 3\) 2 | 3 | In the last two chapters we covered how to create and use the webhook's via code. 4 | 5 | In this chapter we will cover third party services such as [Zapier](https://zapier.com/), [IFTT](https://ifttt.com/) and even [ShareX](https://getsharex.com/). If you're a member of my guild you'll know it has a \#video-guides channel where a webhook posts my videos when I release them, that youtube-to-discord bridge is provided by Zapier \(More on that later\). 6 | 7 | But before we jump to Zapier, let's cover something I should of covered in the first chapter. 8 | 9 | The [official documentation](https://discord.com/developers/docs/intro) for the [webhook endpoints](https://discord.com/developers/docs/resources/webhook) for Discord list 3 "types", you have your [Standard Webhook](https://discord.com/developers/docs/resources/webhook#execute-webhook), you have a [Slack-Compatible Webhook](https://discord.com/developers/docs/resources/webhook#execute-slackcompatible-webhook) and a [Github-Compatible Webhook](https://discord.com/developers/docs/resources/webhook#execute-githubcompatible-webhook) 10 | 11 | ![Standard Webhook](../.gitbook/assets/wh07.png) 12 | 13 | The standard webhook \(above\) basically acts like a normal message you would send with your bot, but you can set the avatar, username, etc, which means you don't need to set them when you create the webhook. 14 | 15 | ![Slack-Compatible Webhook](../.gitbook/assets/wh08.png) 16 | 17 | The slack-compatible webhook \(above\) is very fancy in comparison, they're basically `MessageEmbeds` that you see a lot of moderation bots use, you can have thumbnails, images, and more \(see the [Slack Documentation](https://api.slack.com/incoming-webhooks) for more information\). 18 | 19 | ![Github-Compatible Webhook](../.gitbook/assets/wh09.png) 20 | 21 | The github-compatible webhook \(above\) is like the slack-compatible webhook, but it's limited in it's customisability, you can only "customise" what information it sends from Github and you cannot style it. 22 | 23 | Now we've established the types of webhook Discord can handle, we'll focus on the regular webhook, and the Slack-Compatible webhook. 24 | 25 | Now there's not much we can do with third party services in regards to the standard webhook, but a friend of mine informed me, that you can use the discord webhook's with [ShareX](https://getsharex.com/). 26 | 27 | ## ShareX 28 | 29 | Now, download and install [ShareX](https://getsharex.com/) and open up the program and click the drop down menu for `Destinations` and click [Destination Settings](https://raw.githubusercontent.com/AnIdiotsGuide/discordjs-bot-guide/master/.gitbook/assets/wh10.png) then scroll down to the bottom for [Custom Uploaders](https://raw.githubusercontent.com/AnIdiotsGuide/discordjs-bot-guide/master/.gitbook/assets/wh11.png) and click it. 30 | 31 | In the text field just under "Uploaders" put in `Discord`. 32 | 33 | {% hint style="info" %} 34 | Take note of the `Add`, `Remove` and `Update` buttons a little below that field, we'll be using those later. 35 | {% endhint %} 36 | 37 | That will be the name for this custom uploader. After that, make sure the `Request type` is set to `POST`, and where it says `Request URL`, that is where you want to put the webhook URL you got from your discord channel. 38 | 39 | Under that text field is another with the label `File form name`, just put `file` in there. 40 | 41 | ![Request type, url and file from name.](../.gitbook/assets/wh12.png) 42 | 43 | We're almost finished! Now in the bottom right corner you will find a section with three fields, `URL`, `Thumbnail URL` and `Deletion URL`, the only one we want is the `URL` field. 44 | 45 | Drop this in there. `$json:attachments[0].url$` and now let's save and test! 46 | 47 | Just below the text box where you entered `Discord` for this custom uploader, hit the `Add` button and it will save the settings for you, now onto testing. 48 | 49 | On the left side, there is a series of drop down menus, click the drop down for the `Image uploader` and select Discord and give that shiny `Test` button a click. 50 | 51 | If everything went smoothly, you should have a response in the bottom right text box, it should look something like this; 52 | 53 | ![A successful response.](../.gitbook/assets/wh13.png) 54 | 55 | And if we go to our channel and check it out we should see the following; 56 | 57 | ![Success!](../.gitbook/assets/wh14.png) 58 | 59 | ## Congratulations 60 | 61 | You've successfully managed to use Discord to store your quick image snips, but we're not done yet, in the next chapter we'll cover [Zapier](https://zapier.com/) and [IFTTT](https://ifttt.com/) for more advanced usages. 62 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | -------------------------------------------------------------------------------- /examples/making-an-eval-command.md: -------------------------------------------------------------------------------- 1 | # Making an Eval command 2 | 3 | ## What is eval exactly? 4 | 5 | In JavaScript \(and node\), `eval()` is a function that evaluates any string _as javascript code_ and actually executes it. Meaning, if I `eval(2+2)`, eval will return `4`. If I eval `client.guilds.cache.size`, it'll return however many guilds the bot is currently on. And if I eval `client.guilds.cache.map(g=>g.name).join('\n')` then it will return a list of guild names separated by a line return. Cool, right? 6 | 7 | ## But eval is dangerous 8 | 9 | I'll say this in a way that's probably dead simple to understand: _Giving someone access to_ `eval()` _is literally like sitting them **at your computer**, with **full admin access**, and then **stepping out of the room**._ `eval()` in browser javascript is trivial and not dangerous - you're running in your own browser, anything you fuck up is going to be on your own PC, not the web servers. 10 | 11 | But `eval()` in **node** is really, really dangerous and powerful. Because it can run anything **you** run as a bot, and it can also run code you're not **expecting** to run, if someone else has access to it. **Node.js** has access to your **hard drive**. The whole thing. Every bit of it. To understand what this means, look at the following command: `rm -rf / --no-preserve-root` . Do you know what this command does? **It deletes your entire server's hard drives**. I mean, it only works on Linux, but most VPS systems and most hosting providers are on UNIX-based systems. 12 | 13 | Another thing that's on your hard drive is passwords. Have a config file with your database password? I can get that. `config.json` with your token and other API keys in it? I can grab that easy. Hell I can just grab your token straight from the `client` object if I know how to. 14 | 15 | ## Securing your eval 16 | 17 | So first, we need to understand the \#1 rule when using `eval` commands: 18 | 19 | {% hint style="danger" %} 20 | _**NEVER EVER GIVE EVAL PERMS TO ANYONE ELSE**_ 21 | {% endhint %} 22 | 23 | I don't care if it's a server owner, someone you've been talking to for months, you **cannot** trust anyone with eval. There's only one exception to this rule: Someone you know **in real life** that you can punch in the face when they actually destroy half your server or mistakenly ban everyone in every server your bot is in. Eval bypasses any command-based permission you might have, it bypasses all security checks. Eval is all powerful. 24 | {% endhint %} 25 | 26 | So how do you secure it? Simple: only allow use from your own user ID. So for example my user ID is `139412744439988224` so I check whether the message author's ID is mine, which we added into our config at the start: 27 | 28 | ```javascript 29 | { 30 | // The rest of the config 31 | "ownerID": "139412744439988224" 32 | } 33 | ``` 34 | 35 | In the code for the bot: 36 | 37 | ```javascript 38 | // If the message author's ID does not equal 39 | // our ownerID, get outta there! 40 | if (message.author.id !== config.ownerID) return; 41 | ``` 42 | 43 | It's as simple as that to protect the command directly inside of your condition or file or whatever. Of course, if you have some sort of command handler there's most likely a way to restrict to an ID too. This isn't specific to discord.js: there's always a way to do this. If there isn't \(if a command handler won't let you restrict by ID\), then **find a new library!**. 44 | 45 | ## Simple Eval Command 46 | 47 | So now you've been thoroughly briefed on the dangers of Eval, let's take a look at how to implement a simple eval command. 48 | 49 | First though I strongly suggest using the following function \(plop it outside of any event handler/functions you have, so it's accessible anywhere\). This function prevents the use of actual mentions within the return line by adding a zero-width character between the `@` and the first character of the mention - blocking the mention from happening. 50 | 51 | {% hint style="info" %} 52 | The following clean snippet is _**REQUIRED**_ to make the eval work. 53 | {% endhint %} 54 | 55 | ```javascript 56 | // This function cleans up and prepares the 57 | // result of our eval command input for sending 58 | // to the channel 59 | const clean = async (text) => { 60 | // If our input is a promise, await it before continuing 61 | if (text && text.constructor.name == "Promise") 62 | text = await text; 63 | 64 | // If the response isn't a string, `util.inspect()` 65 | // is used to 'stringify' the code in a safe way that 66 | // won't error out on objects with circular references 67 | // (like Collections, for example) 68 | if (typeof text !== "string") 69 | text = require("util").inspect(text, { depth: 1 }); 70 | 71 | // Replace symbols with character code alternatives 72 | text = text 73 | .replace(/`/g, "`" + String.fromCharCode(8203)) 74 | .replace(/@/g, "@" + String.fromCharCode(8203)); 75 | 76 | // Send off the cleaned up result 77 | return text; 78 | } 79 | ``` 80 | 81 | Alright, So let's get down to the brass tax: The actual eval command. Here it is in all its glory, assuming you've followed this guide all along: 82 | 83 | ```javascript 84 | // This command goes inside of our message event handler 85 | client.on("messageCreate", async (message) => { 86 | 87 | // Get our input arguments 88 | const args = message.content.split(" ").slice(1); 89 | 90 | // The actual eval command 91 | if (message.content.startsWith(`${config.prefix}eval`)) { 92 | 93 | // If the message author's ID does not equal 94 | // our ownerID, get outta there! 95 | if (message.author.id !== config.ownerID) 96 | return; 97 | 98 | // In case something fails, we to catch errors 99 | // in a try/catch block 100 | try { 101 | // Evaluate (execute) our input 102 | const evaled = eval(args.join(" ")); 103 | 104 | // Put our eval result through the function 105 | // we defined above 106 | const cleaned = await clean(evaled); 107 | 108 | // Reply in the channel with our result 109 | message.channel.send(`\`\`\`js\n${cleaned}\n\`\`\``); 110 | } catch (err) { 111 | // Reply in the channel with our error 112 | message.channel.send(`\`ERROR\` \`\`\`xl\n${cleaned}\n\`\`\``); 113 | } 114 | 115 | // End of our command 116 | } 117 | 118 | // End of our message event handler 119 | }); 120 | ``` 121 | 122 | That's it. That's the command. Note a couple of things though: 123 | 124 | * Your IDE or editor may scream at the `eval(code)` for being `unsafe`. See the rest of this page for WHY it says that. Can't say you haven't been warned! 125 | * If the response is more than 2000 characters this will error when it tries to send the results. This is due to the character limit of messages sent to channels. You will need to handle splitting this up and sending the larger results if you want to be able to send larger results. 126 | 127 | {% hint style="info" %} 128 | The above eval command WILL NOT censor/remove your client token if it is returned as part of the result output. To censor your token from result outputs, add the following code. 129 | {% endhint %} 130 | 131 | Adjust our `clean()` function to receive the client as an input like so. 132 | 133 | ```javascript 134 | const clean = async (client, text) => { 135 | // The rest of the code 136 | } 137 | ``` 138 | 139 | Be sure to supply the client as an input when we call the function later in the code. 140 | 141 | ```javascript 142 | // ... 143 | 144 | try { 145 | // Put our eval result through the function we defined above 146 | const cleaned = await clean(client, evaled); 147 | } 148 | 149 | // ... 150 | ``` 151 | 152 | ```javascript 153 | const clean = async (client, text) => { 154 | // ... 155 | 156 | // You will need to place this inside the clean 157 | // function, before the result is returned. 158 | text = text.replaceAll(client.token, "[REDACTED]"); 159 | 160 | // ... 161 | } 162 | ``` 163 | 164 | ## Final Reminder 165 | 166 | {% hint style="danger" %} 167 | **I AM NOT RESPONSIBLE IF YOU FUCK UP, AND NEITHER ARE ANY OF THE DISCORD.JS USERS AND DEVELOPERS** 168 | {% endhint %} 169 | 170 | Hopefully the warnings were clear enough to help you understand the dangers... but the idea of eval is still attractive enough that you'll use it for yourself anyway! 171 | -------------------------------------------------------------------------------- /examples/miscellaneous-examples.md: -------------------------------------------------------------------------------- 1 | # Miscellaneous Examples 2 | 3 | ## Conventions Used in Examples 4 | 5 | Conventions are important - they are the agreements on which society functions. So let's take a moment to agree on a few. 6 | 7 | ### Placeholders 8 | 9 | A "Placeholder" is a piece of text that _replaces something else_. In these FAQs we assume the following variables as "placeholders" for your own: 10 | 11 | * `client` is a placeholder that corresponds to your `client` variable, as we've covered at the end of the [Getting Started](../getting-started/getting-started-long-version.md) guide. `client.on("ready", () => {` for example. 12 | * `message` is a placeholder corresponds to your `messageCreate` event's variable which looks something like this: `client.on("messageCreate", message => {`. 13 | 14 | ## Examples 15 | 16 | ### awaitMessages 17 | 18 | {% hint style="info" %} 19 | Example by: Lewdcario \(84484653687267328\) 20 | {% endhint %} 21 | 22 | This example sends a question and waits to receive a message response that says `test`. 23 | 24 | ```javascript 25 | message.channel.send("What tag would you like to see? This will await will be cancelled in 30 seconds. It will finish when you provide a message that goes through the filter the first time.") 26 | .then(() => { 27 | message.channel.awaitMessages(response => response.content === "test", { 28 | max: 1, 29 | time: 30000, 30 | errors: ["time"], 31 | }) 32 | .then((collected) => { 33 | message.channel.send(`The collected message was: ${collected.first().content}`); 34 | }) 35 | .catch(() => { 36 | message.channel.send("There was no collected message that passed the filter within the time limit!"); 37 | }); 38 | }); 39 | ``` 40 | 41 | ### Creating a guild 42 | 43 | Discord quietly changed the Create Guild API endpoint, small bots \(10 guilds or fewer\) are able to create guilds programmatically now. This example will have your bot create a new guild and create a role with the administrator permission, and the single line of code at the bottom will apply it to you when you execute it when you join the guild. 44 | 45 | ```javascript 46 | const { Permissions } = require("discord.js"); 47 | 48 | /* ES6 Promises */ 49 | client.guilds.create("Example Guild").then(guild => { 50 | guild.channels.cache.first().createInvite() 51 | .then(invite => client.users.cache.get("").send(invite.url)); 52 | guild.roles.create({ name: "Example Role", permissions: Permissions.FLAGS.ADMINISTRATOR }) 53 | .then(role => client.users.cache.get("").send(role.id)) 54 | .catch(error => console.log(error)) 55 | }); 56 | 57 | /* ES8 async/await */ 58 | async function createGuild(client, message) { 59 | const { Permissions } = require("discord.js"); 60 | try { 61 | const guild = await client.guilds.create("Example Guild"); 62 | const defaultChannel = guild.channels.cache.find(channel => channel.permissionsFor(guild.me).has(Permissions.FLAGS.SEND_MESSAGES)); 63 | const invite = await defaultChannel.createInvite(); 64 | await message.author.send(invite.url); 65 | const role = await guild.roles.create({ name: "Example Role", permissions: Permission.FLAGS.ADMINISTRATOR }); 66 | await message.author.send(role.id); 67 | } catch (e) { 68 | console.error(e); 69 | } 70 | } 71 | createGuild(client, message); 72 | // Run this once you've joined the bot created guild. 73 | message.member.roles.add(""); 74 | ``` 75 | 76 | ### Command Cooldown 77 | 78 | {% hint style="info" %} 79 | Example by ItsJordan\#4297 80 | {% endhint %} 81 | 82 | Adds a cooldown to your commands so the user will have to wait 2.5 seconds between each command. 83 | 84 | You can change the nature of the cool down by changing the return to something else. 85 | 86 | ```javascript 87 | // First, this must be at the top level of your code, **NOT** in any event! 88 | const talkedRecently = new Set(); 89 | ``` 90 | 91 | ```javascript 92 | // Inside your messageCreate event, this code will stop any command during cooldown. 93 | // Should be placed after your code that checks for bots & prefix, for best performance 94 | 95 | if (talkedRecently.has(message.author.id)) 96 | return; 97 | 98 | // Adds the user to the set so that they can't talk for 2.5 seconds 99 | talkedRecently.add(message.author.id); 100 | setTimeout(() => { 101 | // Removes the user from the set after 2.5 seconds 102 | talkedRecently.delete(message.author.id); 103 | }, 2500); 104 | ``` 105 | 106 | ### Mention Prefix 107 | 108 | {% hint style="info" %} 109 | Regex or Regular Expressions are used to match character combinations in strings. Read more about them [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions). You can create and test them [here](https://regex101.com/?flavor=javascript) 110 | {% endhint %} 111 | 112 | Requiring a little bit of regex, this will catch when a message starts with the bot being mentioned. 113 | 114 | ```javascript 115 | client.on("messageCreate", message => { 116 | const prefixMention = new RegExp(`^<@!?${client.user.id}> `); 117 | const prefix = message.content.match(prefixMention) ? message.content.match(prefixMention)[0] : '!'; 118 | 119 | // Go ahead with the rest of your code! 120 | }); 121 | ``` 122 | 123 | ### Multiple Prefixes 124 | 125 | Let's make it 3 prefixes, this is fairly universal. This could also be in the config.json once you get there. So we'll start by finding if the message content starts with either of the prefixes mentioned in the array. If it doesn't, we return. 126 | 127 | ```javascript 128 | client.on("messageCreate", message => { 129 | const prefixes = ["!", "?", "/"]; 130 | const prefix = prefixes.find(p => message.content.startsWith(p)); 131 | if (!prefix) return; 132 | 133 | // Go ahead with the rest of your code! 134 | }); 135 | ``` 136 | 137 | ### Multiple Prefixes Extension 138 | 139 | ```javascript 140 | client.on("messageCreate", async message => { 141 | const prefixes = ["!", "\\?", "\\/", `<@!?${client.user.id}> `]; 142 | const prefixRegex = new RegExp(`^(${prefixes.join("|")})`); 143 | const prefix = message.content.match(prefixRegex); 144 | 145 | // Go ahead with the rest of your code! 146 | }); 147 | ``` 148 | 149 | ### Purging a Channel 150 | 151 | {% hint style="info" %} 152 | Example by Hindsight \(139412744439988224\) 153 | {% endhint %} 154 | 155 | Example usage: !purge @user 10 , or !purge 25 156 | 157 | ```javascript 158 | const user = message.mentions.users.first(); 159 | 160 | if (!/^\d+$/.test(message.content.split(" ")[1])) return message.reply('Please provide a valid number'); 161 | // Check if the provided argument is completely a number. We run this because parseInt can parse numbers like this 564gb, leading to some undesirable results 162 | 163 | // Parse Amount 164 | const amount = !!parseInt(message.content.split(" ")[1]) ? parseInt(message.content.split(" ")[1]) : parseInt(message.content.split(" ")[2]) 165 | 166 | if (!amount) return message.reply("Must specify an amount to delete!"); 167 | if (!amount && !user) return message.reply("Must specify a user and amount, or just an amount, of messages to purge!"); 168 | // Fetch 100 messages (will be filtered and lowered up to max amount requested) 169 | message.channel.messages.fetch({ 170 | limit: 100, 171 | }).then((messages) => { 172 | if (user) { 173 | const filterBy = user ? user.id : Client.user.id; 174 | messages = messages.filter(m => m.author.id === filterBy).array().slice(0, amount); 175 | } 176 | message.channel.bulkDelete(messages).catch(error => console.log(error.stack)); 177 | }); 178 | ``` 179 | 180 | ### Swear Detector 181 | 182 | This quick & dirty swear detector takes an array of swear words we don't want to see, and triggers on it. 183 | 184 | ```javascript 185 | const swearWords = ["darn", "shucks", "frak", "shite"]; // Make sure all of the words are lowercased only. 186 | if (swearWords.some(word => message.content.toLowerCase().includes(word.toLowerCase()))) { // Lowercase the message content for better matching 187 | message.reply("Oh no you said a bad word!!!"); 188 | // Or just do message.delete(); 189 | } 190 | ``` 191 | 192 | ### Kicking users \(or bots\) from a voice channel 193 | 194 | Support for kicking members from voice channels has now been added by Discord and can be achieved by doing the following. 195 | 196 | ```javascript 197 | const { Permissions } = require("discord.js"); 198 | 199 | // Make sure the bot user has permissions to move members in the guild: 200 | if (!message.guild.me.permissions.has(Permissions.FLAGS.MOVE_MEMBERS)) return message.reply("Missing the required `Move Members` permission."); 201 | 202 | // Get the mentioned user/bot and check if they're in a voice channel: 203 | const member = message.mentions.members.first(); 204 | if (!member) return message.reply("You need to @mention a user/bot to kick from the voice channel."); 205 | if (!member.voice.channel) return message.reply("That user/bot isn't in a voice channel."); 206 | 207 | // Now we set the member's voice channel to null, in other words disconnecting them from the voice channel. 208 | member.voice.setChannel(null); 209 | 210 | // Finally, pass some user response to show it all worked out: 211 | message.react("👌"); 212 | /* or just "message.reply", etc.. up to you! */ 213 | ``` 214 | 215 | This does the same as clicking the disconnect button on a user or bot while they are in a voice channel. 216 | -------------------------------------------------------------------------------- /first-bot/README.md: -------------------------------------------------------------------------------- 1 | # First Bot 2 | -------------------------------------------------------------------------------- /first-bot/a-basic-command-handler.md: -------------------------------------------------------------------------------- 1 | # A Basic Command Handler 2 | 3 | A _Command Handler_ is essentially a way to separate your commands into different files, instead of having a bunch of `if/else` conditions inside your code \(or a `switch/case` if you're being fancy\). 4 | 5 | In this case, the code shows you how to separate each command into its own file. This means that each command can be _edited_ separately, and also _reloaded_ without the need to restart your bot. Yes, really! 6 | 7 | {% hint style="info" %} 8 | Want a better, updated version of this code? We're now maintaining this command handler at the community level. [Guide Bot is on Github](https://github.com/AnIdiotsGuide/guidebot/) and not only can you use the code, you can also contribute if you feel proficient enough! 9 | {% endhint %} 10 | 11 | ## What you need to know 12 | 13 | In order to correctly write and use a command handler, I would suggest you get familiar with a few things. 14 | 15 | * Check out [Introduction to Modules](https://js.evie.dev/modules) for information on modules \(which we'll use for each command\) 16 | * Understand how [Events](../understanding/events-and-handlers.md) work, and how each event has different arguments provided. 17 | * Have a good grasp of, at the very least, [Commands with Arguments](command-with-arguments.md), which we'll be using as a base for most of our code. 18 | 19 | ## Main File Changes 20 | 21 | Because we're creating a separate file \(module\) for each event and each commands, our main file \(app.js, or index.js, or whatever you're calling it\) will change drastically from a list of commands to a simple file that loads other files. 22 | 23 | Two main loops are needed to execute this master plan. First off, the one that will load all the `events` files. Each event will need to have a file in that folder, named _exactly_ like the event itself. So for `messageCreate` we want `./events/messageCreate.js`, for `guildBanAdd` we want `./events/guildBanAdd.js` , etc. 24 | 25 | ```javascript 26 | // Read the Files in the Events Directory and filter files that ends with .js 27 | const files = fs.readdirSync("./events").filter(file => file.endsWith(".js")); 28 | // Loop over each file 29 | for (const file of files) { 30 | // Split the file at its extension and get the event name 31 | const eventName = file.split(".")[0]; 32 | // Require the file 33 | const event = require(`./events/${file}`); 34 | // super-secret recipe to call events with all their proper arguments *after* the `client` var. 35 | // without going into too many details, this means each event will be called with the client argument, 36 | // followed by its "normal" arguments, like message, member, etc etc. 37 | // This line is awesome by the way. Just sayin'. 38 | client.on(eventName, event.bind(null, client)); 39 | } 40 | ``` 41 | 42 | The second loop is going to be for the commands themselves. For a couple of reasons, we want to put the commands inside of a structure that we can refer to later - we'll use a Discord Collection: 43 | 44 | ```javascript 45 | client.commands = new Discord.Collection(); 46 | // Read the Commands Directory, and filter the files that end with .js 47 | const commands = fs.readdirSync("./commands").filter(file => file.endsWith(".js")); 48 | // Loop over the Command files 49 | for (const file of commands) { 50 | // Get the command name from splitting the file 51 | const commandName = file.split(".")[0]; 52 | // Require the file 53 | const command = require(`./commands/${file}`); 54 | 55 | console.log(`Attempting to load command ${commandName}`); 56 | // Set the command to a collection 57 | client.commands.set(commandName, command); 58 | } 59 | ``` 60 | 61 | Ok so with that being said, our main file now looks like this \(how _clean_ is that, really?\): 62 | 63 | ```javascript 64 | const { Client, Intents, Collection } = require("discord.js"); 65 | const fs = require("fs"); 66 | 67 | const client = new Client({ 68 | intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MESSAGES] 69 | }); 70 | const config = require("./config.json"); 71 | // We also need to make sure we're attaching the config to the CLIENT so it's accessible everywhere! 72 | client.config = config; 73 | client.commands = new Collection(); 74 | 75 | const events = fs.readdirSync("./events").filter(file => file.endsWith(".js")); 76 | for (const file of events) { 77 | const eventName = file.split(".")[0]; 78 | const event = require(`./events/${file}`); 79 | client.on(eventName, event.bind(null, client)); 80 | } 81 | 82 | const commands = fs.readdirSync("./commands").filter(file => file.endsWith(".js")); 83 | for (const file of commands) { 84 | const commandName = file.split(".")[0]; 85 | const command = require(`./commands/${file}`); 86 | 87 | console.log(`Attempting to load command ${commandName}`); 88 | client.commands.set(commandName, command); 89 | } 90 | 91 | client.login(config.token); 92 | ``` 93 | 94 | ## Our first Event: Message 95 | 96 | The `messageCreate` event is obviously the most important one, as it will receive all messages sent to the bot. Create the `./events/messageCreate.js` file \(make sure it's spelled _exactly_ like that\) and look at this bit of code: 97 | 98 | ```javascript 99 | module.exports = (client, message) => { 100 | // Ignore all bots 101 | if (message.author.bot) return; 102 | 103 | // Ignore messages not starting with the prefix (in config.json) 104 | if (message.content.indexOf(client.config.prefix) !== 0) return; 105 | 106 | // Our standard argument/command name definition. 107 | const args = message.content.slice(client.config.prefix.length).trim().split(/ +/g); 108 | const command = args.shift().toLowerCase(); 109 | 110 | // Grab the command data from the client.commands Enmap 111 | const cmd = client.commands.get(command); 112 | 113 | // If that command doesn't exist, silently exit and do nothing 114 | if (!cmd) return; 115 | 116 | // Run the command 117 | cmd.run(client, message, args); 118 | }; 119 | ``` 120 | 121 | There are more things we could do here, like get per-guild settings or check permissions before running the command, etc. Out of a desire to keep this page simple, I've avoided all that extra code, but you can still find it on the [GuideBot repository](https://github.com/AnIdiotsGuide/guidebot/)! 122 | 123 | ## Example commands 124 | 125 | This would be the content of the `./commands/ping.js` file, which is called with `!ping` \(assuming `!` as a prefix\) 126 | 127 | ```javascript 128 | exports.run = (client, message, args) => { 129 | message.channel.send("pong!").catch(console.error); 130 | } 131 | 132 | exports.name = "ping"; 133 | ``` 134 | 135 | Another example would be the more complex `./commands/kick.js` command, called using `!kick @user` 136 | 137 | ```javascript 138 | exports.run = (client, message, [mention, ...reason]) => { 139 | const modRole = message.guild.roles.cache.find(role => role.name === "Mods"); 140 | if (!modRole) 141 | return console.log("The Mods role does not exist"); 142 | 143 | if (!message.member.roles.cache.has(modRole.id)) 144 | return message.reply("You can't use this command."); 145 | 146 | if (message.mentions.members.size === 0) 147 | return message.reply("Please mention a user to kick"); 148 | 149 | if (!message.guild.me.permissions.has("KICK_MEMBERS")) 150 | return message.reply("I don't have the `KICK_MEMBERS` permission"); 151 | 152 | const kickMember = message.mentions.members.first(); 153 | 154 | kickMember.kick(reason.join(" ")).then(member => { 155 | message.reply(`${member.user.username} was successfully kicked.`); 156 | }); 157 | }; 158 | 159 | exports.name = "kick"; 160 | ``` 161 | 162 | Notice the structure on the first line. `exports.run` is the "function name" that is exported, with 3 arguments: `client` \(the client\), `message` \(the message variable from the handler\) and `args`. Here, `args` is replaced by fancy destructuring that captures the `reason` \(the rest of the message after the mention\) in an array. See [Commands with Arguments](command-with-arguments.md) for details. 163 | 164 | ## Other Events 165 | 166 | Events are handled almost exactly in the same way, except that the number of arguments depends on which event it is. For example, the `ready` event: 167 | 168 | ```javascript 169 | module.exports = (client) => { 170 | console.log(`Ready to serve in ${client.channels.cache.size} channels on ${client.guilds.cache.size} servers, for a total of ${client.users.cache.size} users.`); 171 | } 172 | ``` 173 | 174 | Note that the `ready` event normally doesn't have any arguments, it's just `()`. But because we're in separate modules, it's necessary to "pass" the `client` variable to it or it would not be accessible. That's what our fancy `bind` is for in the main file! 175 | 176 | Here's another example with the `guildMemberAdd` event: 177 | 178 | ```javascript 179 | const { Permissions } = require("discord.js"); 180 | 181 | module.exports = (client, member) => { 182 | const defaultChannel = member.guild.channels.cache.find(channel => channel.permissionsFor(guild.me).has(Permissions.FLAGS.SEND_MESSAGES)); 183 | defaultChannel.send(`Welcome ${member.user} to this server.`).catch(console.error); 184 | } 185 | ``` 186 | 187 | Now we have `client` and also `member` which is the argument provided _by_ the `guildMemberAdd` event. 188 | 189 | ## BONUS: The "reload" command 190 | 191 | Because of the way `require()` works in node, if you modify any of the command files in `./commands` , the changes are not reflected immediately when you call that command again - because `require()` _caches_ the file in memory instead of reading it every time. While this is great for efficiency, it means we need to clear that cached version if we change commands. 192 | 193 | The _Reload_ command does just that, simply deletes the cache so the next time that specific command is run, it'll refresh its code from the file. 194 | 195 | ```javascript 196 | exports.run = (client, message, args) => { 197 | if (!args || args.length < 1) return message.reply("Must provide a command name to reload."); 198 | const commandName = args[0]; 199 | // Check if the command exists and is valid 200 | if (!client.commands.has(commandName)) { 201 | return message.reply("That command does not exist"); 202 | } 203 | // the path is relative to the *current folder*, so just ./filename.js 204 | delete require.cache[require.resolve(`./${commandName}.js`)]; 205 | // We also need to delete and reload the command from the client.commands Enmap 206 | client.commands.delete(commandName); 207 | const props = require(`./${commandName}.js`); 208 | client.commands.set(commandName, props); 209 | message.reply(`The command ${commandName} has been reloaded`); 210 | }; 211 | 212 | exports.name = "reload"; 213 | ``` 214 | 215 | Remember that all of this is just a fairly basic version of the GuideBot command handler which also has permissions, levels, per-guild configurations, and a whole lot of example commands and events! [Head on over to Github](https://github.com/AnIdiotsGuide/guidebot/) to see the completed handler. 216 | 217 | Next up is making this basic command handler _better_ with the addition of [slash commands](slash-commands/md). 218 | -------------------------------------------------------------------------------- /first-bot/adding-a-config-file.md: -------------------------------------------------------------------------------- 1 | # Adding a Config File 2 | 3 | Now that you have a bot up and running, we can start splitting it into some more useful parts. And the first part of this is separating some of the variables we have defined into a configuration file, `config.json`. We'll be loading this file on boot. 4 | 5 | {% hint style="danger" %} 6 | Putting your token in a config file is fine, but **DO NOT COMMIT IT TO GITHUB** or any other public location. Usually, adding a `.gitignore` file to your project should be enough. [Here's an example](https://github.com/github/gitignore/blob/master/Node.gitignore). Simply add a line that says `config.json` to that file, save it in your project root and you should be good. [More Details in this video](https://www.youtube.com/watch?v=iyNIHQkVGao), and [this page](../other-guides/using-git-to-share-and-update-code.md). 7 | {% endhint %} 8 | 9 | ## Why a config file? 10 | 11 | One of the advantages of having a configuration file is that you can safely copy your bot's code into, say, hastebin.com to show people, and your token won't be in there. A second advantage is that you can upload the code to a repository like github and, as long as you ignore the config file, your bot can be shared but remain secure. We'll see that in action in a future walk through. 12 | 13 | ## Step 1: The config file 14 | 15 | The 2 things that we can add to the config file are: 16 | 17 | * The bot's token 18 | * The prefix 19 | 20 | Simply take the following example, and create a new file in the same folder as your bot's file, calling it `config.json`: 21 | 22 | ```javascript 23 | { 24 | "token": "insert-bot-token-here", 25 | "prefix": "!" 26 | } 27 | ``` 28 | 29 | ## Step 2: Require the config file 30 | 31 | At the top of your bot file, you need to add a line that will load this configuration, and put it in a variable. This is what it looks like: 32 | 33 | ```javascript 34 | const { Client, Intents } = require("discord.js"); 35 | const client = new Client({ 36 | intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MESSAGES] 37 | }); 38 | const config = require("./config.json"); 39 | ``` 40 | 41 | This means that now, `config` is your configuration object. `config.token` is your token, `config.prefix` is your prefix! Simple enough. 42 | 43 | ## Step 3: Using `config` in your code 44 | 45 | So let's use what we did earlier, and use the token from the config file, instead of putting it directly in the file. The last line of our bot looks like this: 46 | 47 | ```javascript 48 | client.login("SuperSecretBotTokenHere"); 49 | ``` 50 | 51 | And we simply need to change it to this: 52 | 53 | ```javascript 54 | client.login(config.token); 55 | ``` 56 | 57 | The other thing we have, is of course the prefix. Again from before, we have this line in our message handler: 58 | 59 | ```javascript 60 | const prefix = "!"; 61 | client.on("messageCreate", (message) => { 62 | if (!message.content.startsWith(prefix) || message.author.bot) return; 63 | 64 | if (message.content.startsWith(`${prefix}ping`)) { 65 | message.channel.send("pong!"); 66 | } else 67 | 68 | if (message.content.startsWith(`${prefix}foo`)) { 69 | message.channel.send("bar!"); 70 | } 71 | }); 72 | ``` 73 | 74 | We're using `prefix` in a few places, so we need to change them all. Here's how it looks like after the changes: 75 | 76 | ```javascript 77 | client.on("messageCreate", (message) => { 78 | if (!message.content.startsWith(config.prefix) || message.author.bot) return; 79 | 80 | if (message.content.startsWith(`${config.prefix}ping`)) { 81 | message.channel.send("pong!"); 82 | } else 83 | 84 | if (message.content.startsWith(`${config.prefix}foo`)) { 85 | message.channel.send("bar!"); 86 | } 87 | }); 88 | ``` 89 | 90 | {% hint style="info" %} 91 | We remove the line that sets the prefix. We don't need it anymore! 92 | {% endhint %} 93 | 94 | ## Changing the config 95 | 96 | If you're asking yourself "but how do I change the prefix, now?" fear not, we have some help for you. We suggest you start by reading the rest of this section of the guide \("First Bot"\) and then hop on to the [Per-Server Configuration Guide on the Enmap Documentation](https://enmap.evie.dev/examples/per-server-settings)! 97 | 98 | ## Extending the idea 99 | 100 | So is there anything else you could put in that config file? Absolutely. One thing I use it for is to store my personal user ID, so that my bot can use it to recognize me and give me exclusive access to some commands. 101 | 102 | ```javascript 103 | { 104 | "token": "insert-bot-token-here", 105 | "prefix": "!", 106 | "ownerID": "your-user-ID" 107 | } 108 | ``` 109 | 110 | Then, in a protected command ([eval](../examples/making-an-eval-command.md) for example), I could use the following line to prevent access to all the users that think they can use it!: 111 | 112 | ```javascript 113 | if (message.author.id !== config.ownerID) return; 114 | ``` 115 | 116 | ## What's next _now_? 117 | 118 | So now that we have a functional bot with a configuration file, let's add more stuff to it! Follow me to the [Command with arguments](command-with-arguments.md) for the next part! 119 | -------------------------------------------------------------------------------- /first-bot/better-basic-handler.md: -------------------------------------------------------------------------------- 1 | # COMING SOON 2 | -------------------------------------------------------------------------------- /first-bot/command-with-arguments.md: -------------------------------------------------------------------------------- 1 | # Command with arguments 2 | 3 | ## Creating an array of arguments 4 | 5 | The first thing that we need to do to use arguments, is to actually separate them. A command with arguments would normally look something like this: `!mycommand arg1 arg2 arg3` 6 | 7 | In this, we need to do 3 things: 8 | 9 | * Remove the prefix 10 | * Grab the _command_ part \(`mycommand`\) 11 | * Grab the _array_ of _arguments_ which will be: 12 | 13 | `["arg1", "arg2", "arg3"]` 14 | 15 | In the greatest majority of the code I've seen, arguments are _split_ at the beginning of the code, and each command will put the argument array back together as necessary within the command code. 16 | 17 | In my experience, the best \(and most efficient\) way of separating all these things is the following 2 lines of code: 18 | 19 | ```javascript 20 | const args = message.content.slice(prefix.length).trim().split(/ +/g); 21 | const command = args.shift().toLowerCase(); 22 | ``` 23 | 24 | Let's break this down into what it _actually_ does, line by line. 25 | 26 | * `.slice(prefix.length)` removes the prefix such as `!` or `+` from the message content, leaving `mycommand arg1 arg2 arg3`. 27 | * `.trim()` ensures there's no extra spaces before/after the text. 28 | * `.split(/ +/g)` splits the string by _one or many spaces_. Why not just by space? Because sometimes especially on mobile, you might have an extra space before or after mentions, or just straight up to an extra space by mistake. This means that `mycommand arg1 arg2 arg3` will work just as well as if they only had 1 space. 29 | 30 | On the second line: 31 | 32 | * `args.shift()` where `shift()` will **remove one element from the array** and return it. This gives us `mycommand` that's returned, and the `args` array becomes only `['arg1', 'arg2','arg3']` 33 | * `.toLowerCase()` so our command is always in lowercase, meaning `!Ping`, `!ping` and `!PiNg` will all work. 34 | 35 | ## Using the `command` variable properly 36 | 37 | So now that we have our `command` variable, we no longer need to use the `if (message.content.startsWith(prefix+'command'))` for every command. We can simplify this by looking only at the `command` variable itself. For example, these 2 very basic commands: 38 | 39 | ```javascript 40 | if (command === 'ping') { 41 | message.channel.send('Pong!'); 42 | } else 43 | 44 | if (command === 'blah') { 45 | message.channel.send('Meh.'); 46 | } 47 | ``` 48 | 49 | Now isn't that just so pretty and clean? I love it! 50 | 51 | Alternatively, you can also use this in a switch/case command block \(I don't like it, but to each their own, right?\): 52 | 53 | ```javascript 54 | switch (command) { 55 | case "ping" : 56 | message.channel.send('Pong!'); 57 | break; 58 | case "blah" : 59 | message.channel.send('Meh.'); 60 | break; 61 | } 62 | ``` 63 | 64 | Here's a complete example that's very often the command handler I've used as a base to build on: 65 | 66 | ```javascript 67 | client.on("messageCreate", message => { 68 | if (message.author.bot) return; 69 | // This is where we'll put our code. 70 | if (message.content.indexOf(config.prefix) !== 0) return; 71 | 72 | const args = message.content.slice(config.prefix.length).trim().split(/ +/g); 73 | const command = args.shift().toLowerCase(); 74 | 75 | if (command === 'ping') { 76 | message.channel.send('Pong!'); 77 | } else 78 | 79 | if (command === 'blah') { 80 | message.channel.send('Meh.'); 81 | } 82 | }); 83 | ``` 84 | 85 | ## Working with the arguments 86 | 87 | Alright let's get to the meat of this page: actually using the `args` array in a few command examples. 88 | 89 | The first one is a perfectly useless command, but for the life of me, I can't actually think of a really simple command using only one-word arguments. So here is a ridiculous ASL command: 90 | 91 | ```javascript 92 | if (command === "asl") { 93 | let age = args[0]; // Remember arrays are 0-based!. 94 | let sex = args[1]; 95 | let location = args[2]; 96 | message.reply(`Hello ${message.author.username}, I see you're a ${age} year old ${sex} from ${location}. Wanna date?`); 97 | } 98 | ``` 99 | 100 | And if you want to be **really** fancy with ECMAScript 6, here's an awesome one: 101 | 102 | ```javascript 103 | if (command === "asl") { 104 | let [age, sex, location] = args; 105 | message.reply(`Hello ${message.author.username}, I see you're a ${age} year old ${sex} from ${location}. Wanna date?`); 106 | } 107 | ``` 108 | 109 | {% hint style="info" %} 110 | This is called [Destructuring](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment) and it's awesome! 111 | {% endhint %} 112 | 113 | ## Grabbing Mentions 114 | 115 | Another way to use arguments, when the command should target a specific user \(or users\), is to use _Mentions_. For instance, to kick annoying shit-posters with `!kick @Xx_SniperBitch_xX @UselessIdiot` can be done with ease, instead of attempting to grab their ID or their name. 116 | 117 | In the context of the `message` event handler, all mentions in a message are part of the `message.mentions` object. This object then contains multiple [Collections](../understanding/collections.md) of different mention types. Here are the various available mention types: 118 | 119 | * `message.mentions.members` contains all @mention as [GuildMember](https://discord.js.org/#/docs/discord.js/stable/class/GuildMember) objects. 120 | * `message.mentions.users` contains all @mention as [User](https://discord.js.org/#/docs/discord.js/stable/class/User) objects. 121 | * `message.mentions.roles` contains all @role mention as [Role](https://discord.js.org/#/docs/discord.js/stable/class//Role) objects. 122 | * `message.mentions.channels` contains all \#channel mentions as [TextChannel](https://discord.js.org/#/docs/discord.js/stable/class/TextChannel) or [VoiceChannel](https://discord.js.org/#/docs/discord.js/stable/class/VoiceChannel) objects. 123 | 124 | Each of these are collections so any collection method can be used on them. The most common method to use on mentions is .first\(\) which gets the very first mention, since there is often only one of them. 125 | 126 | Let's build a quick and dirty `kick` command, then. No error handling or mod checks - just straight up! \(_Cul Sec_, as the French would say\): 127 | 128 | ```javascript 129 | // Kick a single member in the mention 130 | if (command === "kick") { 131 | let member = message.mentions.members.first(); 132 | member.kick(); 133 | } 134 | ``` 135 | 136 | This would be called with, for example, `!kick @AnnoyingUser23` 137 | 138 | ## Variable Length arguments 139 | 140 | Let's make the above kick command a little better. Because Discord now supports kick _reasons_ in the Audit Logs, the Discord.js `kick()` command also supports an optional `reason` argument. But, because the reason can have multiple words in it, we need to _join_ all these words together. 141 | 142 | So let's do this now, with what we've already learned, and a little extra: 143 | 144 | ```javascript 145 | if (command === "kick") { 146 | let member = message.mentions.members.first(); 147 | let reason = args.slice(1).join(" "); 148 | member.kick(reason); 149 | } 150 | ``` 151 | 152 | So, the reason is obtained by removing the first elements \(the mention, which looks like `<@1234567489213>`\) and re-joining the rest of the array elements with a space. 153 | 154 | To use this command, a user would do something like: `!kick @SuperGamerDude Obvious Troll, shit-posting`. 155 | 156 | Here's another example, with a super simple command, the `say` command. It makes the bot say what you just sent, and then delete your message: 157 | 158 | ```javascript 159 | if (command === "say"){ 160 | let text = args.join(" "); 161 | message.delete(); 162 | message.channel.send(text); 163 | } 164 | ``` 165 | 166 | {% hint style="info" %} 167 | If you're thinking, "What if I have more than one argument with spaces?", yes that's a tougher problem. Ideally, if you need more than one argument with spaces in it, do not use spaces to split the arguments. For example, `!newtag First Var Second Var Third Var` won't work. But `!newtag First Var;Second Var;Third Var;` could work by removing the command, splitting by `;` then splitting by space. Not for the faint of heart! 168 | {% endhint %} 169 | 170 | ## Going one step further 171 | 172 | Now, there's most definitely always room for some optimization, and better code. At this point, "parsing arguments" becomes something you might realize is necessary for _all_ of your commands, and writing "\(command === 'thing'\)" for every command is dull and boring. So, as your next step, consider looking at making [A Basic Command Handler](a-basic-command-handler.md). This **greatly** simplifies the creation of new commands. 173 | -------------------------------------------------------------------------------- /first-bot/using-embeds-in-messages.md: -------------------------------------------------------------------------------- 1 | # Using Embeds in messages 2 | 3 | {% hint style="warning" %} 4 | Embeds might look nice but they can be disabled through permissions and user preferences, and will not look the same on mobile - especially complex ones. It's strongly recommended _not_ to use them unless you have a text-only fallback. Yes they're nice, but, don't use them if you don't _need_ to! 5 | {% endhint %} 6 | 7 | ## Embeds 8 | 9 | Here are a few rules for embeds: 10 | 11 | * Every field is optional 12 | * At least one field must be present 13 | * No field can be empty, null, or undefined. 14 | 15 | Those aren't just guidelines, they are rules, and breaking those rules means your embed will not send - it will return `Bad Request`. 16 | 17 | There are 2 ways to do embeds. The cleanest way is by using the `MessageEmbed` builder and the second is writing the object itself, we'll cover the `MessageEmbed` builder _first._ 18 | 19 | ## MessageEmbed Builder 20 | 21 | The same rules apply for `MessageEmbed` as they do for object based ones. In fact, the builder is just a shortcut to get the same object and offers no more, no less functionality. 22 | 23 | ```javascript 24 | const embed = new Discord.MessageEmbed() 25 | /* 26 | * Alternatively, use "#3498DB", [52, 152, 219] or an integer number. 27 | */ 28 | .setColor(0x3498DB) 29 | .setAuthor("Author Name, it can hold 256 characters", "https://i.imgur.com/lm8s41J.png") 30 | .setTitle("This is your title, it can hold 256 characters") 31 | .setURL("https://discord.js.org/#/docs/main/stable/class/MessageEmbed") 32 | .setDescription("This is the main body of text, it can hold 4096 characters.") 33 | .setImage("http://i.imgur.com/yVpymuV.png") 34 | .setThumbnail("http://i.imgur.com/p2qNFag.png") 35 | .addField("This is a single field title, it can hold 256 characters", "This is a field value, it can hold 1024 characters.") 36 | /* 37 | * Inline fields may not display as inline if the thumbnail and/or image is too big. 38 | */ 39 | .addFields( 40 | { name: "Inline fields", value: "They can have different fields with small headlines, and you can inline them.", inline: true }, 41 | { name: "Masked links", value: "You can put [masked links](https://discord.js.org/#/docs/main/master/class/MessageEmbed) inside of rich embeds.", inline: true }, 42 | { name: "Markdown", value: "You can put all the *usual* **__Markdown__** inside of them.", inline: true } 43 | ) 44 | /* 45 | * Blank field, useful to create some space. 46 | */ 47 | .addField("\u200b", "\u200b") 48 | /* 49 | * Takes a Date object, defaults to current date. 50 | */ 51 | .setTimestamp() 52 | .setFooter("This is the footer text, it can hold 2048 characters", "http://i.imgur.com/w1vhFSR.png"); 53 | /* 54 | * With Discord now allowing messages to contain up to 10 embeds, we need to put it in an array. 55 | */ 56 | message.channel.send({ embeds: [embed] }); 57 | ``` 58 | 59 | Which produces the following: 60 | 61 | ![The example embed](../.gitbook/assets/first-bot-embed-example.png) 62 | 63 | Now doesn't that code look clean and amazing? Well let's look at the object version. 64 | 65 | ```javascript 66 | message.channel.send({ embeds: [{ 67 | color: 3447003, 68 | author: { 69 | name: "Author Name, it can hold 256 characters", 70 | icon_url: "https://i.imgur.com/lm8s41J.png" 71 | }, 72 | thumbnail: { 73 | url: "http://i.imgur.com/p2qNFag.png" 74 | }, 75 | image: { 76 | url: "http://i.imgur.com/yVpymuV.png" 77 | }, 78 | title: "This is your title, it can hold 256 characters", 79 | url: "https://discord.js.org/#/docs/main/master/class/MessageEmbed", 80 | description: "This is the main body of text, it can hold 2048 characters.", 81 | fields: [{ 82 | name: "This is a single field title, it can hold 256 characters", 83 | value: "This is a field value, it can hold 1024 characters.", 84 | inline: false 85 | }, 86 | { 87 | name: "Inline fields", 88 | value: "They can have different fields with small headlines, and you can inline them.", 89 | inline: true 90 | }, 91 | { 92 | name: "Masked links", 93 | value: "You can put [masked links](https://discord.js.org/#/docs/main/master/class/MessageEmbed) inside of rich embeds.", 94 | inline: true 95 | }, 96 | { 97 | name: "Markdown", 98 | value: "You can put all the *usual* **__Markdown__** inside of them.", 99 | inline: true 100 | }, 101 | { 102 | name: "\u200b", 103 | value:"\u200b" 104 | }], 105 | timestamp: new Date(), 106 | footer: { 107 | icon_url: "http://i.imgur.com/w1vhFSR.png", 108 | text: "This is the footer text, it can hold 2048 characters" 109 | } 110 | }]}); 111 | ``` 112 | 113 | As you can see it's bit of a chonker in comparison, but they both output the same embed so there's no real reason to show it off, congratulations you know how to use embeds in normal messages now! 114 | 115 | Not that it's any different if you're using a webhook, or an interaction response. 116 | -------------------------------------------------------------------------------- /first-bot/your-first-bot.md: -------------------------------------------------------------------------------- 1 | # Your First Bot 2 | 3 | This chapter assumes you've followed the Getting Started chapter and your bot code compiles. Also, I have to repeat: if you don't understand the code you're about to see, coding a bot might not be for you. Go to [CodeAcademy](https://www.codecademy.com/learn/javascript) and learn Javascript. 4 | 5 | In this chapter I'll guide you through the development of a simple bot with some useful commands. We'll start with the example we created in the first chapter: 6 | 7 | ```javascript 8 | const { Client, Intents } = require("discord.js"); 9 | const client = new Client({ 10 | intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MESSAGES] 11 | }); 12 | 13 | client.on("ready", () => { 14 | console.log("I am ready!"); 15 | }); 16 | 17 | client.on("messageCreate", (message) => { 18 | if (message.content.startsWith("ping")) { 19 | message.channel.send("pong!"); 20 | } 21 | }); 22 | 23 | client.login("SuperSecretBotTokenHere"); 24 | ``` 25 | 26 | ## Introducing Events 27 | 28 | Before we dive into any further coding, we need to first understand what an _Event_ is. 29 | 30 | This is an event: 31 | 32 | ```javascript 33 | client.on("messageCreate", (message) => { 34 | // This code runs when the event is triggered 35 | }); 36 | ``` 37 | 38 | This is, specifically, an event in _discord.js_ but it's similar to how other APIs handle events. This event triggers _every time the bot sees a message_. This includes every channel the bot has access to as well as any direct or private message it receives. If someone sends 5 messages on a channel, this event fires 5 times. 39 | 40 | Why is this important? Well, if you intend to use your bot on a large server, or if you want it to be on multiple servers, this becomes a large number of events triggering at every moment. I don't want to go into too much optimization talk, but for a single point: **use a single event function for each event**. 41 | 42 | Discord.js contains a large number of events that can trigger under certain situations. For instance, the `ready` event triggers when the bot comes online. The `guildMemberAdd` event triggers when a new user joins a server shared with the bot. For a full list of events, see [Events in the documentation](https://discord.js.org/#/docs/main/stable/class/Client?scrollTo=e-applicationCommandCreate). We will come back to some of those later in this chapter. 43 | 44 | ## Adding a second command 45 | 46 | One of the first useful things you might want to learn is how to add a second command to your bot. While there are _better_ ways than what I'm about to show you, for the time being this will be enough. 47 | 48 | {% hint style="info" %} 49 | From now on I will omit the code that requires and initiates the discord.js and concentrate on specific parts of the code. 50 | {% endhint %} 51 | 52 | ```javascript 53 | client.on("messageCreate", (message) => { 54 | if (message.content.startsWith("ping")) { 55 | message.channel.send("pong!"); 56 | } else 57 | 58 | if (message.content.startsWith("foo")) { 59 | message.channel.send("bar!"); 60 | } 61 | }); 62 | ``` 63 | 64 | Save your code and restart your bot. To do so, use `CTRL+C` in the command line, and re-run `node index.js`. Yes, there are better ways to reload the code, as you will see later in this book. 65 | 66 | You can test your new command by saying `foo` in a channel you share with the bot. You can also confirm that `ping` still returns `pong`! 67 | 68 | ## Using a Prefix 69 | 70 | You might have noticed that a lot of bots respond to commands that have a prefix. This might be an exclamation mark \(!\), a dot \(.\), a question mark\(?\), or another character but with the introduction of slash commands it is heavily advised against using `/`. But this is useful for two things. 71 | 72 | First, if you don't use a unique prefix and have more than one bot on a server, both will respond to the same commands. On developer servers, typing `!help` leads to a flood of replies and private messages which is something to avoid. 73 | 74 | Second, in the example above we respond when the message _starts with_ the 3 characters, `foo`. In its current state, this means the following sentence will trigger the bot's response: **fool, you have not heard the last of me!**. Yes, that's an odd example, but it's still valid - say this on your bot's channel and he will respond. 75 | 76 | To work around this, we'll be using prefix, which we will store in a variable. This way we get the prefix as well as the ability to change it for all commands in one place. Here's an example code that does this: 77 | 78 | ```javascript 79 | // Set the prefix 80 | const prefix = "!"; 81 | client.on("messageCreate", (message) => { 82 | // Exit and stop if it's not there 83 | if (!message.content.startsWith(prefix)) return; 84 | 85 | // The back ticks are Template Literals introduced in Javascript in ES6 or ES2015, as an replacement for String Concatenation Read them up here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals 86 | if (message.content.startsWith(`${prefix}ping`)) { 87 | message.channel.send("pong!"); 88 | } else 89 | 90 | if (message.content.startsWith(`${prefix}foo`)) { 91 | message.channel.send("bar!"); 92 | } 93 | }); 94 | ``` 95 | 96 | The changes to the code are still simple. Let's go through them: 97 | 98 | * `const prefix = "!";` defines the prefix as the exclamation mark. You can change it to something else, of course. 99 | * The line `if (!message.content.startsWith(prefix)) return;` is a small bit of optimization which reads: "If the message does not start with my prefix, stop what you're doing". This prevents the rest of the function from running, making your bot faster and more responsive. 100 | * The commands have changed so use this prefix, where ``startsWith(`${prefix}ping`)`` would only be triggered when the message starts with `!ping`. 101 | 102 | The second point is just as important as having a single `messageCreate` event handler. Let's say the bot receives a hundred messages every minute \(not much of an exaggeration on popular bots\). If the function does not break off at the beginning, you are processing these hundred messages in each of your command conditions. If, on the other hand, you break off when the prefix is not present, you are saving all these processor cycles for better things. If commands are 1% of your messages, you are saving 99% processing power... 103 | 104 | {% hint style="info" %} 105 | OK I'm sorry, I'm bullshitting a little. It's not 99%, that is an exaggeration. It _is_, however, true that you save a ton on processor and RAM power. 106 | {% endhint %} 107 | 108 | ## Preventing Botception 109 | 110 | We're pretty much done with the basic bot. There's one last thing that I want to talk about: bots answering each other. Let's pretend for a moment that you have two bots on your server and each can respond to the same prefixed command, `!help`. But when that command is called, it replies: `!help commands: Type !help followed by one of the following to see details: ping , foo`. 111 | 112 | Now, one person types `!help` in a channel, and both bots respond. But, they will also see the **other** bot saying `!help commands: [...]`, will see that as a request for help, answer each other... in an infinite loop. To prevent that from happening, we can add a second condition inside our `message` event handler, right below the one that checks for the prefix: 113 | 114 | ```javascript 115 | const prefix = "!"; 116 | client.on("messageCreate", (message) => { 117 | // our new check: 118 | if (!message.content.startsWith(prefix) || message.author.bot) return; 119 | // [rest of the code] 120 | }); 121 | ``` 122 | 123 | That condition contains an _OR_ \( \|\| \) operator, which reads as the following: 124 | 125 | {% hint style="info" %} 126 | If there is no prefix or the author of this message is a bot, stop processing. This includes this bot, itself. 127 | {% endhint %} 128 | 129 | And now, we have a bot that only responds to 2 commands and does not waste any power trying to figure out anything else. Is this a complete basic bot? Sure! So let's end this page here and we'll take a look at some new concept next. 130 | 131 | The full bot code would now be: 132 | 133 | ```javascript 134 | const { Client, Intents } = require("discord.js"); 135 | const client = new Client({ 136 | intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MESSAGES] 137 | }); 138 | 139 | // Set the prefix 140 | let prefix = "!"; 141 | client.on("messageCreate", (message) => { 142 | // Exit and stop if the prefix is not there or if user is a bot 143 | if (!message.content.startsWith(prefix) || message.author.bot) return; 144 | 145 | if (message.content.startsWith(`${prefix}ping`)) { 146 | message.channel.send("pong!"); 147 | } else 148 | 149 | if (message.content.startsWith(`${prefix}foo`)) { 150 | message.channel.send("bar!"); 151 | } 152 | }); 153 | 154 | client.login("SuperSecretBotTokenHere"); 155 | ``` 156 | 157 | Every time I see that `SuperSecretBotTokenHere`, I cringe a little. See, it's not good practice to have tokens and auth stuff in your code, it really should be in a separate file! Head on over to [Adding a Config File](adding-a-config-file.md) and let's get this done. 158 | -------------------------------------------------------------------------------- /frequently-asked-questions.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: >- 3 | Some very quick code samples for common actions on the bot, users, members, 4 | channels and messages! 5 | --- 6 | 7 | # Frequently Asked Questions 8 | 9 | In this page, some very basic, frequently-asked questions are answered. It's important to understand that **these examples are generic** and will most likely not work if you just copy/paste them in your code. You need to **understand** these lines, not just blindly shove them in your code. 10 | 11 | ## Code Examples 12 | 13 | ## Bot and Bot Client 14 | 15 | ```javascript 16 | // Set the bot's "Playing: " status (must be in an event!) 17 | client.on("ready", () => { 18 | client.user.setActivity("my code", { type: "WATCHING"}) 19 | }) 20 | ``` 21 | 22 | ```javascript 23 | // Set the bot's online/idle/dnd/invisible status 24 | client.on("ready", () => { 25 | client.user.setStatus("online"); 26 | }); 27 | ``` 28 | 29 | ```javascript 30 | // Set the bot's presence (activity and status) 31 | client.on("ready", () => { 32 | client.user.setPresence({ 33 | activities: [{ 34 | name: "my code", 35 | type: "WATCHING" 36 | }], 37 | status: "idle" 38 | }) 39 | }) 40 | ``` 41 | 42 | Note: You can find a list of all possible activity types [here](https://discord.js.org/#/docs/main/stable/typedef/ActivityType). 43 | 44 | {% hint style="info" %} 45 | If you want your bot's status to show up as `STREAMING`, you need to provide a Twitch or YouTube URL. 46 | For `setActivity`, you need to provide an options object, which needs to have the URL, and the type should be set to streaming. 47 | For `setPresence`, you need to provide the presence data object, which needs to contain the activities array, with the url and type \(Set it to "STREAMING"\). 48 | {% endhint %} 49 | 50 | ```javascript 51 | client.on("ready", () => { 52 | client.user.setActivity("my code", { type: "STREAMING", url: "https://www.twitch.tv/an_idiots_guide" }) 53 | }) 54 | ``` 55 | 56 | ## Users and Members 57 | 58 | {% hint style="info" %} 59 | In these examples `Guild` is a placeholder for where you get the guild. This can be `message.guild` or `member.guild` or just `guild` depending on the event. Or, you can get the guild by ID \(see next section\) and use that, too! 60 | {% endhint %} 61 | 62 | ```javascript 63 | // Get a User by ID 64 | client.users.cache.get("user id here"); 65 | // Returns 66 | ``` 67 | 68 | ```javascript 69 | // Get a Member by ID 70 | message.guild.members.cache.get("user ID here"); 71 | // Returns 72 | ``` 73 | 74 | ```javascript 75 | // Get a Member from message Mention 76 | message.mentions.members.first(); 77 | // Returns , if there is a mentioned member 78 | ``` 79 | 80 | ```javascript 81 | // Send a Direct Message to a user 82 | message.author.send("hello"); 83 | // With Member it works too: 84 | message.member.send("Heya!"); 85 | ``` 86 | 87 | ```javascript 88 | // Mention a user in a message 89 | message.channel.send(`Hello ${message.author.toString()}, and welcome!`); 90 | // or 91 | message.channel.send("Hello " + message.author.toString() + ", and welcome!"); 92 | ``` 93 | 94 | ```javascript 95 | // Restrict a command to a specific user by ID 96 | if (message.content.startsWith(`${prefix}commandname`)) { 97 | if (message.author.id !== "A user ID") return; 98 | // Your Command Here 99 | } 100 | ``` 101 | 102 | ```javascript 103 | message.guild.members.fetch(message.author) 104 | .then(member => { 105 | // The member is available here. 106 | }) 107 | .catch(() => { 108 | // When an error occurs. 109 | }) 110 | ``` 111 | 112 | ## Channels and Guilds 113 | 114 | ```javascript 115 | // Get a Guild by ID 116 | client.guilds.cache.get("the guild id"); 117 | // Returns 118 | ``` 119 | 120 | ```javascript 121 | // Get a Channel by ID 122 | client.channels.cache.get("the channel id"); 123 | // Returns 124 | ``` 125 | 126 | ```javascript 127 | // Get a Channel by Name 128 | message.guild.channels.cache.find(channel => channel.name === "channel-name"); 129 | // returns 130 | ``` 131 | 132 | ```javascript 133 | // Create an invite and send it in the channel 134 | // You can only create an invite from a GuildChannel 135 | // Messages can only be sent to a TextChannel 136 | message.guild.channels.cache.get('').createInvite().then(invite => 137 | message.channel.send(invite.url) 138 | ); 139 | ``` 140 | 141 | ## Messages 142 | 143 | ```javascript 144 | // Editing a message the bot sent 145 | message.channel.send("Test").then(sentMessage => sentMessage.edit("Blah")); 146 | // message now reads : "Blah" 147 | ``` 148 | 149 | ```javascript 150 | message.channel.messages.fetch("352292052538753025") 151 | .then(message => { 152 | // do something with it 153 | // Check if the author of the message is the bot 154 | if (message.client.user.id !== message.author.id) return console.log("I'm not the author of that message!"); 155 | // Edit the message 156 | message.edit("This fetched message was edited"); 157 | }); 158 | ``` 159 | -------------------------------------------------------------------------------- /getting-started/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | In this section we will teach you how to get started in the world of bot development! 4 | -------------------------------------------------------------------------------- /getting-started/getting-started-long-version.md: -------------------------------------------------------------------------------- 1 | # Getting Started - Long Version 2 | 3 | So, you want to write a bot and you know some JavaScript, or maybe even node.js. You want to do cool things like a music bot, tag commands, random image searches, the whole shebang. Well you're at the right place! 4 | 5 | {% hint style="info" %} 6 | **This is the long version** with a whole lot of useless blabbering text, jokes and explanations. 7 | Here's the [TL;DR \(short\) version](getting-started-tl-dr.md) 8 | {% endhint %} 9 | 10 | This tutorial will get you through the first steps of creating a bot, configuring it, making it run, and adding a couple of commands to it. 11 | 12 | ## Step 1: Creating your App and Bot account 13 | 14 | The first step in creating a bot is to create your own Discord _application_. The bot will use the Discord API, which requires the creation of an account for authentication purposes. Don't worry though, it's super simple. 15 | 16 | ### Creating the App account 17 | 18 | To create the application, head to the [Discord application page](https://discord.com/developers/applications/). Assuming you're logged in \(if not, do so now\), you'll reach a page that looks like this: 19 | 20 | ![The application page](../.gitbook/assets/gs-application-page.png) 21 | 22 | Click on \(you guessed it!\) **New Application**. This brings up the following modal, in which you should simply enter a name for the _application_ \(this will be the initial bot username\). Click **Create** which will create the application itself. 23 | 24 | ![New application modal](../.gitbook/assets/gs-new-application.png) 25 | 26 | The **Application ID** on the page will be your bot's user ID. The application description is used in the bots `About me` section. So feel free to add a description of your bot in under 190 characters. 27 | 28 | {% hint style="info" %} Whilst the page clearly indicates a maximum of 400, only 190 will be displayed in the `About me` section.{% endhint %} 29 | 30 | ![The created application](../.gitbook/assets/gs-created-application.png) 31 | 32 | ### Create the bot account 33 | 34 | After creating the application, we need to create the **Bot User**. Go to the **Bot** section on the left, and you will be greeted with the following screen. 35 | 36 | ![Build-a-bot](../.gitbook/assets/gs-making-bot.png) 37 | 38 | Finally click on **Add Bot**, then **Yes, Do it** to create your bot. 39 | 40 | ![Are you sure?](../.gitbook/assets/gs-add-bot-modal.png) 41 | 42 | There's a few things you can change here and most importantly the token. 43 | 44 | ![Making the bot](../.gitbook/assets/gs-bot-created.png) 45 | 46 | * `Icon` to change the bot's avatar \(can also be done with discord.js\) 47 | * `Username` to change your bot's username on Discord \(this can also be done through code\). 48 | * `Token` This is your bot's token, which will be used when connecting to discord. See [the section below](getting-started-long-version.md#getting-your-bot-token) for details. 49 | * `Public bot` This toggles the ability for other users to add your bot to their server. You can turn this off during development to prevent random users inviting it. 50 | * `Require Oauth2 Code Grant` Don't check this. Just, don't. It's not useful to you and will cause problems if you turn it on. 51 | * `Privileged Gateway Intents` Now this is important, if your bot is checking presence data, or downloading the member list, you will need to toggle either or both of these, for now they're not needed. But please note, if your bot reaches 100 servers you will need to be whitelisted and verified to use these. 52 | 53 | ### Add your bot to a server 54 | 55 | Okay so, this might be a bit early to do this but it doesn't really matter - even if you haven't written a single line of code for your bot, you can already "invite" it to a server. In order to add a bot, you need _Manage Server_ or _Administrator_ permissions on that server. This is the **only** way to add a bot, it cannot use invite links or any other methods. 56 | 57 | To generate the link, click on **OAuth2** in the app page (it's above `Bot`), and scroll down to **Scopes**. Check the `bot` scope to generate a link, if you're planning on adding slash commands make sure to click `applications.commands` as well. 58 | 59 | Usually, bots are invited with the specific _permissions_ which are given to the bot's role which cannot be removed unless you kick and reinvite the bot. This is optional, but you can set those permissions in the **Bot** page, scrolling down to the **Bot Permissions** section. Check any permissions your bot requires. This modifies the invite link above, which you can then share. 60 | 61 | Once you have the link, you can copy it to a browser window and visit it. When you do this, You get shown a window letting you choose the server where to add the bot, simply select the server and click **Authorize**. 62 | 63 | ![Inviting the bot](../.gitbook/assets/gs-invite-bot.png) 64 | 65 | {% hint style="info" %} 66 | You need to be logged in to Discord on the browser with your account to see a list of servers. You can only add a bot to servers where you have **Manage Server** or **Administrator** permissions. 67 | {% endhint %} 68 | 69 | ### Getting your Bot Token 70 | 71 | {% hint style="danger" %} 72 | Alright so, **big flashy warning**, **PAY ATTENTION**. This next part is really, really important: Your bot's **token** is meant to be **SECRET**. It is the way by which your bot authenticates with the Discord server in the same way that you login to Discord with a username and password. **Revealing your token is like putting your password on the internet**, and anyone that gets this token can use **your** bot connection to do things. Like delete all the messages on your server and ban everyone. If your token ever reaches the internet, **change it immediately**. This includes putting it on pastebin/hastebin, having it in a public github repository, displaying a screenshot of it, anything. **GOT IT? GOOD!**, Github has partnered with Discord to invalidate your token if it's found within your code repository and message you via a `System` message on Discord. 73 | {% endhint %} 74 | 75 | With that warning out of the way, on to the next step. The Token, as I just mentioned, is the way in which the bot authenticates. To get it, go to the **Bot** section of the app page, then click **Copy** to copy it to the clipboard. You can also _view_ your token here if you wish. Not forgetting that ever important `Regenerate` key if your token is compromised: 76 | 77 | ![NEVER SHARE YOUR TOKEN! This cannot be overstated.](../.gitbook/assets/gs-copy-token.png) 78 | 79 | ## Step 2: Getting your coding environment ready 80 | 81 | This might go beyond saying but I'll say it anyway: You can't just start shoving bot code in notepad.exe and expect it to work. In order to use discord.js you will need a couple of things installed. At the very least: 82 | 83 | * Get Node.js version 16.6 or higher \(earlier versions are not supported\). [Download for windows](https://nodejs.org/en/download/) or if you're on a linux distro, via [package manager](https://nodejs.org/en/download/package-manager/). 84 | * Get an actual code editor. Don't use notepad or notepad++, they are not sufficient. [VS Code](https://www.visualstudio.com/en-us/products/code-vs.aspx) , [Sublime Text 3](https://www.sublimetext.com/3) and [Atom](https://atom.io/) are often recommended. 85 | 86 | Once you have the required software, the next step is to prepare a _space_ for your code. Please don't just put your files on your desktop it's... unsanitary. If you have more than one hard drive or partition, you could create a special place for your development project. Mine, for example, is `D:\develop\` , and my bot is `D:\develop\guide-bot\` . Once you've created a folder, open your CLI \(command line interface\) in that folder. Linux users, you know how. Windows users, here's a trick: `SHIFT + Right Click` in the folder, then choose the "secret" command **Open PowerShell window here**. Magic! 87 | 88 | And now ready for the next step! 89 | 90 | ## Installing Discord.js 91 | 92 | So you have your CLI ready to go, in an empty folder, and you just wanna start coding. Alright, hold on one last second: let's install discord.js. But first we'll initialize this folder with NPM, which will ensure that any installed module will be here, and nowhere else. Simply run `npm init -y` and then hit Enter. A new file is created called `package.json`, [click here](https://docs.npmjs.com/files/package.json) for more info about it. 93 | 94 | And now we install Discord.js through NPM, the Node Package Manager: 95 | 96 | `npm i discord.js` _At the time of writing this v13 hasn't been released yet._ 97 | 98 | ![Installing the packages](../.gitbook/assets/gs-installing-discordjs.gif) 99 | 100 | This will take a couple of heartbeats and display a lot of things on screen. Unless you have a big fat red message saying it didn't work, or package not found, or whatever, you're good to go. If you look at your folder, you'll notice that there's a new folder created here: `node_modules` . This contains all the installed packages for your project. 101 | 102 | ## Getting your first bot running 103 | 104 | {% hint style="info" %} 105 | I honestly consider that if you don't understand the code you're about to see, coding a bot might not be for you. If you do not understand the following sample, please go to [CodeAcademy](https://www.codecademy.com/learn/javascript) and learn Javascript first. I beg of you: stop, drop, and roll. 106 | {% endhint %} 107 | 108 | Okay finally, we're ready to start coding. \o/ Let's take a look at the most basic of examples, the ping-pong bot. Here's the code in its entirety: 109 | 110 | ```javascript 111 | const { Client, Intents } = require("discord.js"); 112 | // The Client and Intents are destructured from discord.js, since it exports an object by default. Read up on destructuring here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment 113 | const client = new Client({ 114 | intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MESSAGES] 115 | }); 116 | 117 | client.on("ready", () => { 118 | console.log("I am ready!"); 119 | }); 120 | 121 | client.on("messageCreate", (message) => { 122 | if (message.content.startsWith("ping")) { 123 | message.channel.send("pong!"); 124 | } 125 | }); 126 | 127 | client.login("SuperSecretBotTokenHere"); 128 | ``` 129 | 130 | {% hint style="info" %} 131 | The variable `client` here is used an an example to represent the [<Client>](https://discord.js.org/#/docs/main/stable/class/Client) class. Some people call it `bot`, but you can technically call it whatever you want. I recommend sticking to `client` though! 132 | {% endhint %} 133 | 134 | Okay let's just... actually get this guy to work, because this is literally **a functional bot**. So let's make it run! 135 | 136 | 1. Copy that code and paste it in your editor. 137 | 2. Replace the string in the `client.login()` function with _your_ token 138 | 3. Save the file as `index.js`. 139 | 4. In the CLI \(which should still be in your project folder\) type the following command: `node index.js` 140 | 141 | If all went well \(hopefully it did\) your bot is now connected to your server, it's in your user list, and ready to answer all your commands... Well, at least, _one_ command: `ping`. In its current state, the bot will reply "pong!" to any message that starts with, _exactly_, `ping`. Let's demonstrate! 142 | 143 | ![Ping?, Pong!](../.gitbook/assets/gs-ping-pong.png) 144 | 145 | Success! You now have a bot running! As you probably realize by now I could probably blabber on from here, showing you a bunch of stuff. But the scope of this tutorial is completed, so I'll shut up now! Ciao! 146 | 147 | ## The Next Step? 148 | 149 | Now that you have a basic, functional bot, it's time to start adding new features! Head on over to [Your First Bot](../first-bot/your-first-bot.md) to continue on your journey with adding new commands and features! 150 | 151 | ## Addendum: Getting help and Support 152 | 153 | Before you start getting support from Discord servers to help you with your bot, I strongly advise taking a look at the following, very useful, resources. 154 | 155 | * [Discord.js Documentation](http://discord.js.org) : For the love of all that is \(un\)holy, **read the documentation**. Yes, it will be alien at first if you are not used to "developer documentation" but it contains a whole lot of information about each and every feature of the API. Combine this with the examples above to see the API in context. 156 | * [An Idiot's Guide](https://www.youtube.com/c/AnIdiotsGuide) is another great channel with more material. York's guides are great, and he continues to update them. 157 | * [Evie.Codes on YouTube](https://www.youtube.com/channel/UCvQubaJPD0D-PSokbd5DAiw): If you prefer video to words, Evie's YouTube series \(which is good, though no longer maintained with new videos!\) gets you started with bots. 158 | * [An Idiot's Guide Official Server](https://discord.gg/vXVxsAjSMF): The official server for An Idiot's Guide. Full of friendly helpful users! 159 | * [Discord.js Official Server](https://discord.gg/djs): The official server has a number of competent people to help you, and the development team is there too! 160 | -------------------------------------------------------------------------------- /getting-started/getting-started-tl-dr.md: -------------------------------------------------------------------------------- 1 | # Getting Started - TL;DR 2 | 3 | {% hint style="info" %} 4 | **This is the TL;DR version**. If you wish for a long version with more explanations, please see [this guide](getting-started-long-version.md) 5 | {% endhint %} 6 | 7 | ## Create App and Bot Account 8 | 9 | * Go to the [Discord.com Application Page](https://discord.com/developers/applications/me) 10 | * Create a **New Application**, and give it a name 11 | * Click **Bot**, **Add Bot** then finally click **Yes, do it** 12 | * Visit `https://discord.com/oauth2/authorize?client_id=APP_ID&scope=bot` , replacing **APP\_ID** with the **Application ID** from the app page, to add the bot to your server \(or ask a server admin to do it for you\). If you're wanting slash commands as well, add `%20applications.commands` to the end of the URL above. 13 | * Copy your bot's **Secret Token** and keep it for later 14 | 15 | ## Pre-requisite software 16 | 17 | Depending on the operating system you're running the installation will be slightly different. 18 | 19 | * nodejs \(Version 16.6 and higher required, see [Windows](https://nodejs.org/en/download/) or [Linux](https://nodejs.org/en/download/package-manager/)\) 20 | 21 | Once you have this all installed, create a folder for your project and install discord.js: 22 | 23 | `mkdir mybot` `cd mybot` `npm i discord.js` 24 | 25 | ## Example Code 26 | 27 | The following is a simple ping/pong bot. Save as a text file \(e.g. `index.js`\), replacing the string on the last line with the secret bot token you got earlier: 28 | 29 | ```javascript 30 | const { Client, Intents } = require("discord.js"); 31 | // Since discord.js exports an object by default, we can destructure it. Read up more here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment 32 | const client = new Client({ 33 | intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MESSAGES] 34 | }); 35 | 36 | client.on("ready", () => { 37 | console.log("I am ready!"); 38 | }); 39 | 40 | client.on("messageCreate", (message) => { 41 | if (message.content.startsWith("ping")) { 42 | message.channel.send("pong!"); 43 | } 44 | }); 45 | 46 | client.login("SuperSecretBotTokenHere"); 47 | ``` 48 | 49 | ## Launching the bot 50 | 51 | In your command prompt, from inside the folder where `index.js` is located, launch it with: 52 | 53 | `node index.js` 54 | 55 | If no errors are shown, the bot should join the server\(s\) you added it to. 56 | 57 | ## Resources 58 | 59 | * [Discord.js Documentation](http://discord.js.org) : For the love of all that is \(un\)holy, **read the documentation**. Yes, it will be alien at first if you are not used to "developer documentation" but it contains a whole lot of information about each and every feature of the API. Combine this with the examples above to see the API in context. 60 | * [An Idiot's Guide](https://www.youtube.com/c/AnIdiotsGuide) is another great channel with more material. York's guides are great, and he continues to update them. 61 | * [Evie.Codes on YouTube](https://www.youtube.com/channel/UCvQubaJPD0D-PSokbd5DAiw): If you prefer video to words, Evie's YouTube series \(which is good, though no longer maintained with new videos!\) gets you started with bots. 62 | * [An Idiot's Guide Official Server](https://discord.gg/vXVxsAjSMF): The official server for An Idiot's Guide. Full of friendly helpful users! 63 | * [Discord.js Official Server](https://discord.gg/bRCvFy9): The official server has a number of competent people to help you, and the development team is there too! 64 | -------------------------------------------------------------------------------- /other-guides/README.md: -------------------------------------------------------------------------------- 1 | # Other Guides 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /other-guides/async-await.md: -------------------------------------------------------------------------------- 1 | # Async / Await 2 | 3 | When an async function is called, it returns a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). When the async function returns a value, the Promise will be resolved with the returned value. When the async function throws an exception or some value, the Promise will be rejected with the thrown value. 4 | 5 | An async function can contain an [await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await) expression, that pauses the execution of the async function and waits for the passed Promise's resolution, and then resumes the async function's execution and returns the resolved value. 6 | 7 | ### Promise 8 | 9 | But, what are Promises? Promise Object is used for asynchronous computations/calls, but unlike functions, they don't return the value immediately, as they have three states: 10 | 11 | * **Pending**: initial state, not fulfilled or rejected. 12 | * **Fulfilled**: meaning that the operation completed successfully. 13 | * **Rejected**: meaning that the operation failed. 14 | 15 | A function returns a Promise when you call an asynchronous method, it means, as explained above, the **final** value isn't available when the function has been called, but when the object Promise resolves. 16 | 17 | An example in the real world would be: 18 | 19 | **You get a bottle of water, you open it, turn over and drain it. Then dispose it.** 20 | 21 | The problem above is, when you get the bottle of water, you can immediately open it \(sync function\), but when you turn it over and drain it, you have to **await** until the bottle gets empty. This is, an **AsyncFunction**. 22 | 23 | **AoDude\#8676** proposed the following example: 24 | 25 | ```javascript 26 | const perrier = require("PerrierBrandWater"); 27 | const bottle = new perrier.BottleOfWater(); 28 | 29 | bottle.open(); // sync operation 30 | bottle.turnOverAndDrain() // async operation 31 | .then(emptyBottle => emptyBottle.dispose()) 32 | .catch((err) => { 33 | console.error(err); 34 | runForYourLives(); 35 | }); 36 | 37 | const runForYourLives = () => process.exit(); 38 | ``` 39 | 40 | In the example above, you open the bottle \(sync operation, you can do that in the code execution\), then you call `turnOverAndDrain`, in which is an async operation. You don't know if the operation will be executed successfully \(the water gets completely drained\) or it'll fail \(something happened and the water couldn't get completely drained\). 41 | 42 | When you call an asynchronous function, in [**ES6**](https://www.ecma-international.org/ecma-262/6.0/), you can use the keywords `then` and `catch`. At some point they make some logic. 43 | 44 | You open the bottle, turn it over and drain, **then** you dispose it. But if something failed, there's an `error`, you **catch** it, display a console error and run for your lives. 45 | 46 | Inside `then` and `catch` there are functions, inside then, we use the variable **emptyBottle**, in which is the value that the function **turnOverAndDrain** returns when it resolves. And inside catch, **err** is the error object that the function returns when it fails. 47 | 48 | Don't get it? There's an example with Discord.JS: 49 | 50 | Some practise, the method [client.users.fetch](https://discord.js.org/#/docs/main/stable/class/UserManager?scrollTo=fetch) returns `Promise`. It means, when you call that method, it'll return a **Promise**, resolving with a **User** object \(but it can also throw an error\). 51 | 52 | ```javascript 53 | client.users.fetch(id) 54 | .then((User) => { 55 | // Do something with the User object 56 | }) 57 | .catch((err) => { 58 | // Do something with the Error object, for example, console.error(err); 59 | }) 60 | ``` 61 | 62 | The code above requires a UserID, but you don't have the User object since you are going to retrieve information from Discord to get the User object, that takes a while, once you get the data, Discord.JS will resolve the method, running the function inside the `then`, passing the object `User`, described in the docs. 63 | 64 | ### Async/Await usage 65 | 66 | Once we know how to use the `Object Promise` and we know how to work with it, it's now time to learn how to use ES8 Promises, with `async`/`await`. 67 | 68 | ```javascript 69 | async () => { 70 | const User = await client.users.fetch(id); 71 | // Do something with the User object 72 | } 73 | ``` 74 | 75 | **WAIT WHAT? THAT'S ALL?** Yes, it is. in the code above, you're defining the constant `User` as the result of the Promise, hence the keyword `await`. In this context, your code \(when it executes\), calls the method `client.users.fetch()`, but it'll stop there, once the promise resolves, the returned value \(User Object\) is assigned to the constant User. 76 | 77 | **Wait, we have the replacement for** `then`**, but what if the method fails?** An advantage of ES8 Async/Await is that, you can call multiple AsyncFunctions, and catch them all once. As in the following example: 78 | 79 | ```javascript 80 | async () => { 81 | try { 82 | const User = await client.users.fetch(id); 83 | const member = await guild.members.fetch(User); 84 | const role = guild.roles.cache.find(r => r.name === "Idiot Subscribers"); 85 | await member.roles.add(role); 86 | await channel.send("Success!"); 87 | } catch (e) { 88 | console.error(e); 89 | } 90 | } 91 | ``` 92 | 93 | In the example above, you fetch a user, once you have the User object, declare it as the constant `User`, then you fetch a member with the `User`, if it's found, it'll get a role \(sync method, doesn't return a `Object Promise`, so you don't need the `await` keyword\), add the role to the member, and send a message to the channel. 94 | 95 | If **ONE** of the promises fail, the code stops executing the rest of the methods inside the `try`'s block and will run the block inside `catch`, with the Error object, and will send an error to the console. 96 | 97 | The example executes a **Promise Chain** \(runs promises, one after the previous\), you don't want to see them with ES6 async/await. Seriously. It's something we call **Callback Hell** \(a lot of indentation levels, code that is very hard to follow, hence much harder to work with...\). 98 | 99 | ## Important! 100 | 101 | To use the `await` keyword, you **MUST** have written the `async` keyword in the function whose block contains the code. For example: 102 | 103 | ```javascript 104 | const EditMessage = async (id, content) => { 105 | const Message = await channel.messages.fetch(id); // Async 106 | return Message.edit(content); 107 | } 108 | ``` 109 | 110 | ```javascript 111 | async function EditMessage(id, content) { 112 | const Message = await channel.messages.fetch(id); // Async 113 | return Message.edit(content); 114 | } 115 | ``` 116 | 117 | However, if you have a function inside another, for example: 118 | 119 | ```javascript 120 | const EditMessage = async (id, content) => { 121 | const Message = await channel.messages.fetch(id); 122 | setTimeout(() => { 123 | await Message.edit(content); 124 | Message.channel.send("Edited!"); 125 | }, 5000); 126 | } 127 | ``` 128 | 129 | That will throw an error: 130 | 131 | ```javascript 132 | await Message.edit(content); 133 | ^^^^^^^ 134 | SyntaxError: Unexpected identifier 135 | ``` 136 | 137 | This example failed because the function inside `setTimeout` doesn't have the `async` keyword. 138 | 139 | ## Documentation 140 | 141 | The following links are from MDN \(Mozilla Developer Network\), they provide syntax, description and examples. 142 | 143 | * [JavaScript Reference](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference) 144 | * [AsyncFunction \(Statement\)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) 145 | * [AsyncFunction \(Operator\)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/async_function) 146 | * [await \(Operator\)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await) 147 | * [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) 148 | -------------------------------------------------------------------------------- /other-guides/env-files.md: -------------------------------------------------------------------------------- 1 | # Using Environment Variables 2 | 3 | A `.env` file is a type of file that holds environment variables of an application. Environment variables allow you to easily integrate your bot with various online platforms \(ex. Heroku\), easily split your production and development environment as well as keep important information like your bot token, API tokens and database details secure. 4 | 5 | You do not want this information to get out to anyone. And using environment variables in a `.env` file is one of the best ways to secure your information, being preferred over a `config.json` file. 6 | 7 | To start out, it's suggested you install the `dotenv` package from NPM. This allows you to easily integrate environment variables into your bot. You may do that via the console. Make sure you are in your bot's root directory. Run this command: 8 | 9 | `npm install dotenv` 10 | 11 | Once installed, you will need to create a `.env` file. I'll create an example one here \(I will be using the `CLIENT_TOKEN` variable from the master branch of discord.js\). 12 | 13 | _If using v12 branch of discord.js, use_ `DISCORD_TOKEN=`_._ 14 | 15 | ```text 16 | CLIENT_TOKEN=[YOUR_BOT_TOKEN] 17 | OWNER=[YOUR_OWNER_ID] 18 | PREFIX=[DEFAULT_BOT_PREFIX] 19 | ``` 20 | 21 | After you create and update this file, save it and head over to your main file. I will build off of the default Discord.js example code. 22 | 23 | ```javascript 24 | const { Client } = require("discord.js"); 25 | // Importing this allows you to access the environment variables of the running node process 26 | require("dotenv").config(); 27 | 28 | const client = new Client(); 29 | 30 | // "process.env" accesses the environment variables for the running node process. PREFIX is the environment variable you defined in your .env file 31 | const prefix = process.env.PREFIX; 32 | 33 | client.on("ready", () => { 34 | console.log(`Logged in as ${client.user.tag}!`); 35 | }); 36 | 37 | client.on("messageCreate", message => { 38 | 39 | // Here's I'm using one of An Idiot's Guide's basic command handlers. Using the PREFIX environment variable above, I can do the same as the bot token below 40 | if (message.author.bot) return; 41 | if (message.content.indexOf(prefix.length) !== 0) return; 42 | 43 | const args = message.content.slice(prefix.length).trim().split(/ +/g); 44 | const command = args.shift().toLowerCase(); 45 | 46 | if (command === "ping") { 47 | message.reply("Pong!"); 48 | } 49 | }); 50 | 51 | // Here you can login the bot. It automatically attempts to login the bot with the environment variable you set for your bot token (either "CLIENT_TOKEN" or "DISCORD_TOKEN") 52 | client.login(); 53 | ``` 54 | 55 | This is all you will need to get off the ground. Now I will explain why this is important and why you should use environment variables with a `.env` file over a `config.json` file. 56 | 57 | ## Using Git \(ex. GitHub\) 58 | 59 | If you're going to publish your code with Git to a site like GitHub, then it's imperative you secure information by making sure your `.env` isn't committed to a repository. You may do that using a `.gitignore` file. Simply add the `.env` file to the `.gitignore` file in your local repository. 60 | 61 | From there on out, any future commits will ignore that file or any other files or directories in `.gitignore`. You can set up a `.env.example` file in its place, but that's not always necessary. 62 | 63 | ```text 64 | .env 65 | ``` 66 | 67 | Following this, all users will see in your bot code is `process.env.ENV_VARIABLE` which exposes nothing! 68 | 69 | ## Using Heroku 70 | 71 | Heroku is a website that allows you to start out and host your applications for free. In this case, your Discord bot. However, Heroku requires that you use environment variables. If you setup your files with the `dotenv` package and required the specific environment variables in your code, then all you have to do is go the environment variables in your Heroku application and add the key and value. 72 | 73 | In Heroku, these variables are called "Config Vars". I'm not going to go in-depth here about Heroku, but the same way you setup your `.env` file, you would add in new environment variables in Heroku. 74 | 75 | ![Heroku Config Vars](https://i.imgur.com/MSmEO5K.png) 76 | 77 | The only way your bot token can be exposed along with other environment variables is if you do one or more of these things: 78 | 79 | * a\) You accidentally expose your `.env` file because you didn't add it to `.gitignore` 80 | * b\) You added a collaborator through Heroku. A collaborator has almost full permissions as the app owner 81 | 82 | Both a and b can be controlled. It's just you need to be smart. 83 | 84 | ## Set environment variables in the start script 85 | 86 | When starting your application, either locally on your computer, in a npm start script in your `package.json` or even in a `Dockerfile` you can set what environment your bot should run in. 87 | 88 | For example, you may want to run your bot in production on Heroku or Glitch and in development on your computer. You can simply do that via adding new scripts in your `package.json`. 89 | 90 | Upon doing `npm init` when you first made your bot, you should have seen a `test` script created. The scripts portion of your `package.json` should look like this if you added nothing. 91 | 92 | ```javascript 93 | "scripts": { 94 | "test": "echo \"Error: no test specified\" && exit 1", 95 | }, 96 | ``` 97 | 98 | Here, you can add multiple scripts. Where going to add a few scripts. `production`, `development`, and `start`. 99 | 100 | ```javascript 101 | "scripts": { 102 | "test": "echo \"Error: no test specified\" && exit 1", 103 | "start": "node .", 104 | "production": "NODE_ENV=production&&npm start", 105 | "development": "set NODE_ENV=development&&npm start" 106 | }, 107 | ``` 108 | 109 | * To start your bot in a production environment, you would do `npm run production`. This will set `process.env.NODE_ENV` to `production` 110 | * To start your bot in a development environment, you would do `npm run development`. This will set `process.env.NODE_ENV` to `development` 111 | 112 | In your code, you can define what should happen depending on the environment loaded. Here's an example where your bot should only show the stream status if the environment is in `production` when the `ready` event is fired: 113 | 114 | ```javascript 115 | require("dotenv").config(); 116 | 117 | // process.env.NODE_ENV allows you to get the environment the node process is in 118 | let ver = process.env.NODE_ENV; 119 | 120 | client.on("ready", () => { 121 | 122 | if (ver === "production") { 123 | client.user.setActivity("An Idiot's Guide", { type: "STREAMING", url: "https://twitch.tv/something" }) 124 | } 125 | 126 | else { 127 | client.user.setActivity("in code land", { type: "PLAYING" }); 128 | } 129 | }); 130 | ``` 131 | 132 | Here I make sure that the bot is set to a streaming status only if my node environment is in `production`. I don't want my bot shown as streaming while I'm working on it in a development environment. There is a lot more you can do with this though. 133 | 134 | For example, you can change the `production` and `development` scripts to use entirely different main files if you wish. But I won't get into that here. 135 | 136 | If you don't want to use start scripts, you can always set the node environment directly in the command line. Here's how: 137 | 138 | ```bash 139 | # Windows 140 | SET NODE_ENV=development&&npm start 141 | SET NODE_ENV=development&&node . 142 | SET NODE_ENV=development&&node app.js 143 | 144 | # Linux/MacOS 145 | NODE_ENV=development&&npm start 146 | NODE_ENV=development&&node . 147 | NODE_ENV=development&&node app.js 148 | ``` 149 | 150 | Either running these commands directly through the command line or in start scripts should work. It's entirely up to you. But for ease of use when deploying your bot, you should use start scripts. So for example, in Heroku, you can add this in your `Procfile`: 151 | 152 | ```text 153 | # Heroku will run the bot in production mode 154 | worker npm run production 155 | ``` 156 | 157 | If you're using Glitch, you can add this in your `start` script in your `package.json`: 158 | 159 | ```javascript 160 | "scripts": { 161 | "test": "echo \"Error: no test specified\" && exit 1", 162 | "start": "NODE_ENV=production&&node app.js" 163 | }, 164 | ``` 165 | 166 | You may need to change the name of the main file depending on what you called it/where it's located in your bot project. 167 | 168 | ## More information 169 | 170 | Here are some links to more information you can read regarding environment variables and Git if you aren't that familiar with it: 171 | 172 | * [Using Git to share and update code \(An Idiot's Guide\)](https://anidiots.guide/other-guides/using-git-to-share-and-update-code#ignoring-files) 173 | * [Working with Environment Variables in Node.js \(Twilio Blog\)](https://www.twilio.com/blog/2017/08/working-with-environment-variables-in-node-js.html) 174 | * [`dotenv` NPM](https://www.npmjs.com/package/dotenv) 175 | * [`dotenv-flow` NPM \(Used for multiple `.env` file\)](https://www.npmjs.com/package/dotenv-flow) 176 | -------------------------------------------------------------------------------- /other-guides/installing-and-using-a-proper-editor.md: -------------------------------------------------------------------------------- 1 | # Installing and Using Atom 2 | 3 | Let's take a moment to appreciate the fact that the best code, is not just code that _works_ but also code that is _readable_. And _readable_ code is already easier to troubleshoot. To simplify the life of any code, a good editor is key. A good editor will tell you where your mistakes are, validate your code, give you best practices, and some will even run your code for you. 4 | 5 | In this tutorial we'll be looking at one such editor: Atom. Now, I'm not personally biased towards or away from Atom, but the editor, and some help from people who use it, were readily available, so this is the one I'm showing you. 6 | 7 | Other alternatives would be VS Code and Sublime Text 3. You'll have to look up specific instructions for those editors on your own. 8 | 9 | ## Getting Atom Installed 10 | 11 | Far be it from me to actually show you how to install stuff on your computer - I will pretend for a moment you know what you're doing, and give you the outline: 12 | 13 | 1. Go to the [Atom.io website](https://atom.io/). 14 | 2. Click the big red **Download Windows Installer** button \(statistically, you're on Windows\). 15 | 3. Once the .exe is downloaded, run it, and install it. 16 | 17 | Atom starts off with a simple interface with a nice welcome screen and a guide, so if you feel like it, go ahead and read up a bit. 18 | 19 | ## Opening your project 20 | 21 | To the contrary of some more basic editors, Atom has the ability to open a project _folder_ so you don't have to keep opening files individually. 22 | 23 | * Go to **File**, **Open Folder** \(CTRL+SHIFT+O\) 24 | * Browse to the location of your main bot file \(index.js, app.js, or whatever\) 25 | * Click on **Select Folder** \(_Note: your files will not appear here, only the folder structure. Don't worry they haven't gone anywhere_\) 26 | 27 | On the left of the editor you will have all the files. Just double-click any of them to open it. To clean things up, you can close the Welcome Screen and such. 28 | 29 | {% hint style="info" %} 30 | Already, you can see that this code looks super clean, and colorful. Also the dark theme doesn't hurt the eyes so that's a plus. 31 | {% endhint %} 32 | 33 | ## Getting ESLint installed 34 | 35 | A 'Linter' is a plugin or app that verifies your code to tell you where the errors are, and also helps in formatting the code with proper indentation and styles. 36 | 37 | {% hint style="info" %} 38 | There's obviously a debate with what linter you should use. ESLint? JSHint? Some other obscure library? I'll use ESLint because... I know how to use it. Not endorsing it in any way! 39 | {% endhint %} 40 | 41 | ESLint works in 2 parts. The first is the plugin for Atom, which is installed through the command line: 42 | 43 | * Hit Windows+R on your keyboard 44 | * Type in `cmd` then press Enter 45 | * Enter the command `apm install linter-eslint` then press Enter 46 | * This may take a few seconds to a minute to complete, be patient. 47 | * Keep the Command Prompt open. 48 | 49 | Next, we need to install the eslint npm module, which is what actually verifies your code. `linter-eslint` just integrates those results in Atom. 50 | 51 | In the command line, type `npm i -g eslint` then press Enter. Once it completes \(it will show you a list of eslint and installed dependencies\). 52 | 53 | You'll need to restart Atom to take the changes into effect. 54 | 55 | ## Setting up ESLint options 56 | 57 | ESLint, by default, will expect _very_ strict code from you, and will complain for a number of things. If you use the wrong type of quotes \(single vs double\), or if you use 4 instead of 2 spaces, alarms will go blaring. 58 | 59 | ESLint options can be global or local to your project. Let's do a local one as an example. 60 | 61 | * Create a new file in Atom 62 | * Save it as `.eslintrc.json` in your project folder \(where your app.js/index.js is\) 63 | * Copy the code below inside the file and save it again: 64 | 65 | ```javascript 66 | { 67 | "env": { 68 | "es6": true, 69 | "node": true 70 | }, 71 | "extends": "eslint:recommended", 72 | "parserOptions": { 73 | "sourceType": "module" 74 | }, 75 | "rules": { 76 | "no-console": "off", 77 | "indent": [ 78 | "error", 79 | 2 80 | ], 81 | "linebreak-style": [ 82 | "error", 83 | "windows" 84 | ], 85 | "quotes": [ 86 | "warn", 87 | "double" 88 | ], 89 | "semi": [ 90 | "warn", 91 | "always" 92 | ] 93 | } 94 | } 95 | ``` 96 | 97 | Don't be daunted by this: it's just a little config in JSON format. I won't go into details of what each option does, they are all explained [in the docs](http://eslint.org/docs/rules/) but this is the rules I'm using. Basically: 98 | 99 | * Force linter to support node 100 | * Force linter to support ES6 code `<3` 101 | * Indent to 2 spaces \(and not a 4-space-sized tab, ugh\) 102 | * Make linebreaks Windows styles 103 | * Warn on single quotes, prefer double quotes 104 | * Warn when the semicolon is absent 105 | -------------------------------------------------------------------------------- /other-guides/using-git-to-share-and-update-code.md: -------------------------------------------------------------------------------- 1 | # Using Git to share and update code 2 | 3 | Have you ever come to a point where you're editing code, removing and adding and changing stuff and all of a sudden you realize, _Shit, I deleted this piece and I need to rewrite or re-use it now. Damn!_ 4 | 5 | Have you ever wished there was a simpler way to transfer your code to your hosting, rather than having to connect to an FTP, or zip your file, upload to your host and unzip... you know the drill, right? Ugh! 6 | 7 | Have you ever wished you could easily share your code and have other people help you out in some bits, making your bot better? 8 | 9 | If you answered **Yes** to any of these questions, boy do I have a product for _you_! It's called `git` and it's a pretty magical tool. 10 | 11 | ## Pre-requisites and Software 12 | 13 | To take full advantage of `git` you need to first have _at least_ completed the [Adding a config.json file](../first-bot/your-first-bot.md#adding-a-configjson-file-to-your-bot) walkthrough. This means that at the very least, your token is located in a separate file, and not within your javascript files. Having the prefix and the owner ID in that separate configuration file means that you get the advantage of easy testing: Change the prefix and token, and you have a secondary bot you can test your code with, wooh! 14 | 15 | What else do you need? `git` itself, of course! For Windows get [Git SCM](https://git-scm.com/download/win) , on Linux run `sudo apt-get install git-all` or `sudo yum install git-all` depending on your distro's install method. Mac users also have a [Git SCM](http://git-scm.com/download/mac) installer. 16 | 17 | Anything else? Eeeeeh, nope! That's all, really. Let's get on to usage! 18 | 19 | ## Initialization and `.gitignore` 20 | 21 | So the first baby step into the world of git, is to initialize your project folder as a git repository. To do that, you need to navigate to that folder in your command line. Or use an OS shortcut: 22 | 23 | * Mac users can use [this trick from lifehacker.com](http://lifehacker.com/launch-an-os-x-terminal-window-from-a-specific-folder-1466745514). 24 | * Windows users, remember the magic trick: SHIFT+Right-Click in your project folder, then choose **Open command window here**. 25 | * Most Linux distros have an **Open in Terminal** option. But you use Linux, you can figure it out, right? 26 | 27 | Once you have your prompt open in that folder, go ahead and run `git init`. It doesn't have any options or questions - it just inits the folder. 28 | 29 | ### Ignoring Files 30 | 31 | In git, one of the most important files is `.gitignore` which, as the name would imply, ignores certain files. This is pretty critical with node.js apps, and our bot: you definitely want and need to ignore both the `node_modules` folder and the `config.json` file. The former because you don't want thousands of files being uploaded \(and they don't work on other systems anyway\), the latter because you don't want your token to end up on a public `git` repository! 32 | 33 | There are pre-built `.gitignore` files you can grab off the internet, but for the purpose of this exercise you only need 2 lines in that file: 34 | 35 | ```text 36 | node_modules 37 | config.json 38 | ``` 39 | 40 | But how do you create it? Linux and Mac users, you can probably easily create the file directly. Windows users, you have to take a tiny detour - Windows doesn't like files with only an extension and no filename. Don't worry thought it's simple. Start by creating a new file called, for example, `gitignore.txt` and put in the 2 lines above. Then, in your console, run `rename gitignore.txt .gitignore` and this will rename it as expected. 41 | 42 | ## Your first `git` command 43 | 44 | And now we're ready to start to `git gud` \(no no don't type that it's a meme!\) 45 | 46 | The first command you can try is `git status`. This will show you a list of all your files and in all subfolders and indicate them as **untracked files**, meaning they're not being tracked \(saved\) by git. 47 | 48 | Let's resolve that with our second command, which essentially tells git to track all these files and start doing its magic on them: `git add .` where `.` is just a shortcut to 'all the files including subfolders'. 49 | 50 | And now it's time to _commit_ to the task. _Commiting_ basically means you're creating a snapshot of your project, that is forever saved in your history. A commit can be a small as a tiny bugfix of one line or rewriting a complete function or module. Committing is always going to be with a message that describes the change, so smaller commits can be easier to manage and track in the future. So here goes: 51 | 52 | `git commit -m 'Initial Project Commit'` 53 | 54 | This will output a new commit with some stats including the number of changed lines, and the list of changed files. Great! Now we've got a commit. Funny story: you can continue committing changes locally, and do all the great things `git` does without every pushing to a remote repository. But that's what we're here for, right? So let's move on! 55 | 56 | ## Creating a GitHub/GitLab/BitBucket account 57 | 58 | With `git` you have a lot of choices when it comes to hosting your projects online. Pushing to an online repository gives you the safety in backups - your whole commit history is available, and nothing is lost whatever happens. Also gives you the ability to share your projects either publicly or privately with other people, and have them contribute to your project if you want them to. 59 | 60 | The 3 main `git` hosting services that are free are GitHub, GitLab and BitBucket. I'll be going with GitHub in this case, but most of the steps I'll take here are the same for all 3 services. You just need an account and you're good to go! 61 | 62 | So let's go ahead and prepare GitHub. On [GitHub.com](https://github.com/) , create an account and then create a **New Repository**. Give it a name that identifies your project - it doesn't need to be identical to your folder name, and you can change it later if you want. Don't add a readme for now. 63 | 64 | In the page that displays after creating the repository you actually get the instructions you need to do, under **push an existing repository from the command line**: 65 | 66 | ```text 67 | git remote add origin git@github.com:your-username/your-repo-name.git 68 | git push -u origin master 69 | ``` 70 | 71 | Obviously, change `your-username` and `your-repo-name` to the appropriate strings. The first line _links_ your local repository to the GitHub website. The second line actually takes all your local commit history, and it _pushes_ it directly to github. Note that this action will ask you to enter your github username and password. 72 | 73 | 'Wait, really? That's it?' you ask. Yep, your code is now on github. Refresh the github page and you'll see all your code there - without the `node_modules` and `config.json` if you've followed correctly! 74 | 75 | ## Pushing further updates 76 | 77 | Linking your account doesn't save you from future commands. Actually, every time you make a change and want to push it to the repository you have to use that `push` command again. Note that you don't _need_ to push every commit individually - you can work for a day and do multiple commits, and push them all at once at the end of the day. 78 | 79 | A small shortcut that you can use is to `add` and `commit` at the same time. This only works if you have changed files but not created new ones \(for that just do `git add .` separately\). So if you're done just small changes, you can use this: 80 | 81 | ```text 82 | git commit -am 'Fixed permission issue in 8ball command' 83 | ``` 84 | 85 | And don't forget to `git push origin master` again! 86 | 87 | ## Getting your code to another machine 88 | 89 | So, let's say you have a VPS hosting somewhere. Or a Raspberry Pi. Or whatever other machine. You want to get that code of yours onto it. You first need to follow the `install` steps above to get `git` installed on the machine, and then you need to _clone_ the repository from the git remote: 90 | 91 | ```text 92 | git clone git@github.com:your-username/your-repo-name.git 93 | ``` 94 | 95 | That's all! Well, you need to enter your username and password again, but once that's done, that code is available! 96 | 97 | Don't forget that any file that's been added to your `.gitignore` file isn't present here, so you'll need to do 2 things: 98 | 99 | 1. Create your `config.json` again, either with the same token and prefix, or different ones. 100 | 2. Install your node modules again. If you've properly run `npm init` and installed all your modules with the `-S` or `--save` argument, you should only need to do `npm install`. Otherwise, you need to install all of them again, for example using `npm install discord.js` 101 | 102 | Now, remember that these files aren't synced so they won't be overwritten or modified whenever you push or pull to and from the repo. 103 | 104 | Speaking of which, any time you have `push`ed to GitHub, you can get an updated copy of the code by simply running `git pull origin master`. 105 | 106 | You'll have to reset your bot for the changes to take effect, of course. 107 | 108 | ## Some Last Tips 109 | 110 | The process isn't one-way. If you modify files on _any_ computer you can `git push origin master` and `git pull origin master` on the other one. Be careful about editing on both locations though, as this may cause conflicts that are annoying to resolve \(and are beyond this guide's scope\) 111 | 112 | IF you ever have 'temporary' changes and you want to overwrite them - like a quick path on your VPS that you've also fixed locally - you can avoid the whole 'conflict' process by _Resetting_ a repository to your last `pull`. Simply run `git reset --hard HEAD` to erase any local changes, then run `git pull origin master` to grab the latest changes. 113 | 114 | You can connect to multiple remote repositories by running the `remote add` command above. You'll need a different name, for example instead of `origin` you can call a remote `gitlab` and then any command should reflect that, like `git pull gitlab master`. 115 | 116 | There is a **lot** more to `git` than what was shown here \(and I'm aware even that's already a shitton of information\). You can create and merge `branches`, `revert` to previous commits, make and accept `PR`s... [There is a lot more](https://www.google.com/search?q=git+tutorials) that you can you can learn! 117 | 118 | But for now... I think this massive wall of text is plenty. 119 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gitbook-guide", 3 | "version": "12.3.0", 4 | "description": "This guidebook was originally authored by eslachance#4611 then handed down to me for future updates, but when she started the book originally it was because the examples for discord.js and the documentation were quite daunting for newcomers. There was definitely a space for this kind of document online, made obvious by the recurring questions that pop up almost every day on the support channels.", 5 | "main": "index.js", 6 | "dependencies": { 7 | "gitbook-plugin-block-align": "^0.2.1", 8 | "gitbook-plugin-video": "^0.0.3" 9 | }, 10 | "devDependencies": {}, 11 | "scripts": { 12 | "test": "echo \"Error: no test specified\" && exit 1" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/AnIdiotsGuide/discordjs-bot-guide.git" 17 | }, 18 | "keywords": [], 19 | "author": "The Idiot's Staff", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/AnIdiotsGuide/discordjs-bot-guide/issues" 23 | }, 24 | "homepage": "https://github.com/AnIdiotsGuide/discordjs-bot-guide#readme" 25 | } 26 | -------------------------------------------------------------------------------- /retype.yml: -------------------------------------------------------------------------------- 1 | input: . 2 | output: .retype 3 | url: anidiots.guide 4 | branding: 5 | title: An Idiot's Guide 6 | favicon: .gitbook/assets/favicon.ico 7 | links: 8 | - text: Github 9 | link: https://github.com/AnIdiotsGuide/discordjs-bot-guide 10 | - text: Discord 11 | link: https://discord.gg/vXVxsAjSMF 12 | edit: 13 | repo: "https://github.com/AnIdiotsGuide/discordjs-bot-guide/edit/" 14 | footer: 15 | copyright: "© Copyright {{ year }}. All rights reserved." -------------------------------------------------------------------------------- /understanding/README.md: -------------------------------------------------------------------------------- 1 | # Understanding 2 | -------------------------------------------------------------------------------- /understanding/collections.md: -------------------------------------------------------------------------------- 1 | # Collections 2 | 3 | In this page we will explore Collections, and how to use them to grab data from various part of the API. 4 | 5 | A **Collection** is a _utility class_ that stores data. Collections are the Javascript Map\(\) data structure with additional utility methods. This is used throughout discord.js rather than Arrays for anything that has an ID, for significantly improved performance and ease-of-use. 6 | 7 | Examples of Collections include: 8 | 9 | * `client.users.cache`, `client.guilds.cache`, `client.channels.cache` 10 | * `guild.channels.cache`, `guild.members.cache` 11 | * message logs \(in the callback of `messages.fetch()`\) 12 | * `client.emojis.cache` 13 | 14 | ## Getting by ID 15 | 16 | Very simply, to get anything by ID you can use `Collection.get(id)`. For instance, getting a channel can be `client.channels.cache.get("81385020756865024")`. Getting a user is also trivial: `client.users.cache.get("139412744439988224")` 17 | 18 | ## Finding by key 19 | 20 | If you don't have the ID but only some other property, you may use `find()` to search by property: 21 | 22 | `let guild = client.guilds.cache.find(guild => guild.name === "discord.js - imagine a bot");` 23 | 24 | The _first_ result that returns `true` within the function, will be returned. The generic idea of this is: 25 | 26 | `let result = .find(item => item.property === "a value")` 27 | 28 | You can also be looking at other data, properties not a the top level, etc. Your imagination is the limit. 29 | 30 | Want a great example? Here's getting the first role that matches one of 4 role names: 31 | 32 | ```javascript 33 | const acceptedRoles = ["Mod", "Moderator", "Staff", "Mod Staff"]; 34 | const modRole = member.roles.cache.find(role => acceptedRoles.includes(role.name)); 35 | if (!modRole) return "No role found"; 36 | ``` 37 | 38 | {% hint style="info" %} 39 | Don't need to return the actual role? `.some()` might be what you need. It's faster than find, but will only return a boolean true/false if it finds something: 40 | {% endhint %} 41 | 42 | ```javascript 43 | const hasModRole = member.roles.cache.some(role => acceptedRoles.includes(role.name)); 44 | // hasModRole is boolean. 45 | ``` 46 | 47 | ## Custom filtering 48 | 49 | _Collections_ also have a custom way to filter their content with an anonymous function: 50 | 51 | `let large_guilds = client.guilds.cache.filter(g => g.memberCount > 100);` 52 | 53 | `filter()` returns a new collection containing only items where the filter returned `true`, in this case guilds with more than 100 members. 54 | 55 | ## Mapping Fields 56 | 57 | One great thing you can do with a collection is to grab specific data from it with `map()`, which is useful when listing stuff. `.map()` takes a function which returns a string. Its result is an array of all the strings returned by each item. Here's an example: let's get a complete list of all the guilds a bot is in, by name: 58 | 59 | ```javascript 60 | const guildNames = client.guilds.cache.map(g => g.name).join("\n") 61 | ``` 62 | 63 | Since `.join()` is an array method, which links all entries together, we get a nice list of all guilds, with a line return between each. Neat! 64 | 65 | We can also get a most custom string. Let's pretend the `user.tag` property doesn't exist, and we wanted to get all the user\#discriminator in our bot. Here's how we'd do it \(using awesome template literals\): 66 | 67 | ```javascript 68 | const tags = client.users.cache.map(u => `${u.username}#${u.discriminator}`).join(", "); 69 | ``` 70 | 71 | ## Combining and Chaining 72 | 73 | In a lot of cases you can definitely chain methods together for really clean code. For instance, this is a comma-delimited list of all the small guilds in a bot: 74 | 75 | ```javascript 76 | const smallGuilds = client.guilds.cache.filter(g => g.memberCount < 10).map(g => g.name).join("\n"); 77 | ``` 78 | 79 | ## More Data! 80 | 81 | To see **all** of the Discord.js Collection Methods, please [refer to the docs](https://discord.js.org/#/docs/collection/main/general/welcome). Since Collection extends Map\(\), you will also need to refer to [this awesome mdn page](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Map) which describe the native methods - most notably `.forEach()`, `.has()`, etc. 82 | -------------------------------------------------------------------------------- /understanding/events-and-handlers.md: -------------------------------------------------------------------------------- 1 | # Events and Handlers 2 | 3 | We already explored one event handler in [your first bot](../first-bot/your-first-bot.md), the `message` handler. Now let's take a look at some of the most important handlers that you will use, along with an example. 4 | 5 | {% hint style="danger" %} 6 | _**DO NOT NEST EVENTS**_ One important point: Do not nest any events \(aka "put one inside another"\). Ever. Events should be at the "root" level of your code, _beside_ the `message` handler and not within it. 7 | {% endhint %} 8 | 9 | ## The `ready` event and its importance 10 | 11 | Ah, asynchronous coding. So awesome. So hard to grasp when you first encounter it. The reality of discord.js and many, many other libraries you will encounter, is that code is not executed one line at a time, one after the other. 12 | 13 | It should have been made obvious with the user of `client.on("message")` which triggers for each message. To explain how the `ready` event is important, let's look at the following code: 14 | 15 | ```javascript 16 | const { Client, Intents } = require("discord.js"); 17 | const client = new Client({ 18 | intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MESSAGES] 19 | }); 20 | 21 | client.user.setActivity("Online!"); 22 | 23 | client.login("SuperSecretBotTokenHere"); 24 | ``` 25 | 26 | This code will not work, because `client` is not immediately available after it's been initialized. `client.user` will be undefined in this case, even if we flipped the setActivity and login lines. This is because it takes a small amount of time for discord.js to load its servers, users, channels, and all that jazz. The more servers the bot is on, the longer it takes. 27 | 28 | To ensure that `client` and all its "stuff" is ready, we can use the `ready` event. Any code that you want to run on bootup that requires access to the `client` object, will need to be in this event. 29 | 30 | Here's a simple example of using the `ready` event handler: 31 | 32 | ```javascript 33 | client.on("ready", () => { 34 | client.user.setActivity(`on ${client.guilds.cache.size} servers`); 35 | console.log(`Ready to serve on ${client.guilds.cache.size} servers, for ${client.users.cache.size} users.`); 36 | }); 37 | ``` 38 | 39 | ## Detecting New Members 40 | 41 | Another useful event once you've enabled the privileged intent and added `GUILD_MEMBERS` to your intents array that is, is [`guildMemberAdd`](https://discord.js.org/#/docs/main/master/class/Client?scrollTo=e-guildMemberAdd). Which triggers whenever someone joins any of the servers the bot is on. You'll see this on smaller servers: a bot welcomes every new member in the \#welcome channel. The following code does this. 42 | 43 | ```javascript 44 | client.on("guildMemberAdd", (member) => { 45 | console.log(`New User "${member.user.username}" has joined "${member.guild.name}"` ); 46 | member.guild.channels.cache.find(c => c.name === "welcome").send(`"${member.user.username}" has joined this server`); 47 | }); 48 | ``` 49 | 50 | The objects available for each event are important: they're only available within these contexts. Calling `message` from the `guildMemberAdd` would not work - it's not in context. `client` is always available within all its callbacks, of course. 51 | 52 | ## Errors, Warn and Debug messages 53 | 54 | Yes, bots fail sometimes. And yes, the library can too! There's a little trick we can use, however, to prevent complete crashes sometimes: Capturing the `error` event. 55 | 56 | The following small bit of code \(which can be anywhere in your file\) will catch all output message from discord.js. This includes all errors, warning and debug messages. 57 | 58 | {% hint style="danger" %} 59 | **NOTE:** The debug event **WILL** output your token partially, so exercise caution when handing over a debug log. 60 | {% endhint %} 61 | 62 | ```javascript 63 | client.on("error", (e) => console.error(e)); 64 | client.on("warn", (e) => console.warn(e)); 65 | client.on("debug", (e) => console.info(e)); 66 | ``` 67 | 68 | ## Testing Events 69 | 70 | So now you're wondering, how do I test those events? Do I have to join a server with an alternate account to test the guildMemberAdd event? Isn't that, like, super annoying? 71 | 72 | Actually, there's an easy way to test almost any event. Without going into too many details, `client` , your Discord Client, extends something called the `EventHandler`. Any time you see `client.on("something")` it means you're handling an `event` called `"something"`. But EventHandler has another function other than `on`. It has `emit`. Emit is the counterpart for `on`. When you `emit` an event, it's handled by the callback for that event in `on`. 73 | 74 | So what does it _mean_??? It means that if _you_ emit an event, your code can capture it. I know I know I'm rambling without giving you an example and you're here for examples. Here's one: 75 | 76 | ```javascript 77 | client.emit("guildMemberAdd", message.member); 78 | ``` 79 | 80 | This emits the event that normally triggers when a new member joins a server. So it's _pretending_ like this particular member has rejoined the server even if they have not. This obviously works for any event but you have to provide the proper arguments for it. Since `guildMemberAdd` requires only a member, any member will do \(see [FAQ](../frequently-asked-questions.md) to know how to get another member\). I can trigger the `ready` event again by using `client.emit("ready")` \(the ready event does not take any parameter\). 81 | 82 | What about other events? Let's see. `guildBanAdd` takes 2 parameters: `guild` and `user` , to simulate that a user was banned. So, you could `client.emit("guildBanAdd", message.guild, message.author)` to simulate banning the person sending a message. Again, getting those things \(Guilds and Users\) is in the FAQ. 83 | 84 | You can do all this in a "test" command, or you can do what I do: use `eval`. [Check the Eval command](../examples/making-an-eval-command.md) when you're ready to go that route. 85 | -------------------------------------------------------------------------------- /understanding/roles.md: -------------------------------------------------------------------------------- 1 | # Roles and Permissions 2 | 3 | Roles are a powerful feature in Discord, and admittedly have been one of the hardest parts to master in discord.js. This walk through aims at explaining how roles and permissions work. We'll also explore how to use roles to protect your commands. 4 | 5 | ## Role hierarchy 6 | 7 | Let's start with a basic overview of the hierarchy of roles in Discord. 8 | 9 | ... or actually not, they already explain it better than I care to: [Role Management 101](https://support.discord.com/hc/en-us/articles/214836687-Role-Management-101). Read up on that, then come back here. I'll wait. \(Yeah I know that's cheesy, so sue me\). 10 | 11 | ## Role code 12 | 13 | Let's get down to brass tacks. You want to know how to use roles and permissions in your bot. 14 | 15 | ### Get Role by Name or ID 16 | 17 | This is the "easy" part once you actually get used to it. It's just like getting any other Collection element, but here's a reminder anyway! 18 | 19 | ```javascript 20 | // get role by ID 21 | let myRole = message.guild.roles.cache.get("264410914592129025"); 22 | 23 | // get role by name 24 | let myRole = message.guild.roles.cache.find(role => role.name === "Moderators"); 25 | ``` 26 | 27 | {% hint style="info" %} 28 | To get the ID of a role, you can either mention it with a `\` before it, like `\@rolename`, or copy it from the role menu. If you mention it, the ID is the numbers between the `<>`. To get the ID of a role without mentioning it, enable developer mode in the Appearance section of your user settings, then go to the role menu in the server settings and right click on the role you want the ID of, then click "Copy ID". 29 | {% endhint %} 30 | 31 | ### Check if a member has a role 32 | 33 | In a `messageCreate` handler, you have access to checking the GuildMember class of the message author: 34 | 35 | ```javascript 36 | // assuming role.id is an actual ID of a valid role: 37 | if (message.member.roles.cache.has(role.id)) { 38 | console.log("Yay, the author of the message has the role!"); 39 | } 40 | 41 | else { 42 | console.log("Nope, noppers, nadda."); 43 | } 44 | ``` 45 | 46 | ```javascript 47 | // Check if they have one of many roles 48 | if (message.member.roles.cache.some(r=>["Dev", "Mod", "Server Staff", "Proficient"].includes(r.name)) ) { 49 | // has one of the roles 50 | } 51 | 52 | else { 53 | // has none of the roles 54 | } 55 | ``` 56 | 57 | {% hint style="info" %} 58 | To grab members and users in different ways see the [FAQ Page](../frequently-asked-questions.md). 59 | {% endhint %} 60 | 61 | ### Get all members that have a role 62 | 63 | ```javascript 64 | let roleID = "264410914592129025"; 65 | let membersWithRole = message.guild.roles.cache.get(roleID).members; 66 | console.log(`Got ${membersWithRole.size} members with that role.`); 67 | ``` 68 | 69 | ### Add a member to a role 70 | 71 | Alright, now that you have roles, you probably want to add a member to a role. Simple enough! Discord.js provides 2 handy methods to add, and remove, a role. Let's look at them! 72 | 73 | ```javascript 74 | let role = message.guild.roles.cache.find(r => r.name === "Team Mystic"); 75 | 76 | // Let's pretend you mentioned the user you want to add a role to (!addrole @user Role Name): 77 | let member = message.mentions.members.first(); 78 | 79 | // or the person who made the command: let member = message.member; 80 | 81 | // Add the role! 82 | member.roles.add(role).catch(console.error); 83 | 84 | // Remove a role! 85 | member.roles.remove(role).catch(console.error); 86 | ``` 87 | 88 | Alright I feel like I have to add a _little_ precision here on implementation: 89 | 90 | * You can **not** add or remove a role that is higher than the bot's. This should be obvious. 91 | * The bot requires `MANAGE_ROLES` permissions for this. You can check for it using the code further down this page. 92 | * Because of global rate limits, you cannot do 2 role "actions" immediately one after the other. The first action will work, the second will not. You can go around that by using `.roles.set([array, of, roles])`. This will overwrite all existing roles and only apply the ones in the array so be careful with it. 93 | 94 | ## Permission code 95 | 96 | ### Check specific permission of a member on a channel 97 | 98 | To check for a single permission override on a channel: 99 | 100 | ```javascript 101 | // Getting all permissions for a member on a channel. 102 | let perms = message.channel.permissionsFor(message.member); 103 | 104 | // Checks for Manage Messages permissions. 105 | let can_manage_messages = message.channel.permissionsFor(message.member).has("MANAGE_MESSAGES", false); 106 | 107 | // View permissions as an object (useful for debugging or eval) 108 | message.channel.permissionsFor(message.member).serialize(false) 109 | ``` 110 | 111 | {% hint style="info" %} We pass `false` for the checkAdmin parameter because Administrator channel overwrites don't implicitly grant any permissions, unlike in Roles or when you are the Guild Owner. \(The API will allow you to create an overwrite with Administrator, and even tell D.JS that a channel overwrite has had Administrator permissions set. Discord developers have stated this is [intended behavior](https://github.com/discord/discord-api-docs/issues/640).\) 112 | {% endhint %} 113 | 114 | ### Get all permissions of a member on a guild 115 | 116 | Just as easy, woah! 117 | 118 | ```javascript 119 | let perms = message.member.permissions; 120 | 121 | // Check if a member has a specific permission on the guild! 122 | let has_kick = perms.has("KICK_MEMBERS"); 123 | ``` 124 | 125 | ezpz, right? 126 | 127 | Now get to coding! 128 | 129 | ## ADDENDUM: Permission Names 130 | 131 | Click [here](https://discord.js.org/#/docs/main/master/class/Permissions?scrollTo=s-FLAGS) for the full list of internal permission names, used for `.has(name)` in the above examples 132 | -------------------------------------------------------------------------------- /understanding/sharding.md: -------------------------------------------------------------------------------- 1 | # Sharding 2 | 3 | **Sharding** is the method by which a bot's code is "split" into multiple _instances_ of itself. When a bot is sharded, each shard handles only a certain percentage of all the guilds the bot is on. 4 | 5 | There are additional difficulties when sharding a bot that add complexity to your code \(one of the reasons you shouldn't shard too early\). You do not need to worry about sharding until your bot hits around 2,400 guilds. YOU MUST SHARD by the time you hit 2,500 guilds, however. 6 | 7 | ## Sharding Styles 8 | 9 | There are two styles of sharding that we'll be discussing: [internal sharding](sharding.md#internal-sharding) and [traditional sharding](sharding.md#traditional-sharding). Each of these sharding styles holds benefits depending on your situation. 10 | 11 | ## Internal Sharding 12 | 13 | `internal` sharding is the method by which a bot's code creates multiple shard connections to the Discord API _within a single process_. This means that all the guilds, channels, and users on one shard will be available to another shard via a direct call \(e.g. `client.guilds.cache.get('GUILD_ID')`\). Due to the large memory size the single bot process will grow to using this style of sharding, it is not ideal for bots with many guilds. 14 | 15 | ### Internally Sharded Client 16 | 17 | If you would like to use this, adjust the Client options in your main bot file where you define your client like so: 18 | 19 | ```javascript 20 | /* 21 | The following code goes into your main bot file. 22 | */ 23 | 24 | // Include discord.js ShardingManager 25 | const { Client } = require("discord.js"); 26 | 27 | // When we define our client, we include the property "shardCount" 28 | // and set it to 'auto' to allow the client to automatically create 29 | // the correct number of shards. 30 | // If you would like to have a different number of shards, you may 31 | // also set this to a number. 32 | const client = new Client({ shardCount: "auto" }); 33 | ``` 34 | 35 | ## Traditional Sharding 36 | 37 | `traditional` sharding is the method by which a bot's code spawns individual child processes via a main shard manager process, each child process being one shard of the bot. When using this style of sharding, guilds, channels, and users on one shard will _not_ be available to another via direct call \(e.g. `client.guilds.cache.get('GUILD_ID')`\) because each shard is in a separate process. 38 | 39 | This style of sharding is ideal for larger bots, or bots that need to be scalable to allow for future growth. The rest of this page will discuss [how to make use of traditional sharding](sharding.md#example-sharding-manager-code) and [how to share information between shards](sharding.md#sharing-information-between-shards). 40 | 41 | To learn how to make use of this, read on! 42 | 43 | ### Traditional Sharding Caveats 44 | 45 | * Collections do not cache data from all shards, so you can't grab data from a guild in another shard easily. 46 | * In order to do anything across shards you need to worry about using [`fetchClientValues`](sharding.md#fetchclientvalues) and [`broadcastEval`](sharding.md#broadcasteval) \(Examples and explanation below\). 47 | 48 | And again: 49 | 50 | * Traditionally sharded bots often gain very marginal performance increase and might even use _more memory_ due to using more node processes. 51 | * If you're using any sort of database or connection, multiple shards may cause issues with multiple processes connecting to a single end point. 52 | 53 | ### Example Sharding Manager Code 54 | 55 | ```javascript 56 | /* 57 | The following code goes into it's own file, and you run this file 58 | instead of your main bot file. 59 | */ 60 | 61 | // Include discord.js ShardingManager 62 | const { ShardingManager } = require("discord.js"); 63 | 64 | // Create your ShardingManager instance 65 | const manager = new ShardingManager("./YOUR_BOT_FILE_NAME.js", { 66 | // for ShardingManager options see: 67 | // https://discord.js.org/#/docs/main/stable/class/ShardingManager 68 | totalShards: "auto", 69 | token: "YOUR_TOKEN_GOES_HERE" 70 | }); 71 | 72 | // Emitted when a shard is created 73 | manager.on("shardCreate", (shard) => console.log(`Shard ${shard.id} launched`)); 74 | 75 | // Spawn your shards 76 | manager.spawn(); 77 | ``` 78 | 79 | ## Sharing Information Between Shards 80 | 81 | Remember how we were talking about sharding being a method of "splitting" the bot into multiple instances of itself? Because of this, things like your adding your total guilds or getting a specific guild are not as simple as they were before. We must now use either [`fetchClientValues`](sharding.md#fetchclientvalues) or [`broadcastEval`](sharding.md#broadcasteval) to get information from across shards. 82 | 83 | These two functions are your go-to for getting any information from other shards, so get familiar with them! 84 | 85 | ### FetchClientValues 86 | 87 | [`fetchClientValues`](https://discord.js.org/#/docs/main/stable/class/ShardClientUtil?scrollTo=fetchClientValues) gets Client properties from all shards. This is what you should use when you would like to get any of the nested properties of the Client, such as `guilds.cache.size` or `uptime`. It's useful for getting things like Collection sizes, basic client properties, and unprocessed information about the client. 88 | 89 | Example: 90 | 91 | ```javascript 92 | /* 93 | Example result of fetchClientValues() on a bot with 4,300 guilds split across 94 | 4 shards. 95 | Assume this is being executed on shard 0, the first shard. 96 | */ 97 | 98 | // If we just get our client.guilds.cache.size, it will return 99 | // only the number of guilds on the shard this is being run on. 100 | console.log(client.guilds.cache.size); 101 | // 1050 102 | 103 | // If we would like to get our client.guilds.cache.size from all 104 | // of our shards, we must make use of fetchClientValues(). 105 | const res = await client.shard.fetchClientValues("guilds.cache.size"); 106 | console.log(res); 107 | // [ 108 | // 1050, // shard 0 109 | // 1100, // shard 1 110 | // 1075, // shard 2 111 | // 1075 // shard 3 112 | // ] 113 | ``` 114 | 115 | Let's say you want to do something like get your total server count - In a non-sharded environment, this would be as simple as getting the `client.guilds.cache.size`. However, in this case `client.guilds.cache.size` will not return the total servers your bot is in. Instead it returns only the total number of servers _on this shard_, like in the first part of the example above. 116 | 117 | Here's an example of a function that uses `fetchClientValues()` to first get, then add the total number of guilds from _all shards_ \(i.e. your bot's total guild count\): 118 | 119 | ```javascript 120 | /* 121 | Example by ZiNc#2032 122 | 123 | discord.js version 12.x 124 | client = new discordjs.Client() 125 | 126 | returns number 127 | */ 128 | 129 | const getServerCount = async () => { 130 | // get guild collection size from all the shards 131 | const req = await client.shard.fetchClientValues("guilds.cache.size"); 132 | 133 | // return the added value 134 | return req.reduce((p, n) => p + n, 0); 135 | } 136 | ``` 137 | 138 | {% hint style="info" %} 139 | `fetchClientValues()` does not allow you to make use of javascript methods or client methods to get or process information before returning it. It only allows you to get information from client properties. 140 | {% endhint %} 141 | 142 | ### BroadcastEval 143 | 144 | [`broadcastEval`](https://discord.js.org/#/docs/main/stable/class/ShardClientUtil?scrollTo=broadcastEval) evaluates the input in the context of each shard's Client\(s\). This is what you should use when you want to execute a method or process data on a shard and return the result. It's useful for getting information that isn't available through client properties and must instead be retrieved through the use of methods. 145 | 146 | Example: 147 | 148 | ```javascript 149 | /* 150 | Example of result of broadcastEval() on a bot with 4 servers split across 151 | 2 shards. 152 | Assume this is being executed on shard 0, the first shard. 153 | */ 154 | 155 | // If we just map our guilds' members.cache.size, it will return 156 | // only the mapped members.cache.size of the shard this is being run on. 157 | console.log(client.guilds.cache.map((guild) => guild.members.cache.size)); 158 | // [ 159 | // 30, 160 | // 25 161 | // ], 162 | 163 | // If we would like to map our guilds' members.cache.size from our 164 | // servers on all of our shards, we must make use of broadcastEval(). 165 | // Remember, this runs in the context of the client, so we refer to the 166 | // Client using "this". 167 | const res = await client.shard.broadcastEval((c) => c.guilds.cache.map((guild) => 168 | guild.members.cache.size)); 169 | console.log(res); 170 | // [ 171 | // [ // shard 0 172 | // 30, 173 | // 25 174 | // ], 175 | // [ // shard 1 176 | // 55, 177 | // 10 178 | // ] 179 | // ] 180 | ``` 181 | 182 | Say you want to get a guild from your client. In a non-sharded environment, you would simply use `client.guilds.cache.get('ID')` or something of that nature and then carry on with your code. In this case however, it is possible that the guild you're trying to get _is not present on the shard_. In order to get the guild for use, you would then need to fetch it from whatever shard it is present on using `broadcastEval()`. 183 | 184 | Here's an example of a function that uses `broadcastEval()` to get a single guild no matter what shard it is present on: 185 | 186 | ```javascript 187 | /* 188 | Example by ZiNc#2032 189 | 190 | NOTE: Fetched guild's properties such as "Guild.members.cache" and 191 | "Guild.roles.cache" will not be Managers or Collections; these 192 | properties will be arrays of snowflake IDs. 193 | 194 | discord.js version 12.x 195 | client = new discordjs.Client() 196 | 197 | returns Guild 198 | */ 199 | 200 | const getServer = async (guildID) => { 201 | // try to get guild from all the shards 202 | const req = await client.shard.broadcastEval((c, id) => c.guilds.cache.get(id), { 203 | context: guildID 204 | }); 205 | 206 | // return Guild or null if not found 207 | return req.find(res => !!res) || null; 208 | } 209 | ``` 210 | 211 | {% hint style="info" %} 212 | `broadcastEval()` will only return basic javascript objects, and will not return Discord.js structures such as Collections, Guild objects, Member object, and the likes. You will need to interact directly with the API or build these structures yourself after getting a response back from your `broadcastEval()`. 213 | {% endhint %} 214 | 215 | Example of a Guild object returned by [`broadcastEval`](sharding.md#broadcasteval): 216 | 217 | ```javascript 218 | const res = await client.shard.broadcastEval((c) => c.guilds.cache.map((guild) => 219 | guild.members.cache.size)); 220 | console.log(res); 221 | // [ 222 | // [ // whichever shard has the guild 223 | // { 224 | // members: [ 225 | // '123456789123456789', '123456789123456789', '123456789123456789', 226 | // '123456789123456789', '123456789123456789', '123456789123456789', 227 | // '123456789123456789', '123456789123456789', '123456789123456789', 228 | // '123456789123456789', '123456789123456789', '123456789123456789', 229 | // '123456789123456789', '123456789123456789', '123456789123456789' 230 | // ], 231 | // channels: [ 232 | // '123456789123456789', '123456789123456789', '123456789123456789', 233 | // '123456789123456789', '123456789123456789', '123456789123456789', 234 | // '123456789123456789', '123456789123456789', '123456789123456789', 235 | // '123456789123456789', '123456789123456789', '123456789123456789', 236 | // '123456789123456789', '123456789123456789', '123456789123456789' 237 | // ], 238 | // roles: [ 239 | // '123456789123456789', '123456789123456789', '123456789123456789', 240 | // '123456789123456789', '123456789123456789', '123456789123456789' 241 | // ], 242 | // emojis: [ 243 | // '123456789123456789', '123456789123456789', '123456789123456789', 244 | // '123456789123456789', '123456789123456789', '123456789123456789' 245 | // ], 246 | // afkChannelID: null, 247 | // afkTimeout: 300, 248 | // applicationID: null, 249 | // banner: null, 250 | // bannerURL: null 251 | // createdTimestamp: 1234567891234, 252 | // defaultMessageNotifications: 'MENTIONS', 253 | // deleted: false, 254 | // description: null, 255 | // explicitContentFilter: 'MEMBERS_WITHOUT_ROLES', 256 | // features: [ 'ANIMATED_ICON', 'INVITE_SPLASH' ], 257 | // icon: 'ICON_ID', 258 | // iconURL: 'ICON_URL', 259 | // id: '123456789123456789', 260 | // joinedTimestamp: 1234567891234, 261 | // large: true, 262 | // memberCount: 6000, 263 | // mfaLevel: 1, 264 | // name: 'Server_Name', 265 | // nameAcronym: 'E', 266 | // ownerID: '123456789123456789', 267 | // premiumSubscriptionCount: 3, 268 | // premiumTier: 1, 269 | // publicUpdatesChannelID: null, 270 | // region: 'us-east', 271 | // rulesChannelID: null, 272 | // shardID: 0, 273 | // splash: 'SPLASH_ID', 274 | // splashURL: 'SPLASH_URL', 275 | // systemChannelFlags: 1, 276 | // systemChannelID: '123456789123456789', 277 | // vanityURLCode: null, 278 | // verificationLevel: 'MEDIUM', 279 | // } 280 | // ], 281 | // 282 | // ...null // all other shard replies 283 | // ] 284 | ``` 285 | -------------------------------------------------------------------------------- /video-guides/README.md: -------------------------------------------------------------------------------- 1 | # Video Guides 2 | 3 | Coming Soon...? 4 | --------------------------------------------------------------------------------