تنفيذ الدوال باستخدام Google Apps Script API

توفّر واجهة برمجة التطبيقات "برمجة التطبيقات" طريقة scripts.run تتيح تنفيذ دالة محدّدة في "برمجة تطبيقات Google" عن بُعد. يمكنك استخدام هذه الطريقة في تطبيق لإجراء المكالمات لتشغيل دالة في أحد مشاريع النصوص البرمجية عن بُعد وتلقّي ردّ.

المتطلبات

قبل أن يتمكّن تطبيق اتصال من استخدام طريقة scripts.run، عليك استيفاء الشروط التالية:

  • انشر مشروع البرنامج النصي كملف API قابل للتنفيذ. يمكنك نشر المشاريع وإلغاء نشرها وإعادة نشرها حسب الحاجة.

  • قدِّم رمز OAuth مميزًا بنطاق مناسب للتنفيذ. يجب أن يغطي رمز OAuth المميز هذا جميع النطاقات التي يستخدمها النص البرمجي، وليس فقط النطاقات التي تستخدمها الدالة التي تم استدعاؤها. يمكنك الاطّلاع على القائمة الكاملة بنطاقات الأذونات في مرجع الطريقة.

  • تأكَّد من أنّ النص البرمجي وبرنامج OAuth2 الذي يستدعي النص البرمجي يشتركان في مشروع Google Cloud نفسه. يجب أن يكون مشروع Cloud مشروعًا عاديًا على السحابة الإلكترونية، فالمشاريع التلقائية التي يتم إنشاؤها لمشاريع "برمجة التطبيقات" غير كافية. يمكنك استخدام مشروع على السحابة الإلكترونية جديد أو مشروع حالي.

  • فعِّل Google Apps Script API في مشروع Cloud.

طريقة scripts.run

تتطلّب طريقة scripts.run المعلومات التالية:

يمكنك ضبط النص البرمجي اختياريًا ليتم تنفيذه في وضع التطوير. يتم تنفيذ هذا الوضع باستخدام أحدث نسخة محفوظة من مشروع النص البرمجي، وليس أحدث نسخة تم نشرها. لإجراء ذلك، اضبط قيمة devMode المنطقية في نص الطلب على true. لا يمكن تنفيذ النص البرمجي في وضع التطوير إلا من خلال مالكه.

التعامل مع أنواع بيانات المَعلمات

يتضمّن استخدام طريقة scripts.run في Apps Script API عادةً إرسال بيانات إلى Apps Script كمعلَمات للدالة واسترجاع البيانات كقيم معروضة للدالة. يمكن لواجهة برمجة التطبيقات تلقّي قيم وعرضها فقط باستخدام أنواع أساسية، مثل السلاسل النصية والمصفوفات والكائنات والأرقام والقيم المنطقية. لا يمكن لواجهة برمجة التطبيقات تمرير عناصر "برمجة التطبيقات" الأكثر تعقيدًا، مثل المستند أو ورقة، إلى مشروع النص البرمجي أو منه.

عندما يكون تطبيق الاتصال مكتوبًا بلغة ذات كتابة قوية، مثل Java، يتم تمرير المَعلمات كقائمة أو مصفوفة من الكائنات العامة التي تتوافق مع هذه الأنواع الأساسية. في كثير من الحالات، يمكنك تطبيق عمليات تحويل الأنواع تلقائيًا. على سبيل المثال، يمكن منح دالة تأخذ معلَمة رقمية كائن Java Double أو Integer أو Long كمعلَمة بدون معالجة إضافية.

عندما تعرض واجهة برمجة التطبيقات استجابة الدالة، غالبًا ما تحتاج إلى تحويل القيمة المعروضة إلى النوع الصحيح قبل أن تتمكّن من استخدامها. في ما يلي بعض الأمثلة المستندة إلى Java:

  • تصل الأرقام التي تعرضها واجهة برمجة التطبيقات لتطبيق Java على شكل عناصر java.math.BigDecimal وقد تحتاج إلى تحويلها إلى أنواع Double أو int.
  • إذا عرضت دالة برمجة تطبيقات مصفوفة من السلاسل، سيحوّل تطبيق Java الردّ إلى عنصر List<String>:

    List<String> mylist = (List<String>)(op.getResponse().get("result"));
    
  • إذا أردت عرض مصفوفة من Bytes، عليك ترميز المصفوفة كسلسلة base64 ضمن دالة برمجة تطبيقات وعرض السلسلة بدلاً من ذلك:

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

توضّح عيّنات الرموز البرمجية أدناه طرقًا لتفسير استجابة واجهة برمجة التطبيقات.

الإجراء العام

لاستخدام Apps Script API لتنفيذ دوال Apps Script، اتّبِع الخطوات التالية:

الخطوة 1: إعداد مشروع على السحابة الإلكترونية المشترك

يجب أن يشترك النص البرمجي والتطبيق الذي يستدعيه في مشروع على السحابة الإلكترونية نفسه. يمكن أن يكون هذا المشروع مشروعًا حاليًا أو مشروعًا جديدًا تم إنشاؤه لهذا الغرض. بعد إنشاء مشروع على السحابة الإلكترونية، عليك تبديل مشروع النص البرمجي لاستخدامه.

الخطوة 2: نشر النص البرمجي كملف API قابل للتنفيذ

  1. افتح مشروع برمجة تطبيقات الذي يتضمّن الوظائف التي تريد استخدامها.
  2. في أعلى يسار الصفحة، انقر على نشر > عملية نشر جديدة.
  3. في مربّع الحوار الذي يظهر، انقر على تفعيل أنواع النشر > ملف تنفيذي لواجهة برمجة التطبيقات.
  4. في القائمة المنسدلة "مَن لديه إذن الوصول"، اختَر المستخدمين المسموح لهم باستدعاء وظائف البرنامج النصي باستخدام واجهة برمجة التطبيقات Apps Script API.
  5. انقر على نشر.

الخطوة 3: ضبط تطبيق الاتصال

يجب أن يفعّل التطبيق الذي يتم استدعاؤه واجهة برمجة التطبيقات Apps Script ويُنشئ بيانات اعتماد OAuth قبل الاستخدام. يجب أن يكون لديك إذن الوصول إلى مشروع على السحابة الإلكترونية لتنفيذ هذا الإجراء.

  1. اضبط مشروع Cloud الذي يستخدمه تطبيق الاتصال والنص البرمجي باتّباع الخطوات التالية:
    1. فعِّل واجهة برمجة التطبيقات "برمجة التطبيقات" في مشروع على السحابة الإلكترونية.
    2. ضبط شاشة طلب الموافقة المتعلّقة ببروتوكول OAuth
    3. إنشاء بيانات اعتماد OAuth
  2. افتح مشروع نص الفيديو وانقر على نظرة عامة على يمين الصفحة.
  3. ضمن نطاقات OAuth للمشروع، سجِّل جميع النطاقات التي يتطلّبها النص البرمجي.
  4. في الرمز البرمجي لتطبيق الاتصال، أنشئ رمز دخول OAuth للبرنامج النصي لطلب البيانات من واجهة برمجة التطبيقات. هذا ليس رمزًا مميزًا تستخدمه واجهة برمجة التطبيقات نفسها، بل هو رمز يتطلبه النص البرمجي عند تنفيذه. يجب إنشاؤه باستخدام معرّف العميل الخاص بمشروع على السحابة الإلكترونية ونطاقات النص البرمجي التي سجّلتها.

    يمكن أن تساعد مكتبات برامج Google في إنشاء هذا الرمز المميز والتعامل مع OAuth للتطبيق، ما يتيح لك عادةً إنشاء عنصر "بيانات اعتماد" أعلى مستوى باستخدام نطاقات البرنامج النصي. راجِع البدايات السريعة لواجهة برمجة التطبيقات "برمجة التطبيقات" للاطّلاع على أمثلة حول إنشاء عنصر بيانات اعتماد من قائمة بالنطاقات.

الخطوة 4: تقديم طلب scripts.run

بعد إعداد تطبيق الاتصال، يمكنك إجراء مكالمات scripts.run:

  1. أنشئ طلب بيانات من واجهة برمجة التطبيقات باستخدام رقم تعريف النص البرمجي واسم الدالة وأي مَعلمات مطلوبة.
  2. أجرِ طلب scripts.run وأدرِج رمز OAuth المميز الخاص بالبرنامج النصي الذي أنشأته في العنوان (في حال استخدام طلب POST أساسي) أو استخدِم عنصر بيانات اعتماد أنشأته باستخدام نطاقات البرنامج النصي.
  3. اسمح للنص البرمجي بإنهاء التنفيذ. يمكن أن تستغرق النصوص البرمجية ما يصل إلى ست دقائق من وقت التنفيذ، لذا يجب أن يسمح تطبيقك بذلك.
  4. عند الانتهاء، قد تعرض دالة النص البرمجي قيمة، والتي تقدّمها واجهة برمجة التطبيقات إلى التطبيق إذا كانت القيمة من النوع المتوافق.

يمكنك العثور على أمثلة على طلبات البيانات من واجهة برمجة التطبيقات scripts.run في القسم التالي.

لإعادة تحميل رمز الدخول، أضِف المقتطف التالي قبل طلب البيانات من واجهة برمجة التطبيقات scripts.run:

if (credential.getExpiresInSeconds() <= 360) {
  credential.refreshToken();
}

أمثلة على طلبات البيانات من واجهة برمجة التطبيقات

توضّح الأمثلة التالية كيفية إرسال طلب تنفيذ إلى واجهة برمجة التطبيقات Apps Script API بلغات مختلفة، وذلك من خلال استدعاء دالة Apps Script لطباعة قائمة بالمجلدات في الدليل الجذر للمستخدم. يجب تحديد معرّف نص برمجي لمشروع برمجة تطبيقات الذي يحتوي على الدالة التي تم تنفيذها في المكان المحدّد بعلامة ENTER_YOUR_SCRIPT_ID_HERE. تعتمد الأمثلة على مكتبات Google API Client.

البرنامج النصي المستهدَف

تستخدم الدالة في هذا النص البرمجي واجهة برمجة تطبيقات Drive.

يجب تفعيل 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;
}

جافا


/**
 * 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 للقيود التالية:

  1. مشروع على السحابة الإلكترونية مشترك يجب أن يتشارك النص البرمجي الذي يتم استدعاؤه والتطبيق الذي يستدعيه في مشروع على السحابة الإلكترونية. يجب أن يكون مشروع Cloud مشروعًا على السحابة الإلكترونية عاديًا، فالمشاريع التلقائية التي يتم إنشاؤها لمشاريع &quot;برمجة التطبيقات&quot; غير كافية.

  2. أنواع المَعلمات الأساسية وأنواع الإرجاع لا يمكن لواجهة برمجة التطبيقات تمرير أو عرض كائنات خاصة بـ &quot;برمجة تطبيقات Google&quot; (مثل المستندات أو الكائنات الثنائية الكبيرة أو التقاويم أو ملفات Drive وما إلى ذلك) إلى التطبيق. يمكن تمرير الأنواع الأساسية فقط، مثل السلاسل والمصفوفات والعناصر والأرقام والقيم المنطقية، وعرضها.

  3. نطاقات OAuth لا يمكن لواجهة برمجة التطبيقات تنفيذ النصوص البرمجية التي لا تتضمّن نطاقًا واحدًا على الأقل من النطاقات المطلوبة. وهذا يعني أنّه لا يمكنك استخدام واجهة برمجة التطبيقات لاستدعاء نص برمجي لا يتطلّب تفويض خدمة واحدة أو أكثر.

  4. ما مِن إجراءات تفعّل القاعدة: لا يمكن لواجهة برمجة التطبيقات إنشاء مشغِّلات في "برمجة التطبيقات".