为防止用户在 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 应用会预览指向此网域的链接。
如要显示特定子网域(例如 subdomain.example.com
)的 Chat 应用预览链接,请添加该子网域。
如要查看整个网域的 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 客户服务聊天应用
以下是 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 应用配置链接预览时,请注意以下限制和注意事项:
- 每个聊天应用最多支持 5 种网址格式的链接预览。
- 聊天应用会在每条消息中显示一个链接。如果一条消息中包含多个可预览的链接,则只有第一个可预览的链接会进行预览。
- 聊天应用只能预览以
https://
开头的链接,因此 https://support.example.com/cases/
可以预览以 https://
开头的链接,但 support.example.com/cases/
不会。
- 除非消息包含发送到 Chat 应用的其他信息(例如斜杠命令),否则只有链接网址会通过链接预览发送到 Chat 应用。
- 附加到预览链接的卡片仅支持
UPDATE_USER_MESSAGE_CARDS
类型的 ActionResponse
,并且仅适用于响应 Chat 应用互动事件。链接预览不支持 UPDATE_MESSAGE
,也不支持通过 Chat API 更新附加到预览链接的卡片的异步请求。如需了解详情,请参阅更新卡片。
调试链接预览
实现链接预览时,您可能需要通过读取应用日志来调试 Chat 应用。如需读取日志,请访问 Google Cloud 控制台上的日志浏览器。