【コピペで30分】GoogleフォームとSlackBotを使った完全匿名投稿システム【サンプルコード付き】

# 背景
なぜslackで匿名投稿できる仕組みを自分で作ろうとしたのかという背景ですが、
例えば **「とても初歩的な質問」** や **「要望」**  **「不平不満」** などどうしても **「自分が言うの抵抗があるけど言いたいこと」** は何かしらあるかと思います。
けれどそれらの内容は実は組織をより良くしていく上では必要な意見だったりするかもしれません。
そんな内容をもっと **手軽** に **心理的安全性を確保** した上で投稿できる仕組みが欲しいと思ったことがきっかけとなります。

# 今回作成したシステムの概要
Googleフォームから「宛先」としてSlackユーザID(Uから始まるメンバーID)を指定して「投稿内容」に記載した文章を「AnonymousPost」というslackBotからのDMとして送ることができるというシンプルなシステムです。
slackBotでメッセージを送った際にGoogleフォームの回答内容を全て削除することで、後からも追えないようにして匿名性を担保しております。
    <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2252800/abb488a1-760e-e31e-ce5f-d9c643dfafb1.png" width="50%">


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2252800/e1a3bfdc-1d3a-8caa-a06b-8ef00d506712.png)


# 実装の流れの概要
1. SlackBotを作成する
1. 投稿用のGoogleフォームを作成する
1. GoogleフォームにSlackへ投稿するスクリプトを記述する

以下にそれぞれの項目の具体的な実装を記載してまいります。

# SlackBotを作成する
1. [slack API](https://api.slack.com/apps)にアクセスする
1. 右上あたりにある「Create New App」をクリックする
1. Create an appのモーダルから「From scratch」をクリックする
    <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2252800/ea3783a0-d8e8-1c8a-7a3d-8fe4e736b8bb.png" width="50%">
1. 「App Name」にそのアプリにつけたい名前を入力する
1. 「Pick a workspace to develop your app in:」でBotを追加したいワークスペースを選択する
1. 「Create App」を選択してアプリを作成する
    <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2252800/c4cd7f57-4b9b-773c-c8fb-6fcae68831f1.png" width="50%">
1. 左のメニューの「Features」から「OAuth & Permissions」を開く
1. 「Scopes」の「Bot Token Scopes」の「Add an OAuth Scope」から「im:write」「chat:write」を追加する
    <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2252800/e7035730-b3d5-8f89-b31d-16b78ea454d1.png" width="50%">
1. 「OAuth Tokens」の項目から「Install to {Workspace名}」からワークスペースに追加する
ここで生成されたTokenを後ほど使うので覚えておく
    <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2252800/6187b372-6c36-12f0-e083-63c69ea2f446.png" width="50%">

# 投稿用のGoogleフォームを作成する
1. [Googleフォーム](https://docs.google.com/forms/u/0/)から「空白のフォーム」を作成
1. 質問として「宛先」(短文回答)「投稿内容」(長文回答)を作成
    <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2252800/54572bb5-865e-dafa-48bf-c824a0808b6f.png" width="50%">

# GoogleフォームにSlackへ投稿するスクリプトを記述する
1. 右上のフォームのメニューから「スクリプトエディタ」を開く
1. スクリプトの内容を全て消して以下のコードに書き換える
    ```js
    const ANONYMOUS_POST_BOT_TOKEN = PropertiesService.getScriptProperties().getProperty("ANONYMOUS_POST_BOT_TOKEN");//例：xoxb-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    const FORM_URL = PropertiesService.getScriptProperties().getProperty("FORM_URL");//例:https://docs.google.com/forms/d/xxxxxxxxxxxxxxxxxxxxxxxxxxx/edit#responses
    
    //フォームが投稿されたらフォームの内容をslackで送信して、投稿された内容をすべて削除する
    function getResponse(e){
      var form = FormApp.openByUrl(FORM_URL);
      let itemResponses;
    
      //フォームから直接投稿されたらeに値が入るが、万一入らなかった場合はフォームからデータを取得する
      if (e !== undefined) {
        itemResponses = e.response.getItemResponses();
      } else {
        var formRes = form.getResponses();
        itemResponses =  formRes[formRes.length-1].getItemResponses();
      }
    
      let sendTarget = '';
      let sendMessage = '';
      //1つ目の設問に「宛先」、2つ目の設問に「投稿内容」が入っているため、その順番でそれぞれ内容を取得する
      for(var i = 0; i < itemResponses.length; i++) {
        switch (i){
          case 0:
            sendTarget = itemResponses[i].getResponse();
            break;
          case 1:
            sendMessage = itemResponses[i].getResponse();
            break;
          default:
            break;
        }
      }
      //送信対象と送信内容にエラーがあった場合に、誤った対象に誤った内容が送られてしまうことの方がまずいと考えて空値チェックをしている。
      if (sendTarget !== '' && sendMessage !== '') {
        sendSlackDM(sendTarget, sendMessage);
      }
      deleteRespose();
    }
    
    //このフォームに送られた内容を全て削除する処理
    //フォーム送信時にも実行されるが、万一エラーが発生した時のために別途1日1回実行されるようにトリガー設定済み
    function deleteRespose() {
      var form = FormApp.openByUrl(FORM_URL);
      form.deleteAllResponses();
    }
    
    //slackのDMにbotからメッセージを送信する処理
    function sendSlackDM(sendTarget, sendMessage) {
      var userId = sendTarget;
    
      var url = 'https://slack.com/api/chat.postMessage';
      var payload = {
        token: ANONYMOUS_POST_BOT_TOKEN,
        channel: userId,
        text: sendMessage
      };
    
      var options = {
        method: 'post',
        contentType: 'application/json',
        headers: {
          'Authorization': 'Bearer ' + ANONYMOUS_POST_BOT_TOKEN
        },
        payload: JSON.stringify(payload)
      };
    
      var response = UrlFetchApp.fetch(url, options);
      var result = JSON.parse(response.getContentText());
    
      if (result.ok) {
        Logger.log('Message sent successfully: ');
      } else {
        Logger.log('Error sending message: ' + result.error);
      }
    }
    ```
1. 「プロジェクトの設定」を開く
1. スクリプトプロパティにそれぞれ「ANONYMOUS_POST_BOT_TOKEN」「FORM_URL」を設定する
ANONYMOUS_POST_BOT_TOKEN：SlackBotを作成するの9.で作成したToken
FORM_URL：Googleフォームの回答ページのURL
    <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2252800/92b2024c-d654-bc27-2b75-c8ed739178c4.png" width="50%">
1. 「トリガー」を開き、「トリガーを追加」ボタンから以下のようにトリガーを **2つ** 設定する
【1つ目のトリガー】
実行する関数を選択：getResponse
実行するデプロイを選択：Head
イベントのソースを選択：フォームから
イベントの種類を選択：フォーム送信時
    <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2252800/c19565d0-fd72-3aeb-5eea-f93b03c194c1.png" width="50%">

【2つ目のトリガー】
実行する関数を選択：deleteResponse
実行するデプロイを選択：Head
イベントのソースを選択：時間主導型
時間ベースのトリガーのタイプを選択：日付ベースのタイマー
時間を選択：午前0時〜1時
    <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2252800/ec5a7308-bcc8-f798-8723-c03377f4290b.png" width="50%">

以上でGoogleフォームから投稿した際にslackで匿名投稿を行うシステムが完成しました。
他にも以下のような工夫をすることでより便利に利用することも可能かと思いますので、必要に応じて対応していただけますと幸いです。

# 工夫事項
- チャンネルID(Cから始まるID)を指定してチャンネルへも投稿できるようにAnonymousPostを特定のslackチャンネルにアプリを追加する
- Googleフォームから宛先を選ぶ際に検証機能を使って必ず「Uから始まるユーザID」or 「Cから始まるチャンネルID」が入力されるようにバリデーションを設定しておく
- 入力対象をスプレッドシートと連携してプルダウンで選べるようにする
    - スプレッドシートのデータをプルダウンに連携するには別途GASによる連携が必要です

# おわりに
ここまでご覧いただきありがとうございます。
この記事がどなたかの困りごとを少しでも解消するための一助になると幸いです。