github.com/Jeffail/benthos/v3@v3.65.0/website/cookbooks/discord_bot.md (about) 1 --- 2 id: discord-bot 3 title: Create a Discord Bot 4 description: Learn how to use Benthos to create a vanity chat bot. 5 --- 6 7 Stream processing is stupid and boring, and so it's important to re-purpose tools like Benthos for fun things occasionally. This cookbook outlines how Benthos can be used to create a Discord bot for important tasks such as providing insults and bad jokes to your chat. If you're a member of the [Benthos Discord server][discord-link] then you're likely already familiar with Blob Bot which is the resulting product. 8 9 import ReactPlayer from 'react-player/youtube'; 10 11 <div className='container margin-vert--lg'> 12 <div className='row row--no-gutters'> 13 <ReactPlayer 14 className='col' 15 height='300px' 16 url='https://www.youtube.com/embed/nX5-s1-Vrjc' 17 controls={true} 18 /> 19 </div> 20 </div> 21 22 ## Consuming Messages 23 24 Before you start messing with Benthos you need to register a new bot with the [Discord Developer Portal][discord-applications]. Start by building an Application, then use the build-a-bot page to choose a bot name and avatar. You should end up with a token generated for the bot, and you'll also need to add it to your server. 25 26 As soon as your bot is added to your server and you have a token you can immediately begin consuming messages from a channel with the [`discord` input][inputs.discord]: 27 28 ```yaml 29 input: 30 discord: 31 poll_period: 3s 32 channel_id: ${DISCORD_CHANNEL} 33 bot_token: ${DISCORD_BOT_TOKEN} 34 cache: request_tracking 35 limit: 10 36 37 cache_resources: 38 - label: request_tracking 39 file: 40 directory: /tmp/discord_bot 41 ``` 42 43 > If you aren't sure how to access the ID of a channel try [this tutorial][discord-channel-id]. 44 45 The `poll_period` shouldn't be too short as it'll exhaust your rate limits. If you plan to use the bot for hitting multiple Discord APIs then give it a fair few seconds between each poll. It's also necessary to point the input to a [cache resource][caches], and this will be used to store the ID of the latest message received for paginating the messages endpoint. 46 47 The `limit` is the maximum number of messages to consume from the channel when we haven't got a message to track and are consuming a backlog. The first time we run our bot we will pull a maximum of 10 of the latest messages in the channel, the maximum you can set here is 100. 48 49 If you were to run this config (setting the channel and bot token as env vars in a file called `testing.env`) you'll see it print messages from the channel to stdout in JSON form: 50 51 ```sh 52 $ benthos -e testing.env -c ./config.yaml 53 {"content":"so i like totally just tripped over my own network cables","author":{"id":"1234"}} 54 {"content":"like omg that is SO you!!!","author":{"id":"4321"}} 55 {"content":"yas totally","author":{"id":"1234"}} 56 {"content":"yeah","author":{"id":"4321"}} 57 ``` 58 59 It might be tempting to leave your silent surveillance bot running indefinitely but that's creepy and weird, so instead let's add the ability to respond to messages. 60 61 ## Writing Messages 62 63 Writing messages to a Discord channel is pretty easy. You can feed the [`discord` output][outputs.discord] either a JSON object following the [Message Object structure][discord-message-object], or just a raw string and the structure will be created for you. Therefore we can write a hypothetical uppercasing echo bot with a simple [Bloblang mapping][bloblang]: 64 65 ```yaml 66 pipeline: 67 processors: 68 - bloblang: | 69 root = if !this.content.has_prefix("SHOUTS BACK") { 70 "SHOUTS BACK BOT SAYS " + this.content.uppercase() 71 } else { 72 deleted() 73 } 74 75 output: 76 discord: 77 channel_id: ${DISCORD_CHANNEL} 78 bot_token: ${DISCORD_BOT_TOKEN} 79 ``` 80 81 If we add that to the end of the first config you should see the bot respond to messages in the channel by posting an uppercase version of it with a prefix. Note that we also delete the message in our mapping if it has the same prefix that we're adding ourselves, which is a quick and dirty way of ensuring the bot doesn't echo its own messages. 82 83 ## Custom Commands 84 85 Shout bot is clearly an absolute riot and a true fan favourite. However, it will get old fast. Let's make our bot more elegant by introducing some commands by swapping our plain mapping with a [`switch` processor][processors.switch]: 86 87 ```yaml 88 pipeline: 89 processors: 90 - switch: 91 - check: this.type == 7 92 processors: 93 - bloblang: 'root = "Welcome to the server <@%v>!".format(this.author.id)' 94 95 - processors: 96 - bloblang: 'root = deleted()' 97 ``` 98 99 By changing our mapping out to this switch we can add specialised commands for different message types, and if none of the cases match then we don't respond. Technically, we can do all of this within a single Bloblang mapping by using a match expression, but having a switch processor would also allow us to add cases where we do cool things like hit other APIs, etc. 100 101 The only case we've added here is one that activates when the message type is a specific one sent when a new person joins, and in response we give them a warm welcome. The welcome mentions the new user by injecting the user id into the welcome string with `.format(this.author.id)`, which replaces the `%v` placeholder with the author ID (the user that joined and therefore created the join message). 102 103 This response is cool but not very interactive, let's add a few commands that people can play with: 104 105 ```yaml 106 pipeline: 107 processors: 108 - switch: 109 - check: this.type == 7 110 processors: 111 - bloblang: 'root = "Welcome to the server <@%v>!".format(this.author.id)' 112 113 - check: this.content == "/joke" 114 processors: 115 - bloblang: | 116 let jokes = [ 117 "What do you call a belt made of watches? A waist of time.", 118 "What does a clock do when it’s hungry? It goes back four seconds.", 119 "A company is making glass coffins. Whether they’re successful remains to be seen.", 120 ] 121 root = $jokes.index(timestamp_unix_nano() % $jokes.length()) 122 123 - check: this.content == "/roast" 124 processors: 125 - bloblang: | 126 let roasts = [ 127 "If <@%v>'s brain was dynamite, there wouldn’t be enough to blow their hat off.", 128 "Someday you’ll go far <@%v>, and I really hope you stay there.", 129 "I’d give you a nasty look, but you’ve already got one <@%v>.", 130 ] 131 root = $roasts.index(timestamp_unix_nano() % $roasts.length()).format(this.author.id) 132 133 - processors: 134 - bloblang: 'root = deleted()' 135 ``` 136 137 Here we have two new commands. If someone posts a message "/joke" then we respond by selecting one of several exceptionally funny jokes from a static list in the mapping. 138 139 The second new command is "/roast" and is exclusively for brave souls as the responses can be cruel and personal. The command works similarly to "/joke" with the exception being the ID of the user that made the command will be injected into the roast, as mentioning the target of the roast makes it significantly more heartbreaking (as intended). 140 141 ## Hitting Other APIs 142 143 Clicking websites and browsing the internet is very difficult and most people are simply too busy for it, it'd therefore be useful if we could have our bot do some browsing for us occasionally. 144 145 The final command we're going to add to our bot is "/release", where it will hit the Github API and find out for us what the latest Benthos release is: 146 147 ```yaml 148 pipeline: 149 processors: 150 - switch: 151 # Other cases omitted for brevity 152 - check: this.content == "/release" 153 processors: 154 - bloblang: 'root = ""' 155 - try: 156 - http: 157 parallel: true 158 url: https://api.github.com/repos/Jeffail/benthos/releases/latest 159 verb: GET 160 - bloblang: 'root = "The latest release of Benthos is %v: %v".format(this.tag_name, this.html_url)' 161 162 - catch: 163 - log: 164 fields: 165 error: "${! error() }" 166 message: "Failed to process message" 167 - bloblang: 'root = "Sorry, my circuits are all bent from twerking and I must have malfunctioned."' 168 ``` 169 170 Here we've added a switch case that clears the contents of the message, hits the Github API to obtain the latest Benthos release as a JSON object, and finally maps the tag name and the URL of the release to a useful message. 171 172 > We're hitting the Github API with the [generic `http` processor][processors.http], which can be configured to work with most HTTP based APIs. In fact, the Discord input and output are actually [configuration templates][templates] that use the generic HTTP components [under the hood][templates.discord]. 173 174 Since this command is networked and therefore has a chance of failure we've added some [error handling][error-handling] mechanisms after the switch processor so that it'd capture errors from this new case and any new cases we add later. 175 176 Within the catch block we simply log the error for the admin to peruse and change the response message out for a generic "whoopsie daisy" apology. 177 178 ## Final Words 179 180 The full config for Blob Bot (with some super secret responses redacted) can be found [in the Github repo][full-config]. To find out more about Bloblang check out [the guide page][bloblang]. To find out more about config templates check out the [templates documentation page][templates]. 181 182 If you want to play with Blob Bot then [join our Discord][discord-link]. There are also some humans in there that will help you manage your disappointment when you see Blob Bot in action. 183 184 [discord-link]: https://discord.gg/6VaWjzP 185 [discord-applications]: https://discord.com/developers/applications 186 [discord-channel-id]: https://support.discord.com/hc/en-us/articles/206346498-Where-can-I-find-my-User-Server-Message-ID- 187 [discord-message-object]: https://discord.com/developers/docs/resources/channel#message-object 188 [inputs.discord]: /docs/components/inputs/discord 189 [outputs.discord]: /docs/components/outputs/discord 190 [caches]: /docs/components/caches/about 191 [processors.switch]: /docs/components/processors/switch 192 [processors.http]: /docs/components/processors/http 193 [bloblang]: /docs/guides/bloblang/about 194 [full-config]: https://github.com/Jeffail/benthos/blob/master/config/examples/discord_bot.yaml 195 [error-handling]: /docs/configuration/error_handling 196 [templates]: /docs/configuration/templating 197 [templates.discord]: https://github.com/Jeffail/benthos/blob/master/template/outputs/discord.yaml