GASとLINE BOTでお買い物リストを管理する

#やりたかったこと
【ツール開発の背景】
・まだ子供が小さいので、どちらか一方が買い物に行くことが多いが、その時の買いたいものの共有は直前に口頭で行っていた。
→病院など一人で出かけたときに、あそこであれ買っておけばよかったということが多々あった
→後からこれも買ってきてほしかったという買いたいものリストの共有漏れがあった


【なぜこのツールにしたか】
スマホであれば思いついたときに書き留めておける。
ただLINEだと日々のやりとりで共有事項が流れてしまうのでどこかでリスト化したかった

あとは
・単純にGASを学習したかったから
・同じことを行っている人の資料が多々あったので、そこまで工数かけずにできそうだと思ったから

★参考URL
https://qiita.com/WdknWdkn/items/b78ae572e7cb5c9dfdca


#要件
・お買い物リストは「スーパー」「ドラッグストア」「100均」等カテゴリ分けをしたい
・「スーパー」と入力したらそのカテゴリに入っている値をリスト表示したい
・リストからの「追加」と「削除」もLINEBOTから行いたい
・「カテゴリ　商品　追加」「カテゴリ　商品　削除」とLINEに入力するとリストへの追加、削除がしたい
・費用は0円で

#実装
```コード.gs
// スプレッドシートの定義
var SHEET_ID = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
var SHEET_NAME = 'all';
var sheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName(SHEET_NAME);

// LINE Message APIの定義
var ACCESS_TOKEN = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
var PUSH = "https://api.line.me/v2/bot/message/push";
var REPLY = "https://api.line.me/v2/bot/message/reply";
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;
  
  // 検索語に対しての回答をSSから取得
  var answers = findResponseArray(postMsg);
  var splitText = postMsg.split('　');
  var data = sheet.getRange(2, 2, sheet.getLastRow() - 1).getValues();
  var id = splitText[1];

  // 回答メッセージを作成
  var replyText = '「' + postMsg + '」の買い出しリストは以下のものだよ！\n\n追加したい場合は\n'+ postMsg + ' 商品 追加\n\n削除したい場合は\n'+ postMsg + ' 商品 削除\nと入力してね！\n==================\n';
  // 回答の有無に応じて分岐
  if(splitText[2] === '追加'){
    //postMsgに追加の文言があった場合
    //リストから該当商品を探してメッセージを表示
    if (data.flat().includes(id)===true){
      //リストに該当商品があった場合
      sendMessage(replyToken,splitText[1] + 'はすでにリストにあるよ！');
    }else{
      //リストに該当商品がない場合
          sheet.appendRow([splitText[0],splitText[1]]);
          sendMessage(replyToken,splitText[0] +'に' + splitText[1] + 'を追加したよ！');
    };
  }else if(splitText[2] === '削除'){
    //postMsgに削除の文言があった場合
    //リストに該当商品がある場合には文言を返す
    if (data.flat().includes(id)){
      //リストに該当商品があった場合
      //forを回して該当行をマークして行削除する
      var last_row = sheet.getLastRow();
      for(var i = 2; i < last_row + 1; i++){
        var range = sheet.getRange("B"+ i);
        var value = range.getValue();
        if(value === splitText[1]){
          var start_row = i;
          var num_row = 1;
          sheet.deleteRows(start_row, num_row);
          i = i - 1;
        }
      }
      sendMessage(replyToken,splitText[0] +'から' + splitText[1] + 'を削除したよ！');
    }else{
      //リストにすでに商品がない場合
      sendMessage(replyToken,splitText[1] + 'は今リストにないよ!');
    }
  }else if(answers.length !== 0){
    // リスト表示の場合のメッセージ生成
    answers.forEach(function(answer) {
    replyText = replyText + answer.value + "\n";
    });
    // メッセージAPI送信
    sendMessage(replyToken, replyText);
  }else{
    //それ以外の場合
    sendMessage(replyToken, '現在リストに商品がないよ！');
  }
}

// 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]}; });
}

// 単語が一致したセルの回答を配列で返す
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;
  }, []) || [];
}


// 画像形式で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 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);      
}

```

LINEBOTのメニュ―側には以下の画像をセットして、
メニューをクリックした場合にはリスト表示するようにした
![LINEBOT_MENU.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217070/8d360ca2-f58c-6cf8-63fb-9814f6a30333.png)

LINEBOT側
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217070/a7aeeb8f-97e8-b357-9f99-e30b882f95c3.png)

スプレッド側
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217070/94507fdb-fbc7-577a-1ae6-144d2899ea77.png)

#さらに追加したい機能

* 複数行追加機能の実装
* 複数削除機能の実装

→今は配列の3番目の値が追加もしくは削除だったらという条件で組んでいるので
　リストに複数行追加ということができない。なので「スーパー　ひき肉、キャベツ、ニラ　追加」という感じでとりあえず使用している
⇒配列の最後の値が追加ならという条件にすればいけそう

* Alaxaトリガーを取り入れる

→家にはAlexaがあり、今やタイマー機能しか使用していないので、
　スマホからだけじゃなくて、Alexaからもリストに追加できたらなお便利

#最後に
forの回し方とかifの条件分岐とかこっちのほうがいいよ！早いよ！等々あれば是非アドバイスが欲しいです！