我有一个使用 Google Picker API 的脚本。在我对其进行测试时,它运行良好,直到我将其作为私人插件发布。从那时起,脚本 getOAuthToken 失败并出现以下(非常无用)错误:
例外:很抱歉,发生服务器错误。请稍等,然后重试。在 getOAuthToken(代码:37:12)
我试过的:
- 创建新的 API 密钥
- 将脚本所有者添加到 GCP 项目(根据公司设置,这是一个通用的 Google 帐户)
- 在插件的 GCP 项目中启用 Picker API 而不是旧项目并生成新密钥
API 密钥具有以下设置:
- 应用程序限制:HTTP 引荐来源网址
- 网站限制:
- *.google.com
- *.googleusercontent.com
- API 限制:不限制密钥
这些设置在发布之前就可以使用。
谷歌选择器的代码也在下面。它基于Google API 文档中的样板,并按原样工作:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons.css">
<script>
const pickFileType = '<?= fileType ?>';
// IMPORTANT: Replace the value for DEVELOPER_KEY with the API key obtained
// from the Google Developers Console.
var DEVELOPER_KEY = 'intentionally removed';
var DIALOG_DIMENSIONS = {width: 900, height: 500};
var pickerApiLoaded = false;
/**
* Loads the Google Picker API.
*/
function onApiLoad() {
gapi.load('picker', {'callback': function() {
pickerApiLoaded = true;
}});
}
/**
* Gets the user's OAuth 2.0 access token from the server-side script so that
* it can be passed to Picker. This technique keeps Picker from needing to
* show its own authorization dialog, but is only possible if the OAuth scope
* that Picker needs is available in Apps Script. Otherwise, your Picker code
* will need to declare its own OAuth scopes.
*/
function getOAuthToken() {
google.script.run.withSuccessHandler(createPicker)
.withFailureHandler(showError).getOAuthToken();
}
/**
* Creates a Picker that can access the user's spreadsheets. This function
* uses advanced options to hide the Picker's left navigation panel and
* default title bar.
*
* @param {string} token An OAuth 2.0 access token that lets Picker access the
* file type specified in the addView call.
*/
function createPicker(token) {
if (pickerApiLoaded && token) {
const docsUploadView = new google.picker.DocsUploadView();
docsUploadView.setIncludeFolders(true);
const viewId = pickFileType === 'folder' ?
google.picker.ViewId.FOLDERS : google.picker.ViewId.DOCUMENTS;
const drivesView = new google.picker.DocsView(viewId);
drivesView.setEnableDrives(true);
drivesView.setIncludeFolders(true);
if (pickFileType === 'folder') drivesView.setSelectFolderEnabled(true);
const driveView = new google.picker.DocsView(viewId);
driveView.setSelectFolderEnabled(true);
driveView.setParent('root');
if (pickFileType === 'folder') driveView.setIncludeFolders(true);
console.log(`viewId = ${viewId}`);
// const docsViewId = new google.picker.ViewGroup(google.picker.viewId.DOCS)
// .addView(viewId);
var picker = new google.picker.PickerBuilder()
// Instruct Picker to display only spreadsheets in Drive. For other
// .addViewGroup(docsViewId)
.addView(driveView)
.addView(drivesView)
// .addView(viewId)
// .addView(docsUploadView)
// Hide the navigation panel so that Picker fills more of the dialog.
// .enableFeature(google.picker.Feature.NAV_HIDDEN)
// .enableFeature(google.picker.Feature.MULTISELECT_ENABLED)
.enableFeature(google.picker.Feature.SUPPORT_DRIVES)
// Hide the title bar since an Apps Script dialog already has a title.
.hideTitleBar()
.setOAuthToken(token)
.setDeveloperKey(DEVELOPER_KEY)
.setCallback(pickerCallback)
.setOrigin(google.script.host.origin)
// Instruct Picker to fill the dialog, minus 2 pixels for the border.
.setSize(DIALOG_DIMENSIONS.width - 2,
DIALOG_DIMENSIONS.height - 2)
.build();
picker.setVisible(true);
} else {
showError('Unable to load the file picker.');
}
}
/**
* A callback function that extracts the chosen document's metadata from the
* response object. For details on the response object, see
* https://developers.google.com/picker/docs/result
*
* @param {object} data The response object.
*/
function pickerCallback(data) {
let selectedId;
console.log(data);
var action = data[google.picker.Response.ACTION];
if (action == google.picker.Action.PICKED) {
// const array = [['Nom', 'ID', 'URL']];
const docs = data[google.picker.Response.DOCUMENTS];
docs.forEach(doc => {
var id = doc[google.picker.Document.ID];
selectedId = id;
// var url = doc[google.picker.Document.URL];
// var title = doc[google.picker.Document.NAME];
// array.push([title, id, url]);
});
google.script.run.withSuccessHandler(() => {
google.script.run.showFront(true);
}).writeVar(pickFileType, selectedId);
} else if (action == google.picker.Action.CANCEL) {
google.script.run.showFront(true);
}
}
/**
* Displays an error message within the #result element.
*
* @param {string} message The error message to display.
*/
function showError(message) {
document.getElementById('result').innerHTML = 'Error: ' + message;
}
</script>
</head>
<body>
<div>
<p id='result'></p>
</div>
<script src="https://apis.google.com/js/api.js?onload=onApiLoad"></script>
<script>
window.onload = getOAuthToken;
</script>
</body>
</html>
编辑:这就是服务器端 getOAuthToken 的样子。我把评论留在里面。
/**
* Gets the user's OAuth 2.0 access token so that it can be passed to Picker.
* This technique keeps Picker from needing to show its own authorization
* dialog, but is only possible if the OAuth scope that Picker needs is
* available in Apps Script. In this case, the function includes an unused call
* to a DriveApp method to ensure that Apps Script requests access to all files
* in the user's Drive.
*
* @return {string} The user's OAuth 2.0 access token.
*/
function getOAuthToken() {
DriveApp.getRootFolder();
return ScriptApp.getOAuthToken();
}
编辑 2
我想我越来越接近理解了。现在,每次我尝试访问 Drive 应用程序时,脚本都会抛出这些类型的错误。当我这样做时DriveApp.getFolderById(id)
,我得到了同样的错误
当我在对象 DriveApp 上获取方法或属性 getFileById 时出现意外错误
DriveApp.getFileById(id)
。
我已经在清单中添加了范围,但它仍然没有帮助。这是清单:
{
"timeZone": "Europe/Paris",
"dependencies": {
},
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8",
"oauthScopes": [
"https://www.googleapis.com/auth/drive",
"https://www.googleapis.com/auth/spreadsheets",
"https://www.googleapis.com/auth/script.container.ui",
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/spreadsheets"
]
}