Skip to content

Instantly share code, notes, and snippets.

@christophrumpel
Created May 24, 2018 13:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save christophrumpel/4c1a163527bd6035131f0e38f135a99b to your computer and use it in GitHub Desktop.
Save christophrumpel/4c1a163527bd6035131f0e38f135a99b to your computer and use it in GitHub Desktop.
title categories summary preview_image preview_image_twitter published
How I Built The LaravelQuiz Chatbot With BotMan and Laravel
laravel, chatbots
Ever wondered how well you know your favorite PHP framework Laravel? Give the LaravelQuiz Chatbot a try, and you will find out. This article is a step by step guide on how I created this chatbot with BotMan and Laravel.
images/blog/headers/blog_header_laravelquiz_web.png
images/blog/headers/blog_header_laraelquiz_web.png
true

Screenshot showing phone mockups witht laravel quiz bot examples

Intro

Hey everyone. In this article, I want to show you how I built the LaravelQuiz Chatbot. If you haven't tried it yet, it is a good idea to check it out before reading this article. This will help to understand what I am talking about. Right now it only supports Telegram.

The chatbot provides a quiz with all kinds of questions about the Laravel framework. Every question comes with up to three possible answers. You need to pick the right one to collect points and make it to the highscore. But besides the ranks, the quiz is about having a good time. Enjoy the questions and see what you know about Laravel. Have fun!

Note: All the code can be found in the public repository.

Setup

Let's start this project by installing BotMan Studio.

https://gist.github.com/75b2bd1b7f236aef619bfd9523929b4f

Since we want to provide this bot on Telegram, we need to install the TelegramDriver as well.

https://gist.github.com/a5f8c8d89c1070aa141fff524dc7b560

This will install the Telegram driver and add the Telegram config file /config/botman/telegram.php. In there, you will see that it loads the Telegram bot token from the environment file. You get this token when you create a Telegram bot. Add it to the .env file like:

https://gist.github.com/c149d2bc4c787dd7c0357b9a2d973d04

Now we can connect the BotMan application to the Telegram bot. Create a secured public URL with Laravel Valet.

https://gist.github.com/6885e3dd693a208c1448b63708f9cfed

There is an excellent artisan command, that lets us set our BotMan URL via the command line. Make sure to add "/botman" to your application's URL. Just follow the instructions.

https://gist.github.com/88b9f1ef8f1f88aebc7ebf52de603c8c

You should get a success message. If not, please check that your provided URL is correct. To make sure the bot is connected to our application, type hi.

Screenshot of the test message

Note: If you don't use Laravel Valet, try a service like ngrok.

Storing Questions and Answers

Next, we want to think about our questions. The bot will ask the user about 20 questions. It will also provide two or three possible answers. I want to store questions and answers in a database. Each answer is connected to one question. So we need a table for the questions and one for the answers. We start with the questions.

Questions

https://gist.github.com/4e81cbde70884b74cd97edc1ab78fdbf

The model we will use later. Now we define our table structure for the questions.

https://gist.github.com/3969dbf0021c01c73766232df07eb917

Every question has text and points. The user will get these points if he answers correctly.

Answers

Next, we create a migration for the answers.

https://gist.github.com/a5f30e0621dc7dd0083c86e8fe247e8b

Since an answer is connected to a question, we use a question_id field. Additionally, we also add a text field and a flag to define if this answer is the correct one.

https://gist.github.com/b35d5df690e649f1bd41028758f64078

Create and Fill The Tables

Before we can create these tables, we need to define our database credentials in the .env file. I use Laravel Valet with this settings. Of course, they will be different, depending on your local setup.

https://gist.github.com/bc3f77148e9f9c252ba816cc3fa0d569

Now, we can create the tables.

https://gist.github.com/15871d641b8543695f3f71bb74919226

Nice! Our tables are ready to get filled. I decided to use a Laravel Seeder class to do so. This way I can keep all questions and answers inside the repository and use an artisan command to fill the database.

https://gist.github.com/d2fa758fc879953b691c29aa369a9a5e

You could use two different seeders as well, but I am okay with one here. In the run method of the seeder, I am truncating the tables and then store each question and its answers.

https://gist.github.com/a696238e9edb4fcecffa6e1766d04e47

For the actual data, I use a separate getData method to keep it cleaner. In this method, I return a big collection with all the questions and answers. For better reading, I only show two of them here.

https://gist.github.com/f7f619373e21241e774f353fa60bd22f

As you can see, the code is quite straightforward. It is just a big array with all the data for questions and answers. Don't forget to import the namespaces for the Answer and Question class. Inside database/seeds/DatabaseSeeder.php I add this new seeder to the run method.

https://gist.github.com/b22ddbde62bdaf0b1c28b48a153dd255

As a result, we can now seed the tables with an artisan command.

https://gist.github.com/c175829cdf57f25d49af216260b79331

Voilà, we have our questions and answers in the database!

QuizConversation

Now that everything is setup, we can go on and implement the chatbot logic for showing the questions to the user. I already know that I want to use a conversation class just for this purpose. Create a app/Conversation/QuizConversation.php file and fill it with this base code.

https://gist.github.com/b00370a0b237857109a7236491c4d9a0

Let's recap what we want to achieve - The questions are stored in the database. We want to grab them and ask the user each of them. If you have followed other chatbot tutorials of mine, you've probably recognized that I usually write a method for every question. This would lead to more than 20 methods and a lot of code here. So what else can we do? All the questions are very similar to each other. The only significant difference is that some have two and some three possible replies. But that's not a big deal. The goal is to use one single method to ask these questions.

Show Info

But before dealing with the questions, we tell the user what this conversation is about.

https://gist.github.com/96d709ded0236ae94f02308f3490c742

To try this out, we need to add a listener to the routes/botman.php file.

https://gist.github.com/60fb03810ad1d16766d4045be081f99e

Typing start will kick off this conversation. Try it out in the Telegram chat.

Screenshot showing the start infor message of the bot

Looping Questions

To make looping the questions work, we need a few things. First, we define some more class properties. We will require them to show the user how many questions are left and to count the user's points.

https://gist.github.com/0d8d1a34cd9465896cb0c3b0d55c5d8a

Then in the run method, we load our questions and fill some properties. We also shuffle the questions and switch the collection's key to the id. This will make it easier for us to remove items from the collection.

https://gist.github.com/c0fa2d193d34245f11e7047c914995a8

Then, before showing a question, we check if there are any left. After every question we will remove it. When there are no more questions left, we show the user the quiz result.

https://gist.github.com/43b8b5f590e500df234302bd061c86f1

And here is the askQuestion method. It always gets the first item of the questions collections and uses the BotMan ask method to show the text to the user, as well as the answers. We loop over the question's answers, to add a button for each of them to the template.

https://gist.github.com/cae00af2a6b58ca22c4b04094a03a6e8

Also, we add a showResult method, which is called when there are no more questions left. When you start the conversation again, you should be able to see all the questions. Each click on an answer will result in the next question, and after the last one, you should get the finish message. Try it out yourself.

Screenshot showing looped messages work

Note: Since conversations get stored in the cache, you sometimes need to clear it with "php artisan cache:clear" to see changes.

Adding Details

This is already great. We can use this code to show as many questions as we want. At any time we can add a question to the database and the code will still work. I love it!

Now I want to work on the details. There are a bunch of things to add:

  • show current question
  • show how many questions are left
  • show the users' choice
  • tell if the reply was correct
  • show the result

I am not able to go into each of them in depth. This is why I will show the changed methods and explain important steps.

https://gist.github.com/8827117809fa9966d17f6033ab8a891b

Note that there is a new separate createQuestionTemplate method to make the code cleaner. Also interesting is that I check if the user's reply is from a button click. If not, I will repeat this question. This is possible because the buttons have the answer's id as the value. We use it to find the answer.

https://gist.github.com/ed7c4a461008255e73de3601f4d052e3

HighscoreConversation

Create a Table and Model

To make the quiz more fun, we need a highscore. We start by adding a migration and the model.

https://gist.github.com/83cee675452968bf5410f3baa99f5266

In there, we save the user's chat ID, the name, points, count of correct answers and how often he tried. Since we work with points, it might be possible that someone answers less question correctly, but still leads the highscore table.

The Highscore model will include a few things. First, we define the fillable property to whitelist what values we can store later. Also, the table name needs to be defined here because it differs from the traditional names.

https://gist.github.com/95446711dcfe415d3c5c0083a7c22b58

Then we add a method for storing highscore entries. I use the updateOrCreate method to prevent duplicate entries. Every user should only have one entry. With every new try, the highscore will get updated. The unique field for these entries is the chat_id and not, like normally, the id field. The updateOrCreate method gets this info as the first parameter. Also note how I increase the tries field if the entry is not a new one. For this, the wasRecentlyCreated method comes in handy.

https://gist.github.com/1f6a756b02815064a029e88457fad911

At the end of the quiz, we show the user his rank. This is what the next method is for. We count how many users have more points than the current one. We are only interested in the unique values because users with the same count will share a rank.

https://gist.github.com/da2ef12eda6c66f23b76b4a00a98e0e8

For the highscore, only the top 10 users will be shown.

https://gist.github.com/a9669de73c27c03a779a2a61f29a4aac

We fetch the users with the most points and add a rank field to every entry.

Show The Highscore

For displaying the highscore we create a new conversation class app/Conversations/HighscoreConversation.php.

https://gist.github.com/548d9a606e957a8af6824c5a17ba95f6

We use the topUsers data to build a little highscore table, as well as adding some other text replies. Also, emojis always help! 😉

To trigger this conversation, we need another listener. Besides the highscore keyword I also add /highscore which will become a Telegram command later.

https://gist.github.com/2d9b920aea5ef096b762378d65e9f798

Type one of the keywords to the bot to see what happens. It will tell you that the highscore is still empty.

Screenshot showing empty highscore

For testing purposes, we can manually add a few highscore entries to the table. I will use Sequel Pro to do that. Now we should get some highscore entries back. Notice the users with the same points will share a rank.

Screenshot showing the highscore

Ask User About Highscore

After a quiz, I want to ask the user, if he wants to get added to the highscore. This is a good idea because we need to store personal data like his chat id and name to do so. I don't want to save him without consent.

After showing the user his result, we ask him about the highscore. If he agrees, we will store him to the highscore table. We use the saveUser method we created earlier within the Highscore model. Additionally, we start the highscore conversation to show the other results. If the user does not want to get stored, we just reply with text messages.

https://gist.github.com/dcb4560f1c57d810a0e268519fc7d3b5

When you give the quiz another try, you will see the question about the highscore at the end.

Screenshot showing question about highscore question

Nice! We have already come a long way, and the bot looks excellent. We can play the quiz and the highscore works as well.

Extras

Welcome

To give the user a better experience, we are going to create an app/Conversations/WelcomeConversation.php class. It will take care of onboarding the user. First, we welcome him and tell some info about the purpose of this bot. Then we ask if he is ready for the quiz. If he is, we start the quiz conversation.

https://gist.github.com/802c42c6256b2dabf56cca10670cf6c8

To trigger this new conversation, we add another listener. The keyword /start is also what gets called when a Telegram user joins the bot for the first time.

https://gist.github.com/0dcf7a7218ac3ead03568ea63f49fddc

This time the listener gets extended through the stopsConversation method. It means that when this command is used within a conversation, it will get stopped to start a new quiz. I use this for all Telegram commands so that the user can use them at any time.

Screenshot showing start of the conversation

Delete User Data

Since the GDPR it is essential to give the user the possibility to delete his stored data. I want to handle that in a new app/Conversations/PrivacyConversation.php class. First we make sure we have stored this current user. If so, we ask him if he really wants to delete his highscore entry.

https://gist.github.com/3725a658fea4c74974fca95b35b71360

The deleteUser is a new Highscore model method you have to add.

https://gist.github.com/d5de377d2e2f7886255482c97e01c026

Note: Find more info about this topic in my article Make Your Chatbots GDPR Compliant.

About

If someone wants to know more about this bot, I will give him the possibility by adding some about info. Since I only return one little text message, I will handle it inside the listener.

https://gist.github.com/f55e5bad8a501e16a8ad422a0ad73bb1

Telegram Commands

This is an excellent time to add the Telegram commands to the bot. You need to use Telegram's BotFather to achieve that. Follow his instructions to edit the bot and add the commands. You always have to fill all the commands at once.

Screenshot showing how to setup Telegram commands

When you start typing / you will see all the commands. Now it is effortless for the user to trigger the main features.

Screenshot showing how to trigger Telegram commands

Typing Indicator

With chatbots, it is common to show that they are typing while processing the message. This feels more natural because we are used to these indicators when we talk to friends. In BotMan it is straightforward to use it inside conversations, but I want to add it to a middleware. This way I can activate them for all outgoing messages.

https://gist.github.com/354057eb7f5975e0da22da1b397e7548

This command will generate the class for use. Delete the dummy code and replace it with the following. We only need the sending method, which will be active when BotMan sends a message back to the messenger.

https://gist.github.com/c48b41fbdc508804c3d0bec742f3f02e

Also, the middleware must be added to the main BotMan instance in the BotMan route file.

https://gist.github.com/75ee82d041f05b4735d334857a0ddf02

Screenshot showing the typing indicator

Testing

For this article, I left out handling tests. Of course, this doesn't mean they are not important. But the focus was on different parts of this app. If you like to know more about how to test conversations, check my article Build a newsletter chatbot in PHP - Part 3.

Conclusion

And again, this article got much longer than expected. But I hope all the details can help you to build similar chatbots yourself. For me, especially the part about looping over stored questions was fascinating. This gives you a lot of new possibilities to build conversations dynamically.

I always love to put out real projects because they help people more than fictive ones. Another real chatbot project is included in my upcoming book Build Chatbots with PHP. Subscribe to the email list to get a free sample chapter and to receive more info about this new project of mine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment