在本快速入门中,我们将创建一个简单的 Google Workspace 插件,演示首页、情境触发器以及连接到第三方 API。
该插件会在 Gmail、日历和云端硬盘中创建上下文相关和非上下文相关界面。该插件会随机显示一张猫的图片,并在图片上叠加文字。对于首页,文本是静态的;对于内容相关触发器,文本则取自托管应用上下文。
目标
- 设置环境。
- 设置脚本。
- 运行脚本。
前提条件
如需使用此示例,您需要满足以下前提条件:
- Google 账号(Google Workspace 账号可能需要管理员批准)。
一个能够访问互联网的网络浏览器。
具有一个 Google Cloud 项目。
设置环境
在 Google Cloud 控制台中打开您的 Cloud 项目
如果尚未打开,请打开您打算用于此示例的 Cloud 项目:
- 在 Google Cloud 控制台中,前往选择项目页面。
- 选择您要使用的 Google Cloud 项目。或者,点击创建项目,然后按照屏幕上的说明操作。如果您创建 Google Cloud 项目,则可能需要为项目启用结算功能。
配置 OAuth 权限请求页面
Google Workspace 插件需要配置意见征求界面。配置插件 OAuth 权限请求页面可定义 Google 向用户显示的内容。
- 在 Google Cloud 控制台中,依次点击“菜单”图标 > API 和服务 > OAuth 权限请求页面。
- 在用户类型字段中,选择内部,然后点击创建。
- 填写应用注册表单,然后点击保存并继续。
现在,您可以跳过添加镜重,然后点击保存并继续。今后,如果您创建的应用要在 Google Workspace 组织之外使用,则必须将用户类型更改为外部,然后添加应用所需的授权范围。
- 查看应用注册摘要。如需进行更改,请点击修改。如果应用注册未出现任何问题,请点击返回信息中心。
设置脚本
创建 Apps 脚本项目
- 如需创建新的 Apps 脚本项目,请前往 script.new。
- 点击无标题项目。
- 将 Apps 脚本项目重命名为 Cats,然后点击 Rename(重命名)。
- 在
Code.gs
文件旁边,依次点击“更多”图标 > 重命名。将文件命名为Common
。 - 依次点击“添加文件”图标 > 脚本。将文件命名为
Gmail
。 - 重复第 5 步,再创建 2 个名为
Calendar
和Drive
的脚本文件。完成后,您应该会得到 4 个单独的脚本文件。 将每个文件的内容替换为以下相应代码:
Common.gs
/** * This simple Google Workspace Add-on shows a random image of a cat in the * sidebar. When opened manually (the homepage card), some static text is * overlayed on the image, but when contextual cards are opened a new cat image * is shown with the text taken from that context (such as a message's subject * line) overlaying the image. There is also a button that updates the card with * a new random cat image. * * Click "File > Make a copy..." to copy the script, and "Publish > Deploy from * manifest > Install add-on" to install it. */ /** * The maximum number of characters that can fit in the cat image. */ var MAX_MESSAGE_LENGTH = 40; /** * Callback for rendering the homepage card. * @return {CardService.Card} The card to show to the user. */ function onHomepage(e) { console.log(e); var hour = Number(Utilities.formatDate(new Date(), e.userTimezone.id, 'H')); var message; if (hour >= 6 && hour < 12) { message = 'Good morning'; } else if (hour >= 12 && hour < 18) { message = 'Good afternoon'; } else { message = 'Good night'; } message += ' ' + e.hostApp; return createCatCard(message, true); } /** * Creates a card with an image of a cat, overlayed with the text. * @param {String} text The text to overlay on the image. * @param {Boolean} isHomepage True if the card created here is a homepage; * false otherwise. Defaults to false. * @return {CardService.Card} The assembled card. */ function createCatCard(text, isHomepage) { // Explicitly set the value of isHomepage as false if null or undefined. if (!isHomepage) { isHomepage = false; } // Use the "Cat as a service" API to get the cat image. Add a "time" URL // parameter to act as a cache buster. var now = new Date(); // Replace forward slashes in the text, as they break the CataaS API. var caption = text.replace(/\//g, ' '); var imageUrl = Utilities.formatString('https://cataas.com/cat/says/%s?time=%s', encodeURIComponent(caption), now.getTime()); var image = CardService.newImage() .setImageUrl(imageUrl) .setAltText('Meow') // Create a button that changes the cat image when pressed. // Note: Action parameter keys and values must be strings. var action = CardService.newAction() .setFunctionName('onChangeCat') .setParameters({text: text, isHomepage: isHomepage.toString()}); var button = CardService.newTextButton() .setText('Change cat') .setOnClickAction(action) .setTextButtonStyle(CardService.TextButtonStyle.FILLED); var buttonSet = CardService.newButtonSet() .addButton(button); // Create a footer to be shown at the bottom. var footer = CardService.newFixedFooter() .setPrimaryButton(CardService.newTextButton() .setText('Powered by cataas.com') .setOpenLink(CardService.newOpenLink() .setUrl('https://cataas.com'))); // Assemble the widgets and return the card. var section = CardService.newCardSection() .addWidget(image) .addWidget(buttonSet); var card = CardService.newCardBuilder() .addSection(section) .setFixedFooter(footer); if (!isHomepage) { // Create the header shown when the card is minimized, // but only when this card is a contextual card. Peek headers // are never used by non-contexual cards like homepages. var peekHeader = CardService.newCardHeader() .setTitle('Contextual Cat') .setImageUrl('https://www.gstatic.com/images/icons/material/system/1x/pets_black_48dp.png') .setSubtitle(text); card.setPeekCardHeader(peekHeader) } return card.build(); } /** * Callback for the "Change cat" button. * @param {Object} e The event object, documented {@link * https://developers.google.com/gmail/add-ons/concepts/actions#action_event_objects * here}. * @return {CardService.ActionResponse} The action response to apply. */ function onChangeCat(e) { console.log(e); // Get the text that was shown in the current cat image. This was passed as a // parameter on the Action set for the button. var text = e.parameters.text; // The isHomepage parameter is passed as a string, so convert to a Boolean. var isHomepage = e.parameters.isHomepage === 'true'; // Create a new card with the same text. var card = createCatCard(text, isHomepage); // Create an action response that instructs the add-on to replace // the current card with the new one. var navigation = CardService.newNavigation() .updateCard(card); var actionResponse = CardService.newActionResponseBuilder() .setNavigation(navigation); return actionResponse.build(); } /** * Truncate a message to fit in the cat image. * @param {string} message The message to truncate. * @return {string} The truncated message. */ function truncate(message) { if (message.length > MAX_MESSAGE_LENGTH) { message = message.slice(0, MAX_MESSAGE_LENGTH); message = message.slice(0, message.lastIndexOf(' ')) + '...'; } return message; }
Gmail.gs
/** * Callback for rendering the card for a specific Gmail message. * @param {Object} e The event object. * @return {CardService.Card} The card to show to the user. */ function onGmailMessage(e) { console.log(e); // Get the ID of the message the user has open. var messageId = e.gmail.messageId; // Get an access token scoped to the current message and use it for GmailApp // calls. var accessToken = e.gmail.accessToken; GmailApp.setCurrentMessageAccessToken(accessToken); // Get the subject of the email. var message = GmailApp.getMessageById(messageId); var subject = message.getThread().getFirstMessageSubject(); // Remove labels and prefixes. subject = subject .replace(/^([rR][eE]|[fF][wW][dD])\:\s*/, '') .replace(/^\[.*?\]\s*/, ''); // If neccessary, truncate the subject to fit in the image. subject = truncate(subject); return createCatCard(subject); } /** * Callback for rendering the card for the compose action dialog. * @param {Object} e The event object. * @return {CardService.Card} The card to show to the user. */ function onGmailCompose(e) { console.log(e); var header = CardService.newCardHeader() .setTitle('Insert cat') .setSubtitle('Add a custom cat image to your email message.'); // Create text input for entering the cat's message. var input = CardService.newTextInput() .setFieldName('text') .setTitle('Caption') .setHint('What do you want the cat to say?'); // Create a button that inserts the cat image when pressed. var action = CardService.newAction() .setFunctionName('onGmailInsertCat'); var button = CardService.newTextButton() .setText('Insert cat') .setOnClickAction(action) .setTextButtonStyle(CardService.TextButtonStyle.FILLED); var buttonSet = CardService.newButtonSet() .addButton(button); // Assemble the widgets and return the card. var section = CardService.newCardSection() .addWidget(input) .addWidget(buttonSet); var card = CardService.newCardBuilder() .setHeader(header) .addSection(section); return card.build(); } /** * Callback for inserting a cat into the Gmail draft. * @param {Object} e The event object. * @return {CardService.UpdateDraftActionResponse} The draft update response. */ function onGmailInsertCat(e) { console.log(e); // Get the text that was entered by the user. var text = e.formInput.text; // Use the "Cat as a service" API to get the cat image. Add a "time" URL // parameter to act as a cache buster. var now = new Date(); var imageUrl = 'https://cataas.com/cat'; if (text) { // Replace forward slashes in the text, as they break the CataaS API. var caption = text.replace(/\//g, ' '); imageUrl += Utilities.formatString('/says/%s?time=%s', encodeURIComponent(caption), now.getTime()); } var imageHtmlContent = '<img style="display: block; max-height: 300px;" src="' + imageUrl + '"/>'; var response = CardService.newUpdateDraftActionResponseBuilder() .setUpdateDraftBodyAction(CardService.newUpdateDraftBodyAction() .addUpdateContent(imageHtmlContent,CardService.ContentType.MUTABLE_HTML) .setUpdateType(CardService.UpdateDraftBodyType.IN_PLACE_INSERT)) .build(); return response; }
Calendar.gs
/** * Callback for rendering the card for a specific Calendar event. * @param {Object} e The event object. * @return {CardService.Card} The card to show to the user. */ function onCalendarEventOpen(e) { console.log(e); var calendar = CalendarApp.getCalendarById(e.calendar.calendarId); // The event metadata doesn't include the event's title, so using the // calendar.readonly scope and fetching the event by it's ID. var event = calendar.getEventById(e.calendar.id); if (!event) { // This is a new event still being created. return createCatCard('A new event! Am I invited?'); } var title = event.getTitle(); // If necessary, truncate the title to fit in the image. title = truncate(title); return createCatCard(title); }
Drive.gs
/** * Callback for rendering the card for specific Drive items. * @param {Object} e The event object. * @return {CardService.Card} The card to show to the user. */ function onDriveItemsSelected(e) { console.log(e); var items = e.drive.selectedItems; // Include at most 5 items in the text. items = items.slice(0, 5); var text = items.map(function(item) { var title = item.title; // If neccessary, truncate the title to fit in the image. title = truncate(title); return title; }).join('\n'); return createCatCard(text); }
点击项目设置 。
选中在编辑器中显示“appsscript.json”清单文件复选框。
点击编辑器
。打开
appsscript.json
文件,将其内容替换为以下代码,然后点击“保存”图标 。appsscript.json
{ "timeZone": "America/New_York", "dependencies": { }, "exceptionLogging": "STACKDRIVER", "oauthScopes": [ "https://www.googleapis.com/auth/calendar.addons.execute", "https://www.googleapis.com/auth/calendar.readonly", "https://www.googleapis.com/auth/drive.addons.metadata.readonly", "https://www.googleapis.com/auth/gmail.addons.current.action.compose", "https://www.googleapis.com/auth/gmail.addons.current.message.readonly", "https://www.googleapis.com/auth/gmail.addons.execute", "https://www.googleapis.com/auth/script.locale"], "runtimeVersion": "V8", "addOns": { "common": { "name": "Cats", "logoUrl": "https://www.gstatic.com/images/icons/material/system/1x/pets_black_48dp.png", "useLocaleFromApp": true, "homepageTrigger": { "runFunction": "onHomepage", "enabled": true }, "universalActions": [{ "label": "Learn more about Cataas", "openLink": "https://cataas.com" }] }, "gmail": { "contextualTriggers": [{ "unconditional": { }, "onTriggerFunction": "onGmailMessage" }], "composeTrigger": { "selectActions": [{ "text": "Insert cat", "runFunction": "onGmailCompose" }], "draftAccess": "NONE" } }, "drive": { "onItemsSelectedTrigger": { "runFunction": "onDriveItemsSelected" } }, "calendar": { "eventOpenTrigger": { "runFunction": "onCalendarEventOpen" } } } }
复制 Cloud 项目编号
- 在 Google Cloud 控制台中,依次选择“菜单”图标 > IAM 和管理 > 设置。
- 在项目编号字段中,复制相应值。
设置 Apps 脚本项目的 Cloud 项目
- 在您的 Apps 脚本项目中,点击项目设置 。
- 在 Google Cloud Platform (GCP) Project(Google Cloud Platform [GCP] 项目)下,点击 Change project(更改项目)。
- 在 GCP project number(GCP 项目编号)中,粘贴 Google Cloud 项目编号。
- 点击设置项目。
安装测试部署
- 在 Apps 脚本项目中,点击编辑器 。
- 依次点击 Deploy > Test deployments。
- 依次点击安装 > 完成。
运行脚本
- 前往 Gmail。
- 如需打开该插件,请在右侧边栏中点击“猫”图标 。
- 如果系统提示,请为该插件授权。
- 该插件会显示一张猫咪图片和文字。如需更改图片,请点击更改类别。
- 如果您在该插件处于打开状态时打开电子邮件,图片会刷新,文本会更改为电子邮件的主题行(如果主题行过长,则会截断)。
您可以在 Google 日历和云端硬盘中看到类似的功能。您无需重新为该插件授权,即可在这些托管应用中使用它。