# Build a digital jukebox for WhatsApp using Spotify, Python, Flask and Twilio

Remember those old jukeboxes where people used to insert a coin for it to take a record out and play the selected song? In this tutorial, we will implement our own digital jukebox that will allow you or your friends to search and add music to a playlist using the [Twilio API for WhatsApp](https://www.twilio.com/whatsapp), [spotipy](https://github.com/spotipy-dev/spotipy) (a lightweight Python library for the [Spotify Web API](https://developer.spotify.com/documentation/web-api/)) and the [Flask framework](https://flask.palletsprojects.com/) for Python.

![](https://lh7-us.googleusercontent.com/ZvGX4GhPpEdv7ssEKJkiqUJbFJImM7Tnpf27RY07v_prg6mubgpyxBGokL7UTFIUkS2v_qa4y7e9XURaI8RzYer_SE0p-u_Z2i0PEWShraHGXBiSTY4nT2CXAxqm_Tp7cxdH3Y7zCYUvN49douadSQ align="center")

## Tutorial Requirements

For this tutorial, we will need the following:

* Python 3.6 or newer. You can download it from [this link](https://www.python.org/downloads/)
    
* [ngrok](https://ngrok.com/). We will use this handy utility to connect the Flask application running on your system to a public URL that Twilio can connect to. This is necessary for the development version of the application because your computer is likely behind a router or firewall, so it isn’t directly reachable on the Internet. If you don’t have ngrok installed, you can [download a copy for Windows, MacOS or Linux](https://ngrok.com/download)
    
* A smartphone with an active phone number and WhatsApp installed
    
* A Twilio account. If you are new to Twilio [create a free account](http://www.twilio.com/referral/ACCqc5) now. You can review the [features and limitations of a free Twilio account](https://www.twilio.com/docs/usage/tutorials/how-to-use-your-free-trial-account)
    

## Create a Python Virtual Environment

Following Python best practices, we are going to make a separate directory for our chatbot project, and inside it we are going to create a [virtual environment](https://docs.python.org/3/tutorial/venv.html). We then are going to install the Python packages that we need for our chatbot on it.

If you are using a Unix or Mac OS system, open a terminal and enter the following commands to do the tasks described above:

```bash
mkdir spotwilio-bot
cd spotwilio-bot
python3 -m venv spotwilio-bot
source spotwilio-bot-venv/bin/activate
pip install twilio flask requests spotipy python-dotenv
```

For those of you following the tutorial on Windows, enter the following commands in a command prompt window:

```plaintext
$ md spotwilio-bot
$ cd spotwilio-bot
$ python -m venv spotwilio-bot-venv
$ spotwilio-bot-venvScripts\activate
(spotwilio-bot-venv) $ pip install twilio flask requests spotipy python-dotenv
```

The last command uses `pip`, the Python package installer, to install the five packages that we are going to use in this project, which are:

* The [Flask](https://www.palletsprojects.com/p/flask/) framework, to create the web application that responds to incoming WhatsApp messages with it
    
* The [Twilio Python Helper library](https://www.twilio.com/docs/libraries/python), to work with the Twilio APIs
    
* The [Requests](https://requests.kennethreitz.org/en/master/) package, to access third party APIs
    
* The [Spotipy](https://spotipy.readthedocs.io/en/2.13.0/#) library for the Spotify Web API that provides for us the basic functionality that we will be needing for this tutorial
    
* The [Dotenv](https://pypi.org/project/python-dotenv/) package, to load environment variables from a `.env` file
    

For your reference, at the time this tutorial was released these were the versions of the above packages and their dependencies tested:

```plaintext
certifi==2020.6.20
chardet==3.0.4
click==7.1.2
python-dotenv==0.14.0
flask==1.1.2
idna==2.10
itsdangerous==1.1.0
jinja2==2.11.2
markupsafe==1.1.1
pyjwt==1.7.1
pytz==2020.1
requests==2.24.0
six==1.15.0
spotipy==2.13.0
twilio==6.44.1
urllib3==1.25.9
werkzeug==1.0.1
```

## Configure the Twilio WhatsApp Sandbox

Twilio provides a [WhatsApp sandbox](https://www.twilio.com/console/sms/whatsapp/learn) where you can easily develop and test your application. Once your application is complete you can request [production access for your Twilio phone number](https://www.twilio.com/whatsapp/request-access), which requires approval by WhatsApp.

Let’s connect your smartphone to the sandbox. From your [Twilio Console](https://www.twilio.com/console), select [Programmable Messaging](https://www.twilio.com/console/sms/dashboard), then click on “Try it Out”, and finally click on “[Try WhatsApp](https://www.twilio.com/console/sms/whatsapp/learn)”. The WhatsApp sandbox page will show you the sandbox number assigned to your account, and a join code.

![](https://lh7-us.googleusercontent.com/C_grpIZRx1Z145wzr7xQZ24L4OF03D23XlAx9aW58ChytFDS1FxlF5M400WYf8ai9VeaGKTVy5H9SkCJXNEW9bo475Egd7dDouiadY-KxCsZaduU0yzeLpGA5jvlyJS7jbwCN9XmjN_4Kn8OAHiXuA align="left")

To enable the WhatsApp sandbox for your smartphone, send a WhatsApp message with the given code to the number assigned to your account. The code is going to begin with the word **join**, followed by a randomly generated two-word phrase. Shortly after you send the message you should receive a reply from Twilio indicating that your mobile number is connected to the sandbox and can start sending and receiving messages.

Note that this step needs to be repeated for any additional phones you’d like to have connected to your sandbox.

## Getting Spotify ready for our application

Now we are going to be doing a series of tasks that need to be done before we start working with the Spotify API, like creating our playlist, authorizing our app and testing both the Spotify Web API directly and through the spotipy library.

### Create your playlist

For this tutorial we will be creating a private, non-collaborative playlist as the modifications to it will only be done with our own user. You can adapt this later on to your own requirements.

In case you are not familiar with this procedure, here is a very brief and clear video from Spotify that will get you there:

%[https://vimeo.com/122512074] 

Once you have added your first song by creating the playlist, it should look something like this:

![](https://lh7-us.googleusercontent.com/NfyqPSNa6Weu9QBZtQiaGaYpAQ0lgfpecu0ihyGdPR6BNup4c09CGnzeyFb3dTzRr2tStIhgFro4Z5JItagTxMR_gTNLJjIgQuBGk13vFYmhBniC5x_G8xNfqMRCxjgYiJWy44v4mW6nDE8g_S_Baw align="center")

### Authorize your application on the Spotify Web API

For this, you will have to go to the [Spotify Web API](https://developer.spotify.com/documentation/web-api/) site (where there is a lot of information if you’re interested), click on “Dashboard” on the upper navigation bar and login with your Spotify account.

![](https://lh7-us.googleusercontent.com/MvDFbS9hLkchYnVopXJkBnGOU-wWBO711iAa_Gcza86JvSfwzqsHS5pcZdw0ptQJ3vSaeF3oUC76j83cUe5rqpHgeN0rttuGB1xKdHFLcgw_U22fxUVQ2QxOexsoqbPlo5NJwuhDczG-d15nRwCWHA align="left")

![](https://lh7-us.googleusercontent.com/cov5-5TrdQH6ZyzlLS9pFZp0SdzstICm8hBdZEJIBpEVUHorG3E-JZn23-fvf0mKhUNa1L1RACmHpemJwNovBz_jYbwIg7yZOeOwHKN73pUAjJ8oSuD3RLmQNRmfJp8zXNWKeM_eZAczUwOwkGBHbw align="left")

Once you’re in the Dashboard, click on “Create an app” to begin.

![](https://lh7-us.googleusercontent.com/ukqsSr8Q09vh-QD2pZzQgS_27xofKDDY8kWrp0i7PZgo2wmpQDIi1whuNCPGBNRJVU5XqY8_f50HZQHBuO8ohLq83Mn2gUTe9AJeZpM2IGvDixjBA3-RuGn_8U24HcUM0PUMJLPLS92kC7JB4LRAdA align="left")

We will then provide the required information.

![](https://lh7-us.googleusercontent.com/yTtl0Zd2YVy2ofXgXfKqYHBauVVZmTjCTGb8jf9aq8a9w03E9hymZAhAmgOOQZEkibknFJnKG4vGrgCP8taquExVsAV0eD4X6SWN_57x1N5sofa03cvju2f9_LMx7XpaNIgdeP4Fa5yaaWXb3IbiZg align="center")

Click on “Edit Settings” and add your “Redirect URI” as *http://127.0.0.1:5000/callback*. This is the URL where we will be redirected to once the authentication is successful (or if it fails too) and it can be any URL as long as it is whitelisted in this section of our app.

![](https://lh7-us.googleusercontent.com/dnVcjymjmdIKqHxX44rQuVsnk7ad1AQpxmh6-V9-gQhHvnEYyfF3Z6WSTvTU1lChBCoHBjJLW_2m6ybA34PQjF-AH07XZ_LwaxzORfM4d2eCSVLmdA_HYI37GkinT4A-w9gDfDBv7v7DK6j90906nw align="left")

### Test the Spotify API

Now we are going to be doing a quick test on the Spotify API so you get familiar with how Spotify exposes its information.

Let’s navigate to the “Console” menu and click on “Search” in the submenu, and then select the only method available.

![](https://lh7-us.googleusercontent.com/RB8tyyfwP0vdZEvuFN-a-khqFD5BS_RgPWlCuO9-cYr4CUNNkVzBvH_XqVADHvpB2M3rLt-fZBuQ_4QAHEVn3dIc8iXyhfIboPRs8h7Sh9yptBI-GSAc0H2ZSkmNbQmNwlYCaxCuDO1zToprGVr3tg align="left")

![](https://lh7-us.googleusercontent.com/ZgTfFK2HRYmgr90CLfTqirG1aIdM6sG5FkTZdX7R18h_rS3K-eVr-il9sjLytdSC7vciUcWWdt2cFjW0-NKAQ4Nn398Qsm8LOHWbaZeuwrHtPSPqTq7A6ALx99fyH9DkqWPla-f5TPIiDt8ZoWuavA align="left")

Now fill in the “q” and “type” fields (for example: q: Muse, type: artist). Click on “Get token”. Note that a “Select scopes” window shows up, but don’t worry, we won’t select any as we are only doing a simple search here, so just continue with “Request token”.

![](https://lh7-us.googleusercontent.com/26NQ0SRKlD8YtBw2OPjkwepwIbDbUoey3o-ZJEgK0yPFFjF-3CO2Xc2JcPvRylOyKAWgKu8h1nIXrzKo4fvzmKv8QkzmIp-RMRA52MsR5_EWWBoW2Un7jNSTklWPj6c763t7gIkNTpMf3ubHhAz0_w align="center")

To execute the request click “Try it”. Once executed, if you scroll down, you will see the response message on the right black frame.

![](https://lh7-us.googleusercontent.com/gSfeWOc5VINIU9mUv2-X8I2sxbMxY4QVqiZZE9eippNdUrRvOGMJf_SkLhAOoGzHWXkPi_Rm8_D0mZ-sT6j-JRa55yyH5xOJwTIwW6MMUoB6tgYcBLjWvwDN7fib2_8dakWamStx3QM6LosVtcLnqA align="left")

Now take a minute to examine the response and understand how the information is structured. Try making a few more tests with different search queries and types, or even try different endpoints.

### Test the Spotify API through spotipy

Now, let’s go back to your terminal. We are going to try doing the exact same search procedure, but through the spotipy library with Python.

We will now create the following environment variables as the spotipy library uses these as a more secure option instead of having to type this sensitive information on your code and looks it up automatically. We just need to create the `.env` file in the project directory that will look like this:

```plaintext
SPOTIPY_CLIENT_USERNAME="<your_spotify_username>"
SPOTIPY_CLIENT_ID="<your_client_id>"
SPOTIPY_CLIENT_SECRET="<your_client_secret>"
SPOTIPY_REDIRECT_URI="http://127.0.0.1:5000/callback"
SPOTIFY_PLAYLIST_URI=”<your_playlist_uri>”
SPOTIFY_MARKET=”<your_country_code>”
```

This is where we can get the values for each variable:

1. **Username.** Go to [spotify.com](https://www.spotify.com/). On the upper right corner, click on “Profile” next to your avatar, and then “Account”
    

![](https://lh7-us.googleusercontent.com/SNVl3iBa1sfIk9TTmtO6izw_PXrRCKDPF4USCZgbpRUk01MOFd0EIn3-hAna5911uH4uGxV8rWtYCI8sFfq5Yiabn7TglOQzHhBWb3dk0T95aFFCsz3zi8ql4JOCUUcg-k7WmHPVSM6-lqydoNI8ug align="left")

There you will find your basic user information. We’re looking for the one next to “username”.

![](https://lh7-us.googleusercontent.com/WHX6bwdjuLb2ct4tHwgtw_sCOQWPPCJxITyAAVHMOw7ItCAkECDb-f4Q4wsut8zmzrduZDipTdEHT5lt11hDpP2c7uQ1GuWUKn7ngXl8BRXRKHCDFc5tnT7wMQKCnHIsmDfmgzyWBpPeihZj-Uk1bw align="left")

2. **Client Id and Client Secret.** For this, we need to go back to our [Spotify for Developers Dashboard](https://developer.spotify.com/dashboard/), click on “My Twilio Chatbot” and there you will find this information. You need to click on “Show Client Secret” for this information to show up.
    

![](https://lh7-us.googleusercontent.com/Er07gfmJ5OOknWueBiMqMZhvTcn7ImW33MDUPRmAPF3RO9K1AMA75oY5whqazU2Y_jjT2MXmlx2EOt3uxRofaru0F6GLsClgj5aNbBTqDc7ScELJt6q_V5Khzn6LF3OEy6UgPJ1CsIeA-974283nQQ align="left")

![](https://lh7-us.googleusercontent.com/M4IH1T5La0wI3aBLiVuIbeL-7nPyD2izIDopUY3CeEgF0QnpEKeIZl8q-7HusxJXUzAXbkFOpavFtP3iw3cWAomUNRBjgTPvSToOcLhSq4D9qBAbuWsZMQNYSdHmywlaple-6vXeB_GKG1o93Libyw align="left")

3. **Redirect URI.** This is the URL we previously configured when we first created our app (http://). You can get it from the “Edit Settings” button.
    
4. **Playlist URI.** This is the URI for the specific playlist we will be working with. For that you need to open your playlist and click on “(...)” and then select “Copy Playlist link”
    

![](https://lh7-us.googleusercontent.com/ywvqze5YG5d6j7a8NIOlfcb0Kqi86QT263SJ4LyoUlYnz3bkam9Var1B7_94EyJnSqd2ik6uQj2-jMxmwVOPayO63o8Q3__o0udhS8PIKGM9rEue-wTltXArLZzhlSLosH280y377WT6ZZIYLhlcNA align="center")

5. **Market.** This is a parameter on Spotify because there might be some restrictions to play music based on the country you live in. So for example, if you are located somewhere in the European Union, you won’t be able to play a song that is only available in a particular Asian country (if that’s a real case). It won’t really make any sense for you to add a song to the playlist that you won’t be able to play, therefore, we will add this value to our variables to make sure we will only get results from songs we can actually reproduce. You can grab the [ISO 3166-1 alpha 2 code](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) for your country and enter it here. For example, use “US” for the United States.
    

Now let’s start our Python console by typing `python` in your terminal and then type the following code. This short script will generate a cache file with our tokens, which will be used by our application for future requests. It is important to run these lines in the same directory where the rest of our code will be.

```python
>>> import os
>>> import spotipy
>>> from spotipy.oauth2 import SpotifyOAuth
>>> from dotenv import load_dotenv
>>> load_dotenv()
>>> spotify_user = os.environ.get("SPOTIPY_CLIENT_USERNAME")
>>> spotify_scope = "playlist-modify-private"
>>> oauth = SpotifyOAuth(username=spotify_user, scope=spotify_scope)
>>> user_token = oauth.get_access_token(as_dict=False)
```

As part of the user token generation (last line above), your browser will take you to a Spotify login page. Once you enter your credentials you will be taken to our redirect URL with a code added to it and this message on your browser:

![](https://lh7-us.googleusercontent.com/DEWoTDTE8HSyCdgxHZHwT2bvI3QOIZW0yFK55012pa-P1QZKa3jFupIF4NB_TtfB94LYXkbHE7GvOE2_KXBCoVHWm4EzdOEjPIBwFDnbhN283kcmqeUPYL4jtvTJr-iPK9TwZYNM_KZaCLxwQ_qkfw align="left")

You were probably already logged in to Spotify when testing their API so this window will likely open and close automatically, but still generating your token successfully.

This is a one time process that will only occur now. At the end of it, you will have a new file with a name `.cache-<your_spotify_username>` that contains your token information that will later be used to send the requests to Spotify and, when needed, the information to refresh your token (each generated token expires after an hour, but worry you must not!, all the process of verifying if a token is expired and obtaining a new token is handled by the methods on the spotipy library).

The cached token has a structure similar to this:

```json
{
    'access_token': '<your_access_token>',
    'token_type': 'Bearer',
    'expires_in': 3600,
    'scope': ‘playlist-modify-private’,
    'expires_at': 1597259838,
    'refresh_token': '<your_refresh_token>'
}
```

If you print your `user_token` variable after it was generated, you will see the string corresponding to the `access_token` from the cached token structure from above, which is the information we are going to be using next.

```python
>>> print(user_token)
```

Now type the following to create the Spotify client, passing the token as an argument:

```python
>>> spotify = spotipy.Spotify(auth=user_token)
```

And finally our search request:

```python
>>> api_results = spotify.search(q="Muse", type="artist")
```

When you print `api_results`, you will see something like this (which should be very similar to what we received on our direct test on the Spotify Console).

![](https://lh7-us.googleusercontent.com/UaaWm3NoPX3OuKax4CENPAWzzVd-pWgU7N1Chat7w-VXEBLQjX7Xb_VClYtk2irzOMIK_6-5Wi4ve6oaa5hwdo4E46aMVS56qqD9e7lRNgbpjd18OQcIziHczA7UJU6Gu838cm52P9sN5bI55Ipipg align="center")

We have now tested a basic search with the spotipy library on Python and we have already generated our access token for future requests.

## Create the Flask Chatbot Service

Let’s now get to the core of our application. First we will start ngrok, which as mentioned before is a utility to connect the local Flask application to a public URL, and configure our endpoint.

### Running ngrok and configure our Twilio endpoint

We need to open a terminal window and run `ngrok http 5000` to allocate a temporary public domain that redirects HTTP requests to our local port 5000 where our flask application will be running. On a Unix or Mac OS computer you may need to use `./ngrok http 5000` if you have the ngrok executable in your current directory. The output of ngrok should be something like this:

![](https://lh7-us.googleusercontent.com/hGjb6hCW_5W2FT28XLQ5P44iZ6DWFvYEcmZPWpDM-4ZSHGtBZa00Y9orKunLxvYgfeiZQ3Kh46O4iWKHFDSXimdKPsWl4hRmKMBoAykSO3lBYq4vpXQ-pAk8JXIk3KQaSMY5s9fjsS05AS854o7HXg align="left")

The “Forwarding” lines show the public URL that ngrok uses to redirect requests into our service. What we need to do now is tell Twilio to use this URL to send incoming message notifications.

Go to the [Twilio Console](https://www.twilio.com/console), click on Programmable Messaging &gt; Settings &gt; [WhatsApp Sandbox Settings](https://www.twilio.com/console/sms/whatsapp/sandbox). Copy the https:// URL from the ngrok output and then paste it on the “When a message comes in” field, with `/jukebox` appended at the end. The `/jukebox` path is where we will install the endpoint of our application.

![](https://lh7-us.googleusercontent.com/8D_bU_hRoWiNLeaUi4vb6x1dP4wbQ3PNPUIuMOOw8UmpHAMq3_6s_ZW6yuW-3ZrQIUzTzUFe3lNpS6d_fkUDXLdWiI5-T4FMelWvxTQyAIfRfaCaXreys8fI99wlYsCdpclte5znOK4Ztz2N-tB9WQ align="left")

Make sure the request method is set to `HTTP Post` and don’t forget to click the “Save” button at the bottom of the page to record these changes.

### Webhook

The [Twilio API for WhatsApp](https://www.twilio.com/whatsapp) uses a [webhook](https://sendgrid.com/blog/whats-webhook/) to notify an application when there is an incoming message. Our chatbot application needs to define an endpoint that is going to be configured as this webhook so that Twilio can communicate with it.

Here, we will define our webhook with Flask so let’s create a file `twilio_jukebox.py` with the following piece of code:

```python
from flask import Flask

app = Flask(__name__)

@app.route('/jukebox', methods=['GET'])
def jukebox():
    # webhook logic here with a response
    pass

if __name__ == "__main__":
    app.run(debug=True)
```

If you are not familiar with the Flask framework, its documentation has a [quick start](https://flask.palletsprojects.com/en/1.1.x/quickstart/) section that should bring you up to speed quickly. There is also a nice [tutorial](https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-i-hello-world) that can help you a lot.

As mentioned before, this application defines a `/jukebox` endpoint. Each time an incoming WhatsApp message from a user is received by Twilio, they will in turn invoke this endpoint. The body of the function `jukebox()` is going to analyze the message sent by the user and should provide the appropriate response.

The `if __name__ == “__main__”` line makes sure the application will only run when our file is executed as a main script (not imported as a module by some other script).

Now we will add the return of the function just to test the application:

```python
from flask import Flask

app = Flask(__name__)

@app.route('/jukebox', methods=['GET'])
def jukebox():
    return "Hello from Flask!"

if __name__ == "__main__":
    app.run(debug=True)
```

Leave the terminal session running **ngrok** and start a second terminal window. After you activate the virtual environment, run the code with `python twilio_jukebox.py`. You should see something like this in your terminal:

```bash
* Serving Flask app "main" (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: on
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 958-679-664
```

Now let’s take the **ngrok** application URL that we entered in the Twilio Console and paste it in a new tab on your browser. The request going to the ngrok URL should be redirected to the Flask server you just started, and you should get something like this in your terminal:

```bash
127.0.0.1 - - [11/Aug/2020 20:49:46] "GET /jukebox HTTP/1.1" 200 -
127.0.0.1 - - [11/Aug/2020 20:49:46] "GET /favicon.ico HTTP/1.1" 404 -
```

And this in your browser:

![](https://lh7-us.googleusercontent.com/3MPjr3w_ncLnuXSl_m6zveH4oS9u-gIx3WZCChphBPYdqwLUL4rc8MOsuTt7vA55kAA0tY3POABSLjdnJQMkZZUyuEnHbpaTttKcRhRlx5BvFVppuztpJp_DcB92RnH_o0GuEap8_HyqEAHAfNW79Q align="center")

Our application is up and running!

### Messages and Responses

One of the key things in our chatbot is to obtain the message entered by the user. This message comes in the payload of the `POST` request with a key of `’Body’`. We can access it through Flask’s `request` object.

```python
from flask import request

incoming_msg = request.values.get('Body', '')
```

The response that Twilio expects from the webhook needs to be given in [TwiML or Twilio Markup Language](https://www.twilio.com/docs/glossary/what-is-twilio-markup-language-twiml), which is an XML-based language. The Twilio helper library for Python comes with classes that make it easy to create this response without having to create XML directly. Below you can see how to create a response:

```python
from twilio.twiml.messaging_response import MessagingResponse

resp = MessagingResponse()
resp.message("This is the response text")
```

So if we put them together we can test a basic function that will respond with the same incoming message back to the user:

```python
from flask import Flask, request
from twilio.twiml.messaging_response import MessagingResponse

app = Flask(__name__)

@app.route('/jukebox', methods=['POST'])
def jukebox():
    incoming_msg = request.values.get('Body', '')
    resp = MessagingResponse()
    resp.message(incoming_msg)

    return str(resp)

if __name__ == "__main__":
    app.run(debug=True)
```

Note that the HTTP method in this example changed from **GET** to **POST**. This matches the configuration of the webhook in the Twilio Console, which was set to **POST** as well.

Now run your app one more time and send a message like “Hello there” to your WhatsApp bot.

You should then see a response with the same message:

![](https://lh7-us.googleusercontent.com/knF3FFcwD_wjOU0leDVwdXfKDdGGC8428RJCQ1JTtPSFTx-iGzMjcXqhVHCmMft77wXmpytSg-r1LMjFjz0T6iTLizIjw-ofwQNkb7ICd9JDnBezEzozQnm0thTdsyhx16VH97wL6HkmGTCl6Z0UFA align="center")

This is what happened: the Twilio API for WhatsApp sent the request containing your message to the URL configured in the Console. Ngrok then redirected the request to our computer, where our application is running locally, and executed our `jukebox` function defined within the `/jukebox` endpoint. This function receives the message that was typed on WhatsApp (\`incoming\_msg\` assignment) and creates a response message with that same text to be sent back to the user (\`resp\` assignment).

Your terminal will also display a message like this letting us know it received a *POST* request, on the */jukebox* endpoint and with an *HTTP 200* (ok) response:

```bash
127.0.0.1 - - [14/Aug/2020 02:07:50] "POST /jukebox HTTP/1.1" 200 -
```

### Flask sessions

A [session](https://flask.palletsprojects.com/en/1.1.x/quickstart/#sessions) is a flask object that allows you to store information related to one user from one request to the next in a dictionary. This is implemented on top of cookies, and with the help of cryptographic signatures to keep the information secure.

In order to use sessions, Flask needs to be given a secret key that is used to generate the signatures. The `secrets` module comes in very handy to generate this key. Let’s open a new terminal window, start a python console and type the following pair of lines:

```python
>>> import secrets
>>> secrets.token_hex(16)
'6061d134dbc8335199d6c2fde4fab240'
```

The returned string will be your secret key. Now we are going to open our `.env` file and add a line assigning this to an environment variable.

```plaintext
FLASK_APP_SECRET="<your_secret_key>"
```

The session is going to be a fundamental part of our logic since we will be simulating a conversation, and for that we need to remember certain things, like if a search cycle has already started, which results have already been shown to the user, etc. We will define a series of variables that will be used to store particular information related to the user on a particular search in our session. Before getting into the code itself, I would like to put you in context of what would be the purpose of each one of these variables:

* **results** - Will store a list structure with the results obtained from the Spotipy in the following order:
    
    * Index that we will use once the user sends the selection back to us
        
    * Song title
        
    * Album name
        
    * Artist Name
        
    * Spotify song uri to be used when adding the song to the playlist
        
* **search** - a flag that will indicate us if there is an ongoing search cycle
    
* **next\_result** - a flag to let us know if there are more results to be shown next and let the user know
    
* **offset** - the value that will let us know which “page” from the total results from Spotify we are currently in. So as the user asks for more results we need to know what’s the current page to know which results to show next
    
* **search\_str** - as long as we are in a search cycle, we need to know which search string the user sent in the beginning in order to keep searching if needed
    

Now let’s go back to our `twilio_jukebox.py` file and add the session import, as well as the variable assignment that will set the flask secret key for our session. Replace the beginning of the file with the following code:

```python
from flask import Flask, request, session  # session import
from twilio.twiml.messaging_response import MessagingResponse

app = Flask(__name__)

app.secret_key = os.environ.get("FLASK_APP_SECRET")  # Assign the generated key to flask’s secret key
```

### Chatbot logic

As a general overview, this chatbot will work depending on certain keywords and logic that will be in our main function:

* Any word will start the conversation with a greeting
    
* `search` will start the cycle and ask for a search string
    
* `stop-search` to stop the conversation and end the current cycle
    
* `more`. Every time search results are displayed, 8 items will be shown so this command will allow us to display 8 more available results. You should take note that WhatsApp only allows 1,600 characters on a message, so if your search results are bigger you should consider splitting it into two different messages, reduce the number of results to a lower number or even change the formatting showed here
    
* `search-again` will reset the current cycle and ask again for a search string to lookup
    
* Once the results are displayed, it will expect any number corresponding to those results (1, 2, 3… n) to indicate which song will be added to the playlist
    

With that, let’s go back to our main file up to the point where we left off and let’s complement it section by section with all the conditions that will handle these keywords.

First we will import the rest of the modules we are going to be using, then delete the previous content of the main `jukebox` function that echoed our message from WhatsApp, and add this new content inside it.

```python
from flask import Flask, request, session
from twilio.twiml.messaging_response import MessagingResponse
import os
from spotipy.oauth2 import SpotifyOAuth
import spotipy
from dotenv import load_dotenv
load_dotenv()

app = Flask(__name__)

app.secret_key = os.environ.get('FLASK_APP_SECRET')


@app.route("/jukebox", methods=['POST'])
def jukebox():
    """
    Chatbot's main logic
    """
    valid_response = False
    resp_messages = []
    
    if not valid_response:
        resp_message = create_help_message("help")
        resp_messages.append(resp_message)
        resp = send_message(resp_messages)

    print(session)

    return str(resp)

if __name__ == "__main__":
    app.run(debug=True)
```

At this point we are only receiving any text for the first time so we will generate and send a help message back to go on with the conversation and let the user know what’s next.

Right after the main function definition, we will add two more:

```python
def send_message(messages):
    """
    Receives a list and sends the TwiML response message(s)
    """
    resp = MessagingResponse()
    
    for message in messages:
        resp.message(message)

    return resp
```

The `send_message` function receives a list of strings and creates a response with them. We use a list because there might be times when we will need to send more than one message back to the user.

```python
def create_help_message(message):
    """
    List of response messages depending on what needs to be sent back
    to the user.
    """
    if message == "ask_search":
        return "Ok, which song / album / artist are you looking for? " \
            "Try being as specific as possible. E.g.: Bad Michael Jackson"
    elif message == "all_results":
        return "These are all the results. Please type the " \
            "number of the song you would like to add to the playlist " \
            "(example: 9) or type search-again to start a new search. " \
            "You can type stop-search to end at any time"
    elif message == "more_results":
        return "Please type the number of the song you would like to add to " \
            "the playlist (Example: 9). If you would like to see more " \
            "results, type more or search-again to start a new search. " \
            "You can type stop-search to end at any time"
    elif message == "no_results":
        return "No results found. Please try again. You can type " \
            "*stop-search* to end at any time"
    elif message == "more_with_no_results":
        return "No more results are available. Please type the number of " \
            "the song you would like to add to the playlist (Example: 9) " \
            "or type search-again to start a new search. You can type " \
            "*stop-search* to end at any time"
    elif message == "song_selected":
        return "The selected song is already on the playlist. Please select " \
            "a different one or type search-again to start a new search"
    elif message == "help":
        return "Hello! I can help you search and add music to our Spotify " \
            "playlist. Let's type search to begin!"
```

The `create_help_message` function contains the definition of all the messages that are going to be sent back to the user depending on the time and situation.

Let’s save the file, run your application and say “Hello” to your bot.

![](https://lh7-us.googleusercontent.com/91_VYRFOo4FWaNx_5HpWBBH80iffewQ0kXdrg-i6cW_tCKAVapWfSI4f15eD21M8T1PfwsqMTJT0amFFZlGfdqwJANu4htSNL5AaeD8BYavryVFtburlymrwbzob9mN__H5JoERsF7R8gjpWg3RGLw align="center")

We now started our conversation. Notice that your session has been printed out on your terminal.

```plaintext
<SecureCookieSession {}>
127.0.0.1 - - [25/Aug/2020 20:09:31] "POST /jukebox HTTP/1.1" 200 -
```

It is currently empty, but let’s keep an eye on it as we complete the application.

Our next step is to add the condition that reads the `search` and `stop-search` keywords in the `jukebox()` function, so let’s go to that. Right after the `resp_messages` assignment, add the following:

```python
@app.route("/jukebox", methods=['POST'])
def jukebox():
    """
    Chatbot's main logic
    """
    valid_response = False
    resp_messages = []
    incoming_msg = request.values.get('Body', '').lower()

    if (incoming_msg == 'search' and 'search' not in session) \
            or incoming_msg == 'search-again':
        resp_message = create_help_message("ask_search")
        resp_messages.append(resp_message)
        session['results'] = []
        session['search'] = True
        session['offset'] = 0
        session['next_result'] = True
        valid_response = True
        resp = send_message(resp_messages)
    elif incoming_msg == 'stop-search':
        session.clear()
        help_message = "Got it. Bye!"
        resp_messages.append(help_message)
        resp = send_message(resp_messages)
        valid_response = True

    if not valid_response:
        resp_message = create_help_message("help")
        resp_messages.append(resp_message)
        resp = send_message(resp_messages)

    print(session)

    return str(resp)
```

We added the `incoming_msg` assignment (because now we need to know what’s the actual message from the user) with a `.lower()` function so we don’t have to worry about different ways the user could send each keyword and then our condition that will generate a new message and initialize most of our session variables mentioned earlier.

Save and if you didn’t shut down your app, just make sure it did not get stuck in any errors. If everything’s okay, let’s send the new keyword (`search`) to our bot.

![](https://lh7-us.googleusercontent.com/2BgQ-Y3uBFe8wYyXzqU8MdkkAk9oAW5jruNc1o-H9D8vkgG0N1pY_37wmR5R1Q7cbM7eALBarcN4t92nJTBfFs5uEUbGS8ikGIgRYqzOQVm4Scur2UiLIoEhuD_7YvHry0y_pq9r6sb7RtZBgyDg3w align="center")

Great! Notice how your session object is now showing more information with the variables we just initialized.

```plaintext
<SecureCookieSession {'results': [], 'search': True, 'offset': 0, 'next_result': True}>
127.0.0.1 - - [25/Aug/2020 20:42:50] "POST /jukebox HTTP/1.1" 200 -
```

Let’s go on with our next section. Now we need our bot to handle the search string we send to it so that it can look for the information on Spotify:

```python
@app.route("/jukebox", methods=['POST'])
def jukebox():
    """
    Chatbot's main logic
    """
    valid_response = False
    resp_messages = []

    incoming_msg = request.values.get('Body', '').lower()

    if (incoming_msg == 'search' and 'search' not in session) \
            or incoming_msg == 'search-again':
        resp_message = create_help_message("ask_search")
        resp_messages.append(resp_message)
        session['results'] = []
        session['search'] = True
        session['offset'] = 0
        session['next_result'] = True
        valid_response = True
        resp = send_message(resp_messages)
    elif 'search' in session \
            and not incoming_msg.isnumeric() \
            and (
                incoming_msg != 'search-again'
                and incoming_msg != 'stop-search'
            ):
        results, next = search(incoming_msg, 0, [])
        if results == []:
            help_message = create_help_message("no_results")
        else:
            session['results'] = results
            resp_message = respond_results(results, session['offset'])
            resp_messages.append(resp_message)
            session['offset'] += 8
            session['search_str'] = incoming_msg
            if next is None:
                help_message = create_help_message("all_results")
                session['next_result'] = False
            else:
                help_message = create_help_message("more_results")
                session['next_result'] = True
        resp_messages.append(help_message)
        resp = send_message(resp_messages)
        valid_response = True
    elif incoming_msg == 'stop-search':
        session.clear()}
        help_message = "Got it. Bye!"
        resp_messages.append(help_message)
        resp = send_message(resp_messages)
        valid_response = True
    if not valid_response:
        resp_message = create_help_message("help")
        resp_messages.append(resp_message)
        resp = send_message(resp_messages)

    print(session)

    return str(resp)
```

This addition will handle the new keyword, initializing the collection where the results from Spotify will be stored and assign a couple of variables from our session. It will also determine which message needs to be sent back to the user. For this part to work, we need to add four more functions below `create_help_message()`.

```python
def get_user_token():
    """
    Obtain an access token from Spotify
    """
    oauth = SpotifyOAuth(
        username=os.environ.get("SPOTIPY_CLIENT_USERNAME"),
        scope='playlist-modify-private'
    )
    user_token = oauth.get_access_token(as_dict=False, check_cache=True)

    return user_token
```

This function uses the **spotipy** library to generate or obtain the access token just the way we did manually before.

```python
def create_spotify_client():
    """
    Creates Spotify API client
    """
    user_token = get_user_token()
    spotify = spotipy.Spotify(auth=user_token)

    return spotify
```

This is the base of all interactions with the Spotify API.

```python
def search(search_str, offset_val=0, results=[]):
    '''
    Returns a list of coincidences either on songs, albums or artists as well
    as if there are any more results next
    '''
    spotify = create_spotify_client()
    api_results = spotify.search(
        q=search_str,
        limit=8,
        market=os.environ.get("SPOTIFY_MARKET"),
        offset=offset_val
    )
    items = api_results['tracks']['items']
    next = api_results['tracks']['next']
    if len(items) > 0:
        for id, item in enumerate(items):
            song = item['name']
            album = item['album']['name']
            artist = item['artists'][0]['name']
            uri = item['uri']
            new_item = [id+1+int(offset_val), song, album, artist, uri]
            results.append(new_item)

    return results, next
```

The `search` function will get the list of tracks with the information sent as a query (in case there is a match), as well as a value for `next` from Spotify which is the request URL for the next set of results or `None` if there are no more results. Notice how the `market` variable we talked about earlier is being used now.

```python
def respond_results(results, offset_val):
    """
    Receives the search result and formats it to text for response
    """
    text_results = ''

    for i in range(offset_val, len(results)):
        text_results += "{}.  🎵 {} \n     💿 {} \n     👤 {} \n" \
            "---------------------------------\n".format(
                results[i][0], results[i][1], results[i][2], results[i][3])

    return text_results
```

Once the results are obtained, this function will process and format them to be ready to display for the user. Notice that the `search` function limits the number of results to “8”. This is the number I chose to avoid exceeding the maximum number of characters allowed by WhatsApp in one message. You could change the formatting on `respond_results` to a more plain message and be able to show 10 or 15 results per message, for example.

It’s time to test our app again. Save it and let’s send our search string. I’ll try “Sultans of Swing Dire Straits”.

![](https://lh7-us.googleusercontent.com/vgmhLywfZFqs7rnVFOglp7bUSKVb4_l6H4Uhp9GQcBhkS5ZvvOPcyevoxlKPUTnZfH_cqtAlpWh44ahwDbRVfWtv-2i1nwyZGFt1krVlAkgUo_uu-UTimnCOIwlZYC8rsfV6_KfZ3k-6oRdFm5D6NA align="center")

![](https://lh7-us.googleusercontent.com/mR7m96jqCjupdDNACqCeh7sR_jr6COpbUKvm1VG6F6QmMOlcKtpgzndG2vVjLSocpPCkKAwd5xNHSfHldkDuEjgkXB5nWrR40fmuMC49ABTDw2bpBMxb1Q3-caw2uT5QXC9igXDV3zTViD55SZyuMQ align="center")

A list of results with the specific format on `respond_results` function will show up, along with a new instruction message. Notice one more time that our Session object has a lot of information now. This includes our `results` list with the structure we mentioned earlier, that is because we can’t anticipate what the user will send next, so we will need to know which songs have been shown, their index and URI.

```bash
<SecureCookieSession {'next_result': True, 'offset': 8, 'results': [[1, 'Sultans Of Swing', 'Dire Straits', 'Dire Straits', 'spotify:track:37Tmv4NnfQeb0ZgUC4fOJj'], [2, 'Sultans Of Swing - Live At Hammersmith Odeon, London/1983', 'Alchemy: Dire Straits Live', 'Dire Straits', 'spotify:track:3s9QsxxTlcWcga9agSARTV'], [3, 'Sultans Of Swing - Live At The BBC', 'Live At The BBC', 'Dire Straits', 'spotify:track:4yP4oR6G7l9MeHyDmQRRP1'], [4, 'Sultans Of Swing', 'Sultans Of Swing - The Very Best Of Dire Straits', 'Dire Straits', 'spotify:track:6pKDYgGvwfalcsx1tbYfn7'], [5, 'Sultans Of Swing', 'Live In Concert', 'Dire Straits', 'spotify:track:3TiQGEP0UYsT5RpXRo2q0W'], [6, 'Sultans of Swing - Live at Live Aid, Wembley Stadium, 13th July 1985', 'Live Aid (Live, 13th July 1985)', 'Dire Straits', 'spotify:track:1n8WWItMvZ4bASlBKbbeCY'], [7, 'Sultans Of Swing', 'Sultans Of Swing - The Very Best Of Dire Straits', 'Dire Straits', 'spotify:track:7E6jbnH4zCTxJ6ok8gcxuj'], [8, 'Sultans Of Swing', 'Sultans Of Swing / Eastbound Train', 'Dire Straits', 'spotify:track:7eqHsDc6PND6reFaF9ba4b']], 'search': True, 'search_str': 'sultans of swing dire straits'}>
```

Now let’s say we didn’t find the exact version we are looking for (which I don’t think will be very likely) and that we need to show more results. We will now add the piece of code that handles that:

```python
@app.route("/jukebox", methods=['POST'])
def jukebox():
    """
    Chatbot's main logic
    """
    valid_response = False
    resp_messages = []
    incoming_msg = request.values.get('Body', '').lower()
    if (incoming_msg == 'search' and 'search' not in session) \
            or incoming_msg == 'search-again':
        resp_message = create_help_message("ask_search")
        resp_messages.append(resp_message)
        session['results'] = []
        session['search'] = True
        session['offset'] = 0
        session['next_result'] = True
        valid_response = True
        resp = send_message(resp_messages)
    elif incoming_msg == 'more' \
            and session['search'] \
            and len(session['results']) > 0 \
            and session['next_result']:
        results, next = search(
            session['search_str'], session['offset'], session['results'])
        resp_message = respond_results(results, session['offset'])
        resp_messages.append(resp_message)
        session['offset'] += 8
        valid_response = True
        if next is None:
            help_message = create_help_message("all_results")
            session['next_result'] = False
        else:
            help_message = create_help_message("more_results")
            session['next_result'] = True
        resp_messages.append(help_message)
        resp = send_message(resp_messages)

    elif incoming_msg == 'more' and not session['next_result']:
        help_message = create_help_message("more_with_no_results")
        resp_messages.append(help_message)
        resp = send_message(resp_messages)
        valid_response = True
    elif 'search' in session \
            and not incoming_msg.isnumeric() \
            and (
                incoming_msg != 'search-again'
                and incoming_msg != 'stop-search'
            ):
        results, next = search(incoming_msg, 0, [])
        if results == []:
            help_message = create_help_message("no_results")
        else:
            session['results'] = results
            resp_message = respond_results(results, session['offset'])
            resp_messages.append(resp_message)
            session['offset'] += 8
            session['search_str'] = incoming_msg
            if next is None:
                help_message = create_help_message("all_results")
                session['next_result'] = False
            else:
                help_message = create_help_message("more_results")
                session['next_result'] = True
        resp_messages.append(help_message)
        resp = send_message(resp_messages)
        valid_response = True
    elif incoming_msg == 'stop-search':
        session.clear()
        help_message = "Got it. Bye!"
        resp_messages.append(help_message)
        resp = send_message(resp_messages)
        valid_response = True
    if not valid_response:
        resp_message = create_help_message("help")
        resp_messages.append(resp_message)
        resp = send_message(resp_messages)

    print(session)

    return str(resp)
```

This will bring the next set of 8 results and validate whether there are more to display or not to determine what to respond next, so let’s save and try it one more time, but this time sending `more` to our bot.

![](https://lh7-us.googleusercontent.com/XheAkoTXCX3nfk-9gNiUd4hXlrMS2MN2DOpOzVv7m1WECUJmxBbweLe0AWRq_HNbgTejZ2xFtKrWIAiGVV5grbt5-NwLOFxrA0IMCWaQ7wkCbGi4cYT8hpJ6NOpuEbHviKmlZ2V4vRT_L7a1TPaygA align="left")

![](https://lh7-us.googleusercontent.com/DGJXqnXjuYpjhn_y3RSI0kpL7VzpUThBJoM3y7-W7xajptAQErSSRr11Rb7_YnDGevrlru7k6N9kBG-xjhFFg1Dcy8J_iFtQyV-ETR5nPRbuY1wvXKYbhw9X7iKUTc4HNadfYkSas7reJOFpOZ3vrA align="left")

So I just noticed that the first set of results had what I was looking for. We will now let our bot know we want to add our song but first we need to add the last conditions to our main function: The one that handles the number of the song we want to add. We will also need our last two functions.

```python
@app.route("/jukebox", methods=['POST'])
def jukebox():
    """
    Chatbot's main logic
    """
    valid_response = False
    resp_messages = []

    incoming_msg = request.values.get('Body', '').lower()

    if (incoming_msg == 'search' and 'search' not in session) \
            or incoming_msg == 'search-again':
        resp_message = create_help_message("ask_search")
        resp_messages.append(resp_message)
        session['results'] = []
        session['search'] = True
        session['offset'] = 0
        session['next_result'] = True
        valid_response = True
        resp = send_message(resp_messages)

    elif incoming_msg == 'more' \
            and session['search'] \
            and len(session['results']) > 0 \
            and session['next_result']:
        results, next = search(
            session['search_str'], session['offset'], session['results'])
        resp_message = respond_results(results, session['offset'])
        resp_messages.append(resp_message)
        session['offset'] += 8
        valid_response = True
        if next is None:
            help_message = create_help_message("all_results")
            session['next_result'] = False
        else:
            help_message = create_help_message("more_results")
            session['next_result'] = True
        resp_messages.append(help_message)
        resp = send_message(resp_messages)

    elif incoming_msg == 'more' and not session['next_result']:
        help_message = create_help_message("more_with_no_results")
        resp_messages.append(help_message)
        resp = send_message(resp_messages)
        valid_response = True

    elif 'search' in session \
            and not incoming_msg.isnumeric() \
            and (
                incoming_msg != 'search-again'
                and incoming_msg != 'stop-search'
            ):
        results, next = search(incoming_msg, 0, [])
        if results == []:
            help_message = create_help_message("no_results")
        else:
            session['results'] = results
            resp_message = respond_results(results, session['offset'])
            resp_messages.append(resp_message)
            session['offset'] += 8
            session['search_str'] = incoming_msg
            if next is None:
                help_message = create_help_message("all_results")
                session['next_result'] = False
            else:
                help_message = create_help_message("more_results")
                session['next_result'] = True
        resp_messages.append(help_message)
        resp = send_message(resp_messages)
        valid_response = True

    elif incoming_msg.isnumeric() and int(incoming_msg) in [
            result[0] for result in session['results']]:
        # Valid response with an integer that corresponds to a displayed song
        current_songs_in_playlist = get_playlist_songs()
        selected_song_uri = session['results'][int(incoming_msg)-1][4]
        selected_song_name = session['results'][int(incoming_msg)-1][1]

        if selected_song_uri in current_songs_in_playlist:
            help_message = create_help_message("song_selected")
        else:
            add_song_to_playlist(selected_song_uri)
            help_message = "Great! The song _{}_ has been added to the " \
                "playlist. Have fun!".format(selected_song_name)
            session.clear()
        resp_messages.append(help_message)
        resp = send_message(resp_messages)
        valid_response = True

    elif incoming_msg == 'stop-search':
        session.clear()
        help_message = "Got it. Bye!"
        resp_messages.append(help_message)
        resp = send_message(resp_messages)
        valid_response = True

    if not valid_response:
        resp_message = create_help_message("help")
        resp_messages.append(resp_message)
        resp = send_message(resp_messages)

    print(session)

    return str(resp)
```

Here, when we send the valid id of an existing song, the bot will get a list of all the song URIs that are currently on the playlist (\`get\_playlist\_songs()\`), then the id of the selected song will be looked up on our `results` list, get the song URI and verify if the song already exists on the playlist. If it does, a message will be returned, otherwise, the song will be added (with `add_song_to_playlist()`), returning a confirmation message. We can send `stop-search` to stop the conversion too, but let’s now add our mentioned functions right below `respond_results()`:

```python
def get_playlist_songs():
    """
    Retrieve the list uris from the songs in the playlist
    """
    spotify = create_spotify_client()
    results = spotify.user_playlist(
        user=os.environ.get("SPOTIPY_CLIENT_USERNAME"),
        playlist_id=os.environ.get("SPOTIFY_PLAYLIST_URI")
    )
    items = results['tracks']['items']

    return [item['track']['uri'] for item in items if len(items) > 0]


def add_song_to_playlist(uri):
    """
    Will add the selected song to the playlist by song uri
    """
    spotify = create_spotify_client()

    spotify.user_playlist_add_tracks(
        user=os.environ.get("SPOTIPY_CLIENT_USERNAME"),
        playlist_id=os.environ.get("SPOTIFY_PLAYLIST_URI"),
        tracks=[uri])

    return spotify
```

We are now using the `SPOTIFY_PLAYLIST_URI` variable created before.

Save your file and send the last message to our bot:

![](https://lh7-us.googleusercontent.com/U9GhJ_KzVOgUz-f6F8aU5JFCDYgO7zgGXCTUvinnp2sA9KWXi3FFX96XKC5Jen--knOB8Yrbjy1z4gVijlvtgor6jwuw_UlMpEjryuXPsUCXUDhKmBts25L6fXElpEZsddvYAisoIC8-M2viqxHgoQ align="left")

You should now be able to see the confirmation message. Let’s open our playlist on Spotify to confirm:

![](https://lh7-us.googleusercontent.com/aEVjcP6FRwop6mZcHgvFxyAczpjmBuj9q-Y16-a61BazLNp2SK3a4bcbrVbfNF4TXjZ0DgJ5c4RvvFY0zafItMClt95J4DoPoyte0dZ9WMyAuOGZZTu5rqkqlnTnlQP-A4oxTedyQhjePLd36slcmg align="left")

Awesome! And see how our Session object should be now back to the way it started.

```bash
<SecureCookieSession {}>
```

### Everything together

Our `twilio_jukebox.py` file should now look like this:

```python
from twilio.twiml.messaging_response import MessagingResponse
from flask import Flask, request, session
import os
from spotipy.oauth2 import SpotifyOAuth
import spotipy
from dotenv import load_dotenv
load_dotenv()

app = Flask(__name__)

app.secret_key = os.environ.get("FLASK_APP_SECRET")


@app.route("/jukebox", methods=['POST'])
def jukebox():
    """
    Chatbot's main logic
    """
    valid_response = False
    resp_messages = []

    incoming_msg = request.values.get('Body', '').lower()

    if (incoming_msg == 'search' and 'search' not in session) \
            or incoming_msg == 'search-again':
        resp_message = create_help_message("ask_search")
        resp_messages.append(resp_message)
        session['results'] = []
        session['search'] = True
        session['offset'] = 0
        session['next_result'] = True
        valid_response = True
        resp = send_message(resp_messages)

    elif incoming_msg == 'more' \
            and session['search'] \
            and len(session['results']) > 0 \
            and session['next_result']:
        results, next = search(
            session['search_str'], session['offset'], session['results'])
        resp_message = respond_results(results, session['offset'])
        resp_messages.append(resp_message)
        session['offset'] += 8
        valid_response = True
        if next is None:
            help_message = create_help_message("all_results")
            session['next_result'] = False
        else:
            help_message = create_help_message("more_results")
            session['next_result'] = True
        resp_messages.append(help_message)
        resp = send_message(resp_messages)

    elif incoming_msg == 'more' and not session['next_result']:
        help_message = create_help_message("more_with_no_results")
        resp_messages.append(help_message)
        resp = send_message(resp_messages)
        valid_response = True

    elif 'search' in session \
            and not incoming_msg.isnumeric() \
            and (
                incoming_msg != 'search-again'
                and incoming_msg != 'stop-search'
            ):
        results, next = search(incoming_msg, 0, [])
        if results == []:
            help_message = create_help_message("no_results")
        else:
            session['results'] = results
            resp_message = respond_results(results, session['offset'])
            resp_messages.append(resp_message)
            session['offset'] += 8
            session['search_str'] = incoming_msg
            if next is None:
                help_message = create_help_message("all_results")
                session['next_result'] = False
            else:
                help_message = create_help_message("more_results")
                session['next_result'] = True
        resp_messages.append(help_message)
        resp = send_message(resp_messages)
        valid_response = True

    elif incoming_msg.isnumeric() and int(incoming_msg) in [
            result[0] for result in session['results']]:
        # Valid response with an integer that corresponds to a displayed song
        current_songs_in_playlist = get_playlist_songs()
        selected_song_uri = session['results'][int(incoming_msg)-1][4]
        selected_song_name = session['results'][int(incoming_msg)-1][1]

        if selected_song_uri in current_songs_in_playlist:
            help_message = create_help_message("song_selected")
        else:
            add_song_to_playlist(selected_song_uri)
            help_message = "Great! The song _{}_ has been added to the " \
                "playlist. Thank you and have fun!".format(selected_song_name)
            session.clear()
        resp_messages.append(help_message)
        resp = send_message(resp_messages)
        valid_response = True

    elif incoming_msg == 'stop-search':
        session.clear()
        help_message = "Got it. Bye!"
        resp_messages.append(help_message)
        resp = send_message(resp_messages)
        valid_response = True

    if not valid_response:
        resp_message = create_help_message("help")
        resp_messages.append(resp_message)
        resp = send_message(resp_messages)

    print(session)

    return str(resp)


def send_message(messages):
    """
    Receives a list and sends the TwiML response message(s)
    """
    resp = MessagingResponse()

    for message in messages:
        resp.message(message)

    return resp


def create_help_message(message):
    """
    List of response messages depending on what needs to be sent back
    to the user.
    """
    if message == "ask_search":
        return "Ok, which song / album / artist are you looking for? " \
            "Try being as specific as possible. E.g.: _Bad Michael Jackson_"
    elif message == "all_results":
        return "These are all the results. Please type the " \
            "number of the song you would like to add to the playlist " \
            "(example: *9*) or type *search-again* to start a new search. " \
            "You can type *stop-search* to end at any time"
    elif message == "more_results":
        return "Please type the number of the song you would like to add to " \
            "the playlist (Example: *9*). If you would like to see more " \
            "results, type *more* or *search-again* to start a new search. " \
            "You can type *stop-search* to end at any time"
    elif message == "no_results":
        return "No results found. Please try again. You can type " \
            "*stop-search* to end at any time"
    elif message == "more_with_no_results":
        return "No more results are available. Please type the number of " \
            "the song you would like to add to the playlist (Example: *9*) " \
            "or type *search-again* to start a new search. You can type " \
            "*stop-search* to end at any time"
    elif message == "song_selected":
        return "The selected song is already on the playlist. Please select " \
            "a different one or type *search-again* to start a new search"
    elif message == "help":
        return "Hello! I can help you search and add music to our Spotify " \
            "playlist. Let's type *search* to begin!"


def get_user_token():
    """
    Obtain an access token from Spotify
    """
    oauth = SpotifyOAuth(
        username=os.environ.get("SPOTIPY_CLIENT_USERNAME"),
        scope='playlist-modify-private'
    )
    user_token = oauth.get_access_token(as_dict=False, check_cache=True)

    return user_token


def create_spotify_client():
    """
    Creates Spotify API client
    """
    user_token = get_user_token()
    spotify = spotipy.Spotify(auth=user_token)

    return spotify


def search(search_str, offset_val=0, results=[]):
    """
    Returns a list of coincidences either on songs, albums or artists as well
    as if there are more results to show next
    """
    spotify = create_spotify_client()
    api_results = spotify.search(
        q=search_str,
        limit=8,
        market=os.environ.get("SPOTIFY_MARKET"),
        offset=offset_val
    )
    items = api_results['tracks']['items']
    next = api_results['tracks']['next']

    if len(items) > 0:
        for id, item in enumerate(items):
            song = item['name']
            album = item['album']['name']
            artist = item['artists'][0]['name']
            uri = item['uri']
            new_item = [id+1+int(offset_val), song, album, artist, uri]
            results.append(new_item)

    return results, next


def respond_results(results, offset_val):
    """
    Receives the search result and formats it to text for response
    """
    text_results = ''

    for i in range(offset_val, len(results)):
        text_results += "{}.  🎵 {} \n     💿 {} \n     👤 {} \n" \
            "---------------------------------\n".format(
                results[i][0], results[i][1], results[i][2], results[i][3])

    return text_results


def get_playlist_songs():
    """
    Retrieve the list uris from the songs in the playlist
    """
    spotify = create_spotify_client()
    results = spotify.user_playlist(
        user=os.environ.get("SPOTIPY_CLIENT_USERNAME"),
        playlist_id=os.environ.get("SPOTIFY_PLAYLIST_URI")
    )
    items = results['tracks']['items']

    return [item['track']['uri'] for item in items if len(items) > 0]


def add_song_to_playlist(uri):
    """
    Will add the selected song to the playlist by song uri
    """
    spotify = create_spotify_client()

    spotify.user_playlist_add_tracks(
        user=os.environ.get("SPOTIPY_CLIENT_USERNAME"),
        playlist_id=os.environ.get("SPOTIFY_PLAYLIST_URI"),
        tracks=[uri])

    return spotify


if __name__ == "__main__":
    app.run(debug=True)
```

### Running and testing the application

Now it’s time to run our application again with `python3 twilio_jukebox.py`. Write anything to your WhatsApp chatbot to start the conversation. Write `search`, follow the instructions and add a few songs to your playlist!

![](https://lh7-us.googleusercontent.com/TqKTA7qSjQor-ZWfCW3gZ_N9020Aq5lSBpsKk7NPGsKrQmeqrTkzUzQEawzbBgBsgBjr-gqZbRI9hLTZyzsQixyBqyBCKzAVsu8cwM99ZyUbvTxMRmLydtRBSU1sgfB8GKtBkEjNp-J2dPi8RBQf1Q align="left")

## Conclusion

We have just created a digital jukebox using a WhatsApp bot with Python and Twilio that can be used as those classic jukeboxes with a little modern touch, where people can add their favorite music to a playlist.

The repository on GitHub can be found [here](https://github.com/migrmrz/spotwilio-jukebox), where I’m working on a few improvements like providing a song “fun fact” at the end that is taken from [Genius](https://genius.com/) through its [API](https://docs.genius.com/). There might be some other things that can be added like time frame restrictions based on the phone number (so that people can only add music every 30 minutes or so), a location restriction (so that people can only add music when within a defined radius), etc. The possibilities are endless!
