جدولة الاجتماعات من Google Chat

مستوى الترميز: متوسط
المدة: 25 دقيقة
نوع المشروع: تطبيق Google Chat

الأهداف

  • فهم وظيفة الحلّ
  • فهم وظائف خدمات "برمجة تطبيقات Google" ضمن الحلّ
  • إعداد البيئة
  • إعداد النص البرمجي
  • شغِّل النص البرمجي.

لمحة عن هذا الحلّ

تحديد موعد اجتماع في "تقويم Google" من داخل رسالة مباشرة أو مساحة في Google Chat يمكنك ضبط تفاصيل معيّنة للاجتماع، مثل الموضوع أو وقت البدء أو المدة، أو استخدام الإعدادات التلقائية لتحديد موعد اجتماع فوري.

واجهة الحوار في تطبيق "أداة جدولة الاجتماعات" على Chat

آلية العمل

يستخدم نص برمجي لتطبيق Chat أوامر تبدأ بشرطة مائلة ومربّعات حوار للحصول على تفاصيل الاجتماع من المستخدمين وجدولة حدث في التقويم. يتضمّن النص البرمجي إعدادات تلقائية للاجتماع يمكن تخصيصها لتناسب احتياجاتك.

خدمات "برمجة تطبيقات Google"

يستخدم هذا الحل الخدمات التالية:

  • خدمة التقويم: تنشئ حدث في التقويم من معلومات الاجتماع المقدَّمة.
  • الخدمة الأساسية: تستخدِم الفئة Session للحصول على المنطقة الزمنية للبرنامج النصي. يستخدم "تقويم Google" هذه المنطقة الزمنية عند جدولة الحدث.
  • خدمة الأدوات المساعدة: تعمل هذه الخدمة على تنسيق التاريخ الخاص بحدث في التقويم وترميز رقم تعريف الحدث للمساعدة في الحصول على عنوان URL الخاص بالحدث.

المتطلبات الأساسية

إعداد البيئة

افتح مشروعك على السحابة الإلكترونية في Google Cloud Console

افتح مشروع على السحابة الإلكترونية الذي تريد استخدامه لهذا النموذج إذا لم يكن مفتوحًا من قبل:

  1. في Google Cloud Console، انتقِل إلى صفحة اختيار مشروع.

    اختيار مشروع على السحابة الإلكترونية

  2. اختَر مشروع Google Cloud الذي تريد استخدامه. يمكنك أيضًا النقر على إنشاء مشروع واتّباع التعليمات الظاهرة على الشاشة. في حال إنشاء مشروع على Google Cloud، قد تحتاج إلى تفعيل الفوترة للمشروع.

تفعيل واجهة برمجة التطبيقات

قبل استخدام واجهات Google APIs، عليك تفعيلها في مشروع على Google Cloud. يمكنك تفعيل واجهة برمجة تطبيق واحدة أو أكثر في مشروع واحد على Google Cloud.

تتطلّب جميع تطبيقات Chat ضبط إعدادات شاشة طلب الموافقة. يؤدي ضبط شاشة طلب الموافقة المتعلّقة ببروتوكول OAuth في تطبيقك إلى تحديد ما تعرضه Google للمستخدمين وتسجيل تطبيقك حتى تتمكّن من نشره لاحقًا.

  1. في Google API Console، انتقِل إلى القائمة > منصة Google Auth > العلامة التجارية.

    الانتقال إلى "هوية العلامة التجارية"

  2. إذا سبق لك ضبط منصة Google Auth، يمكنك ضبط إعدادات شاشة طلب الموافقة المتعلّقة ببروتوكول OAuth التالية في العلامة التجارية والجمهور والوصول إلى البيانات. إذا ظهرت لك رسالة لم يتم إعداد منصة Google Auth بعد، انقر على البدء:
    1. ضمن معلومات التطبيق، في اسم التطبيق، أدخِل اسمًا للتطبيق.
    2. في حقل البريد الإلكتروني لدعم المستخدمين، اختَر عنوان بريد إلكتروني مخصّصًا للدعم يمكن للمستخدمين التواصل معك من خلاله إذا كانت لديهم أسئلة حول موافقتهم.
    3. انقر على التالي.
    4. ضمن الجمهور، اختَر داخلي.
    5. انقر على التالي.
    6. ضمن معلومات الاتصال، أدخِل عنوان بريد إلكتروني يمكنك تلقّي إشعارات عليه بشأن أي تغييرات في مشروعك.
    7. انقر على التالي.
    8. ضمن إنهاء، راجِع سياسة بيانات المستخدمين في خدمات Google API، وإذا كنت موافقًا عليها، ضَع علامة في المربّع أوافق على "سياسة بيانات المستخدمين في خدمات Google API".
    9. انقر على متابعة.
    10. انقر على إنشاء.
  3. يمكنك حاليًا تخطّي إضافة النطاقات. في المستقبل، عند إنشاء تطبيق للاستخدام خارج مؤسسة Google Workspace، عليك تغيير نوع المستخدم إلى خارجي. بعد ذلك، أضِف نطاقات التفويض التي يتطلّبها تطبيقك. لمزيد من المعلومات، يُرجى الاطّلاع على دليل ضبط موافقة OAuth الكامل.

إعداد النص البرمجي

إنشاء مشروع "برمجة تطبيقات Google"

  1. انقر على الزر التالي لفتح مشروع جدولة الاجتماعات من Google Chat في برمجة تطبيقات.
    افتح المشروع
  2. انقر على نظرة عامة .
  3. في صفحة النظرة العامة، انقر على "إنشاء نسخة" رمز إنشاء نسخة.

في المستقبل، إذا أردت استخدام Google APIs معيّنة أو نشر تطبيقك، عليك ربط مشروعك على السحابة الإلكترونية بمشروعك على "برمجة التطبيقات". ليس عليك إجراء ذلك في هذا الدليل. لمزيد من المعلومات، راجِع دليل مشاريع Google Cloud.

إنشاء عملية نشر تجريبية

  1. في مشروع برمجة تطبيقات الذي نسخته، انقر على نشر > عمليات النشر التجريبية.
  2. انسخ معرّف نشر العنوان لاستخدامه في خطوة لاحقة، ثم انقر على تم.

ضبط إعدادات Chat API

  1. في Google API Console، انتقِل إلى صفحة Chat API.
    الانتقال إلى Chat API
  2. انقر على الإعداد.
  3. اضبط إعدادات Chat API باستخدام المعلومات التالية:
    • الاسم: Meeting Scheduler
    • عنوان URL الخاص بالصورة الرمزية: https://goo.gle/3SfMkjb.
    • الوصف: Quickly create meetings.
    • الوظائف: ضَع علامة في كلا المربّعين للسماح للمستخدمين بمراسلة التطبيق مباشرةً وإضافته إلى المساحات.
    • إعدادات الربط: انقر على برمجة تطبيقات وأدخِل رقم تعريف النشر الرئيسي.
    • الأوامر: أضِف أوامر تبدأ بشرطة مائلة لـ /help و/schedule_Meeting باتّباع الخطوات التالية:
      1. انقر على إضافة أمر واضبطه باستخدام المعلومات التالية:
        • معرّف الأمر: 1
        • الوصف: Learn what this app does.
        • نوع الأمر: Slash command
        • اسم الأمر الذي يبدأ بشرطة مائلة: /help.
      2. انقر على إضافة أمر مرة أخرى واضبطه باستخدام المعلومات التالية:
        • معرّف الأمر: 2
        • الوصف: Schedule a meeting.
        • نوع الأمر: Slash command.
        • اسم الأمر الذي يبدأ بشرطة مائلة: /schedule_Meeting.
        • ضَع علامة في المربّع يفتح مربّع حوار.
    • الأذونات: اختَر أشخاص ومجموعات محدّدة في نطاقك وأدخِل عنوان بريدك الإلكتروني.
  4. انقر على حفظ.

تشغيل النص البرمجي

  1. افتح Google Chat.

    الانتقال إلى Google Chat

  2. انقر على رمز بدء محادثة .

  3. ابحث عن اسم التطبيق، Meeting Scheduler.

  4. أرسِل رسالة أولية، مثل hello، لطلب التفويض.

  5. عندما يردّ التطبيق، انقر على ضبط وامنح التطبيق الإذن بالوصول إلى البيانات. إذا عرضت شاشة موافقة بروتوكول OAuth التحذير لم يتم التحقّق من هذا التطبيق، تابِع من خلال النقر على خيارات متقدمة > الانتقال إلى {اسم المشروع} (غير آمن).

  6. أرسِل /schedule_Meeting إلى التطبيق.

  7. في مربّع الحوار، أضِف عنوان بريد إلكتروني واحدًا على الأقل للمدعوين. يمكنك تعديل الحقول الأخرى أو استخدام الإدخالات التلقائية.

  8. انقر على إرسال.

  9. لعرض الاجتماع، انقر على فتح حدث في التقويم.

مراجعة الرمز البرمجي

انقر لمراجعة رمز برمجة تطبيقات لهذا الحل المستضاف على GitHub:

عرض على GitHub

لمراجعة رمز Apps Script الخاص بهذا الحل بشكل مضمّن، انقر على عرض رمز المصدر:

عرض رمز المصدر

Code.gs

apps-script/chat/schedule-meetings/Code.gs
// To learn how to use this script, refer to the documentation:
// https://developers.google.com/workspace/add-ons/samples/tutorial-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.
*/

// Command IDs
const COMMANDS = {
  HELP: 1, // /help
  DIALOG: 2, // /schedule_Meeting
};

/**
 * Responds to a added to space event in Google Chat.
 *
 * @param {Object} event The event object from the Google Workspace add-on.
 * @return {Object} Response from the Chat app.
 */
function onAddedToSpace(event) {
  // Lets users know what they can do and how they can get help.
  return { hostAppDataAction: { chatDataAction: { createMessageAction: { message: {
    text: `Hi there! I can quickly schedule a meeting for you with just a few clicks. Try me out by typing */schedule_Meeting*. To learn what else I can do, type */help*.`
  }}}}};
}

/**
 * Responds to a message event in Google Chat.
 *
 * @param {Object} event The event object from the Google Workspace add-on.
 * @return {Object} Response from the Chat app.
 */
function onMessage(event) {
  // Displays help message to users.
  return getHelpTextResponse_();
}

/**
 * Responds to an app command event in Google Chat.
 *
 * @param {Object} event The event object from the Google Workspace add-on.
 * @return {Object} Response from the Chat app.
 */
function onAppCommand(event) {
  // Executes the app command logic based on ID.
  switch (event.chat.appCommandPayload.appCommandMetadata.appCommandId) {
    case COMMANDS.DIALOG:
      // Displays meeting dialog for /schedule_Meeting.
      return getInputFormAsDialog_({
        invitee: "",
        startTime: getTopOfHourDateString_(),
        duration: 30,
        subject: "Status Stand-up",
        body: "Scheduling a quick status stand-up meeting.",
      });
    case COMMANDS.HELP:
      // Displays help message for /help.
      return getHelpTextResponse_();
  }
}

/**
 * Handles the form submission from the meeting scheduling dialog.
 * 
 * @param {Object} event The event object from the Google Workspace add-on.
 * @return {Object} Response from the Chat app.
 */
function handleFormSubmit(event) {
  const recipients = getFieldValue_(event.commonEventObject.formInputs, "email");
  const subject = getFieldValue_(event.commonEventObject.formInputs, "subject");
  const body = getFieldValue_(event.commonEventObject.formInputs, "body");

  // Assumes dialog card inputs for date and times are in the correct format. mm/dd/yyy HH:MM
  const dateTimeInput = getFieldValue_(event.commonEventObject.formInputs, "date");
  const startTime = getStartTimeAsDateObject_(dateTimeInput);
  const duration = Number(
    getFieldValue_(event.commonEventObject.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 || Number.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);
}

/**
 * Handles the form submission from the meeting scheduling dialog confirmation.
 * 
 * @param {Object} event The event object from the Google Workspace add-on.
 * @return {Object} Response from the Chat app.
 */
function closeDialog(event) {
  return { action: {
    navigations: [{ endNavigation: { action: "CLOSE_DIALOG"}}],
    notification: { text: "Success!" }
  }};
}

/**
 * Gets the help text message creation response.
 * 
 * @return {Object} Response from the Chat app.
 */
function getHelpTextResponse_() {
  const help = `*Meeting Scheduler* 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 Chat apps built as Google Workspace add-on at https://developers.google.com/workspace/add-ons/chat.`;

  return { hostAppDataAction: { chatDataAction: { createMessageAction: { message: {
    text: help
  }}}}};
}

Dialog.gs

apps-script/chat/schedule-meetings/Dialog.gs
/**
 * 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.
 */

/**
 * Gets the open / update dialog response for the input form.
 * 
 * @return {Object} Response from the Chat app.
 */
function getInputFormAsDialog_(options) {
  const form = getForm_(options);
  return { action: { navigations: [{
    pushCard: form
  }]}};
}

/**
 * Generates the meeting scheduling form as JSON based on given options.
 * 
 * @return {Object} JSON-formatted card.
 */
function getForm_(options) {
  const sections = [];

  // If errors present, display additional section with validation messages.
  if (options.errors?.length) {
    let errors = options.errors.reduce((str, err) => `${str}• ${err}<br>`, "");
    errors = `<font color="#ba0000"><b>Errors:</b><br>${errors}</font>`;
    sections.push({ widgets: [{
      textParagraph: { text: errors }
    }]});
  }
  // Main form section.
  sections.push({
    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,
  });
  // Build the card with sections.
  return {
    sections: sections,
    name: "Google Chat Scheduled Meeting",
    fixedFooter: { primaryButton: {
      text: "Submit",
      onClick: { action: { function: "handleFormSubmit" }},
      altText: "Submit"
    }}
  };
}

/**
 * Gets the open / update dialog response for the confirmation form.
 * 
 * @param {string} url The Google Calendar Event url.
 * @return {Object} Response from the Chat app.
 */
function getConfirmationDialog_(url) {
  return { action: { navigations: [{ pushCard: {
    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

apps-script/chat/schedule-meetings/Utilities.gs
/**
 * 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.
 * 
 * @param {object} formInputs The form inputs object from the event
 * @param {string} fieldName The name of the field to get the value for
 * @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.
 * @param {string} dateTimeStr The date/time string in mm/dd/yyyy HH:MM format
 * 
 * @return {date} Date object representing the input date/time or null if invalid
 */
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://google.com/calendar/event?eid={event-id}'
 */
function getCalendarEventURL_(event, cal) {
  const baseCalUrl = "https://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;
}

الخطوات التالية

تطبيقات Chat التي ليست إضافات: جدولة الاجتماعات من Google Chat

تنطبق التعليمات وعيّنات التعليمات البرمجية التالية على تطبيق Chat ليس إضافة Google Workspace (عرض على GitHub).

إعداد مشروع "برمجة تطبيقات Google"

  1. افتح مشروع جدولة الاجتماعات من Google Chat في "برمجة تطبيقات Google" لتطبيق Chat ليس إضافة، ثم أنشئ نسخة منه.
  2. عند ضبط إعدادات Chat API في Google API Console، ألغِ تحديد إنشاء تطبيق Chat هذا كإضافة في Google Workspace. سيظهر مربّع حوار يطلب منك التأكيد. في مربّع الحوار، انقر على إيقاف.

رمز مصدر

Code.gs

apps-script/schedule-meetings/Code.js
// To learn how to use this script, refer to the documentation:
// https://developers.google.com/workspace/chat/tutorial-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);
      */
    }
  }
  // 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/workspace/chat/receive-respond-interactions
 */
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 || Number.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);
  }
  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

apps-script/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?.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);
  }
  const 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

apps-script/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;
}