0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

コラボフロー Advent Calendar 2023」3日目の記事だよ~
コラボフローの詳細は こちら


去年はJavaScriptカスタマイズしたので、今年はWebhookカスタマイズで遊んでみました。

コラボフローでジャンケン!

対戦よろしくお願いします。

image.png

申請して挑みます。今回は「ぱー」でいきましょう!

image.png

じゃんけん~

image.png

image.png

どきどき・・・
 

 

 

 

 

image.png

うわぁぁーー! 負けちゃいました。

戦績が見れるように リストビュービューフォルダ設定 を組み合わせて一覧表示してみました。

image.png

ちょき率が高そう。ふむふむ・・・。もう一回!

といきたいですが、そろそろ本題(実装)の紹介です。

用意するもの

コラボフロー

30日間無料お試し からクラウドの環境を申し込めます。

Webhook 受信サーバー

Webhook は指定したURLに対して何かしらのイベント通知を行う仕組みで、コラボフローではPOST送信が使われています。イベント通知の受け手となる受信サーバー&プログラムの用意が必要です。

今回は、お手軽に使えるみんな大好き Google Action Script (GAS) を使います。

大まかな流れ

ざっくりとこんな流れでイベント・処理がすすみます。

  1. コラボフロー で申請書が出されて判定段階に到達する
  2. コラボフロー Webhook 通知が発火する
  3. GAS doPost(e) メソッドが呼び出される
    • e.postData.contents に通知内容が入っている
  4. GAS の自前処理
    • 判定段階チェック
    • ジャンケン処理、勝ち負け判定
    • コラボフローの判定REST APIを実行

実装

GAS で新しくプロジェクトを作成して「コード.gs」に Webhook を受信するための doPost() メソッドの実装と、フィードバックするためにコラボフローのREST API設定・認証情報を組み込みます。

image.png

ソースコード

コード.gs
/** APIエンドポイント (システム管理>環境変数>REST API) */
const API_ENDPOINT = "https://cloud.collaboflow.com/example/api/index.cfm";
/** 実行ユーザーID */
const API_USER = "tonakai";
/** APIキー */
const API_KEY = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";

/**
 * POST で受け取る処理
 */
function doPost(e) {
  const out = ContentService.createTextOutput();
  out.setMimeType(ContentService.MimeType.JSON);

  const result = handlePost(e);

  out.setContent(JSON.stringify({
    ...result
  }));
  return out;
}

function handlePost(e) {
  // GASで受信時はJSON形式の文字列。オブジェクトに変換します
  const payload = e?.postData?.contents;
  if (!payload || typeof payload !== "string") {
    return {
      error: "エラー: 不正な payload",
    };
  }
  const webhook = JSON.parse(payload);

  // It's ゲームタイム!
  if (isGameTime(webhook, "ジャンケン相手")) {
    return gameJudge(webhook);
  }
}

/**
 * 指定した判定段階への到達チェック
 * @param phaseTitle {string} 判定アイテムで設定した判定名
 */
function isGameTime(webhook, phaseTitle) {
  // 判定中で受信時
  if (webhook.flow_status === "flow" && webhook.action_type === "receive") {
    if (!Array.isArray(webhook.determs)) {
      return false;
    }

    // 段階名チェック
    for (const d of webhook.determs) {
      if (d.phase_title === phaseTitle) {
        return true;
      }
    }
  }

  return false;
}

/**
 * ジャンケン!
 */
function gameJudge(webhook) {
  const appId = webhook.app_cd;
  const docId = webhook.document_id; // 文書ID
  const userName = webhook.request_user.name; // 申請者名
  const doc = webhook.contents; // 申請内容

  // トナカイの手札を決定する
  const hands = ["ぐー", "ちょき", "ぱー"];
  const cpuHandId = Math.round(Math.random() * 2);
  const cpuHand = hands[cpuHandId];

  // 挑戦者の手札を把握する
  const fighterHand = doc.fidFighterHand.value;

  // ジャッジメントですの!
  let judge = `「${fighterHand}」と「${cpuHand}」で、`;
  if (fighterHand === cpuHand) {
    judge += "あいこでした";
  } else {
    // あいこ以外
    // 「挑戦者の手札vsトナカイの手札」形式で組み合わせキーにする
    const matching = {
      "ぐーvsちょき": "勝ち!",
      "ぐーvsぱー": "負け",
      "ちょきvsぐー": "負け",
      "ちょきvsぱー": "勝ち!",
      "ぱーvsぐー": "勝ち!",
      "ぱーvsちょき": "負け",
    };
    const key = `${fighterHand}vs${cpuHand}`;
    judge += `${userName}さんの` + matching[key];
  }

  // コラボフローに判定フィードバック
  return api_acceptDocument(appId, docId, {
    fidCpuHand: cpuHand,
    fidJudge: judge,
  });
}

/**
 * 認証ヘッダーを生成
 */
function makeAuthHeader() {
  const credential = `${API_USER}/apikey:${API_KEY}`;
  return {
    "X-Collaboflow-Authorization": "basic " + Utilities.base64Encode(credential),
  }
}

/**
 * 申請書を承認判定する
 * @param appId {number} アプリケーションコード
 * @param docId {number} 対象の文書ID
 * @param document {object} 更新内容
 */
function api_acceptDocument(appId, docId, document) {
  // @see http://docs.collaboflow.com/api-docs/#/Document/putDocumentStatus
  const apiUrl = `${API_ENDPOINT}/v1/documents/${Number(docId)}`;
  const body = {
    app_cd: appId,
    action: "accept",
    document,
  };

  const response = UrlFetchApp.fetch(apiUrl, {
    method: "PUT",
    headers: makeAuthHeader(),
    contentType: "application/json",
    payload: JSON.stringify(body),
    muteHttpExceptions: true,
    followRedirects: false,
  });

  const responsePayload = response.getContentText("UTF-8");
  return {
    success: response.getResponseCode() === 200,
    result: responsePayload,
  };
}

GAS 設定

コードを書いたらデプロイして、Webbhook で受け付けれるURLを入手します。
「デプロイ>新しいデプロイ」を選択します。

デプロイ>新しいデプロイ

続いて、歯車アイコン>ウェブアプリを選択します。

歯車アイコン>ウェブアプリ

名称を適宜入れ、アクセスできるユーザーを「全員」にしてデプロイします。

003.png

初回のみ、各種アクセス権限を付与していいか確認があります。許可して続行します。

デプロイが終わると、ウェブアプリの URL が表示されます。これをコピーしてメモしておきます。
※後でも「デプロイ>デプロイを管理」から見れます。

006.png

アクセスできるユーザーが全員の、ウェブアプリURLは知られると誰でも送信できてしまいます。漏れないよう取扱いに注意します。

コラボフロー設定

フォーム

image.png

パーツの例。「パーツID」の自動OFFにして fidFighterHand と指定して作ります。
相手側は fidCpuHand と指定して作ります。これで Webhook 受信時もこれらの名称が使えます。

image.png

判定メッセージも受け取れるようにしましょう。

image.png

経路・判定設定

image.png

申請書類の設定で、フォームと紐付け、ジャンケン挑戦者(申請者)がズルできないように相手の手をブロックします。

image.png

判定アイテムの設定でポイントになるのは2つ。

  • 判定アイテムの段階名を Webhook 内でチェックする段階名(phase_title)と一致させる。
  • GAS の応答で書き込むパーツを編集可にする。

image.png

一通り経路設定ができたら保存します。

経路・Webhook 設定

いよいよ最後です。GAS へのつなぎ込みをします。

image.png

Webhook のタブから新規登録します。

  • 通知先のWebhook URLに、メモしたウェブアプリのURLを入れます。
  • 通知条件で、判定者・回覧者操作の「受信時」のみチェックを付けます。
    (action_type で受け取る "receive" に相当します。)

image.png

完成!

これで GAS を利用してコラボフローの Webhook 通知を処理できるようになりました。

あとは、トナカイさんに挑みましょう。ねらい目はぐー✊ですね。

リファレンス

コラボフロー関連

Google Action Script 関連

謝辞

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?