回答データの連携

スプレッドシートに回答を送る

Googleスプレッドシートへの直接連携(Google認証)は行っていませんが、Google Apps Scriptで簡単な中継スクリプトを作ることで、無料でスプレッドシートへの自動記録が実現できます。

1行目には「送信日時 / pageId / source / 送信元URL / セッションID / ステータス / 最終更新日時」の固定列に続けて質問ラベルの見出しが作られ、 2行目以降に訪問ごとの回答が1行ずつ記録されます。「送信元URL」には送信元のページのURLが入り、 埋め込み(iframe)経由の場合は埋め込み先のページのURLが記録されます。 あとからフローに質問を追加・変更しても、既存の列はそのままに新しい質問の列だけが末尾に追加されるので、 表の列がずれたり上書きされたりすることはありません(スクリプト自体の更新で固定列が増えた場合も同様に、 既存シートでは新しい固定列が末尾に追加されます)。
  1. 1. 記録先のGoogleスプレッドシートを用意する

    新規、または既存のGoogleスプレッドシートを開きます。

    開いたときのURL(https://docs.google.com/spreadsheets/d/スプレッドシートID/edit)に含まれる「スプレッドシートID」の部分を控えておきます。手順2で使います。

  2. 2. Apps Scriptを作成する

    スプレッドシートのメニューから「拡張機能」→「Apps Script」を開き、デフォルトのコードを消して以下を貼り付けます。SPREADSHEET_IDの値は、手順1で控えたスプレッドシートIDに置き換えてください。

    function doPost(e) {
      // 記録先のスプレッドシートIDを指定します。スプレッドシートを開いたときのURLの
      // https://docs.google.com/spreadsheets/d/【ここ】/edit の部分です。
      var SPREADSHEET_ID = "ここにスプレッドシートIDを貼り付け";
    
      var data = JSON.parse(e.postData.contents);
      // complete: 回答完了時 / progress: 回答ごとの途中経過(「回答ごとに送信」設定時のみ届く)
      if (data.event !== "complete" && data.event !== "progress") {
        return ContentService.createTextOutput(JSON.stringify({ ok: true })).setMimeType(ContentService.MimeType.JSON);
      }
    
      // 回答ごとの送信は同じ訪問から短い間隔で続けて届くため、同時に処理して行が重複しないようロックする。
      var lock = LockService.getScriptLock();
      lock.waitLock(30000);
      try {
        var ss = SpreadsheetApp.openById(SPREADSHEET_ID);
        var sheet = ss.getSheetByName("回答") || ss.insertSheet("回答");
    
        var FIXED_HEADERS = ["送信日時", "pageId", "source", "送信元URL", "セッションID", "ステータス", "最終更新日時"];
    
        // 1行目(ヘッダー行)を読み込む。まだ無ければ固定列だけで作成する。
        var lastColumn = sheet.getLastColumn();
        var headers = lastColumn > 0 ? sheet.getRange(1, 1, 1, lastColumn).getValues()[0] : [];
        if (headers.length === 0) {
          headers = FIXED_HEADERS.slice();
          sheet.getRange(1, 1, 1, headers.length).setValues([headers]);
        }
    
        // 固定列・質問ラベル(answer.name)列とも、ヘッダーに無ければ末尾に列を追加する。
        // フローを編集して質問が増えたときだけでなく、スクリプト更新で固定列が増えたときも、
        // 既存の列は上書き・並べ替えせず末尾に追加するだけにすることで、以前から使っているシートを壊さない。
        FIXED_HEADERS.concat(data.answers.map(function (answer) { return answer.name; })).forEach(function (name) {
          if (headers.indexOf(name) === -1) {
            headers.push(name);
            sheet.getRange(1, headers.length).setValue(name);
          }
        });
    
        // 同じ訪問(セッションID)の行が既にあれば、その行を上書きする(1訪問=1行)。
        var rowIndex = -1;
        if (data.sessionId && sheet.getLastRow() > 1) {
          var found = sheet
            .getRange(2, headers.indexOf("セッションID") + 1, sheet.getLastRow() - 1, 1)
            .createTextFinder(data.sessionId)
            .matchEntireCell(true)
            .findNext();
          if (found) rowIndex = found.getRow();
        }
    
        // 現在のヘッダーの並びに合わせて1行分のデータを組み立てる(列がずれないように)。
        var now = new Date();
        var row;
        if (rowIndex === -1) {
          row = new Array(headers.length).fill("");
          row[headers.indexOf("送信日時")] = now;
        } else {
          row = sheet.getRange(rowIndex, 1, 1, headers.length).getValues()[0];
        }
        row[headers.indexOf("pageId")] = data.pageId;
        row[headers.indexOf("source")] = data.source;
        row[headers.indexOf("送信元URL")] = data.sourceUrl || "";
        row[headers.indexOf("セッションID")] = data.sessionId || "";
        row[headers.indexOf("最終更新日時")] = now;
        // 完了済みの行は、通信の順序が前後して後から届いた途中経過で「回答中」に戻さない。
        var statusIndex = headers.indexOf("ステータス");
        if (row[statusIndex] !== "完了") {
          row[statusIndex] = data.event === "complete" ? "完了" : "回答中";
        }
        data.answers.forEach(function (answer) {
          row[headers.indexOf(answer.name)] = answer.value;
        });
    
        if (rowIndex === -1) {
          sheet.appendRow(row);
        } else {
          sheet.getRange(rowIndex, 1, 1, headers.length).setValues([row]);
        }
      } finally {
        lock.releaseLock();
      }
    
      return ContentService
        .createTextOutput(JSON.stringify({ ok: true }))
        .setMimeType(ContentService.MimeType.JSON);
    }
  3. 3. ウェブアプリとしてデプロイする

    Apps Scriptエディタ右上の「デプロイ」→「新しいデプロイ」を選び、種類として「ウェブアプリ」を選択します。

    以下の設定にしてください。

    • 実行するユーザー: 自分
    • アクセスできるユーザー: 全員

    「デプロイ」をクリックすると、https://script.google.com/macros/s/.../execという形式のURLが発行されます。このURLをコピーしておきます。

    スクリプトの内容を変更した場合は、「デプロイを管理」から既存のデプロイを「編集」し、 バージョンを「新バージョン」にして再デプロイしないと変更が反映されません。
  4. 4. chachat.page側にURLを登録する

    ビルダーの「設定」タブ →「外部サービス連携」→「スプレッドシート」カードで、以下のように登録します。

    • 送信先URL: 手順3で発行されたURL

    保存後、「テスト送信」ボタンでスプレッドシートに行が追加されることを確認してください。

送信タイミングについて

スプレッドシートカードの「送信タイミング」で、記録するタイミングを選べます。

  • 完了時のみ送信:会話を最後まで進めた訪問者の回答だけが、完了時に1行追加されます(ステータスは「完了」)。
  • 回答ごとに送信:質問に答えるたびに、その訪問者の行が作成・更新されます。途中で離脱した訪問者の回答も ステータス「回答中」として残り、最後まで進むと同じ行が「完了」に変わります。
「回答ごとに送信」を使うには、このページに掲載している最新のスクリプトが必要です。 以前のスクリプトをお使いの場合は、手順2のコードに差し替えたうえで再デプロイしてください (古いスクリプトのままだと、途中経過は記録されず完了時のみ記録されます)。

シークレットについて

スプレッドシート連携の「シークレット」を設定すると、送信時にAuthorization: Bearer <シークレット>ヘッダーが付与されます。ただしGoogle Apps ScriptのdoPost関数はリクエストヘッダーを読み取れないため、この方式では検証できません。 第三者からの不正な呼び出しを防ぎたい場合は、シークレットの代わりに送信先URLの末尾へ任意のトークンをクエリパラメータとして付与し (例: …/exec?token=あなたが決めた文字列)、 スクリプト内でe.parameter.tokenを検証する方法をご利用ください。