Create a simple reception system with the Python serverless framework Chalice and Twilio

Thing you want to do

I wrote about the reception system, but what I want to do is basically an extension of the following quick start.

PYTHON Quickstart: Make a call from your browser

employee-call.png

When I wondered if I could do something with WebRTC, a Twilio client, I thought I could implement a serverless reception phone, which is common in the company, using a Web browser such as a tablet.

Twilio officially has a sample code for browser calls using Flask, but the information on twilio-python and twilio.js is a little old, so it didn't work as it was, so it works with the latest code. I'm trying to do it.

Instead of using an extension phone, you can make a call directly to your personal smartphone. The advantage is that you don't need a landline phone and you don't have to give out your personal phone number. Also, since it is implemented without a server, it is maintenance-free and inexpensive. (Call charges will be incurred ...)

environment

Things necessary

This time, I wanted to create an environment of AWS Lambda + API GateWay quickly, so I used AWS Chalice.

Installation of required libraries

$ pip install chalice

Next, set up your AWS credentials. If you have already set it using boto3 etc., you probably don't need it.

$ mkdir ~/.aws
$ vim ~/.aws/credentials
[default]
aws_access_key_id=YOUR_ACCESS_KEY_HERE
aws_secret_access_key=YOUR_SECRET_ACCESS_KEY
region=YOUR_REGION (such as us-west-2, us-west-1, etc)

Creating a Lambda function

Create a project in Chalice.

$ chalice new-project EmployeeCaller
$ cd EmployeeCaller

When you execute the above command, a file will be created under the directory, so edit that file.

First, write the library to deploy in requirements.txt.

$ vim requirements.txt
twilio==6.0.0

Then edit app.py and put in the code.

app.py


import os, json
from urlparse import parse_qs
from chalice import Chalice, Response
from twilio.jwt.client import ClientCapabilityToken
from twilio.twiml.voice_response import VoiceResponse

app = Chalice(app_name='EmployeeCaller')

#Generate TwiML, the endpoint of voice calls
@app.route('/voice', methods=['POST'], content_types=['application/x-www-form-urlencoded'], cors=True)
def voice():
    parsed = parse_qs(app.current_request.raw_body)
    dest_number = parsed.get('PhoneNumber', [])

    resp = VoiceResponse()

    resp.dial(dest_number[0], caller_id=os.environ['TWILIO_CALLER_ID'])
    return Response(body=str(resp), status_code=200, headers={'Content-Type': 'application/xml'})

#Issue a token
@app.route('/client', methods=['GET'])
def client():
    request = app.current_request

    account_sid = os.environ['TWILIO_ACCOUNT_SID']
    auth_token = os.environ['TWILIO_AUTH_TOKEN']
    application_sid = os.environ['TWILIO_TWIML_APP_SID']

    capability = ClientCapabilityToken(account_sid, auth_token)
    capability.allow_client_outgoing(application_sid)
    capability.allow_client_incoming(os.environ['DEFAULT_CLIENT'])
    token = capability.to_jwt()

    callback = request.query_params['callback']
    return str(callback) + "(" + json.dumps({"token": token}) + ")"

A little supplement to Chalice, Chalice is based on a micro-framework called Flask. So I think you should look at Flask's documentation for routing etc.

About routing

First of all, regarding the following routing

@app.route('/client', methods=['GET'])

You'll need to get a Capability Token issued to make a call on your Twilio client. Hit / client to generate this token and return it with json. It feels like the front side uses this to make a call.

You can read more about tokens in TWILIO Client Capability Tokens.

Then the following routing

@app.route('/voice', methods=['POST'], content_types=['application/x-www-form-urlencoded'], cors=True)

This / voice will receive a Request from the Twilio server that includes the phone number of the connection destination. Since it is sent by POST, you need to specify ʻapplication / x-www-form-urlencoded` for Content-Type.

About environment variables

The part of os.environ ['---'] in the code is the place to read the environment variable, which is written in the Chalice configuration file, but it is set after deploying once and setting Twilio.

Deploy

Deploy the above code. Execute the following command in the project directory.

$ chalice deploy
Updating IAM policy.
Updating lambda function...
Regen deployment package...
Sending changes to lambda.
API Gateway rest API already found.
Deploying to: dev
https://********.execute-api.ap-northeast-1.amazonaws.com/dev/

When you run it, you should see a result like the one above. Make a note of the URL that was output last, as it will be used later as the URL to call APIGateWay.

Create a TwiML App with Twilio

Account SID and Auth Token

Log in to Twilio and make a note of the Console Dash Board's "ACCOUNT SID" and "AUTH TOKEN".

Creating a TwiML APP

Create a new TwiML APP from Phone Number> Tools. Enter the App name as appropriate for the friendly name.

Enter the URL + / voice that you wrote down when you did chalice deploy earlier in "Request URL" of "Voice call" and save it. (https: //********.execute-api.ap-northeast-1.amazonaws.com/dev/voice)

When you create a TwiML APP, the App will appear in the list, so display the details, check the "SID" (TwiML App SID), and make a note of it.

Purchase a phone number

Purchase a phone number from "Phone Number" with "Buy a Number". Make a note of your phone number.

How to purchase a phone number [previously written article](http://qiita.com/hidesakai/items/20873fa354cb49911608#twilio%E3%81%A7%E9%9B%BB%E8%A9%B1%E7%95 % AA% E5% 8F% B7% E8% B3% BC% E5% 85% A5% E8% AA% B2% E9% 87% 91), so if you don't know, please refer to that.

Set environment variables in Lambda function

After creating the TwiML APP, go back to your chalice project and edit the following files.

$ vim .chalice/config.json

Add an item called ʻenviroment_variables` to the config.json file. Set the SID and Token you wrote down earlier here. This will be an environment variable on AWS Lambda.

.....
"app_name": "EmployeeCaller",
//Added the following items
"environment_variables": {
    "TWILIO_ACCOUNT_SID": "*******************",
    "TWILIO_AUTH_TOKEN": "*******************",
    "TWILIO_TWIML_APP_SID": "*******************",
    "TWILIO_CALLER_ID": "+81********",
    "DEFAULT_CLIENT": "reception"
}

After editing config.json, deploy again.

$ chalice deploy

Create reception screen, upload to S3

Next, create a reception screen.

Screen creation

$ vim employee.html

employee_call.html


<html>
    <head>
        <script src="https://code.jquery.com/jquery-3.2.1.min.js" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"></script>
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" integrity="sha384-rwoIResjU2yc3z8GV/NPeZWAv56rSmLldC3R/AZzGRnGxQQKnKkoFVhFQhNUwEyJ" crossorigin="anonymous">
        <script src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.4.0/js/tether.min.js" integrity="sha384-DztdAPBWPRXSA/3eYEEUWrWCy7G5KFbe8fFjk5JAIxUYHKkDx6Qin1DkWx51bBrb" crossorigin="anonymous"></script>
        <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/js/bootstrap.min.js" integrity="sha384-vBWWzlZJ8ea9aCX4pEW3rVHjgjt7zpkNpZk+02D9phzyeVkE+jo0ieGizqPLForn" crossorigin="anonymous"></script>
        <script type="text/javascript" src="https://media.twiliocdn.com/sdk/js/client/v1.4/twilio.min.js"></script>
        <script type="text/javascript">
            Twilio.Device.ready(function (device) {
                console.log("Ready");
            });

            Twilio.Device.error(function (error) {
                console.log("Error: " + error.message);
            });

            Twilio.Device.connect(function (conn) {
                console.log("Successfully established call");
            });

            Twilio.Device.disconnect(function (conn) {
                console.log("Call ended");
                $('.employee-hangup').addClass('disabled').prop('disabled', true);
                $('.employee-call').removeClass('disabled').prop('disabled', false);
            });

            Twilio.Device.incoming(function (conn) {
                console.log("Incoming connection from " + conn.parameters.From);
                conn.accept();
            });

            function twilioReadyAsync(phoneNumber) {
                return new Promise(function(resolve){
                    (function ready(){
                        if (Twilio.Device.status() == 'ready') {
                            resolve({"PhoneNumber": phoneNumber});
                        }
                        setTimeout(ready, 1000);
                    })();
                });
            }

            $(function() {
                $('.employee-hangup').addClass('disabled').prop('disabled', true);

                $('.employee-call').click(function(){
                    var employeePhoneNumber = $(this).attr('data-phone-number');

                    $(this).next().removeClass('disabled').prop('disabled', false);
                    $('.employee-call').addClass('disabled').prop('disabled', true);

                    $.ajax({
                        url: 'https://******.execute-api.ap-northeast-1.amazonaws.com/dev/client',
                        dataType: 'jsonp',
                        jsonCallback: 'callback'
                    })
                    .done(function(data) {
                        Twilio.Device.setup(data.token);
                        twilioReadyAsync(employeePhoneNumber).then(Twilio.Device.connect);
                    });
                });

                $('.employee-hangup').click(function(){
                    Twilio.Device.disconnectAll();
                    $(this).addClass('disabled').prop('disabled', true);
                    $('.employee-call').removeClass('disabled').prop('disabled', false);
                });
            });

        </script>
        <style>
            .container {width: auto;}
        </style>
    </head>
    <body>
        <div class="container">
            <h1>Reception</h1>
            <div class="card-deck">
                <div class="card text-center" id="employee-1">
                    <img class="card-img-top img-fluid" src="hidesakai.png " alt="Card image cap">
                    <div class="card-block">
                        <h4 class="card-title">hidesakai</h4>
                        <p class="card-text">
                        <p>Development: Engineer</p>
Please contact us by extension when you need it.
                        </p>
                        <button class="btn btn-primary employee-call" data-phone-number="+8190-****-****">Call</button>
                        <button class="btn btn-danger employee-hangup">Hangup</button>
                    </div>
                </div>
                <div class="card text-center" id="employee-2">
                    <img class="card-img-top img-fluid" src="spam.jpg " alt="Card image cap">
                    <div class="card-block">
                        <h4 class="card-title">Spam</h4>
                        <p class="card-text">
                        <p>Design: Designer</p>
Please contact us from the reception.
                        </p>
                        <button class="btn btn-primary employee-call" data-phone-number="+8190-****-****">Call</button>
                        <button class="btn btn-danger employee-hangup">Hangup</button>
                    </div>
                </div>
                <div class="card text-center" id="employee-3">
                    <img class="card-img-top img-fluid" src="egg.png " alt="Card image cap">
                    <div class="card-block">
                        <h4 class="card-title">Egg</h4>
                        <p class="card-text">
                        <p>Sales: Marketer</p>
                        </p>
                        <button class="btn btn-primary employee-call" data-phone-number="+8190-****-****">Call</button>
                        <button class="btn btn-danger employee-hangup">Hangup</button>
                    </div>
                </div>
                <div class="card text-center" id="employee-4">
                    <div class="card-block">
                        <h4 class="card-title">Reception</h4>
                        <p class="card-text">
This is the reception.
                        </p>
                        <button class="btn btn-primary employee-call" data-phone-number="+8180-****-****">Call</button>
                        <button class="btn btn-danger employee-hangup">Hangup</button>
                    </div>
                </div>
            </div>
        </div>
    </body>
</html>

$ .ajax () ʻurl:'https: //******.execute-api.ap-northeast-1.amazonaws.com/dev/client'` URL when charis deployed earlier Is added as appropriate.

Then enter the phone number starting with the country code (+81) in data-phone-number.

Upload to S3

Create a suitable bucket on S3 and upload the html file.

Call from the screen

After uploading to S3, go to the page and press the Call button. It is OK if you can send to the set phone number.

スクリーンショット 2017-05-09 1.50.01.png

Challenges point

At this rate, you can access it from anywhere and you can play tricks on it.

After that, I have put the phone number directly on the html, but I would like to get the data from Lambda such as DynamoDB.

Recommended Posts

Create a simple reception system with the Python serverless framework Chalice and Twilio
Create a simple Python development environment with VS Code and Docker
Touch AWS with Serverless Framework and Python
[CRUD] [Django] Create a CRUD site using the Python framework Django ~ 1 ~
Create Python version Lambda function (+ Lambda Layer) with Serverless Framework
Create a Todo app with the Django REST framework
Let's make a simple game with Python 3 and iPhone
[CRUD] [Django] Create a CRUD site using the Python framework Django ~ 2 ~
[CRUD] [Django] Create a CRUD site using the Python framework Django ~ 3 ~
[CRUD] [Django] Create a CRUD site using the Python framework Django ~ 4 ~
[CRUD] [Django] Create a CRUD site using the Python framework Django ~ 5 ~
Easily serverless with Python with chalice
Create a directory with python
Probably the easiest way to create a pdf with Python3
Let's create a PRML diagram with Python, Numpy and matplotlib.
Create a Twitter BOT with the GoogleAppEngine SDK for Python
Create a simple video analysis tool with python wxpython + openCV
Create a simple Python development environment with VSCode & Docker Desktop
Create a virtual environment with Python!
Make a recommender system with python
Create a Python general-purpose decorator framework
Create a striped illusion with gamma correction for Python3 and openCV3
Create a color picker for the color wheel with Python + Qt (PySide)
Make a simple OMR (mark sheet reader) with Python and OpenCV
Specify or create a python folder and then save the screenshot.
Create a simple scheduled batch using Docker's Python Image and parse-crontab
Solve the Python knapsack problem with a branch and bound method
Install pip in Serverless Framework and AWS Lambda with Python environment
Create a C ++ and Python execution environment with WSL2 + Docker + VSCode
Create a REST API to operate dynamodb with the Django REST Framework
Create and return a CP932 CSV file for Excel with Chalice
Create a compatibility judgment program with the random module of python.
Create a Python function decorator with Class
Creating a simple PowerPoint file with Python
Calculate the shortest route of a graph with Dijkstra's algorithm and Python
Install Python as a Framework with pyenv
Build a blockchain with Python ① Create a class
[AWS] Create a Python Lambda environment with CodeStar and do Hello World
Create a dummy image with Python + PIL.
Create a simple GUI app in Python
[Python] Create a virtual environment with Anaconda
Let's create a free group with Python
A memo with Python2.7 and Python3 on CentOS
Search the maze with the python A * algorithm
Create and decrypt Caesar cipher with python
Create a star system with Blender 2.80 script
Create a simple web app with flask
Get and convert the current time in the system local timezone with python
Create a word frequency counter with Python 3.4
Create a record with attachments in KINTONE using the Python requests module
Let's make a web framework with Python! (1)
Create a Python3 environment with pyenv on Mac and display a NetworkX graph
Create a decision tree from 0 with Python and understand it (5. Information Entropy)
Let's make a web framework with Python! (2)
I made a simple blackjack with Python
Implement a simple application with Python full scratch without using a web framework.
Create a Python image in Django without a dummy image file and test the image upload
Put Docker in Windows Home and run a simple web server with Python
Let's create it by applying Protocol Buffer to the API with Serverless Framework.
Zip-compress any file with the [shell] command to create a file and delete the original file.
I tried to easily create a fully automatic attendance system with Selenium + Python