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