【GAS】Googleフォームでサクッと社内の問い合わせフォームを作った

## 概要
社内の情シス問い合わせフォームを作ってみました。  
システムの概要と個人的に引っ掛かりそうなポイントを自分用備忘録として記しておきます。

## システム構成

![image001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/289210/eeb64018-d775-8fc4-1d1f-c394e8383963.png)

* Google Form
  * Googleの入力フォーム作成ツール
  * アンケートや問い合わせフォームをノーコードで直感的に作成でき、回答の確認が可能
* GAS (Google App Script)
  * Googleのスクリプト実行サービス
  * Web上で動作し、サーバーの環境構築無しでスクリプトを動作させることができる
* Chatwork
  * 社内で使用しているチャットアプリ
  * ざっくり言うとSlackやMicrosoft Teamsみたいな立ち位置のツール

問い合わせ内容をGoogleフォームで入力し、入力されたタイミングでGASが実行され  
Chatworkの任意のチャンネルへとメッセージを自動送信する...という流れ。

単純にGoogleフォームだけでも回答の確認や集計等は可能なのだが、
フォームに張り付くわけにもいかないので、普段業務で使っているチャットアプリであるChatwork側で確認できるようにしたかった。

## 構築
### 問い合わせ入力フォームを作成
Googleフォームで問い合わせ入力画面を作成。

![image002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/289210/b2228fec-99c7-6702-809f-32338b16ece9.png)

ブラウザ上で作成します。Google Chromeを使っていると画面はこんな感じ。

次に、問い合わせフォームの入力項目を追加します。
「氏名」「緊急度」「分類」「問い合わせ内容」など、このへんはお好みで。

![image003.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/289210/7090efe9-0d36-1114-0a1d-90e707f4284b.png)

できました。

![image004.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/289210/23ae31cf-aa99-4e08-9ea6-dd2d6c6d929f.png)

完成したらプレビューを確認しておきましょう。右上の目のマークから確認できます。
ここから項目を入力して回答を送信することもできます。

### Chatwork側の環境構築
入力フォームはできたので、次にメッセージを飛ばす仕組みを作ります。
ですがその前に、メッセージ送信先となるChatwork側の環境構築を済ませておきましょう。

* Chatwork APIトークンの発行

Chatworkに何かコンテンツを送信したりChatwork側のコンテンツを受信するため、APIトークンを取得します。
このへんの手順は公式サイトに手順が記載されているので、手順にしたがって進めてください。

[Chatwork:APIトークンを発行する](https://help.chatwork.com/hc/ja/articles/115000172402-API%E3%83%88%E3%83%BC%E3%82%AF%E3%83%B3%E3%82%92%E7%99%BA%E8%A1%8C%E3%81%99%E3%82%8B)

* メッセージを飛ばすルームのIDを確認

メッセージを自動送信したいルームのIDが後に必要になります。
以下手順にしたがいルームIDを確認し、控えておいてください。数字9桁のやつです。

[Chatwork:ルームIDを確認する](https://help.chatwork.com/hc/ja/articles/360000142942-%E3%83%AB%E3%83%BC%E3%83%A0ID%E3%82%92%E7%A2%BA%E8%AA%8D%E3%81%99%E3%82%8B)


### GASの作成
送信先であるChatwork側の準備ができたので、メッセージを飛ばす仕組みを作ります。

![image005.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/289210/3715b0a4-ce9e-4721-4a96-b41e1190b628.png)

Googleフォーム設定画面、右上の方から「スクリプトエディタ」を選択します。

![image006.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/289210/0b500884-cf1d-a724-09c0-85acae231604.png)

上記のような画面が出てきます。
GASの基本的な説明はここでは省略します（検索したらいっぱい記事が出てくると思います）

スクリプト名やプロジェクト名は適宜リネームしてください。デフォルトだと「無題のプロジェクト」といった名称になっています。
基本的な文法などはJavaScriptのものに従ってコーディングすればOK...だと思います。

今回使用したコードは以下の通り。

```JavaScript
/** 
 * Chatwork の設定 
 */ 
const CHATWORK_API_TOKEN = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'; // ChatworkのAPIトークン
const CHATWORK_ROOM_ID = '123456789'; // 対象となるChatworkのルームID

/** 
 * Google Formの回答送信時に実行される関数
 */ 
function onFormSubmit() {
  // フォームの回答を取得
  const form = FormApp.getActiveForm();
  const responses = form.getResponses();
  const latestResponse = responses[responses.length - 1];
  const items = latestResponse.getItemResponses();

  // 項目名リスト
  const fieldNames = [
    '氏名',
    '緊急度',
    '分類',
    '問い合わせ内容'
  ];

  // メッセージの作成
  let message = '[info]';
  message += '[title]新しいお問い合わせが届きました[/title]\n';

  // タイムスタンプを取得,メッセージに追加
  const timestamp = latestResponse.getTimestamp();
  const formattedTimestamp = Utilities.formatDate(timestamp, Session.getScriptTimeZone(), 'yyyy/MM/dd HH:mm:ss');
  message += `日時：\n${formattedTimestamp}[hr]\n`;
  
  // 各項目をループで処理
  fieldNames.forEach(fieldName => {
    // 値が取得できなかった場合は「不明」として処理
    const value = items.find(item => item.getItem().getTitle() === fieldName)?.getResponse() || '不明';

    // 「問い合わせ内容」のみ [code] タグを使用
    if (fieldName === '問い合わせ内容') {
      message += `${fieldName}：\n[code]${value}[/code][hr]\n`;
    } else {
      message += `${fieldName}：\n${value}[hr]\n`;
    }
  });

  message += '[/info]';

  // Chatworkに通知
  sendToChatwork(message);
}


/** 
 * Chatwork にメッセージを送信する 
 * 
 * @param {string} message - 送信するメッセージ 
 */ 
function sendToChatwork(message) { 
  Logger.log('Sending the following message to Chatwork:');
  Logger.log(message);

  const options = { 
    method: 'POST', 
    headers: { 
      'X-ChatWorkToken': CHATWORK_API_TOKEN, 
    }, 
    payload: { 
      body: message, 
    }, 
  }; 

  const url = 'https://api.chatwork.com/v2/rooms/' + CHATWORK_ROOM_ID + '/messages'; 
   
  // Chatwork APIからのレスポンスをログに出力 
  try { 
    const response = UrlFetchApp.fetch(url, options); 
    Logger.log('Response from Chatwork:'); 
    Logger.log(response.getContentText()); 
  } catch (error) { 
    Logger.log('Error occurred when sending to Chatwork:'); 
    Logger.log(error.toString()); 
  } 
} 
```

コード内、はじめに定義している`CHATWORK_API_TOKEN`と`CHATWORK_ROOM_ID`の値は環境に合わせて書き換えてください。
`Chatwork側の環境構築`の手順にて取得した値を控えているかと思うので、その値にすればOKです。

また、上記コードはGoogleフォームにて
「氏名」「緊急度」「分類」「問い合わせ内容」が存在する前提で書かれています。
Googleフォームで別の項目を追加した場合は、都度コードを書き換える必要があります。

今回はメッセージの見やすさを重視し、項目ごとに表示形式を変えたりしていますが、頻繁に項目の追加等がある場合はもっと汎用的な動作にした方がいいかもしれません。

スクリプトの内容ができたので、次にトリガーを設定します。

![image007.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/289210/cbc63e17-e0ce-de20-9f6b-c54b6eec9c62.png)

画面左より「トリガー」を選択。トリガーの設定画面へ進みます。
右下に＋マークがあるので、ここから新しいトリガーの定義を追加しましょう。

![image008.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/289210/e561e6c7-b536-7ffd-e96d-11cfc23b33c1.png)

上記のよう設定を変更します。
これで先ほど作成したフォームから送信処理が行われると、それをトリガーとして`OnFormSubmit`が実行され、
その中でフォーム内の内容を取得、`sendToChatwork`で取得したメッセージを整形、Chatworkに送信する...という流れ。

## 動かしてみる
実際に動作させてみましょう。
Googleフォーム画面から内容を入力し、「送信」を選択。

![image009.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/289210/dd488577-44f3-b5e5-1b47-b1e0d7c21178.png)

すると送信をトリガーとしてGASが実行され、最終的にChatworkにメッセージが送信されます。

![image010.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/289210/8563c615-5286-dbdf-e794-3f788414998b.png)

いい感じ。
あとはChatwork内でメッセージごとタスクとして登録するなり何なりして対応しましょう。

## 引っ掛かったところ
### タイムスタンプの扱い
今回は、自動送信するメッセージの内容として以下の項目を指定しています。

* 日時
* 氏名
* 緊急度
* 分類
* 問い合わせ内容

上記のうち、「日時」のみ回答者が手動で記入する項目ではありません。
それが関係しているかどうかはわかりませんが、情報を引っ張ってくる際のメソッドが他項目と異なります。

```JavaScript
/** 
 * Google Formの回答送信時に実行される関数
 */ 
function onFormSubmit() {
  // フォームの回答を取得
  const form = FormApp.getActiveForm();
  const responses = form.getResponses();
  const latestResponse = responses[responses.length - 1];
  const items = latestResponse.getItemResponses();

  ...

  // タイムスタンプを取得,メッセージに追加
  const timestamp = latestResponse.getTimestamp();
  const formattedTimestamp = Utilities.formatDate(timestamp, Session.getScriptTimeZone(), 'yyyy/MM/dd HH:mm:ss');
  message += `日時：\n${formattedTimestamp}[hr]\n`;
  
  // 各項目をループで処理
  fieldNames.forEach(fieldName => {
    ...
  });

  ...

}
```

ユーザーが入力した項目は`getItemResponses`メソッドで、タイムスタンプは`getTimestamp`メソッドにて取得しています。

はじめはChatworkの技術ブログ記事を参考に実装を行っていたのですが、
2020年の記事なので仕様が異なっていたのか、うまく動作しませんでした。
調査したところ上記のメソッドの違いが原因だったようです。

### デバッグ時の注意点
GASの動作確認のため、エディタ内のデバッグ機能を利用しようと「デバッグ」を押したところ、エラーが発生しました。

![image011.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/289210/e1051ee5-9c48-d82a-73fb-5db59cd6dcbb.png)

`getItemResponses`なんて無いよと怒られています。
本処理のはじめに、「アクティブな入力フォーム内の値を取得する」という動作を行うため、
そもそも何も入力していない段階でメソッドを実行しても値が取得できない...という流れだと思います。

本来であればデバッグ用のモック等を用意してデバッグを行うべきなのでしょうが、
入力フォーム自体そこまで複雑なものではないので、テスト時には回答を送信して動作確認することにしました。

### スクリプトの保存先について
これは引っ掛かったというよりはGASにおける基礎知識みたいなものですが、
「作成したスクリプト(xxx.gs)の保存先はどこだろう？」と戸惑ってしまいました。

結論から言うと、コード単体での保存先はありません。
今回の場合、Googleフォーム自体に付属しているマクロのような扱いとなっているので、フォーム自体にコードが合わせて保存されているというイメージです。
このように、ドキュメントなどの他ファイルに付随してコードを管理する方式を**コンテナバインド型**というようです。

対して、コード単体で管理する方式を**スタンドアロン型**と呼びます。
Googleドライブ上のリソースにアクセスしないシステムや、Webアプリケーションを構築したい時などはこちらの方針で開発してもいいかもしれません。

### その他注意点
手軽にリソースへのアクセスや操作を行うことができるGASですが、当然無限に処理させられるわけではなく、「最大実行時間は6分まで」などの制限があります。
今回のように簡単な処理や実行時間のかからないものであれば考慮する必要はないかもしれませんが、多くのドキュメントにアクセスしたり、非常に多くのループ処理を行うようなシステムの場合、別の手段を検討する必要があるかもしれません。

## まとめ・今後の課題
GASの優れている点として、一般的なツールやWebアプリケーションの開発と比較し圧倒的に環境構築が楽だと感じました。
また、ユーザーが入力することとなる入力フォームのUI作成がGoogle Form内で完結しているので、所謂フロント側でのコーディングが必要ないのが個人的には最も恩恵が大きいです。
加えて、料金がまったくかからない点も素晴らしいです。制限はありますが、無料プランのアカウントでもこれくらいのことであれば可能です。

課題点ですが、ナレッジの蓄積が難しいことです。
これはシステムの問題ではないですが、当然Googleフォームは回答を入力・蓄積するだけなので、その回答に対する返答やコメントの付与はできません。
「その問い合わせに対してどのような対応を行ったか？」を問い合わせと紐づけ、ナレッジとして蓄積できると役に立ちそうだなぁとか考えていたのですが
今のところはスプレッドシートに直接コメントを記入 or 社内wikiなどの別リソースとして管理 のような方式になるかと思います。

今回はGoogleフォームとGASを使って社内の問い合わせフォームの作成、チャットツールへの通知を行いました。
社内で使用しているツールであるChatworkを前提にシステムを開発しましたが、Slack等でも同じような手順で開発できるかと思います。

## 参考リンク
[【コピペOK】Googleフォームの送信内容をChatWorkに連携する方法（GAS）](https://glass-inc.jp/media/google-form-gas-chatwork/)
[Chatwork APIについて](https://developer.chatwork.com/docs/getting-started)
[GAS各種制限について：Google サービスの割り当て](https://developers.google.com/apps-script/guides/services/quotas?hl=ja)