Google Chat 앱으로 슬래시 명령어에 응답

이 페이지에서는 Google Chat 앱에서 슬래시 명령어를 설정하고 여기에 응답하는 방법을 설명합니다.

슬래시 명령어는 사용자가 채팅 앱을 호출하고 상호작용하는 일반적인 방법입니다. 또한 슬래시 명령어는 사용자가 채팅 앱의 주요 기능을 검색하고 사용하는 데 도움이 됩니다.

슬래시 명령어를 사용하려면 사용자는 슬래시(/)를 입력한 후 짧은 텍스트 명령어(예: /about)를 입력하여 채팅 앱에 대한 정보를 가져옵니다. 사용자는 Google Chat에 슬래시를 입력하여 사용 가능한 슬래시 명령어를 검색할 수 있습니다. 그러면 채팅 앱에서 사용할 수 있는 명령어가 나열된 창이 표시됩니다.

슬래시 명령어 창
그림 1: 사용자가 Google Chat에 슬래시를 입력할 때 표시되는 창

슬래시 명령어를 설정해야 하는지 결정하고 사용자 상호작용을 디자인하는 방법을 알아보려면 Google Chat 디자인 원칙을 참고하세요.

자격 요건

Node.js

Apps Script

Python

슬래시 명령어 설정

이 섹션에서는 다음 단계를 완료하여 슬래시 명령어를 설정하는 방법을 설명합니다.

  1. 슬래시 명령어의 이름을 만듭니다.
  2. Google Chat API에서 슬래시 명령어를 구성합니다.

슬래시 명령어 이름 지정

슬래시 명령어의 이름은 사용자가 채팅 앱을 호출하기 위해 채팅 메시지에 입력하는 내용입니다. 이름 아래에 간단한 설명도 표시되어 사용자에게 명령어 사용 방법을 추가로 안내하는 역할을 합니다.

슬래시 명령어 이름 및 설명
그림 2: 슬래시 명령어의 이름 및 설명

슬래시 명령어의 이름과 설명을 선택할 때 다음 권장사항을 고려하세요.

  • 슬래시 명령어의 이름을 지정하려면 다음 안내를 따르세요.

    • 짧고, 구체적이며 실행 가능한 단어나 문구를 사용하여 사용자가 명령어를 명확하고 간단하게 만들 수 있도록 합니다. 예를 들어 /createAReminder 대신 /remindMe를 사용합니다.
    • 명령어에 단어가 두 개 이상 포함된 경우 첫 번째 단어는 모두 소문자를 사용하고 추가 단어의 첫 글자는 대문자로 표기하여 사용자가 명령어를 읽을 수 있도록 합니다. 예를 들어 /updatecontact 대신 /updateContact를 사용합니다.
    • 명령어에 고유한 이름을 사용할지, 일반 이름을 사용할지 고려하세요. 명령어가 일반적인 상호작용이나 기능을 설명하는 경우 /settings 또는 /feedback와 같이 사용자가 인식하고 예상할 수 있는 일반 이름을 사용할 수 있습니다. 그렇지 않은 경우 고유한 명령어 이름을 사용하는 것이 좋습니다. 명령어 이름이 다른 채팅 앱에서 같다면 사용자가 유사한 명령어를 필터링하여 자신의 명령어를 찾아 사용해야 하기 때문입니다.
  • 슬래시 명령어를 설명하는 방법은 다음과 같습니다.

    • 사용자가 명령어를 호출할 때 예상되는 내용을 알 수 있도록 설명을 짧고 명확하게 유지합니다.
    • 명령어에 관한 형식 요구사항이 있으면 사용자에게 알립니다. 예를 들어 인수 텍스트가 필요한 /remindMe 명령어를 만드는 경우 설명을 Remind me to do [something] at [time]와 같이 설정합니다.
    • 채팅 앱이 스페이스의 모든 사용자에게 응답하는지 아니면 명령어를 호출하는 사용자에게 비공개로 응답하는지 사용자에게 알립니다. 예를 들어 슬래시 명령어 /about의 경우 Learn about this app (Only visible to you)로 설명할 수 있습니다. 슬래시 명령어에 비공개로 응답하려면 비공개 메시지로 응답 섹션을 참고하세요.

Google Chat API에서 슬래시 명령어 구성

슬래시 명령어를 만들려면 Google Chat API의 채팅 앱 구성에서 명령어에 대한 정보를 지정해야 합니다.

Google Chat API에서 슬래시 명령어를 구성하려면 다음 단계를 완료하세요.

  1. Google Cloud 콘솔에서 메뉴 > API 및 서비스 > 사용 설정된 API 및 서비스 > Google Chat API를 클릭합니다.

    Google Chat API 페이지로 이동

  2. 구성을 클릭합니다.

  3. 슬래시 명령어에서 슬래시 명령어 추가를 클릭합니다.

  4. 명령어의 이름, 명령어 ID, 설명을 입력합니다.

    1. 이름: 명령어의 표시 이름과 앱을 호출하기 위해 사용자가 입력하는 내용입니다. 슬래시로 시작하고 텍스트만 포함해야 하며 최대 50자(영문 기준)입니다.
    2. 설명: 명령어 사용 및 형식을 지정하는 방법을 설명하는 텍스트입니다. 설명은 최대 50자(영문 기준)까지 입력할 수 있습니다.
    3. 명령어 ID: 채팅 앱이 슬래시 명령어를 인식하고 응답을 반환하는 데 사용하는 1에서 1,000 사이의 숫자입니다.
  5. 선택사항: 채팅 앱이 대화상자를 사용하여 명령어에 응답하도록 하려면 대화상자 열기 체크박스를 선택합니다.

  6. 저장을 클릭합니다.

이제 채팅 앱에 슬래시 명령어가 구성됩니다.

슬래시 명령어에 응답

사용자가 슬래시 명령어가 포함된 Chat 메시지를 만들면 채팅 앱이 MESSAGE 상호작용 이벤트를 수신합니다. 이벤트 페이로드에는 slashCommandslashCommandMetadata 필드를 비롯한 슬래시 명령어에 대한 정보가 포함됩니다. 이러한 필드를 사용하여 명령어 ID를 식별하고 커스텀 응답을 반환합니다.

다음 예시는 슬래시 명령어 /vote가 포함된 MESSAGE 상호작용 이벤트의 JSON 페이로드를 보여줍니다.

    {
      ...
      "message": {
        ...
        "text": "/vote yes",
        "argumentText": " yes",
        "slashCommand": {
          "commandId": 2
        },
        "annotations": [
          {
            "length": 5,
            "startIndex": 0,
            "type": "SLASH_COMMAND",
            "slashCommand": {
              "commandName":"/vote",
              "commandId":1,
              "type": "INVOKE",
              "bot": {
                "avatarUrl": "https://www.example.com/images/vote-app-icon.png",
                "displayName": "Voter Chat App",
                "name": "users/1234567890987654321",
                "type": "BOT"
              }
            }
          }
        ]
      }
    }

슬래시 명령어에 응답하려면 slashCommand 필드가 이벤트 페이로드에 있는지 감지하여 이 필드가 있는 경우 명령어에 응답을 반환합니다. 다음 코드 샘플은 슬래시 명령어가 포함된 MESSAGE 상호작용 이벤트에 응답하는 방법을 보여줍니다.

Node.js

/**
* Responds to a MESSAGE event in Google Chat.
*
* @param {Object} event the event object from Chat API.
*
* @return {object} function in response to a slash command.
*/

exports.onMessage = function onMessage(req, res) {

  // Stores the Google Chat event as a variable.
  var event = req.body;

  // Checks for the presence of event.message.slashCommand.
  if (event.message.slashCommand) {
    switch (event.message.slashCommand.commandId) {
      case ID: // The ID for your slash command
        res.runFunction; // The response to the slash command.
    }
  }

Apps Script

/**
* Responds to a MESSAGE event in Google Chat.
*
* @param {Object} event the event object from Chat API.
*
* @return {object} function in response to a slash command.
*/

function onMessage(event) {

  // Checks for the presence of event.message.slashCommand
  if (event.message.slashCommand) {
    switch (event.message.slashCommand.commandId) {
      case ID: // The ID for your slash command
        return runFunction; // The response to the slash command.
    }
  }
}

Python

from typing import Any, Mapping

import flask
import functions_framework

@functions_framework.http
def main(req: flask.Request) -> Mapping[str, Any]:
  """Responds to a MESSAGE event in Google Chat that includes a slash command.

  Args:
      req (flask.Request): the event object from Chat API.

  Returns:
      Mapping[str, Any]: function in response to a slash command.
  """
  if req.method == 'GET':
    return 'Sorry, this function must be called from a Google Chat.'

  request = req.get_json(silent=True)

  if slash_command := request.get('message', dict()).get('slashCommand'):
    command_id = slash_command['commandId']
    if command_id == ID:
      return runFunction

코드를 사용하려면 다음을 바꿉니다.

선택사항: 비공개 메시지로 응답하기

대부분의 경우 사용자는 채팅 앱이 스페이스의 모든 사용자에게 표시되는 메시지로 슬래시 명령어에 응답하기를 원하거나 기대하지 않습니다.

예를 들어 팀에서 고객 지원 서비스를 관리하는 채팅 앱을 사용하는 경우 사용자는 /myCases와 같은 슬래시 명령어를 호출하여 자신에게 할당된 지원 케이스를 볼 수 있습니다. 팀에서 채팅 앱을 스페이스에 추가하는 경우 스페이스에서 이 슬래시 명령어를 사용하는 사용자는 채팅 앱이 해당 메시지에만 응답하도록 할 수 있습니다. 사용자의 지원 케이스를 스페이스의 모든 사용자에게 게시하지 않도록 채팅 앱에서 비공개로 응답할 수 있습니다.

슬래시 명령어에 비공개로 응답하려면 Google Chat 사용자에게 비공개 메시지 보내기를 참조하세요.

전체 예: Rolodex 채팅 앱을 사용하여 연락처 설정

다음 예는 다음 슬래시 명령어에 응답하는 채팅 앱을 보여줍니다.

  • /help 명령어는 채팅 앱 지원을 받는 방법을 설명하는 텍스트 메시지를 반환합니다. 명령어 ID는 1로 설정됩니다.
  • /createContact 명령어는 사용자가 연락처에 대한 세부정보를 입력할 수 있는 대화상자를 엽니다. 명령어 ID는 2로 설정됩니다.

이 샘플을 실행하기 전에 Google Chat API에서 슬래시 명령어를 구성하는 단계를 따릅니다.

Node.js

/**
* Responds to messages that have links whose URLs
* match URL patterns configured for link previews.
*
* @param {Object} event The event object from Chat
* API.
*
* @return {Object} Response from the Chat app
* attached to the message with the previewed link.
*/
exports.onMessage = function onMessage(req, res) {

  // Store the Google Chat event as a variable.
  const event = req.body;

  if (req.method === "GET" || !event.message) {
    res.send("Hello! This function is meant to be used in a Google Chat " +
      "Space.");
  }

  // Checks for the presence of event.message.slashCommand.
  // If the slash command is "/help", responds with a text message.
  // If the slash command is "/createContact", opens a dialog.
  if (event.message.slashCommand) {
    switch (event.message.slashCommand.commandId) {
      case 1: // /help
        res.json({"text": "Contact bot helps you update your address book!"});
      case 2:  // /createContact
        res.openDialog(event);
    }
  }

  // If the Chat app doesn"t detect a slash command, it responds
  // with a card that prompts the user to add a contact
  else {
    res.json({
      "cardsV2": [{
        "cardId": "addContact",
        "card": {
          "header": {
            "title": "Rolodex",
            "subtitle": "Manage your contacts!",
            "imageUrl": "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
            "imageType": "CIRCLE"
          },
          "sections": [
            {
              "widgets": [
                {
                  "buttonList": {
                    "buttons": [
                      {
                        "text": "Add Contact",
                        "onClick": {
                          "action": {
                            "function": "openDialog",
                            "interaction": "OPEN_DIALOG"
                          }
                        }
                      }
                    ]
                  }
                }
              ]
            }
          ]
        }
      }]
    });
  }

  // Respond to button clicks on attached cards
  if (event.type === "CARD_CLICKED") {

    if (event.common.invokedFunction === "openDialog") {
      res.openDialog(event);
    }

    if (event.common.invokedFunction === "openSequentialDialog") {
      res.openSequentialDialog(event);
    }

    if (event.common.invokedFunction === "confirmDialogSuccess") {
      res.confirmDialogSuccess(event);
    }

  }
};

/**
* Opens and starts a dialog that lets users add details about a contact.
*
* @param {object} event the event object from Google Chat.
*
* @return {object} open a dialog.
*/
function openDialog(event) {
  res.json({
    "action_response": {
      "type": "DIALOG",
      "dialog_action": {
        "dialog": {
          "body": {
            "sections": [
              {
                "header": "Add new contact",
                "widgets": [
                  {
                    "textInput": {
                      "label": "Name",
                      "type": "SINGLE_LINE",
                      "name": "name"
                    }
                  },
                  {
                    "textInput": {
                      "label": "Address",
                      "type": "MULTIPLE_LINE",
                      "name": "address"
                    }
                  },
                  {
                    "decoratedText": {
                      "text": "Add to favorites",
                      "switchControl": {
                        "controlType": "SWITCH",
                        "name": "saveFavorite"
                      }
                    }
                  },
                  {
                    "decoratedText": {
                      "text": "Merge with existing contacts",
                      "switchControl": {
                        "controlType": "SWITCH",
                        "name": "mergeContact",
                        "selected": true
                      }
                    }
                  },
                  {
                    "buttonList": {
                      "buttons": [
                        {
                          "text": "Next",
                          "onClick": {
                            "action": {
                              "function": "openSequentialDialog"
                            }
                          }
                        }
                      ]
                    }
                  }
                ]
              }
            ]
          }
        }
      }
    }
  });
};

/**
* Opens a second dialog that lets users add more contact details.
*
* @param {object} event the event object from Google Chat.
*
* @return {object} open a dialog.
*/
function openSequentialDialog(event) {
  res.json({
    "action_response": {
      "type": "DIALOG",
      "dialog_action": {
        "dialog": {
          "body": {
            "sections": [
              {
                "header": "Add new contact",
                "widgets": [
                  {
                    "textInput": {
                      "label": "Notes",
                      "type": "MULTIPLE_LINE",
                      "name": "notes"
                    }
                  },
                  {
                    "selectionInput": {
                      "type": "RADIO_BUTTON",
                      "label": "Contact type",
                      "name": "contactType",
                      "items": [
                        {
                          "text": "Work",
                          "value": "Work",
                          "selected": false
                        },
                        {
                          "text": "Personal",
                          "value": "Personal",
                          "selected": false
                        }
                      ]
                    }
                  },
                  {
                    "buttonList": {
                      "buttons": [
                        {
                          "text": "Submit",
                          "onClick": {
                            "action": {
                              "function": "confirmDialogSuccess",
                              "parameters": [
                                {
                                  "key": "confirmDialogSuccess",
                                  "value": "confirmDialogSuccess"
                                }
                              ]
                            }
                          }
                        }
                      ]
                    },
                    "horizontalAlignment": "END"
                  }
                ]
              }
            ]
          }
        }
      }
    }
  });
}

/**
* Checks for a form input error, the absence of
* a "name" value, and returns an error if absent.
* Otherwise, confirms successful receipt of a dialog.
*
* Confirms successful receipt of a dialog.
*
* @param {Object} event the event object from Chat API.
*
* @return {object} open a Dialog in Google Chat.
*/
function receiveDialog(event) {

  // Checks to make sure the user entered a name
  // in a dialog. If no name value detected, returns
  // an error message.
  if (event.common.formInputs.contactName.stringInputs.value[0] === "") {
    return {
      "actionResponse": {
        "type": "DIALOG",
        "dialogAction": {
          "actionStatus": {
            "statusCode": "OK",
            "userFacingMessage": "Don't forget to name your new contact!"
          }
        }
      }
    };

    // Otherwise the app indicates that it received
    // form data from the dialog. Any value other than "OK"
    // gets returned as an error. "OK" is interpreted as
    // code 200, and the dialog closes.
  } else {
    res.json({
      "actionResponse": {
        "type": "DIALOG",
        "dialogAction": {
          "actionStatus": "OK"
        }
      }
    });
  }
}

Apps Script

apps-script/dialogs/rolodex.gs
/**
* Responds to a MESSAGE event in Google Chat.
*
* @param {Object} event the event object from Chat API.
*
* @return {Object} open a Dialog in response to a slash command
* or a card"s button click.
*/
function onMessage(event) {

  // Checks for the presence of event.message.slashCommand.
  // If the slash command is "/help", responds with a text message.
  // If the slash command is "/createContact", opens a dialog.
  if (event.message.slashCommand) {
    switch (event.message.slashCommand.commandId) {
      case 1: // /help
        return {"text": "Contact bot helps you update your address book!"}
      case 2:  // /createContact
        return openDialog(event);
    }
  }

  // If the Chat app doesn"t detect a slash command, it responds
  // with a card that prompts the user to add a contact
  else {
    return {
      "cardsV2": [{
        "cardId": "addContact",
        "card": {
          "header": {
            "title": "Rolodex",
            "subtitle": "Manage your contacts!",
            "imageUrl": "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
            "imageType": "CIRCLE"
          },
          "sections": [
            {
              "widgets": [
                {
                  "buttonList": {
                    "buttons": [
                      {
                        "text": "Add Contact",
                        "onClick": {
                          "action": {
                            "function": "openDialog",
                            "interaction": "OPEN_DIALOG"
                          }
                        }
                      }
                    ]
                  }
                }
              ]
            }
          ]
        }
      }]

    };
  }
}

/**
* Responds to a CARD_CLICKED event in Google Chat.
*
* @param {Object} event the event object from Google Chat
*/
function onCardClick(event) {

  if (event.common.invokedFunction === "openDialog") {
    return openDialog(event);
  }

  if (event.common.invokedFunction === "openSequentialDialog") {
    const contactName = fetchFormValue(event, "contactName");
    const address = fetchFormValue(event, "address");
    return openSequentialDialog(contactName, address);
  }

  if (event.common.invokedFunction === "receiveDialog") {
    const parameters = event.common.parameters;
    parameters["contactType"] = fetchFormValue(event, "contactType");
    parameters["notes"] = fetchFormValue(event, "notes");
    return receiveDialog(parameters);
  }
}

/**
 * Extracts form input value for a given widget
 * 
 * @param {Object} event the event object from Google Chat
 * @param {String} widgetName the widget name
 * @returns the form input value for the widget
 */
function fetchFormValue(event, widgetName) {
  const widget = event.common.formInputs[widgetName];
  if (widget) {
    return widget[""]["stringInputs"]["value"][0];
  }
}

/**
* Opens and starts a dialog that lets users add details about a contact.
*
*
* @return {Object} open a dialog.
*/
function openDialog(event) {
  return {
    "action_response": {
      "type": "DIALOG",
      "dialog_action": {
        "dialog": {
          "body": {
            "sections": [
              {
                "header": "Add new contact",
                "widgets": [
                  {
                    "textInput": {
                      "label": "Name",
                      "type": "SINGLE_LINE",
                      "name": "contactName"
                    }
                  },
                  {
                    "textInput": {
                      "label": "Address",
                      "type": "MULTIPLE_LINE",
                      "name": "address"
                    }
                  },
                  {
                    "decoratedText": {
                      "text": "Add to favorites",
                      "switchControl": {
                        "controlType": "SWITCH",
                        "name": "saveFavorite"
                      }
                    }
                  },
                  {
                    "decoratedText": {
                      "text": "Merge with existing contacts",
                      "switchControl": {
                        "controlType": "SWITCH",
                        "name": "mergeContact",
                        "selected": true
                      }
                    }
                  },
                  {
                    "buttonList": {
                      "buttons": [
                        {
                          "text": "Next",
                          "onClick": {
                            "action": {
                              "function": "openSequentialDialog"
                            }
                          }
                        }
                      ]
                    }
                  }
                ]
              }
            ]
          }
        }
      }
    }
  };
}

/**
* Opens a second dialog that lets users add more contact details.
*
* @param {String} contactName the contact name from the previous dialog.
* @param {String} address the address from the previous dialog.
*
* @return {Object} open a dialog.
*/
function openSequentialDialog(contactName, address) {
  return {
    "action_response": {
      "type": "DIALOG",
      "dialog_action": {
        "dialog": {
          "body": {
            "sections": [
              {
                "header": "Add new contact",
                "widgets": [
                  {
                    "textInput": {
                      "label": "Notes",
                      "type": "MULTIPLE_LINE",
                      "name": "notes"
                    }
                  },
                  {
                    "selectionInput": {
                      "type": "RADIO_BUTTON",
                      "label": "Contact type",
                      "name": "contactType",
                      "items": [
                        {
                          "text": "Work",
                          "value": "Work",
                          "selected": false
                        },
                        {
                          "text": "Personal",
                          "value": "Personal",
                          "selected": false
                        }
                      ]
                    }
                  },
                  {
                    "buttonList": {
                      "buttons": [
                        {
                          "text": "Submit",
                          "onClick": {
                            "action": {
                              "function": "receiveDialog",
                              "parameters": [
                                {
                                  "key": "contactName",
                                  "value": contactName
                                },
                                {
                                  "key": "address",
                                  "value": address
                                }
                              ]
                            }
                          }
                        }
                      ]
                    },
                    "horizontalAlignment": "END"
                  }
                ]
              }
            ]
          }
        }
      }
    }
  };
}

/**
* Checks for a form input error, the absence of
* a "name" value, and returns an error if absent.
* Otherwise, confirms successful receipt of a dialog.
*
* Confirms successful receipt of a dialog.
*
* @param {Object} parameters the form input values.
*
* @return {Object} open a Dialog in Google Chat.
*/
function receiveDialog(parameters) {

  // Checks to make sure the user entered a name
  // in a dialog. If no name value detected, returns
  // an error message.
  if (!parameters.contactName) {
    return {
      "actionResponse": {
        "type": "DIALOG",
        "dialogAction": {
          "actionStatus": {
            "statusCode": "INVALID_ARGUMENT",
            "userFacingMessage": "Don't forget to name your new contact!"
          }
        }
      }
    };

    // Otherwise the Chat app indicates that it received
    // form data from the dialog. Any value other than "OK"
    // gets returned as an error. "OK" is interpreted as
    // code 200, and the dialog closes.
  } else {
    return {
      "actionResponse": {
        "type": "DIALOG",
        "dialogAction": {
          "actionStatus": {
            "statusCode": "OK",
            "userFacingMessage": "Success " + JSON.stringify(parameters)
          }
        }
      }
    };
  }
}

Python

from typing import Any, Mapping

import flask
import functions_framework

@functions_framework.http
def main(req: flask.Request) -> Mapping[str, Any]:
  """Responds to a MESSAGE event in Google Chat that includes the /createContact
     slash command by opening a dialog.

  Args:
      req (flask.Request): the event object from Chat API.

  Returns:
      Mapping[str, Any]: open a Dialog in response to a card's button click.
  """

  if req.method == 'GET':
    return 'Sorry, this function must be called from a Google Chat.'

  request = req.get_json(silent=True)

  if request.get('type') == 'CARD_CLICKED':
    invoked_function = request.get('common', dict()).get('invokedFunction')
    if invoked_function == 'open_dialog':
      return open_dialog(request)

    elif invoked_function == 'open_sequential_dialog':
      return open_dialog(request)

    elif invoked_function == "receive_dialog":
      return receive_dialog(request)

  else:
    return {
      'cardsV2': [{
        'cardId': 'addContact',
        'card': {
          'header': {
            'title': 'Rolodex',
            'subtitle': 'Manage your contacts!',
            'imageUrl': 'https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png',
            'imageType': 'CIRCLE'
          },
          'sections': [
            {
              'widgets': [
                {
                  'buttonList': {
                    'buttons': [
                      {
                        'text': 'Add Contact',
                        'onClick': {
                                'action': {
                                  'function': 'open_dialog',
                                  'interaction': 'OPEN_DIALOG'
                                }
                        }
                      }
                    ]
                  }
                }
              ]
            }
          ]
        }
      }]
    }

def open_dialog(request: Mapping[str, Any]) -> Mapping[str, Any]:
  """Opens a dialog in Google Chat.

  Args:
      request (Mapping[str, Any]): the event object from Chat API.

  Returns:
      Mapping[str, Any]: open a Dialog in response to a card's button click.
  """
  return {
    'action_response': {
      'type': 'DIALOG',
      'dialog_action': {
        'dialog': {
          'body': {
            'sections': [
              {
                'header': 'Add new contact',
                'widgets': [
                  {
                    'textInput': {
                      'label': 'Name',
                      'type': 'SINGLE_LINE',
                      'name': 'name'
                    }
                  },
                  {
                    'textInput': {
                      'label': 'Address',
                      'type': 'MULTIPLE_LINE',
                      'name': 'address'
                    }
                  },
                  {
                    'decoratedText': {
                      'text': 'Add to favorites',
                      'switchControl': {
                        'controlType': 'SWITCH',
                        'name': 'saveFavorite'
                      }
                    }
                  },
                  {
                    'decoratedText': {
                      'text': 'Merge with existing contacts',
                      'switchControl': {
                        'controlType': 'SWITCH',
                        'name': 'mergeContact',
                        'selected': True
                      }
                    }
                  },
                  {
                    'buttonList': {
                      'buttons': [
                        {
                          'text': 'Next',
                          'onClick': {
                            'action': {
                              'function': 'open_sequential_dialog'
                            }
                          }
                        }
                      ]
                    }
                  }
                ]
              }
            ]
          }
        }
      }
    }
  }

def open_sequential_dialog(request: Mapping[str, Any]) -> Mapping[str, Any]:
  """Opens a second dialog that lets users add more contact details.

  Args:
      request (Mapping[str, Any]): the event object from Chat API.

  Returns:
      Mapping[str, Any]: open a Dialog in response to a card's button click.
  """
  return {
    'action_response': {
      'type': 'DIALOG',
      'dialog_action': {
        'dialog': {
              'body': {
                'sections': [
                  {
                    'header': 'Add new contact',
                    'widgets': [
                      {
                        'textInput': {
                          'label': 'Notes',
                          'type': 'MULTIPLE_LINE',
                          'name': 'notes'
                        }
                      },
                      {
                        'selectionInput': {
                          'type': 'RADIO_BUTTON',
                          'label': 'Contact type',
                          'name': 'contactType',
                          'items': [
                            {
                              'text': 'Work',
                              'value': 'Work',
                              'selected': False
                            },
                            {
                              'text': 'Personal',
                              'value': 'Personal',
                              'selected': False
                            }
                          ]
                        }
                      },
                      {
                        'buttonList': {
                          'buttons': [
                            {
                              'text': 'Submit',
                              'onClick': {
                                'action': {
                                  'function': 'receive_dialog',
                                  'parameters': [
                                    {
                                      'key': 'receiveDialog',
                                      'value': 'receiveDialog'
                                    }
                                  ]
                                }
                              }
                            }
                          ]
                        },
                        'horizontalAlignment': 'END'
                      }
                    ]
                  }
                ]
              }
        }
      }
    }
  }

def receive_dialog(event: Mapping[str, Any]) -> Mapping[str, Any]:
  """Checks for a form input error, the absence of a "name" value, and returns
     an error if absent. Otherwise, confirms successful receipt of a dialog.

  Args:
      event (Mapping[str, Any]): the event object from Chat API.

  Returns:
      Mapping[str, Any]: the response.
  """

  if event.get('common', dict()) \
      .get('formInputs', dict()).get('contactName', dict()) \
          .get('stringInputs').get('value', list()):
    return {
      'actionResponse': {
        'type': 'DIALOG',
        'dialogAction': {
          'actionStatus': 'OK'
        }
      }
    }
  else:
    return {
      'actionResponse': {
        'type': 'DIALOG',
        'dialogAction': {
          'actionStatus': "Don't forget to name your new contact!"
        }
      }
    }