接收和回复 Google Chat 事件

本页介绍了您的 Google Chat 应用如何接收、处理和响应来自 Google Chat 的事件。

当用户与 Google Chat 应用互动时,该应用会同步接收并能响应事件事件类型的示例包括消息(包含斜杠命令和用“@”提及)、卡片点击以及被添加到聊天室或从中移除。

聊天应用可以通过多种方式回复这些活动。例如,Chat 应用可以发送短信卡片消息,每一条都表示为一个 JSON 对象。

短信

短信非常适合用于简单通知。它们支持用 @提及你的消息,以及粗体斜体code 等基本格式。

例如,应用可能会使用短信来通知软件开发者代码冻结即将发生:

Google Chat 中公布代码冻结问题的短信示例
图 1:有关 Chat 聊天室出现代码冻结问题的短信。

如需了解详情,请参阅发送短信

卡片消息

卡片消息支持定义的布局、交互式界面元素(如按钮)和富媒体(如图片)。使用卡片消息呈现详细信息、向用户收集信息并引导用户采取下一步行动。卡片消息可以单独以消息的形式附加到对话信息流中,也可以附加到短信中,或者以对话窗口的形式在对话上方打开。

例如,应用可能会使用卡片消息来开展投票活动:

在 Chat 聊天室中使用卡片消息开展投票活动
图 2:卡片消息可让 Chat 聊天室中的用户参与投票。

为了帮助用户完成多步流程(例如填写表单数据),您可以在对话框中依序合并卡片。对话框会在窗口中打开,允许应用直接与用户进行互动。

例如,应用可能会启动一个对话框来收集详细联系信息:

包含各种不同微件的对话框。
图 3:一个打开的对话框,提示用户添加联系人。

如需了解详情,请参阅发送卡片消息

端点类型

如需接收和响应 Google Chat 事件,请在 Google Chat 应用的配置中指定服务端点。您可以使用以下任一端点类型:

  • HTTPS 端点将您的应用显示为网络服务。您必须设置一个 Web 服务器,用作应用的实现接口。您的应用可以同步异步响应这些事件。
  • Google Cloud Pub/Sub 端点使用 Google Cloud Pub/Sub 上的主题将事件中继到应用的实现。如果您的实现位于防火墙后面,这一点非常有用。使用 Pub/Sub 端点的应用只能异步响应,并且需要服务帐号
  • DialogFlow 端点可让您的应用利用 DialogFlow 的自然语言处理 (NLP) 功能。如需了解详情,请参阅 DialogFlow 文档

对于简单直接的应用架构,请尝试使用可实现同步响应的 HTTPS 端点(基本上是网络服务)来实现应用,并始终将其载荷包含在 HTTPS POST 响应中。此方法不涉及授权,因此不需要服务帐号。请参见下文中的简单应用实现部分,查看此类应用的示例。

异步响应的应用(包括 Pub/Sub 端点上的所有应用)需要服务帐号才能向 Google Chat 授权。如果应用受到防火墙保护,或向 Google Chat 发送自发消息(例如闹钟或其他通知),您可能需要采用更复杂的方法。

一个非常简单的应用实现

以下代码使用 Flask Web 框架在 Python 中实现一个简单的应用。

#!/usr/bin/env python3
"""Example app that returns a synchronous response."""

from flask import Flask, request, json


app = Flask(__name__)


@app.route('/', methods=['POST'])
def on_event():
  """Handles an event from Google Chat."""
  event = request.get_json()
  if event['type'] == 'ADDED_TO_SPACE' and not event['space']['singleUserBotDm']:
    text = 'Thanks for adding me to "%s"!' % (event['space']['displayName'] if event['space']['displayName'] else 'this chat')
  elif event['type'] == 'MESSAGE':
    text = 'You said: `%s`' % event['message']['text']
  else:
    return
  return json.jsonify({'text': text})


if __name__ == '__main__':
  app.run(port=8080, debug=True)

由于应用是 Web 服务,因此应用会显示 HTTPS 端点,并且无需使用 Cloud Pub/Sub 向其中继事件。由于它始终在 JSON 响应中返回其响应载荷,因此不需要使用服务帐号进行身份验证。

处理来自 Google Chat 的事件

本部分介绍如何接收和处理您的应用从 Google Chat 收到的事件。

注册应用

发布应用之前,您必须在 Chat API 配置标签页中指定应用端点,这样您的应用才能接收来自 Google Chat 的事件。

注册端点并发布应用后,Google Chat 会识别发送到您的应用的事件,并将其分派到指定的端点。

验证应用的真实性

注册 HTTPS 应用后,您需要通过一种方法让您的实现验证请求是否确实来自 Google。

Google Chat 会在发送给应用的每个 HTTPS 请求的 Authorization 标头中包含不记名令牌。例如:

POST
Host: yourappurl.com
Authorization: Bearer AbCdEf123456
Content-Type: application/json
User-Agent: Google-Dynamite

上例中的字符串 AbCdEf123456 是不记名授权令牌。这是 Google 生成的加密令牌。您可以使用开源 Google API 客户端库验证不记名令牌:

通过 Google Chat 发送的请求的所有不记名令牌的 chat@system.gserviceaccount.com 都是问题发出者,audience 字段可用于指定目标应用通过 Google Cloud Console 的项目编号。例如,如果请求针对的是项目编号为 1234567890 的应用,则目标设备为 1234567890

您应验证请求是否来自 Google 并针对目标应用。如果令牌未验证,应用应使用 HTTPS 响应代码 401 (Unauthorized) 响应请求。

Java

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Collections;

import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;
import com.google.api.client.googleapis.auth.oauth2.GooglePublicKeysManager;
import com.google.api.client.http.apache.ApacheHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson.JacksonFactory;

/** Tool for verifying JWT Tokens for Apps in Google Chat. */
public class JWTVerify {
  // Bearer Tokens received by apps will always specify this issuer.
  static String CHAT_ISSUER = "chat@system.gserviceaccount.com";

  // Url to obtain the public certificate for the issuer.
  static String PUBLIC_CERT_URL_PREFIX =
      "https://www.googleapis.com/service_accounts/v1/metadata/x509/";

  // Intended audience of the token, which will be the project number of the app.
  static String AUDIENCE = "1234567890";

  // Get this value from the request's Authorization HTTPS header.
  // For example, for "Authorization: Bearer AbCdEf123456" use "AbCdEf123456"
  static String BEARER_TOKEN = "AbCdEf123456";

  public static void main(String[] args) throws GeneralSecurityException, IOException {
    JsonFactory factory = new JacksonFactory();

    GooglePublicKeysManager.Builder keyManagerBuilder =
        new GooglePublicKeysManager.Builder(new ApacheHttpTransport(), factory);

    String certUrl = PUBLIC_CERT_URL_PREFIX + CHAT_ISSUER;
    keyManagerBuilder.setPublicCertsEncodedUrl(certUrl);

    GoogleIdTokenVerifier.Builder verifierBuilder =
        new GoogleIdTokenVerifier.Builder(keyManagerBuilder.build());
    verifierBuilder.setIssuer(CHAT_ISSUER);
    GoogleIdTokenVerifier verifier = verifierBuilder.build();

    GoogleIdToken idToken = GoogleIdToken.parse(factory, BEARER_TOKEN);
    if (idToken == null) {
      System.out.println("Token cannot be parsed");
      System.exit(-1);
    }

    // Verify valid token, signed by CHAT_ISSUER.
    if (!verifier.verify(idToken)
        || !idToken.verifyAudience(Collections.singletonList(AUDIENCE))
        || !idToken.verifyIssuer(CHAT_ISSUER)) {
      System.out.println("Invalid token");
      System.exit(-1);
    }

    // Token originates from Google and is targeted to a specific client.
    System.out.println("The token is valid");
  }
}

Python

import sys

from oauth2client import client

# Bearer Tokens received by apps will always specify this issuer.
CHAT_ISSUER = 'chat@system.gserviceaccount.com'

# Url to obtain the public certificate for the issuer.
PUBLIC_CERT_URL_PREFIX = 'https://www.googleapis.com/service_accounts/v1/metadata/x509/'

# Intended audience of the token, which will be the project number of the app.
AUDIENCE = '1234567890'

# Get this value from the request's Authorization HTTPS header.
# For example, for 'Authorization: Bearer AbCdEf123456' use 'AbCdEf123456'.
BEARER_TOKEN = 'AbCdEf123456'

try:
  # Verify valid token, signed by CHAT_ISSUER, intended for a third party.
  token = client.verify_id_token(
      BEARER_TOKEN, AUDIENCE, cert_uri=PUBLIC_CERT_URL_PREFIX + CHAT_ISSUER)

  if token['iss'] != CHAT_ISSUER:
    sys.exit('Invalid issuee')
except:
  sys.exit('Invalid token')

# Token originates from Google and is targeted to a specific client.
print 'The token is valid'

事件负载

当您的应用收到来自 Google Chat 的事件时,该事件会包含请求正文:即代表事件的 JSON 载荷。请求正文始终包含以下信息:

  • type:用于指定活动类型的字符串。
  • eventTime:包含事件时间戳的字符串。

请求正文中包含的其他信息取决于事件类型。 以下示例展示了可能的载荷:

{
  "type": "MESSAGE",
  "eventTime": "2017-03-02T19:02:59.910959Z",
  "space": {
    "name": "spaces/AAAAAAAAAAA",
    "displayName": "Best Dogs Discussion Space",
    "type": "ROOM"
  },
  "message": {
    "name": "spaces/AAAAAAAAAAA/messages/CCCCCCCCCCC",
    "sender": {
      "name": "users/12345678901234567890",
      "displayName": "Chris Corgi",
      "avatarUrl": "https://lh3.googleusercontent.com/.../photo.jpg",
      "email": "chriscorgi@example.com"
    },
    "createTime": "2017-03-02T19:02:59.910959Z",
    "text": "I mean is there any good reason their legs should be longer?",
    "thread": {
      "name": "spaces/AAAAAAAAAAA/threads/BBBBBBBBBBB"
    }
  }
}

如需详细了解不同事件类型及其请求格式,请参阅事件格式参考。

处理事件

当您的应用收到来自 Google Chat 的事件时,它对该事件执行的操作取决于您的实现。应用可以查找数据源中的一些信息、记录事件信息,或记录其他任何信息。这种处理行为本质上就是应用的定义。

Google Chat 应用通常会处理事件中包含的信息,并将响应生成回发起事件的线程。下图展示了用户与 Chat 聊天室中的典型互动:

应用的事件处理的架构。

上图显示了三种事件:ADDED_TO_SPACEMESSAGEREMOVED_FROM_SPACE。从聊天室中移除后,应用将无法响应,但它可以响应其他类型的消息。

同步响应

应用可以通过在 HTTPS 响应中返回 JSON 格式的消息载荷来同步响应事件。同步响应的截止时间为 30 秒。

来自应用的同步响应始终会发布到生成事件的线程中。

异步响应

如果应用需要在 30 秒内回复用户消息(例如,可能需要在完成长时间运行的任务后返回),则可以使用 Google Chat API 创建消息来异步响应

重试

如果对应用的 HTTPS 请求失败(例如超时、临时网络故障或非 2xx HTTPS 状态代码),Google Chat 会重试两次,每次重试之间至少会延迟 10 秒。因此,在某些情况下,一个应用最多可收到三次消息。如果请求成功完成,但返回无效的消息载荷,Google Chat 不会重试该请求。