为防止用户在 Google Chat 中分享链接时切换上下文,您的 Chat 应用可以通过在消息中附加卡片来预览链接,该卡片会提供更多信息,并让用户直接通过 Google Chat 执行操作。
例如,假设有一个 Google Chat 聊天室,其中包含一家公司的所有客户服务代理,以及一款名为 Case-y 的 Chat 应用。客服人员经常在 Chat 聊天室中分享客户服务支持请求的链接,每次执行此操作时,同事都必须打开支持请求链接才能查看分配对象、状态和主题等详细信息。同样,如果有人想获得某个支持请求的所有权或更改状态,则需要打开相应链接。
通过链接预览功能,每当有人分享支持请求链接时,聊天室的常驻 Chat 应用 Case-y 都可以附加一张卡片,其中显示了分配对象、状态和主题。客服人员可以通过卡片上的按钮接管支持请求,并直接在聊天信息流中更改状态。
链接预览的运作方式
当有人在消息中添加链接时,系统会显示一个条状标签,让用户知道 Chat 应用可以预览该链接。

用户发送消息后,系统会将链接发送到 Chat 应用,该应用随后会生成卡片并将其附加到用户的消息中。

在链接旁边,卡片还会提供有关链接的其他信息,包括按钮等互动元素。您的 Chat 应用可以更新附加的卡片来响应用户互动(例如点击按钮)。
如果用户不希望 Chat 应用通过在消息中附加卡片来预览链接,可以点击预览条状标签上的 cancel 来禁止预览。用户可以随时点击移除预览来移除附加的卡片。
在 Google Cloud 控制台的 Chat 应用的配置页面上,将特定链接(例如 example.com
、support.example.com
和 support.example.com/cases/
)注册为网址格式,以便您的 Chat 应用可以预览这些链接。

- 打开 Google Cloud Console。
- 点击“Google Cloud”旁边的向下箭头 arrow_drop_down,然后打开您的 Chat 应用的项目。
- 在搜索字段中输入
Google Chat API
,然后点击 Google Chat API。
- 依次点击管理 > 配置。
- 在“链接预览”下,添加或修改网址格式。
- 要为新网址格式配置链接预览,请点击添加网址格式。
- 要修改现有网址格式的配置,请点击向下箭头 expand_more。
在主机格式字段中,输入网址格式的域名。Chat 应用将预览指向此网域的链接。
如要让 Chat 应用预览特定子网域(例如 subdomain.example.com
)的链接,请添加该子网域。
如要让 Chat 应用预览整个网域的链接,请指定一个带星号 (*) 的通配符作为子网域。例如,*.example.com
与 subdomain.example.com
和 any.number.of.subdomains.example.com
匹配。
在路径前缀字段中,输入要附加到主机模式域名的路径。
如需匹配主机模式网域中的所有网址,请将路径前缀留空。
例如,如果主机格式为 support.example.com
,若要匹配托管在 support.example.com/cases/
上的案例的网址,请输入 cases/
。
点击完成。
点击保存。
现在,只要用户添加了与包含您的 Chat 应用的 Chat 聊天室中的消息相匹配的链接预览网址格式,您的应用就会预览该链接。
预览链接
为给定链接配置链接预览后,您的 Chat 应用可以通过向该链接附加更多信息来识别和预览该链接。
在包含您的 Chat 应用的 Chat 聊天室中,如果有人的消息包含与链接预览网址格式匹配的链接,您的 Chat 应用会收到 MESSAGE
互动事件。互动事件的 JSON 载荷包含 matchedUrl
字段:
JSON
message {
. . . // other message attributes redacted
"matchedUrl": {
"url": "https://support.example.com/cases/case123"
},
. . . // other message attributes redacted
}
通过检查 MESSAGE
事件载荷中是否存在 matchedUrl
字段,您的 Chat 应用可以向包含预览链接的消息添加信息。您的 Chat 应用可以使用简单的短信回复或附加一张卡片。
使用短信回复
对于简单回复,您的 Chat 应用可以通过用简单的短信回复链接来预览链接。此示例会附加一条消息,该消息重复了与链接预览网址格式匹配的链接网址。
附加卡
如需将卡片附加到预览链接,请返回 UPDATE_USER_MESSAGE_CARDS
类型的 ActionResponse
。本示例附加了一张简单的卡片。

Node.js
/**
* Responds to messages that have links whose URLs match URL patterns
* configured for link previewing.
*
* @param {Object} req Request sent from Google Chat.
* @param {Object} res Response to send back.
*/
exports.onMessage = (req, res) => {
if (req.method === 'GET' || !req.body.message) {
res.send(
'Hello! This function is meant to be used in a Google Chat Space.');
}
// Checks for the presence of event.message.matchedUrl and attaches a card
// if present
if (req.body.message.matchedUrl) {
res.json({
'actionResponse': {'type': 'UPDATE_USER_MESSAGE_CARDS'},
'cardsV2': [
{
'cardId': 'attachCard',
'card': {
'header': {
'title': 'Example Customer Service Case',
'subtitle': 'Case basics',
},
'sections': [
{
'widgets': [
{'keyValue': {'topLabel': 'Case ID', 'content': 'case123'}},
{'keyValue': {'topLabel': 'Assignee', 'content': 'Charlie'}},
{'keyValue': {'topLabel': 'Status', 'content': 'Open'}},
{
'keyValue': {
'topLabel': 'Subject', 'content': 'It won"t turn on...',
}
},
],
},
{
'widgets': [
{
'buttons': [
{
'textButton': {
'text': 'OPEN CASE',
'onClick': {
'openLink': {
'url': 'https://support.example.com/orders/case123',
},
},
},
},
{
'textButton': {
'text': 'RESOLVE CASE',
'onClick': {
'openLink': {
'url': 'https://support.example.com/orders/case123?resolved=y',
},
},
},
},
{
'textButton': {
'text': 'ASSIGN TO ME',
'onClick': {
'action': {
'actionMethodName': 'assign',
},
},
},
},
],
},
],
},
],
},
},
],
});
}
// If the Chat app doesn’t detect a link preview URL pattern, it says so.
res.json({'text': 'No matchedUrl detected.'});
};
Apps 脚本
/**
* Responds to messages that have links whose URLs match URL patterns
* configured for link previewing.
*
* @param {Object} event The event object from Chat API.
* @return {Object} Response from the Chat app attached to the message with
* the previewed link.
*/
function onMessage(event) {
// Checks for the presence of event.message.matchedUrl and attaches a card
// if present
if (event.message.matchedUrl) {
return {
'actionResponse': {
'type': 'UPDATE_USER_MESSAGE_CARDS',
},
'cardsV2': [{
'cardId': 'attachCard',
'card': {
'header': {
'title': 'Example Customer Service Case',
'subtitle': 'Case basics',
},
'sections': [{
'widgets': [
{'keyValue': {'topLabel': 'Case ID', 'content': 'case123'}},
{'keyValue': {'topLabel': 'Assignee', 'content': 'Charlie'}},
{'keyValue': {'topLabel': 'Status', 'content': 'Open'}},
{
'keyValue': {
'topLabel': 'Subject', 'content': 'It won\'t turn on...',
},
},
],
},
{
'widgets': [{
'buttons': [
{
'textButton': {
'text': 'OPEN CASE',
'onClick': {
'openLink': {
'url': 'https://support.example.com/orders/case123',
},
},
},
},
{
'textButton': {
'text': 'RESOLVE CASE',
'onClick': {
'openLink': {
'url': 'https://support.example.com/orders/case123?resolved=y',
},
},
},
},
{
'textButton': {
'text': 'ASSIGN TO ME',
'onClick': {'action': {'actionMethodName': 'assign'}},
},
},
],
}],
}],
},
}],
};
}
// If the Chat app doesn’t detect a link preview URL pattern, it says so.
return {'text': 'No matchedUrl detected.'};
}
更新银行卡
如需更新附加到预览链接的卡片,请返回类型为 UPDATE_USER_MESSAGE_CARDS
的 ActionResponse
。聊天应用只能更新用于预览链接作为对 Chat 应用互动事件的响应的卡片。聊天应用无法通过异步调用 Chat API 来更新这些卡片。
链接预览不支持返回 UPDATE_MESSAGE
类型的 ActionResponse
。由于 UPDATE_MESSAGE
会更新整条消息(而不只是卡片),因此只有在原始消息由 Chat 应用创建的情况下才能正常运行。链接预览会将卡片附加到用户创建的消息中,因此 Chat 应用无权更新该卡片。
如要确保函数同时更新 Chat 信息流中用户创建的和创建的卡片,请根据消息是 Chat 应用还是用户创建的,动态设置 ActionResponse
。
为此,您可以采用下面两种方法:在附加卡片的 onclick
属性(用于将消息标识为由用户创建)中指定并检查自定义 actionMethodName
,或者检查消息是否由用户创建。
如需使用 actionMethodName
正确处理预览卡片上的 CARD_CLICKED
互动事件,请将自定义 actionMethodName
设置为所附加卡片的 onclick
属性的一部分:
JSON
. . . // Preview card details
{
"textButton": {
"text": "ASSIGN TO ME",
"onClick": {
// actionMethodName identifies the button to help determine the
// appropriate ActionResponse.
"action": {
"actionMethodName": "assign",
}
}
}
}
. . . // Preview card details
使用 "actionMethodName": "assign"
在链接预览中标识该按钮后,可以通过检查是否存在匹配的 actionMethodName
来动态返回正确的 ActionResponse
:
方法 2:查看发件人类型
检查 message.sender.type
是 HUMAN
还是 BOT
。如果为 HUMAN
,则将 ActionResponse
设置为 UPDATE_USER_MESSAGE_CARDS
,否则将 ActionResponse
设置为 UPDATE_MESSAGE
。具体方法如下:
更新卡片的一个典型原因是响应按钮点击。复习前面附加卡片部分的分配给我按钮。下面的完整示例更新了卡片,会在用户点击分配给我后显示卡片已分配给“您”。该示例通过检查发送者类型来动态设置 ActionResponse
。
完整示例:Case-y - 客户服务 Chat 应用
以下是 Case-y 的完整代码。Case-y 是一款 Chat 应用,用于预览客服人员在协作处理的 Chat 聊天室中分享的支持请求的链接。
Node.js
/**
* Responds to messages that have links whose URLs match URL patterns
* configured for link previewing.
*
* @param {Object} req Request sent from Google Chat.
* @param {Object} res Response to send back.
*/
exports.onMessage = (req, res) => {
if (req.method === 'GET' || !req.body.message) {
res.send(
'Hello! This function is meant to be used in a Google Chat Space.');
}
// Checks for the presence of event.message.matchedUrl and attaches a card
// if present
if (req.body.message.matchedUrl) {
res.json(createMessage());
}
// Respond to button clicks on attached cards
if (req.body.type === 'CARD_CLICKED') {
// Checks whether the message event originated from a human or a Chat app
// and sets actionResponse.type to "UPDATE_USER_MESSAGE_CARDS if human or
// "UPDATE_MESSAGE" if Chat app.
const actionResponseType = req.body.action.actionMethodName === 'HUMAN' ?
'UPDATE_USER_MESSAGE_CARDS' :
'UPDATE_MESSAGE';
if (req.body.action.actionMethodName === 'assign') {
res.json(createMessage(actionResponseType, 'You'));
}
}
// If the Chat app doesn’t detect a link preview URL pattern, it says so.
res.json({'text': 'No matchedUrl detected.'});
};
/**
* Message to create a card with the correct response type and assignee.
*
* @param {string} actionResponseType
* @param {string} assignee
* @return {Object} a card with URL preview
*/
function createMessage(
actionResponseType = 'UPDATE_USER_MESSAGE_CARDS',
assignee = 'Charlie'
) {
return {
'actionResponse': {'type': actionResponseType},
'cardsV2': [
{
'cardId': 'previewLink',
'card': {
'header': {
'title': 'Example Customer Service Case',
'subtitle': 'Case basics',
},
'sections': [
{
'widgets': [
{'keyValue': {'topLabel': 'Case ID', 'content': 'case123'}},
{'keyValue': {'topLabel': 'Assignee', 'content': assignee}},
{'keyValue': {'topLabel': 'Status', 'content': 'Open'}},
{
'keyValue': {
'topLabel': 'Subject', 'content': 'It won"t turn on...',
},
},
],
},
{
'widgets': [
{
'buttons': [
{
'textButton': {
'text': 'OPEN CASE',
'onClick': {
'openLink': {
'url': 'https://support.example.com/orders/case123',
},
},
},
},
{
'textButton': {
'text': 'RESOLVE CASE',
'onClick': {
'openLink': {
'url': 'https://support.example.com/orders/case123?resolved=y',
},
},
},
},
{
'textButton': {
'text': 'ASSIGN TO ME',
'onClick': {
'action': {
'actionMethodName': 'assign',
},
},
},
},
],
},
],
},
],
}
},
],
};
}
Apps 脚本
/**
* 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.
*/
function onMessage(event) {
// Checks for the presence of event.message.matchedUrl and attaches a card
// if present
if (event.message.matchedUrl) {
return {
'actionResponse': {
'type': 'UPDATE_USER_MESSAGE_CARDS',
},
'cardsV2': [{
'cardId': 'previewLink',
'card': {
'header': {
'title': 'Example Customer Service Case',
'subtitle': 'Case basics',
},
'sections': [{
'widgets': [
{'keyValue': {'topLabel': 'Case ID', 'content': 'case123'}},
{'keyValue': {'topLabel': 'Assignee', 'content': 'Charlie'}},
{'keyValue': {'topLabel': 'Status', 'content': 'Open'}},
{
'keyValue': {
'topLabel': 'Subject', 'content': 'It won\'t turn on...',
}
},
],
},
{
'widgets': [{
'buttons': [
{
'textButton': {
'text': 'OPEN CASE',
'onClick': {
'openLink': {
'url': 'https://support.example.com/orders/case123',
},
},
},
},
{
'textButton': {
'text': 'RESOLVE CASE',
'onClick': {
'openLink': {
'url': 'https://support.example.com/orders/case123?resolved=y',
},
},
},
},
{
'textButton': {
'text': 'ASSIGN TO ME',
'onClick': {'action': {'actionMethodName': 'assign'}}
},
},
],
}],
}],
},
}],
};
}
// If the Chat app doesn’t detect a link preview URL pattern, it says so.
return {'text': 'No matchedUrl detected.'};
}
/**
* Updates a card that was attached to a message with a previewed link.
*
* @param {Object} event The event object from Chat API.
* @return {Object} Response from the Chat app. Either a new card attached to
* the message with the previewed link, or an update to an existing card.
*/
function onCardClick(event) {
// Checks whether the message event originated from a human or a Chat app
// and sets actionResponse to "UPDATE_USER_MESSAGE_CARDS if human or
// "UPDATE_MESSAGE" if Chat app.
const actionResponseType = event.message.sender.type === 'HUMAN' ?
'UPDATE_USER_MESSAGE_CARDS' :
'UPDATE_MESSAGE';
// To respond to the correct button, checks the button's actionMethodName.
if (event.action.actionMethodName === 'assign') {
return assignCase(actionResponseType);
}
}
/**
* Updates a card to say that "You" are the assignee after clicking the Assign
* to Me button.
*
* @param {String} actionResponseType Which actionResponse the Chat app should
* use to update the attached card based on who created the message.
* @return {Object} Response from the Chat app. Updates the card attached to
* the message with the previewed link.
*/
function assignCase(actionResponseType) {
return {
'actionResponse': {
// Dynamically returns the correct actionResponse type.
'type': actionResponseType,
},
'cardsV2': [{
'cardId': 'assignCase',
'card': {
'header': {
'title': 'Example Customer Service Case',
'subtitle': 'Case basics',
},
'sections': [{
'widgets': [
{'keyValue': {'topLabel': 'Case ID', 'content': 'case123'}},
{'keyValue': {'topLabel': 'Assignee', 'content': 'You'}},
{'keyValue': {'topLabel': 'Status', 'content': 'Open'}},
{
'keyValue': {
'topLabel': 'Subject', 'content': 'It won\'t turn on...',
}
},
],
},
{
'widgets': [{
'buttons': [
{
'textButton': {
'text': 'OPEN CASE',
'onClick': {
'openLink': {
'url': 'https://support.example.com/orders/case123',
},
},
},
},
{
'textButton': {
'text': 'RESOLVE CASE',
'onClick': {
'openLink': {
'url': 'https://support.example.com/orders/case123?resolved=y',
},
},
},
},
{
'textButton': {
'text': 'ASSIGN TO ME',
'onClick': {'action': {'actionMethodName': 'assign'}},
},
},
],
}],
}],
},
}],
};
}
限制和注意事项
为 Chat 应用配置链接预览时,请注意以下限制和注意事项:
- 每个 Chat 应用最多支持 5 种网址格式的链接预览。
- 聊天应用会为每条消息预览一个链接。如果一条消息中存在多个可预览的链接,则只有第一个可预览的链接才会预览。
- 聊天应用只能预览以
https://
开头的链接,因此 https://support.example.com/cases/
会预览,但 support.example.com/cases/
不会。
- 除非消息中包含发送到 Chat 应用的其他信息(例如斜杠命令),否则链接预览只会将链接网址发送到 Chat 应用。
- 附加到预览链接的卡片仅支持
UPDATE_USER_MESSAGE_CARDS
类型的 ActionResponse
,并且仅适用于响应 Chat 应用互动事件。链接预览不支持 UPDATE_MESSAGE
,也不支持通过 Chat API 更新已预览链接所附卡片的异步请求。如需了解详情,请参阅更新卡片。
调试链接预览
在实现链接预览时,您可能需要通过读取 Chat 应用的日志来调试该应用。如需读取日志,请访问 Google Cloud 控制台中的日志浏览器。