GASで自動ペア作成botを作ってみた

# はじめに
はじめまして！
医療系学部に通う一端の大学生です。

そんな私が大学で所属するサークルのため、ペアをランダムに決めてくれるLINEbotの作成をGASで行ってみました！

私自身プログラミング初学者のペーペーで、いろいろと調べながら精一杯作ったので、生暖かい目で見てください。


#目的
私が大学で所属するテニスサークルで行う試合のペア決めをいつもじゃんけんで決めていたのですが、人数が多くなりじゃんけんがなかなか終わらなく;;

そこで自動化してみようと思い、ランダムにペアを作ってくれるLINEbotを作りました！


#機能
ペア決めLINEbotの機能は以下の通りです。

#####ボタンテンプレートにより可能

１.参加 ： SSにユーザーネームとIDを記録する

２.取り消し ： SSの自身のデータを削除する

３.確認 ： SSに自身のデータがあるか

▼こんな感じです
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1214525/ae07cf8b-18f3-eab0-b1e2-381818a56a36.jpeg" width=40%>



#####コマンドにより可能
４.button : ボタンテンプレートを送信させる
　（私はリッチメニューからもできるようにしました）

５.random ： 参加したメンバーでランダムに2人1組のペアを組む



#準備
▶︎Googleアカウント
▶︎LINEアカウント

※チャンネルアクセストークンの取得方法やGASを書くためのスクリプトエディタの起動方法は省略します。

#参考記事
▶[Google Apps Scriptでオウム返しLINE Botを作る。](https://qiita.com/tetrapod117/items/e7b48485c98f6b88f311)
▶[Google Apps Script試行錯誤 Blog](https://www.pre-practice.net/2017/10/line-bot_25.html)
▶[くらぶろぐ【LINE BOT】GASで『ボタンテンプレートメッセージ』の送り方](https://arukayies.com/gas/line_bot/pushmessage-buttons-template)
▶[くらぶろぐ【LINE BOT】GASで『ポストバックアクション』を試してみた](https://arukayies.com/gas/line_bot/postback-action)



#コード
拙いですがこんな感じで書いてみました。

```javascript:LINEbot.gs
var CHANNEL_ACCESS_TOKEN = 'チャンネルアクセストークン';

function doPost(e) {
  var contents = e.postData.contents;
  var obj = JSON.parse(contents);
  var events = obj['events'];
  var replay_text = '';
  
  for (var i = 0; i < events.length; i++) {
    if (events[i].type == 'message') {
      reply_message(events[i]);
    } else if (events[i].type == 'postback') {
      post_back(events[i]);
    }
  }
}

function reply_message(e) {
  var input_text = e.message.text;
  if (input_text == 'button') {
    var postData = {
      'replyToken': e.replyToken,
      'messages': [{
           'type': 'template',
           'altText': 'select',
           'template': {
                'type': 'buttons',
                'title': 'ペア決め',
                'text': '押してね',
           'actions': [
              {
           'type': 'postback',
           'label': '参加',
           'data': 'postback_joined'
         },
         {
           'type': 'postback',
           'label': '取り消し',
           'data': 'postback_deleted'
         },
         {
           'type': 'postback',
           'label': '確認',
           'data': 'postback_verify'
         }
       ]
     }
    }]
   };
  
  }else if(input_text == 'random') {
    var ssID = 'スプレッドシートID';
    var ssName = 'スプレッドシートの名前';
    var userID = e.source.userId;
    var nickname = getUserProfile(userID);
    var data = SpreadsheetApp.openById(ssID).getSheetByName(ssName);
    var last_row = data.getLastRow();
    var pairs_message = '';
    var count = 0;
    var ary = data.getDataRange().getValues();
    ary.shift();   
    
    for(var j = ary.length - 1; j >= 0; j--){
        var random_row = Math.floor(Math.random() * j + 1);
        var tmp = ary[j];
        ary[j] = ary[random_row];
        ary[random_row] = tmp;
      }
    
    if(ary.length % 2 == 0){//人数が偶数の時
      pairs_message += ary[count][0] + ' - ' + ary[count+1][0];
      count += 2;
      
      while(ary.length != count){
       pairs_message += '\n' + ary[count][0] + ' - ' + ary[count+1][0];
       count += 2;
      }
     
    }else if(ary.length % 2 == 1){//人数が奇数の時
      while(ary.length != count + 1){
       pairs_message += ary[count][0] + ' - ' + ary[count+1][0] + '\n';
       count += 2;
      }
      pairs_message += ary[count][0] + ' - ' + 'free';
    }
    data.deleteRows(2, last_row-1);
    
    var postData = {
      'replyToken': e.replyToken,
      'messages': [{
        'type': 'text',
        'text': pairs_message,   
      }]
    };
    
  }
  fetch_data(postData);
}

function post_back(e) {
  var postdata = e.postback.data;
  var ssID = 'スプレッドシートID';
  var ssName = 'スプレッドシートの名前';
  var userID = e.source.userId;
  var nickname = getUserProfile(userID);
  var data = SpreadsheetApp.openById(ssID).getSheetByName(ssName);
  var last_row = data.getLastRow();
  
  if (postdata == 'postback_joined') {//ボタンテンプレートの参加が押されたとき
    replay_text = '参加しました' ;
    for(var m = last_row; m >= 1; m--) {
      if(data.getRange(m,1).getValue() != '') {
        var n = m + 1;
        data.getRange(n,1).setValue(nickname);//n行の1列目にニックネームを追加
        data.getRange(n,2).setValue(userID);//n行の2列目にユーザーIDを追加
        data.getDataRange().removeDuplicates([2]);//2つ同じデータがあるときに1つを削除
        break;
      }
    }

  }else if (postdata == 'postback_deleted') {//ボタンテンプレートの取り消しが押されたとき
    replay_text = '取り消しました';
    ary = data.getDataRange().getValues();
    for(var s = ary.length-1; s >= 0; s--){
      if(ary[s][1] == userID){
        data.deleteRow(s+1);
      }
    }
  }else if(postdata == 'postback_verify'){//ボタンテンプレートの確認が押されたとき
   var confirm_number = 0; 
   ary = data.getDataRange().getValues();
    for(var s = 1; s <= last_row-1; s++){
     if(ary[s][1] == userID){
      confirm_number += 1;
     }else{
      confirm_number += 0;
     }
    }
    if(confirm_number == 1){
     replay_text = '確認できました。';
    }else if(confirm_number == 0){
     replay_text = '確認できませんでした。\nもう一度入力してください。';
    }
  }

  var postData = {
    'replyToken': e.replyToken,
    'messages': [{
      'type': 'text',
      'text': replay_text
    }]
  };
  fetch_data(postData);
}

function fetch_data(postData) {
  var options = {
    'method': 'post',
    'headers': {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer ' + CHANNEL_ACCESS_TOKEN
    },
    'payload': JSON.stringify(postData)
  };
  UrlFetchApp.fetch('https://api.line.me/v2/bot/message/reply', options);
}

function getUserProfile(userId){ 
  var url = 'https://api.line.me/v2/bot/profile/' + userId;
  var userProfile = UrlFetchApp.fetch(url,{
    'headers': {
      'Authorization' :  'Bearer ' + CHANNEL_ACCESS_TOKEN,
    },
  })
  return JSON.parse(userProfile).displayName;
}
```

最後にデプロイをごちゃごちゃっとすると完成です！
ごめんなさい。次に書くときはちゃんと書きます。。。

#動作確認
▼buttonと送信することでペア決めのボタンテンプレートが出現
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1214525/698838b8-0c4e-245f-8231-cc0df27700e2.jpeg" width=50%>


▼ボタンテンプレートを押すことでSSに反映されることを確認
　（確認→SSにユーザー情報の追加　　取り消し→SSのユーザー情報の削除）
<img width="50%" alt="20210521_155807000_iOS.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1214525/539c66c1-e48a-1c9c-e42a-8624ee5fde47.png">


▼randomと送信することでSS内のデータからランダムに2人1組のペアを組んで送信
　【ニックネーム ー ニックネーム】と送信される
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1214525/592e8029-1ca5-86ee-1194-fb3520604de3.jpeg" width=50%>





#改善点
▶︎ボタンテンプレートを押すタイミングが被るとうまくSSに反映されない

▶ボタンテンプレートを押してからのbotの返信のにラグがある

▶randomと送信してペアを出すまでにもラグがある（人数が多いほどラグがひどい）←処理に時間かかってるからなのですが


#さいごに
実際にサークルで使うことができました！
ですが、同時に改善点も発見できたため今後どんどん改善していき、機能も追加していきたいと思います！

プログラミング初学者の私ですら1週間程度で完成させることが出来たので、皆さんもぜひLINEbot作ってみてください！