星とか面白いこととか

暇なのでいろいろ書きます

星とか面白いこととか

スポンサーリンク

【LINE BOT作ってみた】GoogleAppScriptを使っていい感じに返信してくれるLINEBOTを作ってみた

スポンサーリンク

スポンサーリンク

こんにちは。まりもです。

 

GoogleAppScriptを使っていい感じに返信してくれるLINEBOTを作ったので、作り方を公開します(主にソースコードを載せるだけ)

必要な環境など

・Googleアカウント(たぶんみんな持ってる)

・LINE developersアカウント(10分あれば作れます)

・PC(Windows/Macどちらでも)

 

作成にかかった時間

2時間

※大枠の実装1時間 + 返信用のデータ登録1時間くらい

 

作成の経緯・ロジックとか

諸事情で、友人とのラインのログをテキストに落としていろいろ作業しているときに思いつきました。

「2年分のLINEの発言のログをデータとして格納してやって、その手札を使えばユーザーの発言に対していい感じの返答を返すことができるbotが作れるのでは??」

って感じです。

もうすこし詳しく

友人をA、僕をBとします。

A:ラーメン食べたい B:わかる

A:でんわしよ    B:いいよ

A:男の人は一生中二病だと思う B:それはある

こんな感じのなんでもない会話を記録しておいて、LINEBOTに向かって投げられた発言(strとします)と、格納されているAをすべて比べて、strと最も近いAを特定します。

特定したAに紐づくBを、LINEBOTから返してやればいい感じに返事が帰って来たふうに見えるのでは、という感じです。

上記の例では、3会話しかありませんが、実際は2年分の会話のすべてを格納します(テキストで2万行くらいでした)

LINEBOTに投げられた発言と最も近いAの特定方法

これはだいぶ迷いましたが、機械学習とかAIを実装できる気はしなかったので、レーベンシュタイン距離という考え方を使うことにしました。

レーベンシュタイン距離は、二つの文字列がどの程度異なっているかを示す距離の一種である。編集距離とも呼ばれる。具体的には、1文字の挿入・削除・置換によって、一方の文字列をもう一方の文字列に変形するのに必要な手順の最小回数として定義される。

レーベンシュタイン距離 - Wikipedia

 ここではレーベンシュタイン距離の詳しい説明は省略します(数学とかそっちらへんの話になってしまうので)

いよいよ実装

BOTを作るための諸々の手続き、どこにコーディングすんの? みたいなのは説明省略します。

【LINE Botの作り方】Messaging API × GAS(Google Apps Script)でおうむ返しボットを作成する|TAKEIHO

https://www.takeiho.com/messaging-api-gas

 こちらの方のブログを参考にしました。非常にわかりやすくておすすめです。

リンクを参考にGoogleAppScriptを書くぞ! って部分まで環境を整えてからこの下を読むといいかもしれないです。

 

コード.gs

// xxx部分をChannel Access Token => ISSUE で発行された文字列に置き換える
var channel_access_token = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; 

function doPost(e) {
  var posted_json = JSON.parse(e.postData.contents);
  var events = posted_json.events;
   //送られたLINEメッセージを取得
  var json = JSON.parse(e.postData.contents);
  var user_message = json.events[0].message.text;  

  //送られて来た種類
  var receive_message_type = json.events[0].message.type;
  events.forEach(function(event) {
   //発言A ArrayMessage[i]とArrayResponse[i]は対になるように登録してね
    var ArrayMessage = ['明日が本番だから','酔ってると八方美人やめれる','焼きあごラーメン','秋の四角形みたいなやつが同時に見えた','星めっちゃ見た','なにたべるかまじでまよう','とりあえずこたつ買って電車乗っとこ','ちょっと盛った','夏終わる','しおり作りたい','くそかわいい','東京湿度高すぎ','帰ってる','後でまた連絡するね','まじ寝坊しないか不安','7時集合','美味しすぎた','たのしい','とおくね?','1分くらい待って','でんわしよ','いま家?','どへんたいだな','気が向いたらもってくる','でんぱくそわるい','こんどいこ','秋の四角形みたいなやつが同時に見えた'];

   //発言B  発言Aと発言Bは好きな会話を入れてね。ここにあるのは筆者と筆者の友人の会話の一部だよ
    var ArrayResponse = ['気合い入れないと','常に酔ってれば勝てる','たべたい','秋の四辺形わかるの強すぎ','羨ましすぎ','ラーメンが肉か定めよう','そして夜行バス','盛りすぎ','夏らしいことやってないわ','旅行の時しおり作る派だわ','かわいい','まじで暑い','おつ','まってる','モニコするわ','早い','うらやま','いいなあ','遠いわ','まつわ','おけまる水産','家なう','ばれた','#持ってこなそう','無線LAN繋がらなさそう','いこ','秋の四辺形わかるの強すぎ'];

     //レーベンシュタイン距離 
    //ユーザーが入力したテキストと、ArrayMessageのレーベンシュタイン距離を比較。最も小さいものの返答を出力
    var minlevenshtein  = levenshtein(user_message, ArrayMessage[0]);
    var min_i = 0;

    for(var i=1;i<ArrayMessage.length;i++){
      if (levenshtein(user_message, ArrayMessage[i]) < minlevenshtein){
        minlevenshtein = levenshtein(user_message, ArrayMessage[i]); //最小のレーベンシュタイン距離更新
        min_i = i;
      }
    }
     var postData = {
      "replyToken" :event.replyToken,
      "messages" : [
        {
          "type" : "text",
          "text" :  ArrayResponse[min_i]
                         }
      ]
    };
    var options = {
      "method" : "post",
      "headers" : {
        "Content-Type" : "application/json",
        "Authorization" : "Bearer " + channel_access_token
      },
      "payload" : JSON.stringify(postData)
    };
    var reply = UrlFetchApp.fetch("https://api.line.me/v2/bot/message/reply", options);
  });
};

//レーベンシュタイン距離
function levenshtein (s1, s2) {
    // http://kevin.vanzonneveld.net
    // +            original by: Carlos R. L. Rodrigues (http://www.jsfromhell.com)
    // +            bugfixed by: Onno Marsman
    // +             revised by: Andrea Giammarchi (http://webreflection.blogspot.com)
    // + reimplemented by: Brett Zamir (http://brett-zamir.me)
    // + reimplemented by: Alexander M Beedie
    // *                example 1: levenshtein('Kevin van Zonneveld', 'Kevin van Sommeveld');
    // *                returns 1: 3

    if (s1 == s2) {
        return 0;
    }

    var s1_len = s1.length;
    var s2_len = s2.length;
    if (s1_len === 0) {
      return s2_len;
    }

    if (s2_len === 0) {
        return s1_len;
    }

    // BEGIN STATIC
    var split = false;
    try{
        split=!('0')[0];
    } catch (e){
        split=true; // Earlier IE may not support access by string index
    }

    // END STATIC
    if (split){
        s1 = s1.split('');
        s2 = s2.split('');
    }

    var v0 = new Array(s1_len+1);
    var v1 = new Array(s1_len+1);
    var s1_idx=0, s2_idx=0, cost=0;

    for (s1_idx=0; s1_idx<s1_len+1; s1_idx++) {
       v0[s1_idx] = s1_idx;
    }

    var char_s1='', char_s2='';
    for (s2_idx=1; s2_idx<=s2_len; s2_idx++) {
        v1[0] = s2_idx;
        char_s2 = s2[s2_idx - 1];

        for (s1_idx=0; s1_idx<s1_len;s1_idx++) {
            char_s1 = s1[s1_idx];
            cost = (char_s1 == char_s2) ? 0 : 1;
            var m_min = v0[s1_idx+1] + 1;
            var b = v1[s1_idx] + 1;
            var c = v0[s1_idx] + cost;

            if (b < m_min) {
                m_min = b; }
            if (c < m_min) {
               m_min = c; }
            v1[s1_idx+1] = m_min;
        }

        var v_tmp = v0;
        v0 = v1;
        v1 = v_tmp;
    }

    return v0[s1_len];

}

上記のコードのうち、2行目のトークンを書き換えてください。最低限それで動きます。

ソース上の方にある鬼みたいな配列は、発言Aと発言Bです。最終的には6000アイテムくらい入れる予定なので、配列の要素は外付けで持つように拡張する予定です(このままベタで書いてもソースが汚いだけで普通に動きます)

なお、スタンプと画像が送られて来た際の処理は未実装です。(テキスト以外が送られた処理は非常に面倒っぽかったので)

 

動かしてみた

f:id:Marimofmof:20180820214931p:plain

格納しているデータが少ないこともあり、怪しい箇所もありますがまあまあいい感じに返信してくれてます(返信しやすいようなメッセージを送ったのもありますが)

ちなみに、アイコンがキズナアイちゃんなのは「AI返信したかった(技術が足りなくてできなかった)」という悲しい事情からです。 

 

まとめ

 機械学習もAIも使わずにレーベンシュタイン距離でいい感じの返答ができるLINEBOTを作ることができました。

LINEでの発言をLINEBOTに使うって、なんかエモさがあって楽しかったです。

最後に、今回作成したアカウントを載せておくのでよかったら友達登録してね!!

 

f:id:Marimofmof:20180820215316j:image

 f:id:Marimofmof:20180820214935p:plain