تنفيذ الوظائف باستخدام واجهة برمجة التطبيقات لبرمجة التطبيقات

تنظيم صفحاتك في مجموعات يمكنك حفظ المحتوى وتصنيفه حسب إعداداتك المفضّلة.

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

المتطلّبات

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

  • انشر مشروع النص البرمجي كواجهة برمجة تطبيقات قابلة للتنفيذ. يمكنك نشر المشاريع ونشرها وإعادة نشرها عند اللزوم.

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

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

  • مكّن Google Apps Script API في مشروع السحاب.

طريقة scripts.run

تتطلب طريقة scripts.run معلومات تعريف رئيسية حتى يتم تشغيل:

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

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

يتضمن استخدام واجهة برمجة تطبيقات برمجة التطبيقات scripts.run عادةً إرسال البيانات إلى برمجة التطبيقات كمعلمات للدالة والحصول على البيانات كقيم إرجاع الدالة. ولا يمكن لواجهة برمجة التطبيقات استخدام القيم الأساسية إلا مع أنواع أساسية: السلاسل والمصفوفات والعناصر والكائنات والقيم المنطقية. وهي تشبه الأنواع الأساسية في جافا سكريبت. لا يمكن نقل كائنات برمجة التطبيقات الأكثر تعقيدًا مثل المستند أو الورقة إلى أو من مشروع النص البرمجي بواسطة واجهة برمجة التطبيقات.

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

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

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

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

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

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

إجراء عام

يوضح ما يلي الإجراء العام لاستخدام واجهة برمجة التطبيقات "لبرمجة التطبيقات" لتنفيذ وظائف "برمجة التطبيقات":

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

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

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

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

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

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

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

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

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

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

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

يمكنك الاطّلاع على أمثلة على طلبات البيانات من واجهة برمجة التطبيقات script.run أدناه.

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

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

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

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

يجب تمكين واجهة برمجة تطبيقات Drive في المشروع الذي يستضيف النص البرمجي.

بالإضافة إلى ذلك، يجب أن ترسل تطبيقات الاتصال بيانات اعتماد 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

from __future__ import print_function

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}."
                          f"{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()

القيود

هناك بعض القيود على واجهة برمجة التطبيقات لبرمجة التطبيقات:

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

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

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

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