Estoy tratando de generar diapositivas de Google a partir de hojas de cálculo de Google; he utilizado el script Sheets sin problemas, pero cuando trato de incluir Google Slides, después de autenticar y obtener los permisos de Oauth, recibo este error para el que no puedo encontrar ninguna referencia; Me aseguré de que las API de Google Slides y Drive API estén habilitadas en la Consola de desarrolladores.

"Error en la solicitud de https://slides.googleapis.com/v1/presentations/. .. código devuelto 403. Respuesta del servidor truncada: {"error": {"código": 403, "mensaje": "La API de Presentaciones de Google no se ha utilizado en el proyecto project-id -... antes o está inhabilitada. . (use la opción muteHttpExceptions para examinar la respuesta completa) (línea 93, archivo "Código") "

La falla del código es la siguiente, la función que falla está copiada de Cómo descargar Diapositivas de Google como imágenes?. La identificación del cliente y el secreto están definidos, omitidos solo por seguridad

// from https://mashe.hawksey.info/2015/10/setting-up-oauth2-access-with-google-apps-script-blogger-api-example/

function getService() {
  // Create a new service with the given name. The name will be used when
  // persisting the authorized token, so ensure it is unique within the
  // scope of the property store.
  return OAuth2.createService('slidesOauth')

      // Set the endpoint URLs, which are the same for all Google services.
      .setAuthorizationBaseUrl('https://accounts.google.com/o/oauth2/auth')
      .setTokenUrl('https://accounts.google.com/o/oauth2/token')


      // Set the client ID and secret, from the Google Developers Console.
      .setClientId(CLIENT_ID)
      .setClientSecret(CLIENT_SECRET)

      // Set the name of the callback function in the script referenced
      // above that should be invoked to complete the OAuth flow.
      .setCallbackFunction('authCallback')

      // Set the property store where authorized tokens should be persisted.
      .setPropertyStore(PropertiesService.getUserProperties())

      // Set the scopes to request (space-separated for Google services).
      // this is blogger read only scope for write access is:
      // https://www.googleapis.com/auth/blogger
      .setScope('https://www.googleapis.com/auth/blogger.readonly')

      // Below are Google-specific OAuth2 parameters.

      // Sets the login hint, which will prevent the account chooser screen
      // from being shown to users logged in with multiple accounts.
      .setParam('login_hint', Session.getActiveUser().getEmail())

      // Requests offline access.
      .setParam('access_type', 'offline')

      // Forces the approval prompt every time. This is useful for testing,
      // but not desirable in a production application.
      .setParam('approval_prompt', 'force');
}

function authCallback(request) {
  var oauthService = getService();
  var isAuthorized = oauthService.handleCallback(request);
  if (isAuthorized) {
    return HtmlService.createHtmlOutput('Success! You can close this tab.');
  } else {
    return HtmlService.createHtmlOutput('Denied. You can close this tab');
  }
}

// from https://stackoverflow.com/questions/31662455/how-to-download-google-slides-as-images/40678925#40678925

function downloadPresentation(id) {
  var slideIds = getSlideIds(id); 

  for (var i = 0, slideId; slideId = slideIds[i]; i++) {
    downloadSlide('Slide ' + (i + 1), id, slideId);
  }
}
function downloadSlide(name, presentationId, slideId) {
  var url = 'https://docs.google.com/presentation/d/' + presentationId +
    '/export/png?id=' + presentationId + '&pageid=' + slideId; 
  var options = {
    headers: {
      Authorization: 'Bearer ' + getService().getAccessToken()
    }
  };
  var response = UrlFetchApp.fetch(url, options); // This is the failing line 93
  var image = response.getAs(MimeType.PNG);
  image.setName(name);
  DriveApp.createFile(image);
}
1
Bruno Guardia 28 dic. 2016 a las 21:01

3 respuestas

La mejor respuesta

EDITAR: Obtuve esto trabajando con este fragmento de código:

var CLIENT_ID = '...';
var CLIENT_SECRET = '...';
var PRESENTATION_ID = '...';

// from https://mashe.hawksey.info/2015/10/setting-up-oauth2-access-with-google-apps-script-blogger-api-example/

function getService() {
  // Create a new service with the given name. The name will be used when
  // persisting the authorized token, so ensure it is unique within the
  // scope of the property store.
  return OAuth2.createService('slidesOauth')

      // Set the endpoint URLs, which are the same for all Google services.
      .setAuthorizationBaseUrl('https://accounts.google.com/o/oauth2/auth')
      .setTokenUrl('https://accounts.google.com/o/oauth2/token')


      // Set the client ID and secret, from the Google Developers Console.
      .setClientId(CLIENT_ID)
      .setClientSecret(CLIENT_SECRET)

      // Set the name of the callback function in the script referenced
      // above that should be invoked to complete the OAuth flow.
      .setCallbackFunction('authCallback')

      // Set the property store where authorized tokens should be persisted.
      .setPropertyStore(PropertiesService.getUserProperties())

      // Set the scopes to request (space-separated for Google services).
      .setScope('https://www.googleapis.com/auth/drive')

      // Below are Google-specific OAuth2 parameters.

      // Sets the login hint, which will prevent the account chooser screen
      // from being shown to users logged in with multiple accounts.
      .setParam('login_hint', Session.getActiveUser().getEmail())

      // Requests offline access.
      .setParam('access_type', 'offline')

      // Forces the approval prompt every time. This is useful for testing,
      // but not desirable in a production application.
      .setParam('approval_prompt', 'force');
}

function authCallback(request) {
  var oauthService = getService();
  var isAuthorized = oauthService.handleCallback(request);
  if (isAuthorized) {
    return HtmlService.createHtmlOutput('Success! You can close this tab.');
  } else {
    return HtmlService.createHtmlOutput('Denied. You can close this tab');
  }
}

function getSlideIds(presentationId) {
  var url = 'https://slides.googleapis.com/v1/presentations/' + presentationId;
  var options = {
    headers: {
      Authorization: 'Bearer ' + getService().getAccessToken()
    }
  };
  var response = UrlFetchApp.fetch(url, options);

  var slideData = JSON.parse(response);
  return slideData.slides.map(function(slide) {
    return slide.objectId;
  });
}


// from http://stackoverflow.com/questions/31662455/how-to-download-google-slides-as-images/40678925#40678925

function downloadPresentation(id) {
  var slideIds = getSlideIds(id); 

  for (var i = 0, slideId; slideId = slideIds[i]; i++) {
    downloadSlide('Slide ' + (i + 1), id, slideId);
  }
}

function downloadSlide(name, presentationId, slideId) {
  var url = 'https://docs.google.com/presentation/d/' + presentationId +
    '/export/png?id=' + presentationId + '&pageid=' + slideId; 
  var options = {
    headers: {
      Authorization: 'Bearer ' + getService().getAccessToken()
    }
  };
  var response = UrlFetchApp.fetch(url, options); // This is the failing line 93
  var image = response.getAs(MimeType.PNG);
  image.setName(name);
  DriveApp.createFile(image);
}

function start() {
  var service = getService();
  var authorizationUrl = service.getAuthorizationUrl();
  Logger.log('Open the following URL and re-run the script: %s',
      authorizationUrl);

  if (service.hasAccess()) {
    downloadPresentation(PRESENTATION_ID);
  }
}

Supongo que la identificación y el secreto del cliente no provienen del proyecto del que crees que provienen. Puede verificar esto visitando la página de credenciales de su proyecto y ver si hay un ID de cliente coincidente enumerado en 'ID de cliente OAuth 2.0'. El proyecto que contiene ese ID de cliente debe tener la API de diapositivas habilitada.

También tenga en cuenta : el punto final / export / png que está utilizando no es una API de Google documentada / compatible, por lo que puede cambiar su nombre o romperse en el futuro. Si está interesado en una API oficial para obtener PNG procesados de diapositivas a través de la API de diapositivas, siga esta problema en el rastreador.


Contenido anterior:

Su código también es ligeramente diferente del fragmento desde el que está copiando. Está utilizando ScriptApp.getOAuthToken() para obtener el valor del encabezado de autorización, pero está llamando a una función getService().getAccessToken() diferente. Parece que estás usando la apps-script-oauth2 para generar tu OAuth simbólico. Si ese es el caso, confirme que la API de Slides está habilitada en el proyecto de la consola del desarrollador que generó el clientId y el secreto del cliente que está pasando a OAuth2.createService, ya que no es necesariamente el mismo proyecto adjunto a su script. Si cambiar a ScriptApp.getOAuthToken() es una opción para usted, puede funcionar también.

Si eso no soluciona su problema, ¿le importaría proporcionar más de su código? El fragmento que ha pegado no parece coincidir con el mensaje de error, ya que su código parece estar solicitando docs.google.com, no slides.googleapis.com como se menciona en el error.

1
Maurice Codik 29 dic. 2016 a las 22:16

Esta no es una respuesta directa a la pregunta de OP, pero aborda directamente la primera parte de su primera oración, que es: "Estoy tratando de generar diapositivas de Google a partir de hojas de cálculo de Google ...". Este es el caso de uso exacto que creé un video (y la publicación de blog que lo acompaña [s ]) para. NOTA: la carga útil en la publicación es JSON, pero el ejemplo completo del video está en Python, por lo que los desarrolladores que no son Python simplemente pueden usarlo como pseudocódigo).

-1
wescpy 12 feb. 2018 a las 09:46

Versión corta de la solución: Gracias a los esfuerzos de Maurice Codik conseguí que tanto su código como el mío funcionaran.

El problema era con la configuración de URI de redireccionamiento autorizado en las credenciales de OAuth, que tenía que configurarse para

https://script.google.com/macros/d/[ScriptID]/usercallback

0
Bruno Guardia 30 dic. 2016 a las 18:05