【GAS】Google Apps Script + OAuthライブラリで、BASE APIを利用

# 概要
ネットショップBASEのAPIを、Google Apps Script（GAS）で利用できるようになるまでの手順を、ステップバイステップで記しました。

なお、認証・認可の手順に重点を置いた記事であるため、API自体は最低限（自分のショップ名の表示、商品の追加）しか記載していません。

この記事の内容は、Windows 11、ブラウザ：Microsoft Edgeバージョン 123 (64bit) 上のGoogle Apps Script にて動作を確認しています。
また、認証コードは、@TakeshiNickOsanaiさんの 「[Google Apps Script + OAuthライブラリで、freee APIの操作を行う](https://qiita.com/TakeshiNickOsanai/items/62810b0e96bf37bd0eca)」より大部分を引用させていただきました。感謝いたします。

# 注意事項
・BASE APIを利用するには、ショップの開設と電話番号認証が必要です。
・GASのスクリプトは、スプレッドシートのコンテナバインドスクリプトとします。（後述のコードでメニューを表示するため）

# 手順
## 1. [BASE Developers ポータル](https://developers.thebase.in/)にアクセス。

 https://developers.thebase.in/

ショップ未開設の場合は右上の「アカウント登録」でショップを開設しておく。

## 2. 電話番号の登録

※ショップ開設済み・電話番号登録済の場合は、スキップして 3.へ

「ログイン」をクリック
![s-1_vログイン.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611121/fe84526a-4e35-4527-43d9-9292c84fc8a7.png)

「管理画面」をクリック
![s-2_管理画面クリック.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611121/ec017eb4-0974-0a30-045f-28321022dbbd.png)

右上のアイコンから「ショップ設定」をクリック
![s-3_ショップ設定.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611121/4fed698d-6041-97d6-e674-84b4663791d7.png)

「運営に関する情報」をクリック
![s-4_運営に関する情報.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611121/84cce6e7-db3d-a352-aeba-821d6424ee07.png)

中段までスクロールすると、電話番号入力欄があるので、SMSを受け取れる電話番号を入力して認証する。
![s-5_電話番号認証.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611121/863d0b38-abb5-5aa8-63dc-f15ad697fb4c.png)

下部の「保存」ボタンをクリック。


## 3. GASへのOAuthライブラリの導入

[OAuthライブラリ](https://github.com/googleworkspace/apps-script-oauth2)をGASに導入する。

スプレッドシートのメニューの「拡張機能」→「AppsScript」をクリックしてスクリプトエディタを開く。

エディタ左「ライブラリ」の「＋」マークをクリック。
![s-b1_GASエディタ_ライブラリ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611121/2e7df440-62c8-f65b-9457-20cb6ba7a54a.png)

スクリプトID欄に
`1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF`
を入力。
「検索」をクリック→「追加」をクリック。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611121/bc98710e-2a08-38c3-ed2d-e2b73c717bcb.png)



## 4. スクリプトIDの確認

GASのスクリプトIDを確認する。（APIのコールバックURLに必要）

GASのエディタの左に並んでいるアイコンのうち、歯車のアイコンをクリック
![s-b3_設定(歯車アイコン）.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611121/6c8638fb-53e0-32ce-34fa-5d145d352578.png)

スクリプトIDをコピーし、テキストエディタ等にメモしておく。
![s-b4_スクリプトIDのコピー.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611121/65f6214b-7dc7-c784-8fa2-c423727e65d2.png)

## 5. GASの仮デプロイ

API申請画面では「有効なアプリURL」が必要なので、ひとまず土台となるウェブアプリをデプロイする。


GASに次のコードを入力し保存する。
```js
function doGet(e) {
  return HtmlService.createHtmlOutput();
}
```

エディタ右上の「デプロイ」から「新しいデプロイ」を選択。
![s-c1_デプロイ１.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611121/ecef7cd4-d8d5-a142-954b-66e2e83425f1.png)

「種類の選択」の歯車から「ウェブアプリ」を選択。
![s-c2_デプロイ＿ウェブアプリ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611121/2cacd0d4-a097-335d-4af3-e79f4de417c6.png)

「次のユーザとして実行」は「自分」、
「アクセスできるユーザー」は「全員」に設定。
「デプロイ」ボタンをクリック。
![s-c3_デプロイ全員.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611121/d5586585-3ef1-93ff-1831-e60581314caa.png)


表示されたダイアログ内の「デプロイURL」をコピーし、テキストエディタ等にメモしておく。
![s-c4_デプロイURLの取得.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611121/de4f519e-8ec0-ae3f-998f-811e1c1866f3.png)


## 6. BASE DevelopersでAPI申請

再び[BASE Developers ポータル](https://developers.thebase.in/)にアクセス。
下にスクロールして「申請する」をクリック。
![s-a1_BASE_DEVELOPERS_申請する.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611121/1eb48ef9-9893-aa6d-b555-fa47c211f332.png)

アプリ情報を入力する画面に遷移する。

![s-a2_1_BASE_アプリ設定にコールバック追加.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611121/a4c7cd14-ec59-3c57-a79a-ccc1ef50081d.png)

入力項目について：
⭕️は後から変更可能、❌登録後変更不可（BASE API画面からのアプリ新規登録が必要）
|入力項目|変更可否|
|:-----------|:------------:|
|アプリ名|⭕️|
|アプリの説明|⭕️|
|**アプリURL**|❌|
|コールバックURL|⭕️|
|アプリのロゴ画像|⭕️|
|**利用権限**|❌|
|**開発者情報** |❌|

・アプリ名、アプリの説明を記入
・アプリURLとコールバックURLに下記を指定する。
```
アプリURL：（先ほどのデプロイURL）
https://script.google.com/macros/s/＊＊＊＊＊＊＊＊/exec

コールバックURL：（「スクリプトID」の部分は、先ほどメモしたスクリプトID）
https://script.google.com/macros/d/スクリプトID/usercallback

```
![s-a2_2 URL.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611121/9c6f45ed-cfab-f5f7-de72-327eab90f0e1.png)

・利用権限は必要なものにチェックを入れる。
・**一度作成(申請)したアプリの利用権限は変更できない。権限を変更したい場合は [BASE DEVELOPERSのアプリケーションページ ](https://developers.thebase.com/apply)からアプリの新規作成が必要となる。**
（非推奨であるが、どのAPIを使用するか不明な場合や、後から権限追加が必要になりそうな場合は、あらかじめ全部にチェックを入れておくという手もある。ただしこのようなことを行うと当然バグや不正アクセスにより不必要な機能が実行されてしまう可能性もあるので、自己責任で）

・開発者情報のうち、電話番号は、2.で認証済みの番号を入力。
・メールアドレスは認証キー発行のお知らせを受け取るのに必要なので、きちんとしたアドレスを入力すること。
・[API利用規約のリンク](https://thebase.in/pages/api_term)をクリックして規約を読み、同意するならチェックを入れて「申請する」のボタンをクリック。
![s-a2_3_規約同意、申請.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611121/8c532668-a78d-b99e-9e11-8f5f95759c34.png)


## 7. 認証キー（Client ID, Client Secret）の確認

数分したら、developers＠thebase.in というアカウントから「認証キー発行のお知らせ」というメールが届くので、そこに書いてあるリンクをクリック。（またはBASE Developersの管理画面を開く）
↓
登録したアプリのアイコンをクリック。
↓
表示されている `client_id` と `client_secret` をそれぞれコピーしておく（次の認証キー保存の時にGASで使う）
![s-a3_CLIENTID,CLIENTSECRET.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611121/60992e17-a1b9-86e6-962e-8257af12eb99.png)

## 8. GASのスクリプトプロパティに認証キーを保存

先ほどのGASに戻る。
GASエディタを開き、左側にある歯車アイコンをクリックして設定画面を開き、一番下までスクロールする。
![s-e1 スクリプトプロパティ＿歯車クリック＿スクロール.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611121/b6624077-1bd5-6cd5-9b07-7a903736a3a4.png)



![s-e2 スクリプトプロパティを追加ボタンクリック.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611121/9c82edc3-998c-e958-a02c-becd9f89ab82.png)
「スクリプトプロパティを追加」ボタンをクリックし、`ClientId`・`ClientSecret`というプロパティ名を登録する。
それぞれの値として、先ほど取得したクライアントID及びクライアントシークレットを設定する。
（ここで設定したプロパティ名は、GASのコードでは大文字小文字が区別されるので注意すること）
![s-e3 スクリプトプロパティを保存.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611121/a677120b-ba32-f920-017f-f7ad48311721.png)
最後に「スクリプトプロパティを保存」ボタンを押して完了。

## 9. 認可処理

GASエディタに追加で下記のスクリプトを入力：
（※下記のコードは、@TakeshiNickOsanaiさんの 「[Google Apps Script + OAuthライブラリで、freee APIの操作を行う](https://qiita.com/TakeshiNickOsanai/items/62810b0e96bf37bd0eca)」より大部分を引用させていただきました。大変参考になる情報に感謝いたします）
```js
const Properties = PropertiesService.getScriptProperties();
const driveService = getDriveService_();

function getDriveService_() {
  return OAuth2.createService('BASEAPI')
    .setAuthorizationBaseUrl('https://api.thebase.in/1/oauth/authorize')
    .setTokenUrl('https://api.thebase.in/1/oauth/token')
    .setClientId(Properties.getProperty('ClientId'))
    .setClientSecret(Properties.getProperty('ClientSecret'))
    .setCallbackFunction('authCallback_')
    .setPropertyStore(PropertiesService.getUserProperties())
    .setCache(CacheService.getUserCache());
}


function onOpen() {
  SpreadsheetApp.getUi()
    .createMenu('BASE API連携')
    .addItem('認可処理', 'showAuth_')
    .addItem('ログアウト', 'logout_')
    .addToUi();
}


function createModelessDialog_(html, title) {
  const htmlOutput = HtmlService.createHtmlOutput(html)
    .setWidth(360)
    .setHeight(120);
  SpreadsheetApp.getUi().showModelessDialog(htmlOutput, title);
}


function showAuth_() {
  if (!driveService.hasAccess()) {
    const authorizationUrl = driveService.getAuthorizationUrl();
    const template = HtmlService.createTemplate(
      '<a href="<?= authorizationUrl ?>" target="_blank">Authorize</a>. ' +
      'BASE APIの認可をします。'
    );
    template.authorizationUrl = authorizationUrl;
    const page = template.evaluate();
    const title = 'BASEアプリの認可処理';

    createModelessDialog_(page, title);
  } else {
    showUser_();
  }
}


function authCallback_(request) {
  const isAuthorized = driveService.handleCallback(request);
  if (isAuthorized) {
    return HtmlService.createHtmlOutput('Success! You can close this tab.');
  } else {
    return HtmlService.createHtmlOutput('Denied. You can close this tab');
  }
}


function showUser_() {
  const response = UrlFetchApp.fetch(
    'https://api.thebase.in/1/users/me',
    {
      headers: {
        Authorization: 'Bearer ' + driveService.getAccessToken(),
      },
    }
  );
  const myJson = JSON.parse(response);
  const message = 'OAuth認可済みです。\\nショップ名：' + myJson.user.shop_name;
  Browser.msgBox(message);
}


function logout_() {
  driveService.reset();
  const mes = 'BASEアプリからログアウトしました。';
  const logoutTitle = 'ログアウト終了';
  createModelessDialog_(mes, logoutTitle);
}
```

上記スクリプトを入力して保存したら、スクリプトが紐づいている**元のスプレッドシートを一旦閉じて開きなおす**。
スプレッドシートを開きなおして10秒くらい待っていると、上に「BASE API連携」というメニューが現れるので
「BASE API連携」→「認可処理」をクリックする。
![s-d1_メニューから認可処理.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611121/1c2a0656-5e2f-d3ad-4e13-9ae04a5ffcd4.png)

ダイアログが現れるので、Authorizeのリンクをクリックする。
![s-d2_gasダイアログ＿認可処理.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611121/01569ed1-ac6c-9912-39fb-a331fe466cc9.png)

新規ページが開いてBASEの連携画面に遷移するので、「アプリを認証する」をクリック。
![s-d3_遷移後アプリを認証する.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611121/9be685a6-80f4-b597-7830-d4a4cbbcfc8e.png)

認可に成功すると、「Success!」という文面のページが表示される。このページは閉じてよい。
![s-d4_success.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611121/b8690d3e-ca62-8794-da66-a2fdf2f45d10.png)

　
## 10. APIを実行してみる。
スプレッドシートに戻り、ダイアログを閉じてから、もう一度メニューの「BASE API連携」→「認可処理」をクリックする。
認可に成功していれば、ショップ名が表示される。
（`https://api.thebase.in/1/users/me`のAPIを叩けたということ）

```js
function showUser() {
  const response = UrlFetchApp.fetch(
    'https://api.thebase.in/1/users/me',
    { headers: { Authorization: 'Bearer ' + driveService.getAccessToken() }}
  );
  const myJson = JSON.parse(response);
  console.log(myJson.user.shop_name);
}
```
## 11. トークンの確認

認可が完了していれば、直近で取得したアクセストークン、リフレッシュトークンは下記で確認できる。
```js
function showTokens() {
   // アクセストークン
   console.log('AccessToken: ' + driveService.getToken().access_token);   // または .getAccessToken()
   // リフレッシュトークン
   console.log('RefleshToken: ' + driveService.getToken().refresh_token);
}
```

・アクセストークンは有効時間が約1時間、リフレッシュトークンは約30日間。
・アクセストークンが揮発しても driveService（getDriveService_）呼び出し時に自動的にリフレッシュトークンを使って新しいアクセストークンが取得され、認可されている状態が継続する。
・リフレッシュトークンが揮発した場合は、再度認証処理を行って新しいリフレッシュトークンを取得する必要がある。

# 商品を追加する例
（※６．に記載している通り、作成済のアプリケーションの利用権限を変更することはできないので、下記の権限にチェックを入れていない場合は[BASE DEVELOPERSのアプリケーションページ ](https://developers.thebase.com/apply)でアプリケーションの新規作成が必要になる）
・[BASE DEVELOPERSのアプリケーションページ ](https://developers.thebase.com/apply)で、アプリケーションの新規作成を行う。利用権限の「商品情報を見る」「商品情報を更新する」の両方にチェックを入れる。（「商品情報を更新する」だけにチェックを入れても動作しないので注意）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611121/e63478c8-4405-dffe-0578-f3416fccff8f.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611121/997b88fd-e921-4dc5-2fbe-041c5c08557d.png)

・次に、GASのコードの `getDriveService_()`関数内に、スコープを設定する行を追加（下記）。
```diff_javascript
function getDriveService_() {
  return OAuth2.createService('BASEAPI')
    .setAuthorizationBaseUrl('https://api.thebase.in/1/oauth/authorize')
    .setTokenUrl('https://api.thebase.in/1/oauth/token')
    .setClientId(Properties.getProperty('ClientId'))
    .setClientSecret(Properties.getProperty('ClientSecret'))
    .setCallbackFunction('authCallback_')
    .setPropertyStore(PropertiesService.getUserProperties())
    .setCache(CacheService.getUserCache())
+   .setScope('read_items write_items');
}
```
・複数のスコープを設定する場合は上記のようにsetScope関数内の引数に半角スペースで区切って指定する。
・設定するスコープはhttps://docs.thebase.in/docs/api/#scope を参照。

・スクリプト上のスコープを設定し直した場合は、スプレッドシートのメニュー→BASE API連携→ログアウトを実行後、認可処理を再度行うこと。

---
商品を追加するスクリプトの例。1日当たり1000件まで追加可能。
```js
// 商品を追加する。
function addItem() {
  const payload = {
    title : '商品1',  // 商品名
    detail : '商品1の説明',
    price : 500,    // 価格 
    stock : 2000,  // 在庫数 
    identifier : 'A00000001'  // 商品コード 
  };

  const options = {
    headers: {
      Authorization: 'Bearer ' + driveService.getAccessToken(),
    },
    method : 'post',
    payload : payload,
    muteHttpExceptions : true
  };

  const endpoint = '/1/items/add';
  try {
    const res = UrlFetchApp.fetch('https://api.thebase.in' + endpoint, options);
    const resObj = JSON.parse(res.getContentText());
    console.log(`商品追加完了。ID:${resObj.item.item_id}, 商品名:${resObj.item.title}`);
  } catch(e) {
    console.log('Error.');
    console.log(e);
  }
}

```