איך קובעים פגישות ב-Google Chat

רמת קידוד: בינונית
משך הזמן: 25 דקות
סוג הפרויקט: אפליקציה ל-Google Chat

מטרות

  • להבין מה הפתרון עושה.
  • הסבר על הפעולות של שירותי Apps Script בפתרון.
  • מגדירים את הסביבה.
  • מגדירים את הסקריפט.
  • מריצים את הסקריפט.

מידע על הפתרון הזה

אתם יכולים לקבוע פגישה ביומן Google מתוך צ'אט ישיר או מתוך מרחב ב-Google Chat. אתם יכולים להגדיר פרטים ספציפיים לפגישה, כמו נושא, שעת התחלה או משך, או להשתמש בהגדרות ברירת המחדל לתזמון פגישה מיידית.

ממשק תיבת הדו-שיח של אפליקציית Chat לתזמון פגישות

איך זה עובד

הסקריפט של אפליקציית Chat משתמש בפקודות סלאש ובתיבות דו-שיח כדי לקבל פרטים על פגישות מהמשתמשים ולתזמן אירוע ביומן. הסקריפט כולל הגדרות פגישה שמוגדרות כברירת מחדל, שאפשר להתאים אישית לפי הצורך.

שירותי Apps Script

הפתרון הזה משתמש בשירותים הבאים:

  • שירות היומן – יוצר את האירוע ביומן מפרטי הפגישה שסופקו.
  • שירות בסיסי – נעשה שימוש במחלקה Session כדי לקבל את אזור הזמן של הסקריפט. היומן משתמש באזור הזמן הזה כשמגדירים את האירוע.
  • שירות כלי עזר – מעצב את התאריך של האירוע ביומן ומקודד את מזהה האירוע כדי לקבל את כתובת ה-URL של האירוע.

דרישות מוקדמות

הגדרת הסביבה

פותחים את פרויקט Cloud במסוף Google Cloud

אם הוא לא פתוח, פותחים את פרויקט Cloud שבו רוצים להשתמש בדוגמה הזו:

  1. נכנסים לדף Select a project במסוף Google Cloud.

    בוחרים פרויקט ב-Cloud

  2. בוחרים את הפרויקט ב-Google Cloud שבו רוצים להשתמש. לחלופין, לוחצים על יצירת פרויקט ופועלים לפי ההוראות במסך. אם יוצרים פרויקט ב-Google Cloud, יכול להיות שיהיה צורך להפעיל את החיוב בפרויקט.

הפעלת ה-API

לפני שמשתמשים בממשקי Google API, צריך להפעיל אותם בפרויקט ב-Google Cloud. אפשר להפעיל ממשק API אחד או יותר בפרויקט יחיד ב-Google Cloud.

כל אפליקציות Chat דורשות הגדרה של מסך בקשת הסכמה. הגדרת מסך ההסכמה ל-OAuth של האפליקציה קובעת מה Google מציגה למשתמשים, ורושמת את האפליקציה כדי שתוכלו לפרסם אותה בהמשך.

  1. במסוף Google Cloud, עוברים אל תפריט > > Branding.

    מעבר לדף 'מיתוג'

  2. אם כבר הגדרתם את , תוכלו להגדיר את ההגדרות הבאות של מסך ההסכמה ל-OAuth בקטעים מיתוג, קהל וגישה לנתונים. אם מופיעה ההודעה עדיין לא הוגדר, לוחצים על תחילת העבודה:
    1. בקטע App Information (פרטי האפליקציה), בשדה App name (שם האפליקציה), מזינים שם לאפליקציה.
    2. בקטע כתובת אימייל לתמיכה במשתמשים, בוחרים כתובת אימייל לתמיכה שדרכה משתמשים יכולים ליצור איתכם קשר אם יש להם שאלות לגבי ההסכמה שלהם.
    3. לוחצים על הבא.
    4. בקטע קהל, בוחרים באפשרות פנימי.
    5. לוחצים על הבא.
    6. בקטע פרטים ליצירת קשר, מזינים כתובת אימייל שאליה אפשר לשלוח התראות על שינויים בפרויקט.
    7. לוחצים על הבא.
    8. בקטע סיום, קוראים את המדיניות של Google בנושא נתוני משתמשים בשירותי API. אם אתם מסכימים, מסמנים את התיבה אני מסכים/ה למדיניות של Google בנושא נתוני משתמשים בשירותי API.
    9. לוחצים על המשך.
    10. לוחצים על יצירה.
  3. בינתיים, אפשר לדלג על הוספת היקפי הרשאות. בעתיד, כשתיצרו אפליקציה לשימוש מחוץ לארגון Google Workspace שלכם, תצטרכו לשנות את סוג המשתמש לחיצוני. לאחר מכן מוסיפים את היקפי ההרשאות שהאפליקציה דורשת. למידע נוסף, אפשר לעיין במדריך המלא בנושא הגדרת הסכמה ל-OAuth.

הגדרת הסקריפט

יצירת פרויקט Apps Script

  1. לוחצים על הלחצן הבא כדי לפתוח את פרויקט Apps Script של תזמון פגישות מ-Google Chat.
    פתיחת הפרויקט
  2. לוחצים על סקירה כללית .
  3. בדף הסקירה הכללית, לוחצים על סמל יצירת העותק הסמל ליצירת עותק.

העתקת מספר הפרויקט ב-Cloud

  1. במסוף Google Cloud, נכנסים לתפריט > IAM & Admin ‏(IAM ומנהל מערכת) > Settings (הגדרות).

    כניסה לדף IAM & Admin Settings

  2. מעתיקים את הערך בשדה מספר הפרויקט.

הגדרת פרויקט Cloud לפרויקט Apps Script

  1. בפרויקט Apps Script שהעתקתם, לוחצים על Project Settings (הגדרות הפרויקט) הסמל של הגדרות הפרויקט.
  2. בקטע פרויקט Google Cloud Platform (GCP)‎, לוחצים על שינוי הפרויקט.
  3. בשדה מספר פרויקט GCP, מדביקים את מספר הפרויקט ב-Google Cloud.
  4. לוחצים על הגדרת פרויקט.

יצירת פריסת בדיקה

  1. בפרויקט Apps Script שהעתקתם, לוחצים על Deploy (פריסה) > Test deployments (פריסות לבדיקה).
  2. מעתיקים את מזהה הפריסה של הגרסה האחרונה לשימוש בשלב מאוחר יותר ולוחצים על סיום.

הגדרת Chat API

  1. במסוף Google Cloud, עוברים לדף Chat API.
    ל-Chat API
  2. לוחצים על הגדרה.
  3. מגדירים את Chat API עם המידע הבא:
    • Name (שם): Meeting Scheduler
    • כתובת ה-URL של הדמות: מוסיפים כתובת URL שמפנה לתמונה בגודל מינימלי של 256x256 פיקסלים.
    • תיאור: Quickly create meetings.
    • פונקציונליות: מסמנים את שתי התיבות כדי לאפשר למשתמשים לשלוח הודעות ישירות לאפליקציה ולהוסיף אותה למרחבים.
    • הגדרות חיבור: לוחצים על Apps Script ומזינים את מזהה הפריסה הראשית.
    • פקודות דרך שורת הפקודות: כדי להוסיף פקודות דרך שורת הפקודות עבור /help ו-/schedule_Meeting, מבצעים את השלבים הבאים:
      1. לוחצים על הוספת פקודה דרך שורת הפקודות ומגדירים אותה עם המידע הבא:
        • Name (שם): /help
        • מזהה הפקודה: 1
        • תיאור: Learn what this app does.
      2. לוחצים שוב על הוספת פקודה דרך שורת הפקודות ומגדירים אותה עם המידע הבא:
        • Name (שם): /schedule_Meeting
        • מזהה הפקודה: 2
        • תיאור: Schedule a meeting.
        • מסמנים את התיבה הקישור פותח תיבת דו-שיח.
    • הרשאות: בוחרים באפשרות אנשים וקבוצות ספציפיים בדומיין ומזינים את כתובת האימייל.
  4. לוחצים על שמירה ומרעננים את הדף.
  5. בדף ההגדרות, בקטע App status (סטטוס האפליקציה), מגדירים את הסטטוס ל-Live - available to users (פעילה – זמינה למשתמשים).
  6. לוחצים על שמירה.

הפעלת הסקריפט

  1. פותחים את Google Chat.
  2. לוחצים על הסמל 'התחלת צ'אט' .
  3. מחפשים את שם האפליקציה, Meeting Scheduler.
  4. שולחים הודעה ראשונית, כמו hello, כדי לבקש הרשאה.
  5. כשהאפליקציה משיבה, לוחצים על Configure (הגדרה) ומאשרים את האפליקציה. אם במסך ההסכמה ל-OAuth מוצגת האזהרה This app isn't verified (האפליקציה הזו לא אומתה), ממשיכים בתהליך על ידי בחירה באפשרות Advanced (מתקדם) > Go to {Project Name} (unsafe) (מעבר אל {שם הפרויקט} (לא בטוח)).

  6. שליחת /schedule_Meeting לאפליקציה.

  7. בתיבת הדו-שיח, מוסיפים לפחות כתובת אימייל אחת של מוזמן. אפשר לעדכן את השדות האחרים או להשתמש בערכי ברירת המחדל.

  8. לוחצים על שליחה.

  9. כדי לראות את הפגישה, לוחצים על פתיחת האירוע ביומן.

בדיקת הקוד

כדי לבדוק את קוד 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);

}

השלבים הבאים