Google FormとLINE Notifyを使って出欠botを作った

# はじめに
今まで週２回の部活のグループラインにて出欠を取る際、LINEmessagingAPIを使用していました。しかし、LINEmessagingAPIの料金プランが変更になり、無料で送信できるメッセージ数が月1000通から月200通になってしまいました。月200通では週2回の出席を確認することができないため、LINE Notifyを使ったものに変更することにしました。本記事ではLINE Notifyを使った出欠botの作成手順を示していきます。

# 概要
### 作成までの流れ
1. LINE Notifyにてトークンを発行する
2. メンバー情報を収集するためのGoogle Form(以下Form)を作成する
3. 回答されたデータをスプレッドシートに記録する
4. 出欠を取るためのFormを作成する
5. 回答されたデータをスプレッドシートに記録する
6. 4で作成したFormのリンクを含めたメッセージをLINE Notifyで送信できるようにする
7. 出欠状況を報告するメッセージをLINE Notifyで送信できるようにする
8. 出欠確認を取りたいグループのメンバー情報を得るためにFormに回答をしてもらう

### 作成後の出欠確認の流れ
2. LINE NotifyからグループラインにGoogle Formのリンクを含めたメッセージを送信し、出欠を回答してもらう
3. 特定の時間になったらLINE Notifyから現在の参加人数と参加者を送信する


# 事前準備
LINE Notifyの公式サイトにて自分のLINEアカウントでログインしてください。

https://notify-bot.line.me/ja/

右上のリストからマイページへ移動します。

![スクリーンショット 2023-10-27 18.21.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651685/2c1ffdba-309e-08e3-cafb-cc6f481ce4ba.png)

マイページ下部の`アクセストークンの発行(開発者向け)`からトークンを発行します。
トークン名はメッセージを送る際に`[トークン名]　本文`のように表示されるため、適切なトークンを入力してください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651685/86893a60-ca34-4720-f164-e99b65e18294.png)

![スクリーンショット 2023-10-29 4.34.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651685/a3417833-824d-d7e4-2b82-98c6eeaddd31.png)

トークンをコピーし、保管しておいてください。

![スクリーンショット 2023-10-29 4.37.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651685/03d9920d-aa90-65a8-18f1-3915365d22f2.png)

# 1. メンバーリストの作成
### Formの作成
[GoogleForm](https://docs.google.com/forms/u/0/?tgif=d&ec=asw-forms-hero-goto)にてメンバーの情報を収集するためのFormを作成します。

![スクリーンショット 2023-11-13 5.20.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651685/5eac1795-dc8a-d5a5-1b1f-2341967592f3.png)

![スクリーンショット 2023-11-13 5.21.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651685/a199214e-b1a3-7edf-4577-0ae34e09d769.png)



以下の例の場合は本名, 表示名, 学年, メールアドレスを収集しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651685/4fd006f9-6dea-99b2-308b-8d980fffb216.png)

:::note warn 
メールアドレスを収集するの項目を確認済みに変更してください。
このように設定することで自動でGoogleに使用しているメールアドレスを収集することができます。
<img width="50%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651685/97828048-9210-7d9d-8f97-e55659eb74b9.png">
:::

### スプレッドシートへの記録
これらの情報をスプレッドシートに記録するために、Google App Script(GAS)に以下のコードを記載します。

:::note info
Google Formには自動でspreadsheetに記録する機能があるためそちらを使用しても大丈夫です。ただし、重複したデータも記録されてしまうため、それに適したものに変更する必要があります。
以下の画面から設定できます。
<img width="80%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651685/46a44460-b557-c40a-3492-ce19f74ea274.png">
:::

Google Formからスクリプトエディタを開きます。
<img width="60%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651685/c462521a-6967-040a-4859-5a4582008f6b.png">

function関数を削除し、左上のGASのファイル名を無題のプロジェクトから適切なものに変更してください。
ここでは、ファイル名を`member_list`とします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651685/f53a3a3c-924d-e4a6-fac1-1769913b30e9.png)

上記で作成した`member_list`ファイルに以下のコードを追加します。

``` js
//任意のspreadSheetを作成し、そのidを入力する
const spreadSheet = SpreadsheetApp.openById("spreadSheetのid");
const sheet = spreadSheet.getSheetByName("メンバー表");

function recordMemberInfoToSheet(e) {
  const questions = e.response.getGradableItemResponses();
  const [fullName, displayName, grade] = questions.map(x => x.getResponse())
  const mailAddress = e.response.getRespondentEmail()
  let isDupulicate = sheet.createTextFinder(mailAddress).findAll().length >= 1
  if(!isDupulicate){
    sheet.appendRow([fullName,displayName,grade, mailAddress])
  }
}
```
これでこの関数が実行されると、Formで回答したデータがスプレッドシートに記録されます。

### トリガーの設定
Form送信時に上記の関数を発火させるためのトリガーを追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651685/ece25b64-e82f-523e-16c5-f80fb18952ec.png)

以下のように設定することで、Form送信時にSpread Sheetに記録されるようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651685/5b3e3da2-49bf-ce97-4d01-dade181e8f53.png)

# 2. 出欠確認Formの作成
[GoogleForm](https://docs.google.com/forms/u/0/?tgif=d&ec=asw-forms-hero-goto)にて出欠情報を収集するためのFormを作成してください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651685/1bba7d20-3e55-4176-cd29-7469b294b0ea.png)

[1. で作成したForm](#メンバーリストの作成)のようにメールアドレスを収集するを`確認済み`にし、スクリプトエディタを開いてください。
出欠確認FormのGASのファイル名は`club_attendance`とします。
以下のコードを`club_attendance`ファイルに記述します。


``` js
// 1. で使用しているシートと同様のid
const sheet = SpreadsheetApp.openById("spreadSheetのid")
const today = new Date()
const dayArray = ["日", "月", "火", "水", "木", "金", "土"]
let sheetName = `${today.getMonth() + 1}月${today.getDate()}日 (${dayArray[today.getDay()]})`

const memberSheet = sheet.getSheetByName("メンバー表")
const attendanceSheet = sheet.getSheetByName(sheetName)

function recordAttendanceToSheet(e) {
  const attendanceSheetLastRow = attendanceSheet.getLastRow()
  //セルをチェックボックスに変更するための設定
  let rule = SpreadsheetApp.newDataValidation().requireCheckbox("yes", "no").build();

  attendanceSheet.getRange("A1").setValue("名前")
  attendanceSheet.getRange("B1").setValue("学年")
  attendanceSheet.getRange("C1").setValue("出欠")

  const questions = e.response.getGradableItemResponses();
  let isAttend = questions[0].getResponse() === "参加 (Yes)" ? "yes" : "no"
  let mailAddress = e.response.getRespondentEmail()
  const rowNum = memberSheet.createTextFinder(mailAddress).findAll()[0].getRow()
  const nickName = memberSheet.getRange(rowNum, 2).getValue()
  const grade = memberSheet.getRange(rowNum, 3).getValue()
  let isDuplicate = attendanceSheet.createTextFinder(nickName).findAll().length >= 1
  if (isDuplicate) {
    let hitRowNum = attendanceSheet.createTextFinder(nickName).findAll()[0].getRow()
    attendanceSheet.getRange(`C${hitRowNum}`).setValue(isAttend)
  } else {
    attendanceSheet.appendRow([nickName, grade, isAttend])
    attendanceSheet.getRange(2, 3, attendanceSheetLastRow).setDataValidation(rule)
  }
  attendanceSheet.sort(2)
}
```

recordAttendanceToSheet関数はformが送信された時に発火する'e'を引数にもつ関数です。この'e'にはformの回答情報が入っていて、この関数はformの回答結果をその日付のsheetに出力するものです。

2.の[トリガーの設定](#トリガーの設定)にあるように上記の関数をForm送信時に発火するように設定をしてください。

# 3. 出欠Formの送信
出欠Formのリンクを含んだLINEメッセージをLINE Notifyから送信するために、GASにコードを書いていきます。
`club_attendance`ファイルに追加をしていきます。

LINE NotifyをGASで簡単に使用するためにライブラリを使用します。
GASのエディタ画面でライブラリの`+ボタン`を押します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651685/fce17094-9ab0-eb51-e861-4fa22329c87f.png)

すると、スクリプトIDの入力を求められるため、以下のスクリプトIDを入力します。

``` :スクリプトID
1L4uz6F_w_o0RZsH-W6oR-eXklMmGvHddVjA4FrU6VNdlm7mgk2Q4Wmrc
```

検索ボタンを押すと以下の画面が表示されるので、最新バージョンを追加します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651685/87cd4acc-a34a-c421-5353-c14cf8225795.png)

ライブラリの欄にLINE_Notifyが追加されているはずです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651685/336d8865-0ebc-884d-7ace-d79a0604a65a.png)

次に、以下の`createSheet`関数と`isExistSheet`関数,`postActivityNotice`関数を追加していきます。
``` js
function createSheet() {
  sheet.insertSheet().setName(sheetName)
}

function isExistSheet() {
  let sheetList = []
  sheet.getSheets().map(x => sheetList.push(x.getSheetName()))
  console.log(sheetList.indexOf(sheetName) !== -1)
  return sheetList.indexOf(sheetName) !== -1
}

function postActivityNotice() {
  if(!isExistSheet) {
    createSheet()
  }
  const sheetId = attendanceSheet.getSheetId()
  const notifyToken = "事前準備で発行したトークン"
  const formUrl = "出欠を回答してもらうためのフォームのurl"
  const sheetUrl = "出欠を記録してあるスプレッドシートのurl"
  const postText = `\n今日(${today.getMonth() + 1 }/${today.getDate()})は活動日です。\n以下のフォームにて出欠を回答してください。\n${formUrl}\n\nまた、以下のリンクから現在の出欠状況を確認することができます。\n${sheetUrl}#gid=${sheetId}`
  new LINE_Notify.init(notifyToken).post(postText)
}
```
[1.で行ったトリガー設定](#トリガー設定)を参考に、以下のように設定をしてください。
以下の設定は毎週月曜日の11:00~12:00に`postAcrivityNotice`関数を実行するというトリガーです。
実際の活動日ごとに適宜変更をしてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651685/43690bdc-2390-bf36-bed6-85fd0a09496a.png)


# 4. 参加人数の通知
`club_attendance`ファイルに以下の`postAttendee`関数を追加します。
``` js
function postAttendee() {
  const attendanceSheetLastRow = attendanceSheet.getLastRow()
  const applicantList = attendanceSheet.getRange(2,1,attendanceSheetLastRow - 1).getValues()
  const attendanceList = attendanceSheet.getRange(2,3,attendanceSheetLastRow - 1).getValues()
  let attendeeList = []

  applicantList.map((x,index) => {
    if(attendanceList[index][0] == "yes") attendeeList.push(x)
  })

  let desplayAttendeeList = ""
  attendeeList.map(x => desplayAttendeeList += "\n - " + x);

  const notifyToken = "事前準備で発行したトークン"
  const postText = `\n今日の参加人数は${attendeeList.length}人です。\n参加者は${desplayAttendeeList}\nです。`

  new LINE_Notify.init(notifyToken).post(postText)
}
```
`club_attendance`ファイルに以下のトリガーを追加します。
以下の設定は毎週月曜日の19:00~20:00に`postAttendee`関数を実行するというトリガーです。
実際の活動日ごとに適宜変更をしてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651685/4d005d98-e18a-c0ad-ccd4-793908b4e2ab.png)

# おわりに
本記事では、出欠確認のためのフォームとその回答を記録するスプレッドシートを作成する方法について説明しました。また、LINE Notifyを使用して活動日の通知や参加者の通知を行うためのスクリプトも提供しました。
記事内で提供されたスクリプトや設定手順を適切に実施することで、出欠確認botを作成できます。是非、実際の活動に導入してみてください。

#### 参考記事

https://qiita.com/n0bisuke/items/153fa71b22f44c3d4766