超初心者がGoogle Spread SheetでLINE BOTを作ってみた

##はじめに
プログラミングを勉強してから2か月経過した超初心者です。
仕事をしながら空き時間でやってますので、なかなか前進できません。
が、比較的簡単にできるLINEBOTに活用できる技を増やしたい！！！
そんな思いで[【30分でやる】Google Spread Sheetで管理するLINE BOTの作り方](https://qiita.com/WdknWdkn/items/b78ae572e7cb5c9dfdca)を参考に秋にちなんで紅葉に関するLINE BOTをトライしてみました。
##仕様
日本観光振興協会が出されている[観るなび](http://www.nihon-kankou.or.jp/)で提供されている
[紅葉見頃ランキング](http://www.nihon-kankou.or.jp/season/rankingList.jsp?category__=kouyou)の情報を活用して、各名所の見頃や今のランキングを教えてくれるLINE BOTを目標にしました。
詳細は分かりませんが、一般にはAPIが公開されていないようなので今回の題材としてはぴったりです。
##環境
・Windows 10 pro
・Google Spread Sheet
・Google Script
##作り方
下記の2つの記事を参考に作成しています。
ノウハウシェアしていただいている皆様に感謝感謝です。
[【30分でやる】Google Spread Sheetで管理するLINE BOTの作り方](https://qiita.com/WdknWdkn/items/b78ae572e7cb5c9dfdca)
[保育園・小中学校での感染症流行状況がわかる　Line Botの作成](https://qiita.com/doikatsuyuki/items/5939ba28326422c2b9bc)

###Step1.LINE BOT~BOTと友達なる
詳細は[1時間でLINE BOTを作るハンズオン (資料+レポート) in Node学園祭2017 #nodefest](https://qiita.com/n0bisuke/items/ceaa09ef8898bee8369d#bot%E3%81%A8%E5%8F%8B%E9%81%94%E3%81%AB%E3%81%AA%E3%82%8D%E3%81%86)を参照下さい。
BOTアカウントを作成した「LINE developers」の画面でChannel SecretとChannel Access Tokenを確認しておきます。

###Step2.スプレッドシートの準備をします。
スプレッドシートを作成したら、シートを３つ準備します。
各シートに「faq」と「maybe」と「debug」と名前を付けます。
名前を変更すると動かなくなってしまいます。
シートを作成したら、「faq]のシートのA欄に質問、B欄に回答を記載します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/508834/a57d81f9-bdcf-0fd8-ce75-0e3526ad6632.png)
###Step3.スクリプトエディタを開き、下記コードを入力
spread sheetのツールからスクリプトエディタを開き、下記コードを貼り付けます。
Google Spread SheetのシートID（URLの1部）とLINE BOTのアクセストークンを入れます。

```js
// 利用しているシート
var SHEET_ID ='Google Spread Sheetのspreadsheets/d/と/editを入れる';
// 利用しているSSのシート名（※変えるとみえなくなる）
var SHEET_NAME = 'faq';
// 利用しているもしかしてSSのシート名（※変えるとみえなくなる）
var SHEET_NAME_MAYBE = 'maybe';

// LINE Message API アクセストークン
var ACCESS_TOKEN = '「LINE developers」の画面のChannel Access Tokenを入れる';
// 通知URL
var PUSH = "https://api.line.me/v2/bot/message/push";
// リプライ時URL
var REPLY = "https://api.line.me/v2/bot/message/reply";
// プロフィール取得URL
var PROFILE = "https://api.line.me/v2/profile";

/**
 * doPOST
 * POSTリクエストのハンドリング
 */
function doPost(e) {
  var json = JSON.parse(e.postData.contents);
  reply(json);
}

/** 
 * doGet
 * GETリクエストのハンドリング
 */
function doGet(e) {
    return ContentService.createTextOutput("SUCCESS");
}

/** 
 * reply
 * ユーザからのアクションに返信する
 */
function reply(data) {
  // POST情報から必要データを抽出
  var lineUserId = data.events[0].source.userId;
  var postMsg    = data.events[0].message.text;
  var replyToken = data.events[0].replyToken;
  var action    = data.events[0].message.action;
  // 記録用に検索語とuserIdを記録
//  debug(postMsg, lineUserId);
  debug(action, lineUserId);

  // 検索語に対しての回答をSSから取得
  var answers = findResponseArray(postMsg);

  // 回答メッセージを作成
  var replyText = '「' + postMsg + '」ですね。かしこまりました。以下、回答です。';
  // 回答の有無に応じて分岐
  if (answers.length === 0) {
    // 「類似の検索キーワード」がないかチェック
    var mayBeWord = findMaybe(postMsg);
    if (typeof mayBeWord === "undefined") {
      // 回答がない場合の定型文
      sendMessage(replyToken, '答えが見つかりませんでした。別のキーワードで質問してみてください。');        
    } else {
      sendMayBe(replyToken, mayBeWord);
    }
  } else {
    // 回答がある場合のメッセージ生成
    answers.forEach(function(answer) {
      replyText = replyText + "\n\n＝＝＝＝＝＝＝＝＝＝＝＝＝\n\nQ：" + answer.key + "\n\nA：" + answer.value;
    });

    // 1000文字を超える場合は途中で切る
    if (replyText.length > 1000) {
      replyText = replyText.slice(0,1000) + "……\n\n＝＝＝＝＝＝＝＝＝＝＝＝＝\n\n回答文字数オーバーです。詳細に検索キーワードを絞ってください。";
    }
    // メッセージAPI送信
    sendMessage(replyToken, replyText);
  }
}

// SSからデータを取得
function getData() {
  var sheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName(SHEET_NAME);
  var data = sheet.getDataRange().getValues();

  return data.map(function(row) { return {key: row[0], value: row[1], type: row[2]}; });
}

// SSから「もしかして」データを取得
function getMayBeData() {
  var sheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName(SHEET_NAME_MAYBE);
  var data = sheet.getDataRange().getValues();
  return data.map(function(row) { return {key: row[0], value: row[1], type: row[2]}; });
}

// 単語が一致したセルの回答を配列で返す
function findResponseArray(word) {
  // スペース検索用のスペースを半角に統一
  word = word.replace('　',' ');
  // 単語ごとに配列に分割
  var wordArray = word.split(' ');
  return getData().reduce(function(memo, row) {
    // 値が入っているか
    if (row.value) {
      // AND検索ですべての単語を含んでいるか
      var matchCnt = 0;
      wordArray.forEach(function(wordUnit) {
        // 単語を含んでいればtrue
        if (row.key.indexOf(wordUnit) > -1) {
          matchCnt = matchCnt + 1;
        }
      });
      if (wordArray.length === matchCnt) {
        memo.push(row);
      }
    }
    return memo;
  }, []) || [];
}

// 単語が一致したセルの回答を「もしかして」を返す
function findMaybe(word) {
  return getMayBeData().reduce(function(memo, row) { return memo || (row.key === word && row.value); }, false) || undefined;
}

// 画像形式でAPI送信
function sendMessageImage(replyToken, imageUrl) {
  // replyするメッセージの定義
  var postData = {
    "replyToken" : replyToken,
    "messages" : [
      {
        "type": "image",
        "originalContentUrl": imageUrl
      }
    ]
  };
  return postMessage(postData);
}

// LINE messaging apiにJSON形式でデータをPOST
function sendMessage(replyToken, replyText) {  
  // replyするメッセージの定義
  var postData = {
    "replyToken" : replyToken,
    "messages" : [
      {
        "type" : "text",
        "text" : replyText
      }
    ]
  };
  return postMessage(postData);
}

// LINE messaging apiにJSON形式で確認をPOST
function sendMayBe(replyToken, mayBeWord) {  
  // replyするメッセージの定義
  var postData = {
    "replyToken" : replyToken,
    "messages" : [
      {
        "type" : "template",
        "altText" : "もしかして検索キーワードは「" + mayBeWord + "」ですか？",
        "template": {
          "type": "confirm",
          "actions": [
            {
                "type":"postback",
                "label":"はい",
                "data":"action=detail",
            },
            {
                "type": "message",
                "label": "いいえ",
                "text": "いいえ、違います。"
            }
          ],
          "text": "答えが見つかりませんでした。もしかして検索キーワードは「" + mayBeWord + "」ですか？"
        }

      }
    ]
  };
  return postMessage(postData);
}

// LINE messaging apiにJSON形式でデータをPOST
function postMessage(postData) {  
  // リクエストヘッダ
  var headers = {
    "Content-Type" : "application/json; charset=UTF-8",
    "Authorization" : "Bearer " + ACCESS_TOKEN
  };
  // POSTオプション作成
  var options = {
    "method" : "POST",
    "headers" : headers,
    "payload" : JSON.stringify(postData)
  };
  return UrlFetchApp.fetch(REPLY, options);      
}

/** ユーザーのアカウント名を取得
 */
function getUserDisplayName(userId) {
  var url = 'https://api.line.me/v2/bot/profile/' + userId;
  var userProfile = UrlFetchApp.fetch(url,{
    'headers': {
      'Authorization' :  'Bearer ' + ACCESS_TOKEN,
    },
  })
  return JSON.parse(userProfile).displayName;
}

// userIdシートに記載
function lineUserId(userId) {
  var sheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName('userId');
  sheet.appendRow([userId]);
}

// debugシートに値を記載
function debug(text, userId) {
  var sheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName('debug');
  var date = new Date();
  var userName = getUserDisplayName(userId);
  sheet.appendRow([userId, userName, text, Utilities.formatDate( date, 'Asia/Tokyo', 'yyyy-MM-dd HH:mm:ss')]);
}
```
###Step4.WEBアプリケーションとして公開
スクリプトエディタの上部にある公開から「ウェブアプリケーションとして導入」をクリック。
アプリケーションにアクセスできるユーザーを「全員（匿名ユーザーを含む）」を選びましょう。
私は「全ユーザー」を選択し、15分くらい動かなくて悩みました。

###Step.5　LINEアカウントでWebhookの設定
発行された「ウェブアプリケーションのURL」を、LINE developersの「WEBhook URL」に入れます。https://以降をそのままコピペすれば大丈夫です。
##動作確認

![Screenshot_20191111-083822.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/508834/a940dc94-a488-2c07-9aa3-f1d827e9eb30.png)

##感想
少し迷子になった時間を入れても掛かった時間は45分程度。
出来た瞬間「Wow」と何度も何度も頭の中で言ってしまいました。
いや、すいません。何も自分でコードは書いておりません。
ひたすら先人の知恵です。
で、でも出来ちゃうなんて本当にすごいすごいすごい！！！
調べもの系のLINE BOTさんなら、こちらで対応できそうなので私のような超初心者さんには本当にお勧めです。コードが上手くいかない時に試してもらえると、出来た喜びでまた頑張れるかも！？
改めて、この再現性の高さ。プログラミングって、と～っても難しいけど、一度誰かが成功するとたくさんの人に役に立ちますね。あ～、ありがたや～。