הפעלת פונקציות באמצעות Apps Script API

Google Apps Script API מספק אמצעי תשלום scripts.run שמפעיל מרחוק פונקציית Apps Script מסוימת. אפשר להשתמש בשיטה הזו באפליקציה של קריאה כדי להריץ פונקציה באחד מהפרויקטים של הסקריפטים שלכם מרחוק ולקבל תשובה.

דרישות

כדי להשתמש באפליקציה לשיחות, צריך לעמוד בדרישות הבאות scripts.run . חובה:

  • פורסים את פרויקט הסקריפט כקובץ הפעלה של API. אפשר לפרוס, לבטל פריסה ולפרוס מחדש פרויקטים לפי הצורך.

  • צריך לספק אסימון OAuth בהיקף תקין להפעלה. אסימון ה-OAuth הזה צריך לכסות את כל היקפי ההרשאות ששימשו את הסקריפט, לא רק את אלה ששימשה את הפונקציה שנקראה. לצפייה הרשימה המלאה של היקפי ההרשאות בהפניה ל-method.

  • ודא שהסקריפט והאפליקציה ששולחת קריאה באמצעות OAuth2 לקוח משותף בפרויקט Google Cloud משותף. הפרויקט ב-Cloud חייב להיות פרויקט סטנדרטי ב-Cloud. פרויקטי ברירת מחדל שנוצרו לפרויקטים של Apps Script אינם מספיקים. אפשר להשתמש בפרויקט חדש רגיל ב-Cloud או בפרויקט קיים.

  • הפעלת Google Apps Script API בפרויקט בענן.

השיטה scripts.run

scripts.run צריך להזין פרטים מזהים עיקריים כדי להריץ את הפקודה:

אפשר להגדיר שהסקריפט יופעל במצב פיתוח. המצב הזה פועל באמצעות הגרסה האחרונה ששמורה של פרויקט הסקריפט ולא בגרסה האחרונה שנפרסה. כדי לעשות את זה, ערך בוליאני של devMode במטבע גוף הבקשה אל true. רק הבעלים של הסקריפט יכול להפעיל אותו במצב פיתוח.

טיפול בסוגי נתונים של פרמטרים

שימוש ב-Apps Script API אמצעי תשלום scripts.run בדרך כלל כרוך בשליחת נתונים ל-Apps Script כפרמטרים של פונקציה והחזרת נתונים כפונקציה, מחזירה ערכים. ה-API יכול רק לקבל ולהחזיר מהסוגים הבסיסיים: מחרוזות, מערכים, אובייקטים, מספרים ובוליאני. האלה דומים לסוגים הבסיסיים ב-JavaScript. התוצאות מורכבות יותר אובייקטים של Apps Script, כמו Document או גיליון או מפרויקט הסקריפט על ידי ה-API.

כשאפליקציית הקריאה נכתבת בשפה בעלת טיפוס חזק, כמו ב-Java, היא מעבירה פרמטרים כרשימה או כמערך של אובייקטים גנריים שתואמים לסוגים הבסיסיים האלו. במקרים רבים אפשר להשתמש מקלידים המרות באופן אוטומטי. לדוגמה, פונקציה שלוקחת מספר אפשר לתת לפרמטר את האובייקט של Java Double או Integer או Long בתור אובייקט ללא טיפול נוסף.

כשה-API מחזיר את התגובה של הפונקציה, לעיתים קרובות צריך להפעיל Cast של מוחזר ערך לסוג הנכון לפני שאפשר להשתמש בו. הנה כמה דוגמאות דוגמאות מבוססות Java:

  • המספרים שהוחזרו על ידי ה-API לאפליקציית Java מגיעים בתור java.math.BigDecimal אובייקטים, ויכול להיות שצריך להמיר אותו ל- Doubles או int סוגים לפי הצורך.
  • אם הפונקציה Apps Script מחזירה מערך של מחרוזות, אפליקציית Java הפונקציה ממירה את התשובה לאובייקט List<String>:

    List<String> mylist = (List<String>)(op.getResponse().get("result"));
    
  • אם ברצונך להחזיר מערך של Bytes, כנראה שיהיה לך נוח יותר לקודד את המערך כמחרוזת base64 בתוך פונקציית Apps Script, הפונקציה מחזירה את המחרוזת הזו במקום זאת:

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

דוגמאות הקוד לדוגמה שבהמשך ממחישות דרכים פירוש תגובת ה-API.

נוהל כללי

בהמשך מתואר התהליך הכללי לשימוש ב-Apps Script API להפעלת פונקציות של Apps Script:

שלב 1: הגדרת הפרויקט המשותף ב-Cloud

הסקריפט ואפליקציית הקריאה צריכים לחלוק את אותו הדבר פרויקט בענן. הפרויקט הזה ב-Cloud יכול להיות פרויקט קיים או נוצר פרויקט חדש למטרה הזו. אחרי שיוצרים פרויקט ב-Cloud, חייבים לשנות את פרויקט הסקריפט כדי להשתמש בו.

שלב 2: פריסת הסקריפט כקובץ הפעלה של API

  1. פותחים את פרויקט Apps Script עם הפונקציות שבהן רוצים להשתמש.
  2. בצד שמאל למעלה, לוחצים על פריסה &gt; פריסה חדשה.
  3. בתיבת הדו-שיח שנפתחת, לוחצים על הסמל להפעלת סוגי פריסה &gt; API להפעלה.
  4. בקטע 'למי יש גישה' בתפריט הנפתח, בוחרים את המשתמשים מורשים לקרוא לפונקציות של הסקריפט באמצעות Apps Script API.
  5. לוחצים על Deploy (פריסה).

שלב 3: מגדירים את אפליקציית הקריאה

אפליקציית הקריאה צריכה להפעיל את Apps Script API וליצור OAuth חשובים לפני שאפשר להשתמש בו. צריכה להיות לכם גישה לפרויקט Cloud לשם כך.

  1. מגדירים את הפרויקט ב-Cloud שבו משתמשים האפליקציה והסקריפט. כדי לעשות זאת:
    1. מפעילים את Apps Script API בפרויקט ב-Cloud.
    2. מגדירים את מסך ההסכמה של OAuth.
    3. יצירת פרטי כניסה של OAuth.
  2. פותחים את פרויקט הסקריפט ולוחצים על סקירה כללית בצד ימין.
  3. בקטע היקפי ההרשאות של Project OAuth, מתעדים את כל היקפי ההרשאות של נדרש סקריפט.
  4. בקוד של האפליקציה לקריאה, צריך ליצור סקריפט גישה ל-OAuth לקריאה ל-API. זהו לא אסימון שה-API עצמו משתמש בו, אלא אסימון נדרש בעת הביצוע של הסקריפט. צריך ליצור אותו באמצעות מזהה הלקוח של פרויקט ב-Cloud והיקפי הסקריפט שתיעדתם.

    ספריות הלקוח של Google יכולות מאוד יעזרו לבנות את האסימון הזה ולטפל ב-OAuth עבור האפליקציה, בדרך כלל מאפשרות ליצור במקום זאת פרטי כניסה ברמה גבוהה יותר אובייקט באמצעות היקפי הסקריפט. לצפייה דוגמאות למדריך למתחילים של Apps Script API של בניית אובייקט של פרטי כניסה מתוך רשימה של היקפים.

שלב 4: מגישים בקשה של script.run

אחרי שאפליקציית השיחות הוגדרה, אפשר לבצע את הפעולות הבאות: scripts.run שיחות. כל API כולל את השלבים הבאים:

  1. יצירה של בקשת API באמצעות מזהה הסקריפט, שם הפונקציה וכל דרישה אחרת .
  2. מגדירים את scripts.run וכוללים את הסקריפט OAuth שיצרתם הכותרת (אם משתמשים בבקשת POST בסיסית) או באובייקט פרטי כניסה. שיצרתם באמצעות היקפים של הסקריפטים.
  3. צריך לאפשר לסקריפט לסיים את ביצוע ההפעלה. סקריפטים יכולים לפעול עד שש דקות של זמן ביצוע, כך שהאפליקציה שלכם צריכה לאפשר זאת.
  4. בסיום, פונקציית הסקריפט עשויה להחזיר ערך, וה-API מועבר חזרה לאפליקציה אם הערך הוא מסוג נתמך.

דוגמאות לקריאות ל-API של script.run שלמטה.

דוגמאות לבקשות API

הדוגמאות הבאות מראות איך לשלוח בקשת הפעלה ל-Apps Script API מגוון שפות, קריאה לפונקציה של Apps Script כדי להדפיס רשימה בתיקיות שבתיקיית השורש של המשתמש. מזהה הסקריפט של פרויקט Apps Script שיש לציין בו את הפונקציה המופעלת ENTER_YOUR_SCRIPT_ID_HERE הדוגמאות מבוססות על ספריות לקוח של Google API בשפות שונות.

סקריפט יעד

הפונקציה בסקריפט הזה משתמשת ב-Drive API.

עליך להפעיל את Drive API של הפרויקט שמארח את הסקריפט.

בנוסף, אפליקציות לשיחות חייבות לשלוח פרטי כניסה של OAuth שכוללים את היקף ההרשאות הבא ב-Drive:

  • 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 חייב להיות standard Cloud project; פרויקטי ברירת מחדל שנוצרו לפרויקטים של Apps Script אינם מספיקים. פרויקט רגיל ב-Cloud יכול להיות פרויקט חדש או קיים.

  2. פרמטרים בסיסיים וסוגי החזרה. ה-API לא יכול להעביר או להחזיר אובייקטים ספציפיים ל-Apps Script (כמו מסמכים, Blobs, יומנים, קבצים ב-Drive וכו') אל תרגום מכונה. רק סוגים בסיסיים, כמו מחרוזות, מערכים, אובייקטים, מספרים ו אפשר להעביר ולהחזיר ערך בוליאני.

  3. היקפי הרשאות OAuth ה-API יכול להפעיל רק סקריפטים שמכילים לפחות היקף נדרש אחד. כלומר, לא ניתן להשתמש ב-API כדי לקרוא לסקריפט שאינם מחייבים הרשאה של שירות אחד או יותר.

  4. אין טריגרים.ל-API אין אפשרות ליצור Apps Script הטריגרים.