<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Untitled Publication]]></title><description><![CDATA[Untitled Publication]]></description><link>https://migrmrz.dev</link><generator>RSS for Node</generator><lastBuildDate>Sun, 12 Apr 2026 17:02:31 GMT</lastBuildDate><atom:link href="https://migrmrz.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[AWS Lambda functions using Go]]></title><description><![CDATA[Recently, I wanted to deploy a Go service into an AWS Lambda for the first time and found myself struggling a lot more than I expected.
Even though there are plenty of great and helpful posts with examples, there where minor aspects in which they dif...]]></description><link>https://migrmrz.dev/aws-lambda-functions-using-go</link><guid isPermaLink="true">https://migrmrz.dev/aws-lambda-functions-using-go</guid><category><![CDATA[Go Language]]></category><category><![CDATA[AWS]]></category><category><![CDATA[aws lambda]]></category><dc:creator><![CDATA[Miguel Ángel]]></dc:creator><pubDate>Tue, 05 Mar 2024 14:00:37 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1709587010577/b71eb7a9-2c74-4d78-b65a-5442144b248c.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Recently, I wanted to deploy a <em>Go</em> service into an AWS Lambda for the first time and found myself struggling a lot more than I expected.</p>
<p>Even though there are plenty of great and helpful posts with examples, there where minor aspects in which they differed (from each other and from the official documentation) given that the cloud seems to evolve so fast with constant improvement updates.</p>
<p>This is why I decided to create my own updated version using the <a target="_blank" href="https://github.com/aws/aws-lambda-go">official lambda library for go</a> hoping it might help someone out.</p>
<h2 id="heading-what-is-aws-lambda">What is AWS Lambda?</h2>
<p>Lambda is a serverless, event-driven compute service that lets you run your code without provisioning and/or managing servers, only paying for what you use which is extremely convenient.</p>
<p>Lambdas can be triggered with any of the other AWS services for things like:</p>
<ul>
<li><p>File processing (S3)</p>
</li>
<li><p>Data and analytics (DynamoDB)</p>
</li>
</ul>
<h2 id="heading-what-youll-need">What you'll need</h2>
<ul>
<li>An <a target="_blank" href="https://aws.amazon.com/">AWS account</a></li>
</ul>
<h2 id="heading-create-a-new-lambda-function">Create a new lambda function</h2>
<p>Log into your account, go to the search bar on top and type <em>"lambda"</em>. Click on the first result.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709587637627/88ba59b9-3b86-436b-a1c6-3b65c5e5bc8d.png" alt class="image--center mx-auto" /></p>
<p>Then click "<em>Create function</em>".</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709588693975/47334481-d730-49cf-8287-836028d6a101.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>Type: <strong><em>Author from scratch</em></strong></p>
</li>
<li><p>Function name: <strong><em>lambda-example-go</em></strong></p>
</li>
<li><p>Runtime: <strong><em>Amazon Linux 2023</em></strong></p>
</li>
<li><p>Architecture: <strong><em>arm64</em></strong></p>
</li>
</ul>
<p>According to the <a target="_blank" href="https://docs.aws.amazon.com/lambda/latest/dg/foundation-arch.html?icmpid=docs_lambda_help">AWS documentation</a>, "<em>Functions that use arm64 architecture offer lower cost per Gb/s compared with the equivalent function running on an x86-based CPU.</em>" This is the one we'll be using for now.</p>
<p>Also, notice the note box that says <em>"you must provide your own executable or script name '<strong><strong>bootstrap</strong></strong>' as the entry point"</em>. We will be using this information in a few steps.</p>
<p>Click on <em>"Create function"</em> at the bottom and leave this page open.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709588890883/41a66322-bd37-4556-8886-f66b22a73c09.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-build-the-app">Build the app</h2>
<p>Let's now create our simple <em>Hello World</em> service in Go.</p>
<h3 id="heading-create-your-project-directory">Create your project directory</h3>
<pre><code class="lang-bash">mkdir lambda-example-go
<span class="hljs-built_in">cd</span> lambda-example-go
</code></pre>
<h3 id="heading-import-dependencies">Import dependencies</h3>
<p>We will need the lambda handler function to run our code. For more information, check out the <a target="_blank" href="https://docs.aws.amazon.com/lambda/latest/dg/golang-handler.html">official Developer Guide</a>.</p>
<pre><code class="lang-bash">go get github.com/aws/aws-lambda-go/lambda
</code></pre>
<h3 id="heading-create-your-go-module">Create your go module</h3>
<pre><code class="lang-bash">go mod init example.com/lambda-go
</code></pre>
<h3 id="heading-download-dependencies-and-update-gomod">Download dependencies and update go.mod</h3>
<pre><code class="lang-bash">go mod tidy
</code></pre>
<h3 id="heading-write-the-code">Write the code</h3>
<p>Create a <code>main.go</code> file with the following content:</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"log"</span>

    <span class="hljs-string">"github.com/aws/aws-lambda-go/lambda"</span>
)

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    lambda.Start(runService)
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">runService</span><span class="hljs-params">()</span></span> {
    log.Println(<span class="hljs-string">"Hello from AWS"</span>)
    log.Println(<span class="hljs-string">"exiting..."</span>)
}
</code></pre>
<p>We will now build our application, and for this, we need to remember a couple of things:</p>
<ol>
<li><p>We need to use <em>"bootstrap"</em> as the name for our executable. This is very important, otherwise the function will fail on execution</p>
</li>
<li><p>We have to use arm64 architecture for a linux OS</p>
</li>
</ol>
<p>Run the following <em>build</em> command inside your project directory:</p>
<pre><code class="lang-bash">GOOS=linux GOARCH=arm64 go build -v -o ./build/bootstrap main.go
</code></pre>
<p>Here, <strong><em>GOOS</em></strong> defines the operating system and <strong><em>GOARCH</em></strong> the architecture of the target system our code will run on.</p>
<p>Now let's zip our executable file. On macOS we can do that with <em>zip</em>:</p>
<pre><code class="lang-bash">zip -j lambda-example-go.zip ./build/bootstrap
</code></pre>
<p>Let's go back to our AWS page and click on <em>"Upload from"</em> on <em>"Code Source"</em> and choose <em>".zip file"</em>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709589641278/2aec0df6-e088-440b-90eb-93f22bfc219e.png" alt class="image--center mx-auto" /></p>
<p>Click on <em>"Upload"</em> and choose your recently zipped file <em>lambda-example-go.zip</em></p>
<p>Now go to the <em>"Test"</em> tab and just click <em>"Test"</em> on the right.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709589848010/7aee8bb2-70eb-4313-9ab3-e888ed197e9f.png" alt class="image--center mx-auto" /></p>
<p>Execution should succeed and you will see a status box like this one:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709589882036/6e0b4fb4-e6e8-4c11-958b-fb8f6de80d56.png" alt class="image--center mx-auto" /></p>
<p>Click on <em>"logs"</em> and open the log stream</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709589983497/3f31c592-e7ee-445a-bda9-836f63e92b1c.png" alt class="image--center mx-auto" /></p>
<p>You should be able to see our log messages</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709590026795/f8655e2b-8618-4940-85a3-c0ea8c66f78d.png" alt class="image--center mx-auto" /></p>
<p>And that's about it.</p>
]]></content:encoded></item><item><title><![CDATA[Configuring and setting up subdomains on Django using django-hosts]]></title><description><![CDATA[A subdomain is basically a domain that’s part of another main domain and it is used a lot in many websites. I spent a decent amount of time figuring out how to do that and found this very helpful post and decided to extend it a little in a tutorial f...]]></description><link>https://migrmrz.dev/configuring-and-setting-up-subdomains-on-django-using-django-hosts</link><guid isPermaLink="true">https://migrmrz.dev/configuring-and-setting-up-subdomains-on-django-using-django-hosts</guid><category><![CDATA[django-hosts]]></category><category><![CDATA[Django]]></category><category><![CDATA[subdomains]]></category><category><![CDATA[Python 3]]></category><category><![CDATA[Python]]></category><dc:creator><![CDATA[Miguel Ángel]]></dc:creator><pubDate>Mon, 21 Dec 2020 06:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1709086502934/465e37fe-d566-41d9-87ec-6a6c02c06361.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>A subdomain is basically a domain that’s part of another main domain and it is used a lot in many websites. I spent a decent amount of time figuring out how to do that and found <a target="_blank" href="http://www.learningaboutelectronics.com/Articles/How-to-set-up-subdomains-Django-website.php">this very helpful post</a> and decided to extend it a little in a tutorial form. So if you ever want to add a subdomain for your Django site, something like <a target="_blank" href="http://subdomain.your-site.com"><code>subdomain.your-site.com</code></a>, this might help. </p>
<p>We are going to go through all the steps from creating a project in django, to getting our subdomain up and running.</p>
<h2 id="heading-create-a-python-virtual-environment">Create a Python virtual environment</h2>
<p>As a best practice, we will create a virtual environment. I will use <a target="_blank" href="https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html">conda here</a> to create it with a specific version of python.</p>
<pre><code class="lang-bash">$ conda create -n django-env python=3.6
$ conda activate django-env
</code></pre>
<h2 id="heading-packages-installation">Packages installation</h2>
<p>Now let’s use pip to install <a target="_blank" href="https://www.djangoproject.com/">Django</a> [3] and <a target="_blank" href="https://pypi.org/project/django-hosts/">django-hosts</a> [4]:</p>
<pre><code class="lang-bash">$ pip install django django-hosts
</code></pre>
<h2 id="heading-create-a-django-project-and-app">Create a Django project and app</h2>
<p>Once installed, let’s go to our workspace directory and create our django project:</p>
<pre><code class="lang-bash">$ django-admin startproject simpletest
</code></pre>
<p>You will see a new directory created named <code>simpletest</code> and inside it, a <code>manage.py</code> file and another <code>simpletest</code> folder. To avoid confusion between the project and our apps, I like to rename the parent folder so l’ll just call it <code>simpletest-prj</code>. Now you will have something like this:</p>
<pre><code class="lang-plaintext">simpletest-prj
├── manage.py
└── simpletest
    ├── __init__.py
    ├── asgi.py
    ├── settings.py
    ├── urls.py
    └── wsgi.py
</code></pre>
<p>Let’s move inside our <code>simpletest</code> directory to run <code>python3 manage.py runserver</code> and open <a target="_blank" href="http://localhost:8000/">http://localhost:8000/</a> to make sure our project works. You should get a page like this.</p>
<p><img src="https://res.cloudinary.com/hu0vek80i/image/upload/v1/media/uploads/2020/12/21/django-create-project_nmc1rp" alt class="image--center mx-auto" /></p>
<p>Now stop your application and create a new app that we will call <code>home</code>:</p>
<pre><code class="lang-bash">$ python3 manage.py startapp home
</code></pre>
<p>We now want to put our own homepage instead of the django default landing page by following a few steps: </p>
<p>First we need to add a new line to the <code>urls.py</code> file in <code>simpletest</code> under the admin url so that Django knows where to look and redirect when opening our site:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.contrib <span class="hljs-keyword">import</span> admin
<span class="hljs-keyword">from</span> django.urls <span class="hljs-keyword">import</span> path
<span class="hljs-keyword">from</span> . <span class="hljs-keyword">import</span> views  <span class="hljs-comment"># &lt;-- new import</span>

urlpatterns = [
    path(<span class="hljs-string">'admin/'</span>, admin.site.urls),
    path(<span class="hljs-string">''</span>, views.home),  <span class="hljs-comment"># &lt;-- new line</span>
]
</code></pre>
<p>Then we need to create a <code>views.py</code> file under the <code>simpletest</code> folder with the following code. This is the one with the logic, but now we will only render and return a <code>home.html</code> file:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.shortcuts <span class="hljs-keyword">import</span> render

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">home</span>(<span class="hljs-params">request</span>):</span>
    <span class="hljs-keyword">return</span> render(request, <span class="hljs-string">'home.html'</span>)
</code></pre>
<p>The next thing is to create a template folder under the main project directory <code>simpletest-prj</code> and our <code>home.html</code> file with the following content:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Welcome!<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
        Hello there, and welcome to my new site.
    <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>Finally, we need to create our <code>TEMPLATE_DIR</code> variable and add it to the <code>TEMPLATES</code> structure on the <code>settings.py</code> file:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> os

<span class="hljs-comment"># Right below the BASE_DIR declaration</span>
TEMPLATE_DIR = os.path.join(BASE_DIR, <span class="hljs-string">'templates'</span>)
</code></pre>
<pre><code class="lang-python">TEMPLATES = [
    {
        <span class="hljs-string">'BACKEND'</span>: <span class="hljs-string">'django.template.backends.django.DjangoTemplates'</span>,
        <span class="hljs-string">'DIRS'</span>: [TEMPLATE_DIR,],  <span class="hljs-comment"># &lt;-- This is the modified line</span>
        <span class="hljs-string">'APP_DIRS'</span>: <span class="hljs-literal">True</span>,
        <span class="hljs-string">'OPTIONS'</span>: {
            <span class="hljs-string">'context_processors'</span>: [
                <span class="hljs-string">'django.template.context_processors.debug'</span>,
                <span class="hljs-string">'django.template.context_processors.request'</span>,
                <span class="hljs-string">'django.contrib.auth.context_processors.auth'</span>,
                <span class="hljs-string">'django.contrib.messages.context_processors.messages'</span>,
            ],
        },
    },
]
</code></pre>
<p>Refresh your page or run it one more time to get the expected result:</p>
<p><img src="https://res.cloudinary.com/hu0vek80i/image/upload/v1/media/uploads/2020/12/21/main-modified-page_icsjyw" alt class="image--center mx-auto" /></p>
<p>Our directory structure will now look like this:</p>
<pre><code class="lang-python">simpletest-prj
├── db.sqlite3
├── home
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── migrations
│   │   └── __init__.py
│   ├── models.py
│   ├── tests.py
│   └── views.py
├── manage.py
├── simpletest
│   ├── __init__.py
│   ├── asgi.py
│   ├── settings.py
│   ├── urls.py
│   ├── views.py
│   └── wsgi.py
└── templates
    └── home.html
</code></pre>
<h2 id="heading-configure-the-app-with-subdomain">Configure the app with subdomain</h2>
<p>So now our main page is located at <code>localhost:8000</code> and what we like to do is to host our <code>home</code> app on <code>home.localhost:8000</code> instead of something like <code>localhost:8000/home</code>. For that we will do the following:</p>
<p>We need to add <code>home</code> to our app list along with the already installed <code>django-hosts</code> in the <code>settings.py</code> file:</p>
<pre><code class="lang-python">INSTALLED_APPS = [
    <span class="hljs-string">'django.contrib.admin'</span>,
    <span class="hljs-string">'django.contrib.auth'</span>,
    <span class="hljs-string">'django.contrib.contenttypes'</span>,
    <span class="hljs-string">'django.contrib.sessions'</span>,
    <span class="hljs-string">'django.contrib.messages'</span>,
    <span class="hljs-string">'django.contrib.staticfiles'</span>,
    <span class="hljs-string">'home'</span>,  <span class="hljs-comment"># &lt;-- Add this</span>
    <span class="hljs-string">'django_hosts'</span>,  <span class="hljs-comment"># &lt;-- and this</span>
]
</code></pre>
<p>Then let’s add our hosts to the <code>ALLOWED_HOSTS</code> variable in the same file:</p>
<pre><code class="lang-python">ALLOWED_HOSTS = [<span class="hljs-string">'localhost'</span>, <span class="hljs-string">'home.localhost'</span>]
</code></pre>
<p>The <code>MIDDLEWARE</code> variable needs to be updated with the following two values, the first one at the beginning and the second one at the end</p>
<pre><code class="lang-python"><span class="hljs-string">'django_hosts.middleware.HostsRequestMiddleware'</span>
<span class="hljs-string">'django_hosts.middleware.HostsResponseMiddleware'</span>
</code></pre>
<pre><code class="lang-python">MIDDLEWARE = [
    <span class="hljs-string">'django_hosts.middleware.HostsRequestMiddleware'</span>,  <span class="hljs-comment"># &lt;-- new line</span>
    <span class="hljs-string">'django.middleware.security.SecurityMiddleware'</span>,
    <span class="hljs-string">'django.contrib.sessions.middleware.SessionMiddleware'</span>,
    <span class="hljs-string">'django.middleware.common.CommonMiddleware'</span>,
    <span class="hljs-string">'django.middleware.csrf.CsrfViewMiddleware'</span>,
    <span class="hljs-string">'django.contrib.auth.middleware.AuthenticationMiddleware'</span>,
    <span class="hljs-string">'django.contrib.messages.middleware.MessageMiddleware'</span>,
    <span class="hljs-string">'django.middleware.clickjacking.XFrameOptionsMiddleware'</span>,
    <span class="hljs-string">'django_hosts.middleware.HostsResponseMiddleware'</span>,  <span class="hljs-comment"># &lt;-- another new line</span>
]
</code></pre>
<p>Under the <code>ROOT_URLCONF</code> line, add the following:</p>
<pre><code class="lang-python">ROOT_HOSTCONF = <span class="hljs-string">'simpletest.hosts'</span>
DEFAULT_HOST = <span class="hljs-string">'www'</span>
</code></pre>
<p>And now we create a <code>hosts.py</code> file under <code>simpletest</code>. This file will contain all the list of subdomains that will be on our site:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django_hosts <span class="hljs-keyword">import</span> patterns, host
<span class="hljs-keyword">from</span> django.conf <span class="hljs-keyword">import</span> settings

host_patterns = patterns(
    <span class="hljs-string">''</span>,
    host(<span class="hljs-string">r'www'</span>, settings.ROOT_URLCONF, name=<span class="hljs-string">'www'</span>),
    host(<span class="hljs-string">r'home'</span>, <span class="hljs-string">'home.urls'</span>, name=<span class="hljs-string">'home'</span>)
)
</code></pre>
<p>So now we go on to create and edit a <code>urls.py</code> file under the <code>home</code> directory:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.conf.urls <span class="hljs-keyword">import</span> url
<span class="hljs-keyword">from</span> . <span class="hljs-keyword">import</span> views

urlpatterns = [
    url(<span class="hljs-string">r'^$'</span>, views.home, name=<span class="hljs-string">'home'</span>),
]
</code></pre>
<p>And create our view in its corresponding <code>views.py</code> file:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.shortcuts <span class="hljs-keyword">import</span> render

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">home</span>(<span class="hljs-params">request</span>):</span>
    <span class="hljs-keyword">return</span> render(request, <span class="hljs-string">'home/hello.html'</span>)
</code></pre>
<p>Create a <code>templates</code> folder under <code>home</code> with another <code>home</code> subdirectory and a <code>hello.html</code> file inside:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Home<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
        Welcome to the subdomain homepage!
    <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>Save all, refresh your application or run it one more time, and now go to <a target="_blank" href="http://home.localhost:8000/">http://home.localhost:8000/</a></p>
<p><img src="https://res.cloudinary.com/hu0vek80i/image/upload/v1/media/uploads/2020/12/21/home-app-subdomain_lwwpep" alt class="image--center mx-auto" /></p>
<p>For reference, the directory structure should look something like the following at this point:</p>
<pre><code class="lang-plaintext">simpletest-prj
├── db.sqlite3
├── home
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── migrations
│   │   └── __init__.py
│   ├── models.py
│   ├── templates
│   │   └── home
│   │       └── hello.html
│   ├── tests.py
│   ├── urls.py
│   └── views.py
├── manage.py
├── simpletest
│   ├── __init__.py
│   ├── asgi.py
│   ├── hosts.py
│   ├── settings.py
│   ├── urls.py
│   ├── views.py
│   └── wsgi.py
└── templates
    └── home.html
</code></pre>
<p>And that would be it! When you’re ready to deploy your application on your configured domain, just make sure you edit the <code>ALLOWED_HOSTS</code> variable to match the name of the actual domain.</p>
]]></content:encoded></item><item><title><![CDATA[Configure Google Domain and Subdomain for your Heroku Applications]]></title><description><![CDATA[I found myself very confortable working with Heroku, so I decided to use it as my main platform to deploy my applications. That, along with my Google domain, was a great combo to get my website up and running in no time, or so I thought.
It wasn't to...]]></description><link>https://migrmrz.dev/configure-google-domain-and-subdomain-for-your-heroku-applications</link><guid isPermaLink="true">https://migrmrz.dev/configure-google-domain-and-subdomain-for-your-heroku-applications</guid><category><![CDATA[google domains]]></category><category><![CDATA[Heroku]]></category><category><![CDATA[subdomains]]></category><dc:creator><![CDATA[Miguel Ángel]]></dc:creator><pubDate>Sat, 28 Nov 2020 06:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1709085352221/ed49aa5a-9c1d-430b-80e2-0d3b3eb82c2e.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I found myself very confortable working with Heroku, so I decided to use it as my main platform to deploy my applications. That, along with my Google domain, was a great combo to get my website up and running in no time, or so I thought.</p>
<p>It wasn't too long before I realized it was not going to be that easy to achieve. Probably because I was not very familiar with websites and domain configurations, or with Heroku, or it was just not that simple, or maybe all of the above. I found a vast chunk of information related to Google domain configurations and Heroku app configurations, but a very few that talked about both together.</p>
<p>After a few hours and several tries, I managed to get it working (very easily) and so I decided to create a short guide on how to achieve this for anyone who may find it useful.</p>
<p>This might seem obvious, but I don't want to take for granted the things that need to exist for this to work:</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<ul>
<li><p>A Google Domain. You can get it <a target="_blank" href="https://domains.google/">here</a> if you don't already own one</p>
</li>
<li><p>A Heroku application. One with a paid dyno would be recomended to save you the provisioning and management of SSL certificates process</p>
</li>
</ul>
<h2 id="heading-add-domain-to-heroku">Add domain to Heroku</h2>
<p>We need to let Heroku now that we would like to work with our own domain instead of the one that is provisioned. Go to you application, click on <em>Settings</em> and scroll down to the <em>Domains</em> section to click on <em>Add domain</em> and add your own main domain:</p>
<p><img src="https://res.cloudinary.com/hu0vek80i/image/upload/v1/media/uploads/2020/11/28/heroku_new_domain_hl86zg" alt class="image--center mx-auto" /></p>
<p>This will add the record and create a new DNS Target. Copy this and save it because we will use it next.</p>
<p><img src="https://res.cloudinary.com/hu0vek80i/image/upload/v1/media/uploads/2020/11/28/heroku_edit_domain_qnkhhp" alt="Edit domain" class="image--center mx-auto" /></p>
<h2 id="heading-google-domain-configuration">Google Domain configuration</h2>
<p>Now we need to open Google domains and click on DNS under our domain.</p>
<p><img src="https://res.cloudinary.com/hu0vek80i/image/upload/v1/media/uploads/2020/11/28/google_domains_s7hmjq" alt="Google domains" class="image--center mx-auto" /></p>
<p>Scroll down to the <em>Synthetic Records</em> section and fill the following information for a <em>Subdomain forward</em> record: "@" for the <em>Subdomain</em> field, the full url of your main domain for <em>Destination URL</em>, in my case it was "<a target="_blank" href="http://www.migrmrz.dev">http://www.migrmrz.dev</a>", and check <em>Temporary redirect (302)</em>, <em>Forward path</em> and <em>Enable SSL</em>.</p>
<p><img src="https://res.cloudinary.com/hu0vek80i/image/upload/v1/media/uploads/2020/11/28/google_synthetic_records_qvbwgr" alt="Synthetic records" class="image--center mx-auto" /></p>
<p>It will appear below like this once you add it.</p>
<p><img src="https://res.cloudinary.com/hu0vek80i/image/upload/v1/media/uploads/2020/11/28/google_subdomain_forward_bph6tz" alt="Subdomain forward" class="image--center mx-auto" /></p>
<p>Now go down to <em>Custom resource records</em> and fill the following: "www" for the "@" field, choose "CNAME" for <em>Type</em> and for <em>Domain name</em>, paste the <em>DNS Target</em> that you copied from Heroku on the previous section.</p>
<p><img src="https://res.cloudinary.com/hu0vek80i/image/upload/v1/media/uploads/2020/11/28/google_custom_resource_records_kpjkqq" alt="Custom resource records" class="image--center mx-auto" /></p>
<p>You will then have the record below.</p>
<p><img src="https://res.cloudinary.com/hu0vek80i/image/upload/v1/media/uploads/2020/11/28/google_custom_resource_records_2_jizxn8" alt="Custom resource records" class="image--center mx-auto" /></p>
<p>That's all you need, really. You might need to wait a little bit before the changes take effect but after that you will be ready to go.</p>
<h2 id="heading-google-subdomain-configuration">Google Subdomain configuration</h2>
<p>In case you want to create an app and deploy it on a subdomain from the same domain, you can configure it on Google for it to work along with Heroku. The only thing to need to do is repeat the steps on the <em>Add domain to Heroku</em> section with your new subdomain. I'm working on an app that I want it to be deployed on a subdomain called "<a target="_blank" href="http://lastify.migrmrz.dev">lastify.migrmrz.dev</a>" so that's what I added.</p>
<p><img src="https://res.cloudinary.com/hu0vek80i/image/upload/v1/media/uploads/2020/11/28/heroku_domains_hry9ry" alt="Heroku domains" class="image--center mx-auto" /></p>
<p>Again, copy your DNS Target and go back to Google Domains under <em>Synthetic Records</em> to add your new subdomain. This time the name of the subdomain will go in the "@" field.</p>
<p><img src="https://res.cloudinary.com/hu0vek80i/image/upload/v1/media/uploads/2020/11/28/google_subdomains_yrwdmk" alt="Custom resource records" class="image--center mx-auto" /></p>
<p>And that's all there is to it. You can do that for as many subdomains as you would like to configure on your website.</p>
]]></content:encoded></item><item><title><![CDATA[Build a digital jukebox for WhatsApp using Spotify, Python, Flask and Twilio]]></title><description><![CDATA[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 playl...]]></description><link>https://migrmrz.dev/build-a-digital-jukebox-for-whatsapp-using-spotify-python-flask-and-twilio</link><guid isPermaLink="true">https://migrmrz.dev/build-a-digital-jukebox-for-whatsapp-using-spotify-python-flask-and-twilio</guid><category><![CDATA[twilio]]></category><category><![CDATA[whatsapp]]></category><category><![CDATA[Spotify]]></category><category><![CDATA[Python]]></category><category><![CDATA[Flask Framework]]></category><category><![CDATA[ngrok]]></category><category><![CDATA[APIs]]></category><dc:creator><![CDATA[Miguel Ángel]]></dc:creator><pubDate>Fri, 04 Sep 2020 05:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1709080383908/a9d3fb29-2e3e-4dbe-b6c2-d42223b01458.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>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 <a target="_blank" href="https://www.twilio.com/whatsapp">Twilio API for WhatsApp</a>, <a target="_blank" href="https://github.com/spotipy-dev/spotipy">spotipy</a> (a lightweight Python library for the <a target="_blank" href="https://developer.spotify.com/documentation/web-api/">Spotify Web API</a>) and the <a target="_blank" href="https://flask.palletsprojects.com/">Flask framework</a> for Python.</p>
<p><img src="https://lh7-us.googleusercontent.com/ZvGX4GhPpEdv7ssEKJkiqUJbFJImM7Tnpf27RY07v_prg6mubgpyxBGokL7UTFIUkS2v_qa4y7e9XURaI8RzYer_SE0p-u_Z2i0PEWShraHGXBiSTY4nT2CXAxqm_Tp7cxdH3Y7zCYUvN49douadSQ" alt class="image--center mx-auto" /></p>
<h2 id="heading-tutorial-requirements">Tutorial Requirements</h2>
<p>For this tutorial, we will need the following:</p>
<ul>
<li><p>Python 3.6 or newer. You can download it from <a target="_blank" href="https://www.python.org/downloads/">this link</a></p>
</li>
<li><p><a target="_blank" href="https://ngrok.com/">ngrok</a>. 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 <a target="_blank" href="https://ngrok.com/download">download a copy for Windows, MacOS or Linux</a></p>
</li>
<li><p>A smartphone with an active phone number and WhatsApp installed</p>
</li>
<li><p>A Twilio account. If you are new to Twilio <a target="_blank" href="http://www.twilio.com/referral/ACCqc5">create a free account</a> now. You can review the <a target="_blank" href="https://www.twilio.com/docs/usage/tutorials/how-to-use-your-free-trial-account">features and limitations of a free Twilio account</a></p>
</li>
</ul>
<h2 id="heading-create-a-python-virtual-environment">Create a Python Virtual Environment</h2>
<p>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 <a target="_blank" href="https://docs.python.org/3/tutorial/venv.html">virtual environment</a>. We then are going to install the Python packages that we need for our chatbot on it.</p>
<p>If you are using a Unix or Mac OS system, open a terminal and enter the following commands to do the tasks described above:</p>
<pre><code class="lang-bash">mkdir spotwilio-bot
<span class="hljs-built_in">cd</span> spotwilio-bot
python3 -m venv spotwilio-bot
<span class="hljs-built_in">source</span> spotwilio-bot-venv/bin/activate
pip install twilio flask requests spotipy python-dotenv
</code></pre>
<p>For those of you following the tutorial on Windows, enter the following commands in a command prompt window:</p>
<pre><code class="lang-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
</code></pre>
<p>The last command uses <code>pip</code>, the Python package installer, to install the five packages that we are going to use in this project, which are:</p>
<ul>
<li><p>The <a target="_blank" href="https://www.palletsprojects.com/p/flask/">Flask</a> framework, to create the web application that responds to incoming WhatsApp messages with it</p>
</li>
<li><p>The <a target="_blank" href="https://www.twilio.com/docs/libraries/python">Twilio Python Helper library</a>, to work with the Twilio APIs</p>
</li>
<li><p>The <a target="_blank" href="https://requests.kennethreitz.org/en/master/">Requests</a> package, to access third party APIs</p>
</li>
<li><p>The <a target="_blank" href="https://spotipy.readthedocs.io/en/2.13.0/#">Spotipy</a> library for the Spotify Web API that provides for us the basic functionality that we will be needing for this tutorial</p>
</li>
<li><p>The <a target="_blank" href="https://pypi.org/project/python-dotenv/">Dotenv</a> package, to load environment variables from a <code>.env</code> file</p>
</li>
</ul>
<p>For your reference, at the time this tutorial was released these were the versions of the above packages and their dependencies tested:</p>
<pre><code class="lang-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
</code></pre>
<h2 id="heading-configure-the-twilio-whatsapp-sandbox">Configure the Twilio WhatsApp Sandbox</h2>
<p>Twilio provides a <a target="_blank" href="https://www.twilio.com/console/sms/whatsapp/learn">WhatsApp sandbox</a> where you can easily develop and test your application. Once your application is complete you can request <a target="_blank" href="https://www.twilio.com/whatsapp/request-access">production access for your Twilio phone number</a>, which requires approval by WhatsApp.</p>
<p>Let’s connect your smartphone to the sandbox. From your <a target="_blank" href="https://www.twilio.com/console">Twilio Console</a>, select <a target="_blank" href="https://www.twilio.com/console/sms/dashboard">Programmable Messaging</a>, then click on “Try it Out”, and finally click on “<a target="_blank" href="https://www.twilio.com/console/sms/whatsapp/learn">Try WhatsApp</a>”. The WhatsApp sandbox page will show you the sandbox number assigned to your account, and a join code.</p>
<p><img src="https://lh7-us.googleusercontent.com/C_grpIZRx1Z145wzr7xQZ24L4OF03D23XlAx9aW58ChytFDS1FxlF5M400WYf8ai9VeaGKTVy5H9SkCJXNEW9bo475Egd7dDouiadY-KxCsZaduU0yzeLpGA5jvlyJS7jbwCN9XmjN_4Kn8OAHiXuA" alt /></p>
<p>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 <strong>join</strong>, 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.</p>
<p>Note that this step needs to be repeated for any additional phones you’d like to have connected to your sandbox.</p>
<h2 id="heading-getting-spotify-ready-for-our-application">Getting Spotify ready for our application</h2>
<p>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.</p>
<h3 id="heading-create-your-playlist">Create your playlist</h3>
<p>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.</p>
<p>In case you are not familiar with this procedure, here is a very brief and clear video from Spotify that will get you there:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://vimeo.com/122512074">https://vimeo.com/122512074</a></div>
<p> </p>
<p>Once you have added your first song by creating the playlist, it should look something like this:</p>
<p><img src="https://lh7-us.googleusercontent.com/NfyqPSNa6Weu9QBZtQiaGaYpAQ0lgfpecu0ihyGdPR6BNup4c09CGnzeyFb3dTzRr2tStIhgFro4Z5JItagTxMR_gTNLJjIgQuBGk13vFYmhBniC5x_G8xNfqMRCxjgYiJWy44v4mW6nDE8g_S_Baw" alt class="image--center mx-auto" /></p>
<h3 id="heading-authorize-your-application-on-the-spotify-web-api">Authorize your application on the Spotify Web API</h3>
<p>For this, you will have to go to the <a target="_blank" href="https://developer.spotify.com/documentation/web-api/">Spotify Web API</a> 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.</p>
<p><img src="https://lh7-us.googleusercontent.com/MvDFbS9hLkchYnVopXJkBnGOU-wWBO711iAa_Gcza86JvSfwzqsHS5pcZdw0ptQJ3vSaeF3oUC76j83cUe5rqpHgeN0rttuGB1xKdHFLcgw_U22fxUVQ2QxOexsoqbPlo5NJwuhDczG-d15nRwCWHA" alt /></p>
<p><img src="https://lh7-us.googleusercontent.com/cov5-5TrdQH6ZyzlLS9pFZp0SdzstICm8hBdZEJIBpEVUHorG3E-JZn23-fvf0mKhUNa1L1RACmHpemJwNovBz_jYbwIg7yZOeOwHKN73pUAjJ8oSuD3RLmQNRmfJp8zXNWKeM_eZAczUwOwkGBHbw" alt /></p>
<p>Once you’re in the Dashboard, click on “Create an app” to begin.</p>
<p><img src="https://lh7-us.googleusercontent.com/ukqsSr8Q09vh-QD2pZzQgS_27xofKDDY8kWrp0i7PZgo2wmpQDIi1whuNCPGBNRJVU5XqY8_f50HZQHBuO8ohLq83Mn2gUTe9AJeZpM2IGvDixjBA3-RuGn_8U24HcUM0PUMJLPLS92kC7JB4LRAdA" alt /></p>
<p>We will then provide the required information.</p>
<p><img src="https://lh7-us.googleusercontent.com/yTtl0Zd2YVy2ofXgXfKqYHBauVVZmTjCTGb8jf9aq8a9w03E9hymZAhAmgOOQZEkibknFJnKG4vGrgCP8taquExVsAV0eD4X6SWN_57x1N5sofa03cvju2f9_LMx7XpaNIgdeP4Fa5yaaWXb3IbiZg" alt class="image--center mx-auto" /></p>
<p>Click on “Edit Settings” and add your “Redirect URI” as <em>http://127.0.0.1:5000/callback</em>. 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.</p>
<p><img src="https://lh7-us.googleusercontent.com/dnVcjymjmdIKqHxX44rQuVsnk7ad1AQpxmh6-V9-gQhHvnEYyfF3Z6WSTvTU1lChBCoHBjJLW_2m6ybA34PQjF-AH07XZ_LwaxzORfM4d2eCSVLmdA_HYI37GkinT4A-w9gDfDBv7v7DK6j90906nw" alt /></p>
<h3 id="heading-test-the-spotify-api">Test the Spotify API</h3>
<p>Now we are going to be doing a quick test on the Spotify API so you get familiar with how Spotify exposes its information.</p>
<p>Let’s navigate to the “Console” menu and click on “Search” in the submenu, and then select the only method available.</p>
<p><img src="https://lh7-us.googleusercontent.com/RB8tyyfwP0vdZEvuFN-a-khqFD5BS_RgPWlCuO9-cYr4CUNNkVzBvH_XqVADHvpB2M3rLt-fZBuQ_4QAHEVn3dIc8iXyhfIboPRs8h7Sh9yptBI-GSAc0H2ZSkmNbQmNwlYCaxCuDO1zToprGVr3tg" alt /></p>
<p><img src="https://lh7-us.googleusercontent.com/ZgTfFK2HRYmgr90CLfTqirG1aIdM6sG5FkTZdX7R18h_rS3K-eVr-il9sjLytdSC7vciUcWWdt2cFjW0-NKAQ4Nn398Qsm8LOHWbaZeuwrHtPSPqTq7A6ALx99fyH9DkqWPla-f5TPIiDt8ZoWuavA" alt /></p>
<p>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”.</p>
<p><img src="https://lh7-us.googleusercontent.com/26NQ0SRKlD8YtBw2OPjkwepwIbDbUoey3o-ZJEgK0yPFFjF-3CO2Xc2JcPvRylOyKAWgKu8h1nIXrzKo4fvzmKv8QkzmIp-RMRA52MsR5_EWWBoW2Un7jNSTklWPj6c763t7gIkNTpMf3ubHhAz0_w" alt class="image--center mx-auto" /></p>
<p>To execute the request click “Try it”. Once executed, if you scroll down, you will see the response message on the right black frame.</p>
<p><img src="https://lh7-us.googleusercontent.com/gSfeWOc5VINIU9mUv2-X8I2sxbMxY4QVqiZZE9eippNdUrRvOGMJf_SkLhAOoGzHWXkPi_Rm8_D0mZ-sT6j-JRa55yyH5xOJwTIwW6MMUoB6tgYcBLjWvwDN7fib2_8dakWamStx3QM6LosVtcLnqA" alt /></p>
<p>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.</p>
<h3 id="heading-test-the-spotify-api-through-spotipy">Test the Spotify API through spotipy</h3>
<p>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.</p>
<p>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 <code>.env</code> file in the project directory that will look like this:</p>
<pre><code class="lang-plaintext">SPOTIPY_CLIENT_USERNAME="&lt;your_spotify_username&gt;"
SPOTIPY_CLIENT_ID="&lt;your_client_id&gt;"
SPOTIPY_CLIENT_SECRET="&lt;your_client_secret&gt;"
SPOTIPY_REDIRECT_URI="http://127.0.0.1:5000/callback"
SPOTIFY_PLAYLIST_URI=”&lt;your_playlist_uri&gt;”
SPOTIFY_MARKET=”&lt;your_country_code&gt;”
</code></pre>
<p>This is where we can get the values for each variable:</p>
<ol>
<li><strong>Username.</strong> Go to <a target="_blank" href="https://www.spotify.com/">spotify.com</a>. On the upper right corner, click on “Profile” next to your avatar, and then “Account”</li>
</ol>
<p><img src="https://lh7-us.googleusercontent.com/SNVl3iBa1sfIk9TTmtO6izw_PXrRCKDPF4USCZgbpRUk01MOFd0EIn3-hAna5911uH4uGxV8rWtYCI8sFfq5Yiabn7TglOQzHhBWb3dk0T95aFFCsz3zi8ql4JOCUUcg-k7WmHPVSM6-lqydoNI8ug" alt /></p>
<p>There you will find your basic user information. We’re looking for the one next to “username”.</p>
<p><img src="https://lh7-us.googleusercontent.com/WHX6bwdjuLb2ct4tHwgtw_sCOQWPPCJxITyAAVHMOw7ItCAkECDb-f4Q4wsut8zmzrduZDipTdEHT5lt11hDpP2c7uQ1GuWUKn7ngXl8BRXRKHCDFc5tnT7wMQKCnHIsmDfmgzyWBpPeihZj-Uk1bw" alt /></p>
<ol start="2">
<li><strong>Client Id and Client Secret.</strong> For this, we need to go back to our <a target="_blank" href="https://developer.spotify.com/dashboard/">Spotify for Developers Dashboard</a>, 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.</li>
</ol>
<p><img src="https://lh7-us.googleusercontent.com/Er07gfmJ5OOknWueBiMqMZhvTcn7ImW33MDUPRmAPF3RO9K1AMA75oY5whqazU2Y_jjT2MXmlx2EOt3uxRofaru0F6GLsClgj5aNbBTqDc7ScELJt6q_V5Khzn6LF3OEy6UgPJ1CsIeA-974283nQQ" alt /></p>
<p><img src="https://lh7-us.googleusercontent.com/M4IH1T5La0wI3aBLiVuIbeL-7nPyD2izIDopUY3CeEgF0QnpEKeIZl8q-7HusxJXUzAXbkFOpavFtP3iw3cWAomUNRBjgTPvSToOcLhSq4D9qBAbuWsZMQNYSdHmywlaple-6vXeB_GKG1o93Libyw" alt /></p>
<ol start="3">
<li><p><strong>Redirect URI.</strong> This is the URL we previously configured when we first created our app (http://). You can get it from the “Edit Settings” button.</p>
</li>
<li><p><strong>Playlist URI.</strong> 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”</p>
</li>
</ol>
<p><img src="https://lh7-us.googleusercontent.com/ywvqze5YG5d6j7a8NIOlfcb0Kqi86QT263SJ4LyoUlYnz3bkam9Var1B7_94EyJnSqd2ik6uQj2-jMxmwVOPayO63o8Q3__o0udhS8PIKGM9rEue-wTltXArLZzhlSLosH280y377WT6ZZIYLhlcNA" alt class="image--center mx-auto" /></p>
<ol start="5">
<li><strong>Market.</strong> 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 <a target="_blank" href="https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2">ISO 3166-1 alpha 2 code</a> for your country and enter it here. For example, use “US” for the United States.</li>
</ol>
<p>Now let’s start our Python console by typing <code>python</code> 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.</p>
<pre><code class="lang-python"><span class="hljs-meta">&gt;&gt;&gt; </span><span class="hljs-keyword">import</span> os
<span class="hljs-meta">&gt;&gt;&gt; </span><span class="hljs-keyword">import</span> spotipy
<span class="hljs-meta">&gt;&gt;&gt; </span><span class="hljs-keyword">from</span> spotipy.oauth2 <span class="hljs-keyword">import</span> SpotifyOAuth
<span class="hljs-meta">&gt;&gt;&gt; </span><span class="hljs-keyword">from</span> dotenv <span class="hljs-keyword">import</span> load_dotenv
<span class="hljs-meta">&gt;&gt;&gt; </span>load_dotenv()
<span class="hljs-meta">&gt;&gt;&gt; </span>spotify_user = os.environ.get(<span class="hljs-string">"SPOTIPY_CLIENT_USERNAME"</span>)
<span class="hljs-meta">&gt;&gt;&gt; </span>spotify_scope = <span class="hljs-string">"playlist-modify-private"</span>
<span class="hljs-meta">&gt;&gt;&gt; </span>oauth = SpotifyOAuth(username=spotify_user, scope=spotify_scope)
<span class="hljs-meta">&gt;&gt;&gt; </span>user_token = oauth.get_access_token(as_dict=<span class="hljs-literal">False</span>)
</code></pre>
<p>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:</p>
<p><img src="https://lh7-us.googleusercontent.com/DEWoTDTE8HSyCdgxHZHwT2bvI3QOIZW0yFK55012pa-P1QZKa3jFupIF4NB_TtfB94LYXkbHE7GvOE2_KXBCoVHWm4EzdOEjPIBwFDnbhN283kcmqeUPYL4jtvTJr-iPK9TwZYNM_KZaCLxwQ_qkfw" alt /></p>
<p>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.</p>
<p>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 <code>.cache-&lt;your_spotify_username&gt;</code> 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).</p>
<p>The cached token has a structure similar to this:</p>
<pre><code class="lang-json">{
    'access_token': '&lt;your_access_token&gt;',
    'token_type': 'Bearer',
    'expires_in': <span class="hljs-number">3600</span>,
    'scope': ‘playlist-modify-private’,
    'expires_at': <span class="hljs-number">1597259838</span>,
    'refresh_token': '&lt;your_refresh_token&gt;'
}
</code></pre>
<p>If you print your <code>user_token</code> variable after it was generated, you will see the string corresponding to the <code>access_token</code> from the cached token structure from above, which is the information we are going to be using next.</p>
<pre><code class="lang-python"><span class="hljs-meta">&gt;&gt;&gt; </span>print(user_token)
</code></pre>
<p>Now type the following to create the Spotify client, passing the token as an argument:</p>
<pre><code class="lang-python"><span class="hljs-meta">&gt;&gt;&gt; </span>spotify = spotipy.Spotify(auth=user_token)
</code></pre>
<p>And finally our search request:</p>
<pre><code class="lang-python"><span class="hljs-meta">&gt;&gt;&gt; </span>api_results = spotify.search(q=<span class="hljs-string">"Muse"</span>, type=<span class="hljs-string">"artist"</span>)
</code></pre>
<p>When you print <code>api_results</code>, you will see something like this (which should be very similar to what we received on our direct test on the Spotify Console).</p>
<p><img src="https://lh7-us.googleusercontent.com/UaaWm3NoPX3OuKax4CENPAWzzVd-pWgU7N1Chat7w-VXEBLQjX7Xb_VClYtk2irzOMIK_6-5Wi4ve6oaa5hwdo4E46aMVS56qqD9e7lRNgbpjd18OQcIziHczA7UJU6Gu838cm52P9sN5bI55Ipipg" alt class="image--center mx-auto" /></p>
<p>We have now tested a basic search with the spotipy library on Python and we have already generated our access token for future requests.</p>
<h2 id="heading-create-the-flask-chatbot-service">Create the Flask Chatbot Service</h2>
<p>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.</p>
<h3 id="heading-running-ngrok-and-configure-our-twilio-endpoint">Running ngrok and configure our Twilio endpoint</h3>
<p>We need to open a terminal window and run <code>ngrok http 5000</code> 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 <code>./ngrok http 5000</code> if you have the ngrok executable in your current directory. The output of ngrok should be something like this:</p>
<p><img src="https://lh7-us.googleusercontent.com/hGjb6hCW_5W2FT28XLQ5P44iZ6DWFvYEcmZPWpDM-4ZSHGtBZa00Y9orKunLxvYgfeiZQ3Kh46O4iWKHFDSXimdKPsWl4hRmKMBoAykSO3lBYq4vpXQ-pAk8JXIk3KQaSMY5s9fjsS05AS854o7HXg" alt /></p>
<p>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.</p>
<p>Go to the <a target="_blank" href="https://www.twilio.com/console">Twilio Console</a>, click on Programmable Messaging &gt; Settings &gt; <a target="_blank" href="https://www.twilio.com/console/sms/whatsapp/sandbox">WhatsApp Sandbox Settings</a>. Copy the https:// URL from the ngrok output and then paste it on the “When a message comes in” field, with <code>/jukebox</code> appended at the end. The <code>/jukebox</code> path is where we will install the endpoint of our application.</p>
<p><img src="https://lh7-us.googleusercontent.com/8D_bU_hRoWiNLeaUi4vb6x1dP4wbQ3PNPUIuMOOw8UmpHAMq3_6s_ZW6yuW-3ZrQIUzTzUFe3lNpS6d_fkUDXLdWiI5-T4FMelWvxTQyAIfRfaCaXreys8fI99wlYsCdpclte5znOK4Ztz2N-tB9WQ" alt /></p>
<p>Make sure the request method is set to <code>HTTP Post</code> and don’t forget to click the “Save” button at the bottom of the page to record these changes.</p>
<h3 id="heading-webhook">Webhook</h3>
<p>The <a target="_blank" href="https://www.twilio.com/whatsapp">Twilio API for WhatsApp</a> uses a <a target="_blank" href="https://sendgrid.com/blog/whats-webhook/">webhook</a> 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.</p>
<p>Here, we will define our webhook with Flask so let’s create a file <code>twilio_jukebox.py</code> with the following piece of code:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> flask <span class="hljs-keyword">import</span> Flask

app = Flask(__name__)

<span class="hljs-meta">@app.route('/jukebox', methods=['GET'])</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">jukebox</span>():</span>
    <span class="hljs-comment"># webhook logic here with a response</span>
    <span class="hljs-keyword">pass</span>

<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    app.run(debug=<span class="hljs-literal">True</span>)
</code></pre>
<p>If you are not familiar with the Flask framework, its documentation has a <a target="_blank" href="https://flask.palletsprojects.com/en/1.1.x/quickstart/">quick start</a> section that should bring you up to speed quickly. There is also a nice <a target="_blank" href="https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-i-hello-world">tutorial</a> that can help you a lot.</p>
<p>As mentioned before, this application defines a <code>/jukebox</code> 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 <code>jukebox()</code> is going to analyze the message sent by the user and should provide the appropriate response.</p>
<p>The <code>if __name__ == “__main__”</code> 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).</p>
<p>Now we will add the return of the function just to test the application:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> flask <span class="hljs-keyword">import</span> Flask

app = Flask(__name__)

<span class="hljs-meta">@app.route('/jukebox', methods=['GET'])</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">jukebox</span>():</span>
    <span class="hljs-keyword">return</span> <span class="hljs-string">"Hello from Flask!"</span>

<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    app.run(debug=<span class="hljs-literal">True</span>)
</code></pre>
<p>Leave the terminal session running <strong>ngrok</strong> and start a second terminal window. After you activate the virtual environment, run the code with <code>python twilio_jukebox.py</code>. You should see something like this in your terminal:</p>
<pre><code class="lang-bash">* Serving Flask app <span class="hljs-string">"main"</span> (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it <span class="hljs-keyword">in</span> 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 <span class="hljs-built_in">stat</span>
* Debugger is active!
* Debugger PIN: 958-679-664
</code></pre>
<p>Now let’s take the <strong>ngrok</strong> 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:</p>
<pre><code class="lang-bash">127.0.0.1 - - [11/Aug/2020 20:49:46] <span class="hljs-string">"GET /jukebox HTTP/1.1"</span> 200 -
127.0.0.1 - - [11/Aug/2020 20:49:46] <span class="hljs-string">"GET /favicon.ico HTTP/1.1"</span> 404 -
</code></pre>
<p>And this in your browser:</p>
<p><img src="https://lh7-us.googleusercontent.com/3MPjr3w_ncLnuXSl_m6zveH4oS9u-gIx3WZCChphBPYdqwLUL4rc8MOsuTt7vA55kAA0tY3POABSLjdnJQMkZZUyuEnHbpaTttKcRhRlx5BvFVppuztpJp_DcB92RnH_o0GuEap8_HyqEAHAfNW79Q" alt class="image--center mx-auto" /></p>
<p>Our application is up and running!</p>
<h3 id="heading-messages-and-responses">Messages and Responses</h3>
<p>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 <code>POST</code> request with a key of <code>’Body’</code>. We can access it through Flask’s <code>request</code> object.</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> flask <span class="hljs-keyword">import</span> request

incoming_msg = request.values.get(<span class="hljs-string">'Body'</span>, <span class="hljs-string">''</span>)
</code></pre>
<p>The response that Twilio expects from the webhook needs to be given in <a target="_blank" href="https://www.twilio.com/docs/glossary/what-is-twilio-markup-language-twiml">TwiML or Twilio Markup Language</a>, 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:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> twilio.twiml.messaging_response <span class="hljs-keyword">import</span> MessagingResponse

resp = MessagingResponse()
resp.message(<span class="hljs-string">"This is the response text"</span>)
</code></pre>
<p>So if we put them together we can test a basic function that will respond with the same incoming message back to the user:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> flask <span class="hljs-keyword">import</span> Flask, request
<span class="hljs-keyword">from</span> twilio.twiml.messaging_response <span class="hljs-keyword">import</span> MessagingResponse

app = Flask(__name__)

<span class="hljs-meta">@app.route('/jukebox', methods=['POST'])</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">jukebox</span>():</span>
    incoming_msg = request.values.get(<span class="hljs-string">'Body'</span>, <span class="hljs-string">''</span>)
    resp = MessagingResponse()
    resp.message(incoming_msg)

    <span class="hljs-keyword">return</span> str(resp)

<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    app.run(debug=<span class="hljs-literal">True</span>)
</code></pre>
<p>Note that the HTTP method in this example changed from <strong>GET</strong> to <strong>POST</strong>. This matches the configuration of the webhook in the Twilio Console, which was set to <strong>POST</strong> as well.</p>
<p>Now run your app one more time and send a message like “Hello there” to your WhatsApp bot.</p>
<p>You should then see a response with the same message:</p>
<p><img src="https://lh7-us.googleusercontent.com/knF3FFcwD_wjOU0leDVwdXfKDdGGC8428RJCQ1JTtPSFTx-iGzMjcXqhVHCmMft77wXmpytSg-r1LMjFjz0T6iTLizIjw-ofwQNkb7ICd9JDnBezEzozQnm0thTdsyhx16VH97wL6HkmGTCl6Z0UFA" alt class="image--center mx-auto" /></p>
<p>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 <code>jukebox</code> function defined within the <code>/jukebox</code> 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).</p>
<p>Your terminal will also display a message like this letting us know it received a <em>POST</em> request, on the <em>/jukebox</em> endpoint and with an <em>HTTP 200</em> (ok) response:</p>
<pre><code class="lang-bash">127.0.0.1 - - [14/Aug/2020 02:07:50] <span class="hljs-string">"POST /jukebox HTTP/1.1"</span> 200 -
</code></pre>
<h3 id="heading-flask-sessions">Flask sessions</h3>
<p>A <a target="_blank" href="https://flask.palletsprojects.com/en/1.1.x/quickstart/#sessions">session</a> 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.</p>
<p>In order to use sessions, Flask needs to be given a secret key that is used to generate the signatures. The <code>secrets</code> 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:</p>
<pre><code class="lang-python"><span class="hljs-meta">&gt;&gt;&gt; </span><span class="hljs-keyword">import</span> secrets
<span class="hljs-meta">&gt;&gt;&gt; </span>secrets.token_hex(<span class="hljs-number">16</span>)
<span class="hljs-string">'6061d134dbc8335199d6c2fde4fab240'</span>
</code></pre>
<p>The returned string will be your secret key. Now we are going to open our <code>.env</code> file and add a line assigning this to an environment variable.</p>
<pre><code class="lang-plaintext">FLASK_APP_SECRET="&lt;your_secret_key&gt;"
</code></pre>
<p>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:</p>
<ul>
<li><p><strong>results</strong> - Will store a list structure with the results obtained from the Spotipy in the following order:</p>
<ul>
<li><p>Index that we will use once the user sends the selection back to us</p>
</li>
<li><p>Song title</p>
</li>
<li><p>Album name</p>
</li>
<li><p>Artist Name</p>
</li>
<li><p>Spotify song uri to be used when adding the song to the playlist</p>
</li>
</ul>
</li>
<li><p><strong>search</strong> - a flag that will indicate us if there is an ongoing search cycle</p>
</li>
<li><p><strong>next_result</strong> - a flag to let us know if there are more results to be shown next and let the user know</p>
</li>
<li><p><strong>offset</strong> - 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</p>
</li>
<li><p><strong>search_str</strong> - 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</p>
</li>
</ul>
<p>Now let’s go back to our <code>twilio_jukebox.py</code> 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:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> flask <span class="hljs-keyword">import</span> Flask, request, session  <span class="hljs-comment"># session import</span>
<span class="hljs-keyword">from</span> twilio.twiml.messaging_response <span class="hljs-keyword">import</span> MessagingResponse

app = Flask(__name__)

app.secret_key = os.environ.get(<span class="hljs-string">"FLASK_APP_SECRET"</span>)  <span class="hljs-comment"># Assign the generated key to flask’s secret key</span>
</code></pre>
<h3 id="heading-chatbot-logic">Chatbot logic</h3>
<p>As a general overview, this chatbot will work depending on certain keywords and logic that will be in our main function:</p>
<ul>
<li><p>Any word will start the conversation with a greeting</p>
</li>
<li><p><code>search</code> will start the cycle and ask for a search string</p>
</li>
<li><p><code>stop-search</code> to stop the conversation and end the current cycle</p>
</li>
<li><p><code>more</code>. 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</p>
</li>
<li><p><code>search-again</code> will reset the current cycle and ask again for a search string to lookup</p>
</li>
<li><p>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</p>
</li>
</ul>
<p>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.</p>
<p>First we will import the rest of the modules we are going to be using, then delete the previous content of the main <code>jukebox</code> function that echoed our message from WhatsApp, and add this new content inside it.</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> flask <span class="hljs-keyword">import</span> Flask, request, session
<span class="hljs-keyword">from</span> twilio.twiml.messaging_response <span class="hljs-keyword">import</span> MessagingResponse
<span class="hljs-keyword">import</span> os
<span class="hljs-keyword">from</span> spotipy.oauth2 <span class="hljs-keyword">import</span> SpotifyOAuth
<span class="hljs-keyword">import</span> spotipy
<span class="hljs-keyword">from</span> dotenv <span class="hljs-keyword">import</span> load_dotenv
load_dotenv()

app = Flask(__name__)

app.secret_key = os.environ.get(<span class="hljs-string">'FLASK_APP_SECRET'</span>)


<span class="hljs-meta">@app.route("/jukebox", methods=['POST'])</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">jukebox</span>():</span>
    <span class="hljs-string">"""
    Chatbot's main logic
    """</span>
    valid_response = <span class="hljs-literal">False</span>
    resp_messages = []

    <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> valid_response:
        resp_message = create_help_message(<span class="hljs-string">"help"</span>)
        resp_messages.append(resp_message)
        resp = send_message(resp_messages)

    print(session)

    <span class="hljs-keyword">return</span> str(resp)

<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    app.run(debug=<span class="hljs-literal">True</span>)
</code></pre>
<p>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.</p>
<p>Right after the main function definition, we will add two more:</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">send_message</span>(<span class="hljs-params">messages</span>):</span>
    <span class="hljs-string">"""
    Receives a list and sends the TwiML response message(s)
    """</span>
    resp = MessagingResponse()

    <span class="hljs-keyword">for</span> message <span class="hljs-keyword">in</span> messages:
        resp.message(message)

    <span class="hljs-keyword">return</span> resp
</code></pre>
<p>The <code>send_message</code> 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.</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">create_help_message</span>(<span class="hljs-params">message</span>):</span>
    <span class="hljs-string">"""
    List of response messages depending on what needs to be sent back
    to the user.
    """</span>
    <span class="hljs-keyword">if</span> message == <span class="hljs-string">"ask_search"</span>:
        <span class="hljs-keyword">return</span> <span class="hljs-string">"Ok, which song / album / artist are you looking for? "</span> \
            <span class="hljs-string">"Try being as specific as possible. E.g.: Bad Michael Jackson"</span>
    <span class="hljs-keyword">elif</span> message == <span class="hljs-string">"all_results"</span>:
        <span class="hljs-keyword">return</span> <span class="hljs-string">"These are all the results. Please type the "</span> \
            <span class="hljs-string">"number of the song you would like to add to the playlist "</span> \
            <span class="hljs-string">"(example: 9) or type search-again to start a new search. "</span> \
            <span class="hljs-string">"You can type stop-search to end at any time"</span>
    <span class="hljs-keyword">elif</span> message == <span class="hljs-string">"more_results"</span>:
        <span class="hljs-keyword">return</span> <span class="hljs-string">"Please type the number of the song you would like to add to "</span> \
            <span class="hljs-string">"the playlist (Example: 9). If you would like to see more "</span> \
            <span class="hljs-string">"results, type more or search-again to start a new search. "</span> \
            <span class="hljs-string">"You can type stop-search to end at any time"</span>
    <span class="hljs-keyword">elif</span> message == <span class="hljs-string">"no_results"</span>:
        <span class="hljs-keyword">return</span> <span class="hljs-string">"No results found. Please try again. You can type "</span> \
            <span class="hljs-string">"*stop-search* to end at any time"</span>
    <span class="hljs-keyword">elif</span> message == <span class="hljs-string">"more_with_no_results"</span>:
        <span class="hljs-keyword">return</span> <span class="hljs-string">"No more results are available. Please type the number of "</span> \
            <span class="hljs-string">"the song you would like to add to the playlist (Example: 9) "</span> \
            <span class="hljs-string">"or type search-again to start a new search. You can type "</span> \
            <span class="hljs-string">"*stop-search* to end at any time"</span>
    <span class="hljs-keyword">elif</span> message == <span class="hljs-string">"song_selected"</span>:
        <span class="hljs-keyword">return</span> <span class="hljs-string">"The selected song is already on the playlist. Please select "</span> \
            <span class="hljs-string">"a different one or type search-again to start a new search"</span>
    <span class="hljs-keyword">elif</span> message == <span class="hljs-string">"help"</span>:
        <span class="hljs-keyword">return</span> <span class="hljs-string">"Hello! I can help you search and add music to our Spotify "</span> \
            <span class="hljs-string">"playlist. Let's type search to begin!"</span>
</code></pre>
<p>The <code>create_help_message</code> function contains the definition of all the messages that are going to be sent back to the user depending on the time and situation.</p>
<p>Let’s save the file, run your application and say “Hello” to your bot.</p>
<p><img src="https://lh7-us.googleusercontent.com/91_VYRFOo4FWaNx_5HpWBBH80iffewQ0kXdrg-i6cW_tCKAVapWfSI4f15eD21M8T1PfwsqMTJT0amFFZlGfdqwJANu4htSNL5AaeD8BYavryVFtburlymrwbzob9mN__H5JoERsF7R8gjpWg3RGLw" alt class="image--center mx-auto" /></p>
<p>We now started our conversation. Notice that your session has been printed out on your terminal.</p>
<pre><code class="lang-plaintext">&lt;SecureCookieSession {}&gt;
127.0.0.1 - - [25/Aug/2020 20:09:31] "POST /jukebox HTTP/1.1" 200 -
</code></pre>
<p>It is currently empty, but let’s keep an eye on it as we complete the application.</p>
<p>Our next step is to add the condition that reads the <code>search</code> and <code>stop-search</code> keywords in the <code>jukebox()</code> function, so let’s go to that. Right after the <code>resp_messages</code> assignment, add the following:</p>
<pre><code class="lang-python"><span class="hljs-meta">@app.route("/jukebox", methods=['POST'])</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">jukebox</span>():</span>
    <span class="hljs-string">"""
    Chatbot's main logic
    """</span>
    valid_response = <span class="hljs-literal">False</span>
    resp_messages = []
    incoming_msg = request.values.get(<span class="hljs-string">'Body'</span>, <span class="hljs-string">''</span>).lower()

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

    <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> valid_response:
        resp_message = create_help_message(<span class="hljs-string">"help"</span>)
        resp_messages.append(resp_message)
        resp = send_message(resp_messages)

    print(session)

    <span class="hljs-keyword">return</span> str(resp)
</code></pre>
<p>We added the <code>incoming_msg</code> assignment (because now we need to know what’s the actual message from the user) with a <code>.lower()</code> 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.</p>
<p>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 (<code>search</code>) to our bot.</p>
<p><img src="https://lh7-us.googleusercontent.com/2BgQ-Y3uBFe8wYyXzqU8MdkkAk9oAW5jruNc1o-H9D8vkgG0N1pY_37wmR5R1Q7cbM7eALBarcN4t92nJTBfFs5uEUbGS8ikGIgRYqzOQVm4Scur2UiLIoEhuD_7YvHry0y_pq9r6sb7RtZBgyDg3w" alt class="image--center mx-auto" /></p>
<p>Great! Notice how your session object is now showing more information with the variables we just initialized.</p>
<pre><code class="lang-plaintext">&lt;SecureCookieSession {'results': [], 'search': True, 'offset': 0, 'next_result': True}&gt;
127.0.0.1 - - [25/Aug/2020 20:42:50] "POST /jukebox HTTP/1.1" 200 -
</code></pre>
<p>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:</p>
<pre><code class="lang-python"><span class="hljs-meta">@app.route("/jukebox", methods=['POST'])</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">jukebox</span>():</span>
    <span class="hljs-string">"""
    Chatbot's main logic
    """</span>
    valid_response = <span class="hljs-literal">False</span>
    resp_messages = []

    incoming_msg = request.values.get(<span class="hljs-string">'Body'</span>, <span class="hljs-string">''</span>).lower()

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

    print(session)

    <span class="hljs-keyword">return</span> str(resp)
</code></pre>
<p>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 <code>create_help_message()</code>.</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_user_token</span>():</span>
    <span class="hljs-string">"""
    Obtain an access token from Spotify
    """</span>
    oauth = SpotifyOAuth(
        username=os.environ.get(<span class="hljs-string">"SPOTIPY_CLIENT_USERNAME"</span>),
        scope=<span class="hljs-string">'playlist-modify-private'</span>
    )
    user_token = oauth.get_access_token(as_dict=<span class="hljs-literal">False</span>, check_cache=<span class="hljs-literal">True</span>)

    <span class="hljs-keyword">return</span> user_token
</code></pre>
<p>This function uses the <strong>spotipy</strong> library to generate or obtain the access token just the way we did manually before.</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">create_spotify_client</span>():</span>
    <span class="hljs-string">"""
    Creates Spotify API client
    """</span>
    user_token = get_user_token()
    spotify = spotipy.Spotify(auth=user_token)

    <span class="hljs-keyword">return</span> spotify
</code></pre>
<p>This is the base of all interactions with the Spotify API.</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">search</span>(<span class="hljs-params">search_str, offset_val=<span class="hljs-number">0</span>, results=[]</span>):</span>
    <span class="hljs-string">'''
    Returns a list of coincidences either on songs, albums or artists as well
    as if there are any more results next
    '''</span>
    spotify = create_spotify_client()
    api_results = spotify.search(
        q=search_str,
        limit=<span class="hljs-number">8</span>,
        market=os.environ.get(<span class="hljs-string">"SPOTIFY_MARKET"</span>),
        offset=offset_val
    )
    items = api_results[<span class="hljs-string">'tracks'</span>][<span class="hljs-string">'items'</span>]
    next = api_results[<span class="hljs-string">'tracks'</span>][<span class="hljs-string">'next'</span>]
    <span class="hljs-keyword">if</span> len(items) &gt; <span class="hljs-number">0</span>:
        <span class="hljs-keyword">for</span> id, item <span class="hljs-keyword">in</span> enumerate(items):
            song = item[<span class="hljs-string">'name'</span>]
            album = item[<span class="hljs-string">'album'</span>][<span class="hljs-string">'name'</span>]
            artist = item[<span class="hljs-string">'artists'</span>][<span class="hljs-number">0</span>][<span class="hljs-string">'name'</span>]
            uri = item[<span class="hljs-string">'uri'</span>]
            new_item = [id+<span class="hljs-number">1</span>+int(offset_val), song, album, artist, uri]
            results.append(new_item)

    <span class="hljs-keyword">return</span> results, next
</code></pre>
<p>The <code>search</code> 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 <code>next</code> from Spotify which is the request URL for the next set of results or <code>None</code> if there are no more results. Notice how the <code>market</code> variable we talked about earlier is being used now.</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">respond_results</span>(<span class="hljs-params">results, offset_val</span>):</span>
    <span class="hljs-string">"""
    Receives the search result and formats it to text for response
    """</span>
    text_results = <span class="hljs-string">''</span>

    <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(offset_val, len(results)):
        text_results += <span class="hljs-string">"{}.  🎵 {} \n     💿 {} \n     👤 {} \n"</span> \
            <span class="hljs-string">"---------------------------------\n"</span>.format(
                results[i][<span class="hljs-number">0</span>], results[i][<span class="hljs-number">1</span>], results[i][<span class="hljs-number">2</span>], results[i][<span class="hljs-number">3</span>])

    <span class="hljs-keyword">return</span> text_results
</code></pre>
<p>Once the results are obtained, this function will process and format them to be ready to display for the user. Notice that the <code>search</code> 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 <code>respond_results</code> to a more plain message and be able to show 10 or 15 results per message, for example.</p>
<p>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”.</p>
<p><img src="https://lh7-us.googleusercontent.com/vgmhLywfZFqs7rnVFOglp7bUSKVb4_l6H4Uhp9GQcBhkS5ZvvOPcyevoxlKPUTnZfH_cqtAlpWh44ahwDbRVfWtv-2i1nwyZGFt1krVlAkgUo_uu-UTimnCOIwlZYC8rsfV6_KfZ3k-6oRdFm5D6NA" alt class="image--center mx-auto" /></p>
<p><img src="https://lh7-us.googleusercontent.com/mR7m96jqCjupdDNACqCeh7sR_jr6COpbUKvm1VG6F6QmMOlcKtpgzndG2vVjLSocpPCkKAwd5xNHSfHldkDuEjgkXB5nWrR40fmuMC49ABTDw2bpBMxb1Q3-caw2uT5QXC9igXDV3zTViD55SZyuMQ" alt class="image--center mx-auto" /></p>
<p>A list of results with the specific format on <code>respond_results</code> 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 <code>results</code> 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.</p>
<pre><code class="lang-bash">&lt;SecureCookieSession {<span class="hljs-string">'next_result'</span>: True, <span class="hljs-string">'offset'</span>: 8, <span class="hljs-string">'results'</span>: [[1, <span class="hljs-string">'Sultans Of Swing'</span>, <span class="hljs-string">'Dire Straits'</span>, <span class="hljs-string">'Dire Straits'</span>, <span class="hljs-string">'spotify:track:37Tmv4NnfQeb0ZgUC4fOJj'</span>], [2, <span class="hljs-string">'Sultans Of Swing - Live At Hammersmith Odeon, London/1983'</span>, <span class="hljs-string">'Alchemy: Dire Straits Live'</span>, <span class="hljs-string">'Dire Straits'</span>, <span class="hljs-string">'spotify:track:3s9QsxxTlcWcga9agSARTV'</span>], [3, <span class="hljs-string">'Sultans Of Swing - Live At The BBC'</span>, <span class="hljs-string">'Live At The BBC'</span>, <span class="hljs-string">'Dire Straits'</span>, <span class="hljs-string">'spotify:track:4yP4oR6G7l9MeHyDmQRRP1'</span>], [4, <span class="hljs-string">'Sultans Of Swing'</span>, <span class="hljs-string">'Sultans Of Swing - The Very Best Of Dire Straits'</span>, <span class="hljs-string">'Dire Straits'</span>, <span class="hljs-string">'spotify:track:6pKDYgGvwfalcsx1tbYfn7'</span>], [5, <span class="hljs-string">'Sultans Of Swing'</span>, <span class="hljs-string">'Live In Concert'</span>, <span class="hljs-string">'Dire Straits'</span>, <span class="hljs-string">'spotify:track:3TiQGEP0UYsT5RpXRo2q0W'</span>], [6, <span class="hljs-string">'Sultans of Swing - Live at Live Aid, Wembley Stadium, 13th July 1985'</span>, <span class="hljs-string">'Live Aid (Live, 13th July 1985)'</span>, <span class="hljs-string">'Dire Straits'</span>, <span class="hljs-string">'spotify:track:1n8WWItMvZ4bASlBKbbeCY'</span>], [7, <span class="hljs-string">'Sultans Of Swing'</span>, <span class="hljs-string">'Sultans Of Swing - The Very Best Of Dire Straits'</span>, <span class="hljs-string">'Dire Straits'</span>, <span class="hljs-string">'spotify:track:7E6jbnH4zCTxJ6ok8gcxuj'</span>], [8, <span class="hljs-string">'Sultans Of Swing'</span>, <span class="hljs-string">'Sultans Of Swing / Eastbound Train'</span>, <span class="hljs-string">'Dire Straits'</span>, <span class="hljs-string">'spotify:track:7eqHsDc6PND6reFaF9ba4b'</span>]], <span class="hljs-string">'search'</span>: True, <span class="hljs-string">'search_str'</span>: <span class="hljs-string">'sultans of swing dire straits'</span>}&gt;
</code></pre>
<p>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:</p>
<pre><code class="lang-python"><span class="hljs-meta">@app.route("/jukebox", methods=['POST'])</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">jukebox</span>():</span>
    <span class="hljs-string">"""
    Chatbot's main logic
    """</span>
    valid_response = <span class="hljs-literal">False</span>
    resp_messages = []
    incoming_msg = request.values.get(<span class="hljs-string">'Body'</span>, <span class="hljs-string">''</span>).lower()
    <span class="hljs-keyword">if</span> (incoming_msg == <span class="hljs-string">'search'</span> <span class="hljs-keyword">and</span> <span class="hljs-string">'search'</span> <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> session) \
            <span class="hljs-keyword">or</span> incoming_msg == <span class="hljs-string">'search-again'</span>:
        resp_message = create_help_message(<span class="hljs-string">"ask_search"</span>)
        resp_messages.append(resp_message)
        session[<span class="hljs-string">'results'</span>] = []
        session[<span class="hljs-string">'search'</span>] = <span class="hljs-literal">True</span>
        session[<span class="hljs-string">'offset'</span>] = <span class="hljs-number">0</span>
        session[<span class="hljs-string">'next_result'</span>] = <span class="hljs-literal">True</span>
        valid_response = <span class="hljs-literal">True</span>
        resp = send_message(resp_messages)
    <span class="hljs-keyword">elif</span> incoming_msg == <span class="hljs-string">'more'</span> \
            <span class="hljs-keyword">and</span> session[<span class="hljs-string">'search'</span>] \
            <span class="hljs-keyword">and</span> len(session[<span class="hljs-string">'results'</span>]) &gt; <span class="hljs-number">0</span> \
            <span class="hljs-keyword">and</span> session[<span class="hljs-string">'next_result'</span>]:
        results, next = search(
            session[<span class="hljs-string">'search_str'</span>], session[<span class="hljs-string">'offset'</span>], session[<span class="hljs-string">'results'</span>])
        resp_message = respond_results(results, session[<span class="hljs-string">'offset'</span>])
        resp_messages.append(resp_message)
        session[<span class="hljs-string">'offset'</span>] += <span class="hljs-number">8</span>
        valid_response = <span class="hljs-literal">True</span>
        <span class="hljs-keyword">if</span> next <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span>:
            help_message = create_help_message(<span class="hljs-string">"all_results"</span>)
            session[<span class="hljs-string">'next_result'</span>] = <span class="hljs-literal">False</span>
        <span class="hljs-keyword">else</span>:
            help_message = create_help_message(<span class="hljs-string">"more_results"</span>)
            session[<span class="hljs-string">'next_result'</span>] = <span class="hljs-literal">True</span>
        resp_messages.append(help_message)
        resp = send_message(resp_messages)

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

    print(session)

    <span class="hljs-keyword">return</span> str(resp)
</code></pre>
<p>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 <code>more</code> to our bot.</p>
<p><img src="https://lh7-us.googleusercontent.com/XheAkoTXCX3nfk-9gNiUd4hXlrMS2MN2DOpOzVv7m1WECUJmxBbweLe0AWRq_HNbgTejZ2xFtKrWIAiGVV5grbt5-NwLOFxrA0IMCWaQ7wkCbGi4cYT8hpJ6NOpuEbHviKmlZ2V4vRT_L7a1TPaygA" alt /></p>
<p><img src="https://lh7-us.googleusercontent.com/DGJXqnXjuYpjhn_y3RSI0kpL7VzpUThBJoM3y7-W7xajptAQErSSRr11Rb7_YnDGevrlru7k6N9kBG-xjhFFg1Dcy8J_iFtQyV-ETR5nPRbuY1wvXKYbhw9X7iKUTc4HNadfYkSas7reJOFpOZ3vrA" alt /></p>
<p>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.</p>
<pre><code class="lang-python"><span class="hljs-meta">@app.route("/jukebox", methods=['POST'])</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">jukebox</span>():</span>
    <span class="hljs-string">"""
    Chatbot's main logic
    """</span>
    valid_response = <span class="hljs-literal">False</span>
    resp_messages = []

    incoming_msg = request.values.get(<span class="hljs-string">'Body'</span>, <span class="hljs-string">''</span>).lower()

    <span class="hljs-keyword">if</span> (incoming_msg == <span class="hljs-string">'search'</span> <span class="hljs-keyword">and</span> <span class="hljs-string">'search'</span> <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> session) \
            <span class="hljs-keyword">or</span> incoming_msg == <span class="hljs-string">'search-again'</span>:
        resp_message = create_help_message(<span class="hljs-string">"ask_search"</span>)
        resp_messages.append(resp_message)
        session[<span class="hljs-string">'results'</span>] = []
        session[<span class="hljs-string">'search'</span>] = <span class="hljs-literal">True</span>
        session[<span class="hljs-string">'offset'</span>] = <span class="hljs-number">0</span>
        session[<span class="hljs-string">'next_result'</span>] = <span class="hljs-literal">True</span>
        valid_response = <span class="hljs-literal">True</span>
        resp = send_message(resp_messages)

    <span class="hljs-keyword">elif</span> incoming_msg == <span class="hljs-string">'more'</span> \
            <span class="hljs-keyword">and</span> session[<span class="hljs-string">'search'</span>] \
            <span class="hljs-keyword">and</span> len(session[<span class="hljs-string">'results'</span>]) &gt; <span class="hljs-number">0</span> \
            <span class="hljs-keyword">and</span> session[<span class="hljs-string">'next_result'</span>]:
        results, next = search(
            session[<span class="hljs-string">'search_str'</span>], session[<span class="hljs-string">'offset'</span>], session[<span class="hljs-string">'results'</span>])
        resp_message = respond_results(results, session[<span class="hljs-string">'offset'</span>])
        resp_messages.append(resp_message)
        session[<span class="hljs-string">'offset'</span>] += <span class="hljs-number">8</span>
        valid_response = <span class="hljs-literal">True</span>
        <span class="hljs-keyword">if</span> next <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span>:
            help_message = create_help_message(<span class="hljs-string">"all_results"</span>)
            session[<span class="hljs-string">'next_result'</span>] = <span class="hljs-literal">False</span>
        <span class="hljs-keyword">else</span>:
            help_message = create_help_message(<span class="hljs-string">"more_results"</span>)
            session[<span class="hljs-string">'next_result'</span>] = <span class="hljs-literal">True</span>
        resp_messages.append(help_message)
        resp = send_message(resp_messages)

    <span class="hljs-keyword">elif</span> incoming_msg == <span class="hljs-string">'more'</span> <span class="hljs-keyword">and</span> <span class="hljs-keyword">not</span> session[<span class="hljs-string">'next_result'</span>]:
        help_message = create_help_message(<span class="hljs-string">"more_with_no_results"</span>)
        resp_messages.append(help_message)
        resp = send_message(resp_messages)
        valid_response = <span class="hljs-literal">True</span>

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

    <span class="hljs-keyword">elif</span> incoming_msg.isnumeric() <span class="hljs-keyword">and</span> int(incoming_msg) <span class="hljs-keyword">in</span> [
            result[<span class="hljs-number">0</span>] <span class="hljs-keyword">for</span> result <span class="hljs-keyword">in</span> session[<span class="hljs-string">'results'</span>]]:
        <span class="hljs-comment"># Valid response with an integer that corresponds to a displayed song</span>
        current_songs_in_playlist = get_playlist_songs()
        selected_song_uri = session[<span class="hljs-string">'results'</span>][int(incoming_msg)<span class="hljs-number">-1</span>][<span class="hljs-number">4</span>]
        selected_song_name = session[<span class="hljs-string">'results'</span>][int(incoming_msg)<span class="hljs-number">-1</span>][<span class="hljs-number">1</span>]

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

    <span class="hljs-keyword">elif</span> incoming_msg == <span class="hljs-string">'stop-search'</span>:
        session.clear()
        help_message = <span class="hljs-string">"Got it. Bye!"</span>
        resp_messages.append(help_message)
        resp = send_message(resp_messages)
        valid_response = <span class="hljs-literal">True</span>

    <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> valid_response:
        resp_message = create_help_message(<span class="hljs-string">"help"</span>)
        resp_messages.append(resp_message)
        resp = send_message(resp_messages)

    print(session)

    <span class="hljs-keyword">return</span> str(resp)
</code></pre>
<p>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 <code>results</code> 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 <code>add_song_to_playlist()</code>), returning a confirmation message. We can send <code>stop-search</code> to stop the conversion too, but let’s now add our mentioned functions right below <code>respond_results()</code>:</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_playlist_songs</span>():</span>
    <span class="hljs-string">"""
    Retrieve the list uris from the songs in the playlist
    """</span>
    spotify = create_spotify_client()
    results = spotify.user_playlist(
        user=os.environ.get(<span class="hljs-string">"SPOTIPY_CLIENT_USERNAME"</span>),
        playlist_id=os.environ.get(<span class="hljs-string">"SPOTIFY_PLAYLIST_URI"</span>)
    )
    items = results[<span class="hljs-string">'tracks'</span>][<span class="hljs-string">'items'</span>]

    <span class="hljs-keyword">return</span> [item[<span class="hljs-string">'track'</span>][<span class="hljs-string">'uri'</span>] <span class="hljs-keyword">for</span> item <span class="hljs-keyword">in</span> items <span class="hljs-keyword">if</span> len(items) &gt; <span class="hljs-number">0</span>]


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">add_song_to_playlist</span>(<span class="hljs-params">uri</span>):</span>
    <span class="hljs-string">"""
    Will add the selected song to the playlist by song uri
    """</span>
    spotify = create_spotify_client()

    spotify.user_playlist_add_tracks(
        user=os.environ.get(<span class="hljs-string">"SPOTIPY_CLIENT_USERNAME"</span>),
        playlist_id=os.environ.get(<span class="hljs-string">"SPOTIFY_PLAYLIST_URI"</span>),
        tracks=[uri])

    <span class="hljs-keyword">return</span> spotify
</code></pre>
<p>We are now using the <code>SPOTIFY_PLAYLIST_URI</code> variable created before.</p>
<p>Save your file and send the last message to our bot:</p>
<p><img src="https://lh7-us.googleusercontent.com/U9GhJ_KzVOgUz-f6F8aU5JFCDYgO7zgGXCTUvinnp2sA9KWXi3FFX96XKC5Jen--knOB8Yrbjy1z4gVijlvtgor6jwuw_UlMpEjryuXPsUCXUDhKmBts25L6fXElpEZsddvYAisoIC8-M2viqxHgoQ" alt /></p>
<p>You should now be able to see the confirmation message. Let’s open our playlist on Spotify to confirm:</p>
<p><img src="https://lh7-us.googleusercontent.com/aEVjcP6FRwop6mZcHgvFxyAczpjmBuj9q-Y16-a61BazLNp2SK3a4bcbrVbfNF4TXjZ0DgJ5c4RvvFY0zafItMClt95J4DoPoyte0dZ9WMyAuOGZZTu5rqkqlnTnlQP-A4oxTedyQhjePLd36slcmg" alt /></p>
<p>Awesome! And see how our Session object should be now back to the way it started.</p>
<pre><code class="lang-bash">&lt;SecureCookieSession {}&gt;
</code></pre>
<h3 id="heading-everything-together">Everything together</h3>
<p>Our <code>twilio_jukebox.py</code> file should now look like this:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> twilio.twiml.messaging_response <span class="hljs-keyword">import</span> MessagingResponse
<span class="hljs-keyword">from</span> flask <span class="hljs-keyword">import</span> Flask, request, session
<span class="hljs-keyword">import</span> os
<span class="hljs-keyword">from</span> spotipy.oauth2 <span class="hljs-keyword">import</span> SpotifyOAuth
<span class="hljs-keyword">import</span> spotipy
<span class="hljs-keyword">from</span> dotenv <span class="hljs-keyword">import</span> load_dotenv
load_dotenv()

app = Flask(__name__)

app.secret_key = os.environ.get(<span class="hljs-string">"FLASK_APP_SECRET"</span>)


<span class="hljs-meta">@app.route("/jukebox", methods=['POST'])</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">jukebox</span>():</span>
    <span class="hljs-string">"""
    Chatbot's main logic
    """</span>
    valid_response = <span class="hljs-literal">False</span>
    resp_messages = []

    incoming_msg = request.values.get(<span class="hljs-string">'Body'</span>, <span class="hljs-string">''</span>).lower()

    <span class="hljs-keyword">if</span> (incoming_msg == <span class="hljs-string">'search'</span> <span class="hljs-keyword">and</span> <span class="hljs-string">'search'</span> <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> session) \
            <span class="hljs-keyword">or</span> incoming_msg == <span class="hljs-string">'search-again'</span>:
        resp_message = create_help_message(<span class="hljs-string">"ask_search"</span>)
        resp_messages.append(resp_message)
        session[<span class="hljs-string">'results'</span>] = []
        session[<span class="hljs-string">'search'</span>] = <span class="hljs-literal">True</span>
        session[<span class="hljs-string">'offset'</span>] = <span class="hljs-number">0</span>
        session[<span class="hljs-string">'next_result'</span>] = <span class="hljs-literal">True</span>
        valid_response = <span class="hljs-literal">True</span>
        resp = send_message(resp_messages)

    <span class="hljs-keyword">elif</span> incoming_msg == <span class="hljs-string">'more'</span> \
            <span class="hljs-keyword">and</span> session[<span class="hljs-string">'search'</span>] \
            <span class="hljs-keyword">and</span> len(session[<span class="hljs-string">'results'</span>]) &gt; <span class="hljs-number">0</span> \
            <span class="hljs-keyword">and</span> session[<span class="hljs-string">'next_result'</span>]:
        results, next = search(
            session[<span class="hljs-string">'search_str'</span>], session[<span class="hljs-string">'offset'</span>], session[<span class="hljs-string">'results'</span>])
        resp_message = respond_results(results, session[<span class="hljs-string">'offset'</span>])
        resp_messages.append(resp_message)
        session[<span class="hljs-string">'offset'</span>] += <span class="hljs-number">8</span>
        valid_response = <span class="hljs-literal">True</span>
        <span class="hljs-keyword">if</span> next <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span>:
            help_message = create_help_message(<span class="hljs-string">"all_results"</span>)
            session[<span class="hljs-string">'next_result'</span>] = <span class="hljs-literal">False</span>
        <span class="hljs-keyword">else</span>:
            help_message = create_help_message(<span class="hljs-string">"more_results"</span>)
            session[<span class="hljs-string">'next_result'</span>] = <span class="hljs-literal">True</span>
        resp_messages.append(help_message)
        resp = send_message(resp_messages)

    <span class="hljs-keyword">elif</span> incoming_msg == <span class="hljs-string">'more'</span> <span class="hljs-keyword">and</span> <span class="hljs-keyword">not</span> session[<span class="hljs-string">'next_result'</span>]:
        help_message = create_help_message(<span class="hljs-string">"more_with_no_results"</span>)
        resp_messages.append(help_message)
        resp = send_message(resp_messages)
        valid_response = <span class="hljs-literal">True</span>

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

    <span class="hljs-keyword">elif</span> incoming_msg.isnumeric() <span class="hljs-keyword">and</span> int(incoming_msg) <span class="hljs-keyword">in</span> [
            result[<span class="hljs-number">0</span>] <span class="hljs-keyword">for</span> result <span class="hljs-keyword">in</span> session[<span class="hljs-string">'results'</span>]]:
        <span class="hljs-comment"># Valid response with an integer that corresponds to a displayed song</span>
        current_songs_in_playlist = get_playlist_songs()
        selected_song_uri = session[<span class="hljs-string">'results'</span>][int(incoming_msg)<span class="hljs-number">-1</span>][<span class="hljs-number">4</span>]
        selected_song_name = session[<span class="hljs-string">'results'</span>][int(incoming_msg)<span class="hljs-number">-1</span>][<span class="hljs-number">1</span>]

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

    <span class="hljs-keyword">elif</span> incoming_msg == <span class="hljs-string">'stop-search'</span>:
        session.clear()
        help_message = <span class="hljs-string">"Got it. Bye!"</span>
        resp_messages.append(help_message)
        resp = send_message(resp_messages)
        valid_response = <span class="hljs-literal">True</span>

    <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> valid_response:
        resp_message = create_help_message(<span class="hljs-string">"help"</span>)
        resp_messages.append(resp_message)
        resp = send_message(resp_messages)

    print(session)

    <span class="hljs-keyword">return</span> str(resp)


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">send_message</span>(<span class="hljs-params">messages</span>):</span>
    <span class="hljs-string">"""
    Receives a list and sends the TwiML response message(s)
    """</span>
    resp = MessagingResponse()

    <span class="hljs-keyword">for</span> message <span class="hljs-keyword">in</span> messages:
        resp.message(message)

    <span class="hljs-keyword">return</span> resp


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


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_user_token</span>():</span>
    <span class="hljs-string">"""
    Obtain an access token from Spotify
    """</span>
    oauth = SpotifyOAuth(
        username=os.environ.get(<span class="hljs-string">"SPOTIPY_CLIENT_USERNAME"</span>),
        scope=<span class="hljs-string">'playlist-modify-private'</span>
    )
    user_token = oauth.get_access_token(as_dict=<span class="hljs-literal">False</span>, check_cache=<span class="hljs-literal">True</span>)

    <span class="hljs-keyword">return</span> user_token


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">create_spotify_client</span>():</span>
    <span class="hljs-string">"""
    Creates Spotify API client
    """</span>
    user_token = get_user_token()
    spotify = spotipy.Spotify(auth=user_token)

    <span class="hljs-keyword">return</span> spotify


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">search</span>(<span class="hljs-params">search_str, offset_val=<span class="hljs-number">0</span>, results=[]</span>):</span>
    <span class="hljs-string">"""
    Returns a list of coincidences either on songs, albums or artists as well
    as if there are more results to show next
    """</span>
    spotify = create_spotify_client()
    api_results = spotify.search(
        q=search_str,
        limit=<span class="hljs-number">8</span>,
        market=os.environ.get(<span class="hljs-string">"SPOTIFY_MARKET"</span>),
        offset=offset_val
    )
    items = api_results[<span class="hljs-string">'tracks'</span>][<span class="hljs-string">'items'</span>]
    next = api_results[<span class="hljs-string">'tracks'</span>][<span class="hljs-string">'next'</span>]

    <span class="hljs-keyword">if</span> len(items) &gt; <span class="hljs-number">0</span>:
        <span class="hljs-keyword">for</span> id, item <span class="hljs-keyword">in</span> enumerate(items):
            song = item[<span class="hljs-string">'name'</span>]
            album = item[<span class="hljs-string">'album'</span>][<span class="hljs-string">'name'</span>]
            artist = item[<span class="hljs-string">'artists'</span>][<span class="hljs-number">0</span>][<span class="hljs-string">'name'</span>]
            uri = item[<span class="hljs-string">'uri'</span>]
            new_item = [id+<span class="hljs-number">1</span>+int(offset_val), song, album, artist, uri]
            results.append(new_item)

    <span class="hljs-keyword">return</span> results, next


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">respond_results</span>(<span class="hljs-params">results, offset_val</span>):</span>
    <span class="hljs-string">"""
    Receives the search result and formats it to text for response
    """</span>
    text_results = <span class="hljs-string">''</span>

    <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(offset_val, len(results)):
        text_results += <span class="hljs-string">"{}.  🎵 {} \n     💿 {} \n     👤 {} \n"</span> \
            <span class="hljs-string">"---------------------------------\n"</span>.format(
                results[i][<span class="hljs-number">0</span>], results[i][<span class="hljs-number">1</span>], results[i][<span class="hljs-number">2</span>], results[i][<span class="hljs-number">3</span>])

    <span class="hljs-keyword">return</span> text_results


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_playlist_songs</span>():</span>
    <span class="hljs-string">"""
    Retrieve the list uris from the songs in the playlist
    """</span>
    spotify = create_spotify_client()
    results = spotify.user_playlist(
        user=os.environ.get(<span class="hljs-string">"SPOTIPY_CLIENT_USERNAME"</span>),
        playlist_id=os.environ.get(<span class="hljs-string">"SPOTIFY_PLAYLIST_URI"</span>)
    )
    items = results[<span class="hljs-string">'tracks'</span>][<span class="hljs-string">'items'</span>]

    <span class="hljs-keyword">return</span> [item[<span class="hljs-string">'track'</span>][<span class="hljs-string">'uri'</span>] <span class="hljs-keyword">for</span> item <span class="hljs-keyword">in</span> items <span class="hljs-keyword">if</span> len(items) &gt; <span class="hljs-number">0</span>]


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">add_song_to_playlist</span>(<span class="hljs-params">uri</span>):</span>
    <span class="hljs-string">"""
    Will add the selected song to the playlist by song uri
    """</span>
    spotify = create_spotify_client()

    spotify.user_playlist_add_tracks(
        user=os.environ.get(<span class="hljs-string">"SPOTIPY_CLIENT_USERNAME"</span>),
        playlist_id=os.environ.get(<span class="hljs-string">"SPOTIFY_PLAYLIST_URI"</span>),
        tracks=[uri])

    <span class="hljs-keyword">return</span> spotify


<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    app.run(debug=<span class="hljs-literal">True</span>)
</code></pre>
<h3 id="heading-running-and-testing-the-application">Running and testing the application</h3>
<p>Now it’s time to run our application again with <code>python3 twilio_jukebox.py</code>. Write anything to your WhatsApp chatbot to start the conversation. Write <code>search</code>, follow the instructions and add a few songs to your playlist!</p>
<p><img src="https://lh7-us.googleusercontent.com/TqKTA7qSjQor-ZWfCW3gZ_N9020Aq5lSBpsKk7NPGsKrQmeqrTkzUzQEawzbBgBsgBjr-gqZbRI9hLTZyzsQixyBqyBCKzAVsu8cwM99ZyUbvTxMRmLydtRBSU1sgfB8GKtBkEjNp-J2dPi8RBQf1Q" alt /></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>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.</p>
<p>The repository on GitHub can be found <a target="_blank" href="https://github.com/migrmrz/spotwilio-jukebox">here</a>, where I’m working on a few improvements like providing a song “fun fact” at the end that is taken from <a target="_blank" href="https://genius.com/">Genius</a> through its <a target="_blank" href="https://docs.genius.com/">API</a>. 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!</p>
]]></content:encoded></item></channel></rss>