Google Apps Script と EDITROOM の RESTAPI 連携の実例解説１：GAS から API を発火する

# 目次

-   [目次](#目次)
-   [はじめに](#はじめに)
    -   [前提知識](#前提知識)
        -   GAS に関する前提知識
    -   [前提条件・準備](#前提条件準備)
-   [作業](#作業)
    -   [ユースケース](#ユースケース)
    -   [Slack での作業](#slack-での作業)
    -   [GASでの作業](#gasでの作業)
        1. Google Sheets からスプレッドシートを作成 
        2. GAS の作成, doPost() の用意
        3. リクエストからパラメータを抽出する
        4. EDITROOM APIを発火する
        5. 結果をSlackで通知する
-   [所感](#所感)
-   [おわりに](#おわりに)

# はじめに

アップデイティットの毛利です。

先日、弊社から「[EDITROOM](https://editroom.updateit.co.jp/)」という BtoB 向けの文書作成クラウドサービスをリリースしました。  
データの収集・検証から、文書作成・配布までの一連を自動化するだけでなく、REST API を用いたサービス連携の実現を目指しています。

本記事では、EDITROOM で提供する REST API 群を用い、
開発自由度の非常に高いローコードプラットフォーム Google Apps Script(GAS)を利用した、実例を紹介できればと思います。  

## 前提知識

本記事は、主に Google Apps Script の使い方に視点を置いた記事を心がけます。  
――ですので、EDITROOM を使用する箇所については、適宜自分の利用したいサービスに読みかえていただければ、幸いです。

以後、Google Apps Script は「GAS」表記に統一します。

また、以下の知見を有していると読み進めやすいです。

-   JavaScript の知見がある方を想定読者とします。
    -   最低限、ES5,ES2015(ES6)周りのコードが読めること。
    -   自分で実装ができると尚良し
-   REST API の使い方
    -   cURL や Postman で触ったことがあれば尚良し
-   (opt.) Slack 
    -   今回、ユースケースの出口として使用します。

### GAS に関する前提知識

GAS について、以下の点を事前に押さえておくと、解説の理解が進みます。

-   doGet() / doPost() について
    -   この名前の関数を記述してデプロイすると Webhook URL として機能する（固定）。
    -   リダイレクトの追従設定を切ってリクエストすると、「処理は実行されるがレスポンスが返らない」状態になる。
-   プライベート関数は名前の末尾に「\_」が必須。
    -   これを忘れるとエラーになる。
    -   e.g.) function privateSum\_(a,b) { ...
-   [上限及び制限](https://developers.google.com/apps-script/guides/services/quotas#current_limitations)
    -   1 日あたりの外部サービス URL 呼び出し上限が 20000 回
    -   1 回あたりの実行タイムアウトはおおよそ 6 分。
    -   同時実行は 30 まで
-   実行環境は、 Chrome V8 ランタイム上で実行される(2022/11 現在時点のデフォルト設定)。
    -   ES5 は完全動作
    -   一部を除く ES6(ES2015)相当の表現を解釈可能
-   独自 API で Google 製の他サービスと API 連係が可能。
    -   特に、Google Sheets(スプレッドシート)経由で呼び出せば、簡単に Excel 表をログ置き場として利用できる。

## 前提条件・準備

-   Google アカウント
-   EDITROOM アカウント
    -   [初めてサービスを利用する方向けの初期セットアップ](https://editroom.updateit.co.jp/readme/)
        を完了させて、REST API を実行できるようにしておいてください。
    -   後述するユースケースに沿ったベースドキュメントの構成が必要となります。
-   (opt.) Slack 
    -   今回、ユースケースの出口として使用します。

# 作業

## ユースケース

次の一連の動作を GAS で動かせるようにしてみます。

1.  cURL を使い GAS の Webhook URL に発火する
2.  リクエストから、EDITROOM API の発火に必要なパラメータを取り出す。
3.  GAS から EDITROOM に文書作成 API を発火する。
4.  API の結果を担当ユーザーに対して通知する(Slack Incoming Webhook)。

## Slack での作業

最初に、Slack への通知につかう Incoming Webhook の URL を用意します。  
[Slack公式の「Slack での Incoming Webhook の利用」](https://slack.com/intl/ja-jp/help/articles/115005265063-Slack-%E3%81%A7%E3%81%AE-Incoming-Webhook-%E3%81%AE%E5%88%A9%E7%94%A8)を参考に、Incoming Webhook URL を作成・運用していきます。

他の手段としては、[アプリの「Incoming Webhook」を利用](https://slack.com/apps/A0F7XDUAZ--incoming-webhook-?tab=more_info)する方法もありますが、現在は非推奨です。（古い解説記事では、このアプリについて解説されたものをしばしば見かけます、ご注意ください。）

![pic_01.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953427/4c837606-b541-7092-615c-d1e0a6dcfdfb.jpeg)

新規アプリを作成したら、「Incoming Webhooks」を有効化します。  

![pic_02.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953427/8a4427e8-dfec-1d83-c285-8c5bf57b42c6.jpeg)

有効化したら、専用の URL が払い出されるため、この URL を使用します。  

次に、「Add New Webhook to Workspace」を押し、送信先のチャンネルまたはユーザーを指定します。  
従来仕様の解説では、しばしばJSON 側で送り先をハンドリングできると書かれていますが、そういった機能を使うための API は別に用意された関係か、現在は最初に設定した送り先で固定になっています。 

![pic_03.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953427/71da1a7c-b6e2-fac9-8d16-ff0534ee8623.jpeg)


一覧を開くとチャンネルしか出ませんが、無視して「＠」を入力するとユーザーの一覧に変更できます。
今回は、自分に DM するように設定し、「許可する」を押して接続完了です。

最後に、サンプルの cURL コマンドを実行して動作したら、Slack 側での作業は完了です。

## GASでの作業

今後の拡張を考慮し、スプレッドシート経由で GAS を設定してログをシートに残しやすいようにします。

### 1. Google Sheets からスプレッドシートを作成

「空白」を選択し、真っ新なシートを作成してください。

![pic_04.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953427/eab9c3b4-9004-c435-d7e9-3c33a810d861.jpeg)

ワークブック名は自由に付けてください。  
シート名も自由に付けて構いませんが、今後のことを考えて「ログ」としておきます。

![pic_05.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953427/0a8ae1bf-15a3-15ca-3c55-57421d63ead2.jpeg)

### 2. GAS の作成, doPost() の用意

「拡張機能」→「Apps Script」を選択して GAS のエディタを呼び出してください。

![pic_06.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953427/629ee504-1b9c-fa19-8472-2718ba3f811b.jpeg)

![pic_07.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953427/a8b404e3-860d-2c83-97b9-de4989c9252c.jpeg)

次に、次のようなサンプルコードを入力します。

```JavaScript
function doPost(event) {
  
  // TODO
  // const res = editroomApi_(json);
  // postSlack_(res);

  let output = ContentService.createTextOutput();
  output.setMimeType(ContentService.MimeType.TEXT);
  output.setContent("OK");

  return output;
}
```

保存したら「デプロイ」→「新しいデプロイ」からデプロイ設定画面を開きます。

![pic_08.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953427/16bbc6c5-a4a7-d26e-0862-4c91853707de.jpeg)

![pic_09.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953427/0bd0c854-60ee-8986-b5cd-db1bb7aa43a1.jpeg)

Webhook URL として機能させるためには、次の２つの設定が必要です。

-   「次のユーザーとして実行」を自分自身、または特定のユーザーに固定すること。
-   「アクセスできるユーザー」を、「全員」にすること。

このどちらかが違う設定になっていると、API 実行時にログイン認証画面へリダイレクトされます。  
（URL を実行するために Google アカウントのセッションを要求される）

![pic_10.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953427/6671679e-7d16-cc29-c15b-650b13044013.jpeg)

正しくデプロイされると URL が発行されます。  
試しに、この値を cURL を使って接続してみてください。

```sh
curl -L -d "" https://script.google.com/macros/s/ABCDEFG....XYZ/exec

> OK
```

詳細の解説は省きますが、GAS で払い出される URL 先の挙動は特殊で、cURL の標準的な御作法では動作しません。  
-X POST を付与すると NG、-L を付与しないと NG など...(そして、GET/POST のどっちを使うかで更に異なる)  

ひとまず、サンプルコードのレスポンスが「OK」となれば疎通成功です。  

### 3. リクエストからパラメータを抽出する

EDITROOM API に引き渡すためのパラメータをリクエストから抽出するコードを追加します。

まずは、次のようにコードを改修してみましょう。

```JavaScript
function doPost(event) {
  // -- extract parameters from request
  let json = {};
  let body = "";
  try {
    // body = JSON.parse(event.postData.getDataAsString());
    const jsonString = event.postData.getDataAsString();
    json = JSON.parse(jsonString);
    body = JSON.stringify(json, null, 0);
  } catch (e) {
    body = `${e}`;
  }

  // TODO
  // const res = editroomApi_(json);
  // postSlack_(res);

  let output = ContentService.createTextOutput();
  output.setMimeType(ContentService.MimeType.TEXT);
  output.setContent(body);

  return output;
}
```

保存をしたら、先ほどデプロイしたばかりですが再度デプロイを行います。  
「デプロイ」⇒「デプロイを管理」⇒「編集」⇒「バージョンを『新バージョン』にする」⇒「デプロイ」  
でデプロイを更新できます。

![pic_11.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953427/d42c2237-8522-4ebe-0b5d-273afd954339.jpeg)

![pic_12.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953427/850c990e-2f21-0052-c9ab-9d5f42f1df56.jpeg)

デプロイしたら、次の cURL サンプルを実行してみてください。与えた JSON が、レスポンスに返されているを確認できます。  

```sh
curl -L -d '{"year":2017, "export_type":"pdf"}' https://script.google.com/macros/s/ABCDEFG....XYZ/exec

> {"year":2017,"export_type":"pdf"}
```

処理上では、一度 JSON を分解した後、もう一度文字列に戻しています。  
この過程が正常に動いたことで、リクエストから値を抽出する処理が正常に動いたことを確認できました。

### 4. EDITROOM APIを発火する

先ほどのサンプルのパラメータ「year」と「export_type」を使って、EDITROOM API の generate API(POST) を発火します。  
API に与えるパラメータは、弊社であらかじめ作成したデータ構成を基に作成しているため、値の意味等はあまり気にせず読み進めてください。

まず、コメントアウトしていた次の関数を実装します。

> // const res = editroomApi\_(json);

この行のコメントアウトを外すと共に、新しく関数を１つ、doPost()の真下に追加作成します。

```JavaScript
function doPost(event) {
  ～（中略）～

  // TODO
  const res = editroomApi_(json);
  // postSlack_(res);

  ～（中略）～
  output.setContent(res);
  
  return output;
}

const editroomApi_ = (jsonBody) => {
  const url = "https://xxxx.updateit.jp/editroom/bw/abcde....xyz/v1/generate";
  const method = "post";
  const headers = { "Content-Type": "application/json", "X-EDITROOM-API-KEY": "ABCDE....XYZ" };
  const payloadObj = {
    docflow_name: "運用報告書生成docflow2",
    export_type: jsonBody["export_type"],
    records: {
      作成年: jsonBody["year"],
      表題: "サンプル銘柄運用商品",
    },
  };
  const payload = JSON.stringify(payloadObj, null, 0);
  // --
  const options = { method, headers, payload };
  const res = UrlFetchApp.fetch(url, options);
  return res.getContentText();
};
```

このうち、「UrlFetchApp.fetch()」が、外部へ通信するために用意された GAS 独自の関数です。  
ブラウザや Node.js などでよく使用する fetch()関数とは少し異なるので注意してください。

コードを修正したら、またデプロイを行います。  
この際、「このウェブ アプリケーションを使用するには、データへのアクセスを許可する必要があります。」という、次のような画面が表示されます。

![pic_13.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953427/3b7f05e6-8aa5-f476-5069-5c1ffabe81a2.jpeg)

EDITROOM API に接続するコードを追加した関係で、GAS から外部サービスに向けて実行して良いかを確認されることがあります。  
その時は、次の画像を参考に許可するよう進めてください。

![pic_14.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953427/8d41d793-1da6-77fc-8ada-84f124395db9.jpeg)

![pic_15.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953427/2384162a-227d-30f5-579c-361160265c28.jpeg)

問題なくデプロイできたら、前回使用した cURL コマンドをもう一度叩きます。

```sh
curl -L -d '{"year":2017, "export_type":"pdf"}' https://script.google.com/macros/s/ABCDEFG....XYZ/exec

> {
>   "result": "SUCCESS",
>   "request_id": "bcp8jq4m930grsa5pu2sqbkiml"
> }⏎
```

これで、generate API における正常系のレスポンスが返却されていることが確認できました。

### 5. 結果をSlackで通知する

最後に、generate API の結果を Slack に通知する処理を実装します。  
EDITROOM API を発火した時とほとんど同じような内容のコードを、更に追加します。

```JavaScript
function doPost(event) {
  ～（中略）～

  const res = editroomApi_(json);
  postSlack_(res);

  ～（中略）～
  output.setContent("OK");

  return output;
}
const editroomApi_ = (jsonBody) => {
  ～（中略）～
}

const postSlack_ = (res) => {
  const url = "https://hooks.slack.com/services/ABC.../...XYZ";
  const method = "post";
  const payloadObj = { text: res };
  const payload = JSON.stringify(payloadObj, null, 0);
  // --
  const options = { method, payload };
  UrlFetchApp.fetch(url, options);
};
```

cURL に対するレスポンスは「OK」になるよう戻しました。  
その代わり、今回追加した Slack への通知の中で、EDITROOM API の結果を渡すようにしています。

デプロイしたら、早速結果をみてみましょう。

```sh
curl -L -d '{"year":2017, "export_type":"pdf"}' https://script.google.com/macros/s/ABCDEFG....XYZ/exec

> OK
```

![pic_16.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953427/007098fd-a15b-f53c-c7ce-606c6294de14.jpeg)

Slack に結果が通知されることを確認できました。とても便利です。

これで、全ての作業が完了です。  
お疲れ様でした。

もし余力があれば。「スプレッドシートをログ代わりに使用」したり「エラーハンドリング処理を実装」したりすれば、より安全に処理を利用できるでしょう。

# 所感

今回は、プログラムを書いてクラウドサービスをハンドリングする実例を解説しました。

開発経験がある方であれば、ノーコードサービスでポチポチやるよりも GAS などを駆使した方が、素早くワークフローを立ち上げることができます。  
一方で、「プログラムは一度書いたら終わり」とはならないもので、以後メンテナンスや障害時対応のタスクが待っています。（あと属人化の危険性も！）

ともあれ、この記事の内容を実践できるかどうかは、Zapier や make のようなノーコードサービスを導入すべきか、開発者を立てて回すかの判断基準の１つになるのではないか、と思います。

# おわりに

再度の紹介となりますが、先日、弊社から「[EDITROOM](https://editroom.updateit.co.jp/)」という BtoB 向けの文書作成クラウドサービスをリリースしました。  
特に、定期的に文書を作る人的コストや作業にかかる拘束時間の長さを改善するのに、このサービスが大きな一助になると自負しております。

もし、ご興味がありましたら、トライアルをご利用いただければ幸いです。