Google Apps Script API 提供 scripts.run 方法,可遠端執行指定的 Apps Script 函式。您可以在呼叫應用程式中使用這個方法,遠端執行其中一個指令碼專案中的函式,並接收回應。
需求條件
通話應用程式必須符合下列需求,才能使用 scripts.run 方法。您必須:
將指令碼專案部署為 API 執行檔。您可以視需要部署、取消部署及重新部署專案。
為執行作業提供範圍正確的 OAuth 權杖。 這個 OAuth 權杖必須涵蓋指令碼使用的所有範圍,而不只是所呼叫函式使用的範圍。如需完整的授權範圍清單,請參閱方法參考資料。
確認指令碼和呼叫應用程式的 OAuth2 用戶端共用同一個 Google Cloud 專案。 Cloud 專案必須是標準 Cloud 專案;為 Apps Script 專案建立的預設專案不足以支援這項功能。您可以使用新的標準 Cloud 專案或現有專案。
在 Cloud 專案中啟用 Google Apps Script API。
scripts.run 方法
您可以選擇將指令碼設定為在開發模式下執行。
這個模式會使用指令碼專案最近儲存的版本執行,而不是最近部署的版本。只要將要求主體中的 devMode 布林值設為 true,即可完成這項操作。只有指令碼擁有者才能在開發模式下執行指令碼。
處理參數資料型別
使用 Apps Script API scripts.run 方法通常需要將資料做為函式參數傳送至 Apps Script,並以函式傳回值的形式取得資料。API 只能接受及傳回基本型別的值:字串、陣列、物件、數字和布林值。這些類型與 JavaScript 中的基本型別類似。API 無法將 Document 或 Sheet 等較複雜的 Apps Script 物件傳遞至指令碼專案,或從指令碼專案傳遞這類物件。
如果呼叫應用程式是以 Java 等強型別語言編寫,則會將參數做為與這些基本型別對應的泛型物件清單或陣列傳遞。在許多情況下,您可以自動套用簡單的型別轉換。舉例來說,如果函式採用數字參數,則可將 Java Double、Integer 或 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 字串,然後傳回該字串的做法很方便:return Utilities.base64Encode(myByteArray); // returns a String.
下列程式碼範例說明如何解讀 API 回應。
一般程序
以下說明使用 Apps Script API 執行 Apps Script 函式的一般程序:
步驟 1:設定通用 Cloud 專案
指令碼和呼叫應用程式必須共用相同的 Cloud 專案。這個 Cloud 專案可以是現有專案,也可以是專為此目的建立的新專案。建立 Cloud 專案後,請務必將指令碼專案切換為使用該專案。
步驟 2:以 API 執行檔形式部署指令碼
- 開啟含有要使用函式的 Apps Script 專案。
 - 依序按一下右上方的「Deploy」(部署) >「New Deployment」(新部署)。
 - 在開啟的對話方塊中,依序點選「啟用部署類型」圖示 
>「API 可執行檔」。
 - 在「誰可以存取」下拉式選單中,選取允許使用 Apps Script API 呼叫指令碼函式的使用者。
 - 按一下 [Deploy] (部署)。
 
步驟 3:設定通話應用程式
呼叫應用程式必須先啟用 Apps Script API 並建立 OAuth 憑證,才能使用該 API。您必須具備 Cloud 專案的存取權,才能執行這項操作。
- 設定呼叫應用程式和指令碼使用的 Cloud 專案。 操作步驟如下:
 - 開啟指令碼專案,然後按一下左側的「總覽」圖示 
。
 - 在「專案 OAuth 範圍」下方,記錄指令碼需要的所有範圍。
 在呼叫應用程式程式碼中,為 API 呼叫產生指令碼 OAuth 存取權杖。這不是 API 本身使用的權杖,而是指令碼執行時需要的權杖。請使用您記錄的 Cloud 專案用戶端 ID 和指令碼範圍建立。
Google 用戶端程式庫可大幅協助您建構這個權杖,並為應用程式處理 OAuth,通常允許您改用指令碼範圍建構較高層級的「憑證」物件。如需從範圍清單建構憑證物件的範例,請參閱 Apps Script API 快速入門導覽課程。
步驟 4:提出 script.run 要求
設定通話應用程式後,即可撥打 scripts.run 電話。每個 API 呼叫都包含下列步驟:
- 使用指令碼 ID、函式名稱和任何必要參數,建構 API 要求。
 - 發出 
scripts.run呼叫,並在標頭中加入您建立的指令碼 OAuth 權杖 (如果使用基本POST要求),否則請使用您以指令碼範圍建立的憑證物件。 - 讓指令碼執行完畢。指令碼最多可執行六分鐘,因此應用程式應允許這段時間。
 - 完成後,指令碼函式可能會傳回值,如果該值屬於支援的類型,API 會將其傳回應用程式。
 
您可以在下方找到script.run API 呼叫的範例。
API 要求範例
下列範例說明如何以各種語言發出 Apps Script API 執行要求,呼叫 Apps Script 函式,列印使用者根目錄中的資料夾清單。您必須在以 ENTER_YOUR_SCRIPT_ID_HERE 表示的位置,指定包含所執行函式的 Apps Script 專案的指令碼 ID。範例會使用各語言的 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 有下列限制:
共用的 Cloud 專案。所呼叫的指令碼和呼叫應用程式必須共用 Cloud 專案。Cloud 專案必須是標準 Cloud 專案;為 Apps Script 專案建立的預設專案不足以支援這項功能。標準 Cloud 專案可以是新專案或現有專案。
基本參數和傳回型別。API 無法將 Apps Script 專屬物件 (例如 Documents、Blobs、Calendars、Drive Files 等) 傳遞或傳回給應用程式。只能傳遞及傳回字串、陣列、物件、數字和布林值等基本型別。
OAuth 範圍。API 只能執行至少有一個必要範圍的指令碼。也就是說,您無法使用 API 呼叫不需要授權一或多項服務的指令碼。
沒有觸發條件。API 無法建立 Apps Script 觸發條件。