【Google Apps Script】その15　Execution APIを使い、外部からAPIとして叩く

この記事は[Google Apps Scriptを実例交えて基礎からざっくり学ぶ Advent Calendar 2017](https://qiita.com/advent-calendar/2017/gas_beginner)　15日目の記事です。

本アドベントカレンダーは@rt_pの個人プロジェクトですが、筆者は[Ateam Brides Inc. Advent Calendar 2017](https://qiita.com/advent-calendar/2017/a-t-brides)にも参加しています。そちらでも出張版記事を書いているので、覗いていただけると嬉しいです。

# はじめに

遂に来ました！！
本アドベントカレンダーで肝となるExecution APIです。

これが何なのか簡単にお伝えすると、**GET/POSTのAPIとして、GASを外部から叩ける**仕組みです。

…それだけ？と思われましたか？
実はこれ、とても革新的です。

これを使えばGAS以外のWebサーバからGASにデータをPOSTしたり、逆にGETでスプレッドシートの中身をWebサーバで受け取ったり、GASのスクリプト同士をAPIとして連携することもできます。


![command.png](https://qiita-image-store.s3.amazonaws.com/0/199678/2011e3a7-c65e-9876-b6b6-c4189c123603.png)

![sheet4.png](https://qiita-image-store.s3.amazonaws.com/0/199678/f86544e6-3698-0c74-83a1-2ca0f0613bd6.png)

# スプレッドシートの準備

スプレッドシートにダミーデータを準備します。
また、シートに「**アクセス数レポート**」という名前を付けます。

| 日付 | UU |
|------------|-----|
| 2017/12/1 | 100 |
| 2017/12/2 | 120 |
| 2017/12/3 | 100 |
| 2017/12/4 | 110 |
| 2017/12/5 | 130 |
| 2017/12/6 | 140 |
| 2017/12/7 | 130 |
| 2017/12/8 | 120 |
| 2017/12/9 | 110 |
| 2017/12/10 | 110 |
| 2017/12/11 | 100 |
| 2017/12/12 | 130 |
| 2017/12/13 | 120 |
| 2017/12/14 | 110 |
| 2017/12/15 | 160 |
| 2017/12/16 | 110 |

![sheet.png](https://qiita-image-store.s3.amazonaws.com/0/199678/c7759352-a81a-944e-86f1-7b2d3a0a88dc.png)

このスプレッドシートに対し、日付指定でデータを挿入、更新したいと思います。
ちょうど[5日目の記事](https://qiita.com/rf_p/items/f423ed2b23b789b2ebe9)を外部から操作できるようにしているイメージですね。

# GAS内でデータのやり取りができる仕組みを作る

まずは、[5日目の記事](https://qiita.com/rf_p/items/f423ed2b23b789b2ebe9)のコードほぼそのままですが以下内容を追加します。

スクリプトエディタを開き、以下コードに置き換えて実行します。
スクリプトエディタの開き方や*承認が必要です*メッセージが出た際の対処法が分からない場合は
[アドベントカレンダー1日目のHello, world!記事](https://qiita.com/rf_p/items/0535ec495df8f9d2b894)をご参照ください。

```javascript:insert_or_update.gs
function myFunction() {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('アクセス数レポート');
  var newData = ['2017/12/17', 150];
  insertOrUpdate(sheet, newData);
}

// 行の存在に応じて追加もしくは更新を行う
function insertOrUpdate(sheet, data) {
  var row = findRow(sheet, data[0]);
  if (row) { // 行が見つかったら更新
    sheet.getRange(row, 1, 1, data.length).setValues([data]);
  } else { // 行が見つからなかったら新しくデータを挿入
    sheet.appendRow(data);
  }
}

// 行番号を返す
function findRow(sheet, date) {
  var searchDate = Utilities.formatDate(new Date(date), 'Asia/Tokyo','yyyy/MM/dd'); // タイムゾーン違いが面倒なので、GASでの日付比較はgetTimeよりこっちの方が楽
  var values = sheet.getDataRange().getValues();

  for (var i = values.length - 1; i > 0; i--) {
    var dataDate = Utilities.formatDate(new Date(values[i][0]), 'Asia/Tokyo','yyyy/MM/dd');
    if (dataDate == searchDate) {
      return i + 1; // 配列のキーは0から始まり、行数は1から始まるのでズレを直す
    }
  }
  return false;
}
```

変わったところと言えば、

- シートを名前で取得した箇所
- セットする要素の数を減らした

点ぐらいでしょうか。

シートを`getActive()`ではなくシート名で取得した意図としては、外部からAPIとしてGASを実行するとどのシートがアクティブになっているか分からず、思わぬシートに追記されたり動かない不具合を減らす為です。
シート名を直接入力することでやや冗長な書き方にはなりますが、別のシートに書き込まれることは無くなります（ただしシート名を勝手に変更されると動かなくなるリスクが増えます）。

実行すると以下のように、最新の行に新しい値が追加されているはずです。

![sheet2.png](https://qiita-image-store.s3.amazonaws.com/0/199678/96797a3c-0d56-d2b1-28f4-4ae69487af4f.png)

# doGetで値を取得する

スプレッドシートの値を外部から取得できるようにしてみましょう。

APIアプリケーションを公開し**GETのリクエストが来た際、GASは`doGet()`という関数が実行**されます。
今回は`doGet()`が正常に動くか検証する為、デバッグ用に`dummyGet()`を用意してそちらを実行します。

`insertOrUpdate()`、`findRow()`は上記コードをそのまま流用します。
以下コードを追記し、`dummyGet()`を実行します。

```javascript:insert_or_update_do_get.gs
// 行の存在に応じて追加もしくは更新を行う
function insertOrUpdate(sheet, data) {
...
}

// 行番号を返す
function findRow(sheet, date) {
...
}

// doGetのデバッグ用
function dummyGet() {
  var e = {
    'parameter': {date: '2017/12/01'}
  };
  var res = doGet(e);
  var content = res.getContent();
  var getData = JSON.parse(content);
  Logger.log(getData);
  Logger.log(getData['uu']);
}

// getでデータ取得を行う
function doGet(e) {
  var res = {status: null, message: ''};
  try {
    var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('アクセス数レポート');
    var date = Utilities.formatDate(new Date(e.parameter.date), 'Asia/Tokyo','yyyy/MM/dd');
    var row = findRow(sheet, date);
    if (row) { // 値があった場合
      res['uu']      = sheet.getRange(row, 2).getValue();
      res['message'] = 'success';
    } else {
      res['uu']      = null;
      res['message'] = 'no data.';
    }
    res['status'] = 200;
  } catch (e) {
    res['status'] = 500;
    res['message'] = e;
  }
  
  var data = JSON.stringify(res);
  return ContentService.createTextOutput(data).setMimeType(ContentService.MimeType.JSON);
}
```

`dummyGet()`を実行すると、コードに問題がなければ2017/12/01のデータが取得されるので以下のように実行されます。

![dummyLog.png](https://qiita-image-store.s3.amazonaws.com/0/199678/f614dc39-bce7-2387-d841-63b492db629b.png)

この表示が出たら、恐らくウェブアプリケーションとしてAPIを公開しても正常に動作するはずです。

手順としては*公開→ウェブアプリケーションとして導入*を選択します。

![webapp.png](https://qiita-image-store.s3.amazonaws.com/0/199678/0eea7ba1-eabb-f7f5-ef5d-813889d97787.png)

*アプリケーションにアクセスできるユーザー*の選択肢は*全員（匿名ユーザーを含む）*を選択します。
これを選ぶことでGoogleアカウントにログインしていないユーザーでもAPIを叩くことができます。

![open.png](https://qiita-image-store.s3.amazonaws.com/0/199678/d06dd72d-9774-2de7-a991-ec617d48d37f.png)

URLが表示されます。アクセスすると、以下のような表示が出るはずです。

![res.png](https://qiita-image-store.s3.amazonaws.com/0/199678/32c359d1-4ba8-f73c-3c06-9367a6dfdfaf.png)

パラメータがないので正常にデータが取得できていないようです。
GETなのでURLでパラメータを指定しましょう。

https://script.google.com/macros/s/XXXXXXXX/exec?date=2017/12/01

![res2.png](https://qiita-image-store.s3.amazonaws.com/0/199678/c20cb25f-7e09-4b13-e019-84d3c45da1c6.png)

もし
> 現在、ファイルを開くことができません。

という、あまりにもざっくりしすぎたエラーが出たときは、**1回Googleアカウントをログアウトするとうまくいったりします**。

# 超重要！APIのコードを修正したらバージョンを変えること

開発用のURL（最新のコード　のURL）はコードを修正すると即座に反映されますが、本番用のURL（現在のウェブ アプリケーションの URL）は新しいバージョンとして新規作成する必要があります。
これに気付くまで、いくらコード修正しても動かないことに頭を悩ませていました…

![version.png](https://qiita-image-store.s3.amazonaws.com/0/199678/229c5c26-231a-0b34-1973-9d04c2f23d6b.png)

# doPostで値を追加する

スプレッドシートの値を外部から追加・更新できるようにしてみましょう。

APIアプリケーションを公開し**POSTのリクエストが来た際、GASは`doPost()`という関数が実行**されます。
doGetに対するdummyGetと同じように、`doPost()`が正常に動くか検証する為、デバッグ用に`dummyPost()`を用意してそちらを実行します。

`insertOrUpdate()`、`findRow()`、`dummyGet()`、`doGet()`は上記コードをそのまま流用します。
以下コードを追記し、`dummyPost()`を実行します。

```javascript:insert_or_update_post.gs
// 行の存在に応じて追加もしくは更新を行う
function insertOrUpdate(sheet, data) {
...
}

// 行番号を返す
function findRow(sheet, date) {
...
}

// doGetのデバッグ用
function dummyAccess() {
...
}

// getでデータ取得を行う
function doGet(e) {
...
}

// doPostのデバッグ用
function dummyPost() {
  var e = {
    'parameter': {date: '2017/12/17', uu: 1000}
  };
  var res = doPost(e);
  var content = res.getContent();
  var getData = JSON.parse(content);
  Logger.log(getData);
}

// postでデータ追加を行う
function doPost(e) {
  var res = {status: null, message: ''};
  try {
    var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('アクセス数レポート');
    var date = Utilities.formatDate(new Date(e.parameter.date), 'Asia/Tokyo','yyyy/MM/dd');
    var uu   = e.parameter.uu;
    var newData = [date, uu];
    insertOrUpdate(sheet, newData);
    res['status'] = 200;
    res['message'] = 'success';
  } catch (e) {
    res['status'] = 500;
    res['message'] = e;
  }
  
  var data = JSON.stringify(res);
  return ContentService.createTextOutput(data).setMimeType(ContentService.MimeType.JSON);
}
```

コードが合っていると、スプレッドシートに値が追加されます。
日付や値を変えて実行してみましょう。

![sheet3.png](https://qiita-image-store.s3.amazonaws.com/0/199678/9bf0a505-a9db-403e-fd62-8618e0bf3bc9.png)

あとは、これがPOSTリクエストの際に正常に動作するか試してみましょう。

WebブラウザでPOSTリクエストを送るのは面倒なので、Linuxコマンドラインでcurlを実行します。

![command.png](https://qiita-image-store.s3.amazonaws.com/0/199678/2011e3a7-c65e-9876-b6b6-c4189c123603.png)

> curl -L -d "param1=value1&param2=value2" http://example.com/

-L　リダイレクトを追う
-d　POSTパラメータの指定

成功すると、以下のようにスプレッドシートに値が追加されているのが確認できます。

![sheet4.png](https://qiita-image-store.s3.amazonaws.com/0/199678/f86544e6-3698-0c74-83a1-2ca0f0613bd6.png)

本日は以上です。

# おわりに

本アドベントカレンダーでは、今後もこのExecution APIを使ってアプリケーション同士を組み合わせていきます。

# 明日

[【Google Apps Script】その16　GASとスプレッドシートだけで簡易RSSリーダーを作り、Chatworkに新着通知する
](https://qiita.com/rf_p/items/3e20e4114a1182deb7bc)
GASでRSSリーダーを作って、Chatworkに通知してみます。

前の記事
[【Google Apps Script】その14　SEO用にタイトルとディスクリプションを収集する](https://qiita.com/rf_p/items/594d52f44b742489bbdc)
次の記事
[【Google Apps Script】その16　GASとスプレッドシートだけで簡易RSSリーダーを作り、Chatworkに新着通知する
](https://qiita.com/rf_p/items/3e20e4114a1182deb7bc)