Google Chat에서 회의 예약하기

코딩 수준: 중급
시간: 25분
프로젝트 유형: Google Chat 앱

목표

  • 솔루션의 기능을 이해합니다.
  • 애플리케이션 내에서 Apps Script 서비스가 수행하는 작업을 이해 솔루션을 제공합니다
  • 환경을 설정합니다.
  • 스크립트를 설정합니다.
  • 스크립트를 실행합니다.

이 솔루션 정보

채팅 메시지 (DM) 또는 스페이스에 추가합니다. 다음과 같은 회의 관련 세부정보를 설정할 수 있습니다. 제목, 시작 시간 또는 지속 시간을 지정하거나 기본 설정을 사용하여 즉시 회의 일정 예약

Meeting Scheduler Chat 앱의 대화상자 인터페이스

작동 방식

Chat 앱 스크립트는 명령어 대화상자를 사용하여 캘린더 일정을 예약합니다. 이 스크립트에는 사용자의 필요에 맞게 맞춤설정할 수 있는 기본 회의 설정이 포함되어 있습니다. 제공합니다

Apps Script 서비스

이 솔루션은 다음 서비스를 사용합니다.

  • 캘린더 서비스: 캘린더 일정을 삭제합니다.
  • 기본 서비스: Session 클래스를 사용하여 스크립트의 시간대입니다. Calendar는 다음 경우에 이 시간대를 사용합니다. 일정을 예약하는 것입니다.
  • 유틸리티 서비스: 이벤트 URL을 가져올 수 있도록 이벤트 ID를 인코딩합니다.

기본 요건

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

  • Google 계정 (Google Workspace 계정은 관리자의 승인이 필요함)
  • 인터넷에 액세스할 수 있는 웹브라우저
  • Google Cloud 프로젝트

환경 설정

Google Cloud 콘솔에서 Cloud 프로젝트 열기

아직 열려 있지 않으면 사용할 Cloud 프로젝트를 엽니다. 다음을 참조하세요.

  1. Google Cloud 콘솔에서 프로젝트 선택 페이지로 이동합니다.

    Cloud 프로젝트 선택

  2. 사용할 Google Cloud 프로젝트를 선택합니다. 또는 프로젝트 만들기를 클릭하고 화면에 표시된 안내를 따릅니다. Google Cloud 프로젝트를 만드는 경우 프로젝트에 결제를 사용 설정해야 할 수도 있습니다.

API 사용 설정

Google API를 사용하려면 먼저 Google Cloud 프로젝트에서 사용 설정해야 합니다. 단일 Google Cloud 프로젝트에서 하나 이상의 API를 사용 설정할 수 있습니다.
  • Cloud 프로젝트에서 Google Chat API를 사용 설정합니다.

    API 사용 설정

모든 채팅 앱에는 동의 화면 구성이 필요합니다. 구성 앱의 OAuth 동의 화면에는 Google에서 사용자에게 표시하는 내용과 나중에 게시할 수 있도록 앱을 등록합니다.

  1. Google Cloud 콘솔에서 메뉴 로 이동합니다. > API 및 서비스 > OAuth 동의 화면.

    OAuth 동의 화면으로 이동

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

  5. 앱 등록 요약을 검토합니다. 변경하려면 수정을 클릭합니다. 앱이 등록이 확인되면 대시보드로 돌아가기를 클릭합니다.

스크립트 설정

Apps Script 프로젝트 만들기

  1. 다음 버튼을 클릭하여 Google Chat에서 회의 예약을 엽니다. Apps Script 프로젝트.
    프로젝트 열기
  2. 개요 를 클릭합니다.
  3. 개요 페이지에서 사본 만들기 사본 만들기 아이콘를 클릭합니다.

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. 이후 단계에서 사용할 헤드 배포 ID를 복사하고 완료를 클릭합니다.

Chat API 구성

  1. Google Cloud 콘솔에서 Chat API 페이지로 이동합니다.
    Chat API로 이동
  2. 구성을 클릭합니다.
  3. 다음 정보를 사용하여 Chat API를 구성합니다. <ph type="x-smartling-placeholder">
      </ph>
    • 이름: Meeting Scheduler
    • 아바타 URL: 최소 크기가 256x256픽셀
    • 설명: Quickly create meetings.
    • 기능: 사용자가 앱에 직접 메시지를 보낼 수 있도록 두 체크박스를 모두 선택합니다. 스페이스에 추가합니다.
    • 연결 설정: Apps Script를 클릭하고 헤드 배포 ID입니다.
    • 슬래시 명령어: /help/schedule_Meeting 슬래시 명령어를 추가합니다. 방법은 다음과 같습니다. <ph type="x-smartling-placeholder">
        </ph>
      1. 슬래시 명령어 추가를 클릭하고 다음과 같이 구성합니다. 있습니다. <ph type="x-smartling-placeholder">
          </ph>
        • 이름: /help
        • 명령어 ID: 1
        • 설명: Learn what this app does.
      2. 슬래시 명령어 추가를 다시 클릭하고 다음과 같이 구성합니다. 있습니다. <ph type="x-smartling-placeholder">
          </ph>
        • 이름: /schedule_Meeting
        • 명령어 ID: 2
        • 설명: Schedule a meeting.
        • 대화상자 열기 체크박스를 선택합니다.
    • 권한: 내 도메인의 특정 사용자 및 그룹을 선택하고 이메일 주소를 입력합니다.
  4. Save(저장)를 클릭하고 페이지를 새로고침합니다.
  5. 구성 페이지의 앱 상태에서 상태를 설정합니다. 실시간 - 사용자에게 제공됩니다.
  6. 저장을 클릭합니다.

스크립트 실행

  1. Google Chat을 엽니다.
  2. 채팅 시작 을 클릭합니다.
  3. 앱 이름(Meeting Scheduler)을 검색합니다.
  4. 승인을 위해 첫 메시지(예: hello)를 전송합니다.
  5. 앱이 답장하면 구성을 클릭하고 앱을 승인합니다. OAuth 동의 화면에 확인되지 않은 앱입니다.라는 경고가 표시되면, 고급 &gt;을 선택하여 계속합니다. {프로젝트 이름}(으)로 이동(안전하지 않음)

  6. 앱에 /schedule_Meeting를 보냅니다.

  7. 대화상자에서 초대 대상자의 이메일 주소를 하나 이상 추가합니다. 이 기본 항목을 사용합니다.

  8. 제출을 클릭합니다.

  9. 회의를 보려면 Calendar 일정 열기를 클릭합니다.

코드 검토

이 솔루션의 Apps Script 코드를 검토하려면 다음을 클릭합니다. 아래에서 소스 코드 보기:

소스 코드 보기

Code.gs

solutions/schedule-meetings/Code.js
// To learn how to use this script, refer to the documentation:
// https://developers.google.com/apps-script/samples/chat-apps/schedule-meetings

/*
Copyright 2022 Google LLC

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// Application constants
const APPNAME = 'Chat Meeting Scheduler';
const SLASHCOMMAND = {
  HELP: 1, // /help
  DIALOG: 2, // /schedule_Meeting
};

/**
 * Responds to an ADDED_TO_SPACE event in Google Chat.
 * Called when the Chat app is added to a space. The Chat app can either be directly added to the space
 * or added by a @mention. If the Chat app is added by a @mention, the event object includes a message property. 
 * Returns a Message object, which is usually a welcome message informing users about the Chat app.
 *
 * @param {Object} event The event object from Google Chat
 */
function onAddToSpace(event) {
  let message = '';

  // Personalizes the message depending on how the Chat app is called.
  if (event.space.singleUserBotDm) {
    message = `Hi ${event.user.displayName}!`;
  } else {
    const spaceName = event.space.displayName ? event.space.displayName : "this chat";
    message = `Hi! Thank you for adding me to ${spaceName}`;
  }

  // Lets users know what they can do and how they can get help.
  message = message + '/nI can quickly schedule a meeting for you with just a few clicks.' +
    'Try me out by typing */schedule_Meeting*. ' +
    '/nTo learn what else I can do, type */help*.'

  return { "text": message };
}

/**
 * Responds to a MESSAGE event triggered in Chat.
 * Called when the Chat app is already in the space and the user invokes it via @mention or / command.
 * Returns a message object containing the Chat app's response. For this Chat app, the response is either the
 * help text or the dialog to schedule a meeting.
 * 
 * @param {object} event The event object from Google Chat
 * @return {object} JSON-formatted response as text or Card message
 */
function onMessage(event) {

  // Handles regular onMessage logic.
  // Evaluates if and handles for all slash commands.
  if (event.message.slashCommand) {
    switch (event.message.slashCommand.commandId) {

      case SLASHCOMMAND.DIALOG: // Displays meeting dialog for /schedule_Meeting.

        // TODO update this with your own logic to set meeting recipients, subjects, etc (e.g. a group email).
        return getInputFormAsDialog_({
          invitee: '',
          startTime: getTopOfHourDateString_(),
          duration: 30,
          subject: 'Status Stand-up',
          body: 'Scheduling a quick status stand-up meeting.'
        });

      case SLASHCOMMAND.HELP: // Responds with help text for /help.
        return getHelpTextResponse_();

      /* TODO Add other use cases here. E.g:
      case SLASHCOMMAND.NEW_FEATURE:  // Your Feature Here
        getDialogForAddContact(message);
      */

    }
  }
  else {
    // Returns text if users didn't invoke a slash command.
    return { text: 'No action taken - use Slash Commands.' }
  }
}

/**
 * Responds to a CARD_CLICKED event triggered in Chat.
 * @param {object} event the event object from Chat
 * @return {object} JSON-formatted response
 * @see https://developers.google.com/chat/api/guides/message-formats/events
 */
function onCardClick(event) {
  if (event.action.actionMethodName === 'handleFormSubmit') {
    const recipients = getFieldValue_(event.common.formInputs, 'email');
    const subject = getFieldValue_(event.common.formInputs, 'subject');
    const body = getFieldValue_(event.common.formInputs, 'body');

    // Assumes dialog card inputs for date and times are in the correct format. mm/dd/yyy HH:MM
    const dateTimeInput = getFieldValue_(event.common.formInputs, 'date');
    const startTime = getStartTimeAsDateObject_(dateTimeInput);
    const duration = Number(getFieldValue_(event.common.formInputs, 'duration'));

    // Handles instances of missing or invalid input parameters.
    const errors = [];

    if (!recipients) {
      errors.push('Missing or invalid recipient email address.');
    }
    if (!subject) {
      errors.push('Missing subject line.');
    }
    if (!body) {
      errors.push('Missing event description.');
    }
    if (!startTime) {
      errors.push('Missing or invalid start time.');
    }
    if (!duration || isNaN(duration)) {
      errors.push('Missing or invalid duration');
    }
    if (errors.length) {
      // Redisplays the form if missing or invalid inputs exist.
      return getInputFormAsDialog_({
        errors,
        invitee: recipients,
        startTime: dateTimeInput,
        duration,
        subject,
        body
      });
    }

    //  Calculates the end time via duration.
    const endTime = new Date(startTime.valueOf());
    endTime.setMinutes(endTime.getMinutes() + duration);

    // Creates calendar event with notification.
    const calendar = CalendarApp.getDefaultCalendar()
    const scheduledEvent = calendar.createEvent(subject,
      startTime,
      endTime,
      {
        guests: recipients,
        sendInvites: true,
        description: body + '\nThis meeting scheduled by a Google Chat App!'
      });

    // Gets a link to the Calendar event.
    const url = getCalendarEventURL_(scheduledEvent, calendar)

    return getConfirmationDialog_(url);

  } else if (event.action.actionMethodName === 'closeDialog') {

    // Returns this dialog as success.
    return {
      actionResponse: {
        type: 'DIALOG',
        dialog_action: {
          actionStatus: 'OK'
        }
      }
    }
  }
}

/**
 * Responds with help text about this Chat app.
 * @return {string} The help text as seen below
 */
function getHelpTextResponse_() {
  const help = `*${APPNAME}* lets you quickly create meetings from Google Chat. Here\'s a list of all its commands:
  \`/schedule_Meeting\`  Opens a dialog with editable, preset parameters to create a meeting event
  \`/help\`  Displays this help message

  Learn more about creating Google Chat apps at https://developers.google.com/chat.`

  return { 'text': help }
}

Dialog.gs

solutions/schedule-meetings/Dialog.js
/**
 * Copyright 2022 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
* Form input dialog as JSON.
* @return {object} JSON-formatted cards for the dialog.
*/
function getInputFormAsDialog_(options) {
  const form = getForm_(options);
  return {
    'actionResponse': {
      'type': 'DIALOG',
      'dialogAction': {
        'dialog': {
          'body': form
        }
      }
    }
  };
}

/**
* Form JSON to collect inputs regarding the meeting.
* @return {object} JSON-formatted cards.
*/
function getForm_(options) {
  const sections = [];

  // If errors present, display additional section with validation messages.
  if (options.errors && options.errors.length) {
    let errors = options.errors.reduce((str, err) => `${str}• ${err}<br>`, '');
    errors = `<b>Errors:</b><br><font color="#ba0000">${errors}</font>`;
    const errorSection = {
      'widgets': [
        {
          textParagraph: {
            text: errors
          }
        }
      ]
    }
    sections.push(errorSection);
  }
  let formSection = {
    'header': 'Schedule meeting and send email to invited participants',
    'widgets': [
      {
        'textInput': {
          'label': 'Event Title',
          'type': 'SINGLE_LINE',
          'name': 'subject',
          'value': options.subject
        }
      },
      {
        'textInput': {
          'label': 'Invitee Email Address',
          'type': 'SINGLE_LINE',
          'name': 'email',
          'value': options.invitee,
          'hintText': 'Add team group email'
        }
      },
      {
        'textInput': {
          'label': 'Description',
          'type': 'MULTIPLE_LINE',
          'name': 'body',
          'value': options.body
        }
      },
      {
        'textInput': {
          'label': 'Meeting start date & time',
          'type': 'SINGLE_LINE',
          'name': 'date',
          'value': options.startTime,
          'hintText': 'mm/dd/yyyy H:MM'
        }
      },
      {
        'selectionInput': {
          'type': 'DROPDOWN',
          'label': 'Meeting Duration',
          'name': 'duration',
          'items': [
            {
              'text': '15 minutes',
              'value': '15',
              'selected': options.duration === 15
            },
            {
              'text': '30 minutes',
              'value': '30',
              'selected': options.duration === 30
            },
            {
              'text': '45 minutes',
              'value': '45',
              'selected': options.duration === 45
            },
            {
              'text': '1 Hour',
              'value': '60',
              'selected': options.duration === 60
            },
            {
              'text': '1.5 Hours',
              'value': '90',
              'selected': options.duration === 90
            },
            {
              'text': '2 Hours',
              'value': '120',
              'selected': options.duration === 120
            }
          ]
        }
      }
    ],
    'collapsible': false
  };
  sections.push(formSection);
  const card =  {
    'sections': sections,
    'name': 'Google Chat Scheduled Meeting',
    'fixedFooter': {
      'primaryButton': {
        'text': 'Submit',
        'onClick': {
          'action': {
            'function': 'handleFormSubmit'
          }
        },
        'altText': 'Submit'
      }
    }
  };
  return card;
}

/**
* Confirmation dialog after a calendar event is created successfully.
* @param {string} url The Google Calendar Event url for link button
* @return {object} JSON-formatted cards for the dialog
*/
function getConfirmationDialog_(url) {
  return {
    'actionResponse': {
      'type': 'DIALOG',
      'dialogAction': {
        'dialog': {
          'body': {
            'sections': [
              {
                'widgets': [
                  {
                    'textParagraph': {
                      'text': 'Meeting created successfully!'
                    },
                    'horizontalAlignment': 'CENTER'
                  },
                  {
                    'buttonList': {
                      'buttons': [
                        {
                          'text': 'Open Calendar Event',
                          'onClick': {
                            'openLink': {
                              'url': url
                            }
                          }
                        }

                      ]
                    },
                    'horizontalAlignment': 'CENTER'
                  }
                ]
              }
            ],
            'fixedFooter': {
              'primaryButton': {
                'text': 'OK',
                'onClick': {
                  'action': {
                    'function': 'closeDialog'
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

Utilities.gs

solutions/schedule-meetings/Utilities.js
/**
 * Copyright 2022 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
* Helper function that gets the field value from the given form input.
* @return {string} 
*/
function getFieldValue_(formInputs, fieldName) {
  return formInputs[fieldName][''].stringInputs.value[0];
}

// Regular expression to validate the date/time input.
const DATE_TIME_PATTERN = /\d{1,2}\/\d{1,2}\/\d{4}\s+\d{1,2}:\d\d/;

/**
* Casts date and time from string to Date object.
* @return {date} 
*/
function getStartTimeAsDateObject_(dateTimeStr) {
  if (!dateTimeStr || !dateTimeStr.match(DATE_TIME_PATTERN)) {
    return null;
  }

  const parts = dateTimeStr.split(' ');
  const [month, day, year] = parts[0].split('/').map(Number);
  const [hour, minute] = parts[1].split(':').map(Number);


  Session.getScriptTimeZone()

  return new Date(year, month - 1, day, hour, minute)
}

/** 
* Gets the current date and time for the upcoming top of the hour (e.g. 01/25/2022 18:00).
* @return {string} date/time in mm/dd/yyy HH:MM format needed for use by Calendar
*/
function getTopOfHourDateString_() {
  const date = new Date();
  date.setHours(date.getHours() + 1);
  date.setMinutes(0, 0, 0);
  // Adding the date as string might lead to an incorrect response due to time zone adjustments.
  return Utilities.formatDate(date, Session.getScriptTimeZone(), 'MM/dd/yyyy H:mm');
}


/** 
* Creates the URL for the Google Calendar event.
*
* @param {object} event The Google Calendar Event instance
* @param {object} cal The associated Google Calendar 
* @return {string} URL in the form of 'https://www.google.com/calendar/event?eid={event-id}'
*/
function getCalendarEventURL_(event, cal) {
  const baseCalUrl = 'https://www.google.com/calendar';
  // Joins Calendar Event Id with Calendar Id, then base64 encode to derive the event URL.
  let encodedId = Utilities.base64Encode(event.getId().split('@')[0] + " " + cal.getId()).replace(/\=/g, '');
  encodedId = `/event?eid=${encodedId}`;
  return (baseCalUrl + encodedId);

}

참여자

이 샘플은 Google에서 Google Developer Experts의 도움을 받아 유지관리합니다.

다음 단계