鍵管理システムを作ったら褒められた話

#作りたいもの
筆者の所属団体で、鍵の借用時と返却時にボタンを押すことでログが残り、任意のタイミングで利用状況を確認できるシステム
##今まで
鍵の管理用のLINEグループでいちいち借用返却報告をしてた
→通知うざいっす
#使ったもの
プラットフォームは今まで通りLINEで
→MessagingAPIとGASでLINE bot
#概要
LINE botにキーワードを送信するとGASを動かしてspreadsheetに入力
確認用のキーワードを送信するとspreadsheetを参照して利用状況を返信してくれる
![IMG_5131.jpg](https://qiita-image-store.s3.amazonaws.com/0/205561/33b1a652-d8d5-0b81-286b-bc91f68779dd.jpeg)
##MessagingAPIの登録
たくさん投稿があると思うので割愛
[LINE Developers](https://developers.line.biz/ja/)
RichMenuを使うことでユーザーにキーワードをミスなく入力させる事ができる。
アクセストークンを控えておく。

##GAS
新しいGASのファイルとlog用のspreadsheetを作る。
作ったGASファイルのリンクをLINE Developersの方でwebhookに貼り付ける。

```javascript:key.gs
var CHANNEL_ACCESS_TOKEN = '***LINE ACCESS TOKEN***';
var url = "***SHEET URL***";
var spreadsheet = SpreadsheetApp.openByUrl(url);

var sheets = spreadsheet.getSheets(); //log用のシート

function getUserName(UID){//Userの表示名取得
  var url = 'https://api.line.me/v2/bot/profile/' + UID;
  try {//エラー処理
    var response = UrlFetchApp.fetch(url,{
      'headers': {
      'Content-Type': 'application/json; charset=UTF-8',
      'Authorization': 'Bearer ' + CHANNEL_ACCESS_TOKEN,
      }
    })
  }catch(e){//表示名を取得できなかったら
    return 0;
  }
 var json = JSON.parse(response.getContentText());
 return json["displayName"]
}


function makeMsg(array){//返信用メッセージを作るやつ
  var result=[];
  for(var i=0;i<array.length;i++){
    if (array[i] !== "closed"){
      var temp = array[i].split(',');
      result[i]=temp[1]+"\n"+temp[0];
    } else {
      result[i]=array[i];
    }
  }

  var msg = "利用状況\n"+
            "*************\n"+
            "ボックス:" + result[0] + "\n"+
            "会議室 : " + result[1] +"\n"+
            "2北 : " + result[2];
  return msg;
}



function sendMsg(reply_token,message){ //メッセージ送信
  var url = 'https://api.line.me/v2/bot/message/reply';
  UrlFetchApp.fetch(url, {
    'headers': {
      'Content-Type': 'application/json; charset=UTF-8',
      'Authorization': 'Bearer ' + CHANNEL_ACCESS_TOKEN,
    },
    'method': 'post',
    'payload': JSON.stringify({
      'replyToken': reply_token,
      'messages': [{
        'type': 'text',
        'text': message,
      }],
    }),
  });

}



function deleteRows(sheets){ //行削除(なくてもいい)
    for(var i in sheets){
        var LastRow = sheets[i].getLastRow()
        if(LastRow > 6) {
          sheets[i].deleteRow(2)
        }
    }
}

function updateSheet(room,key,value){ //sheetの操作
   if (key == 'take'){

    var data = sheets[room].getDataRange().getValues();
    var lastrow = data.length;
    if(data[lastrow-1][1]==""){
      lastrow = lastrow - 1;
    }
    sheets[room].getRange(lastrow + 1, 1).setValue(value);

  } else if (key == 'return'){

    var lastrow = sheets[room].getDataRange().getLastRow();
    sheets[room].getRange(lastrow, 2).setValue(value);
  }

}


function doPost(e) { //イベントがあったら動く関数

  var date = new Date();
  var event_data = JSON.parse(e.postData.contents).events[0]
  var reply_token= event_data.replyToken;

  if (typeof reply_token === 'undefined') {
    return;
  }

  var message = JSON.parse(e.postData.contents).events[0].message.text.split('-');
  var user_id = JSON.parse(e.postData.contents).events[0].source.userId;
  var user_name = getUserName(user_id);
  var value = Utilities.formatDate( date, 'Asia/Tokyo', 'yyyy/MM/dd HH:mm') + "," + user_name + "," + user_id;
  var msg = "OK!";

  if (message[0] == "box"){

    updateSheet(0,message[1],value)

  } else if (message[0] == "meeting"){

    updateSheet(1,message[1],value)

  } else if (message[0] == "warehouse"){

    updateSheet(2,message[1],value)

  } else if (message[0]=='ログ'){

    msg = url;

  } else if (message[0]=='id'){

    msg = user_id;

  } else if (message[0]=='かぎ'){

    var data_list = [];
    for(var i in sheets){
      var sheet_data = sheets[i].getDataRange().getValues();
      var lastrow = sheet_data.length;
      if(sheet_data[lastrow-1][1]==""){
        data_list[i] = sheet_data[lastrow-1][0];
      } else{
        data_list[i] = "closed";
      }
    }
    msg=makeMsg(data_list);

  } else if(message[0] == 'ヘルプ'){

    msg = "";

  } else {
    msg = "I'm sorry. I can't reply.";

  }

  deleteRows(sheets);
  sendMsg(reply_token,msg);

  return ContentService.createTextOutput(JSON.stringify({'content': 'post ok'})).setMimeType(ContentService.MimeType.JSON);

}



function remind(){//リマインド関数

  var status_dict = {box:"", meeting:"", warehouse:""};
  var key = Object.keys(status_dict);
  for(var i in sheets){
    var sheet_data = sheets[i].getDataRange().getValues();
    var lastrow = sheet_data.length;
    if(sheet_data[lastrow-1][1]==""){
      status_dict[key[i]] = sheet_data[lastrow-1][0].split(',')[2];
    } else{
      status_dict[key[i]] = "";
    }
  }
  for(var key in status_dict){
    var text = remindMsg(key);
    if(!(text =="")){
      push(status_dict[key],text);
    }
  }
}



function remindMsg(key){//セリフ作成
  var text = "";
  if(key =="box"){
    text = "ボックス";
  } else if(key =="meeting") {
    text = "会議室";
  } else if(key =="warehouse") {
    text = "2北";
  }
  return text + "の鍵は返却しましたか？返却済でしたら返却ボタンを押下してください。";
}



function push(user,text){//PUSHメッセージ送信
  var url = "https://api.line.me/v2/bot/message/push";
  var headers = {
    "Content-Type" : "application/json; charset=UTF-8",
    'Authorization': 'Bearer ' + CHANNEL_ACCESS_TOKEN,
  };
  var postData = {
    "to" : user,
    "messages" : [
      {
        'type':'text',
        'text':text,
      }
    ]
  };
  var options = {
    "method" : "post",
    "headers" : headers,
    "payload" : JSON.stringify(postData),
    "muteHttpExceptions": true
  };

  //return UrlFetchApp.fetch(url, options);
  var response = UrlFetchApp.fetch(url, options);
}
```

参考:
[LINE Botをサーバーレスで開発！Google Apps ScriptとLINE Messaging APIを使ってチャットボットを作ってみた](https://devpixiv.hatenablog.com/entry/2016/11/14/150000)
[GASでSpreadsheetを操作する自分的ベストプラクティス](https://qiita.com/ryan5500/items/e72eb205fbe006c2eb6f)
[transpose 2-dimensional Javascript array in one line](http://www.codesuck.com/2012/02/transpose-javascript-array-in-one-line.html)

#完成品
借りたり返したり
<img width="375" alt="IMG_5138 2.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/205561/684965d2-8ed2-490c-4e6f-a961960df6f1.png">
開いてたり閉まってたり
<img width="375" alt="LINE_capture_564165717.282180.JPG" src="https://qiita-image-store.s3.amazonaws.com/0/205561/aad3d25c-c383-3b05-dfbf-42f4e2dc43b3.jpeg">

#所感
今まで自己満というか遊びというかでプログラミングしてて、初めて人のためにモノを作ったんですが悪くないですね。通知から解放された友人に褒められました。
GAS(JavaScript)を触った事がなかったので(これでいいのか…？？？)感は否めないですが、突貫工事の割にちゃんとしたものができてよかったです。
ここをこうしたら速くなるよ！とか、こう書いた方がわかりやすい/JSではこういう作法！とかコメントいただけましたら幸いです。

おしまい

2019/06/07 GASのコードを更新しました