WebサイトのJSからGASの実行可能APIを最短最高で叩く

#はじめに
お金はないですがGoogleCloudPlatformのアカウントは持っているのでスプレッドシートをDBにしてWebサービスを作ってます。GASをけいゆうしてデータの読み込みから書き出しまでやろうと思ったんですが、JSから叩くのに3日かかったのでシンプルにまとめます。情報が少ない上、設定が多いのでかなりかかりました。QuickStartが全然クイックじゃないしサンプルがほんとに意地悪でした。あとは重要なことが文が多すぎて埋もれてしまってます…Googleのサポート問題は日本語化されていない時点でお察しですが、なんとか英訳してやってみました。

また、この記事は以下の記事を全面的に元にしています。
[【GAS】Execution APIを使ってJavaScriptからGASにアクセスする](https://qiita.com/ShishidoToru/items/67a7cad6b997e316cb20#認証情報設定)
# 手順1 クライアント側
Webサイトを作ってください。URLが確定してないとだめです。サーバーレスでFirebaseHostingとかいいですよ。

あらかじめ説明しときますが制作したウェブサイトのURLの

```
https://example.xxx.xxx/yyyy/zzzz
```

`https://`をスキーム、`example`をサーバー名、`xxx.xxx`はドメイン、`example.xxx.xxx`の部分はオーソリティ、`yyyy/zzzz`はパス名といいます。(諸説あるかもしれません)

# 手順2 GAS側
適当に新しいプロジェクトを作って関数を作ります。

```javascript:コード.gs
function helloWebAPI(str) {
  return "Hello!" + str;
}
```
文字列を受け取って`Hello!`とくっつけるだけです。
次に`GoogleCloudPlatform`との連携を行います。GASは昔は自動で連携してたんですがその機能がOFFになっているそうで自分で作らなければいけません。

# 手順3 GoogleCloudPlatform側
これは課金が必要です。よくわからないですが無料トライアルに銀行口座が必要らしいです。自動で請求しないみたいですが。

1. 新しいプロジェクトを作る。(名前はGASと同じがわかりやすい)
2. `APIとサービス`から`OAuth 同意画面`に移動する。同意画面を作成していきます。
3. アプリケーション名(適当)と承認済みドメインに使ってるドメインを入力し作成。(複数可)
4. `APIとサービス`から`認証情報`に移動する。
5. `認証情報を作成`をクリック。`OAuthクライアントID`を作成していきます。
6. アプリケーションの種類は`ウェブアプリケーション`。名前は適当。`承認済みのJavaScript生成元`はオーソリティまでを、`承認済みのリダイレクトURI`はパスまで全部のURLを入れときます。(複数可)そして作成。
7. 出てきた`クライアントID`の方だけを控えときます。こうゆう**トークン**がこれから何個か出てくるので注意です。
8. `認証情報を作成`をクリック。`APIキー`を作成します。すぐにできます。
9. 出てきた`APIキー`を控えます。
10. APIキーは制限を行わないといけないのでペンマークで編集を行い、`アプリケーションの制限`と`API の制限`を行ってください。アプリケーションはウェブアプリケーションのみ、APIのほうはこれから使う`AppsScriptAPI`のみでいいでしょう。

10のAPIの制限に関しては個人で`GoogleCloudPlatform`に別のAPIを適応したりするときには他のAPIにもチェックを付けてください。自分はWindowsApplicationも作る予定があったのでアプリケーションの制限は行いませんでした。

次は外部からこれらのトークンを使って叩けるようにしましょう。
`APIとサービス`から`ライブラリ`に移動して検索窓に`Google Apps API`と入力して出てきたやつを有効にしてください。

**ホームのダッシュボードからプロジェクト情報から`プロジェクト番号`を控えましょう。**

次はこのプロジェクトとGASのプロジェクトをつなげましょう。

---

これらの設定は以下の記事を参考にしました。現在のバージョンと違うので画面が違かったり、APIの名前が違かったりします。
[【GAS】Execution APIを使ってJavaScriptからGASにアクセスする](https://qiita.com/ShishidoToru/items/67a7cad6b997e316cb20#認証情報設定)
[GASのExecution APIを使ってGASを外部からぶっ叩く](https://qiita.com/soundTricker/items/1bcfc5c9e80d29a7ae4b#client-idを発行)

ほぼ同じことをしている上、スクショつきでとてもわかりやすいです。
[【GoogleAppsScript】実行可能API(Execution API)の初期設定でハマったので流れをメモする](https://qiita.com/kamiryo/items/964c353cc51b5f4f27a4)

---

# 手順4 GAS側
メニューバーから`リソース`、`Cloud Platform プロジェクト`を押下。`プロジェクト番号`を入力して横のボタンを押して、上の方に`このスクリプトが現在関連付けられているプロジェクト:`が表示されたらオッケーです。

次はこのGASのソースコードを`実行可能API`にします。
メニューバーの`公開`から`実行可能API`として導入を押します。バージョンを新規作成にして説明を入力。導入を押せばオッケーです。ここで出てくる`API ID`は使いません。

さて、次は必要な情報を取得します。
メニューバーの`ファイル`から`プロジェクトのプロパティ`を押してプロパティを見ます。
必要なのは、

- スクリプト ID
- スコープ

です。スクリプトIDは`情報`タブに、スコープは`スコープ`タブにあります。スクリプトIDはGASプロジェクトの判別トークンですが、スコープはこのGASプロジェクトを実行するときに許可しないといけない権限です。これらを許可していないと実行はできません。何もない人は大丈夫ですが、現在あるいは今後、スプレッドシートを見たりいじったりDriveを見たりいじったり、ほかのGoogleサービスに対して処理を行う際はこのスコープも必要になります。もちろんスコープは複数ある場合もあります。

ではいよいよクライアント側の実装に移ります。現在必要なのは以下のトークンたちです。

> - クライアントID
> - APIキー
> - スクリプトID
> - スコープ

こうゆうのってトークンいっぱいあるからいやになりますよね...

# 手順5 クライアント側
ファイル分ける派です。
## HTML

```html:index.html
<!DOCTYPE html>
<html>
  <head>
    <title>WebサイトのJSからGASの実行可能APIを最短最高で叩く</title>
  </head>
  <body>
    <button id="authorize_button" style="display: none;">Authorize</button>
    <button id="signout_button" style="display: none;">Sign Out</button>
    <script async defer src="https://apis.google.com/js/api.js"
      onload="this.onload=function(){};handleClientLoad()"
      onreadystatechange="if (this.readyState === 'complete') this.onload()">
    </script>
  <!-- index.jsのロード -->
  </body>
</html>
```

APIを叩くにはユーザーにログインさせないといけないのでログインボタンを配置します。実用化のときには要らないですね。CDN(？)でapiをロードし、ロードが終わったときに`handleClientLoad()`を実行します。

## JS

```javascript:index.js
var CLIENT_ID = /* クライアントID */;
var API_KEY = /* APIキー */;
var SCOPES = [/* スコープリスト */];
var scriptId = /* スクリプトID */;
var DISCOVERY_DOCS = ["https://script.googleapis.com/$discovery/rest?version=v1"]; // そのまま

var authorizeButton = document.getElementById('authorize_button');
var signoutButton = document.getElementById('signout_button');

// 勝手に実行される
function handleClientLoad() {
  gapi.load('client:auth2', initClient);
}

function initClient() {
  // GoogleAPIのinitialize
  gapi.client.init({
    apiKey: API_KEY,
    clientId: CLIENT_ID,
    discoveryDocs: DISCOVERY_DOCS,
    scope: SCOPES.join(' ')
  }).then(function () {
    // ログインしているかをupdateSigninStatusに渡す
    updateSigninStatus(gapi.auth2.getAuthInstance().isSignedIn.get());
    // ボタンが押された時の処理を代入。
    authorizeButton.onclick = handleAuthClick;
    signoutButton.onclick = handleSignoutClick;
  }, function(error) {
    appendPre(JSON.stringify(error, null, 2));
  });
}

function updateSigninStatus(isSignedIn) {
  if (isSignedIn) {
    // ログイン済み
    authorizeButton.style.display = 'none';
    signoutButton.style.display = 'block';
    // APIを叩く
    callAppsScript("helloWebAPI", ["World!"], function(resp) {
      // コールバック関数
      if (resp.error) {
        // エラーあり
        if (resp.error.status) {
          console.log('Error calling API: ' + JSON.stringify(resp, null, 2));
        } else {
          var error = resp.error.details[0];
          console.log('Script error! Message: ' + error.errorMessage);
        }
      } else {
        // エラーなし
        appendPre(resp.response.result);
      }
    });
  } else {
    // ログインしていない
    authorizeButton.style.display = 'block';
    signoutButton.style.display = 'none';
  }
}

// ログインボタンが押された時
function handleAuthClick(event) {
  // ログインの処理
  gapi.auth2.getAuthInstance().signIn();
}

// ログアウトボタンが押された時
function handleSignoutClick(event) {
  //　ログアウトの処理。
  gapi.auth2.getAuthInstance().signOut();
}

// 画面に出力する
function appendPre(message) {
  var pre = document.getElementById('content');
  var textContent = document.createTextNode(message + '\n');
  pre.appendChild(textContent);
}

// APIを叩く関数
function callAppsScript(functionName, parameters, callbackFunc) {
  var request = { 'function': functionName, 'parameters': parameters };
  var op = gapi.client.request({
    'root': 'https://script.googleapis.com',
    'path': 'v1/scripts/' + scriptId + ':run',
    'method': 'POST',
    'body': request
  });
  // APIを叩いてコールバック関数を呼ぶ
  op.execute(callbackFunc);
}
```

随分と長いですがやってることはむずくないです。
`handleClientLoad()`は準備が完了したときに実行されます。(HTMLに記載)`initClient()`はOAuthと通信を行い、必要なすべてのスコープが許可されているか、ログインしているかを確認します。そこけっかはすぐ下の`.then()`コールバックに返ってきます。結果によってログイン/アウトボタンを設定し、ログインしていればAPIを叩きます。APIを叩く際、`callAppsScript()`という関数を作ってそれを実行しています。

第一引数に実行GASの関数名、第二には引数を配列で渡し、第三にはそのコールバック関数を設定してください。

実際に叩いているのは`callAppsScript()`内の`op.execute()`です。リクエストJsonを制作し叩きます。`resp.response.result`がreturnされた結果です。

# さいごに
<h3>Google許すまじ!!</h1>

まぁ、頭が悪い自分が悪いんですが。実はAPIがもう一種類あって今回使ったのが`https://apis.google.com/js/api.js`なのに対し`https://apis.google.com/js/client.js`てやつがあるんですよね。この二つの情報が錯綜していて`gapi.client.request()`を使ってリクエストを送る方法を取りました。この方法だとどんなリクエストでも送れるみたいです。`client.js`には`script.scripts.run`というやつがあってこれは今回やったGASの関数を実行する専用のようです。ということは`api.js`の中に`client.js`があるのかな？わがんね。

Twitter→https://twitter.com/CyberHacnoshuke