2
4

GASの関数を外部から実行する

Posted at

はじめに

この記事はGAS(Google Apps Script)の関数をApps Scriptサーバの外から実行する方法を解説したものです。

GASの関数を外部から実行する方法については既に多数の記事がありますが、Google製品はアップデートが頻繁なため、既存の記事を読んでも躓くポイントが多々あると思いました。

この記事が「GASをもっと有効に使いたい」と思っている方の一助になりますと幸いです。

📝免責事項

この記事で紹介するアプリは2024年6月時点での動作確認は取れていますが、Google製品はアップデートが早いため、もしかしたら今後のアップデート次第ではこの記事で紹介するサンプルアプリはうまく動かなくなる場合もあるかもしれません。

また、Google製品の利用には使用料が発生するものがありますが、当方はGoogle製品の利用によって生じた料金には責任を負いません。

さらに、この記事で紹介するサンプルアプリはクライアントサイドのJavaScriptに認証情報を埋め込むことになりますが、業務で使用するには、サーバーサイドで認証を行うなど、ユーザに認証情報を見せなくする工夫が必要になります。

サンプルアプリの作成や改造は飽くまでご自身の責任でお試しください。

目次

  1. 実行可能APIとは
  2. サンプルアプリの概要
  3. 前提
  4. 手順
    1. GAS関数の作成
    2. GCPプロジェクトの設定
      1. プロジェクトの作成
      2. APIの有効化
      3. OAuth認証画面の作成
      4. 認証情報の作成
      5. APIキーの作成
    3. GASのプロジェクトの設定
      1. GASとGCPを紐づける
      2. 実行可能APIをデプロイする
    4. サンプルアプリの作成
      1. index.htmlの作成
    5. サンプルアプリのテスト
      1. ローカルサーバの起動
      2. 動作確認
  5. 最後に

実行可能APIとは

実行可能APIとは、この記事のテーマである「GASの関数を外部から実行する」を実現する機能を提供してくれているGoogleのサービスです。

サンプルアプリの概要

この記事ではローカル端末のブラウザからGASの関数を実行するアプリを作ります。

実行可能API概念図

実行可能API概念図.jpg

前提

この記事で紹介するサンプルアプリを作るには次のものが必要になります。

  • Googleアカウント
  • Google Cloudアカウント
  • Node.js
  • ブラウザ

なお、サンプルアプリの作成には以下の公式チュートリアルを参考にしました。


手順

GAS関数の作成

実行可能APIとしてデプロイして外部から実行できるようにするGASの関数を作成します。

  1. 任意のフォルダにGASのプロジェクトを作成

    スクリプトプロジェクトの作成.png

  2. コード.gsの内容を次の内容に置き換える

    function getFilesInFolder(folderId) {
       let folder = DriveApp.getFolderById(folderId);
       let files = folder.getFiles();
       let fileNameList = [];
       while (files.hasNext()) {
          let file = files.next();
          fileNameList.push(file.getName());
       }
       return fileNameList;
    }
    
    function test_getFileInFolder() {
       let res = getFilesInFolder("フォルダID");
       Logger.log(res);
    }
    
    📝フォルダIDの確認

    フォルダIDとは、Googleドライブ内のフォルダを識別するためのユニークな文字列です。
    このフォルダIDはGoogleドライブのURLの一部として表示されており、フォルダのURLの「folders/」以降の文字列を見れば確認できます。

    【例】
    URL = https://drive.google.com/drive/folders/1A2B3C4D5E6F
      ⇒ フォルダID = 1A2B3C4D5E6F


  3. 「フォルダID」を任意のフォルダIDに置き換えて test_getFileInFolder を実行

    GAS関数のテスト実行.png
    ※ 初回実行時は権限の確認を求められますが、いつも通り「安全でないページ」に移動して続行してください

GCPプロジェクトの設定

プロジェクトの作成

認証機能を使うためのGoogle Cloudプロジェクトを作成します。

  1. Google Cloudのウェルカムページを開く

  2. 画面左上の「プロジェクトの選択」をクリック

    プロジェクトの選択.png

    ※ 組織のアカウントを使用している場合は表示が組織のドメインになっている可能性があります

  3. 画面右上の「新しいプロジェクト」を選択

    新しいプロジェクトの作成.png

  4. 任意のプロジェクト名を設定して「作成」をクリック

    プロジェクト名の設定.png

APIの有効化

サンプルアプリで使用するAPIを有効化します。

  1. 画面左上のメニューボタンからサイドバーを展開して「APIとサービス」→「ライブラリ」と選択

ライブラリを開く.png

  1. APIライブラリページが開いたら上部中央の検索欄で「Google Drive API」を検索

    GoogleDriveAPIを検索.png

  2. 検索結果の中から「Google Drive API」を選択

    GoogleDriveAPIを選択.png

  3. 製品の詳細を確認して「有効にする」をクリック

    GoogleDriveAPIを有効化.png

  4. 同様の手順でApps Script APIも有効化

    AppsScriptを有効化.png

OAuth認証画面の作成

OAuth同意画面を作成します。

ちなみに、OAuth同意画面はGoogle Drive APIなどのユーザの同意が必要なAPIを使う場合にユーザへ権限の確認を求めるために使用します。

  1. 画面左上のメニューボタンからサイドバーを展開して「APIとサービス」→「OAuth同意画面」と選択

    OAuth同意作成画面を開く.png

  2. ユーザタイプの選択では「External(外部)」を選択して「作成」をクリック

    ユーザタイプの選択.png

  3. アプリの情報を求められたら次の項目を入力して「保存して次へ」をクリック

    • アプリ名:OAuth同意画面に表示されるアプリの名称
    • ユーザーサポートメール:OAuth同意画面に表示される問い合わせ先メールアドレス
    • デベロッパーの連絡先情報:Googleからの通知を受け取るメールアドレス

    アプリの情報入力1.png

    アプリの情報入力2.png

  4. 「スコープを追加または削除」をクリックして「フィルタ」欄に「 https://www.googleapis.com/auth/drive.readonly 」入力し、必要なスコープを選択したら「更新」をクリックして「保存して次へ」を選択

    必要なスコープを追加.png
    スコープを保存.png

    ※ 実行可能APIに必要なスコープはApps Scriptプロジェクトの「概要」画面下部の「プロジェクトの OAuth スコープ」欄で確認できます

  5. テストユーザの追加画面で「ADD USERS」をクリックしてテストを実施するユーザのメールアドレスを入力し、「追加」をクリックして「保存して次へ」を選択

    テストユーザを追加.png

認証情報の作成

サンプルアプリ内でユーザを認証するための情報を作成します。

  1. サイドバーの「認証情報」から「認証情報を作成」→「OAuthクライアントID」と順に選択

    OAuthクライアントID作成画面を開く.png

  2. 「アプリケーションの種類」を「ウェブアプリケーション」に設定

    アプリケーションの種類を選択.png

  3. 「承認済みのJavaScript生成元」欄に「 http://localhost:9999 」と入力して「作成」をクリック

    承認済みのJavaScript生成元を入力.png

    📝URIの設定について

    「認証済みのJavaScript生成元」にはドメイン名を含むURIを設定する必要があります。
    そのため、JavaScriptの生成元は「https://(自身のIPアドレス)」のような指定ができず「http://localhost」のような設定が必要ですが、localhostではどの端末からでも接続できてしまうので、このサンプルではポート番号(:9999)を使用して接続経路を絞り込みました。

    ポート番号は65535までの番号を指定できるので、不安がある方はポート番号を変更してテストしてみてください。


  4. 「クライアントID」が表示されたらコピーして「OK」をクリック

    OAuthクライアントID確認画面.png

APIキーの作成

  1. 「認証情報」画面から「認証情報を作成」→「APIキー」と順に選択

    APIキーの作成画面を開く.png

  2. APIキーが表示されたらコピーして「閉じる」をクリック

    APIキーを確認する.png

GASのプロジェクトの設定

GASとGCPを紐づける

  1. 画面上部ののプロジェクト名を確認して左上メニューボタンからGCPプロジェクトのダッシュボードを開く

    プロジェクトダッシュボードを開く.png

  2. GCPプロジェクトのダッシュボードで「プロジェクト番号」をコピー

    プロジェクト番号をコピー.png

  3. GASのプロジェクトの設定画面を開く

    スクリプトプロジェクトの設定画面.png

  4. GASのプロジェクトの設定画面のGoogle Cloud Platform (GCP) プロジェクト欄の「プロジェクトを変更」ボタンをクリック

    プロジェクトを変更をクリック.png

  5. プロジェクト番号を入力して「プロジェクトを設定」をクリック

    プロジェクト番号を入力.png

実行可能APIをデプロイする

  1. GASのプロジェクト画面右上の「デプロイ」から「新しいデプロイ」を選択

    新しいデプロイを選択.png

  2. 新しいデプロイの設定画面でデプロイの種類を「実行可能API」に設定

    デプロイの種類を選択.png

  3. デプロイの詳細を以下のように入力して「デプロイ」をクリック

    デプロイの詳細を設定.png

  4. デプロイが完了したら「デプロイID」をコピーして「閉じる」を選択

    デプロイIDをコピー.png
    (※このデプロイIDは「サンプルスクリプトの作成」で使います)

サンプルアプリの作成

index.htmlの作成

  1. 任意の作業フォルダにindex.htmlを作成

  2. index.htmlの内容を以下のように編集

    <!DOCTYPE html>
    <html>
    <head>
       <title>実行可能APIクイックスタート</title>
       <meta charset="utf-8" />
       <style>
          body {
             background-color: black;
             color: white;
          }
       </style>
    </head>
    <body>
       <h1>実行可能APIテスト</h1>
    
       <button id="run_button" onclick="handleRunClick()" hidden>実行</button>
       <button id="reset_button" onclick="handleResetClick()" hidden>リセット</button>
    
       <ol id="content"></ol>
    
       <script type="text/javascript">
    
          const CLIENT_ID = "クライアントID"; // TODO: GCPで取得したクライアントIDを貼付
          const API_KEY = "APIキー"; // TODO: GCPで取得したAPIキーを貼付
          const SCRIPT_ID = 'デプロイID'; // TODO: GASで取得したデプロイIDを張付
          const FOLDER_ID = "フォルダID"; // TODO: GoogleドライブのフォルダIDを貼付
          const SCOPES = "https://www.googleapis.com/auth/drive";
    
          let tokenClient;
          let gapiInited = false;
          let gisInited = false;
    
          // Google APIクライアントライブラリの初期化
          function gapiLoaded() {
             gapi.load("client", async () => {
                await gapi.client.init({
                   apiKey: API_KEY,
                   discoveryDocs: ["https://script.googleapis.com/$discovery/rest?version=v1"],
                });
                gapiInited = true;
                maybeEnableButtons();
             });
          }
          
          // Google Identity Serviceクライアントライブラリの初期化
          function gisLoaded() {
             tokenClient = google.accounts.oauth2.initTokenClient({
                   client_id: CLIENT_ID,
                   scope: SCOPES,
                   callback: "",
             });
             gisInited = true;
             maybeEnableButtons();
          }
    
          // Google APIライブラリとGISライブラリの初期化が両方完了したら認証ボタンを表示
          function maybeEnableButtons() {
             if (gapiInited && gisInited) {
                   document.getElementById("run_button").hidden = false;
             }
          }
    
          // 実行ボタンがクリックされた時の処理
          function handleRunClick() {
             if (gapi.client.getToken() === null) {
                // トークンを取得していない場合の処理
                tokenClient.callback = async (resp) => {
                      if (resp.error !== undefined) throw (resp);
                      document.getElementById("reset_button").hidden = false;
                      document.getElementById("run_button").innerText = "更新";
                      await callScriptFunction();
                };
                tokenClient.requestAccessToken({
                   prompt: "consent"
                });
             } else {
                // トークを取得している場合の処理
                tokenClient.callback = async (resp) => {
                      if (resp.error !== undefined) throw (resp);
                      document.getElementById("content").innerHTML = "";
                      await callScriptFunction();
                };
                tokenClient.requestAccessToken({
                   prompt: ""
                });
             }
          }
    
          // リセットボタンがクリックされた時の処理
          function handleResetClick() {
             const token = gapi.client.getToken();
             if (token !== null) {
                google.accounts.oauth2.revoke(token.access_token);
                gapi.client.setToken("");
                document.getElementById("content").innerHTML = "";
                document.getElementById("run_button").innerText = "実行";
                document.getElementById("reset_button").hidden = true;
             }
          }
    
          // 実行可能APIを呼び出す処理
          async function callScriptFunction() {
             try {
                gapi.client.script.scripts.run({
                   'scriptId': SCRIPT_ID,
                   'resource': {
                      'function': 'getFilesInFolder',
                      'parameters': [FOLDER_ID],
                   },
                }).then(function(resp) {
                   const result = resp.result;
                   if (result.error && result.error.status) {
                      // API実行前にエラーになった場合の処理
                      console.error('Error calling API:');
                      console.error(JSON.stringify(result, null, 2));
                   } else if (result.error) {
                      // API実行中にエラーになった場合の処理
                      const error = result.error.details[0];
                      console.error('Script error message: ' + error.errorMessage);
                      if (error.scriptStackTraceElements) {
                         console.error('Script error stacktrace:');
                         for (let i = 0; i < error.scriptStackTraceElements.length; i++) {
                            const trace = error.scriptStackTraceElements[i];
                            console.error('\t' + trace.function + ':' + trace.lineNumber);
                         }
                      }
                   } else {
                      // 実行可能APIからのレスポンスを処理
                      const fileList = result.response.result;
                      if (fileList.length == 0) {
                         console.warn('ファイルが見つかりませんでした');
                      } else {
                         fileList.forEach((file) => {
                            li = document.createElement("li");
                            li.textContent = file;
                            document.getElementById('content').appendChild(li);
                         });
                      }
                   }
                });
             } catch (err) {
                document.getElementById('content').innerText = err.message;
                return;
             }
          }
       </script>
       <script async defer src="https://apis.google.com/js/api.js" onload="gapiLoaded()"></script>
       <script async defer src="https://accounts.google.com/gsi/client" onload="gisLoaded()"></script>
    </body>
    </html>
    

サンプルアプリのテスト

ローカルサーバの起動

  1. index.htmlが置かれているディレクトリで下記のコマンドを実行

    npm install http-server
    

  2. 次のコマンドでローカルサーバを起動

    npx http-server -p 9999
    

動作確認

  1. ブラウザで http://localhost:9999 にアクセス

    サンプルアプリを開く.png

  2. 「実行」ボタンをクリックするとOAuth認証画面が開くので実行するアカウントを選択する

    アカウントを選択.png

  3. 「このアプリはGoogleで確認されていません」などのメッセージが出ますが「続行」を選択

    続行を選択.png

  4. スコープの利用について同意を求められたら内容を確認して「続行」を選択

    スコープを確認する.png

  5. 少し待って下図のようにファイル名の一覧が表示されることを確認

    ファイル一覧の出力を確認する.png

最後に

実行可能APIについて学び始めたときは公式ドキュメントやネットの記事を参考にしてもうまく行かないことが多く少し苦労しました。

Google Cloud曰く、Google APIは有効化してから少し時間を置かないと使用できない場合があるようなので、今思うと最初にうまく動かなかったのは結果を急ぎ過ぎたからなのかもしれませんが、上手くいかなくからと色々いじり倒すと結局何が正しいのか分からなくなります。

実行可能APIの学習についてはゆっくりコーヒーでも飲みながら楽しみつつ進めるのがいいかもしれませんねー

長い手順でしたが、最後まで読んでいただき有難うございます!

2
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
4