Google Chat から会議のスケジュールを設定する

コーディング レベル: 中級
所要時間: 25 分
プロジェクト タイプ: Google Chat アプリ

目標

  • ソリューションの機能を理解します。
  • ソリューション内で Apps Script サービスが何を行うかを理解します。
  • 環境をセットアップする。
  • スクリプトを設定します。
  • スクリプトを実行します。

このソリューションについて

Google Chat のダイレクト メッセージ(DM)またはスペース内から Google カレンダーで会議のスケジュールを設定します。会議の件名、開始時間、所要時間などの詳細を設定することも、インスタント会議のスケジュール設定にデフォルトの設定を使用することもできます。

Meeting Scheduler Chat アプリのダイアログ インターフェース

仕組み

Chat 用アプリのスクリプトは、スラッシュ コマンドダイアログを使用して、ユーザーから会議の詳細を取得し、カレンダーの予定をスケジュールします。スクリプトには、ニーズに合わせてカスタマイズできるデフォルトの会議設定が含まれています。

Apps Script サービス

このソリューションでは、次のサービスを使用します。

  • カレンダー サービス - 提供された会議情報からカレンダーの予定を作成します。
  • 基本サービス - Session クラスを使用して、スクリプトのタイムゾーンを取得します。カレンダーでは、予定のスケジュール設定時にこのタイムゾーンが使用されます。
  • ユーティリティ サービス - カレンダーの予定の日付の形式を設定し、予定の URL を取得するために予定の ID をエンコードします。

前提条件

環境の設定

Google Cloud コンソールで Cloud プロジェクトを開く

まだ開いていない場合は、このサンプルで使用する Cloud プロジェクトを開きます。

  1. Google Cloud コンソールで、[プロジェクトを選択] ページに移動します。

    Cloud プロジェクトを選択する

  2. 使用する Google Cloud プロジェクトを選択します。または、[プロジェクトを作成] をクリックし、画面の指示に沿って操作します。Google Cloud プロジェクトを作成する場合は、プロジェクトの課金を有効にする必要がある場合があります。

API を有効にする

Google API を使用する前に、Google Cloud プロジェクトで API を有効にする必要があります。1 つの Google Cloud プロジェクトで 1 つ以上の API を有効にできます。

すべての Chat 用アプリには、同意画面の構成が必要です。アプリの OAuth 同意画面を構成することで、Google がユーザーに表示する内容を定義し、後でアプリを公開できるようにアプリを登録します。

  1. Google Cloud コンソールで、メニュー > > [ブランディング] に移動します。

    [ブランディング] に移動

  2. をすでに構成している場合は、[ブランディング]、[ユーザー]、[データアクセス] で次の OAuth 同意画面の設定を構成できます。[ まだ設定されていません] というメッセージが表示された場合は、[使ってみる] をクリックします。
    1. [アプリ情報] の [アプリ名] に、アプリの名前を入力します。
    2. [ユーザー サポートメール] で、ユーザーが同意について問い合わせる際に使用するサポートのメールアドレスを選択します。
    3. [続行] をクリックします。
    4. [対象] で [内部] を選択します。
    5. [続行] をクリックします。
    6. [連絡先情報] で、プロジェクトに対する変更の通知を受け取るメールアドレスを入力します。
    7. [続行] をクリックします。
    8. [完了] で、Google API サービスのユーザーデータに関するポリシーを確認し、同意する場合は [Google API サービス: ユーザーデータに関するポリシーに同意します] を選択します。
    9. [続行] をクリックします。
    10. [作成] をクリックします。
  3. 現時点では、スコープの追加はスキップできます。今後、Google Workspace 組織外で使用するアプリを作成する場合は、[ユーザータイプ] を [外部] に変更する必要があります。次に、アプリに必要な認可スコープを追加します。詳細については、OAuth 同意画面を構成するの完全なガイドをご覧ください。

スクリプトを設定する

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 を構成します。
    • 名前: Meeting Scheduler
    • アバターの URL: 256×256 ピクセル以上の画像を参照する URL を追加します。
    • 説明: Quickly create meetings.
    • 機能: ユーザーがアプリに直接メッセージを送信したり、スペースに追加したりできるようにするには、両方のチェックボックスをオンにします。
    • 接続設定: [Apps Script] をクリックして、ヘッド デプロイ ID を入力します。
    • スラッシュ コマンド: 次の手順に沿って、/help/schedule_Meeting のスラッシュ コマンドを追加します。
      1. [スラッシュ コマンドを追加] をクリックし、次の情報を使用して構成します。
        • 名前: /help
        • コマンド ID: 1
        • 説明: Learn what this app does.
      2. [スラッシュ コマンドを追加] をもう一度クリックし、次の情報を使用して構成します。
        • 名前: /schedule_Meeting
        • コマンド ID: 2
        • 説明: Schedule a meeting.
        • [ダイアログを開く] チェックボックスをオンにします。
    • 権限: [ドメイン内の特定のユーザーとグループ] を選択し、メールアドレスを入力します。
  4. [保存] をクリックしてページを更新します。
  5. 設定ページの [アプリのステータス] で、ステータスを [公開 - ユーザーが利用可能] に設定します。
  6. [保存] をクリックします。

スクリプトを実行する

  1. Google Chat を開きます。
  2. [チャットを開始] をクリックします。
  3. アプリ名「Meeting Scheduler」を検索します。
  4. hello」などの最初のメッセージを送信して、承認を求めます。
  5. アプリから応答があったら、[構成] をクリックしてアプリを承認します。OAuth 同意画面に「このアプリは確認されていません」という警告が表示された場合は、[詳細] > [{プロジェクト名} に移動(安全でない)] を選択して続行します。

  6. アプリに /schedule_Meeting を送信します。

  7. ダイアログで、招待するユーザーのメールアドレスを 1 つ以上追加します。他のフィールドを更新するか、デフォルトのエントリを使用できます。

  8. [送信] をクリックします。

  9. 会議を表示するには、[カレンダーの予定を開く] をクリックします。

コードを確認する

このソリューションの Apps Script コードを確認するには、下の [ソースコードを表示] をクリックします。

ソースコードを表示

コード.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);

}

次のステップ