Eseguire Functions con l'API Apps Script

L'API Google Apps Script fornisce un metodo scripts.run che esegue una funzione Apps Script specificata da remoto. Puoi utilizzare questo metodo in un'applicazione di chiamata per eseguire una funzione in uno dei tuoi progetti di script da remoto e ricevere una risposta.

Requisiti

Prima che un'applicazione di chiamata possa utilizzare il metodo scripts.run, devi soddisfare i seguenti requisiti. Devi:

  • Esegui il deployment del progetto di script come eseguibile dell'API. Puoi eseguire il deployment dei progetti, annullarne il deployment ed eseguirne nuovamente il deployment in base alle esigenze.

  • Fornisci un token OAuth con ambito corretto per l'esecuzione. Il token OAuth deve includere tutti gli ambiti utilizzati dallo script, non solo quelli utilizzati dalla funzione chiamata. Consulta l'elenco completo degli ambiti di autorizzazione nel riferimento del metodo.

  • Assicurati che lo script e il client OAuth2 dell'applicazione chiamante condividano un progetto Google Cloud comune. Il progetto cloud deve essere un progetto Cloud standard; i progetti predefiniti creati per i progetti Apps Script sono insufficienti. Puoi utilizzare un nuovo progetto Cloud standard o uno esistente.

  • Abilitare l'API Google Apps Script nel progetto Cloud.

Il metodo scripts.run

Il metodo scripts.run richiede informazioni di identificazione chiave per eseguire:

Facoltativamente, puoi configurare lo script per l'esecuzione in modalità di sviluppo. Questa modalità viene eseguita con la versione salvata più recente del progetto di script anziché con la versione di cui è stato eseguito il deployment più di recente. Per farlo, imposta il valore booleano devMode nel corpo della richiesta su true. Solo il proprietario dello script può eseguirlo in modalità di sviluppo.

Gestione dei tipi di dati relativi ai parametri

L'utilizzo del metodo scripts.run dell'API Apps Script generalmente richiede l'invio di dati ad Apps Script come parametri delle funzioni e il recupero dei dati come valori di ritorno delle funzioni. L'API può accettare e restituire valori solo per tipi di base: stringhe, array, oggetti, numeri e valori booleani. Sono simili ai tipi di base in JavaScript. Gli oggetti Apps Script più complessi come Documento o Foglio non possono essere trasferiti dall'API al progetto di script o dal progetto di script.

Quando l'applicazione di chiamata viene scritta in un linguaggio di tipo forte, come Java, trasmette i parametri come un elenco o un array di oggetti generici corrispondenti a questi tipi di base. In molti casi, puoi applicare automaticamente le conversioni di tipo semplice. Ad esempio, a una funzione che accetta un parametro numerico può essere assegnato un oggetto Double, Integer o Long Java come parametro senza alcuna gestione aggiuntiva.

Quando l'API restituisce la risposta della funzione, spesso devi trasmettere il valore restituito al tipo corretto prima di poter essere utilizzato. Ecco alcuni esempi basati su Java:

  • I numeri restituiti dall'API a un'applicazione Java arrivano come oggetti java.math.BigDecimal e, se necessario, potrebbero dover essere convertiti in tipi Doubles o int.
  • Se la funzione Apps Script restituisce una matrice di stringhe, un'applicazione Java trasmette la risposta in un oggetto List<String>:

    List<String> mylist = (List<String>)(op.getResponse().get("result"));
    
  • Se vuoi restituire un array di Bytes, potresti trovarlo conveniente per codificare l'array come stringa base64 all'interno della funzione Apps Script e restituire invece quella stringa:

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

Gli esempi di codice di esempio riportati di seguito illustrano come interpretare la risposta dell'API.

Procedura generale

Di seguito è descritta la procedura generale per l'utilizzo dell'API Apps Script per eseguire le funzioni di Apps Script:

Passaggio 1: configura il progetto Cloud comune

Sia lo script che l'applicazione chiamante devono condividere lo stesso progetto Cloud. Questo progetto Cloud può essere un progetto esistente o un nuovo progetto creato per questo scopo. Dopo aver creato un progetto Cloud, devi cambiare il progetto di script per utilizzarlo.

Passaggio 2: esegui il deployment dello script come eseguibile dell'API

  1. Apri il progetto Apps Script con le funzioni che vuoi utilizzare.
  2. In alto a destra, fai clic su Esegui il deployment > Nuovo deployment.
  3. Nella finestra di dialogo visualizzata, fai clic su Abilita tipi di deployment > API eseguibile.
  4. Nel menu a discesa "Chi ha accesso", seleziona gli utenti autorizzati a chiamare le funzioni dello script utilizzando l'API Apps Script.
  5. Fai clic su Esegui il deployment.

Passaggio 3: configura l'applicazione di chiamata

L'applicazione chiamante deve attivare l'API Apps Script e stabilire le regole OAuth prima di poter essere utilizzata. Per farlo, devi avere accesso al progetto Cloud.

  1. Configura il progetto cloud utilizzato dall'applicazione e dallo script per le chiamate. Per farlo, segui questi passaggi:
    1. Abilitare l'API Apps Script nel progetto Cloud.
    2. Configurare la schermata per il consenso OAuth.
    3. Crea le credenziali OAuth.
  2. Apri il progetto di script e fai clic su Panoramica a sinistra.
  3. In Ambiti OAuth del progetto, registra tutti gli ambiti richiesti dallo script.
  4. Nel codice dell'applicazione chiamante, genera uno script di accesso OAuth per la chiamata API. Questo non è un token utilizzato dall'API, ma uno richiesto dallo script durante l'esecuzione. Deve essere creato utilizzando l'ID client del progetto Cloud e gli ambiti degli script registrati.

    Le librerie client Google possono essere di grande aiuto per creare questo token e gestire OAuth per l'applicazione, consentendo di solito di creare un oggetto "credentials" di livello superiore utilizzando gli ambiti degli script. Consulta le guide rapide sull'API Apps Script per esempi sulla creazione di un oggetto credenziali da un elenco di ambiti.

Passaggio 4: invia una richiesta script.run

Dopo aver configurato l'applicazione di chiamata, puoi effettuare chiamate scripts.run. Ogni chiamata API prevede i seguenti passaggi:

  1. Crea una richiesta API utilizzando l'ID script, il nome della funzione e gli eventuali parametri obbligatori.
  2. Effettua la chiamata scripts.run e includi il token OAuth dello script creato nell'intestazione (se utilizzi una richiesta POST di base) oppure utilizza un oggetto credenziali creato con gli ambiti dello script.
  3. Consenti il completamento dell'esecuzione dello script. Gli script possono richiedere fino a sei minuti di tempo di esecuzione, quindi la tua applicazione dovrebbe consentirlo.
  4. Al termine, la funzione script può restituire un valore, che l'API restituisce all'applicazione se il valore è di tipo supportato.

Di seguito sono riportati alcuni esempi di chiamate API script.run.

Esempi di richieste API

Gli esempi seguenti mostrano come eseguire una richiesta di esecuzione dell'API Apps Script in vari linguaggi, chiamando una funzione Apps Script per stampare un elenco di cartelle nella directory principale dell'utente. L'ID script del progetto Apps Script contenente la funzione eseguita deve essere specificato dove indicato con ENTER_YOUR_SCRIPT_ID_HERE. Gli esempi si basano sulle librerie client dell'API di Google per le rispettive lingue.

Script di destinazione

La funzione in questo script utilizza l'API Drive.

Devi abilitare l'API Drive nel progetto che ospita lo script.

Inoltre, le applicazioni di chiamata devono inviare credenziali OAuth che includano il seguente ambito di Drive:

  • https://www.googleapis.com/auth/drive

Le applicazioni di esempio qui utilizzano le librerie client di Google per creare oggetti di credenziali per OAuth utilizzando questo ambito.

/**
 * 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()

Limitazioni

L'API Apps Script ha diverse limitazioni:

  1. Un progetto cloud comune. Lo script che viene chiamato e l'applicazione chiamante devono condividere un progetto Cloud. Il progetto cloud deve essere un progetto Cloud standard; i progetti predefiniti creati per i progetti Apps Script sono insufficienti. Il progetto Cloud standard può essere un nuovo progetto o uno esistente.

  2. Tipi di parametri di base e di ritorno. L'API non può passare o restituire all'applicazione oggetti specifici di Apps Script (come Documenti, Blob, Calendari, File di Drive e così via). Solo i tipi di base come stringhe, matrici, oggetti, numeri e booleani possono essere passati e restituiti.

  3. Ambiti OAuth. L'API può eseguire solo script con almeno un ambito richiesto. Ciò significa che non puoi utilizzare l'API per chiamare uno script che non richiede l'autorizzazione di uno o più servizi.

  4. Nessun trigger.L'API non può creare attivatori di Apps Script.