21
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

TwitterでつぶやくAlexaスキルを作る

Last updated at Posted at 2018-01-18

初めて投稿します。

ようやくAmazon Echoを手に入れたのでTwitterAPIを使ったAlexaスキル開発をしてみました。

本記事はプログラミングというよりも、Alexaスキル開発の流れを説明する形になっています。

#作成したスキル
今回作成したスキルは、

Alexaに「行ってきます」「ただいま」と声をかけるとツイッターにその時間を投稿する

というシンプルなものです。

2018-01-18_23h01_39.png
スキル名は「ツイッター」+「ログ」で**『ツイログ』**としました。
  • 「アレクサ、ツイログで行ってきます」
  • 「アレクサ、ツイログでただいま」

と、不自然な発話をすることでツイートします。

ツイートが成功するとAlexaが「つぶやきました」と喋ります。

##注意
作成するスキルはTwitterAPIに必要なACCESS_TOKEN、ACCESS_TOKEN_SECRETをプログラム中に埋め込むため、アカウントリンクを行わない特定のTwitterアカウント専用スキルです。

##参考資料
Amazon公式のAlexaスキル開発トレーニングで最低限必要な知識は身につきます。

スキル開発してみようと思ったら、まずはこれを全て実施しましょう。1日あれば終わります。

##全体イメージ
Alexaスキル『ツイログ』のイメージはこんな感じです。
無題のプレゼンテーション.png

#Alexaスキルの開発
##TwitterApplicationManagementの登録
まず初めに外部からTwitterAPIを叩くための各種キーを取得します。(画像は過去の使い回しなので入力内容はあまり気にしないでください)

Twitter Application Managementへアクセスし、自分のTwitterアカウントでログインします。

次に右上の**「Create New App」**をクリックします。
2017-12-24_00h26_32.png

登録するアプリケーションの入力フォームが出ます。「Name」「Description」「Website」は必須入力項目です。適当な内容で入力します。

  • Name:アプリケーションの登録名
  • Description:アプリケーションの説明文
  • Website:アプリケーションの問い合わせ先が分かるようなURLを入力します。
  • CallbackURL:今回は設定不要です。

入力したらページ最下部の**「Create your application」**をクリックします。
2017-12-24_00h32_31.png

これでアプリケーションの登録が完了しました。
2017-12-24_00h33_59.png

続いてAPIの利用に必要となるAccess Tokenを発行します。表示されたページのタブ**「Keys And Access Tokens」から「Create my access token」**をクリックします。
2017-12-24_00h35_06.png
これでTwitterAPIの準備は完了です。ページに表示されているConsumer KeyConsumer SecretAccess TokenAccess Token Secretをプログラム内で利用します。
20180119073527

20180119073742

##Amazon開発者コンソール
いよいよAlexaスキルを作成していきます。まずAmazon開発者コンソールにログインします。

上段メニュー**「ALEXA」を選択して「Alexa Skills Kit」**へ進みます。
2018-01-17_22h23_05.png

右上の**「新しいスキルを追加する」**をクリックします。
アマゾン アプリ 開発者ポータル.png

スキルの基本情報として言語``スキル名``呼び出し名を入力して保存、次へ進みます。

  • 言語:スキルの対応言語。
  • スキル名:このスキルの名前。ユーザーにはスキル名が表示される。
  • 呼び出し名:スキルを呼び出す時のフレーズ。「アレクサ、◯◯を開いて」の「◯◯」の部分。
アマゾン アプリ 開発者ポータル2.png

続いて対話モデルを設定します。ここがAlexaスキル開発のポイントです。**「インテントスキーマ」「サンプル発話」**を設定します。
アマゾン アプリ 開発者ポータル3.png

###インテントスキーマ
インテントスキーマは**「Alexaスキルで実行するアクションの種類」**です。JSON形式で以下のように入力します。

{
  "intents": [
    {
      "intent": "GoOutTweet"
    },
    {
      "intent": "ComeHomeTweet"
    },
    {
      "intent": "AMAZON.HelpIntent"
    },
    {
      "intent": "AMAZON.StopIntent"
    },
    {
      "intent": "AMAZON.CancelIntent"
    }
  ]
}
  • GoOutTweet:「行ってきます」に対応するアクション。
  • ComeHomeTweet:「ただいま」に対応するアクション。
  • AMAZON.HelpIntent:「使い方」や「説明」に対応するアクション。
  • AMAZON.StopIntent:「中止」「停止」に対応するアクション。
  • AMAZON.Cancelntent:「キャンセル」に対応するアクション。

AMAZON.と付いているインテントは**「標準インテント」**とよばれ、インテントスキーマに定義するだけでデフォルトのキーフレーズで使えるようになります。

一方でGoOutTweetComeHomeTweetは今回作成するAlexaスキルのオリジナルアクションである**「カスタムインテント」です。カスタムインテントは次に説明するサンプル発話でキーフレーズを設定する必要があります。**

###サンプル発話
サンプル発話ではカスタムインテントに対して**「そのインテントがアクションを起こすためのキーフレーズ」**を定義します。例えば「行ってきます」っぽいことを言えばGoOutTweetが、「ただいま」っぽいことを言えばComeHomeTweetが起動するイメージです。

今回はGoOutTweet、ComeHomeTweetに対して以下のように発話フレーズを設定します。

GoOutTweet 行く
GoOutTweet 行って
GoOutTweet 行きます
GoOutTweet 出かける
ComeHomeTweet ただいま
ComeHomeTweet 帰った
ComeHomeTweet 帰って
ComeHomeTweet 戻った

入力が完了したら次に進みます。

##AWS Lambda
ここで一度Amazon開発者コンソールを離れ、AWSにログインします。

ログインしたらAWSサービスからLambdaを選択します。
2018-01-17_23h05_39.png

右上の**「関数の作成」**を実行します。
Lambda Management Console.png

作成フォームで**「一から作成」を選択します。名前は任意の名前を入力し、ランタイム「Node.js 6.10」**を選択します。
2018-01-17_23h37_08.png

続いてロールの項目から**「カスタムロールの作成」**を選択します。
2018-01-17_23h38_08.png

ロールの作成画面が表示されます。全てそのままで「許可」をクリックします。
2018-01-17_23h40_49.png

ここまでできたら右下の「関数の作成」をクリック。


実際に動かすプログラムを直接コーディングすることもできますが、今回はzipファイルをアップロードする形式を取ります。zipファイルとコードはgithub上にアップロードしています。 https://github.com/quotto/TweetByAlexa

画面の中頃に「関数コード」という項目があるので、ここでコードエントリータイプ**「.ZIPファイルをアップロード」**を選択して、ファイルskill.zipをアップロードします。
2018-01-17_23h43_23.png

20180118221455

###一応プログラムの説明
index.jsがメインの実行プログラムです。Amazon開発者コンソールで定義したインテントごとにAlexaの振る舞いを実装します。

index.js
'use strict';

const Alexa = require('alexa-sdk');

const Twitter = require('twitter');

const APP_ID = undefined;  // TODO replace with your app ID (OPTIONAL).

const JSTOffset = 60 * 9 * 60 * 1000; // JST時間を求めるためのオフセット

const ErrorMessage = '<say-as interpret-as="interjection">ごめんなさい、</say-as>つぶやけませんでした。';

function calculateJSTTime() {
    var localdt = new Date(); // 実行サーバのローカル時間
    var jsttime = localdt.getTime() + (localdt.getTimezoneOffset() * 60 * 1000) + JSTOffset;
    var dt = new Date(jsttime);
    return dt;
}

const handlers = {
    'LaunchRequest': function () {
        this.emit('AMAZON.HelpIntent');
    },
    'GoOutTweet' : function() {
        var dt = calculateJSTTime();
        var stringTime = dt.getFullYear() + "" + (dt.getMonth()+1) + "" + dt.getDate() + "" + dt.getHours() + "" + dt.getMinutes() + "" + dt.getSeconds() + "";
        var message = stringTime + "に出かけました。(Alexaスキルでつぶやいています)";
        Twitter.postTweet(message).then(()=>{
            this.emit(':tell','つぶやきました。<say-as interpret-as="interjection">いってらっしゃい。</say-as>');
        },(error)=>{
            console.log(error);
            this.emit(':tell',ErrorMessage);
        })
    },
    'ComeHomeTweet' : function() {
        var dt = calculateJSTTime();
        var stringTime = dt.getFullYear() + "" + (dt.getMonth()+1) + "" + dt.getDate() + "" + dt.getHours() + "" + dt.getMinutes() + "" + dt.getSeconds() + "";
        var message = stringTime + "に帰ってきました。(Alexaスキルでつぶやいています)";
        Twitter.postTweet(message).then(()=>{
            this.emit(':tell','つぶやきました。<say-as interpret-as="interjection">おかえりなさい。</say-as>');
        },(error)=>{
            console.log(error);
            this.emit(':tell',ErrorMessage);
        })
    },
    'AMAZON.HelpIntent': function () {
        const speechOutput = '<say-as interpret-as="interjection">「いってきます」「ただいま」</say-as>、とつぶやくと、その時間をツイートします。';
        const reprompt = speechOutput;
        this.emit(':ask', speechOutput, reprompt);
    },
    'AMAZON.CancelIntent': function () {
        this.emit(':tell', this.t('STOP_MESSAGE'));
    },
    'AMAZON.StopIntent': function () {
        this.emit(':tell', this.t('STOP_MESSAGE'));
    },
};

exports.handler = function (event, context) {
    const alexa = Alexa.handler(event, context);
    alexa.APP_ID = APP_ID;
    // To enable string internationalization (i18n) features, set a resources object.
    alexa.registerHandlers(handlers);
    alexa.execute();
};

Alexa.handlerthis.emitを実行することでAlexaが喋ります。

ツイッターに投稿するTwitter.postTweetの部分は同梱のtwitter.jsに自作のOAuthプログラムを実装しています。

twitter.js
"use strict";

const AWS = require('aws-sdk');

const https = require('https');
const request = require('request');
const crypto = require('crypto');

const url='https://api.twitter.com/1.1/statuses/update.json';

function postTweet(message) {
    return new Promise((resolve,reject) => {
        const include_entities = {
            status: message,
            // include_entities: true
        };
        const params = {
            oauth_consumer_key: process.env.CONSUMER_KEY,
            oauth_token: process.env.ACCESS_TOKEN,
            oauth_signature_method: 'HMAC-SHA1',
            oauth_timestamp: (() => {
                const date = new Date();
                return Math.floor(date.getTime() / 1000);
            })(),
            oauth_nonce: (() => {
                const date = new Date();
                return date.getTime();
            })(),
            oauth_version: '1.0'
        };

        let auth_params = Object.assign(include_entities,params);

        let encoded_auth_params = Object.keys(auth_params).map(function(key){
            return `${encodeURIComponent(key)}=${encodeURIComponent(this[key])}`;
        },auth_params);
        encoded_auth_params.sort((a,b) => {
            if(a < b) return -1;
            if(a > b) return 1;
            return 0;
        });

        const sigunature_base = `${encodeURIComponent('POST')}&${encodeURIComponent(url)}&${encodeURIComponent(encoded_auth_params.join('&'))}`;

        const keyOfSign = `${encodeURIComponent(process.env.CONSUMER_SECRET)}&${encodeURIComponent(process.env.ACCESS_TOKEN_SECRET)}`;
        const signature = crypto.createHmac('sha1',keyOfSign).update(sigunature_base).digest('base64');
        params.oauth_signature = signature;

        let authorization = Object.keys(params).map(function(key) {
            return `${encodeURIComponent(key)}="${encodeURIComponent(this[key])}"`;
        },params);
        authorization.sort((a,b) => {
            if(a < b) return -1;
            if(a > b) return 1;
            return 0;
        });


        const headers = {
            Authorization: `OAuth ${authorization.join(', ')}`
        };

        const options = {
            url: url+"?status="+encodeURIComponent(message),
            headers: headers
        };

        request.post(options ,(error,response,body) => {
            if(error) {
                reject("reject:"+error);
            } else if(response.statusCode!=200) {
                reject("reject:statusCode="+response.statusCode);
            } else {
                resolve();
            }
        });
    });
};

module.exports.postTweet=postTweet;

require('aws-sdk')を実行することで、process.env.CONSUMER_KEYという形式で、後述するAWS lambdaに設定した環境変数を利用することができます。


続いて**環境変数**を設定します。`CONSUMER_KEY`、`CONSUMER_SECRET`、`ACCESS_TOKEN`、`ACCESS_TOKEN_SECRET`をキーとして、TwitterApplicationManagementで取得したそれぞれの値を設定します。 2018-01-17_23h47_05.png

次に画面上段の「Designer」のトリガーの追加から**「Alexa Skills Kit」**を選択します。
2018-01-17_23h49_04.png

画面最下部に「トリガーの追加」メニューが表示されるので**「追加」**を実行します。
2018-01-17_23h51_09.png

最画面右上の「保存」を実行します。
2018-01-17_23h54_49.png


ここまでで関数の作成は完了です。正常に関数が動作するかをテストします。

「テスト」をクリックすると子画面が表示されるので、**「新しいイベントの作成」を選びイベントテンプレート「Alexa Start Session」**を選択します。
2018-01-17_23h56_09.png

2018-01-17_23h57_34.png

すると何やらコードが表示されます。イベント名に適当な名前を入力し**「作成」**を実行します。

画面に戻り再び「テスト」を実行して**「実行結果:成功」**と表示されれば関数のテストは完了です。
2018-01-18_00h06_23.png

最後に画面右上に表示された**ARNの値をコピーしておきます。**この後の設定に利用します。
2018-01-18_00h10_28.png

Amazon開発者コンソールで仕上げとテスト

関数の作成が終わったらAmazon開発者コンソールに戻ります。

設定メニューの項目エンドポイントで**「AWS LambdaのARN(Amazonリソースネーム)」**を選択します。すると、下の方にデフォルトというフォームが表示されるので、先ほどコピーしたARNを貼り付けて次へ進みます。
20180118202603

テスト画面が表示されるので、ここで最終的な動作確認を行います。この画面では作成したAlexaスキルが起動した状態になっているため、発話サンプルで設定したキーフレーズを入力します。

フォームに**「ただいま」と入力し、「ツイログを呼び出す」**を実行します。
20180119005034

左側にはAlexaへ送信した入力情報、右側にはAlexaが情報を受け取りAWS Lambdaで実行された関数の結果が表示されます。右側のoutputSpeechがAlexaが喋る内容です。ここに想定通りのセリフが表示されていればOKです!

##AmazonEchoに導入
ここまででAlexaスキルの作成は全て完了です。最後は実機へ導入します。

次の画面へ進むと「公開情報」の設定になります。今回は本当に公開するわけではないので、適当に入力します。
20180118203311

20180118203315

20180118203339

最後に「プライバシーとコンプライアンス」の設定です。とりあえず**「いいえ」「輸出コンプライアンス」**にチェックすればOKです。
20180118203559

保存を実行すると画面左側のメニューから**「スキルのベータテスト」**がクリックできるようになります。
20180118203654

**「テスターのメールアドレス」**に自分のアドレスを入力して追加します。他にも使ってもらいたい人がいれば最大500件まで登録できます。

最後に**「テストを開始」**を実行します。
20180118203833

自分のアドレスにメールが届くので、その中のリンクをクリックします。なお日本語環境では2番目の**「JP customer~」リンク**を選択します。
20180118203922

Alexaスキルのページ(アプリ画面)が表示されるので**「スキルテスト」**でスキルを有効化します。
20180118204001

20180118204050

これで全て完了です!実際にAlexaに呼びかけてみましょう。

#次回
今回はACCESS_TOKENとACCESS_TOKEN_SECRETをベタ打ちにしたため、特定のTwitterアカウント専用のスキルとなりました。

この理由はAlexa Skills Kitがサポートする認可方式がOAuth2.0であるのに対して、TwitterAPI(の投稿)はOAuth1.0であるため、単純にアカウントリンクができないためです。

次はそれを解決するために中間の橋渡しをするサーバーを用意して、アカウントリンクバージョンを作ってみます。

こちらに作成記事を公開しました!
https://qiita.com/quotto/items/865326ac1e7aa3ac9c3c

21
14
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
21
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?