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
方法
scripts.run
方法需要關鍵識別資訊才能執行:
您可以選擇將指令碼設為在開發模式下執行。這個模式會使用最近儲存的指令碼專案版本執行,而不是最近部署的版本。方法是將要求主體中的 devMode
布林值設為 true
。只有指令碼擁有者可以在開發模式下執行指令碼。
處理參數資料類型
使用 Apps Script API scripts.run
方法時,通常會將資料以函式參數的形式傳送至 Apps Script,並將資料以函式傳回值的形式傳回。API 只能使用及傳回基本類型的值:字串、陣列、物件、數字和布林值。這些類型與 JavaScript 中的基本類型相似。較複雜的 Apps Script 物件 (例如 Document 或 Sheet) 無法透過 API 傳入或傳出指令碼專案。
如果呼叫應用程式是使用 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 專案。
- 依序按一下右上方的「部署」>「新部署」。
- 在隨即開啟的對話方塊中,依序按一下「啟用部署類型」 >「API 可執行檔」。
- 在「有權存取」下拉式選單中,選取可使用 Apps Script API 呼叫指令碼函式的使用者。
- 按一下「部署」。
步驟 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。
您必須在代管指令碼的專案中啟用雲端硬碟 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 有幾項限制:
一般 Cloud 專案。被呼叫的指令碼和呼叫應用程式必須共用一個 Cloud 專案。Cloud 專案必須是 標準 Cloud 專案;為 Apps Script 專案建立的預設專案不夠用。標準 Cloud 專案可以是新專案或現有專案。
基本參數和傳回類型。API 無法將 Apps Script 專屬物件 (例如 文件、Blob、日曆、雲端硬碟檔案 等) 傳遞或傳回至應用程式。您只能傳遞及傳回字串、陣列、物件、數字和布林值等基本類型。
OAuth 範圍。API 只能執行至少包含一個必要範圍的腳本。也就是說,您無法使用 API 呼叫不需要一或多項服務授權的腳本。
沒有觸發條件:API 無法建立 Apps Script 觸發條件。