消息 "Google Slides API has not been used in project ... before or it is disabled" 的 403 错误
403 error with message "Google Slides API has not been used in project ... before or it is disabled"
我正在尝试从 Google 表格生成 Google 幻灯片;使用表格脚本没有问题,但是当我尝试包含 Google 幻灯片时,在验证并获得 Oauth 权限提示后,我收到此错误,我找不到任何参考;我已确保在 Developers Console 中启用了 Google Slides API 和 Drive API。
"Request failed for https://slides.googleapis.com/v1/presentations/... returned code 403. Truncated server response: { "错误": { "code": 403, "message": "Google Slides API has not been used in project project-id-... before or it is disab... (use muteHttpExceptions option to examine full response) (line 93, file "代码")"
失败的代码如下,失败的函数复制自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
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);
}
编辑:
我用这个代码片段得到了这个:
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://whosebug.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 client IDs' 下是否列出了匹配的客户端 ID 来验证这一点。包含该客户端 ID 的项目需要启用幻灯片 API。
另请注意:您使用的 /export/png 端点不是 documented/supported Google API 所以它可能是将来更名或中断。如果您对通过幻灯片 API 获取幻灯片渲染 PNG 的官方 API 感兴趣,请关注此 issue on the tracker.
之前的内容:
您的代码也与您从中复制的代码段略有不同。它使用 ScriptApp.getOAuthToken()
获取授权值 header,但您正在调用不同的 getService().getAccessToken()
函数。看起来您正在使用 apps-script-oauth2 库来生成您的 OAuth 令牌。如果是这种情况,请确认幻灯片 API 在生成您传递给 OAuth2.createService
的 clientId 和客户端机密的开发人员控制台项目上已启用,因为它不一定是附加到您的项目的同一项目脚本。如果切换到 ScriptApp.getOAuthToken()
是您的一个选择,那也可能有效。
如果这不能解决您的问题,介意提供更多代码吗?您粘贴的代码段似乎与错误消息不匹配,因为您的代码似乎正在向 docs.google.com 发出请求,而不是错误中提到的 slides.googleapis.com。
解决方案的简短版本:感谢 Maurice Codik 的努力,我的代码和他的代码都可以正常工作。
问题出在 OAuth 凭据中的授权重定向 URI 设置,必须设置为
这不是对 OP 问题的直接回答,而是直接解决了他们第一句话的第一部分,即 "I am trying to generate Google Slides from Google Sheets...." 这正是我创建的 video (and accompanying blog post[ s]) 为。注意:post 中的有效负载是 JSON,但视频中的完整示例在 Python 中,因此非 Python 开发人员可以简单地将其用作伪代码。)
我正在尝试从 Google 表格生成 Google 幻灯片;使用表格脚本没有问题,但是当我尝试包含 Google 幻灯片时,在验证并获得 Oauth 权限提示后,我收到此错误,我找不到任何参考;我已确保在 Developers Console 中启用了 Google Slides API 和 Drive API。
"Request failed for https://slides.googleapis.com/v1/presentations/... returned code 403. Truncated server response: { "错误": { "code": 403, "message": "Google Slides API has not been used in project project-id-... before or it is disab... (use muteHttpExceptions option to examine full response) (line 93, file "代码")"
失败的代码如下,失败的函数复制自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
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);
}
编辑: 我用这个代码片段得到了这个:
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://whosebug.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 client IDs' 下是否列出了匹配的客户端 ID 来验证这一点。包含该客户端 ID 的项目需要启用幻灯片 API。
另请注意:您使用的 /export/png 端点不是 documented/supported Google API 所以它可能是将来更名或中断。如果您对通过幻灯片 API 获取幻灯片渲染 PNG 的官方 API 感兴趣,请关注此 issue on the tracker.
之前的内容:
您的代码也与您从中复制的代码段略有不同。它使用 ScriptApp.getOAuthToken()
获取授权值 header,但您正在调用不同的 getService().getAccessToken()
函数。看起来您正在使用 apps-script-oauth2 库来生成您的 OAuth 令牌。如果是这种情况,请确认幻灯片 API 在生成您传递给 OAuth2.createService
的 clientId 和客户端机密的开发人员控制台项目上已启用,因为它不一定是附加到您的项目的同一项目脚本。如果切换到 ScriptApp.getOAuthToken()
是您的一个选择,那也可能有效。
如果这不能解决您的问题,介意提供更多代码吗?您粘贴的代码段似乎与错误消息不匹配,因为您的代码似乎正在向 docs.google.com 发出请求,而不是错误中提到的 slides.googleapis.com。
解决方案的简短版本:感谢 Maurice Codik 的努力,我的代码和他的代码都可以正常工作。
问题出在 OAuth 凭据中的授权重定向 URI 设置,必须设置为
这不是对 OP 问题的直接回答,而是直接解决了他们第一句话的第一部分,即 "I am trying to generate Google Slides from Google Sheets...." 这正是我创建的 video (and accompanying blog post[ s]) 为。注意:post 中的有效负载是 JSON,但视频中的完整示例在 Python 中,因此非 Python 开发人员可以简单地将其用作伪代码。)