透過 Apps Script API 執行函式

Google Apps Script API 提供 scripts.run 方法 遠端執行指定的 Apps Script 函式。您可以使用這個方法 呼叫應用程式中的函式,以便在其中一個指令碼專案中執行函式 遠端接收回應。

需求條件

您必須符合下列需求條件,才能使用呼叫應用程式 scripts.run 方法。您必須

  • 將指令碼專案部署為 API 執行檔。你可以 視需要部署、取消部署和重新部署專案

  • 提供適用範圍的適當範圍 OAuth 權杖。 此 OAuth 權杖必須涵蓋所有範圍 而非只有呼叫函式所用的參數。詳情請參閱 授權範圍的完整清單 。

  • 確保指令碼和呼叫應用程式的 OAuth2 用戶端共用一個通用 Google Cloud 專案。 Cloud 專案必須是標準 Cloud 專案。 為 Apps Script 專案建立的預設專案不足夠。您可以使用新的標準 Cloud 專案或現有專案。

  • 啟用 Google Apps Script API 在 Cloud 專案中設定容器

,瞭解如何調查及移除這項存取權。

scripts.run 方法

scripts.run 方法需要關鍵識別資訊才能執行:

您可以選擇將指令碼設為在開發模式中執行。 這個模式會使用最近儲存的指令碼專案版本執行 而非最新部署的版本方法是將 布林值的 devMode 布林值 要求主體true。只有指令碼的擁有者可以在開發模式下執行這個指令碼。

處理參數資料類型

使用 Apps Script API scripts.run 方法 通常需要將資料傳送至 Apps Script 做為函式參數。 以函式傳回值的形式傳回資料API 只能擷取及傳回 具有基本型別的值:字串、陣列、物件、數字和布林值。這些 與 JavaScript 中的基本類型類似更複雜 Apps Script 物件,例如 Document 或「工作表」無法傳入 或是透過 API 從指令碼專案中執行

當您呼叫應用程式以強式語言 (例如 Java,它會以一般物件的清單或陣列的形式傳入參數 對應基本型別在許多情況下 自動促成轉換類型例如,函式會 參數可以是 Java DoubleIntegerLong 物件,做為 參數,無需額外處理。

API 傳回函式回應時,您通常需要將 傳回值,才能使用這個值。以下是一些 Java 範例:

  • API 向 Java 應用程式傳回的數字 java.math.BigDecimal 物件,且可能需要轉換為 視需要使用 Doublesint 類型。
  • 如果 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. 依序點選右上方的「部署」&gt;「新增部署作業」
  3. 在隨即開啟的對話方塊中,按一下「啟用部署類型」圖示 &gt; 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 Scope」下方,記錄 指令碼的需求。
  4. 在呼叫的應用程式程式碼中,產生指令碼 OAuth 存取權杖 以供 API 呼叫使用這不是 API 本身使用的權杖,而是 API 本身 就必須在執行時要求指令碼開發人員應使用 Cloud 專案用戶端 ID 和您記錄的指令碼範圍。

    Google 用戶端程式庫可以大幅 協助您建構此權杖及處理應用程式的 OAuth 通常可讓您改為建構更高層級的「憑證」物體 使用指令碼範圍詳情請參閱 Apps Script API 快速入門導覽課程:範例 從範圍清單建構憑證物件

步驟 4:提出 script.run 要求

設定呼叫應用程式後,您就可以 scripts.run 呼叫。每個 API 呼叫包含下列步驟:

  1. 建立 API 要求 使用指令碼 ID、函式名稱 參數。
  2. scripts.run 呼叫並放入您在 標頭 (如果使用基本 POST 要求) 或者使用憑證物件 建立容器
  3. 允許指令碼執行完畢。指令碼最多保留 上限 ,因此應用程式應允許此作業。
  4. 完成後,指令碼函式可能會傳回值,這時 API 如果值是支援的類型,就會傳回應用程式。

您可以查看 script.run API 呼叫的範例

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

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 專案建立的預設專案不足夠。 可以是新專案或現有專案

  2. 基本參數和傳回類型。API 無法傳遞或傳回 Apps Script 專屬物件 (例如 DocumentsBlob日曆雲端硬碟檔案等) 升級至 應用程式。只有字串、陣列、物件、數字和 可以傳遞及傳回布林值。

  3. OAuth 範圍。API 只能執行指令碼至少 必須定義一個必要範圍這表示您無法使用 API 呼叫指令碼 不必取得一或多項服務授權。

  4. 沒有觸發條件。API 無法建立 Apps Script 觸發條件