使用 Apps Script API 执行函数

Google Apps Script API 提供 scripts.run 方法 远程执行指定的 Apps 脚本函数。您可以使用此方法 (在调用应用中),以在您的某个脚本项目中运行函数 并接收响应。

要求

您必须满足以下要求,调用方应用才能使用 scripts.run 方法。您必须

  • 将脚本项目部署为 API 可执行文件。您可以 根据需要部署、取消部署和重新部署项目。

  • 为执行作业提供适当范围的 OAuth 令牌。 此 OAuth 令牌必须涵盖所有范围 而不是仅限于被调用的函数所使用的 ID。请参阅 授权范围的完整列表 。

  • 确保脚本和调用应用的 OAuth2 客户共享一个共同的 Google Cloud 项目。 Cloud 项目必须是标准 Cloud 项目; 为 Apps 脚本项目创建的默认项目是不够的。您可以使用新的标准 Cloud 项目,也可以使用现有项目。

  • 启用 Google Apps Script API 创建项目

scripts.run 方法

scripts.run 方法需要密钥标识信息才能运行:

您可以选择将脚本配置为在开发模式下执行。 此模式使用最近保存的脚本项目版本执行 而不是最近部署的版本。为此,您可以将 devMode 布尔值, 请求正文 发送至 true。只有脚本的所有者才能在开发模式下执行脚本。

处理参数数据类型

使用 Apps Script API scripts.run 方法 通常涉及将数据作为函数参数发送到 Apps 脚本, 以函数返回值的形式返回数据。该 API 只能接受和返回 基本类型的值:字符串、数组、对象、数字和布尔值。这些 类似于 JavaScript 中的基本类型。较为复杂 Apps 脚本对象,例如 Document工作表不能传入 或者从脚本项目获取。

当您调用应用以强类型语言(例如 Java 中,它会以泛型对象列表或数组的形式传入参数 与这些基本类型相对应。在许多情况下,您可以将简单的 类型转换。例如,一个接受数字的函数 参数可以作为 Java DoubleIntegerLong 对象作为 参数,而无需进行额外处理。

当 API 返回函数响应时,您通常需要将 使其返回正确的类型,然后才能使用。以下是一些 基于 Java 的示例:

  • API 返回给 Java 应用的编号以 java.math.BigDecimal 对象,可能需要将其转换为 根据需要使用 Doublesint 类型。
  • 如果 Apps 脚本函数返回字符串数组,则 Java 应用 将响应转换为 List<String> 对象:

    List<String> mylist = (List<String>)(op.getResponse().get("result"));
    
  • 如果您希望返回 Bytes 的数组,可能会发现很方便 在 Apps 脚本函数中将数组编码为 base64 字符串; 返回该字符串:

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

下面的示例代码示例说明了 来解读 API 响应

一般程序

下面介绍了使用 Apps Script API 的一般流程 以执行 Apps 脚本函数:

第 1 步:设置通用 Cloud 项目

您的脚本和调用应用需要共用 Cloud 项目中。此 Cloud 项目可以是现有项目, 为此目的创建一个新项目。有了 Cloud 项目后 必须切换脚本项目才能使用它

第 2 步:将脚本部署为 API 可执行文件

  1. 打开包含您要使用的函数的 Apps 脚本项目。
  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. 项目 OAuth 范围下,记录 脚本所需的资源。
  4. 在调用应用代码中,生成脚本 OAuth 访问令牌 。这并不是 API 本身使用的令牌,而是 所需的资源。应使用 Cloud 项目客户端 ID 和您记录的脚本范围。

    Google 客户端库 帮助构建此令牌和处理应用的 OAuth, 通常可以让您构建更高级别的“凭据”对象 使用脚本范围进行控制请参阅 如需查看示例,请参阅 Apps Script API 快速入门 从范围列表构建凭据对象的流程。

第 4 步:发出 script.run 请求

配置调用应用后,您可以 scripts.run 调用。每个 API 调用包括以下步骤:

  1. 构建 API 请求 脚本 ID、函数名称 参数。
  2. 使 scripts.run 调用并加入您在 标头(如果使用基本的 POST 请求),也可以使用凭据对象 使用脚本范围构建的
  3. 等待脚本执行完毕。脚本最多 6 分钟的执行时间,因此您的应用应有此时间。
  4. 脚本函数在完成后可能会返回一个值, 如果该值是受支持的类型,该值将传回应用程序。

您可以参阅 script.run API 调用示例

API 请求示例

以下示例展示了如何在 Google Cloud 控制台中 调用 Apps 脚本函数来输出 文件夹中。Apps 脚本项目的脚本 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 脚本项目创建的默认项目是不够的。通过 标准 Cloud 项目可以是新项目,也可以是现有项目。

  2. 基本参数和返回值类型。该 API 不能传递或返回 Apps 脚本特有的对象(例如 DocumentsBlob日历云端硬盘文件等)复制到 应用。仅限基本类型,如字符串、数组、对象、数字和 可以传递和返回布尔值。

  3. OAuth 范围。该 API 只能执行 一个必需的范围。这意味着您无法使用 API 来调用脚本, 不需要授权的一项或多项服务。

  4. 无触发器。API 无法创建 Apps 脚本 触发器