おじさんだって、女子高生風LINE BOTを作ってもいいじゃないか（３）～Google Apps ScriptとLINE Messaging APIによるLINE ボット開発日記～

<a href="http://qiita.com/ponjopi/items/652cf001ef34a3a9eca6" target="_blank">前回・前々回と、元インフラSEだった私が偶然にもLINE BOTのことを知り、久しぶりに３日間ほどプログラミングをした結果、みごと自分専用LINE Botを手に入れた経緯をお伝えした。</a>

私の稚拙な記事を読んでいただき、LINE Bot「りの」に登録いただいた方、また誤字の指摘をいただいた方など、何の見返りもないのにご親切な対応をいただいたことにお礼を申し上げたい。

さて...

大晦日と正月を費やして開発した関西女子高生風・雑談ボット「りの」は、私の個人的な趣味趣向で、様々な機能を増やしながら、現在も活発に（？）活動中である（LINE ID: @lmj1644w または下記QRコード）。

![pics.jpg](https://qiita-image-store.s3.amazonaws.com/0/157507/b852ef5a-3bbe-0a9c-2cb7-bbddb90643a4.jpeg)

現時点では、マイクロソフトのCognitive Service APIを利用して、LINEで送られてきた画像から性別と年齢を当てるという機能まで実装しているのだが、その機能については機会とご要望があれば、また改めてご紹介しよう。

興味がある方は、わたしのLINE Bot「りの」に友達申請していただき、メニューから「あんたの年齢、当てよか」という項目を選んでみていただきたい。あなたの性別と年齢を予想する機能を確認していただける。

今回はLINE Botを作る上では避けて通れない（？）、スタンプまわりの対応について考えてみたい。チャットボットというと、テキストメッセージの対応が主に扱われているが、LINEでのチャットを考えると、スタンプのやりとりに対応できるというのは重要なポイントだろう（と思うのは私だけだろうか）。

今回の投稿では、LINE Messaging APIを使って、以下のようなアイデアを実現する例を紹介したい。

* チャット相手からスタンプが送られてきた場合に識別して、テキストメッセージとは違う処理に振り分ける
* 相手から送られてきたスタンプに応じて、なんとなく意味のやり取りが成立しているように見えるスタンプを送り返す。
* こんなスタンプが送られて来たらこのスタンプを返す、という「スタンプ会話シナリオ」をGoogle Spreadsheetに保存しておいて、コード修正なしで柔軟に追加・修正できるようにする


これまでどおり、開発環境やコーディングの説明はGoogle Apps Scriptでおこなっているが、他の言語に精通した方なら、カンタンに読み替えていただくことができると思う（この投稿に、それだけの価値があることを祈るばかりである）。

あなたがLINE Botを制作・カスタマイズする際のヒントになれば、耄碌（もうろく）したおっさんエンジニアとして、これ以上の幸せはない。


## LINE Messaging APIでスタンプを取り扱う方法
私のLINE Bot「りの」には、いろいろと余計な機能が加わっているが、LINE Messaging API自体でのスタンプの取り扱いは非常に単純だ。

これまでテキストメッセージを処理していた時と同じく、LINEから送られてくるWebhook Event Objectのイベントタイプを識別して、messageタイプだった場合に更にそのメッセージタイプを調べる。

それがstickerタイプだった場合、チャット相手のユーザからスタンプが送られてきたということになる。

~~~ javascript
// Messageタイプにより、処理を分岐する（follow, message）
switch(JSON.parse(e.postData.contents).events[0].type){

          case 'message':  // messageの場合

// テキストメッセージの場合
          if (JSON.parse(e.postData.contents).events[0].message.type == 'text'){

      // スタンプを送ってきた場合
          } else if (JSON.parse(e.postData.contents).events[0].message.type == 'sticker'){
~~~


【参考】 LINE Messaging APIリファレンス
https://devdocs.line.me/ja/#webhook-event-object



## スタンプの種類を識別して、処理を振り分ける方法
Qiita読者の方々に対して、この部分は「蛇足」というか「釈迦に説法」という気もするが一応、書いておく。上掲のリファレンスを読んで「わかってしまった」方は、飛ばし読みしていただきたい。

LINEからJSON型式で返されるWebhook Event Objectの「packageId」「stickerId」という２つのパラメータによって、スタンプを特定することができる。

~~~ javascript
} else if (JSON.parse(e.postData.contents).events[0].message.type == 'sticker'){
    // packageId, stickerIdの取得
    var packageId = JSON.parse(e.postData.contents).events[0].message.packageId;
    var stickerId = JSON.parse(e.postData.contents).events[0].message.stickerId;
~~~

通常、LINEスタンプは40パターンで１パッケージとして販売されている。packageIdはそのスタンプパッケージにあたり、stickerIdがひとつひとつのスタンプを表す。packageIdとstickerIdの組み合わせで、LINEストアで販売されているすべてのスタンプは表現できるわけだ。

LINE Botでスタンプに対応する方法として、一番カンタンなのは「スタンプがきたら無視する」つまり、「sticker」メッセージを拾わなければいいわけだが、それではちと寂しいという場合には「何か」を固定で返せばいい。たとえばスタンプとかテキストメッセージとか。

相手から送られてくるスタンプはすべて識別可能だが、悲しいことにLINE Botからユーザに送るスタンプは「標準スタンプ」に限定される。何が「標準」かという話だが、LINE登録すると初期状態から使える「デフォルト・スタンプ」が標準ということなのだろう（packageIdが 1～4のスタンプ）。将来的に、LINEストアで販売されている他のスタンプも送信できるようになればいいのだが。

ちなみにBot側から送れるスタンプの一覧は、以下に掲載されている。

【参考】LINE Sticker一覧
https://devdocs.line.me/files/sticker_list.pdf

「標準スタンプ」に限定されて寂しいが、Botからスタンプをユーザに送るには、Google Apps Scriptでは、これまでのテキストメッセージの場合と同じく、UrlFetchApp.fetchメソッドを使って、LINE APIのエンドポイントにメッセージを送ればいい。違いは、メッセージテキストの代わりにスタンプのpackageIdとstickerIdを指定する点だけ。

~~~ javascript
  UrlFetchApp.fetch(line_endpoint, {
    'headers': {
      'Content-Type': 'application/json; charset=UTF-8',
      'Authorization': 'Bearer ' + CHANNEL_ACCESS_TOKEN,
    },
    'method': 'post',
    'payload': JSON.stringify({
      'replyToken': reply_token,
      'messages': [
        {'type': 'sticker', 
         'packageId': pkgNo,	// パッケージidを指定
         'stickerId': strNo}],	// スタンプidを指定
    }),
  });  
~~~

これで相手にスタンプを送ることはできるが、毎回同じスタンプを送るのも芸がない…と、どうでもいいところが気になりだした。

こちらから送るスタンプを毎回変えるには、乱数を使って無作為にいくつかのスタンプから選ぶとか、外部にカウンターなどを持って、いくつかのスタンプを順番に回しながら送るといった方法もあると思う。

しかし、できれば相手から送られてくるスタンプにマッチしたスタンプを返したい。たとえば、ボケているスタンプが送られてきたら、ツッコミのスタンプを返したいわけだ。

そこで思いついたのが、ユーザから送られてくるpackageIdとstikcerIdをGoogle Spreadsheetに記録しておく、というアナログな方法だった。後でそのスタンプを確認して、ピッタリの「お返しスタンプ」を見つけて「スタンプ会話シナリオ」にどんどん登録していくわけだ。

スタンプには流行り廃れがあるだろうし、LINE Botにあまり友達が少ないと、サンプリング数が足りなくて「偏ったシナリオ」ができてしまいそうだが… まあ、それはそれとして、今どんなスタンプが流行っているか、なんてこともわかりそうで面白い。

機械学習や Recommendation APIなどを使えば、この「スタンプ会話シナリオ」を自動的に追加していくことができるかもしれないが、そのレベルになるとサンデープログラマーの私には少々荷が重い。ゴールデンウィークあたりの大型連休向け「野望」として取っておくことにした。

どなたか「LINEスタンプ自然会話API」やWebサービスを開発されたら、ぜひ利用させていただきたい。


## 「スタンプ会話シナリオ」でスタンプのやりとりを定義する
Google にはGoogle Spreadsheetという便利なものが用意されていて、しかもGoogle Apps Scriptからオブジェクトとしてカンタンにアクセスすることができる。

Spreadsheetオブジェクトに関するデータは、グローバル変数として読み込んでしまうことにした。

~~~ javascript
//変数spreadsheetにログ用スプレッドシートオブジェクトを取得します
var stickerLogUrl = "https://docs.google.com/spreadsheets/xxxxxx "; 

//「スタンプ会話シナリオ」シートのURL
var stickerMasterUrl = "https://docs.google.com/spreadsheets/zzzzzzz";

var stickerReplyMasterFile = SpreadsheetApp.openByUrl(stickerMasterUrl).getSheetByName("スタンプ別処理定義"); 　// 「スタンプ会話シナリオ」シートの定義

// スタンプ別処理定義を配列に読み込み
var stickerDefValues = stickerReplyMasterFile.getDataRange().getValues();
~~~

まずは、ユーザから受け取ったスタンプにどんなスタンプを返すか、という組み合わせを登録しておくシートだが… 以下のようにデータを入力してみた。

|Pkg-id (Rcv)|Stk-id(Rcv)|Pkg-id (Snd)| Stk-id(Snd)| 
|:--|:--|:--|:--|
|1|407|2|19|
|1|413|2|25|

左側の２列(Rcv)がユーザから受け取るスタンプ情報で、右側２列が返信メッセージのパラメータである。

たとえば packageId=1, stickerId=407のように勘違いしてスカしたスタンプには、packageId=2, stickerId=19 のようなスタンプを返したい。
![Stamp.jpg](https://qiita-image-store.s3.amazonaws.com/0/157507/c49e8a05-54d9-c959-7bab-6716df2049a0.jpeg)

ユーザからスタンプが送られてきたら、配列に読み込んでおいた「スタンプ会話シナリオ」の先頭２列を検索して、packageIdとstickerIdがヒットしたら、対応するスタンプを返信する。

「シナリオ」シートに登録した情報は、Google SpreadsheetをSpreadsheetAppオブジェクトのgetDataRange()メソッド、getValues()メソッドを使用して取得する。

getDataRange()はシートの中でデータが入力されている部分のセル全体を選択し、getValues()はその選択されたセル領域のデータを二次元配列で返す。

~~~ javascript
stickerDefValues[i][j]  	// [i] がシートの行番号、[j] が列番号。０から開始。
~~~

以下はサンプル。

~~~ javascript
// This logs the spreadsheet in CSV format with a trailing comma
// i=行、j=列
for (var i = 0; i < stickerDefValues.length; i++) {
        
     // マスター内に 送信されたスタンプのidが見つかった場合
     if ((stickerDefValues[i][0] == package_id) && (stickerDefValues[i][1] == sticker_id)) {
      
        // スタンプ情報の設定
           var packageNo = stickerDefValues[i][2];
           var stickerNo = stickerDefValues[i][3];

           //スタンプを送る
           SendStickerToLINE(reply_token, packageNo, stickerNo);
                    
           }
     };
}
~~~

アクセス数が増えてくれば、レスポンス問題が発生する可能性があるが、現時点では十分に機能しているようだ。

私のLINE Bot「りの」には、この「スタンプ会話シナリオ」機能を実装してあるので、興味がある方は、ぜひお試しいただきたい。

現時点では「シナリオ」登録データが少ないが、たとえば、以下のスタンプを送ってみていただきたい。ちゃんと登録されたスタンプを返すはずだ。

![stamp01.jpg](https://qiita-image-store.s3.amazonaws.com/0/157507/063e3200-0094-ee7b-92b3-500ebf88dda2.jpeg)

さて、これで「スタンプ会話シナリオ」はできた。このシートにスタンプ情報を登録しておけば、頻繁に送られてくるスタンプに関しては、それぞれ対応するスタンプを送り返すことができる。

あとは、相手から送られてきたスタンプ情報のログをとる処理をコーディングして、どんなスタンプが送られてくるのかを調べれば、「スタンプ会話シナリオ」にどんどんデータを登録していくことができる。

ユーザから送られてきたスタンプ情報を、Google Spreadsheetに記録する処理は非常にカンタンだ。

~~~ javascript
           // スタンプ履歴をロギング
               stickerLog.appendRow([userid, packageId, stickerId ,Date()]);
~~~

appendRowメソッドは、Spreadsheetの最終行を勝手に探してきて、データを末尾に追加してくれるという、信じられないほど便利なものだ。

もちろん、これはGoogle Apps ScriptからGoogle Spreadsheetを呼び出す場合なので、外部データベースを利用したり、Google Apps Script以外の言語で開発することもできる。


…と、今回の投稿後半は少々、個人的な趣味とこだわりに走り過ぎたきらいがある… かな。

少々、反省するとして、次回はLINEから画像を人物の顔写真を取得して、Microsoft Cognitive Service APIで性別や年齢を判断するコーディング例を紹介しようと思っている。懲りずにおつきあいいただければ幸いだ。

さて。

LINE Bot制作をはじめてから、ＩＴエンジニア同士でしか理解できない「ある特殊な共感」を久しぶりに思い出すことができたような気がしている。図々しいかもしれないが、似たような環境・境遇で、ときには肉体的・精神的な苦しみに耐えながら、似たような目標や夢を求めた人たちとしか通じ合えない「あの感覚」を、定年退職まであと十年を切った今になって思い出すことができて、私は幸せだと思う。

まだLinuxが登場する前、何百万もするUnixマシンやOracleデータベースに触れることが嬉しくて、顧客先で徹夜を続けたことを思い出す。現場を離れて15年以上経つが、今こそ、あの頃夢見ていた「自宅で自分の趣味として、納期も品質も気にすることなく、何百万円・何千万円もするシステムを好き勝手に触れる」時代が到来していると知り、嬉しいことこの上ない一方で、個人的なこの「失われた15年」が惜しまれるばかりだ。現場を離れてからも、コンピューターを触り続けていればよかった…　

いかん。歳を取ると愚痴が多くなって、余計なことを書いてしまう（苦笑）。

ともあれ、私のように昔ＳＥをしていたが、最近しばらくマシンに触れていないという人でも、LINE Bot制作なら今から十分に楽しめると思う。クラウドサービスが普及した現在、アプリケーションに必要とされるほとんどの機能がWebサービスとして公開され、それらを自由に「つなげる」ことで非常に短期間でカンタンにアプリを開発することができる。しかも誰もが使っているＳＮＳをプラットフォームとして利用できるので、インフラ構築や運用管理も要らない。プロマネに疲れてしまった人や、納期と仕様変更に辟易しているシニアエンジニアの方には、気分転換としてもオススメである（まあ、そんな場合はさすがに、プライベートでまでコンピューターを触るのはイヤかもしれないが）。