Skip to content

Commit

Permalink
feat: [Avatar app] Quick command samples (#355)
Browse files Browse the repository at this point in the history
vinay-google authored Feb 1, 2025

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent 910bf04 commit 0394995
Showing 7 changed files with 337 additions and 233 deletions.
40 changes: 35 additions & 5 deletions apps-script/avatar-app/avatar-app.gs
Original file line number Diff line number Diff line change
@@ -20,6 +20,11 @@
// use the same ID as set in the Google Chat API configuration.
const ABOUT_COMMAND_ID = null;

// The ID of the quick command "Help".
// It's not enabled by default, set to the actual ID to enable it. You need to
// use the same ID as set in the Google Chat API configuration.
const HELP_COMMAND_ID = null;

/**
* Responds to a MESSAGE event in Google Chat.
*
@@ -64,13 +69,38 @@ function createMessage(displayName, avatarUrl) {
header: {
title: `Hello ${displayName}!`,
},
sections: [{ widgets: [{
textParagraph: { text: 'Your avatar picture: ' }
}, {
image: { imageUrl: avatarUrl }
}]}]
sections: [{
widgets: [{
textParagraph: {text: 'Your avatar picture: '}
}, {
image: {imageUrl: avatarUrl}
}]
}]
}
}]
};
}

// [START chat_avatar_quick_command]
/**
* Handles the APP_COMMAND event type. This function is triggered when a user
* interacts with a quick command within the Google Chat app. It responds
* based on the command ID.
*
* @param {Object} event The event object from Google Chat, containing details
* about the app command interaction. It includes information like the
* command ID and the user who triggered it.
*/
function onAppCommand(event) {
// Executes the quick command logic based on its ID.
// Command IDs are set in the Google Chat API configuration.
switch (event.appCommandMetadata.appCommandId) {
case HELP_COMMAND_ID:
return {
privateMessageViewer: event.user,
text: 'The Avatar app replies to Google Chat messages.'
};
}
}
// [END chat_avatar_quick_command]
// [END chat_avatar_app]
38 changes: 12 additions & 26 deletions java/avatar-app/pom.xml
Original file line number Diff line number Diff line change
@@ -17,55 +17,41 @@ limitations under the License.

<!-- [START chat_avatar_app_build] -->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.google.chat</groupId>
<groupId>gcfv2</groupId>
<artifactId>avatar-app</artifactId>
<version>1.0-SNAPSHOT</version>
<version>0.0.1</version>
<name>Avatar App</name>

<properties>
<maven.compiler.target>17</maven.compiler.target>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.release>21</maven.compiler.release>
</properties>

<dependencies>
<dependency>
<groupId>com.google.cloud.functions</groupId>
<artifactId>functions-framework-api</artifactId>
<version>1.0.1</version>
<version>1.1.4</version>
</dependency>

<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.9.1</version>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.12.1</version>
</dependency>

<!-- https://mvnrepository.com/artifact/com.google.apis/google-api-services-chat -->
<dependency>
<groupId>com.google.apis</groupId>
<artifactId>google-api-services-chat</artifactId>
<version>v1-rev20241008-2.0.0</version>
<version>v1-rev20250116-2.0.0</version>
</dependency>

</dependencies>

<!-- Required for Java 11 functions in the inline editor -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<excludes>
<exclude>.google/</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
<!-- [END chat_avatar_app_build] -->
94 changes: 0 additions & 94 deletions java/avatar-app/src/main/java/App.java

This file was deleted.

125 changes: 125 additions & 0 deletions java/avatar-app/src/main/java/AvatarApp.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/**
 * Copyright 2025 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.
 */

// [START chat_avatar_app]
import com.google.api.services.chat.v1.model.CardWithId;
import com.google.api.services.chat.v1.model.GoogleAppsCardV1Card;
import com.google.api.services.chat.v1.model.GoogleAppsCardV1CardHeader;
import com.google.api.services.chat.v1.model.GoogleAppsCardV1Image;
import com.google.api.services.chat.v1.model.GoogleAppsCardV1Section;
import com.google.api.services.chat.v1.model.GoogleAppsCardV1TextParagraph;
import com.google.api.services.chat.v1.model.GoogleAppsCardV1Widget;
import com.google.api.services.chat.v1.model.Message;
import com.google.api.services.chat.v1.model.User;
import com.google.cloud.functions.HttpFunction;
import com.google.cloud.functions.HttpRequest;
import com.google.cloud.functions.HttpResponse;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import java.util.List;

public class AvatarApp implements HttpFunction {
private static final Gson gson = new Gson();

// Command IDs (configure these in Google Chat API)
private static final int ABOUT_COMMAND_ID = 1; // ID for the "/about" slash command
private static final int HELP_COMMAND_ID = 2; // ID for the "Help" quick command

@Override
public void service(HttpRequest request, HttpResponse response) throws Exception {
JsonObject event = gson.fromJson(request.getReader(), JsonObject.class);

if (event.has("appCommandMetadata")) {
handleAppCommands(event, response);
} else {
handleRegularMessage(event, response);
}
}

// [START chat_avatar_slash_command]
/**
* Handles slash and quick commands.
*
* @param event The Google Chat event.
* @param response The HTTP response object.
*/
private void handleAppCommands(JsonObject event, HttpResponse response) throws Exception {
int appCommandId = event.getAsJsonObject("appCommandMetadata").get("appCommandId").getAsInt();

switch (appCommandId) {
case ABOUT_COMMAND_ID:
Message aboutMessage = new Message();
aboutMessage.setText("The Avatar app replies to Google Chat messages.");
aboutMessage.setPrivateMessageViewer(new User()
.setName(event.getAsJsonObject("user").get("name").getAsString()));
response.getWriter().write(gson.toJson(aboutMessage));
return;
case HELP_COMMAND_ID:
Message helpMessage = new Message();
helpMessage.setText("The Avatar app replies to Google Chat messages.");
helpMessage.setPrivateMessageViewer(new User()
.setName(event.getAsJsonObject("user").get("name").getAsString()));
response.getWriter().write(gson.toJson(helpMessage));
return;
}
}
// [END chat_avatar_slash_command]

/**
* Handles regular messages (not commands).
*
* @param event The Google Chat event.
* @param response The HTTP response object.
*/
private void handleRegularMessage(JsonObject event, HttpResponse response) throws Exception {

if (!event.has("user")) {
response.getWriter().write("Invalid request.");
return;
}

JsonObject user = event.getAsJsonObject("user");
String displayName = user.has("displayName") ? user.get("displayName").getAsString() : "";
String avatarUrl = user.has("avatarUrl") ? user.get("avatarUrl").getAsString() : "";
Message message = createMessage(displayName, avatarUrl);
response.getWriter().write(gson.toJson(message));
}

/**
* Creates a card message with the user's avatar.
*
* @param displayName The user's display name.
* @param avatarUrl The URL of the user's avatar.
* @return The card message object.
*/
private Message createMessage(String displayName, String avatarUrl) {
return new Message()
.setText("Here's your avatar")
.setCardsV2(List.of(new CardWithId()
.setCardId("avatarCard")
.setCard(new GoogleAppsCardV1Card()
.setName("Avatar Card")
.setHeader(new GoogleAppsCardV1CardHeader()
.setTitle(String.format("Hello %s!", displayName)))
.setSections(List.of(new GoogleAppsCardV1Section().setWidgets(List.of(
new GoogleAppsCardV1Widget()
.setTextParagraph(new GoogleAppsCardV1TextParagraph()
.setText("Your avatar picture:")),
new GoogleAppsCardV1Widget()
.setImage(new GoogleAppsCardV1Image().setImageUrl(avatarUrl)))))))));
}
}
// [END chat_avatar_app]
107 changes: 62 additions & 45 deletions node/avatar-app/index.js
Original file line number Diff line number Diff line change
@@ -15,57 +15,73 @@
*/

// [START chat_avatar_app]
const functions = require('@google-cloud/functions-framework');

// The ID of the slash command "/about".
// It's not enabled by default, set to the actual ID to enable it. You need to
// use the same ID as set in the Google Chat API configuration.
const ABOUT_COMMAND_ID = "";
// Command IDs (configure these in Google Chat API)
const ABOUT_COMMAND_ID = 1; // ID for the "/about" slash command
const HELP_COMMAND_ID = 2; // ID for the "Help" quick command

/**
* Google Cloud Function that responds to messages sent from a
* Google Chat space.
* Google Cloud Function that handles HTTP requests from Google Chat.
*
* @param {Object} req Request sent from Google Chat space
* @param {Object} res Response to send back
* @param {Object} req - The HTTP request object sent from Google Chat.
* @param {Object} res - The HTTP response object.
*/
exports.avatarApp = function avatarApp(req, res) {
if (req.method === 'GET' || !req.body.message) {
return res.send('Hello! This function is meant to be used ' +
'in a Google Chat Space.');
functions.http('avatarApp', (req, res) => {
const event = req.body;

if (event.appCommandMetadata) {
handleAppCommands(event, res);
} else {
handleRegularMessage(event, res);
}
});

// Stores the Google Chat event as a variable.
const event = req.body;
// [START chat_avatar_slash_command]
/**
* Handles slash and quick commands.
*
* @param {Object} event - The Google Chat event.
* @param {Object} res - The HTTP response object.
*/
function handleAppCommands(event, res) {
const {appCommandId, appCommandType} = event.appCommandMetadata;

// [START chat_avatar_slash_command]
// Checks for the presence of a slash command in the message.
if (event.message.slashCommand) {
// Executes the slash command logic based on its ID.
// Slash command IDs are set in the Google Chat API configuration.
switch (event.message.slashCommand.commandId) {
case ABOUT_COMMAND_ID:
return res.send({
privateMessageViewer: event.user,
text: 'The Avatar app replies to Google Chat messages.'
});
}
switch (appCommandId) {
case ABOUT_COMMAND_ID:
return res.send({
privateMessageViewer: event.user,
text: 'The Avatar app replies to Google Chat messages.'
});
case HELP_COMMAND_ID:
return res.send({
privateMessageViewer: event.user,
text: 'The Avatar app replies to Google Chat messages.'
});
}
// [END chat_avatar_slash_command]
}
// [END chat_avatar_slash_command]

const sender = req.body.message.sender.displayName;
const image = req.body.message.sender.avatarUrl;
const data = createMessage(sender, image);
res.send(data);
};
/**
* Handles regular messages (not commands).
*
* @param {Object} event - The Google Chat event.
* @param {Object} res - The HTTP response object.
*/
function handleRegularMessage(event, res) {
const messageData = createMessage(event.user);
res.send(messageData);
}

/**
* Creates a card with two widgets.
*
* @param {string} displayName the sender's display name
* @param {string} avatarUrl the URL for the sender's avatar
* @return {Object} a card with the user's avatar.
* Creates a card message with the user's avatar.
*
* @param {Object} user - The user who sent the message.
* @param {string} user.displayName - The user's display name.
* @param {string} user.avatarUrl - The URL of the user's avatar.
* @return {Object} - The card message object.
*/
function createMessage(displayName, avatarUrl) {
function createMessage({displayName, avatarUrl}) {
return {
text: 'Here\'s your avatar',
cardsV2: [{
@@ -75,13 +91,14 @@ function createMessage(displayName, avatarUrl) {
header: {
title: `Hello ${displayName}!`,
},
sections: [{ widgets: [{
textParagraph: { text: 'Your avatar picture: ' }
}, {
image: { imageUrl: avatarUrl }
}]}]
}
}]
sections: [{
widgets: [
{textParagraph: {text: 'Your avatar picture:'}},
{image: {imageUrl: avatarUrl}},
],
}],
},
}],
};
}
// [END chat_avatar_app]
3 changes: 3 additions & 0 deletions node/avatar-app/package.json
Original file line number Diff line number Diff line change
@@ -6,5 +6,8 @@
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"dependencies": {
"@google-cloud/functions-framework": "^3.0.0"
},
"license": "Apache-2.0"
}
163 changes: 100 additions & 63 deletions python/avatar-app/main.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2023 Google LLC
# 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.
@@ -18,69 +18,106 @@
import flask
import functions_framework

# The ID of the slash command "/about".
# It's not enabled by default, set to the actual ID to enable it. You need to
# use the same ID as set in the Google Chat API configuration.
ABOUT_COMMAND_ID = ""
# Command IDs (configure these in Google Chat API)
ABOUT_COMMAND_ID = 1 # ID for the "/about" slash command
HELP_COMMAND_ID = 2 # ID for the "Help" quick command


@functions_framework.http
def avatar_app(req: flask.Request) -> Mapping[str, Any]:
"""Google Cloud Function that handles requests from Google Chat
Args:
flask.Request: the request
Returns:
Mapping[str, Any]: the response
"""
if req.method == "GET":
return "Hello! This function must be called from Google Chat."

request_json = req.get_json(silent=True)

# [START chat_avatar_slash_command]
# Checks for the presence of a slash command in the message.
if "slashCommand" in request_json["message"]:
# Executes the slash command logic based on its ID.
# Slash command IDs are set in the Google Chat API configuration.
if request_json["message"]["slashCommand"]["commandId"] == ABOUT_COMMAND_ID:
return {
"privateMessageViewer": request_json["user"],
"text": 'The Avatar app replies to Google Chat messages.'
}
# [END chat_avatar_slash_command]

display_name = request_json["message"]["sender"]["displayName"]
avatar = request_json["message"]["sender"]["avatarUrl"]
response = create_message(name=display_name, image_url=avatar)
return response


def create_message(name: str, image_url: str) -> Mapping[str, Any]:
"""Google Cloud Function that handles requests from Google Chat
Args:
str name: the sender's display name.
str image_url: the URL for the sender's avatar.
Returns:
Mapping[str, Any]: a card with the user's avatar.
"""
return {
"text": "Here's your avatar",
"cardsV2": [{
"cardId": "avatarCard",
"card": {
"name": "Avatar Card",
"header": { "title": f"Hello {name}!" },
"sections": [{
"widgets": [{
"textParagraph": { "text": "Your avatar picture:" }
}, {
"image": { "imageUrl": image_url }
}]
}]
}
}]
}
"""Google Cloud Function that handles HTTP requests from Google Chat.
Args:
flask.Request: the request
Returns:
Mapping[str, Any]: the response
"""
event = req.get_json(silent=True)

if event and "appCommandMetadata" in event:
return handle_app_commands(event)
else:
return handle_regular_message(event)


# [START chat_avatar_slash_command]
def handle_app_commands(event: Mapping[str, Any]) -> Mapping[str, Any]:
"""Handles slash and quick commands.
Args:
Mapping[str, Any] event: The Google Chat event.
Returns:
Mapping[str, Any]: the response
"""
app_command_id = event["appCommandMetadata"]["appCommandId"]

if app_command_id == ABOUT_COMMAND_ID:
return {
"privateMessageViewer": event["user"],
"text": "The Avatar app replies to Google Chat messages.",
}
elif app_command_id == HELP_COMMAND_ID:
return {
"privateMessageViewer": event["user"],
"text": "The Avatar app replies to Google Chat messages.",
}
return {}


# [END chat_avatar_slash_command]


def handle_regular_message(event: Mapping[str, Any]) -> Mapping[str, Any]:
"""Handles regular messages (not commands).
Args:
Mapping[str, Any] event: The Google Chat event.
Returns:
Mapping[str, Any]: the response
"""

if not event or "user" not in event:
return "Invalid request."

message_data = create_message(event["user"])
return message_data


def create_message(user: Mapping[str, Any]) -> Mapping[str, Any]:
"""Creates a card message with the user's avatar.
Args:
Mapping[str, Any] user: The user who sent the message.
Returns:
Mapping[str, Any]: a card with the user's avatar.
"""
display_name = user.get("displayName", "")
avatar_url = user.get("avatarUrl", "")

return {
"text": "Here's your avatar",
"cardsV2": [
{
"cardId": "avatarCard",
"card": {
"name": "Avatar Card",
"header": {"title": f"Hello {display_name}!"},
"sections": [
{
"widgets": [
{"textParagraph": {"text": "Your avatar picture:"}},
{"image": {"imageUrl": avatar_url}},
]
}
],
},
}
],
}


# [END chat_avatar_app]

0 comments on commit 0394995

Please sign in to comment.