/** * Copyright 2024 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. */ const express = require('express'); const PORT = process.env.PORT || 8080; const app = express() .use(express.urlencoded({extended: false})) .use(express.json()); app.post('/', async (req, res) => { let event = req.body; let body = {}; if (event.type === 'MESSAGE') { body = onMessage(event); } else if (event.type === 'CARD_CLICKED') { body = onCardClick(event); } return res.json(body); }); /** * Responds to a MESSAGE interaction event in Google Chat. * * @param {Object} event the MESSAGE interaction event from Chat API. * @return {Object} message response that opens a dialog or sends private * message with text and card. */ function onMessage(event) { if (event.message.slashCommand) { switch (event.message.slashCommand.commandId) { case "1": // If the slash command is "/about", responds with a text message and button // that opens a dialog. return { text: "Manage your personal and business contacts 📇. To add a " + "contact, use the slash command `/addContact`.", accessoryWidgets: [{ // [START open_dialog_from_button] buttonList: { buttons: [{ text: "Add Contact", onClick: { action: { function: "openInitialDialog", interaction: "OPEN_DIALOG" }} }]} // [END open_dialog_from_button] }] } case "2": // If the slash command is "/addContact", opens a dialog. return openInitialDialog(); } } // If user sends the Chat app a message without a slash command, the app responds // privately with a text and card to add a contact. return { privateMessageViewer: event.user, text: "To add a contact, try `/addContact` or complete the form below:", cardsV2: [{ cardId: "addContactForm", card: { header: { title: "Add a contact" }, sections:[{ widgets: CONTACT_FORM_WIDGETS.concat([{ buttonList: { buttons: [{ text: "Review and submit", onClick: { action: { function : "openConfirmation" }} }]} }])}] } }] }; } // [START subsequent_steps] /** * Responds to CARD_CLICKED interaction events in Google Chat. * * @param {Object} event the CARD_CLICKED interaction event from Google Chat. * @return {Object} message responses specific to the dialog handling. */ function onCardClick(event) { // Initial dialog form page if (event.common.invokedFunction === "openInitialDialog") { return openInitialDialog(); // Confirmation dialog form page } else if (event.common.invokedFunction === "openConfirmation") { return openConfirmation(event); // Submission dialog form page } else if (event.common.invokedFunction === "submitForm") { return submitForm(event); } } // [START open_initial_dialog] /** * Opens the initial step of the dialog that lets users add contact details. * * @return {Object} a message with an action response to open a dialog. */ function openInitialDialog() { return { actionResponse: { type: "DIALOG", dialogAction: { dialog: { body: { sections: [{ header: "Add new contact", widgets: CONTACT_FORM_WIDGETS.concat([{ buttonList: { buttons: [{ text: "Review and submit", onClick: { action: { function: "openConfirmation" }} }]} }]) }]}}} }}; } // [END open_initial_dialog] /** * Returns the second step as a dialog or card message that lets users confirm details. * * @param {Object} event the interactive event with form inputs. * @return {Object} returns a dialog or private card message. */ function openConfirmation(event) { const name = fetchFormValue(event, "contactName") ?? ""; const birthdate = fetchFormValue(event, "contactBirthdate") ?? ""; const type = fetchFormValue(event, "contactType") ?? ""; const cardConfirmation = { header: "Your contact", widgets: [{ textParagraph: { text: "Confirm contact information and submit:" }}, { textParagraph: { text: "Name: " + name }}, { textParagraph: { text: "Birthday: " + convertMillisToDateString(birthdate) }}, { textParagraph: { text: "Type: " + type }}, { // [START set_parameters] buttonList: { buttons: [{ text: "Submit", onClick: { action: { function: "submitForm", parameters: [{ key: "contactName", value: name }, { key: "contactBirthdate", value: birthdate }, { key: "contactType", value: type }] }} }]} // [END set_parameters] }] }; // Returns a dialog with contact information that the user input. if (event.isDialogEvent) { return { action_response: { type: "DIALOG", dialogAction: { dialog: { body: { sections: [ cardConfirmation ]}}} }}; } // Updates existing card message with contact information that the user input. return { actionResponse: { type: "UPDATE_MESSAGE" }, privateMessageViewer: event.user, cardsV2: [{ card: { sections: [cardConfirmation]} }] } } // [END subsequent_steps] /** * Validates and submits information from a dialog or card message * and notifies status. * * @param {Object} event the interactive event with parameters. * @return {Object} a message response that opens a dialog or posts a private * message. */ function submitForm(event) { // [START status_notification] const contactName = event.common.parameters["contactName"]; // Checks to make sure the user entered a contact name. // If no name value detected, returns an error message. const errorMessage = "Don't forget to name your new contact!"; if (!contactName && event.dialogEventType === "SUBMIT_DIALOG") { return { actionResponse: { type: "DIALOG", dialogAction: { actionStatus: { statusCode: "INVALID_ARGUMENT", userFacingMessage: errorMessage }} }}; } // [END status_notification] if (!contactName) { return { privateMessageViewer: event.user, text: errorMessage }; } // [START confirmation_success] // The Chat app indicates that it received form data from the dialog or card. // Sends private text message that confirms submission. const confirmationMessage = "✅ " + contactName + " has been added to your contacts."; if (event.dialogEventType === "SUBMIT_DIALOG") { return { actionResponse: { type: "DIALOG", dialogAction: { actionStatus: { statusCode: "OK", userFacingMessage: "Success " + contactName }} } }; } // [END confirmation_success] // [START confirmation_message] return { actionResponse: { type: "NEW_MESSAGE" }, privateMessageViewer: event.user, text: confirmationMessage }; // [END confirmation_message] } /** * Extracts form input value for a given widget. * * @param {Object} event the CARD_CLICKED interaction event from Google Chat. * @param {String} widgetName a unique ID for the widget, specified in the widget's name field. * @returns the value inputted by the user, null if no value can be found. */ function fetchFormValue(event, widgetName) { const formItem = event.common.formInputs[widgetName]; // For widgets that receive StringInputs data, the value input by the user. if (formItem.hasOwnProperty("stringInputs")) { const stringInput = event.common.formInputs[widgetName].stringInputs.value[0]; if (stringInput != null) { return stringInput; } // For widgets that receive dateInput data, the value input by the user. } else if (formItem.hasOwnProperty("dateInput")) { const dateInput = event.common.formInputs[widgetName].dateInput.msSinceEpoch; if (dateInput != null) { return dateInput; } } return null; } /** * Converts date in milliseconds since epoch to user-friendly string. * * @param {Object} millis the milliseconds since epoch time. * @return {string} Display-friend date (English US). */ function convertMillisToDateString(millis) { const date = new Date(Number(millis)); const options = { year: 'numeric', month: 'long', day: 'numeric' }; return date.toLocaleDateString('en-US', options); } app.listen(PORT, () => { console.log(`Server is running in port - ${PORT}`); }); // [START input_widgets] /** * The section of the contact card that contains the form input widgets. Used in a dialog and card message. * To add and preview widgets, use the Card Builder: https://addons.gsuite.google.com/uikit/builder */ const CONTACT_FORM_WIDGETS = [ { "textInput": { "name": "contactName", "label": "First and last name", "type": "SINGLE_LINE" } }, { "dateTimePicker": { "name": "contactBirthdate", "label": "Birthdate", "type": "DATE_ONLY" } }, { "selectionInput": { "name": "contactType", "label": "Contact type", "type": "RADIO_BUTTON", "items": [ { "text": "Work", "value": "Work", "selected": false }, { "text": "Personal", "value": "Personal", "selected": false } ] } } ]; // [END input_widgets]