Создайте приложение Google Chat за брандмауэром с помощью Pub/Sub.

На этой странице объясняется, как создать приложение Chat с использованием Pub/Sub . Этот тип архитектуры для приложения Chat полезен, если в вашей организации используется брандмауэр, который может помешать Chat отправлять сообщения в ваше приложение Chat, или если приложение Chat использует API Google Workspace Events . Однако эта архитектура имеет следующие ограничения, поскольку эти приложения Chat могут отправлять и получать только асинхронные сообщения :

  • Диалоги в сообщениях использовать нельзя. Вместо этого используйте сообщение-карточку .
  • Невозможно обновить отдельные карточки синхронным ответом. Вместо этого обновите всё сообщение, вызвав метод patch .

На следующей диаграмме показана архитектура приложения чата, созданного с помощью Pub/Sub:

Архитектура чат-приложения, реализованного с помощью Pub/Sub.

На предыдущей диаграмме пользователь, взаимодействующий с приложением Pub/Sub Chat, имеет следующий поток информации:

  1. Пользователь отправляет сообщение в чате в приложение чата, либо в прямом сообщении, либо в пространстве чата, или в пространстве чата происходит событие, на которое у приложения чата есть активная подписка .

  2. Чат отправляет сообщение в тему Pub/Sub.

  3. Сервер приложений, представляющий собой либо облачную, либо локальную систему, содержащую логику приложения чата, подписывается на тему Pub/Sub, чтобы получать сообщения через брандмауэр.

  4. При желании приложение чата может вызывать API чата для асинхронной публикации сообщений или выполнения других операций.

Предпосылки

Ява

Питон

Node.js

  • Учетная запись Google Workspace Business или Enterprise с доступом к Google Chat .
  • Проект Google Cloud с включённым биллингом. Чтобы проверить, включён ли биллинг для существующего проекта, см. раздел Проверка статуса биллинга ваших проектов . Чтобы создать проект и настроить биллинг, см. раздел Создание проекта Google Cloud .
  • Node.js 14 или выше
  • Инструмент управления пакетами npm
  • Инициализированный проект Node.js. Чтобы инициализировать новый проект, создайте и перейдите в новую папку, а затем выполните следующую команду в интерфейсе командной строки:
    npm init

Настройте среду

Перед использованием API Google необходимо включить их в проекте Google Cloud. Вы можете включить один или несколько API в одном проекте Google Cloud.
  • В консоли Google Cloud включите API Google Chat и API Pub/Sub.

    Включить API

Настроить Pub/Sub

  1. Создайте тему Pub/Sub , в которую API чата сможет отправлять сообщения. Мы рекомендуем использовать одну тему для каждого приложения чата.

  2. Предоставьте чату разрешение на публикацию в теме, назначив роль издателя Pub/Sub следующей учетной записи службы:

    chat-api-push@system.gserviceaccount.com
    
  3. Создайте учетную запись службы для приложения Chat, чтобы авторизоваться в Pub/Sub и Chat, и сохраните файл закрытого ключа в рабочем каталоге.

  4. Создайте подписку на рассылку по теме.

  5. Назначьте роль подписчика Pub/Sub для подписки на учетную запись службы, которую вы ранее создали.

Написать сценарий

Ява

  1. В CLI укажите учетные данные учетной записи службы :

    export GOOGLE_APPLICATION_CREDENTIALS=SERVICE_ACCOUNT_FILE_PATH
    
  2. В CLI укажите идентификатор проекта Google Cloud:

    export PROJECT_ID=PROJECT_ID
    
  3. В CLI укажите идентификатор подписки Pub/Sub, которую вы создали ранее:

    export SUBSCRIPTION_ID=SUBSCRIPTION_ID
    
  4. В рабочем каталоге создайте файл с именем pom.xml .

  5. В файл pom.xml вставьте следующий код:

    java/pub-sub-app/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</groupId>
      <artifactId>pub-sub-app</artifactId>
      <version>0.1.0</version>
    
      <name>pub-sub-app-java</name>
    
      <properties>
        <maven.compiler.release>21</maven.compiler.release>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
      </properties>
    
      <dependencies>
        <!-- Google Chat GAPIC library -->
        <dependency>
          <groupId>com.google.api.grpc</groupId>
          <artifactId>proto-google-cloud-chat-v1</artifactId>
          <version>0.8.0</version>
        </dependency>
        <dependency>
          <groupId>com.google.api</groupId>
          <artifactId>gax</artifactId>
          <version>2.48.1</version>
        </dependency>
        <dependency>
          <groupId>com.google.cloud</groupId>
          <artifactId>google-cloud-chat</artifactId>
          <version>0.1.0</version>
        </dependency>
        <!-- Google Cloud Pub/Sub library -->
        <dependency>
          <groupId>com.google.cloud</groupId>
          <artifactId>google-cloud-pubsub</artifactId>
        <version>1.125.8</version>
        </dependency>
        <!-- JSON utilities -->
        <dependency>
          <groupId>com.fasterxml.jackson.core</groupId>
          <artifactId>jackson-databind</artifactId>
          <version>2.14.2</version>
        </dependency>
      </dependencies>
    
    </project>
  6. В рабочем каталоге создайте структуру каталогов src/main/java .

  7. В каталоге src/main/java создайте файл с именем Main.java .

  8. В Main.java вставьте следующий код:

    java/pub-sub-app/src/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 simply echoes the
     * 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, please 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 ack/nack 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);
          consumer.nack();
        }
      }
    
      // Send message to Google Chat based on the type of event.
      public void handle(JsonNode eventJson) throws Exception {
        CreateMessageRequest createMessageRequest;
        switch (eventJson.get("type").asText()) {
          case "ADDED_TO_SPACE":
            // 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.
            if (!eventJson.has("message")) {
              createMessageRequest = CreateMessageRequest.newBuilder()
                .setParent(eventJson.get("space").get("name").asText())
                .setMessage(Message.newBuilder().setText(ADDED_RESPONSE).build()).build();
              break;
            }
          case "MESSAGE":
            // In case of message, post the response in the same thread.
            createMessageRequest = CreateMessageRequest.newBuilder()
              .setParent(eventJson.get("space").get("name").asText())
              .setMessageReplyOption(MessageReplyOption.REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD)
              .setMessage(Message.newBuilder()
                .setText("You said: `" + eventJson.get("message").get("text").asText() + "`")
                .setThread(Thread.newBuilder()
                  .setName(eventJson.get("message").get("thread").get("name").asText())
                  .build()).build()).build();
            break;
          case "REMOVED_FROM_SPACE":
          default:
            // Do nothing
            return;
        }
    
        // Post the response to Google Chat.
        chatServiceClient.createMessage(createMessageRequest);
      }
    }

Питон

  1. В CLI укажите учетные данные учетной записи службы :

    export GOOGLE_APPLICATION_CREDENTIALS=SERVICE_ACCOUNT_FILE_PATH
    
  2. В CLI укажите идентификатор проекта Google Cloud:

    export PROJECT_ID=PROJECT_ID
    
  3. В CLI укажите идентификатор подписки Pub/Sub, которую вы создали ранее:

    export SUBSCRIPTION_ID=SUBSCRIPTION_ID
    
  4. В рабочем каталоге создайте файл с именем requirements.txt .

  5. В файл requirements.txt вставьте следующий код:

    python/pub-sub-app/requirements.txt
    google-cloud-pubsub>=2.23.0
    google-apps-chat==0.1.9
  6. В рабочем каталоге создайте файл с именем app.py

  7. В app.py вставьте следующий код:

    python/pub-sub-app/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 ack/nack the received message
      def callback(message):
        event = json.loads(message.data)
        logging.info('Data : %s', event)
        space_name = event['space']['name']
    
        # Post the response to Google Chat.
        request = format_request(event)
        if request is not None:
          chat.create_message(request)
    
        # Ack 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.
      """
      space_name = event['space']['name']
      event_type = event['type']
    
      # If the app was removed, we don't respond.
      if event['type'] == 'REMOVED_FROM_SPACE':
        logging.info('App removed rom space %s', space_name)
        return
      elif event_type == 'ADDED_TO_SPACE' and 'message' not in 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 event_type in ['ADDED_TO_SPACE', 'MESSAGE']:
        # In case of message, post the response in the same thread.
        return google_chat.CreateMessageRequest(
            parent = space_name,
            message_reply_option = google_chat.CreateMessageRequest.MessageReplyOption.REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD,
            message = {
              'text': 'You said: `' + event['message']['text'] + '`',
              'thread': {
                'name': event['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()

Node.js

  1. В CLI укажите учетные данные учетной записи службы :

    export GOOGLE_APPLICATION_CREDENTIALS=SERVICE_ACCOUNT_FILE_PATH
    
  2. В CLI укажите идентификатор проекта Google Cloud:

    export PROJECT_ID=PROJECT_ID
    
  3. В CLI укажите идентификатор подписки Pub/Sub, которую вы создали ранее:

    export SUBSCRIPTION_ID=SUBSCRIPTION_ID
    
  4. В рабочем каталоге создайте файл с именем package.json .

  5. В файл package.json вставьте следующий код:

    узел/pub-sub-app/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"
    }
  6. В рабочем каталоге создайте файл с именем index.js .

  7. В index.js вставьте следующий код:

    node/pub-sub-app/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 ack/nack 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);
        }
    
        // Ack 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 spaceName = event.space.name;
      const eventType = event.type;
    
      // If the app was removed, we don't respond.
      if (event.type == 'REMOVED_FROM_SPACE') {
        console.log(`App removed rom space ${spaceName}`);
        return null;
      } else if (eventType == 'ADDED_TO_SPACE' && !eventType.message) {
        // 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 (eventType == 'ADDED_TO_SPACE' || eventType == 'MESSAGE') {
        // In case of message, post the response in the same thread.
        return {
          parent: spaceName,
          messageReplyOption: MessageReplyOption.REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD,
          message: {
            text: 'You said: `' + event.message.text + '`',
            thread: { name: event.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();

Опубликовать приложение в чате

  1. В консоли Google Cloud перейдите в > API и службы > Включенные API и службы > API Google Chat > ​​Конфигурация .

    Перейти к конфигурации

  2. Настройте приложение чата для Pub/Sub:

    1. В поле «Имя приложения» введите Quickstart App .
    2. В поле URL аватара введите https://developers.google.com/chat/images/quickstart-app-avatar.png .
    3. В поле Описание введите Quickstart app .
    4. В разделе «Функциональность» выберите Присоединяйтесь к пространствам и групповым беседам .
    5. В разделе «Настройки подключения » выберите Cloud Pub/Sub и вставьте имя темы Pub/Sub, которую вы ранее создали.
    6. В разделе «Видимость» выберите «Сделать это приложение Google Chat доступным для определенных людей и групп в вашем домене» и введите свой адрес электронной почты.
    7. В разделе Журналы выберите Записывать ошибки в Журнал .
  3. Нажмите «Сохранить» .

Приложение готово принимать и отвечать на сообщения в чате.

Запустить скрипт

В CLI перейдите в рабочий каталог и запустите скрипт:

Ява

mvn compile exec:java -Dexec.mainClass=Main

Питон

python -m venv env
source env/bin/activate
pip install -r requirements.txt -U
python app.py

Node.js

npm install
npm start

При запуске кода приложение начинает прослушивать сообщения, опубликованные в теме Pub/Sub.

Протестируйте свое приложение чата

Чтобы протестировать приложение Chat, откройте чат-комнату в приложении и отправьте сообщение:

  1. Откройте Google Chat, используя учетную запись Google Workspace, которую вы указали при добавлении себя в качестве доверенного тестировщика.

    Перейти в Google Чат

  2. Нажмите новый чат» .
  3. В поле Добавить 1 или более человек введите название вашего чат-приложения.
  4. Выберите приложение чата из результатов. Откроется личное сообщение.

  5. В новом прямом сообщении с приложением введите Hello и нажмите enter .

Чтобы добавить доверенных тестировщиков и узнать больше о тестировании интерактивных функций, ознакомьтесь с разделом Тестирование интерактивных функций для приложений Google Chat .

Устранение неполадок

Когда приложение или карточка Google Chat возвращает ошибку, в интерфейсе Chat отображается сообщение «Что-то пошло не так» или «Не удалось обработать ваш запрос». Иногда в интерфейсе Chat не отображается сообщение об ошибке, но приложение или карточка Chat выдаёт неожиданный результат; например, сообщение может не появиться.

Хотя сообщение об ошибке может не отображаться в пользовательском интерфейсе чата, при включенном ведении журнала ошибок для приложений чата доступны описательные сообщения об ошибках и данные журнала, которые помогут вам исправить ошибки. Сведения о просмотре, отладке и исправлении ошибок см. в статье «Устранение неполадок и исправление ошибок Google Chat» .

Уборка

Чтобы избежать списания средств с вашего аккаунта Google Cloud за ресурсы, используемые в этом руководстве, мы рекомендуем вам удалить проект Cloud.

  1. В консоли Google Cloud перейдите на страницу «Управление ресурсами» . Выберите « Меню > «IAM и администрирование» > «Управление ресурсами» .

    Перейти к диспетчеру ресурсов

  2. В списке проектов выберите проект .
  3. В диалоговом окне введите идентификатор проекта, а затем нажмите кнопку «Завершить» , чтобы удалить проект.