Exécuter des fonctions avec l'API Apps Script

L'API Google Apps Script fournit une méthode scripts.run qui exécute à distance une fonction Apps Script spécifiée. Vous pouvez utiliser cette méthode dans une application appelante pour exécuter une fonction dans l'un de vos projets de script à distance et recevoir une réponse.

Conditions requises

Vous devez remplir les conditions suivantes avant qu'une application d'appel puisse utiliser la méthode scripts.run. Vous devez :

  • Déployez le projet de script en tant qu'exécutable d'API. Vous pouvez déployer, annuler le déploiement et redéployer des projets selon vos besoins.

  • Fournissez un jeton OAuth correctement défini pour l'exécution. Ce jeton OAuth doit couvrir toutes les habilitations utilisées par le script, et pas seulement celles utilisées par la fonction appelée. Consultez la liste complète des niveaux d'autorisation dans la documentation de référence sur la méthode.

  • Assurez-vous que le script et le client OAuth2 de l'application appelante partagent un projet Google Cloud commun. Le projet Cloud doit être un projet Cloud standard. Les projets par défaut créés pour les projets Apps Script ne suffisent pas. Vous pouvez utiliser un nouveau projet Cloud standard ou un projet existant.

  • Activez l'API Google Apps Script dans le projet Cloud.

La méthode scripts.run

La méthode scripts.run nécessite des informations clés pour s'exécuter :

Vous pouvez éventuellement configurer votre script pour qu'il s'exécute en mode développement. Ce mode s'exécute avec la version enregistrée la plus récente du projet de script, et non avec la version déployée la plus récente. Pour ce faire, définissez le booléen devMode dans le corps de la requête sur true. Seul le propriétaire du script peut l'exécuter en mode Développement.

Gérer les types de données de paramètres

L'utilisation de la méthode scripts.run de l'API Apps Script implique généralement l'envoi de données à Apps Script en tant que paramètres de fonction et la récupération de données en tant que valeurs renvoyées par la fonction. L'API ne peut accepter et renvoyer que des valeurs de types de base : chaînes, tableaux, objets, nombres et valeurs booléennes. Ils sont semblables aux types de base en JavaScript. Les objets Apps Script plus complexes, tels que Document ou Sheet, ne peuvent pas être transmis au projet de script ni à partir de celui-ci par l'API.

Lorsque votre application d'appel est écrite dans un langage fortement typé tel que Java, elle transmet les paramètres sous forme de liste ou de tableau d'objets génériques correspondant à ces types de base. Dans de nombreux cas, vous pouvez appliquer automatiquement des conversions de type simples. Par exemple, une fonction qui accepte un paramètre numérique peut recevoir un objet Java Double, Integer ou Long en tant que paramètre sans traitement supplémentaire.

Lorsque l'API renvoie la réponse de la fonction, vous devez souvent caster la valeur renvoyée vers le type approprié avant de pouvoir l'utiliser. Voici quelques exemples basés sur Java :

  • Les nombres renvoyés par l'API à une application Java arrivent sous forme d'objets java.math.BigDecimal et peuvent avoir besoin d'être convertis en types Doubles ou int selon les besoins.
  • Si la fonction Apps Script renvoie un tableau de chaînes, une application Java convertit la réponse en objet List<String> :

    List<String> mylist = (List<String>)(op.getResponse().get("result"));
    
  • Si vous souhaitez renvoyer un tableau de Bytes, vous pouvez encoder le tableau sous forme de chaîne base64 dans la fonction Apps Script et renvoyer cette chaîne à la place :

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

Les exemples de code ci-dessous illustrent différentes façons d'interpréter la réponse de l'API.

Procédure générale

La procédure générale d'utilisation de l'API Apps Script pour exécuter des fonctions Apps Script est décrite ci-dessous :

Étape 1 : Configurez le projet Cloud commun

Votre script et l'application appelante doivent partager le même projet Cloud. Il peut s'agir d'un projet existant ou d'un nouveau projet créé à cet effet. Une fois que vous avez un projet Cloud, vous devez configurer votre projet de script pour l'utiliser.

Étape 2 : Déployer le script en tant qu'exécutable d'API

  1. Ouvrez le projet Apps Script contenant les fonctions que vous souhaitez utiliser.
  2. En haut à droite, cliquez sur Déployer > Nouveau déploiement.
  3. Dans la boîte de dialogue qui s'ouvre, cliquez sur Activer les types de déploiement  > Exécutable de l'API.
  4. Dans le menu déroulant "Qui a accès", sélectionnez les utilisateurs autorisés à appeler les fonctions du script à l'aide de l'API Apps Script.
  5. Cliquez sur Déployer.

Étape 3 : Configurer l'application d'appel

L'application appelante doit activer l'API Apps Script et établir des identifiants OAuth avant de pouvoir être utilisée. Pour ce faire, vous devez avoir accès au projet Cloud.

  1. Configurez le projet Cloud utilisé par votre application et votre script d'appel. Pour cela, procédez comme suit :
    1. Activez l'API Apps Script dans le projet Cloud.
    2. Configurez l'écran de consentement OAuth.
    3. Créer des identifiants OAuth
  2. Ouvrez le projet de script, puis cliquez sur Vue d'ensemble à gauche.
  3. Sous Champs d'application OAuth du projet, enregistrez tous les champs d'application requis par le script.
  4. Dans le code de l'application appelante, générez un jeton d'accès OAuth de script pour l'appel d'API. Il ne s'agit pas d'un jeton utilisé par l'API elle-même, mais plutôt d'un jeton requis par le script lors de son exécution. Il doit être créé à l'aide de l'ID client du projet Cloud et des portées de script que vous avez enregistrées.

    Les bibliothèques clientes Google peuvent vous aider considérablement à créer ce jeton et à gérer OAuth pour l'application. Elles vous permettent généralement de créer un objet "credentials" de niveau supérieur à l'aide des champs d'application du script. Consultez les guides de démarrage rapide de l'API Apps Script pour obtenir des exemples de création d'un objet d'identifiants à partir d'une liste de champs d'application.

Étape 4 : Effectuez la requête script.run

Une fois l'application d'appel configurée, vous pouvez passer des appels scripts.run. Chaque appel d'API se compose des étapes suivantes :

  1. Créez une requête d'API à l'aide de l'ID de script, du nom de la fonction et de tous les paramètres requis.
  2. Effectuez l'appel scripts.run et incluez le jeton OAuth du script que vous avez créé dans l'en-tête (si vous utilisez une requête POST de base) ou utilisez un objet d'identifiants que vous avez créé avec les niveaux d'accès du script.
  3. Laissez le script s'exécuter jusqu'à la fin. Les scripts peuvent s'exécuter pendant six minutes maximum. Votre application doit donc en tenir compte.
  4. Une fois l'exécution terminée, la fonction de script peut renvoyer une valeur que l'API transmet à l'application si la valeur est d'un type compatible.

Vous trouverez des exemples d'appels d'API script.run ci-dessous.

Exemples de requêtes API

Les exemples suivants montrent comment effectuer une requête d'exécution de l'API Apps Script dans différentes langues, en appelant une fonction Apps Script pour imprimer une liste de dossiers dans le répertoire racine de l'utilisateur. L'ID de script du projet Apps Script contenant la fonction exécutée doit être spécifié à l'emplacement indiqué par ENTER_YOUR_SCRIPT_ID_HERE. Les exemples s'appuient sur les bibliothèques clientes des API Google pour leurs langages respectifs.

Script cible

La fonction de ce script utilise l'API Drive.

Vous devez activer l'API Drive dans le projet hébergeant le script.

De plus, les applications appelantes doivent envoyer des identifiants OAuth incluant le champ d'application Drive suivant :

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

Les exemples d'applications présentés ici utilisent les bibliothèques clientes Google pour créer des objets d'identifiants pour OAuth à l'aide de ce champ d'application.

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

Limites

L'API Apps Script présente plusieurs limites :

  1. Un projet Cloud commun. Le script appelé et l'application appelante doivent partager un projet Cloud. Le projet Cloud doit être un projet Cloud standard. Les projets par défaut créés pour les projets Apps Script ne suffisent pas. Le projet Cloud standard peut être un nouveau projet ou un projet existant.

  2. Types de paramètres et de renvois de base L'API ne peut pas transmettre ni renvoyer d'objets spécifiques à Apps Script (tels que Documents, Blobs, Calendars, Drive Files, etc.) à l'application. Seuls les types de base tels que les chaînes, les tableaux, les objets, les nombres et les valeurs booléennes peuvent être transmis et renvoyés.

  3. Champs d'application OAuth. L'API ne peut exécuter que les scripts qui comportent au moins un champ d'application requis. Cela signifie que vous ne pouvez pas utiliser l'API pour appeler un script qui ne nécessite pas l'autorisation d'un ou de plusieurs services.

  4. Aucun déclencheur : l'API ne peut pas créer de déclencheurs Apps Script.