使用 Apps Script 建構 Google Workspace 外掛程式

本快速入門導覽課程會建立簡單的 Google Workspace 外掛程式,示範首頁、情境觸發條件,以及如何連線至第三方 API。

這個外掛程式會在 Gmail、日曆和雲端硬碟中建立情境式和非情境式介面。外掛程式會隨機顯示貓咪圖片,並在圖片上疊加文字。文字可以是首頁的靜態文字,也可以是內容相關觸發條件的主機應用程式內容。

目標

  • 設定環境。
  • 設定指令碼。
  • 執行指令碼。

必要條件

如要使用這個範例,您必須符合下列先決條件:

  • Google 帳戶 (Google Workspace 帳戶可能需要管理員核准)。
  • 可連上網際網路的網路瀏覽器。

  • Google Cloud 專案

設定環境

在 Google Cloud 控制台中開啟 Cloud 專案

如果尚未開啟,請開啟您打算用於這個範例的 Cloud 專案:

  1. 在 Google Cloud 控制台中,前往「選取專案」頁面。

    選取 Cloud 專案

  2. 選取要使用的 Google Cloud 專案。或者,按一下「建立專案」,然後按照畫面上的指示操作。建立 Google Cloud 專案後,您可能需要為專案啟用計費功能

Google Workspace 外掛程式需要設定同意畫面。設定外掛程式的 OAuth 同意畫面,可定義 Google 向使用者顯示的內容。

  1. 前往 Google Cloud 控制台,依序點選「選單」圖示 > Google Auth platform >「品牌」

    前往「品牌宣傳」

  2. 如果您已設定 Google Auth platform,可以在「品牌」、「目標對象」和「資料存取權」中設定下列 OAuth 同意畫面設定。如果看到「尚未設定」Google Auth platform 訊息,請按一下「開始使用」
    1. 在「App Information」(應用程式資訊) 下方的「App name」(應用程式名稱) 欄位中,輸入應用程式名稱。
    2. 在「使用者支援電子郵件」部分,選擇使用者有同意聲明相關問題時可與您聯絡的支援電子郵件地址。
    3. 點選 [下一步]
    4. 在「目標對象」下方,選取「內部」
    5. 點選 [下一步]
    6. 在「聯絡資訊」下方,輸入可接收專案異動通知的電子郵件地址
    7. 點選 [下一步]
    8. 在「完成」部分,請詳閱《Google API 服務:使用者資料政策》,然後勾選「我同意《Google API 服務:使用者資料政策》」
    9. 按一下 [繼續]。
    10. 按一下「Create」(建立)。
  3. 目前可以略過新增範圍。 日後建立應用程式供 Google Workspace 機構以外的使用者使用時,請務必將「使用者類型」變更為「外部」。然後新增應用程式需要的授權範圍。詳情請參閱完整的「設定 OAuth 同意畫面」指南。

設定指令碼

建立 Apps Script 專案

  1. 如要建立新的 Apps Script 專案,請前往 script.new
  2. 按一下「Untitled project」(未命名的專案)
  3. 將 Apps Script 專案重新命名為「Cats」,然後按一下「重新命名」
  4. 找出 Code.gs 檔案,然後依序點選「更多」圖示 >「重新命名」。將檔案命名為 Common
  5. 依序點選「新增檔案」圖示 >「指令碼」。將檔案命名為 Gmail
  6. 重複步驟 5,再建立 2 個名為 CalendarDrive 的指令碼檔案。 完成後,您應該會有 4 個不同的指令碼檔案。
  7. 將每個檔案的內容替換成下列對應的程式碼:

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

  8. 按一下「專案設定」專案設定圖示

  9. 勾選「在編輯器中顯示『appsscript.json』資訊清單檔案」方塊。

  10. 按一下「Editor」(編輯器) 圖示

  11. 開啟 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 專案編號

  1. 在 Google Cloud 控制台中,依序前往「選單」圖示 >「IAM 與管理」 >「設定」

    前往「IAM & Admin Settings」(IAM 與管理員設定)

  2. 在「專案編號」欄位中,複製該值。

設定 Apps Script 專案的 Cloud 專案

  1. 在 Apps Script 專案中,按一下「專案設定」圖示 專案設定圖示
  2. 在「Google Cloud Platform (GCP) 專案」下方,按一下「變更專案」
  3. 在「GCP 專案編號」中,貼上 Google Cloud 專案編號。
  4. 點選「設定專案」

安裝測試部署作業

  1. 在 Apps Script 專案中,按一下「編輯器」
  2. 依序點選「部署」>「測試部署作業」
  3. 依序點選「安裝」>「完成」

執行指令碼

  1. 前往 Gmail
  2. 如要開啟外掛程式,請按一下右側面板中的「貓咪」圖示
  3. 如果系統提示,請授權外掛程式。
  4. 外掛程式會顯示貓咪圖片和文字。如要變更圖片,請按一下「變更貓咪」
  5. 如果開啟電子郵件時外掛程式也處於開啟狀態,圖片就會重新整理,文字也會變更為電子郵件的主旨行 (如果主旨行過長,系統會截斷)。

日曆和雲端硬碟也提供類似功能。您不需要重新授權外掛程式,就能在這些主機應用程式中使用。

後續步驟