【GAS】Googleフォームで参加者を募集し、Googleカレンダーに招待する方法

## はじめに
この記事は

1. Googleフォームでイベントの参加者を募集
2. フォームを受け付けた際、応募者をGoogleカレンダーに招待

この流れをスクリプトで自動化し、あわよくばテンプレートにして使いまわしたい...！
という方（主に自分）に向けた内容となっております。

* とりあえず直近のイベントで参加者招待を自動化したい ⇒ 手順だけ読む
* コードの詳細まで知りたい ⇒ コード説明まで読む
* 時間あるよ ⇒ ありがとうございます！最後までどうぞ！

ご自身の状況に合わせてお好きな読み方をしてくださいませ。

## 手順
1. Googleカレンダーで予定を作成する
1. Googleフォームを作成する
1. スプレッドシートで設定ファイルを作成する
1. スクリプトエディタを開き、後述するコードをコピペする
1. 作成したフォームをスプレッドシートと紐づける
1. スクリプトを実行する
1. 動作確認

<details><summary>詳細な手順</summary><div>

### 1. Googleカレンダーで予定を作成する
参加者を招待するイベントを作成します。
カレンダーの名前とイベントの名前は、後で設定ファイルに指定します。

### 2. Googleフォームを作成する
参加者募集用のフォームを作成します。中身は何でもOKです。
カレンダー招待に使うため、「設定」で「メールアドレスを収集する」をオンにします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530982/1508bca9-cd46-3e21-b765-5a964d906cef.png)

カレンダー招待の際にゲストへのメール通知ができないので、「設定」の「プレゼンテーション」を開き、「確認メッセージ」でカレンダー招待した旨を添えると、少し親切です。

### 3. スプレッドシートで設定ファイルを作成する
カレンダー招待のスクリプトを実行するためのスプレッドシートを用意します。
スクリプトのコードと対応しているため、以下の内容でシートを作成します。

* シート名を「設定」にする
* B2にカレンダー名（個人の場合はメールアドレス）を入れる
* B3にイベント名を入れる
* B4にイベント実施日を入れる（1か月以内のイベントなら入れなくてもOK）
* B5にフォームのURLを入れる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530982/2ad01f73-2a86-cf0c-16be-1d2e6a78684d.png)

### 4. スクリプトエディタを開き、後述するコードをコピペする
スプレッドシートの「拡張機能」⇒「Apps Script」を選択し、スクリプトエディタを開きます。
スクリプトエディタの中身は消し、後述のコードをコピペして貼り付けます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530982/ceb321fc-dfcc-b93b-05d1-79599e68693a.png)

### 5. フォームとスプレッドシートを紐づける
フォームの「回答」から「既存のスプレッドシートを選択」を選択し、作成したスプレッドシートとフォームを紐づけます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530982/410a4151-aee4-9229-e29c-96c9eadc4240.png)

### 6. スクリプトを実行する
ここまでで下準備が整いました！
再び Apps Script の画面に戻り「実行」ボタンをクリックします。実行する関数は「main」になっていることを確認してください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530982/adb80b30-f12f-653f-bcf1-4d2e004894ab.png)

ここでアクセス権限を求められるので、確認して許可します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530982/de53d468-e440-db9a-d16d-2a9284f0d045.png)

### 7. 動作確認
実際にフォームで参加登録をしてみましょう。
カレンダーのイベントに招待されている事が確認できたら成功です。

お疲れさまでした！

</div></details>

## コード
```js
// シート情報
const SETTINGS = "設定";　// 設定シートの名前
const MAIN = "フォームの回答 1";　// メインシートの名前

const settings = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SETTINGS);
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(MAIN);

function main() {
  // フォームを取得
  const formUrl = settings.getRange(5,2).getValue(); // フォームのURL（必須）
  const form = FormApp.openByUrl(formUrl);

  // フォーム回答時のトリガーをセットする
  deleteTrigger();
  ScriptApp.newTrigger('handleForm')
    .forForm(form)
    .onFormSubmit()
    .create();
}

// トリガーをリセットする
function deleteTrigger() {
  const triggers = ScriptApp.getProjectTriggers();
  for (let i = 0; i < triggers.length; i++) {
    ScriptApp.deleteTrigger(triggers[i]);
  }
}

// フォーム回答時に実行する
function handleForm() {
  // カレンダー情報を取得
  const calendarName = settings.getRange(2,2).getValue(); // イベントが登録されているカレンダー名（必須）
  const calendars = CalendarApp.getCalendarsByName(calendarName);
  const calendarId = calendars[0].getId();
  const calendar = CalendarApp.getCalendarById(calendarId);

  // イベント情報を取得
  const eventName = settings.getRange(3,2).getValue(); // イベントの名称（必須）
  const eventDate = settings.getRange(4,2).getValue(); // イベントの日付（任意）
  const date = eventDate ? new Date(eventDate) : new Date();
  const endDate = new Date(date.getTime() + (30 * 24 * 60 * 60 * 1000)); // 1か月後
  const options = { search: eventName };
  const events = calendar.getEvents(date, endDate, options);
  const eventId = events[0].getId();
  const event = calendar.getEventById(eventId);

  // ゲストをカレンダーに招待
  inviteGuest(event);
}

// ゲストをカレンダーに招待する
function inviteGuest(event) {
  const lastRow = sheet.getLastRow(); // シートの最終行
  const lastColumn = sheet.getLastColumn(); // シートの最終列
  let inviteColumn;
  if (sheet.getRange(1, lastColumn).getValue() === '招待') {
    inviteColumn = lastColumn;
  } else {
    sheet.getRange(1, lastColumn + 1).setValue('招待');
    inviteColumn = lastColumn + 1;
  }

  for (let i = 2; i <= lastRow; i++) {
    // イベント招待状況
    const added = sheet.getRange(i, inviteColumn).getValue();

    // イベント招待状況が空白だったら招待を実行
    if (!added) {
      // アドレス取得
      const target = sheet.getRange(i, 2); // 1列目は送信日時、2列目が送信者アドレス
      const address = target.getValues();

      if (!address) return;

      // アドレスの人をイベントに招待
      event.addGuest(address);
      // イベント招待状況の更新
      sheet.getRange(i, inviteColumn).setValue('済');
    }
  }
}
```

## コード説明
### シート情報の取得～メイン関数
```.js
// シート情報
const SETTINGS = "設定";　// 設定シートの名前
const MAIN = "フォームの回答 1";　// メインシートの名前

const settings = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SETTINGS);
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(MAIN);

function main() {
  // フォームを取得
  const formUrl = settings.getRange(5,2).getValue(); // フォームのURL（必須）
  const form = FormApp.openByUrl(formUrl);

  // フォーム回答時のトリガーをセットする
  deleteTrigger();
  ScriptApp.newTrigger('handleForm')
    .forForm(form)
    .onFormSubmit()
    .create();
}
```

はじめに、スクリプトを実行する際の情報（カレンダーの情報や、参加者のメールアドレス等）をどこから取ってくるのかを定義しています。シートの名前を変える場合、この1, 2行目の名前を変えます。

main 関数では、「フォームが送信された時」をトリガーに関数を実行するように設定しています。
main 関数を1度実行すると、それ以降は「フォームが送信された時」に handleForm 関数が呼ばれてゲストのカレンダー招待が自動で実行されます。

### トリガーのリセット
```.js
function deleteTrigger() {
  const triggers = ScriptApp.getProjectTriggers();
  for (let i = 0; i < triggers.length; i++) {
    ScriptApp.deleteTrigger(triggers[i]);
  }
}
```

main 関数でトリガーを設定する前に登録されているトリガーを一度リセットしています。
こちらの記事 [Googleフォームで参加者募集して、自動でGoogleカレンダーに申込者を招待する小技](https://moneyforward.com/engineers_blog/2021/07/30/googleform-to-googlecalendar/) を丸々参考にさせていただきました...！

### フォーム回答時に実行する関数
```.js 
function handleForm() {
  // カレンダー情報を取得
  const calendarName = settings.getRange(2,2).getValue(); // イベントが登録されているカレンダー名（必須）
  const calendars = CalendarApp.getCalendarsByName(calendarName);
  const calendarId = calendars[0].getId();
  const calendar = CalendarApp.getCalendarById(calendarId);

  // イベント情報を取得
  const eventName = settings.getRange(3,2).getValue(); // イベントの名称（必須）
  const eventDate = settings.getRange(4,2).getValue(); // イベントの日付（任意）
  const date = eventDate ? new Date(eventDate) : new Date();
  const endDate = new Date(date.getTime() + (30 * 24 * 60 * 60 * 1000)); // 1か月後
  const options = { search: eventName };
  const events = calendar.getEvents(date, endDate, options);
  const eventId = events[0].getId();
  const event = calendar.getEventById(eventId);

  // ゲストをカレンダーに招待
  inviteGuest(event);
}
```

「スプレッドシートで設定ファイルを作成」の手順で下記の指定をしたのは、この関数の中でどのセルからカレンダー名やイベント名を取得するかを定義しているからです。

> * B2にカレンダー名（個人の場合はメールアドレス）を入れる
> * B3にイベント名を入れる
> * B4にイベント実施日を入れる（1か月以内のイベントなら入れなくてもOK）

イベント実施日に関しては、指定があればその日から1か月、指定がなければフォームを送信した日から1か月の中で一致するイベントを探すように設定しています。

招待するイベントが特定できたら、ゲストをカレンダーに招待する inviteGuest 関数を更に呼びます。

### ゲストのカレンダー招待

```.js
function inviteGuest(event) {
  const lastRow = sheet.getLastRow(); // シートの最終行
  const lastColumn = sheet.getLastColumn(); // シートの最終列
  let inviteColumn;
  if (sheet.getRange(1, lastColumn).getValue() === '招待') {
    inviteColumn = lastColumn;
  } else {
    sheet.getRange(1, lastColumn + 1).setValue('招待');
    inviteColumn = lastColumn + 1;
  }

  for (let i = 2; i <= lastRow; i++) {
    // イベント招待状況
    const added = sheet.getRange(i, inviteColumn).getValue();

    // イベント招待状況が空白だったら招待を実行
    if (!added) {
      // アドレス取得
      const target = sheet.getRange(i, 2); // 1列目は送信日時、2列目が送信者アドレス
      const address = target.getValues();

      if (!address) return;

      // アドレスの人をイベントに招待
      event.addGuest(address);
      // イベント招待状況の更新
      sheet.getRange(i, inviteColumn).setValue('済');
    }
  }
}
```

フォーム送信結果と紐づく「フォームの回答 1」のシートの情報を使用して、ゲストのカレンダー招待とカレンダー招待状況の更新をしています。


## テンプレートにして使いまわす
手順 3, 4 で「設定」のみ入ったスプレッドシートにスクリプトを追加したファイルを保存します。
保存したファイルをコピーすると、スクリプト毎コピーされるため、そのスプレッドシートをテンプレートとして次のイベントの際も使いまわすことができます。

1, 2, 5, 6, 7 の手順は毎度必要になってしまうのですが、コードのコピペなどはなくなるので少しだけ楽になるはず...！

[【GAS】スプレッドシート上にボタンを配置してスクリプトを実行しよう。](https://www.pnkts.net/2019/12/10/gas-spreadsheet-button)
この記事などを参考に、main関数の実行もボタンでできるようにするとスクリプトも開かなくて済むテンプレートができると思うのですが...そこまではできませんでした。

## さいごに
ここまでお付き合いいただき、誠にありがとうございます。
色々と改良の余地があるコードや記事ではありますが、読んでくれた方にとって何か少しでも役立つ内容になっていたら幸いです！それでは！


## 参考文献
* [Googleフォームで参加者募集して、自動でGoogleカレンダーに申込者を招待する小技](https://moneyforward.com/engineers_blog/2021/07/30/googleform-to-googlecalendar/)
* [Reference overview  |  Apps Script  |  Google Developers](https://developers.google.com/apps-script/reference)
* [FormsとGASを使って既存のカレンダーイベントにゲスト追加する](https://qiita.com/gohh56/items/496b4c0b0ddbb20fd7d6)
* [【GAS】スプレッドシート上にボタンを配置してスクリプトを実行しよう。](https://www.pnkts.net/2019/12/10/gas-spreadsheet-button)