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

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

该链接旁边会提供有关该链接的更多信息,包括按钮等互动元素。您的 Chat 应用可以更新附加的卡片来响应用户互动(例如按钮点击)。
如果您不希望 Chat 应用通过将卡片附加到消息来预览链接,他们可以点击预览条状标签上的 cancel 以防止预览。用户可以随时点击移除预览来移除附加的卡片。
在 Google Cloud Console 中您的 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 应用预览链接,请添加该子网域。
如需让整个网域的聊天应用预览链接,请指定带星号 (*) 的通配符字符作为子网域。例如,*.example.com
与 subdomain.example.com
和 any.number.of.subdomains.example.com
匹配。
在路径前缀字段中,输入要附加到主机模式网域的路径。
如需匹配主机模式网域中的所有网址,请将路径前缀留空。
例如,如果主机模式为 support.example.com
,若要匹配在 support.example.com/cases/
上托管的请求的网址,请输入 cases/
。
点击完成。
点击保存。
现在,每当用户包含的链接链接预览网址格式与 Chat 聊天室中包括您的 Chat 应用的消息匹配时,应用都会预览该链接。
预览链接
为给定链接配置链接预览后,您的 Chat 应用可以向其添加更多信息,从而识别并预览该链接。
在包含您的 Chat 应用的 Chat 聊天室中,如果用户的消息中包含与链接预览网址格式匹配的链接,则系统会将该链接作为 message
对象上的 matchedUrl
属性发送到您的 Chat 应用:
JSON
message {
. . . // other message attributes redacted
"matchedUrl": {
"url": "https://support.example.com/cases/case123"
},
. . . // other message attributes redacted
}
通过检查 message
对象上是否存在 matchedUrl
属性,您的 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 REST API 附加到预览链接的异步卡片请求。
链接预览不支持返回 UPDATE_MESSAGE
类型的 ActionResponse
。由于 UPDATE_MESSAGE
会更新整条消息(而不仅仅是卡片),因此只有 Chat 应用创建了原始消息后,才会更新。链接预览会将卡片附加到用户创建的消息中,因此 Chat 应用无权更新该卡片。
为确保函数同时更新 Chat 信息流中用户创建的应用和应用创建的卡片,请根据 Chat 应用还是用户创建的消息动态设置 ActionResponse
。
为此,您可以使用两种方法:在附加卡片的 onclick
属性中指定并检查自定义 actionMethodName
(将消息标识为用户创建的消息),或检查消息是否由用户创建。
如需使用 actionMethodName
正确处理预览卡片上的卡片点击事件,请在附加卡片的 onclick
属性中设置自定义 actionMethodName
:
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
。
完整示例:客户服务 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 应用的其他信息(如斜杠命令),否则只有链接网址会被发送到链接应用的预览。
- 附加到预览链接的卡片仅支持
UPDATE_USER_MESSAGE_CARDS
类型的 ActionResponse
,并且仅适用于响应 Chat 事件的同步请求。链接预览不支持 UPDATE_MESSAGE
,也不支持异步请求更新通过 Chat REST API 附加到预览链接的卡片。如需了解详情,请参阅更新卡。
调试链接预览
在实现链接预览时,您可能需要读取 Chat 应用的日志以对其进行调试。如需读取日志,请访问 Google Cloud Console 中的日志浏览器。