There's a word game I find very addictive. I play it often, usually in transit and stuck in one terrible traffic from Opebi to Ogba.
The game is pretty straight forward. You're given a couple of letters and some empty word blocks. You are expected to construct some words with the given letters. A correctly formed word fills the empty block. If you fill all the blocks correctly, you get points and move to a new level.
We could replicate this game and its functionality with a WhatsApp bot.
Prerequisites:
Twilio Account
Composer
Laravel Installation
Good Knowledge of PHP
Good Knowledge of Laravel Framework
Ngrok
Twilio Account:
Twilio is a service that allows us to programmatically add communication services through its suite of APIs. We are going to use Twilio to make API calls between WhatsApp and our application, because getting direct access to WhatsApp's API involves a rigid approval process.
Visit Twilio on your browser by going to https://twilio.com (PS: Referral Link, I get sms credits if you sign up with my link)
After verifying your email, you need to also verify your phone number before you'll be granted access to the sandbox.
You'll be taken to a setup page were certain questions will be asked to customize your experience. You can answer as you please.
If you selected the "Send WhatsApp Messages" option on the last setup page. You'll be taken to the messaging dashboard.
Click on the "Sandbox" menu option on the sidebar to go to our sandbox
Congratulations, you've just set up your Twilio WhatsApp sandbox. We shall come back here to configure our call back url.
Laravel Installation:
Next, we create our Laravel project. I'm assuming you already have composer installed.
This will scaffold a new Laravel application for us into a folder called word-game.
We need to also install Twilio's sdk which we will use to return our responses. (We''ll see this in use later)
composer require twilio/sdk
Okay great. Now we're set up. Its time to jump into some coding action.
Let's define our callback url. Twilio will send our WhatsApp messages to this endpoint as a post request, then we can look at the message and determine what to send back to the user.
In our routes/api.php file, add this line:
Route::post('/word-game', 'WordGameController');
Here we are telling Laravel to invoke WordGameController any time the endpoint is matched. We need to create that controller, since it doesn't exist yet. You can spin up a controller by typing in the terminal:
Our Controller has only a single action, which is why we are using an invokable controller. Laravel will automatically call the invoke method. You can read more on invokable controllers in the Laravel documentation
Twilio provides us with a MessagingResponse class to help us handle our messages. It converts our message to Twilio's TwiML response format. We shall come back to this controller later.
This will create our models in an App\Models directory. PS: I prefer having my models in that directory structure but it's entirely optional and a personal preference.
Actors and Workflow:
Our game bot has a guided workflow. There's no fancy AI going on. No NLP to detect intents and actions. We are basically giving instructions to our gamers through keywords and expecting specified answers.
Actors:
We will make use of a handler class, which we'll call an Actor, to interact with any incoming message from a gamer. The Actor class will simply do two things. Take the incoming message, check if it should respond, and return a response to the gamer.
Actor Contract:
From our workflow, we know what an actor is suppose to do. We will create an interface to enforce this contract.
The static shouldTalk method will take in the gamer and the message sent as parameters, then return a boolean if the actor should talk or not.
The talk method will take an optional data parameter, perform some logic and return a response to the gamer.
Let's define a base class that will implement this contract, because why not? The father bears the burden of the children.
Our call method will allow us to pass an actor from within an actor, as we will see later.
Keywords:
Lets create the keywords our game will interact with. I shall put this in a constants class.
I'm keeping the keywords restricted to four letters. Just to make it concise.
The play keyword will begin the game. We will define actors that will respond to this keyword.
Conversations:
I'll add another constants class to hold conversation values.
Notice our SALUTE constant has a place holder (extra_greet _key). This placeholder will be replaced within our actor when responding to a gamer
Actor Factory:
The Actor Factory is responsible for generating the actor suitable for an incoming message.
This code is pretty explanatory, first we get the phone number and message from the request body in the static make method. Next we pass these values to the resolveGamer method, which just retrieves or persist the gamer from our data store. We then go ahead to pass the message to the resolveActor Method. This method gets all the actors (We'll create them soon), loops through them and calls the shouldTalk method already defined in the Actor contract. If the return value is true, the Actor Class is instantiated with the phone number and message as parameters.
SaluteActor:
Lets start with our first Actor. This Actor will greet the gamer on first interaction. Our Salute Actor Class will look like this:
In our shouldTalk method, we only want to greet the gamer, if there's no active game, while in our talk method, we are getting the placeholder values from our constants and replacing them with dynamic values.
PlayKeyword Actor:
This actor will act on the play keyword, if there is no current game.
In our talk method, we generate our questions from a question factory, store the question in the db and return the question to the gamer.
Game Actor:
The Game actor is responsible for handling the gamers responses to question.
It looks like a lot of things are going on here, but its quite straightforward.
In the shouldTalk method, we check if the gamer has a current game,
In the talk method, we check the correctness or wrongness of the response. If the gamer answered correctly, we will give points to the gamer, and return the remaining questions. If the gamer answered wrongly, we repeat the question to the gamer.
In the dropQuestion method, we can see that we made use of the call method, previously defined in our abstract Actor class to pass the action to the PlayKeyword actor.
WordGameController:
Lets put everything together by revisiting our WordGameController
Now we're ready to start playing our game. We'll use ngrok to quickly expose our application. Ngrok is a really cool open source tool that will allow us expose our local environment to the internet. It works by creating a secure tunnel on our local machine along with a public url. This is pretty useful in situations where you need to test a webhook, just like in our case. If you don't have ngrok installed, you can visit the ngrok download page for instructions on how to set up.
Run the following command to tunnel our app using ngrok.
ngrok http 8000
Copy the forwarding url and go back to the Twilio WhatsApp sandbox. we shall paste the url in the callback field.
Using the Game Bot:
Add the Twilio sandbox number +1 415 523 8886 to your phone contact list. Open whatsapp and join the sandbox by sending join empty-toward (My sandbox code would be different from yours)
Congratulations! We now have a functional WhatsApp bot.
Feel free to let me know what you think. I'm on twitter @gabrielnwogu