Amazon Echo経由でLINE botにメッセージを投げてもらう(3)

Amazon Echo経由でLINE botにメッセージを投げてもらう - ごーちのにっき 

Amazon Echo経由でLINE botにメッセージを投げてもらう(2) - ごーちのにっき

の続きです。

My Alexa Skillと書いているところとLINEについてまとめます。

 

f:id:kmgc:20171225231555j:plain

設定の都合上、LINE側からまとめます。

LINE bot

LINE Developers

上記URLから開発者登録 & botを一つ作りましょう。公式ページにも始め方は書いてますが、例えば次のページなど、いろいろ別のページにもあるので割愛します。。

LINE bot の始め方。

 

botはMessaging APIで作ります。プランはDeveloper Trialです。
「メッセージ送受信設定」でBotグループトーク参加が利用するになっているかを確認します。(今回はグループにbotを参加させてメッセージを配信させる)

f:id:kmgc:20171229200612p:plain

Push配信を実現するためには2個の情報が必要です。

  • アクセストーク
  • 配信するユーザ もしくは グループのid

アクセストークンは「メッセージ送受信設定」で作成するだけです。

一方のidの取得は少し手がかかります。
https://developers.line.me/ja/docs/messaging-api/reference/#common-properties
の「送信元ユーザ」や「送信元グループ」に明記してあるIdによって、どこ(誰)からbotへ発信されたかが特定できるようになっています。Push配信ではこのIdが必要です。
しかし、今回のシナリオでは常にPushするだけなのでグループのIdを特定するタイミングがなさそうに見えます。

結論ですが【グループに参加させる】時にIdを取得しておきます。
botをグループに参加させたときの「参加イベント」のデータ形式を見ると
{ "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", "type": "join", "timestamp": 1462629479859, "source": { "type": "group", "groupId": "C4af4980629..." } }
とあります。このデータが【Webhook先に指定したURLへ送信されるデータ】です。つまりWebhook先に「参加イベント」のデータ形式を受け取れるプログラムを別に作って記録すればOKです。*1

Push指示するAWS Lambda

Javaで作っています。ソースコードは次の通り。
public class LambdaFunctionHandler implements RequestHandler<SNSEvent, String> { 
   public String handleRequest(SNSEvent event, Context context) {
       String message = event.getRecords( ).get(0).getSNS( ).getMessage( );
       TextMessage textMessage = new TextMessage(message);
       PushMessage pushMessage = new PushMessage(
                                         System.getenv("DESTINATION"), textMessage);
       try {
          Response<BotApiResponse> response =
               LineMessagingServiceBuilder.create(System.getenv("ACCESSTOKEN"))
               .build( ).pushMessage(pushMessage).execute( ); 

         System.out.println(response.code( ) + " " + response.message( ));
      }catch(IOException ioe) { ioe.printStackTrace( ); }
      return message;   
    }
}

"DESTINATION", "ACCESSTOKEN"にそれぞれ「GroupID」と「アクセストークン」が入ります。

pomの設定です。

https://developers.line.me/ja/docs/messaging-api/line-bot-sdk/からたどれる
https://github.com/line/line-bot-sdk-javaを参考にpom.xmlの<dependencies>タグ内に3個の<dependency>を追加します。

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.25</version>
</dependency>
<dependency>
    <groupId>com.linecorp.bot</groupId>
    <artifactId>line-bot-model</artifactId>
    <version>1.12.0</version>
</dependency>
<dependency>
    <groupId>com.linecorp.bot</groupId>
    <artifactId>line-bot-api-client</artifactId>
    <version>1.12.0</version>
</dependency>

Lambda Functionを作ったあとアップロードします。このFunctionのARNをSNSの設定に使います。

2つのLambdaを繋ぐSNS

トピックを作りサブスクリプション先(通知先)に、Push配信するLambda FunctionのARNを設定します。また作成したトピックのARNをメッセージを加工するAWS Lambdaに使います。

メッセージを加工するAWS Lambda

Amazon Echo経由でLINE botにメッセージを投げてもらう(2) - ごーちのにっき

にも掲載したNode.jsでSNSに渡す情報の部分だけ再掲します。
var params = {
    Message: 'フレックス'+sign+'でお願いします。',
    Subject: 'hoge',
    TopicArn: 'arn:aws:sns:ap-northeast-1:XXX:flex'
};
TopicArnに、SNSのARNを指定します。

 

まとめ

Alexa以降はAWSの各種サービスを使ったことがあれば比較的簡単です。
まだ使ったことがない方はこれを機にAWS Lambdaだけでも触れてみるといいかもしれません。

 

*1:今回はAPI Gateway + AWS Lambdaを利用してWebhook先を実現しましたが詳細は割愛します

Amazon Echo経由でLINE botにメッセージを投げてもらう(2)

Amazon Echo経由でLINE botにメッセージを投げてもらう - ごーちのにっき
の続きです。

主にAmazon Echo ~ Alexaの部分をまとめます。

f:id:kmgc:20171225231555j:plain 

Alexa Skills Kit(ASK)

ASKでAmazon Echo ~ Alexa ~ AWS Lambdaの流れを定義します。
https://developer.amazon.com/ja/

 

A. developer登録から設定はこのページ。
https://developer.amazon.com/ja/blogs/alexa/post/6e716e5c-55b0-445b-b936-9cfac4712e7b/training-1


B. Alexaの設定項目はこのページ。

https://developer.amazon.com/ja/blogs/alexa/post/07377568-2815-4028-8c21-409dd8e84fa2/alexa-training-jp-2nd

「アレクサ、フレックスで10:20」を受け取れるための設定をします。
「ユーザが発した音声データ」を「どういったルールで受け取るか」という設計をします。具体的な項目は次の通りとしました*1

  • 呼び出し名:フレックス
  • Slot名:time(10:20などが入る変数)
  • 想定する音声データ例:フレックスで9:50

[スキル情報]

スキルの種類:カスタム対話モデル
言語:Japanese
スキル名:MyFLex
呼び出し名:フレックス

[対話モデル]

インテントスキーマ

{
    "intents": [
        {"intent": "AMAZON.HelpIntent"},
        {
        "intent": "FlexIntent",
        "slots": [{
            "name": "time",
            "type": "AMAZON.TIME"
            }]
        }
    ]
}

インテントはユーザの要望を示す概念です。インテントに応じて異なる処理を定義するというモデルになっています。今回の例だと9:50で連絡して!という要望に答える形でFlexIntentという定義をしています。
Slot名はtimeと設計し、かつ時間として解釈させたいのでAMAZON.TIMEというあらかじめ用意されたスロット(ビルトインスロット*2 )を設定しました。
AMAZON.TIMEとして定義したことでAlexaからバックエンド(AWS Lambdaなど)へ渡す値が「HH:mm」という文字列になります。

なお、Intentに注目すると他にもAMAZON.HelpIntentがあります。これもあらかじめ用意されたインテント(ビルトインインテント*3です。理由なく、なんとなく使いました。

サンプル発話

FlexIntent {time}
FlexIntent {time} を 連絡して

設計で決めた音声データのルールを書きます。ユーザがインテントをこう話すだろうという文の構造をいくつか書きます。微妙なところは完璧ではないですが適当に補ってくれる印象です。(この設定だとフレックス「で」9:50 の 「で」の部分などです。)

 

とりあえずの設定はここまでです。残りは次へ次へでASKの設定を終えておきましょう。AWS Lambdaで処理ロジックを表現する場合は、作成したLambda Functionのarnを後で埋め込むだけで終わりです。

Lambda Function

ASKで定義した内容の中ではインテントスキーマがLambda Functionの作りに影響します。具体的には次の2個です。

  • intent
  • slots

Lambda Functionでは「このintentならこの処理」のように書きます。*4
Intentによってはslotsに音声入力値が入っています。今回であれば9:50などの時間を示した文字列です。それをslotsで定義したnameを指定して取得します。

作成したLambda Functionのhandlerは次です。*5

index.js

"use strict";
const Alexa = require('alexa-sdk');

// Lambda関数のメイン処理
exports.handler = function(event, context, callback) {
    var alexa = Alexa.handler(event, context); // Alexa SDKインスタンス生成
    alexa.registerHandlers(handlers); // ハンドラの登録
    alexa.execute(); // インスタンスの実行
};

var handlers = {
    // スキルの使い方を尋ねるインテント
    'AMAZON.HelpIntent': function () {
        this.emit(':tell', 'たとえば、フレックスで9:50と言ってください');
    },
    // 本題のインテント
    'FlexIntent': function () {
        var sign = this.event.request.intent.slots.time.value; // スロットtimeを参照
        var params = { Message: 'フレックス'+sign+'でお願いします。', Subject: 'hoge',
            TopicArn: 'arn:aws:sns:ap-northeast-1:XXXXXXXXXX:flex'
        };

        sendTxtMessage(params, myResult=>{
            this.emit(':tell','メッセージを受け付けました');
        }

    );
},
    'Unhandled': function () {
        this.emit(':ask', 'ask', 'ask more');
    },
};

function sendTxtMessage(params, callback) {
    var AWS = require('aws-sdk');
    AWS.config.update({region: 'ap-northeast-1'});
    var SNS = new AWS.SNS();
    SNS.publish(params, function(err, data){
        if (err) {// error handling
        }
        callback('ok');
    });
}

お決まりの部分(exports.handler=…)は無視して、次に注目します。
'FlexIntent': function () {
    var sign = this.event.request.intent.slots.time.value; // スロットtimeを参照

FlexIntent(=HH:mmで連絡して)に対応した処理で、slots.timeで時間を取得しています。この後は適当に加工するだけです。例えば次のような感じです。

var params = { Message: 'フレックス'+sign+'でお願いします。 //以下略

 

今回の設計ではこのLambda FunctionからAmazon SNSへpublishするため、それを実現しています。これについては次のエントリで。
(12/29 追記。まとめました。)

Amazon Echo経由でLINE botにメッセージを投げてもらう(3) - ごーちのにっき

 

*1:詳細はBのページ。ホント分かりやすい。。

*2:スロットタイプリファレンス | Custom Skills

*3:標準インテントを実装する | Custom Skills

*4:Javaですらまだ試してないので誤りであれば後で修正します

*5:Node.jsの書き方への突っ込みはぜひお願いします。。

Amazon Echo経由でLINE botにメッセージを投げてもらう

背景

Amazon Echoを買ったはいいものの「アレクサ、音楽流して」とか「アレクサ、今日の天気は?」など、ごくありふれた用途でしか使っていませんでした。

Amazon Echoには【Alexa Skill】という拡張機能が用意されています。一般に公開されているSkillだけでなくSkillを自作して組み込むこともできるので、じゃ作ってみるかというのが動機です。

 

なぜLINE botなのか

私の所属する課でLINEのグループを作っています。朝起きた時にメンバーからフレックス出勤の連絡が入ることがあります。もちろん私もします。

ちなみに私は、なんとなくダルイなど、そんな理由で取得します。*1そんなときってLINEを起動して手で入力するのって面倒な訳です。むしろ寝ながらでも申請したいのです。

手でLINEへの入力が面倒  ⇒  声で入力すれば楽かも?  ⇒  完成したら寝ながら連絡できるじゃん!とそんな理由です。

 

設計

まず結論から。こんな感じに仕上げました。*2 

f:id:kmgc:20171225231555j:plain

Alexa関連

https://developer.amazon.com/ja/alexa-skills-kit/training/building-a-skill

すごく丁寧に記述されているのでちゃんと読んで手を動かせば、かなりイメージが掴めるはずです。この記事がなかったら作れなかったと思います。

【メッセージ加工】のLambdaで、Alexaから渡された時間情報を使って、後ろにいるLINE botにpushさせるメッセージを作っています。

 

メッセージ加工~LINE botへのPushまわり

完全に私の周りの都合ですがLINEグループに所属していない人もいます。普段はその人へはSMSで投げています。将来的にはそれぞれに同時に投げたいので、複数の配信先に通知するサービス、つまりSNSを間に挟んでいます。*3

 

LINE bot

https://developers.line.me/ja/

公式ページです。
仕様がかなりわかりやすいのと、言語毎のサンプルを用意してくれているので、ほとんど詰まらず作成できました。コピペで動かせるものってホント素晴らしい。*4

実際にPush指示を出すのは前段のLambdaで実装しています。

 

おわりに

設計などの情報をアウトラインレベルですがまとめてみました。

次回以降のエントリでそれぞれの設定や実装をまとめてみます。
(12/29 追記。まとめました。)

Amazon Echo経由でLINE botにメッセージを投げてもらう(2) - ごーちのにっき

Amazon Echo経由でLINE botにメッセージを投げてもらう(3) - ごーちのにっき

*1:私だけでしょう。

*2:EchoとAlexaは正式画像見つかったら差し替えます

*3:SMS側はまだ未実装です

*4:といいつつもpomは少し手をいれましたが。。

JJUG CCC 2017 fall 参加したセッションまとめ #jjug_ccc

これに行ってきた。

www.java-users.jp

 

結果的にroom Mにずっと張り付き。

この部屋は20分セッションが中心。短時間でいろいろと話題が変わって飽きなかった。(個人的にはこっちの方が好みかもしれない。)

 

M1 サーバサイドでの非同期処理で色々試したよ。(#ccc_m1)

https://docs.google.com/presentation/d/1LKcFzspXUQbq-sivRBPb3Q6V7fQBLKB0zH6BSnGrD_I/edit#slide=id.p

 

リクエスト受付~レスポンス返却 までの流れをすべて非同期化した、という内容。DBアクセス回りを非同期化だけは想像できていたけど、レスポンスを返す部分までも非同期化。

非同期化が名目なので、Future#getを使っていたらマージを拒否するなど、やるなら徹底的に。本当に非同期化できている(かつちゃんと動く)かのコードレビューが超大変そう。。

Daggerを使うことである程度見通し良くできるらしいけど再利用性が低いので、今後はRxJava2を使ってみるらしい。おすすめしていた本↓

Reactive Programming with RxJava - O'Reilly Media

shop.oreilly.com

 

M2 ユニットテストアサーション 流れるようなインターフェースのAssertJを添えて 入門者仕立て (#ccc_m2)

https://www.slideshare.net/RyosukeUchitate/assertj-82260732

 

テスティングフレームワークJUnitよりもいろいろカスタマイズできそうなAssertJの紹介。JDK8を使う場合はAssertJ Core3を使用する。

単一オブジェクトへの検証メソッドは微妙だったけど、コレクションへのそれは割と便利そうな印象。softAssertion、カスタムアサーションなども面白そう。

 

M4 DDD × CQRS - コマンドとクエリでORMを使い分けた話 (#ccc_m4)

https://www.slideshare.net/koichiromatsuoka/ddd-x-cqrs-orm

CQRSなどの詳しい説明は下のリンクから。(講演者のページ)

JJUG2017fall 資料リンク集 - little hands' lab

 

データストアに対するRead/Writeで求める要件が違うから、対応するモデルも別にしよう、という当たり前の発想。確かにそうだよなぁ。

潜在的に1つのORMしか使ってはいけないと思ってしまっていた気がする。

対応する読み書きモデルが変わるから異なるORM使っても問題ないし、
リードレプリカなどの読み書きで異なるデータストアへのアクセスまで考慮するとそっちの方が良いなのかもしれない。

 

M5 劇的改善 CI4時間から5分へ 〜私がやった10のこと〜 (#ccc_m5)

https://www.slideshare.net/aha_oretama/ci-82258405

 

CIシステム(?)の作り自体を変えたのではなく、テストをより効率的に実施できるようにしたという話が中心だった。とはいえ

CIを回すには自動化されたテストが必須 ->テストに時間がかかるとCIのサイクルを何度も回せない ->だったらテストを効率的に!

なので考えれば当然かもしれない。

Googleの提唱するテストサイズというのも初めて知った。

Google Testing Blog: Test Sizes

JUnitの@Categoryアノテーション。恥ずかしながら初見だった。。テストケースに付与することでmavenなどで特定のテストを起動できるように。

 

M6 Docker ではじめる Java EE アプリケーション開発 (#ccc_m6)

https://www.slideshare.net/KoheiSaito2/docker-java-ee-for-jjug-ccc-2017/1

 

最近Dockerは一から勉強しなきゃと思っていたのでタイムリーなセッション。yamlファイルとDockerファイルがあれば手軽に環境作れるってのはやはり大きい。

なお、このセッションは講演者がとても可哀そうだった。。。せめて講演者には安定したネットワーク環境を提供してもらえればなぁという気がした。

 

M7 Stream API の代わりにEclipse Collections を使ってみた話 (#ccc_m7)

http://slides.com/nashcft/jjug_ccc_2017_fall#/

Goldman Sachs の開発した GS Collectionsがベースとなったコレクション。Java SE8のStream APIと比較しての説明だったのでとても分かりやすかった。

同じような動きなのにメソッド名の異なっている理由もプチネタとして知れて良かった。とはいえ正直言うと説明を聞きながら手を動かしたかったセッションw

GitHub - eclipse/eclipse-collections-kata: Collections Kata

 

M8 速いソートアルゴリズムを書こう!! (#ccc_m8)

https://www.slideshare.net/masakazumatsubara1/ss-82272241/masakazumatsubara1/ss-82272241

 

その名の通り高速なソートアルゴリズムを作ってみたというセッション。Arrays#sortよりも高速なものを作り上げていた。

https://github.com/m-matsubara/sort

特に Dual-pivot stable Quick sort(mmsSort)がとても面白かった。CPUキャッシュまでも利用するとは。。3way Marge Sortは…よく作れたなぁと、とても感心してしまった。作れる気がしないw

 

M9 Design Pattern in Presto source code (#ccc_m9)

※スライド見つからず

 

PrestoというOSSソースコードにたくさんのDPが使われているよというお話。

https://prestodb.io/

BuilderやSingletonなど様々なものが使われている中で、このセッションではTemplateとVisitorについて言及していた。

(Templateは処理の大枠と処理の詳細を分離し、処理の詳細だけを追加しやすくなる、Visitorはあるデータ構造に対する処理を別クラスに分離し拡張しやすくする )

Visitorは個人的には結構難しい。もう一回DP本見直してみよう。

 

M10 Javaで使えるもう一つのコンパイル方式 -AOT (#ccc_m10)

https://www.slideshare.net/akihironishikawa/another-compilation-method-in-java-aot-ahead-of-time-compilation-82258865

 

JavaSE9からJIT(Just in Time)だけでなくAOT(Ahead of Time)形式のコンパイルができるように。JITに比べて立ち上げ時間を短縮できる。javac ではなく jaotc コマンド。.soファイルが出力される。(.classファイルの代わり) 

AWS Lambdaなど使うときには有効になるかも、と言っていたがまだAWS LambdaはJava SE8までの対応なので検証はまだ先。

現在は実験段階。ノーサポート、ドキュメント。linux x64 parallel or g1 gc だけなど。

 

M11 DBのTCPプロトコルJDBC (#ccc_m11)

https://docs.google.com/presentation/d/1xSwS73_iTPyMKdtXUUiwawJ05XaS-gFRkOeYLgu8mLI/edit#slide=id.g2a927d1286_0_17

 

講演者も話していたが開発業務にはまったく関係がない内容w

JDBCが色々とラッピングしてくれてホント助かるなぁというのが良く分かる内容だった。PostgreSQL/MySQLのパケット構造がまったく違うことも、なるほどという感じ。

そしてこのセッションでもJDBCの非同期APIについての言及が。今後はちゃんと見ておかないといけないかなぁ。。

 

まとめ

Springはいかなかったので1年ぶりの参加。Javaだけでなく周辺知識も一緒に手に入るのでとても勉強になるなぁと実感。

講演者の皆様お疲れさまでした!

AWS Lambdaで戯れる

Lambdaが面白そうとずっと言い続けていたが、ようやく少し手を出してみた。

備忘録的な意味合いがとても強い。

 

概要

S3 <-> Lambda <-> DynamoDB

動きは次のように。

  1. S3へファイルアップロード
  2. アップロードを機に、登録しておいたLambdaが実行
  3. LambdaはS3のファイル内容をDynamoDBへアップロード

入れものの準備(全部東京リージョン)

S3

バケットを作成。このバケットにファイルをアップロードする。

DynamoDB

テーブルを作成。プライマリキーも適当に。

(テーブル名はtest, プライマリキーはdata_id[int]とする)

 

Lambdaの作成

目標:eclipseでコードを作成して、eclipse上でアップロードする

ざっと手順。

  1. eclipseで「New AWS Lambda Java Project」
    入力タイプは「S3Event」にする。プロジェクト名、パッケージ名などは適当に。
    作成されたプロジェクトにはUnit Testも含まれておりローカルで実行可能。
    EventのJSONデータも見れるので非常に作りやすい。

  2. コードを書く

    aws_sample/S3PutDynamoWriteDemo.java at master · 5chi/aws_sample · GitHub


    S3Eventにはメタ情報が含まれているので、必要なものを抜き出す。例えばバケット名やキー名。ファイルの内容を読み込むためにはInputStreamオブジェクトを作成して読み取る。
    後はリージョンを間違えない。

  3. Functionを登録する
    プロジェクトを右クリック->Amazon Web サービス->Upload Function~を選択。

f:id:kmgc:20151203212312j:plain

  Select AWS Region:S3のバケットのあるリージョンに。
  Select or create a Lambda function:AWSで管理する名前。適当に決める。
  
  

f:id:kmgc:20151203212701j:plain

  説明:適当に。
  ハンドラー:作成したクラスを指定。
  IAM Role:(あらかじめ用意していない限り)作成。
  ここで作られるIAM Roleはログを出力する権限がついたもの。
  S3 バケツw:は入力元になるバケット名を指定。
  拡張設定:デフォルトのまま。
  完了を押すと、アップロードされる。

 

ここまでが個々の準備。ここからは連携のための設定。

 

S3:イベント通知設定

マネジメントコンソールで用意しておいたバケットを右クリック->プロパティ。

イベントの項目を埋める。重要なのはイベントと送信先

f:id:kmgc:20151203214131j:plain

今回はS3でオブジェクトが生成されたら、Lambda関数を実行するように設定。
必要に応じてプレフィックスとサフィックスは追記。

 

 Lambda:IAMポリシーを編集

マネジメントコンソールから、eclipseで作ったIAM Roleを編集。前述のとおり、ログ出力だけなので、次の2点を追記。

 

これで準備完了。

動作確認

S3のバケットにファイルをアップロード。今回はテキストファイルで。

 

ちょっと待つと、DynamoDBのtestテーブルに適当なキー値とファイルの内容が登録される。

 

感想

  • Lambda@Javaは思いのほか簡単に書ける

作ってみたソースコードはS3やDynamoDBなど、微妙に静的な情報が入っているが、外部ファイルに書き出しておけば、色々使いまわせる。
# さらに抽象化して「入力」->「処理ロジック」->「出力」で綺麗にかけるといいなぁ。

  • IAM Roleの設定を忘れない

Lambdaからどのサービスにアクセスするかは意識しておく。

 

サーバを管理しないで、サーバアプリっぽいものがサクッとできるのは非常に気持ちがいいですな。