Apps Script로 Google Workspace 부가기능 빌드

이 빠른 시작에서는 홈페이지, 문맥 트리거, 서드 파티 API 연결을 보여주는 간단한 Google Workspace 부가기능을 만듭니다.

이 부가기능은 Gmail, Calendar, Drive에서 문맥 인터페이스와 비문맥 인터페이스를 만듭니다. 이 부가기능은 고양이의 임의 이미지를 표시하고 이미지 위에 텍스트를 오버레이합니다. 텍스트는 홈페이지의 경우 정적 텍스트이거나 문맥 트리거의 경우 호스트 애플리케이션 컨텍스트에서 가져옵니다.

목표

  • 환경을 설정합니다.
  • 스크립트를 설정합니다.
  • 스크립트를 실행합니다.

기본 요건

이 샘플을 사용하려면 다음 기본 요건이 필요합니다.

  • 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 콘솔에서 메뉴 > API 및 서비스 > OAuth 동의 화면으로 이동합니다.

    OAuth 동의 화면으로 이동

  2. 사용자 유형에서 내부를 선택한 다음 만들기를 클릭합니다.
  3. 앱 등록 양식을 작성한 후 저장 및 계속을 클릭합니다.
  4. 지금은 범위 추가를 건너뛰고 저장하고 계속을 클릭해도 됩니다. 향후 Google Workspace 조직 외부에서 사용할 앱을 만들 때는 사용자 유형외부로 변경한 후 앱에 필요한 승인 범위를 추가해야 합니다.

  5. 앱 등록 요약을 검토합니다. 변경하려면 수정을 클릭합니다. 앱 등록이 올바른 것 같으면 대시보드로 돌아가기를 클릭합니다.

스크립트 설정

Apps Script 프로젝트 만들기

  1. 새 Apps Script 프로젝트를 만들려면 script.new로 이동합니다.
  2. 제목 없는 프로젝트를 클릭합니다.
  3. Apps Script 프로젝트 이름을 고양이로 바꾸고 이름 바꾸기를 클릭합니다.
  4. Code.gs 파일 옆에 있는 더보기 > 이름 바꾸기를 클릭합니다. 파일 이름을 Common로 지정합니다.
  5. 파일 추가 > 스크립트를 클릭합니다. 파일 이름을 Gmail로 지정합니다.
  6. 5단계를 반복하여 CalendarDrive라는 스크립트 파일을 2개 더 만듭니다. 완료하면 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. 편집기 를 클릭합니다.

  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 & 관리자 설정으로 이동

  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. 부가기능이 열려 있는 동안 이메일을 열면 이미지가 새로고침되고 텍스트가 이메일의 제목으로 변경됩니다(너무 길면 잘림).

Calendar 및 Drive에서도 유사한 기능을 확인할 수 있습니다. 이러한 호스트 앱에서 부가기능을 사용하기 위해 부가기능을 다시 승인할 필요는 없습니다.

다음 단계