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 プロジェクトを使用できます。

  • 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 の DoubleIntegerLong オブジェクトをパラメータとして渡すことができます。

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 文字列としてエンコードし、その文字列を返す方が便利な場合があります。

    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. [Project Oauth scopes] に、スクリプトに必要なすべてのスコープを記録します。
  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

 import {GoogleAuth} from 'google-auth-library'; import {google} from 'googleapis';  /**  * Calls an Apps Script function to list the folders in the user's root Drive folder.  */ async function callAppsScript() {   // The ID of the Apps Script project to call.   const scriptId = '1xGOh6wCm7hlIVSVPKm0y_dL-YqetspS5DEVmMzaxd_6AAvI-_u8DSgBT';    // Authenticate with Google and get an authorized client.   // TODO (developer): Use an appropriate auth mechanism for your app.   const auth = new GoogleAuth({     scopes: 'https://www.googleapis.com/auth/drive',   });    // Create a new Apps Script API client.   const script = google.script({version: 'v1', auth});    const resp = await script.scripts.run({     auth,     requestBody: {       // The name of the function to call in the Apps Script project.       function: 'getFoldersUnderRoot',     },     scriptId,   });    if (resp.data.error?.details?.[0]) {     // The API executed, but the script returned an error.     // Extract the error details.     const error = resp.data.error.details[0];     console.log(`Script error message: ${error.errorMessage}`);     console.log('Script error stacktrace:');      if (error.scriptStackTraceElements) {       // Log the stack trace.       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 script executed successfully.     // The structure of the response depends on the Apps Script function's return value.     const folderSet = resp.data.response ?? {};     if (Object.keys(folderSet).length === 0) {       console.log('No folders returned!');     } else {       console.log('Folders under your root folder:');       Object.keys(folderSet).forEach((id) => {         console.log('\t%s (%s)', folderSet[id], id);       });     }   } } 

Python

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}.{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 つの必須スコープを持つスクリプトのみを実行できます。つまり、1 つ以上のサービスの承認を必要としないスクリプトを呼び出すために API を使用することはできません。

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