【问题标题】:403 error with message "Google Slides API has not been used in project ... before or it is disabled"403 错误消息“Google Slides API 尚未在项目中使用......之前或已被禁用”
【发布时间】:2017-05-13 01:12:49
【问题描述】:

我正在尝试从 Google 表格生成 Google 幻灯片;使用 Sheets 脚本没有问题,但是当我尝试包含 Google 幻灯片时,在验证并获得 Oauth 权限提示后,我收到了这个错误,我找不到任何参考;我已确保在 Developers Console 中启用了 Google Slides API 和 Drive API。

https://slides.googleapis.com/v1/presentations/ 的请求失败...返回代码 403。截断的服务器响应:{“错误”:{“代码”:403,“消息”:“项目项目 ID 中未使用 Google 幻灯片 API -...之前或者它被禁用...(使用 muteHttpExceptions 选项检查完整响应)(第 93 行,文件“代码”)”

失败的代码如下,失败的函数是从How to download Google Slides as images?复制的。客户端 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).
      // 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);
}

【问题讨论】:

  • 是否为正确的项目 ID 启用了 API?从开发者控制台的项目下拉列表中选择? (我提到这一点是因为我之前在错误的项目中启用 API 时遇到过这个错误)
  • 是的,它是这个帐户中唯一的项目,并且花了一些时间弄清楚如何为该项目启用 API。不过谢谢你的建议

标签: google-apps-script google-apps google-slides-api


【解决方案1】:

编辑: 我用这个代码 sn-p 得到了这个:

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);
  }
}

我猜客户端 ID 和密码并非来自您认为它们来自的项目。您可以通过访问 your project's credentials page 并查看“OAuth 2.0 客户端 ID”下是否列出了匹配的客户端 ID 来验证这一点。包含该客户端 ID 的项目需要启用 Slides API。

另请注意:您使用的 /export/png 端点不是已记录/支持的 Google API,因此将来可能会被重命名或损坏。如果您对通过 Slides API 获取渲染的 PNG 幻灯片的官方 API 感兴趣,请关注此issue on the tracker


以前的内容:

您的代码也与您从中复制的 sn-p 略有不同。它使用ScriptApp.getOAuthToken() 来获取授权标头的值,但您正在调用不同的getService().getAccessToken() 函数。看起来您正在使用 apps-script-oauth2 库来生成 OAuth 令牌。如果是这种情况,请确认在生成您传递给OAuth2.createService 的 clientId 和客户端密码的开发者控制台项目上启用了 Slides API,因为它不一定与附加到您的脚本的项目相同。如果您可以选择切换到ScriptApp.getOAuthToken(),那也可以。

如果这不能解决您的问题,介意提供更多代码吗?您粘贴的 sn-p 似乎与错误消息不匹配,因为您的代码似乎正在向 docs.google.com 发出请求,而不是错误中提到的 slides.googleapis.com。

【讨论】:

  • 添加了更多代码;正如您所说,我用 ScriptApp 调用替换了一个函数,该函数允许确保我控制了 clientId 和客户端密码。 sn-p 是正确的,我从 docs.google.com 请求,因为我想用作模板的幻灯片位于 docs.google.com/presentation/d/1V7k5UTj... 但似乎这会导致重定向。
  • @Bruno,介意添加实际调用downloadSlide 的部分吗?我可以尝试用你的整个脚本来重现。我仍然怀疑客户端 ID/秘密属于您不希望未启用 API 的项目。
  • 你是对的,错过了调用downloadSlide的函数,虽然它与问题不太相关。主要功能只是调用具有有效ID的downloadSlides(验证两次,上次只是将ID添加到docs.google.com/presentation/d并且它可以正常工作)。我还仔细检查了该项目,它是与该帐户关联的唯一项目,它报告 Google Drive 和 Google Slides API 均已启用。我仍然认为问题的关键是错误消息,在“第一次使用”一侧,而不是禁用。
  • @Bruno,我和this code 一起工作。我的客户 ID 和密码来自的项目启用了 Slides API。我对您的原始代码所做的唯一更改是填写一些样板并将请求的范围更改为驱动(不是博主)。问题肯定是您的客户端 ID 和客户端密码;我猜他们不是来自你认为他们来自的项目(检查项目的API credentials page 确认)
  • 谢谢莫里斯。您的代码与我的代码一样失败,但授权 URL 的日志记录允许发现问题:错误:redirect_uri_mismatch 请求中的重定向 URI script.google.com/macros/d/.../usercallback 与 OAuth 客户端授权的不匹配。访问 console.developers.google.com/apis/credentials/oauthclient/…... 以更新授权的重定向 URI。
【解决方案2】:

解决方案的简短版本:感谢 Maurice Codik 的努力,我的代码和他的代码都能正常工作。

问题在于 OAuth 凭据中的授权重定向 URI 设置,必须设置为

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

【讨论】:

    【解决方案3】:

    这不是对 OP 问题的直接回答,而是直接解决了他们第一句话的第一部分,即“我正在尝试从 Google 表格生成 Google 幻灯片......”这是确切的用例我为它创建了一个video (and accompanying blog post[s])。注意:帖子中的有效负载是 JSON,但视频中的完整示例是 Python,因此非 Python 开发人员可以简单地将其用作伪代码。)

    【讨论】:

    • 我在实现我的代码之前看到了......很好的参考,但不是你所说的问题的答案
    猜你喜欢
    • 2019-02-06
    • 1970-01-01
    • 2023-02-09
    • 2015-04-30
    • 1970-01-01
    • 2015-01-04
    • 2023-03-13
    • 2014-01-28
    • 2019-07-27
    相关资源
    最近更新 更多