Google 文档 Add-On:Permission-related 错误 "This App is blocked"
Google Docs Add-On: Permission-related error "This App is blocked"
我在脚本编辑器中创建了一个 Google 文档 Add-On。当我尝试在 Google 文档中启动 Add-On 时,它会在选择并登录 Google 帐户后显示此屏幕:
不确定是否相关,在应用程序脚本的概述部分,项目 OAuth 范围是:
- 连接到外部服务
- 在 Google 应用程序 Google 的提示和边栏中显示和 运行 third-party 网页内容
- 查看和管理已安装此应用程序的文档
我该如何解决这个问题?或者我该如何调试?
代码与official tutorial非常相似。
脚本(code.gs
)如下:
/**
* @OnlyCurrentDoc
*
* The above comment directs Apps Script to limit the scope of file
* access for this add-on. It specifies that this add-on will only
* attempt to read or modify the files in which the add-on is used,
* and not all of the user's files. The authorization request message
* presented to users will reflect this limited scope.
*/
/**
* Creates a menu entry in the Google Docs UI when the document is opened.
* This method is only used by the regular add-on, and is never called by
* the mobile add-on version.
*
* @param {object} e The event parameter for a simple onOpen trigger. To
* determine which authorization mode (ScriptApp.AuthMode) the trigger is
* running in, inspect e.authMode.
*/
function onOpen(e) {
DocumentApp.getUi().createAddonMenu()
.addItem('Start', 'showSidebar')
.addToUi();
}
/**
* Runs when the add-on is installed.
* This method is only used by the regular add-on, and is never called by
* the mobile add-on version.
*
* @param {object} e The event parameter for a simple onInstall trigger. To
* determine which authorization mode (ScriptApp.AuthMode) the trigger is
* running in, inspect e.authMode. (In practice, onInstall triggers always
* run in AuthMode.FULL, but onOpen triggers may be AuthMode.LIMITED or
* AuthMode.NONE.)
*/
function onInstall(e) {
onOpen(e);
}
/**
* Opens a sidebar in the document containing the add-on's user interface.
* This method is only used by the regular add-on, and is never called by
* the mobile add-on version.
*/
function showSidebar() {
var ui = HtmlService.createHtmlOutputFromFile('sidebar')
.setTitle('Some Title Here');
DocumentApp.getUi().showSidebar(ui);
}
/**
* Gets the text the user has selected. If there is no selection,
* this function displays an error message.
*
* @return {Array.<string>} The selected text.
*/
function getSelectedText() {
var selection = DocumentApp.getActiveDocument().getSelection();
var text = [];
if (selection) {
var elements = selection.getSelectedElements();
for (var i = 0; i < elements.length; ++i) {
if (elements[i].isPartial()) {
var element = elements[i].getElement().asText();
var startIndex = elements[i].getStartOffset();
var endIndex = elements[i].getEndOffsetInclusive();
text.push(element.getText().substring(startIndex, endIndex + 1));
} else {
var element = elements[i].getElement();
// Only translate elements that can be edited as text; skip images and
// other non-text elements.
if (element.editAsText) {
var elementText = element.asText().getText();
// This check is necessary to exclude images, which return a blank
// text element.
if (elementText) {
text.push(elementText);
}
}
}
}
}
if (!text.length) throw new Error('Please select some text.');
return text;
}
/**
* Obtain the input text and pass to API
*
*/
function getTextAndAnalyse() {
var text = getSelectedText().join("\n");
var query = text.trim();
var apiURL = "https://example.com/api/?q=" + encodeURIComponent(query);
var response = UrlFetchApp.fetch(apiURL, {'muteHttpExceptions': true});
Logger.log(response);
var json = response.getContentText();
var data = JSON.parse(json);
if (data.error != "") throw new Error('Error connecting to API');
return {
result: data.result_value,
examples: data.examples
}
}
/**
* Replaces the text of the current selection with the provided text, or
* inserts text at the current cursor location. (There will always be either
* a selection or a cursor.) If multiple elements are selected, only inserts the
* translated text in the first element that can contain text and removes the
* other elements.
*
* @param {string} newText The text with which to replace the current selection.
*/
function insertText(newText) {
var selection = DocumentApp.getActiveDocument().getSelection();
if (selection) {
var replaced = false;
var elements = selection.getSelectedElements();
if (elements.length === 1 && elements[0].getElement().getType() ===
DocumentApp.ElementType.INLINE_IMAGE) {
throw new Error('Can\'t insert text into an image.');
}
for (var i = 0; i < elements.length; ++i) {
if (elements[i].isPartial()) {
var element = elements[i].getElement().asText();
var startIndex = elements[i].getStartOffset();
var endIndex = elements[i].getEndOffsetInclusive();
element.deleteText(startIndex, endIndex);
if (!replaced) {
element.insertText(startIndex, newText);
replaced = true;
} else {
// This block handles a selection that ends with a partial element. We
// want to copy this partial text to the previous element so we don't
// have a line-break before the last partial.
var parent = element.getParent();
var remainingText = element.getText().substring(endIndex + 1);
parent.getPreviousSibling().asText().appendText(remainingText);
// We cannot remove the last paragraph of a doc. If this is the case,
// just remove the text within the last paragraph instead.
if (parent.getNextSibling()) {
parent.removeFromParent();
} else {
element.removeFromParent();
}
}
} else {
var element = elements[i].getElement();
if (!replaced && element.editAsText) {
// Only translate elements that can be edited as text, removing other
// elements.
element.clear();
element.asText().setText(newText);
replaced = true;
} else {
// We cannot remove the last paragraph of a doc. If this is the case,
// just clear the element.
if (element.getNextSibling()) {
element.removeFromParent();
} else {
element.clear();
}
}
}
}
} else {
var cursor = DocumentApp.getActiveDocument().getCursor();
var surroundingText = cursor.getSurroundingText().getText();
var surroundingTextOffset = cursor.getSurroundingTextOffset();
// If the cursor follows or preceds a non-space character, insert a space
// between the character and the translation. Otherwise, just insert the
// translation.
if (surroundingTextOffset > 0) {
if (surroundingText.charAt(surroundingTextOffset - 1) != ' ') {
newText = ' ' + newText;
}
}
if (surroundingTextOffset < surroundingText.length) {
if (surroundingText.charAt(surroundingTextOffset) != ' ') {
newText += ' ';
}
}
cursor.insertText(newText);
}
}
这里是appsscript.json
的内容:
{
"timeZone": "Asia/Shanghai",
"dependencies": {
},
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8"
}
如果您打算将该项目用作附加组件,您应该 set the "oauthScopes" explicitly in the manifest file (appsscript.json)。
我在脚本编辑器中创建了一个 Google 文档 Add-On。当我尝试在 Google 文档中启动 Add-On 时,它会在选择并登录 Google 帐户后显示此屏幕:
不确定是否相关,在应用程序脚本的概述部分,项目 OAuth 范围是:
- 连接到外部服务
- 在 Google 应用程序 Google 的提示和边栏中显示和 运行 third-party 网页内容
- 查看和管理已安装此应用程序的文档
我该如何解决这个问题?或者我该如何调试?
代码与official tutorial非常相似。
脚本(code.gs
)如下:
/**
* @OnlyCurrentDoc
*
* The above comment directs Apps Script to limit the scope of file
* access for this add-on. It specifies that this add-on will only
* attempt to read or modify the files in which the add-on is used,
* and not all of the user's files. The authorization request message
* presented to users will reflect this limited scope.
*/
/**
* Creates a menu entry in the Google Docs UI when the document is opened.
* This method is only used by the regular add-on, and is never called by
* the mobile add-on version.
*
* @param {object} e The event parameter for a simple onOpen trigger. To
* determine which authorization mode (ScriptApp.AuthMode) the trigger is
* running in, inspect e.authMode.
*/
function onOpen(e) {
DocumentApp.getUi().createAddonMenu()
.addItem('Start', 'showSidebar')
.addToUi();
}
/**
* Runs when the add-on is installed.
* This method is only used by the regular add-on, and is never called by
* the mobile add-on version.
*
* @param {object} e The event parameter for a simple onInstall trigger. To
* determine which authorization mode (ScriptApp.AuthMode) the trigger is
* running in, inspect e.authMode. (In practice, onInstall triggers always
* run in AuthMode.FULL, but onOpen triggers may be AuthMode.LIMITED or
* AuthMode.NONE.)
*/
function onInstall(e) {
onOpen(e);
}
/**
* Opens a sidebar in the document containing the add-on's user interface.
* This method is only used by the regular add-on, and is never called by
* the mobile add-on version.
*/
function showSidebar() {
var ui = HtmlService.createHtmlOutputFromFile('sidebar')
.setTitle('Some Title Here');
DocumentApp.getUi().showSidebar(ui);
}
/**
* Gets the text the user has selected. If there is no selection,
* this function displays an error message.
*
* @return {Array.<string>} The selected text.
*/
function getSelectedText() {
var selection = DocumentApp.getActiveDocument().getSelection();
var text = [];
if (selection) {
var elements = selection.getSelectedElements();
for (var i = 0; i < elements.length; ++i) {
if (elements[i].isPartial()) {
var element = elements[i].getElement().asText();
var startIndex = elements[i].getStartOffset();
var endIndex = elements[i].getEndOffsetInclusive();
text.push(element.getText().substring(startIndex, endIndex + 1));
} else {
var element = elements[i].getElement();
// Only translate elements that can be edited as text; skip images and
// other non-text elements.
if (element.editAsText) {
var elementText = element.asText().getText();
// This check is necessary to exclude images, which return a blank
// text element.
if (elementText) {
text.push(elementText);
}
}
}
}
}
if (!text.length) throw new Error('Please select some text.');
return text;
}
/**
* Obtain the input text and pass to API
*
*/
function getTextAndAnalyse() {
var text = getSelectedText().join("\n");
var query = text.trim();
var apiURL = "https://example.com/api/?q=" + encodeURIComponent(query);
var response = UrlFetchApp.fetch(apiURL, {'muteHttpExceptions': true});
Logger.log(response);
var json = response.getContentText();
var data = JSON.parse(json);
if (data.error != "") throw new Error('Error connecting to API');
return {
result: data.result_value,
examples: data.examples
}
}
/**
* Replaces the text of the current selection with the provided text, or
* inserts text at the current cursor location. (There will always be either
* a selection or a cursor.) If multiple elements are selected, only inserts the
* translated text in the first element that can contain text and removes the
* other elements.
*
* @param {string} newText The text with which to replace the current selection.
*/
function insertText(newText) {
var selection = DocumentApp.getActiveDocument().getSelection();
if (selection) {
var replaced = false;
var elements = selection.getSelectedElements();
if (elements.length === 1 && elements[0].getElement().getType() ===
DocumentApp.ElementType.INLINE_IMAGE) {
throw new Error('Can\'t insert text into an image.');
}
for (var i = 0; i < elements.length; ++i) {
if (elements[i].isPartial()) {
var element = elements[i].getElement().asText();
var startIndex = elements[i].getStartOffset();
var endIndex = elements[i].getEndOffsetInclusive();
element.deleteText(startIndex, endIndex);
if (!replaced) {
element.insertText(startIndex, newText);
replaced = true;
} else {
// This block handles a selection that ends with a partial element. We
// want to copy this partial text to the previous element so we don't
// have a line-break before the last partial.
var parent = element.getParent();
var remainingText = element.getText().substring(endIndex + 1);
parent.getPreviousSibling().asText().appendText(remainingText);
// We cannot remove the last paragraph of a doc. If this is the case,
// just remove the text within the last paragraph instead.
if (parent.getNextSibling()) {
parent.removeFromParent();
} else {
element.removeFromParent();
}
}
} else {
var element = elements[i].getElement();
if (!replaced && element.editAsText) {
// Only translate elements that can be edited as text, removing other
// elements.
element.clear();
element.asText().setText(newText);
replaced = true;
} else {
// We cannot remove the last paragraph of a doc. If this is the case,
// just clear the element.
if (element.getNextSibling()) {
element.removeFromParent();
} else {
element.clear();
}
}
}
}
} else {
var cursor = DocumentApp.getActiveDocument().getCursor();
var surroundingText = cursor.getSurroundingText().getText();
var surroundingTextOffset = cursor.getSurroundingTextOffset();
// If the cursor follows or preceds a non-space character, insert a space
// between the character and the translation. Otherwise, just insert the
// translation.
if (surroundingTextOffset > 0) {
if (surroundingText.charAt(surroundingTextOffset - 1) != ' ') {
newText = ' ' + newText;
}
}
if (surroundingTextOffset < surroundingText.length) {
if (surroundingText.charAt(surroundingTextOffset) != ' ') {
newText += ' ';
}
}
cursor.insertText(newText);
}
}
这里是appsscript.json
的内容:
{
"timeZone": "Asia/Shanghai",
"dependencies": {
},
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8"
}
如果您打算将该项目用作附加组件,您应该 set the "oauthScopes" explicitly in the manifest file (appsscript.json)。