Apps Script API には、指定された Google Apps Script 関数を
リモートで実行する
scripts.run メソッドが用意されています。呼び出し元アプリケーションでこのメソッドを使用すると、スクリプト プロジェクトの関数をリモートで実行してレスポンスを受け取ることができます。
要件
呼び出し元アプリケーションで
scripts.run メソッドを使用するには、
次の操作を行う必要があります。
スクリプト プロジェクトを実行可能 API としてデプロイする。プロジェクトは、必要に応じてデプロイ、デプロイ解除、再デプロイできます。
実行に適したスコープの OAuth トークンを提供する。この OAuth トークン は、呼び出された関数で使用されるスコープだけでなく、スクリプトで使用されるすべてのスコープをカバーする必要があります 。承認スコープの完全なリストについては、 メソッド リファレンス をご覧ください。
スクリプトと呼び出し元アプリケーションの OAuth2 クライアントが共通の Google Cloud プロジェクトを共有していることを確認する。Cloud プロジェクトは 標準の Cloud プロジェクトである必要があります。 Apps Script プロジェクト用に作成されたデフォルト プロジェクトでは 不十分です。新しい標準のクラウド プロジェクトを使用することも、既存のプロジェクトを使用することもできます。
scripts.run メソッド
The scripts.run メソッド
には次の情報が必要です。
必要に応じて、スクリプトを 開発モードで実行するように構成できます。
このモードでは、最新のデプロイ済みバージョンではなく、スクリプト
プロジェクトの最新の 保存済みバージョンで実行されます。これを行うには、リクエスト本文の
devMode ブール値を
trueに設定します。開発モードで実行できるのは、スクリプトの所有者のみです。
パラメータのデータ型を処理する
通常、Apps Script API
scripts.run メソッド
を使用すると、関数
パラメータとして Apps Script にデータを送信し、関数戻り値としてデータを受け取ります。API
は、基本的な型(文字列、配列、オブジェクト、数値、ブール値)の値のみを受け渡しできます。Document や
Sheet などの複雑な Apps Script オブジェクトは、API を介してスクリプト プロジェクトとの間で受け渡しできません。
呼び出し元アプリケーションが Java などの厳密な型付き言語で記述されている場合、これらの基本型に対応する汎用オブジェクトのリストまたは配列としてパラメータを渡します。多くの場合、型変換を自動的に適用できます。たとえば、数値パラメータを受け取る関数には、追加の処理を行わずに
Java の Double、Integer、Long オブジェクトをパラメータとして渡すことができます。
API が関数レスポンスを返す場合、返された値を使用する前に正しい型にキャストする必要があることがよくあります。Java ベースの例をいくつか示します。
- API から Java アプリケーションに返される数値は
java.math.BigDecimalオブジェクトとして到着するため、Double型または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 プロジェクトを作成したら、スクリプト プロジェクトを切り替えて使用する必要があります 。
ステップ 2: スクリプトを実行可能 API としてデプロイする
- 使用する関数を含む Apps Script プロジェクトを 開きます。
- 右上の [デプロイ] > [新しいデプロイ] をクリックします。
- 表示されたダイアログで、[デプロイタイプを有効にする]
> [実行可能 API] をクリックします。
- [アクセス権を持つユーザー] プルダウン メニューで、Apps Script API を使用してスクリプトの関数を呼び出すことができるユーザー を選択します。
- [デプロイ] をクリックします。
ステップ 3: 呼び出し元アプリケーションを構成する
呼び出し元アプリケーションは、使用する前に Apps Script API を有効にし、OAuth 認証情報を確立する必要があります。これを行うには、Cloud プロジェクトへのアクセス権が必要です。
- 呼び出し元アプリケーションと スクリプトが使用している Cloud プロジェクトを構成します:
- スクリプト プロジェクトを開き、左側の [概要]
をクリックします。
- [プロジェクトの OAuth スコープ] で、 スクリプトに必要なすべてのスコープを記録します。
呼び出し元アプリケーション コードで、API 呼び出し用のスクリプト OAuth アクセス トークン を生成します。これは API 自体が使用するトークンではなく、スクリプトの実行時に必要なトークンです。Cloud プロジェクトのクライアント ID と記録したスクリプト スコープを使用して作成する必要があります。
Google クライアント ライブラリは、このトークンの作成とアプリケーションの OAuth の処理に役立ちます。通常、スクリプトのスコープを使用して、より高レベルの「認証情報」オブジェクトを作成できます。スコープのリストから認証情報オブジェクトを作成する例については、Apps Script API のクイックスタート をご覧ください。
ステップ 4: scripts.run リクエストを行う
呼び出し元アプリケーションを構成したら、
scripts.run 呼び出しを行うことができます。
- API リクエスト をデプロイ ID、関数名、必要なパラメータを使用して作成します。
scripts.run呼び出しを行い、作成したスクリプト OAuth トークンをヘッダーに含めるか( 基本的なPOSTリクエストを使用する場合)、スクリプト スコープで作成した認証情報オブジェクトを使用します。- スクリプトの実行が完了するまで待ちます。スクリプトの実行時間は最大 6 分かかる場合があるため、アプリケーションでこの時間を考慮する必要があります。
- 完了すると、スクリプト関数が値を返すことがあります。値がサポートされている型の場合、API はその値をアプリケーションに返します。
API 呼び出しの例については、
次のセクションをご覧ください。scripts.run
アクセス トークンを更新するには、scripts.run API リクエストの前に次のスニペットを追加します。
if (credential.getExpiresInSeconds() <= 360) {
credential.refreshToken();
}
API リクエストの例
次の例は、さまざまな言語で Apps Script API 実行リクエストを作成し、Apps Script
関数を呼び出してユーザーのルート ディレクトリ内のフォルダのリストを出力する方法を示しています。実行された関数を含む Apps Script
プロジェクトのデプロイ ID は、ENTER_YOUR_DEPLOYMENT_ID_HERE
で示されている場所に指定する必要があります。この例では、Google API クライアント ライブラリを使用しています。
ターゲット スクリプト
このスクリプトの関数は Drive API を使用します。
スクリプトをホストする プロジェクトで Drive API を有効にする必要があります。
また、呼び出し元アプリケーションは、次の Drive スコープを含む 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 プロジェクトである必要があります。 Apps Script プロジェクト用に作成された デフォルト プロジェクトでは不十分です。
基本的なパラメータと戻り値の型 。API は、 Apps Script 固有のオブジェクト( たとえば ドキュメント、 Blob、 カレンダー、 ドライブ ファイルなど)を アプリケーションに渡したり、アプリケーションから返したりすることはできません。文字列、配列、オブジェクト、数値、ブール値などの基本的な型のみを渡して返すことができます。
OAuth スコープ 。API は、必要なスコープが 1 つ以上あるスクリプトのみを実行できます。つまり、API を使用して、1 つ以上のサービスの承認を必要としないスクリプトを呼び出すことはできません。
トリガーなし 。API は Apps Script トリガーを作成できません。