Apps Script API を使用して関数を実行する

Google Apps Script API には、指定した Apps Script 関数をリモートで実行する scripts.run メソッドが用意されています。このメソッドを呼び出し元アプリケーションで使用すると、スクリプト プロジェクトの 1 つで関数をリモートで実行し、レスポンスを受け取ることができます。

要件

呼び出し元のアプリが scripts.run メソッドを使用するには、次の要件を満たす必要があります。必須事項:

  • スクリプト プロジェクトを API 実行可能ファイルとしてデプロイします。プロジェクトのデプロイ、デプロイ解除、再デプロイは、必要に応じて行うことができます。

  • 適切なスコープの OAuth トークンを提供して実行します。この OAuth トークンは、呼び出された関数で使用されるスコープだけでなく、スクリプトで使用されるすべてのスコープをカバーする必要があります。メソッド リファレンスで承認スコープの完全なリストをご覧ください。

  • スクリプトと呼び出し元アプリケーションの OAuth2 クライアントが、共通の Google Cloud プロジェクトを共有していることを確認します。 Cloud プロジェクトは標準 Cloud プロジェクトである必要があります。Apps Script プロジェクト用に作成されたデフォルトのプロジェクトでは不十分です。新しい標準 Cloud プロジェクトまたは既存のプロジェクトを使用できます。

  • Cloud プロジェクトで Google Apps Script API を有効にします

scripts.run メソッド

scripts.run メソッドを実行するには、次のキー識別情報が必要です。

必要に応じて、開発モードで実行するようにスクリプトを構成できます。このモードは、最後にデプロイされたバージョンではなく、最後に保存されたスクリプト プロジェクトで実行されます。これを行うには、リクエスト本文devMode ブール値を true に設定します。スクリプトのオーナーのみが、開発モードでスクリプトを実行できます。

パラメータのデータ型の処理

通常、Apps Script API の scripts.run メソッドでは、データを関数のパラメータとして Apps Script に送信し、関数の戻り値としてデータを取得します。API は、文字列、配列、オブジェクト、数値、ブール値などの基本的な型の値のみを受け取って返すことができます。これらは、JavaScript の基本型に似ています。DocumentSheet などの複雑な Apps Script オブジェクトは、API によってスクリプト プロジェクト間で受け渡しできません。

呼び出し元アプリケーションが Java などの厳密に型付けされた言語で記述されている場合、パラメータは、これらの基本的な型に対応する汎用オブジェクトのリストまたは配列として渡されます。多くの場合、単純な型変換を自動的に適用できます。たとえば、数値パラメータを受け取る関数には、追加の処理を行わずに、パラメータとして Java、DoubleInteger、または Long オブジェクトを指定できます。

API から関数のレスポンスが返されると、戻り値を適切な型にキャストしてから使用することがよくあります。Java ベースの例を以下に示します。

  • API から Java アプリに返される数値は、java.math.BigDecimal オブジェクトとして到着します。必要に応じて Doubles 型や int 型に変換する必要があります。
  • Apps Script 関数が文字列の配列を返す場合、Java アプリケーションはレスポンスを List<String> オブジェクトにキャストします。

    List<String> mylist = (List<String>)(op.getResponse().get("result"));
    
  • Bytes の配列を返す場合は、Apps Script 関数内で配列を base64 String としてエンコードして、その文字列を返すと便利です。

    return Utilities.base64Encode(myByteArray); // returns a String.
    

以下のサンプルコードは、API レスポンスの解釈方法を示しています。

一般的な手順

Apps Script API を使用して Apps Script の関数を実行する一般的な手順は次のとおりです。

ステップ 1: 共通の Cloud プロジェクトを設定する

スクリプトと呼び出し元アプリケーションの両方が同じ Cloud プロジェクトを共有する必要があります。この Cloud プロジェクトは、既存のプロジェクトまたはこの目的のために作成された新しいプロジェクトにできます。Cloud プロジェクトを作成したら、スクリプト プロジェクトを使用するように切り替える必要があります。

ステップ 2: スクリプトを実行可能 API としてデプロイする

  1. 使用する関数を含む Apps Script プロジェクトを開きます。
  2. 右上の [Deploy] > [New Deployment] をクリックします。
  3. 表示されたダイアログで、[デプロイタイプを有効にする] > [API 実行可能ファイル] の順にクリックします。
  4. [アクセスできるユーザー] プルダウン メニューで、Apps Script API を使用してスクリプトの関数を呼び出すことができるユーザーを選択します。
  5. [デプロイ] をクリックします。

ステップ 3: 呼び出し元アプリケーションを設定する

呼び出し元アプリケーションは、使用する前に Apps Script API を有効にして OAuth 認証情報を確立する必要があります。これを行うには、Cloud プロジェクトへのアクセス権が必要です。

  1. 呼び出し元アプリケーションとスクリプトが使用している Cloud プロジェクトを構成します。これを行うには、次の手順を行います。
    1. Cloud プロジェクトで Apps Script API を有効にします
    2. OAuth 同意画面を構成する
    3. OAuth 認証情報を作成する
  2. スクリプト プロジェクトを開き、左側の概要アイコン をクリックします。
  3. [プロジェクト OAuth スコープ] で、スクリプトに必要なすべてのスコープを記録します。
  4. 呼び出し元アプリケーション コードで、API 呼び出し用のスクリプト OAuth アクセス トークンを生成します。これは、API 自体が使用するトークンではなく、スクリプトが実行時に必要とするトークンです。Cloud プロジェクトのクライアント ID と記録したスクリプト スコープを使用して作成する必要があります。

    Google クライアント ライブラリを使用すると、このトークンの作成とアプリケーション用の OAuth の処理に非常に役立ちます。通常は、スクリプト スコープを使用してより高いレベルの「認証情報」オブジェクトを構築できるようになるからです。スコープのリストから認証情報オブジェクトを作成する例については、Apps Script API クイックスタートをご覧ください。

ステップ 4: script.run リクエストを行う

呼び出し元アプリケーションの設定が完了すると、scripts.run 呼び出しを行うことができます。各 API 呼び出しは次のステップで構成されます。

  1. スクリプト ID、関数名、必要なパラメータを使用して API リクエストを作成します。
  2. scripts.run を呼び出して、ヘッダーに作成したスクリプト OAuth トークンを含める(基本的な POST リクエストを使用している場合)か、スクリプト スコープで作成した認証情報オブジェクトを使用します。
  3. スクリプトの実行を終了します。スクリプトの実行時間は最大 6 分であるため、アプリケーションでこの処理を行えるようにする必要があります。
  4. 終了時に、スクリプト関数が値を返すことがあります。値がサポートされている型である場合、API はアプリケーションを返します。

script.run API 呼び出しの例を以下に示します。

API リクエストの例

次の例では、Apps Script API の実行リクエストをさまざまな言語で作成し、Apps Script 関数を呼び出してユーザーのルート ディレクトリ内にあるフォルダのリストを出力します。実行した関数を含む Apps Script プロジェクトのスクリプト ID を ENTER_YOUR_SCRIPT_ID_HERE で指定した場所に指定する必要があります。このサンプルは、言語ごとに Google API クライアント ライブラリを使用しています。

ターゲット スクリプト

このスクリプトの関数は Drive API を使用します。

スクリプトをホストするプロジェクトで Drive API を有効にする必要があります。

また、呼び出し元のアプリケーションは、次のドライブ スコープを含む OAuth 認証情報を送信する必要があります。

  • https://www.googleapis.com/auth/drive

このサンプル アプリケーションでは、Google クライアント ライブラリを使用し、このスコープを使用して OAuth 用の認証情報オブジェクトを構築します。

/**
 * Return the set of folder names contained in the user's root folder as an
 * object (with folder IDs as keys).
 * @return {Object} A set of folder names keyed by folder ID.
 */
function getFoldersUnderRoot() {
  const root = DriveApp.getRootFolder();
  const folders = root.getFolders();
  const folderSet = {};
  while (folders.hasNext()) {
    const folder = folders.next();
    folderSet[folder.getId()] = folder.getName();
  }
  return folderSet;
}

Java


/**
 * Create a HttpRequestInitializer from the given one, except set
 * the HTTP read timeout to be longer than the default (to allow
 * called scripts time to execute).
 *
 * @param {HttpRequestInitializer} requestInitializer the initializer
 *                                 to copy and adjust; typically a Credential object.
 * @return an initializer with an extended read timeout.
 */
private static HttpRequestInitializer setHttpTimeout(
    final HttpRequestInitializer requestInitializer) {
  return new HttpRequestInitializer() {
    @Override
    public void initialize(HttpRequest httpRequest) throws IOException {
      requestInitializer.initialize(httpRequest);
      // This allows the API to call (and avoid timing out on)
      // functions that take up to 6 minutes to complete (the maximum
      // allowed script run time), plus a little overhead.
      httpRequest.setReadTimeout(380000);
    }
  };
}

/**
 * Build and return an authorized Script client service.
 *
 * @param {Credential} credential an authorized Credential object
 * @return an authorized Script client service
 */
public static Script getScriptService() throws IOException {
  Credential credential = authorize();
  return new Script.Builder(
      HTTP_TRANSPORT, JSON_FACTORY, setHttpTimeout(credential))
      .setApplicationName(APPLICATION_NAME)
      .build();
}

/**
 * Interpret an error response returned by the API and return a String
 * summary.
 *
 * @param {Operation} op the Operation returning an error response
 * @return summary of error response, or null if Operation returned no
 * error
 */
public static String getScriptError(Operation op) {
  if (op.getError() == null) {
    return null;
  }

  // Extract the first (and only) set of error details and cast as a Map.
  // The values of this map are the script's 'errorMessage' and
  // 'errorType', and an array of stack trace elements (which also need to
  // be cast as Maps).
  Map<String, Object> detail = op.getError().getDetails().get(0);
  List<Map<String, Object>> stacktrace =
      (List<Map<String, Object>>) detail.get("scriptStackTraceElements");

  java.lang.StringBuilder sb =
      new StringBuilder("\nScript error message: ");
  sb.append(detail.get("errorMessage"));
  sb.append("\nScript error type: ");
  sb.append(detail.get("errorType"));

  if (stacktrace != null) {
    // There may not be a stacktrace if the script didn't start
    // executing.
    sb.append("\nScript error stacktrace:");
    for (Map<String, Object> elem : stacktrace) {
      sb.append("\n  ");
      sb.append(elem.get("function"));
      sb.append(":");
      sb.append(elem.get("lineNumber"));
    }
  }
  sb.append("\n");
  return sb.toString();
}

public static void main(String[] args) throws IOException {
  // ID of the script to call. Acquire this from the Apps Script editor,
  // under Publish > Deploy as API executable.
  String scriptId = "ENTER_YOUR_SCRIPT_ID_HERE";
  Script service = getScriptService();

  // Create an execution request object.
  ExecutionRequest request = new ExecutionRequest()
      .setFunction("getFoldersUnderRoot");

  try {
    // Make the API request.
    Operation op =
        service.scripts().run(scriptId, request).execute();

    // Print results of request.
    if (op.getError() != null) {
      // The API executed, but the script returned an error.
      System.out.println(getScriptError(op));
    } else {
      // The result provided by the API needs to be cast into
      // the correct type, based upon what types the Apps
      // Script function returns. Here, the function returns
      // an Apps Script Object with String keys and values,
      // so must be cast into a Java Map (folderSet).
      Map<String, String> folderSet =
          (Map<String, String>) (op.getResponse().get("result"));
      if (folderSet.size() == 0) {
        System.out.println("No folders returned!");
      } else {
        System.out.println("Folders under your root folder:");
        for (String id : folderSet.keySet()) {
          System.out.printf(
              "\t%s (%s)\n", folderSet.get(id), id);
        }
      }
    }
  } catch (GoogleJsonResponseException e) {
    // The API encountered a problem before the script was called.
    e.printStackTrace(System.out);
  }
}

JavaScript

/**
 * Load the API and make an API call.  Display the results on the screen.
 */
function callScriptFunction() {
  const scriptId = '<ENTER_YOUR_SCRIPT_ID_HERE>';

  // Call the Apps Script API run method
  //   'scriptId' is the URL parameter that states what script to run
  //   'resource' describes the run request body (with the function name
  //              to execute)
  try {
    gapi.client.script.scripts.run({
      'scriptId': scriptId,
      'resource': {
        'function': 'getFoldersUnderRoot',
      },
    }).then(function(resp) {
      const result = resp.result;
      if (result.error && result.error.status) {
        // The API encountered a problem before the script
        // started executing.
        appendPre('Error calling API:');
        appendPre(JSON.stringify(result, null, 2));
      } else if (result.error) {
        // The API executed, but the script returned an error.

        // Extract the first (and only) set of error details.
        // The values of this object are the script's 'errorMessage' and
        // 'errorType', and an array of stack trace elements.
        const error = result.error.details[0];
        appendPre('Script error message: ' + error.errorMessage);

        if (error.scriptStackTraceElements) {
          // There may not be a stacktrace if the script didn't start
          // executing.
          appendPre('Script error stacktrace:');
          for (let i = 0; i < error.scriptStackTraceElements.length; i++) {
            const trace = error.scriptStackTraceElements[i];
            appendPre('\t' + trace.function + ':' + trace.lineNumber);
          }
        }
      } else {
        // The structure of the result will depend upon what the Apps
        // Script function returns. Here, the function returns an Apps
        // Script Object with String keys and values, and so the result
        // is treated as a JavaScript object (folderSet).

        const folderSet = result.response.result;
        if (Object.keys(folderSet).length == 0) {
          appendPre('No folders returned!');
        } else {
          appendPre('Folders under your root folder:');
          Object.keys(folderSet).forEach(function(id) {
            appendPre('\t' + folderSet[id] + ' (' + id + ')');
          });
        }
      }
    });
  } catch (err) {
    document.getElementById('content').innerText = err.message;
    return;
  }
}

Node.js

/**
 * Call an Apps Script function to list the folders in the user's root Drive
 * folder.
 *
 */
async function callAppsScript() {
  const scriptId = '1xGOh6wCm7hlIVSVPKm0y_dL-YqetspS5DEVmMzaxd_6AAvI-_u8DSgBT';

  const {GoogleAuth} = require('google-auth-library');
  const {google} = require('googleapis');

  // Get credentials and build service
  // TODO (developer) - Use appropriate auth mechanism for your app
  const auth = new GoogleAuth({
    scopes: 'https://www.googleapis.com/auth/drive',
  });
  const script = google.script({version: 'v1', auth});

  try {
    // Make the API request. The request object is included here as 'resource'.
    const resp = await script.scripts.run({
      auth: auth,
      resource: {
        function: 'getFoldersUnderRoot',
      },
      scriptId: scriptId,
    });
    if (resp.error) {
      // The API executed, but the script returned an error.

      // Extract the first (and only) set of error details. The values of this
      // object are the script's 'errorMessage' and 'errorType', and an array
      // of stack trace elements.
      const error = resp.error.details[0];
      console.log('Script error message: ' + error.errorMessage);
      console.log('Script error stacktrace:');

      if (error.scriptStackTraceElements) {
        // There may not be a stacktrace if the script didn't start executing.
        for (let i = 0; i < error.scriptStackTraceElements.length; i++) {
          const trace = error.scriptStackTraceElements[i];
          console.log('\t%s: %s', trace.function, trace.lineNumber);
        }
      }
    } else {
      // The structure of the result will depend upon what the Apps Script
      // function returns. Here, the function returns an Apps Script Object
      // with String keys and values, and so the result is treated as a
      // Node.js object (folderSet).
      const folderSet = resp.response.result;
      if (Object.keys(folderSet).length == 0) {
        console.log('No folders returned!');
      } else {
        console.log('Folders under your root folder:');
        Object.keys(folderSet).forEach(function(id) {
          console.log('\t%s (%s)', folderSet[id], id);
        });
      }
    }
  } catch (err) {
    // TODO(developer) - Handle error
    throw err;
  }
}

Python

from __future__ import print_function

import google.auth
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError


def main():
    """Runs the sample.
    """
    # pylint: disable=maybe-no-member
    script_id = '1VFBDoJFy6yb9z7-luOwRv3fCmeNOzILPnR4QVmR0bGJ7gQ3QMPpCW-yt'

    creds, _ = google.auth.default()
    service = build('script', 'v1', credentials=creds)

    # Create an execution request object.
    request = {"function": "getFoldersUnderRoot"}

    try:
        # Make the API request.
        response = service.scripts().run(scriptId=script_id,
                                         body=request).execute()
        if 'error' in response:
            # The API executed, but the script returned an error.
            # Extract the first (and only) set of error details. The values of
            # this object are the script's 'errorMessage' and 'errorType', and
            # a list of stack trace elements.
            error = response['error']['details'][0]
            print(f"Script error message: {0}.{format(error['errorMessage'])}")

            if 'scriptStackTraceElements' in error:
                # There may not be a stacktrace if the script didn't start
                # executing.
                print("Script error stacktrace:")
                for trace in error['scriptStackTraceElements']:
                    print(f"\t{0}: {1}."
                          f"{format(trace['function'], trace['lineNumber'])}")
        else:
            # The structure of the result depends upon what the Apps Script
            # function returns. Here, the function returns an Apps Script
            # Object with String keys and values, and so the result is
            # treated as a Python dictionary (folder_set).
            folder_set = response['response'].get('result', {})
            if not folder_set:
                print('No folders returned!')
            else:
                print('Folders under your root folder:')
                for (folder_id, folder) in folder_set.items():
                    print(f"\t{0} ({1}).{format(folder, folder_id)}")

    except HttpError as error:
        # The API encountered a problem before the script started executing.
        print(f"An error occurred: {error}")
        print(error.content)


if __name__ == '__main__':
    main()

制限事項

Apps Script API には次のような制限があります。

  1. 共通の Cloud プロジェクト。呼び出すスクリプトと呼び出し元のアプリケーションは、Cloud プロジェクトを共有する必要があります。Cloud プロジェクトは標準 Cloud プロジェクトである必要があります。Apps Script プロジェクト用に作成されたデフォルトのプロジェクトでは不十分です。標準の Cloud プロジェクトは、新しいプロジェクトでも既存のプロジェクトでもかまいません。

  2. 基本的なパラメータと戻り値の型。API は、Apps Script 固有のオブジェクト(ドキュメントBlobカレンダードライブ ファイルなど)をアプリケーションに渡したり、返したりできません。文字列、配列、オブジェクト、数値、ブール値などの基本的な型のみを渡して返すことができます。

  3. OAuth スコープ。API は、必要なスコープが 1 つ以上あるスクリプトのみを実行できます。つまり、API を使用して、1 つまたは複数のサービスの認可を必要としないスクリプトを呼び出すことはできません。

  4. トリガーなし。この API は Apps Script のトリガーを作成できません。