SORACOM API を Google Apps Script から叩くための汎用ライブラリの使い方

SORACOM 、使ってますか？

製造業のお偉いさんが『やれ IoT だ！』『やれ プラットフォームだ！』と騒いだ後、キャッチアップされる担当の人は一度は見聞きしたことがあるサービスかと思います。

<img src="https://docs.google.com/drawings/d/e/2PACX-1vRk-BG18mMZ003il9RXzKsR37xGU4CoADStqSHGltU82eKkYRkgd388qNAeXyCbic9C5VVhgXdLgNkp/pub?w=622&amp;h=150">

SORACOM は、ソフトウェア・ハードウェアのどちらのエンジニアにも『IoT の入り口』として良いサービスです。また、精力的にハンズオンやカンファレンス、セミナーなどを開催されていて、ユーザーグループなどのコミュニティも活発とのことです。

# TL;DR

- GAS で SORACOM API を使うなら認証や API を叩く処理は汎用ライブラリを使うことで楽ができる
- 汎用ライブラリの API ID は :arrow_down: 

`MJlievwjcIu8-7grDZqGf-FSucpP_quDO`

- GAS のスクリプトプロパティに SORACOM の SAM ユーザー認証情報を設定すれば使うことができる。
- 自分の GAS のプロジェクトに `getAccessToken` 関数の作成は必要（当記事のテンプレをコピペ）

# この記事の内容

タイトルの通りです。Google Apps Script（以下、GAS）から SORACOM API を実行するための汎用ライブラリの使い方を説明します。

<img src="https://docs.google.com/drawings/d/e/2PACX-1vR_fwuhSsWyKXzC-L5NxkLRvzja33jmS0Gt-dYsvC8tarZ88pIRcvD1t5yST9RhxWDmwLvXtQYtzd_c/pub?w=1088&amp;h=157">

おまけとして Google Spreadsheets（以下、Spreadsheets）へ出力する方法も記載します :wink: 

# 本記事の対象読者

- SORACOM で使う SIM や Inventory の Device、Sigfox Device 等々の情報が増えてきた or 今後増えそう
- GAS から SORACOM API をもっと簡単に使いたい
- ついでに Spreadsheets へ出力してごにょごにょしたい

# 経緯

弊社の関連会社が提供しているサービスで一部 SORACOM （Air, Harvest, Gate）を利用しております。SIM やデバイス、デバイスから取得したデータなどの管理は、数が少ないときは無料で提供されている SORACOM User Console で事足りるのですが数が多くなるに連れて**全件を一覧で見たい** 、**データを別の形に加工したい**といったニーズが出てきました。

具体的には、

- 登録されているグループや SIM の一覧を取得したり、
- Harvest に登録されたデータを取得したり、
- 閉域網（VPG）で遠隔操作するための簡易的な Web ページを作ったり

などです。

対応する方法としては、SORACOM とサードパーティのサービスを連携することで実現することもできるかもしれませんが、[SORACOM では API が公開されている](https://dev.soracom.io/jp/docs/api/)ので、**手っ取り早く無料でできる方法**として GAS を使うことにしました。

<img src="https://docs.google.com/drawings/d/e/2PACX-1vRaCg94Tv8npYqcwLaTRhUnN9UPjmv_fRYuozN2AFQLOQH_uIRdSOmuzX_m03nZ85TGyOmNkb_E-Gx0/pub?w=1273&amp;h=709">

ここで問題が。

処理ごとにスクリプトを分けると認証処理やAPIを叩くための関数をコピペするようになりました。これではメンテナンス性が悪く DRY（Don't repeat yourself）的にモヤるので汎用ライブラリとして切り出しました。実際に使ってみたところ（当然）コードの量も減り使い勝手が向上したので、同じように困っている人がいるかもしれないと考え、汎用ライブラリの使い方を公開しようと考えました。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">SORACOM のAPIをGASから使うための認証処理（samUserAuthentication）とAPIを叩くための汎用処理（api）のライブラリ作った。需要があるかわからないけどQiitaに記事でも書こうかしらん</p>&mdash; takeru oyama (@takeru08ma) <a href="https://twitter.com/takeru08ma/status/1120565398741626880?ref_src=twsrc%5Etfw">2019年4月23日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

一点補足しておきますと、[SORACOM の API リファレンス](https://dev.soracom.io/jp/docs/api/)は非常に丁寧に記載されており、API の基礎的なところが押さえられている人であればドキュメントを見て大体何ができるのかがすぐに分かります。この汎用ライブラリは単に**API を使うための手続きを簡素化しただけ**なので自前で作れる人に対しては釈迦に説法になるかもしれません。

# 汎用ライブラリの使い方

使い方の前にまず事前準備からはじめます。既に準備が整っている場合は読み飛ばしてください。

## 事前準備

### 1. SORACOM のアカウントを取得

公式に説明を譲ります。
:arrow_right: [SORACOM アカウントの作成](https://dev.soracom.io/jp/start/console/#account)

### 2. SAM ユーザーを作成

公式に説明を譲ります。
:arrow_right: [SAM ユーザーの作成と認証設定](https://dev.soracom.io/jp/start/sam/#sam01)

説明中に**認証キーの設定**の項がありますが、汎用ライブラリを使う上では必須ではないので実施しなくても問題ありません。

### 3. SAM ユーザーの権限を設定

公式の説明に設定方法が記載されています。
:arrow_right: [SAM ユーザーの権限設定](https://dev.soracom.io/jp/start/sam/#sam02)

権限の設定方法は、

1. ロールに権限を設定してから、ユーザーにロールを設定するか、
2. ユーザーに直接指定で設定するか

の 2種類ありどちらでも問題ありません。ここでは後者の直接指定を行っています。

<img src="https://docs.google.com/drawings/d/e/2PACX-1vT7FNGrudMt62b3YVPAYThOGwZy26LWsq9yNyoVFG86RLirTl3SDyaakrsdIHkv6xHX9lf5T2jm2M1R/pub?w=1380&amp;h=804">

**※補足**

使用したい API や HTTP メソッドを絞った方が良いのですが、ここではひとまず全て使える状態で進めます。

### 4. GAS を利用可能にする

GAS を導入する方法はググれば腐るほど記事が出てきますが、念の為掲載しておきます。

1. [Google Drive](https://drive.google.com/drive/) を開く
2. `新規` → `その他` → `アプリを追加`
3. `Google Apps Script` で検索し、ヒットしたら `接続` を選択
4. `Google ドライブに接続されました。` という説明が表示されれば OK。
5. 改めて `新規` を選択すると `Google Apps Script` が表示され、選択すれば `無題のプロジェクト` （新規の GAS）が作成されます。

<img src="https://docs.google.com/drawings/d/e/2PACX-1vStguRYJiTAhs1NuzFtPWl2XfRk4HyyQ1lQocnNdwp_tlp3tmgJMjW3KnufXlZSW589SL4HBEaUBAO9/pub?w=1279&amp;h=2354">

## 汎用ライブラリの使い方

### 1. GAS で汎用ライブラリを読み込む

1. GAS のプロジェクトを開く
2. メニューの `リソース` → `ライブラリ` を選択
3. `ライブラリを追加` と記載されているフォームに汎用ライブラリの API ID `MJlievwjcIu8-7grDZqGf-FSucpP_quDO` を入力して `追加`
4. `バージョン` で `SORACOM API 汎用ライブラリ` を選択して `保存`

<img src="https://docs.google.com/drawings/d/e/2PACX-1vRtmVAcqmYF9qwEXN_HJMIEIdfzXTopAGr-gAkensLQcPGVFBh6W09chxrSub7fFbELNt5QQTihscnl/pub?w=1348&amp;h=1037">

汎用ライブラリが正常に読み込めていると GAS のコードで識別子＋ドット（`Soracom.`）まで入力すると、使用可能な関数の選択肢（`samUserAuthentication` や `api`）が表示されるようになります。

<img src="https://docs.google.com/drawings/d/e/2PACX-1vQOt34EIOub8EScChOjJ73ObnO0-DGyiME6O5HlNxd4WY4ENnPWYbeK3LkU9w62xWomsW5ldDtU4xfu/pub?w=768&amp;h=265">

### 2. GAS のスクリプトプロパティに SORACOM の SAM ユーザー認証情報を設定

<font color="red">基本的に、ユーザーアカウントや認証に必要な情報（API のキーやトークン）をハードコーディングすることは大変危険です。</font>万が一情報が漏れてしまった場合、第三者が自由にアクセスできてしまうリスクは言うまでもないと思います。なので汎用ライブラリでは SORACOM の SAM ユーザーアカウントの情報を GAS のスクリプトプロパティに設定して使用します。

1. GAS のプロジェクトを開く
2. メニューの `ファイル` → `プロジェクトプロパティ` を選択
3. `スクリプトのプロパティ` タブを選択
4. `SORACOM_SAM_USERNAME` には SORACOM User Console で作成した SAMユーザー名を設定
5. `SORACOM_SAM_PASSWORD` には SAM ユーザーのパスワードを設定
6. `SORACOM_OPERATORID` には SORACOM User Console にログインするアカウントのオペレーターID（`OPxxxxxxxxxx`）を設定

<img src="https://docs.google.com/drawings/d/e/2PACX-1vTa876fdOhJvUTllAiZbGFVrKcI3cpaDdW-Y1nyp2ISU67Mm7hSrJjK7bS6SS1hmRmEoYPRk8FEgeE3/pub?w=1498&amp;h=781">

<img src="https://docs.google.com/drawings/d/e/2PACX-1vTf_b7h1ocYgisCR59mx1l6JBI6pfu6G-GXFsDyaj3sq-LuBnuQHWnmnJHK265316zZ6RcmKfXyoGsZ/pub?w=776&amp;h=648">

### 3. GAS のプロジェクトに `getAccessToken` 関数を作成

下記のコードを `コード.gs` に追加します。

```js:コード.gs
function getAccessToken() {
  var apiInfo = Soracom.samUserAuthentication({
    "operatorId": PropertiesService.getScriptProperties().getProperty("SORACOM_OPERATORID"),
    "userName": PropertiesService.getScriptProperties().getProperty("SORACOM_SAM_USERNAME"),
    "password": PropertiesService.getScriptProperties().getProperty("SORACOM_SAM_PASSWORD"),
    "apiTokenIssued": PropertiesService.getUserProperties().getProperty("SORACOM_API_TOKEN_ISSUED")
  });
  
  if (apiInfo !== true) {
    Logger.log("New token");
    
    PropertiesService.getUserProperties().setProperty("SORACOM_API_KEY", apiInfo.apiKey);
    PropertiesService.getUserProperties().setProperty("SORACOM_API_TOKEN", apiInfo.token);
    PropertiesService.getUserProperties().setProperty("SORACOM_API_TOKEN_ISSUED", apiInfo.dateAndTime); // YYYY/MM/DD HH:mm:ss
    
    return {
      "key": apiInfo.apiKey,
      "token": apiInfo.token
    };
  }
  else {
    Logger.log("Exist token");
    return {
      "key": PropertiesService.getUserProperties().getProperty("SORACOM_API_KEY"),
      "token": PropertiesService.getUserProperties().getProperty("SORACOM_API_TOKEN")
    };
  }
}
```

### 4. API を叩いてデータを JSON で取得

関数 `myFunction` に汎用ライブラリで SORACOM API を叩く処理を書きます。ここでは、API リファレンスの [listGroups](https://dev.soracom.io/jp/docs/api/#!/Group/listGroups) を使って Group の一覧を返しています。

```js:コードgs
function myFunction() {
  var api = getAccessToken();
  
  var listGroups = Soracom.api({
    "apiKey": api.key,
    "apiToken": api.token,
    "httpMethod": "get",
    "subDirectory": "/v1/groups",
  });
  
  Logger.log(listGroups);
}
```

関数 `myFunction` を選択して実行します。

<img src="https://docs.google.com/drawings/d/e/2PACX-1vTAfRsU801BF12tLg_Dt4rs9sWY--4xqFr5DcQczVmuKfe0kHuUbMuzy9CscyLjHR40gvGyJvS3wgW9/pub?w=780&amp;h=281">

GAS の初回実行時はアクセス権の承認を求められますので、GAS を使用している Google アカウントで許可します。

<img src="https://docs.google.com/drawings/d/e/2PACX-1vT-hDygLvGoTIe3UXnIzxJv4tg9PQCWYmQ9PtJsi2WtRWP-6tU3aLTCDVWBviBSdkfMKABEH6AifT2w/pub?w=956&amp;h=1900">

実際にデータが取得できたのか確認してみます。
メニューから `表示` → `ログ` を選択。（`Ctrl` + `Enter` でも可）

<img src="https://docs.google.com/drawings/d/e/2PACX-1vQdhzht28lATmIs6j_FTkyyRESXXD8jckWhN7_FZPUsR4uaU5fhImM9ddrIrGA_5fXwFvzmbyjd__hG/pub?w=1073&amp;h=1000">

上記画像の青くマスクしているところに Group の一覧が JSON 形式で表示されます。
`New Token` と表示されている通り、新規でトークンを取得しています。

#### `Soracom.samUserAuthentication` の処理内容

```js
var apiInfo = Soracom.samUserAuthentication({
  "operatorId": PropertiesService.getScriptProperties().getProperty("SORACOM_OPERATORID"),
  "userName": PropertiesService.getScriptProperties().getProperty("SORACOM_SAM_USERNAME"),
  "password": PropertiesService.getScriptProperties().getProperty("SORACOM_SAM_PASSWORD"),
  "apiTokenIssued": PropertiesService.getUserProperties().getProperty("SORACOM_API_TOKEN_ISSUED")
});
```

- 内部的には[認証するための auth の API](https://dev.soracom.io/jp/docs/api/#!/Auth/auth) を呼び出し、SAM ユーザーの情報で API Key と API Token を取得しています。
- 引数 `operatorId`、`userName`、`password` は API を使うための API Key と API Token を取得するためのものです。
- 引数 `apiTokenIssued` は API のトークンを取得した時間を保持しています。API の仕様としては最大 48時間の有効期限を設定できるのですが、汎用ライブラリでは 24時間（`tokenTimeoutSeconds` = `86400`）に設定しており、GAS のユーザープロパティに保存します。有効期限内に再度 API を使う場合はユーザープロパティに保持した API Key と API Token を使い回し、（24時間以内ではなく）23時間を経過している場合は新たに API Key と API Token を取得します。

#### `Soracom.api` の処理内容

```js
var listGroups = Soracom.api({
  "apiKey": api.key,
  "apiToken": api.token,
  "httpMethod": "get",
  "subDirectory": "/v1/groups",
});
```

- 引数 `apiKey`、`apiToken` には SORACOM API を使うために必要な情報（HTTP ヘッダーに設定する API Key と API Token）を設定します。
- 引数 `httpMethod` 使用したい API の HTTP メソッドを設定します（例: `get`、`post`、`put`、`delete`）
- 引数 `subDirectory` 使用したい API のパスを設定します（例: `/v1/groups`）
    - API のバージョンが `v1` じゃなくなったら... っていうのはまだ考えてないです

<img src="https://docs.google.com/drawings/d/e/2PACX-1vSeLgNw8tYjQlL7boIeMi5K_pWgaZjBchycI8QwW8x7srh9osZpsxu1tH1NuPZCQ6n6aPYT68gT1pB2/pub?w=588&amp;h=249">

- `Soracom.samUserAuthetication` では `内部的には認証するための auth の API を呼び出し` と記載しましたが、この `auth` の API の HTTP メソッドは `post` です。この場合は引数 `requestBody` を追加し、HTTP メソッドが `post` の場合のみ JSON でリクエストしたいパラメータを設定します。

```js:汎用ライブラリの中でauth認証している部分の処理
var apiInfo = api({
  "httpMethod": "post",
  "subDirectory": "/v1/auth",
  "requestBody": {
    "operatorId": options.operatorId,
    "userName": options.userName,
    "password": options.password,
    "tokenTimeoutSeconds": 86400
  }
});
```

### 5. （おまけ）Spreadsheets にグループと SIM の一覧を出力

自身が使用している SORACOM の Group と SIM の情報を Spreadsheets に出力します。

#### Spreadsheets（テンプレート）をコピーしてマイドライブに追加

SORACOM から取得したデータを出力するための Spreadsheets（テンプレート） を、閲覧のみ可能な状態で公開しております。

1. [Spreadsheets（私が公開しているテンプレート）](https://docs.google.com/spreadsheets/d/1SZ8FA-C9E6IrUN7McquU6kQg6V18wQea93c7Ci08kfE/edit?usp=sharing) を開きます。
2. メニューの `ファイル` → `コピーを作成` を選択
3. 適当なファイル名で `OK` を選択
4. すると、マイドライブの配下にコピーされます。

<img src="https://docs.google.com/drawings/d/e/2PACX-1vQVHF2befgxORtgal8xR2DPzN4er9WC0l12IrqK8l5zKHfevUAD0Ov0DczZcvfU2w2aimjaKartBbQz/pub?w=573&amp;h=874">

#### コピーした Spreadsheets の ID を GAS のスクリプトプロパティに追加

`Spreadsheets の ID` とは Spreadsheets の URI に記載されています。
例:
`https://docs.google.com/spreadsheets/d/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/edit#gid=0` の  `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` の部分です。

<img src="https://docs.google.com/drawings/d/e/2PACX-1vQYMBR8FENdx0DUSBLL_4QGUHEyXqKMyYipAMw0NBIs2QtJah7j7k5myRjKJML3hkLZXyCU7jZ-jool/pub?w=1267&amp;h=167">

コピーして先ほどの GAS のプロジェクトを開き、スクリプトプロパティに `SORACOM_TABLE_ID` という名前で設定します。

<img src="https://docs.google.com/drawings/d/e/2PACX-1vQxzKVroTKfcShRBVqOjnn7VWOVzIStzcHFL1xE9Wq_y1YuyY2nJdQx6ewcDtgO6KzEbgMKfhVgBau4/pub?w=1587&amp;h=734">

#### SORACOM の Group と SIM の情報を Spreadsheets に出力するコードを追加

`コード.gs` に下記のコードを追加。

```js:コード.gs
function setGroup(groups) {
  var sheet = SpreadsheetApp.openById(
    PropertiesService.getScriptProperties().getProperty("SORACOM_TABLE_ID")
  ).getSheetByName("listGroups");

  sheet.getRange("A2:D201").setValue("");
  
  groups.forEach(function (values, index) {
    var rowIndex = index + 2;
    sheet.getRange("A" + rowIndex + ":D" + rowIndex).setValues([[
      (rowIndex - 1),
      values.operatorId,
      values.groupId,
      values.tags.name
    ]]);
  });
}

function setSubscriber(subscribers) {
  var sheet = SpreadsheetApp.openById(
    PropertiesService.getScriptProperties().getProperty("SORACOM_TABLE_ID")
  ).getSheetByName("listSubscribers");

  sheet.getRange("A2:M1001").setValue("");
  
  subscribers.forEach(function (values, index) {
    var rowIndex = index + 2;
    sheet.getRange("A" + rowIndex + ":M" + rowIndex).setValues([[
      (rowIndex - 1),
      values.operatorId,
      values.groupId,
      values.imsi,
      values.tags.name ? values.tags.name : "",
      values.status,
      values.sessionStatus.online,
      values.ipAddress,
      values.iccid,
      values.serialNumber,
      values.subscription,
      values.speedClass,
      values.moduleType
    ]]);
  });
}
```

関数 `myFunction` を下記のように書き換えます。

```js:コード.gs
function myFunction() {
  var api = getAccessToken();
  
  var listGroups = Soracom.api({
    "apiKey": api.key,
    "apiToken": api.token,
    "httpMethod": "get",
    "subDirectory": "/v1/groups",
  });
  
  setGroup(listGroups);

  var listSubscribers = Soracom.api({
    "apiKey": api.key,
    "apiToken": api.token,
    "httpMethod": "get",
    "subDirectory": "/v1/subscribers?tag_value_match_mode=exact",
  });
  
  setSubscriber(listSubscribers);
}
```

#### SORACOM API で取得したデータを Spreadsheets へ出力

関数 `myFunction` を選択して実行します。

<img src="https://docs.google.com/drawings/d/e/2PACX-1vTAfRsU801BF12tLg_Dt4rs9sWY--4xqFr5DcQczVmuKfe0kHuUbMuzy9CscyLjHR40gvGyJvS3wgW9/pub?w=780&amp;h=281">

Spreadsheets API の初回実行時はアクセス権の承認を求められますので、GAS を使用している Google アカウントで許可します。

<img src="https://docs.google.com/drawings/d/e/2PACX-1vSdvqSppwfpAnBMnGeJLbFuzR4Ky5BJck0txReypRMleC1w0LFfc6dUCS7s3TIPlGlJSVULfGlIS3pZ/pub?w=959&amp;h=1999">

Spreadsheets の sheet `listGroups` に API で取得したデータが出力されました。

<img src="https://docs.google.com/drawings/d/e/2PACX-1vSr6cRhnF1VyRHF6bY9si8ZsrMEKhbgExwItQ-jFpAHefai4fgyWSGY7wDOtrPFfmCzWokicj9J3cyu/pub?w=1395&amp;h=477">

Spreadsheets の sheet `listSubscribers` に API で取得したデータが出力されました。

<img src="https://docs.google.com/drawings/d/e/2PACX-1vQSzpNpETZlvt_uWmVgK0jBnGRVfDigOyNyFjwYGRVAwQ8cDmyEdQ98Ocklw031zHBbHednN-ZnrT5T/pub?w=1397&amp;h=447">

## 所感

- `汎用ライブラリ`、`汎用ライブラリ`と書いておりましたが、記事の終盤あたりで『`汎用`ってワード要らなくない？』と感じました...
- API が充実してるって良いですよね。親切なドキュメントのおかげで助かりました :laughing:
- 今回は（おまけで）Spreadsheets に出力しましたが、別のサービスやデータベースに流すことも簡単そうです。
- GAS のライブラリ（実行可能な API）は簡単に作れたので GAS 自体もオススメです。

## 参考文献

- SORACOM
    - [SORACOM API Reference](https://dev.soracom.io/jp/docs/api/)
    - [SORACOM Access Management を使用して操作権限を管理する](https://dev.soracom.io/jp/start/sam/)
    - [アクセス権限設定のためのパーミッション構文](https://dev.soracom.io/jp/docs/sam_permission/)
- GAS
    - [Libraries](https://developers.google.com/apps-script/guides/libraries)

---

分かりやすく記事を書こうとして画像を貼りまくった結果、ボリュームが多くなってしまいました:sweat: 
最後まで読んでいただきありがとうございました:exclamation:
誤っている箇所やツッコミがありましたら、優しくご指摘いただけますと幸いです:bow: