Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
37
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

電話をかけるだけでツイートできるシステムを作った

ツイートしたいけど手元にスマホやパソコンがない。でも固定電話はある。
なんてことありませんか?

そんな状況でも電話一本でツイートできるシステムを作りました。

仕組み

スライド1.JPG

使う技術

  • Twilio
  • Bing Speech API
  • Twitter API
  • PHP
  • Python

準備

Linux環境で行います。今回はVPSでDebianを使いました。

特別ライブラリをインストールする必要はなく、LAMP環境を構築する程度です。
ソースコード内のディレクトリについては適宜変更してください。

Twilioでも音声認識ができるようですが、日本語に非対応とのことなので今回は音声認識のためにBing Speech APIを採用しました。

Twilioの準備

アカウントを取得するだけです。
有料アカウントと無料アカウント(トライアルで制限あり)がありますが、今回の場合は無料アカウントで実装できました。

アカウント取得や仕組みについてはこの辺りが参考になります。
Twilioはじめの一歩

Bing Speech APIの準備

こちらのページから取得できます。
azure.JPG

"APIキーの取得"から利用規約に同意してログインすればすぐにキーが取得できます。
Githubアカウントでのログインも可能です。

キーが二種類発行されますが、どちらでも使えるようです。

プログラム

Twilio側の処理は、TwiMLとPHPでその他の処理はPythonで書きます。

TwiMLを書く

以下のxmlをWebサーバなどインターネットからアクセスできる場所に置きます。
そしてtwilioのアカウントページよりURLを設定します。

record.xml
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Say language="ja-JP">ツイート内容を録音します。録音後、シャープを押してください。</Say>
<Record method="GET" timeout="60" maxLength="30" finishOnKey="#" action="./get.php" />
</Response>

この数行で自動で喋ってくれて録音まで行ってくれます。
TwiML分かりやすくて簡単なので良いです。
詳しくは公式リファレンスを参照。

PHPを書く

TwiMLの<Record>に書かれているaction="./get.php"の部分です。
電話が終了した後get.phpに処理が渡されます。

get.php
<?php

// 録音ファイルURLの取得
$recordingUrl = $_GET['RecordingUrl'];
$recordingUrl .= ".wav";

// 録音ファイルのダウンロード
exec("wget -N -O ./record.wav $recordingUrl");

//pythonの実行
exec("python ./tweet.py");

?>

リファレンスにもあるようにaction属性よりRecordingUrlがパラメーターとして送られます。
GETかPOSTで受けることができます。HTMLとかのフォームと同じです。
今回はTwiMLのmethodでGETを指定したのでGETです。

ファイルや保存先ディレクトリのパーミッション設定によっては、パラメーターが取得できなかったり、音声が保存できなかったりするので注意です。
超超初歩的なパーミッション関連の問題が解決できずに数日悩みました。

録音した音声ファイルをダウンロードした後に、pythonのプログラムを実行して音声認識→ツイートの処理を行います。

Pythonを書く

Pythonのプログラムの作成にあたってはこの記事にお世話になりました。
Twilioの音声をMicrosoftのBing Speech APIを使ってテキスト化する方法

まずはツイート用のAPIキーを参照するためのsettings.pyを作成します。

settings.py
CONSUMER_KEY = "xxxxxxxxxxxxxxxxxxxxxxxxx"
CONSUMER_SECRET = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
ACCESS_TOKEN = "xxxxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
ACCESS_TOKEN_SECRET = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

次に音声認識からツイートまでを行うプログラムです。

tweet.py
#coding:utf-8
from requests_oauthlib import OAuth1Session
import json
import settings
import requests
import urllib

#APIからトークンの取得
def authorize():

    url = "https://api.cognitive.microsoft.com/sts/v1.0/issueToken"

    headers = {
        "Content-type": "application/x-www-form-urlencoded",
        "Ocp-Apim-Subscription-Key": "取得したキー"
    }

    response = requests.post(url, headers=headers)

    if response.ok:
        _body = response.text
        return _body
    else:
        response.raise_for_status()

#取得したトークンを格納する
token = authorize()

#ファイルの選択
infile = open("record.wav","r")
data = infile.read()

#APIに投げて結果をもらう
def speech_to_text( raw_data, token, lang="ja-JP", samplerate=8000, scenarios="ulm"):
    data = raw_data
    params = {
        "version": "3.0",
        "requestid": "b2c95ede-97eb-4c88-81e4-80f32d6aee54",
        "appid": "D4D52672-91D7-4C74-8AD8-42B1D98141A5",
        "format": "json",
        "locale": lang,
        "device.os": "Windows",
        "scenarios": scenarios,
        "instanceid": "565D69FF-E928-4B7E-87DA-9A750B96D9E3"
    }

    url = "https://speech.platform.bing.com/recognize?" + urllib.urlencode(params)
    headers = {"Content-type": "audio/wav; samplerate={0}".format(samplerate),
               "Authorization": "Bearer " + token }

    response = requests.post(url, data=data, headers=headers)

    if response.ok:
        result = response.json()["results"][0]
        return result["lexical"]
    else:
        raise response.raise_for_status()

#APIで取得したデータを格納する
message = speech_to_text(data,token,lang="ja-JP", samplerate=8000, scenarios="ulm")

#settings.pyからTokenを取得
twitter = OAuth1Session(settings.CONSUMER_KEY, settings.CONSUMER_SECRET, settings.ACCESS_TOKEN, settings.ACCESS_TOKEN_SECRET)

#ツイート
params = {"status":"%s"%message}
req = twitter.post("https://api.twitter.com/1.1/statuses/update.json",params = params

電話してみる

Twilioの番号に電話をかけると「ツイート内容を録音します。録音後、シャープを押してください。」とアナウンスされるので、ツイートしたい内容を話します。
しばらくするとツイートされます。

長過ぎたり短すぎたりするとうまく動作しないようです。

課題点

とりあえず動いたが課題がいくつか。

1.なぜかTwilioでエラーが発生する(2017/10/26追記)

電話終了時に「アプリケーションエラーが発生しました」と言われる。
Twilioのデバッガーにもエラーが表示されるが解決していない。

→解決したので記事にしました。
電話をかけるだけでツイートできるシステムのエラーについて

2.パーミッションの設定

今回初歩的なパーミッションの設定でかなりハマった。
未だに把握してない部分があるので要勉強。

3.セキュリティ的なお話

プログラム中にexec()で無理やり実行していたりなどセキュリティ的によろしくなさそうな気がする。(コマンドインジェクション)
あとは、CSRFの対策のためにもPHPのデータ受け渡しに工夫する必要あり。

TwiMLのアクセスにも制限をかけたいけどできるのかな。

4.Twilio上に録音ファイルが残る(2017/10/27追記)

Twilio上に録音ファイルが残ったままになり、ツイートする度に蓄積されていきます。
APIで削除できるそうなのでこの問題については解決できそう。

→削除用のプログラムを書いたので記事にしました。
Twilioで録音データをすべて削除する

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
37
Help us understand the problem. What are the problem?