כאן מוסבר איך מגדירים פקודות דרך שורת הפקודות באפליקציית Google Chat ואיך מגיבים להן.
פקודת לוכסן היא דרך נפוצה שבה משתמשים מפעילים אפליקציות צ'אט ומקיימים איתן אינטראקציה. פקודות לוכסן גם עוזרות למשתמשים לגלות תכונות עיקריות של אפליקציית Chat ולהשתמש בהן.
כדי להשתמש בפקודה דרך לוכסן, המשתמשים מקלידים לוכסן (/
) ואז פקודת טקסט קצרה,
כמו /about
כדי לקבל מידע על אפליקציית Chat.
המשתמשים יכולים למצוא לוכסן באמצעות הקלדת לוכסן ב-Google Chat, שיופיע חלון עם הפקודות הזמינות לאפליקציית Chat:
כשמשתמש שולח הודעה שמכילה פקודה דרך שורת הפקודות, ההודעה גלויה רק למשתמש ולאפליקציית Chat.
במאמר הגדרה של כל התהליכים שעוברים המשתמשים מוסבר איך להגדיר פקודות לוכסן ולהבין איך לעצב אינטראקציות של משתמשים.
דרישות מוקדמות
Node.js
- חשבון Google Workspace עם גישה ל-Google Chat.
- אפליקציה ל-Chat. כדי ליצור אפליקציה ל-Chat, פועלים לפי ההוראות בquickstart.
Apps Script
- חשבון Google Workspace עם גישה ל-Google Chat.
- אפליקציה ל-Chat. כדי ליצור אפליקציה ל-Chat, פועלים לפי ההוראות בquickstart.
Python
- חשבון Google Workspace עם גישה ל-Google Chat.
- אפליקציה ל-Chat. כדי ליצור אפליקציה ל-Chat, פועלים לפי ההוראות בquickstart.
הגדרה של פקודה דרך שורת הפקודות
בקטע הזה נסביר איך לבצע את השלבים הבאים כדי להגדיר פקודת לוכסן:
- נותנים שם לפקודה דרך הלוכסן.
- מגדירים את הפקודה ב-Google Chat API.
צריך לתת שם לפקודה
השם של פקודה דרך שורת הפקודות הוא מה שהמשתמשים מקלידים בהודעה ב-Chat כדי להפעיל את אפליקציית Chat. מתחת לשם של הפקודה מופיע גם תיאור קצר, כדי להסביר למשתמשים איך להשתמש בפקודה:
כשבוחרים שם ותיאור לפקודה דרך שורת הפקודות, כדאי ליישם את ההמלצות הבאות:
כדי לתת שם לפקודה:
- כדאי להשתמש במילים או בביטויים קצרים, תיאוריים ושימושיים כדי שהפקודות יהיו ברורות ופשוטות למשתמש. לדוגמה, במקום לומר
/createAReminder
, צריך להשתמש ב-/remindMe
. - אם הפקודה מכילה יותר ממילה אחת, עוזרים למשתמשים לקרוא את הפקודה על ידי שימוש באותיות קטנות במילה הראשונה ולאחר מכן שימוש באות רישית באות הראשונה של מילים נוספות. לדוגמה, במקום
/updatecontact
, השתמשו ב-/updateContact
. - כדאי לשקול אם להשתמש בשם ייחודי או בשם נפוץ לפקודה. אם הפקודה מתארת אינטראקציה או תכונה אופיינית, אפשר להשתמש בשם נפוץ שהמשתמשים מזהים ומצפים, כמו
/settings
או/feedback
. אחרת, כדאי לנסות להשתמש בשמות של פקודות ייחודיים, כי אם שם הפקודה זהה לאפליקציות Chat אחרות, המשתמש צריך לסנן את הפקודות האלה כדי למצוא את השם שלכם ולהשתמש בו.
- כדאי להשתמש במילים או בביטויים קצרים, תיאוריים ושימושיים כדי שהפקודות יהיו ברורות ופשוטות למשתמש. לדוגמה, במקום לומר
כדי לתאר את הפקודה שנכתבה דרך הלוכסן:
- התיאור צריך להיות קצר וברור, כדי שהמשתמשים ידעו למה לצפות כשהם מפעילים את הפקודה.
- צריך להודיע למשתמשים אם יש דרישות עיצוב לפקודה.
לדוגמה, אם יוצרים פקודת
/remindMe
שמחייבת טקסט ארגומנט, צריך להגדיר לתיאור משהו כמוRemind me to do [something] at [time]
. - המשתמשים צריכים לדעת אם אפליקציית Chat עונה לכל מי שבמרחב המשותף, או באופן פרטי למשתמש שהפעיל את הפקודה.
לדוגמה, אפשר לתאר את הפקודה
/about
כך:Learn about this app (Only visible to you)
. כדי להגיב בפרטיות לפקודה, עיינו בקטע תגובה באמצעות הודעה פרטית.
איך מגדירים את הפקודה ב-Google Chat API
כדי ליצור פקודה של שורת הפקודות, צריך לציין את הפרטים שלה בהגדרות של אפליקציית Chat ל-Google Chat API.
כך מגדירים פקודת לוכסן ב-Google Chat API:
במסוף Google Cloud, לוחצים על סמל התפריט > APIs & Services > Enabled APIs & Services > Google Chat API.
לוחצים על הגדרה.
בקטע פקודות Slash, לוחצים על Add a slash command (הוספת פקודה של לוכסן).
מזינים שם, מזהה פקודה ותיאור לפקודה:
- שם: השם המוצג של הפקודה וסוג המשתמש שמקלידים כדי להפעיל את האפליקציה. השם חייב להתחיל בקו נטוי, להכיל רק טקסט ויכול להכיל עד 50 תווים.
- תיאור: הטקסט שמתאר איך להשתמש בפקודה ולעצב אותה. האורך המקסימלי של תיאורים הוא 50 תווים.
- מזהה פקודה: מספר מ-1 עד 1,000 שבו אפליקציית Chat משתמשת כדי לזהות את הפקודה של הלוכסן ולהחזיר תשובה.
אם רוצים שאפליקציית Chat תגיב לפקודה עם תיבת דו-שיח, מסמנים את התיבה Open aDialog.
לוחצים על שמירה.
הלוכסן מוגדר עכשיו באפליקציית Chat.
תשובה לפקודה דרך שורת הפקודות
כשמשתמשים יוצרים הודעה ב-Chat שמכילה פקודה דרך שורת הפקודות, אפליקציית Chat מקבלת אירוע אינטראקציה של MESSAGE
.
המטען הייעודי של האירועים מכיל מידע על הפקודה לוכסן, כולל השדות slashCommand
ו-slashCommandMetadata
. משתמשים בשדות האלה כדי לזהות את מזהה הפקודה ולהחזיר תשובה מותאמת אישית.
הדוגמה הבאה מציגה את המטען הייעודי (payload) של JSON לאירוע אינטראקציה עם MESSAGE
, שכולל את פקודת הלוכסן /vote
:
{
...
"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
נמצא במטען הייעודי (payload) של האירוע, ואם כן להחזיר תגובה לפקודה.
דוגמת הקוד הבאה ממחישה איך להגיב לאירוע אינטראקציה 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.json(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
כדי להשתמש בקוד, מחליפים את מה שכתוב בשדות הבאים:
ID
: מזהה הפקודה שציינתם בהגדרת הפקודה ב-Google Chat API.runFunction
: פונקציה שיוצרת תגובה לפקודה של הלוכסן.
אם רוצים, אפשר לענות באמצעות הודעה פרטית
הודעות שכוללות פקודות גלויות רק למשתמש ששלח את ההודעה ולאפליקציית Chat שמקבלת את הפקודה. אם הגדרתם שאפליקציית Chat תתווסף למרחבים משותפים עם כמה אנשים, כדאי להגיב לפקודה של הלוכסן בפרטיות כדי לשמור על הפרטיות של האינטראקציה בין המשתמש לבין אפליקציית Chat.
לדוגמה, אם צוות משתמש באפליקציית Chat שמנהלת שירות תמיכת לקוחות, המשתמשים יכולים להפעיל פקודת לוכסן כמו /myCases
כדי להציג את בקשות התמיכה שהוקצו להם. אם הצוות יוסיף את אפליקציית Chat למרחב משותף, משתמש שמשתמש בפקודה הזו במרחב המשותף עשוי לרצות שאפליקציית Chat תגיב רק לו. כדי לא לפרסם את בקשות התמיכה של המשתמש לכל האנשים במרחב המשותף, אפליקציית Chat יכולה לענות לבקשות התמיכה באופן פרטי.
במאמר שליחת הודעות פרטיות למשתמשי Google Chat מוסבר איך מגיבים לפקודה באופן פרטי.
דוגמה מלאה: הגדרת אנשי קשר באמצעות אפליקציית Rolodex Chat
בדוגמה הבאה מוצגת אפליקציית Chat שמגיבה לפקודות הבאות של שורת הפקודות:
- הפקודה
/help
מחזירה הודעת טקסט שמסבירה איך לקבל תמיכה באפליקציית Chat. מזהה הפקודה מוגדר ל-1
. - הפקודה
/createContact
פותחת תיבת דו-שיח שבה משתמשים יכולים להזין פרטים לגבי איש קשר. מזהה הפקודה הוא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.json(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.json(openDialog(event));
}
if (event.common.invokedFunction === "openSequentialDialog") {
res.json(openSequentialDialog(event));
}
if (event.common.invokedFunction === "confirmDialogSuccess") {
res.json(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) {
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": "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) {
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": "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 {
return {
"actionResponse": {
"type": "DIALOG",
"dialogAction": {
"actionStatus": "OK"
}
}
};
}
}
Apps Script
בדוגמה הזו נשלחת הודעת כרטיס על ידי החזרת card JSON. אפשר גם להשתמש בשירות של כרטיסי Apps Script.
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!"
}
}
}