На этой странице объясняется, как создать приложение чата, которое получает события из чата с помощью Cloud Pub/Sub . Такая архитектура полезна, если ваше приложение чата находится за брандмауэром или если вы хотите отправлять или получать события о пространстве чата или пользователе с помощью API событий Google Workspace .
На следующей диаграмме показана архитектура чат-приложения, созданного с использованием модели публикации/подписки (Pub/Sub):
На приведенной выше диаграмме показано следующее взаимодействие пользователя с приложением Pub/Sub Chat:
Пользователь взаимодействует с приложением «Чат», например, отправляя ему сообщение, отдавая команду или добавляя/удаляя его из чата.
Чат отправляет сообщение в тему Pub/Sub.
Сервер приложений, представляющий собой облачную или локальную систему, содержащую логику приложения чата, подписывается на тему Pub/Sub, чтобы получать сообщения через брандмауэр.
При желании приложение «Чат» может вызывать API чата для асинхронной отправки сообщений или выполнения других операций.
Предварительные требования
Node.js
- Корпоративный аккаунт Google Workspace с доступом к Google Chat .
- Проект Google Cloud с включенной оплатой. Чтобы проверить, включена ли оплата в существующем проекте, см. раздел «Проверка статуса оплаты ваших проектов ». Чтобы создать проект и настроить оплату, см. раздел «Создание проекта Google Cloud» .
- Node.js 14 или выше
- Инструмент управления пакетами npm
- Инициализированный проект Node.js. Чтобы инициализировать новый проект, создайте новую папку, перейдите в неё и выполните следующую команду в командной строке:
npm init
Python
- Корпоративный аккаунт Google Workspace с доступом к Google Chat .
- Проект Google Cloud с включенной оплатой. Чтобы проверить, включена ли оплата в существующем проекте, см. раздел «Проверка статуса оплаты ваших проектов ». Чтобы создать проект и настроить оплату, см. раздел «Создание проекта Google Cloud» .
- Python 3.6 или выше
- Инструмент управления пакетами pip
Java
- Корпоративный аккаунт Google Workspace с доступом к Google Chat .
- Проект Google Cloud с включенной оплатой. Чтобы проверить, включена ли оплата в существующем проекте, см. раздел «Проверка статуса оплаты ваших проектов ». Чтобы создать проект и настроить оплату, см. раздел «Создание проекта Google Cloud» .
- Java 11 или более поздняя версия
- Инструмент управления пакетами Maven
Включите API
Перед использованием API Google необходимо включить их в проекте Google Cloud. В одном проекте Google Cloud можно включить один или несколько API.В консоли Google Cloud включите API Google Chat и API Pub/Sub.
Настройка Pub/Sub
Создайте тему Pub/Sub , в которую API чата сможет отправлять сообщения. Рекомендуется использовать одну тему на каждое приложение чата.
Создайте служебную учетную запись для приложения «Чат», чтобы авторизоваться с помощью Pub/Sub и Chat, и сохраните файл закрытого ключа в свою рабочую директорию.
Создайте подписку по запросу (pull subscription) на данную тему.
Назначьте роль подписчика Pub/Sub для подписки на учетную запись службы, которую вы создали ранее.
Напишите сценарий
В этом разделе вы определяете логику приложения для вашего чата. Вы пишете скрипт, который проходит аутентификацию в Google Cloud и подписывается на тему Pub/Sub для получения событий от чата, например, когда пользователь отправляет сообщение в ваше приложение чата.
Когда скрипт получает сообщение, он обрабатывает данные события и использует API Google Chat для отправки ответа пользователю или в пространство. Такая настройка позволяет вашему приложению чата работать за брандмауэром, продолжая при этом взаимодействовать с пользователями чата.
Node.js
В командной строке укажите учетные данные сервисной учетной записи :
export GOOGLE_APPLICATION_CREDENTIALS=SERVICE_ACCOUNT_FILE_PATHВ командной строке укажите идентификатор проекта Google Cloud:
export PROJECT_ID=PROJECT_IDВ командной строке укажите идентификатор подписки для ранее созданной подписки Pub/Sub:
export SUBSCRIPTION_ID=SUBSCRIPTION_IDВ рабочей директории создайте файл с именем
package.json.В файл
package.jsonвставьте следующий код:{ "name": "pub-sub-app", "version": "1.0.0", "description": "Google Chat App that listens for messages via Cloud Pub/Sub", "main": "index.js", "scripts": { "start": "node index.js", "test": "echo \"Error: no test specified\" && exit 1" }, "dependencies": { "@google-apps/chat": "^0.4.0", "@google-cloud/pubsub": "^4.5.0" }, "license": "Apache-2.0" }В рабочей директории создайте файл с именем
index.js.В
index.jsвставьте следующий код:const {ChatServiceClient} = require('@google-apps/chat'); const {MessageReplyOption} = require('@google-apps/chat').protos.google.chat.v1.CreateMessageRequest; const {PubSub} = require('@google-cloud/pubsub'); const {SubscriberClient} = require('@google-cloud/pubsub/build/src/v1'); // Receives messages from a pull subscription. function receiveMessages() { const chat = new ChatServiceClient({ keyFile: process.env.GOOGLE_APPLICATION_CREDENTIALS, scopes: ['https://www.googleapis.com/auth/chat.bot'], }); const subscriptionPath = new SubscriberClient() .subscriptionPath(process.env.PROJECT_ID, process.env.SUBSCRIPTION_ID) const subscription = new PubSub() .subscription(subscriptionPath); // Handle incoming message, then acknowledge the received message const messageHandler = message => { console.log(`Id : ${message.id}`); const event = JSON.parse(message.data); console.log(`Data : ${JSON.stringify(event)}`); // Post the response to Google Chat. const request = formatRequest(event); if (request != null) { chat.createMessage(request); } // Acknowledge the message. message.ack(); } subscription.on('message', messageHandler); console.log(`Listening for messages on ${subscriptionPath}`); // Keep main thread from exiting while waiting for messages setTimeout(() => { subscription.removeListener('message', messageHandler); console.log(`Stopped listening for messages.`); }, 60 * 1000); } // Send message to Google Chat based on the type of event function formatRequest(event) { const chatEvent = event.chat || {}; // If the app was removed, we don't respond. if (chatEvent.removedFromSpacePayload) { console.log(`App removed from space.`); return null; } const payload = chatEvent.messagePayload || chatEvent.addedToSpacePayload; const spaceName = payload?.space?.name; if (!spaceName) { console.log('No space name in event.'); return null; } if (chatEvent.addedToSpacePayload) { // An app can also be added to a space by @mentioning it in a // message. In that case, we fall through to the message case // and let the app respond. If the app was added using the // invite flow, we just post a thank you message in the space. return { parent: spaceName, message: { text: 'Thank you for adding me!' }, }; } else if (chatEvent.messagePayload) { // In case of message, post the response in the same thread. const message = chatEvent.messagePayload.message; return { parent: spaceName, messageReplyOption: MessageReplyOption.REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD, message: { text: 'You said: `' + message.text + '`', thread: { name: message.thread.name }, }, }; } } if (!process.env.PROJECT_ID) { console.log('Missing PROJECT_ID env var.'); process.exit(1); } if (!process.env.SUBSCRIPTION_ID) { console.log('Missing SUBSCRIPTION_ID env var.'); process.exit(1); } if (!process.env.GOOGLE_APPLICATION_CREDENTIALS) { console.log('Missing GOOGLE_APPLICATION_CREDENTIALS env var.'); process.exit(1); } receiveMessages();
Python
В командной строке укажите учетные данные сервисной учетной записи :
export GOOGLE_APPLICATION_CREDENTIALS=SERVICE_ACCOUNT_FILE_PATHВ командной строке укажите идентификатор проекта Google Cloud:
export PROJECT_ID=PROJECT_IDВ командной строке укажите идентификатор подписки для ранее созданной подписки Pub/Sub:
export SUBSCRIPTION_ID=SUBSCRIPTION_IDВ рабочей директории создайте файл с именем
requirements.txt.В файл
requirements.txtвставьте следующий код:google-cloud-pubsub>=2.23.0 google-apps-chat==0.1.9В рабочей директории создайте файл с именем
app.pyВ
app.pyвставьте следующий код:import json import logging import os import sys import time from google.apps import chat_v1 as google_chat from google.cloud import pubsub_v1 from google.oauth2.service_account import Credentials def receive_messages(): """Receives messages from a pull subscription.""" scopes = ['https://www.googleapis.com/auth/chat.bot'] service_account_key_path = os.environ.get('GOOGLE_APPLICATION_CREDENTIALS') creds = Credentials.from_service_account_file(service_account_key_path) chat = google_chat.ChatServiceClient( credentials=creds, client_options={'scopes': scopes} ) project_id = os.environ.get('PROJECT_ID') subscription_id = os.environ.get('SUBSCRIPTION_ID') subscriber = pubsub_v1.SubscriberClient() subscription_path = subscriber.subscription_path(project_id, subscription_id) # Handle incoming message, then acknowledge the received message def callback(message): event = json.loads(message.data) logging.info('Data : %s', event) # Post the response to Google Chat. request = format_request(event) if request is not None: chat.create_message(request) # Acknowledge the message. message.ack() subscriber.subscribe(subscription_path, callback = callback) logging.info('Listening for messages on %s', subscription_path) # Keep main thread from exiting while waiting for messages while True: time.sleep(60) def format_request(event): """Send message to Google Chat based on the type of event. Args: event: A dictionary with the event data. """ chat_event = event.get('chat', {}) # If the app was removed, we don't respond. if 'removedFromSpacePayload' in chat_event: logging.info('App removed from space.') return payload = chat_event.get('messagePayload') or chat_event.get( 'addedToSpacePayload' ) space_name = payload.get('space', {}).get('name') if payload else None if not space_name: logging.warning('No space name in event.') return if 'addedToSpacePayload' in chat_event: # An app can also be added to a space by @mentioning it in a # message. In that case, we fall through to the message case # and let the app respond. If the app was added using the # invite flow, we just post a thank you message in the space. return google_chat.CreateMessageRequest( parent = space_name, message = { 'text': 'Thank you for adding me!' } ) elif 'messagePayload' in chat_event: # In case of message, post the response in the same thread. message = chat_event['messagePayload']['message'] return google_chat.CreateMessageRequest( parent = space_name, message_reply_option = google_chat.CreateMessageRequest.MessageReplyOption.REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD, message = { 'text': 'You said: `' + message['text'] + '`', 'thread': { 'name': message['thread']['name'] } } ) if __name__ == '__main__': if 'PROJECT_ID' not in os.environ: logging.error('Missing PROJECT_ID env var.') sys.exit(1) if 'SUBSCRIPTION_ID' not in os.environ: logging.error('Missing SUBSCRIPTION_ID env var.') sys.exit(1) if 'GOOGLE_APPLICATION_CREDENTIALS' not in os.environ: logging.error('Missing GOOGLE_APPLICATION_CREDENTIALS env var.') sys.exit(1) logging.basicConfig( level=logging.INFO, style='{', format='{levelname:.1}{asctime} {filename}:{lineno}] {message}') receive_messages()
Java
В командной строке укажите учетные данные сервисной учетной записи :
export GOOGLE_APPLICATION_CREDENTIALS=SERVICE_ACCOUNT_FILE_PATHВ командной строке укажите идентификатор проекта Google Cloud:
export PROJECT_ID=PROJECT_IDВ командной строке укажите идентификатор подписки для ранее созданной подписки Pub/Sub:
export SUBSCRIPTION_ID=SUBSCRIPTION_IDВ рабочей директории создайте файл с именем
pom.xml.В файл
pom.xmlвставьте следующий код:<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"> <modelVersion>4.0.0</modelVersion> <groupId>com.google.chat.addon</groupId> <artifactId>pubsub-addon-chat-app</artifactId> <version>0.1.0</version> <name>pubsub-addon-chat-app-java</name> <properties> <maven.compiler.release>11</maven.compiler.release> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>com.google.cloud</groupId> <artifactId>libraries-bom</artifactId> <version>26.41.0</version> <!-- Use a recent BOM version --> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <!-- Google Chat GAPIC library --> <dependency> <groupId>com.google.cloud</groupId> <artifactId>google-cloud-chat</artifactId> </dependency> <!-- Google Cloud Pub/Sub library --> <dependency> <groupId>com.google.cloud</groupId> <artifactId>google-cloud-pubsub</artifactId> </dependency> <!-- Google Apps Add-ons Event Object --> <dependency> <groupId>com.google.apps.addons.v1</groupId> <artifactId>google-apps-addons-v1-java</artifactId> <version>0.2.0</version> <!-- Check for latest version --> </dependency> <!-- Protobuf JSON utility --> <dependency> <groupId>com.google.protobuf</groupId> <artifactId>protobuf-java-util</artifactId> </dependency> <!-- Google Auth Library --> <dependency> <groupId>com.google.auth</groupId> <artifactId>google-auth-library-oauth2-http</artifactId> </dependency> <dependency> <groupId>com.google.api</groupId> <artifactId>gax</artifactId> </dependency> <!-- JSON utilities for PubSub message (if needed, though protobuf-java-util is primary for EventObject) --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.14.2</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-jdk14</artifactId> <version>1.7.36</version> <scope>runtime</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.13.0</version> <configuration> <source>11</source> <target>11</target> </configuration> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>3.3.0</version> <configuration> <mainClass>Main</mainClass> </configuration> </plugin> </plugins> </build> </project>В рабочей директории создайте структуру каталогов
src/main/java.В каталоге
src/main/javaсоздайте файл с именемMain.java.В
Main.javaвставьте следующий код:import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.api.gax.core.FixedCredentialsProvider; import com.google.auth.oauth2.GoogleCredentials; import com.google.chat.v1.ChatServiceClient; import com.google.chat.v1.ChatServiceSettings; import com.google.chat.v1.CreateMessageRequest; import com.google.chat.v1.CreateMessageRequest.MessageReplyOption; import com.google.chat.v1.Message; import com.google.chat.v1.Thread; import com.google.cloud.pubsub.v1.AckReplyConsumer; import com.google.cloud.pubsub.v1.MessageReceiver; import com.google.cloud.pubsub.v1.Subscriber; import com.google.pubsub.v1.ProjectSubscriptionName; import com.google.pubsub.v1.PubsubMessage; import java.io.FileInputStream; import java.util.Collections; public class Main { public static final String PROJECT_ID_ENV_PROPERTY = "PROJECT_ID"; public static final String SUBSCRIPTION_ID_ENV_PROPERTY = "SUBSCRIPTION_ID"; public static final String CREDENTIALS_PATH_ENV_PROPERTY = "GOOGLE_APPLICATION_CREDENTIALS"; public static void main(String[] args) throws Exception { ProjectSubscriptionName subscriptionName = ProjectSubscriptionName.of( System.getenv(Main.PROJECT_ID_ENV_PROPERTY), System.getenv(Main.SUBSCRIPTION_ID_ENV_PROPERTY)); // Instantiate app, which implements an asynchronous message receiver. EchoApp echoApp = new EchoApp(); // Create a subscriber for <var>SUBSCRIPTION_ID</var> bound to the message receiver final Subscriber subscriber = Subscriber.newBuilder(subscriptionName, echoApp).build(); System.out.println("Subscriber is listening to events..."); subscriber.startAsync(); // Wait for termination subscriber.awaitTerminated(); } } /** * A demo app which implements {@link MessageReceiver} to receive messages. * It echoes incoming messages. */ class EchoApp implements MessageReceiver { // Path to the private key JSON file of the service account to be used for posting response // messages to Google Chat. // In this demo, we are using the same service account for authorizing with Cloud Pub/Sub to // receive messages and authorizing with Google Chat to post messages. If you are using // different service accounts, set the path to the private key JSON file of the service // account used to post messages to Google Chat here. private static final String SERVICE_ACCOUNT_KEY_PATH = System.getenv(Main.CREDENTIALS_PATH_ENV_PROPERTY); // Developer code for Google Chat API scope. private static final String GOOGLE_CHAT_API_SCOPE = "https://www.googleapis.com/auth/chat.bot"; private static final String ADDED_RESPONSE = "Thank you for adding me!"; ChatServiceClient chatServiceClient; EchoApp() throws Exception { GoogleCredentials credential = GoogleCredentials.fromStream(new FileInputStream(SERVICE_ACCOUNT_KEY_PATH)) .createScoped(Collections.singleton(GOOGLE_CHAT_API_SCOPE)); // Create the ChatServiceSettings with the app credentials ChatServiceSettings chatServiceSettings = ChatServiceSettings.newBuilder() .setCredentialsProvider(FixedCredentialsProvider.create(credential)) .build(); // Set the Chat service client chatServiceClient = ChatServiceClient.create(chatServiceSettings); } // Called when a message is received by the subscriber. @Override public void receiveMessage(PubsubMessage pubsubMessage, AckReplyConsumer consumer) { System.out.println("Id : " + pubsubMessage.getMessageId()); // Handle incoming message, then acknowledge the received message try { ObjectMapper mapper = new ObjectMapper(); JsonNode dataJson = mapper.readTree(pubsubMessage.getData().toStringUtf8()); System.out.println("Data : " + dataJson.toString()); handle(dataJson); consumer.ack(); } catch (Exception e) { System.out.println(e); // Negative acknowledgement makes Pub/Sub redeliver the message. consumer.nack(); } } // Send message to Google Chat based on the type of event. public void handle(JsonNode eventJson) throws Exception { // Google Chat events for add-ons are wrapped in a 'chat' object. if (!eventJson.has("chat")) { System.out.println("Ignored: Not a Chat event (missing 'chat' field)."); return; } JsonNode chatNode = eventJson.get("chat"); CreateMessageRequest createMessageRequest = null; if (chatNode.has("messagePayload")) { // HANDLE MESSAGE JsonNode messagePayload = chatNode.get("messagePayload"); JsonNode message = messagePayload.get("message"); JsonNode space = messagePayload.get("space"); String spaceName = space.get("name").asText(); String userText = message.has("text") ? message.get("text").asText() : ""; String threadName = message.has("thread") ? message.get("thread").get("name").asText() : ""; System.out.println("Received message in " + spaceName + ": " + userText); createMessageRequest = CreateMessageRequest.newBuilder() .setParent(spaceName) .setMessageReplyOption(MessageReplyOption.REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD) .setMessage( Message.newBuilder() .setText("You said: `" + userText + "`") .setThread(Thread.newBuilder().setName(threadName).build()) .build()) .build(); } else if (chatNode.has("addedToSpacePayload")) { // HANDLE ADDED TO SPACE JsonNode addedPayload = chatNode.get("addedToSpacePayload"); JsonNode space = addedPayload.get("space"); String spaceName = space.get("name").asText(); System.out.println("Added to space: " + spaceName); createMessageRequest = CreateMessageRequest.newBuilder() .setParent(spaceName) .setMessage(Message.newBuilder().setText(ADDED_RESPONSE).build()) .build(); } else if (chatNode.has("removedFromSpacePayload")) { System.out.println("Removed from space."); return; } else { System.out.println("Ignored: Unhandled Chat event type."); return; } if (createMessageRequest != null) { // Post the response to Google Chat. chatServiceClient.createMessage(createMessageRequest); System.out.println("Sent reply."); } } }
Настройте приложение «Чат».
Настройте приложение «Чат» в консоли Google Cloud, указав такие данные, как его имя и аватар, а также установив соединение с темой Pub/Sub.
Подключившись к теме Pub/Sub, вы позволяете чату отправлять события в ваше приложение. Ваш скрипт, подписанный на эту тему, сможет получать эти события и отвечать пользователям.
В консоли Google Cloud перейдите в > API и сервисы > Включенные API и сервисы > Google Chat API > Конфигурация .
Настройте приложение «Чат» для режима публикации/подписки:
- В поле «Название приложения» введите
Add-on Chat App. - В поле "URL аватара" введите
https://developers.google.com/workspace/add-ons/images/quickstart-app-avatar.png. - В поле «Описание» введите
Quickstart app. - В разделе «Функциональность» выберите «Присоединяйтесь к пространствам и групповым беседам» .
- В разделе «Настройки подключения» выберите Cloud Pub/Sub и вставьте имя темы Pub/Sub, которую вы создали ранее.
- В разделе «Видимость» выберите «Сделать это приложение Google Chat доступным для определенных людей и групп в вашем домене» и введите свой адрес электронной почты.
- В разделе «Журналы» выберите « Записывать ошибки в журнал» и перейдите в раздел «Ведение журнала» .
- В поле «Название приложения» введите
Нажмите « Сохранить ».
Теперь, когда вы настроили приложение «Чат», вам необходимо обновить конфигурацию Pub/Sub.
- На странице конфигурации Chat API в разделе «Настройки подключения» скопируйте адрес электронной почты сервисной учетной записи , который является уникальным адресом, сгенерированным для вашего проекта Google Cloud.
- Предоставьте Chat разрешение на публикацию в теме, назначив роль издателя/подписчика (Pub/Sub Publisher) адресу электронной почты сервисной учетной записи, который вы ранее скопировали.
Приложение готово к приему и ответам на сообщения в чате.
Запустите скрипт
В командной строке перейдите в свой рабочий каталог и запустите скрипт:
Node.js
npm install
npm start
Python
python -m venv env
source env/bin/activate
pip install -r requirements.txt -U
python app.py
Java
mvn compile exec:java -Dexec.mainClass=Main
При запуске кода приложение начинает прослушивать сообщения, публикуемые в топик Pub/Sub.
Протестируйте свое приложение для чата
Чтобы протестировать приложение «Чат», откройте личное сообщение в приложении «Чат» и отправьте сообщение:
Откройте Google Chat, используя учетную запись Google Workspace, которую вы указали при добавлении себя в качестве доверенного тестировщика.
- Нажмите новый чат» .
- В поле «Добавить 1 или более человек» введите название вашего приложения для чата.
Выберите ваше приложение для чата из результатов поиска. Откроется личное сообщение.
- В новом личном сообщении в приложении напишите
Helloи нажмитеenter.
Чтобы добавить доверенных тестировщиков и узнать больше о тестировании интерактивных функций, см. раздел «Тестирование интерактивных функций для приложений Google Chat» .
Устранение неполадок
Когда приложение или карточка Google Chat выдает ошибку, интерфейс чата отображает сообщение «Что-то пошло не так» или «Не удалось обработать ваш запрос». Иногда интерфейс чата не отображает никаких сообщений об ошибке, но приложение или карточка чата выдает неожиданный результат; например, сообщение на карточке может не появиться.
Хотя сообщение об ошибке может не отображаться в пользовательском интерфейсе чата, подробные сообщения об ошибках и данные журнала доступны для исправления ошибок, если включено ведение журнала ошибок для приложений чата. Для получения помощи по просмотру, отладке и исправлению ошибок см. раздел «Устранение неполадок и исправление ошибок Google Chat» .
Уборка
Чтобы избежать списания средств с вашего аккаунта Google Cloud за ресурсы, использованные в этом руководстве, мы рекомендуем удалить проект Cloud.
- В консоли Google Cloud перейдите на страницу «Управление ресурсами» > IAM и администрирование > Управление ресурсами .
- В списке проектов выберите проект, который хотите удалить, и нажмите кнопку «Удалить .
- В диалоговом окне введите идентификатор проекта, а затем нажмите «Завершить» , чтобы удалить проект.
Связанные темы
Чтобы добавить в ваше приложение для чата дополнительные функции, см. следующие разделы: