#4 Discord bot & stackable queries
Fourth article of a series about Phoenix and Elixir. This one will be focused on creating a Discord bot and making Ecto queries easy.
Discover the Phoenix Series
#1・Handle HTML & API Authentication
#2・Automatic Deployment
#3・Pimp my Phoenix
#4・Discord Bot & Stackable Queries
Before jumping right into this new article, note that I am currently using Phoenix 1.5.10 for this series and that all articles are going to get an update soon to match the current state (1.6.x) of Phoenix : I am talking about the new authentication logic generation, assets managements, config, etc. Stay tuned!
For this 4th article, I want to show you how easy it is to create a Discord bot using Nostrum. We will create this bot on top of TastyRecipes, the project we are developping since we started this series! Next, let's talk about something that I like to call "stackable queries", I use it to simplify the way I query data using Ecto.
Nostrum is an Elixir library wrapping Discord API. You probably can do your own wrapper but I will use this one because I am way too lazy for this.
Setup Discord consumer
First of all, add Nostrum to your dependencies :
To avoid dependencies resolution errors, you will need to specify override for cowlib. It seems that this problem is well known and will be resolved in the next major update.
Create the consumer that will handle messages coming from your Discord server:
Note that we use pattern matching to handle events. Since this is just a quick demonstration of Nostrum workflow, we will just process create_message
events.
For the next part, you will need to create an application on the Discord developer portal. Once it is done, you will find a token inside the Bot section of your app. Use this token to update the Nostrum configuration:
Let's add our consumer to the supervisor's children so it is started alongside Phoenix:
We should now be ready to make our first tests ! For this, you need to add your bot to your Discord server. You can create the invitation link that will do the job. It should looks like:
https://discord.com/api/oauth2/authorize?client_id=123456789012345678&permissions=2048&scope=bot%20applications.commands
You just need to replace the client_id
value with your application ID. Permissions are set to 2048
which means that when clicking the link, you will be asked to authorize the bot to send messages on your server. In our case, this configuration is enough but you can update it as you may need more permissions.
When your bot has arrived, try to communicate with him! Run your Phoenix server mix do deps.get, phx.server
and try to type !hello
in your discord server :
But... Is that all?
Stackable queries
Alright, now that we can chitchat with our bot, maybe we can update it a little bit so it will do something useful! Let's think about it. Here is a short list of what we can do :
- Retrieve number of recipes available
- Retrieve given user's last recipe
- List all user's recipes containing a specfic word in its name
First, let's update handle_event/1
logic so it will handle mutliple commands:
Printing the number of available recipes looks pretty simple, here is something we can do :
Note: I like to use Inflex to pluralize values and word inflections in general.
Next, when looking for a specific user's last recipe, things spice a lit bit :
Using user's email, our function last_recipe/1
will try to find his most recent recipe. Pattern matching is then used to send a response depending of the outcome.
Another one to find recipes that contain a string in their name and belongs to a given user:
This is pretty much the same thing that for finding last recipe with some little updates.
Lets dry this queries
If you take a closer look at the queries, you can see that we kind of repeat ourself when we look for a specific user email, for example. Now imagine that instead of having three commands, we had a hundred ones to manage, where we do the exact same where
check. For easier management, it might be useful and more readable for this piece of code to be in a single place :
Let's do the same thing for recipe's name matching :
This is way more convenient ! You will not tear your hair out if for some reasons you have to update some part of the query, rename fields or update your database schema. The bonus is that you can "stack" (or chain or compose, call it as you prefer) your queries in a simple way :
Don't you think this is better? This will take some times to adapt your code and create every functions needed to query your data but what a gain of time once everything is setup!
Note that you can chain as many queries as you want but you will need to integrate a little trick to handle multiple joins.
I hope you liked today's article! See you soon for more.
If you have a problem and no one else can help. Maybe you can hire the Kalvad-Team.