预览 Google Chat 消息中的链接

为了防止用户在 Google Chat 中分享链接时切换上下文,您的 Chat 应用可以通过在消息中附加卡片来预览链接,从而提供更多信息,让用户无需离开 Google Chat 即可执行操作。

在 Google Chat 中,插件会以 Google Chat 应用的形式向用户显示。如需了解详情,请参阅扩展 Google Chat 概览

例如,假设有一个 Google Chat 聊天室,其中包含一家公司的所有客户服务人员,以及一个名为 Case-y 的 Chat 应用。客服人员经常在 Chat 聊天室中分享客户服务支持请求的链接,而每次分享后,同事都必须打开支持请求链接,才能查看分配者、状态和主题等详细信息。同样,如果某人想要获得支持请求的所有权或更改状态,则需要打开该链接。

借助链接预览功能,聊天室的常驻 Chat 应用 Case-y 可以在用户分享支持请求链接时附加一个卡片,其中显示了分配者、状态和主题。客服人员可以通过卡片上的按钮直接从聊天会话中接管支持请求并更改状态。

当用户向消息中添加链接时,系统会显示一个条状标签,告知用户 Chat 应用可能会预览该链接。

指示 Chat 应用可能会预览链接的条状标签

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

聊天应用通过将卡片附加到消息来预览链接

除了链接之外,该卡片还会提供有关链接的更多信息,包括按钮等互动元素。您的 Chat 应用可以根据用户互动(例如按钮点击)更新所附的卡片。

如果用户不希望 Chat 应用通过在消息中附加卡片来预览其链接,则可以点击预览条状标签上的 来阻止预览。用户可以随时点击移除预览来移除所附的卡片。

前提条件

Node.js

这款 Google Workspace 插件可扩展 Google Chat 的功能。如需构建一个,请完成 HTTP 快速入门

Apps 脚本

这款 Google Workspace 插件可扩展 Google Chat 的功能。如需构建一个,请完成 Apps Script 快速入门

在 Google Cloud 控制台中的 Chat 应用配置页面上,将特定链接(例如 example.comsupport.example.comsupport.example.com/cases/)注册为网址模式,以便 Chat 应用可以预览它们。

“链接预览”配置菜单

  1. 打开 Google Cloud Console
  2. 点击“Google Cloud”旁边的向下箭头 ,然后打开 Chat 应用的项目。
  3. 在搜索字段中输入 Google Chat API,然后点击 Google Chat API
  4. 依次点击管理 > 配置
  5. 在“链接预览”下,添加或修改网址模式。
    1. 如需为新的网址格式配置链接预览,请点击添加网址格式
    2. 如需修改现有网址模式的配置,请点击向下箭头
  6. Host pattern(主机模式)字段中,输入网址模式的网域。Chat 应用会预览指向此网域的链接。

    如需让 Chat 应用预览特定子网域(例如 subdomain.example.com)的链接,请添加该子网域。

    如需让 Chat 应用预览整个网域的链接,请将星号 (*) 作为子网域指定为通配符字符。例如,*.example.comsubdomain.example.comany.number.of.subdomains.example.com 匹配。

  7. 路径前缀字段中,输入要附加到主机模式网域的路径。

    如需匹配主机模式网域中的所有网址,请将路径前缀留空。

    例如,如果主机模式为 support.example.com,如需匹配托管在 support.example.com/cases/ 的支持请求的网址,请输入 cases/

  8. 点击完成

  9. 点击保存

现在,每当有人在包含 Chat 应用的 Chat 聊天室中向消息中添加与链接预览网址模式匹配的链接时,您的应用都会预览该链接。

为给定链接配置链接预览功能后,Chat 应用便可通过为链接附加更多信息来识别和预览该链接。

在包含 Chat 应用的 Chat 聊天室中,如果某位用户的消息包含与链接预览网址格式匹配的链接,您的 Chat 应用会收到包含 MessagePayload事件对象。在载荷中,message.matchedUrl 对象包含用户在消息中添加的链接:

JSON

message: {
  matchedUrl: {
    url: "https://support.example.com/cases/case123"
  },
  ... // other message attributes redacted
}

通过检查 MESSAGE 事件载荷中是否存在 matchedUrl 字段,您的 Chat 应用可以向消息中添加预览链接的信息。Chat 应用可以使用基本文本消息进行回复,也可以附加卡片。

使用短信回复

对于基本回复,Chat 应用可以通过用文本消息回复链接来预览链接。以下示例会附加一条消息,其中重复了与链接预览网址模式匹配的链接网址。

Node.js

/**
 * Google Cloud Function that handles messages that have links whose
 * URLs match URL patterns configured for link previewing.
 *
 *
 * @param {Object} req Request sent from Google Chat space
 * @param {Object} res Response to send back
 */
exports.previewLinks = function previewLinks(req, res) {
  const chatEvent = req.body.chat;

  // Handle MESSAGE events
  if(chatEvent.messagePayload) {
    return res.send(handlePreviewLink(chatEvent.messagePayload.message));
  // Handle button clicks
  } else if(chatEvent.buttonClickedPayload) {
    return res.send(handleCardClick(chatEvent.buttonClickedPayload.message));
  }
};

/**
 * Respond to messages that have links whose URLs match URL patterns configured
 * for link previewing.
 *
 * @param {Object} chatMessage The chat message object from Google Workspace Add On event.
 * @return {Object} Response to send back depending on the matched URL.
 */
function handlePreviewLink(chatMessage) {
  // If the Chat app does not detect a link preview URL pattern, reply
  // with a text message that says so.
  if (!chatMessage.matchedUrl) {
    return { hostAppDataAction: { chatDataAction: { createMessageAction: { message: {
      text: 'No matchedUrl detected.'
    }}}}};
  }

  // Reply with a text message for URLs of the subdomain "text"
  if (chatMessage.matchedUrl.url.includes("text.example.com")) {
    return { hostAppDataAction: { chatDataAction: { createMessageAction: { message: {
      text: 'event.chat.messagePayload.message.matchedUrl.url: ' + chatMessage.matchedUrl.url
    }}}}};
  }
}

Apps 脚本

/**
 * Reply to messages that have links whose URLs match the pattern
 * "text.example.com" configured for link previewing.
 *
 * @param {Object} event The event object from Google Workspace Add-on.
 *
 * @return {Object} The action response.
 */
function onMessage(event) {
  // Stores the Google Chat event as a variable.
  const chatMessage = event.chat.messagePayload.message;

  // If the Chat app doesn't detect a link preview URL pattern, reply
  // with a text message that says so.
  if (!chatMessage.matchedUrl) {
    return { hostAppDataAction: { chatDataAction: { createMessageAction: { message: {
      text: 'No matchedUrl detected.'
    }}}}};
  }

  // Reply with a text message for URLs of the subdomain "text".
  if (chatMessage.matchedUrl.url.includes("text.example.com")) {
    return { hostAppDataAction: { chatDataAction: { createMessageAction: { message: {
      text: 'event.chat.messagePayload.message.matchedUrl.url: ' + chatMessage.matchedUrl.url
    }}}}};
  }
}

如需将卡片附加到预览的链接,请返回包含类型为 UpdateInlinePreviewActionChatDataActionMarkup 对象的操作 DataActions

在以下示例中,Chat 应用会向包含网址模式 support.example.com 的消息添加预览卡片。

聊天应用通过将卡片附加到消息来预览链接

Node.js

/**
 * Google Cloud Function that handles messages that have links whose
 * URLs match URL patterns configured for link previewing.
 *
 *
 * @param {Object} req Request sent from Google Chat space
 * @param {Object} res Response to send back
 */
exports.previewLinks = function previewLinks(req, res) {
  const chatEvent = req.body.chat;

  // Handle MESSAGE events
  if(chatEvent.messagePayload) {
    return res.send(handlePreviewLink(chatEvent.messagePayload.message));
  // Handle button clicks
  } else if(chatEvent.buttonClickedPayload) {
    return res.send(handleCardClick(chatEvent.buttonClickedPayload.message));
  }
};

/**
 * Respond to messages that have links whose URLs match URL patterns configured
 * for link previewing.
 *
 * @param {Object} chatMessage The chat message object from Google Workspace Add On event.
 * @return {Object} Response to send back depending on the matched URL.
 */
function handlePreviewLink(chatMessage) {
  // Attach a card to the message for URLs of the subdomain "support"
  if (chatMessage.matchedUrl.url.includes("support.example.com")) {
    // A hard-coded card is used in this example. In a real-life scenario,
    // the case information would be fetched and used to build the card.
    return { hostAppDataAction: { chatDataAction: { updateInlinePreviewAction: { cardsV2: [{
      cardId: 'attachCard',
      card: {
        header: {
          title: 'Example Customer Service Case',
          subtitle: 'Case basics',
        },
        sections: [{ widgets: [
        { decoratedText: { topLabel: 'Case ID', text: 'case123'}},
        { decoratedText: { topLabel: 'Assignee', text: 'Charlie'}},
        { decoratedText: { topLabel: 'Status', text: 'Open'}},
        { decoratedText: { topLabel: 'Subject', text: 'It won\'t turn on...' }},
        { buttonList: { buttons: [{
          text: 'OPEN CASE',
          onClick: { openLink: {
            url: 'https://support.example.com/orders/case123'
          }},
        }, {
          text: 'RESOLVE CASE',
          onClick: { openLink: {
            url: 'https://support.example.com/orders/case123?resolved=y',
          }},
        }, {
          text: 'ASSIGN TO ME',
          // Use runtime environment variable set with self URL
          onClick: { action: { function: process.env.BASE_URL }}
        }]}}
        ]}]
      }
    }]}}}};
  }
}

Apps 脚本

此示例通过返回卡片 JSON 来发送卡片消息。您还可以使用 Apps 脚本卡片服务

/**
 * Attach a card to messages that have links whose URLs match the pattern
 * "support.example.com" configured for link previewing.
 *
 * @param {Object} event The event object from Google Workspace Add-on.
 *
 * @return {Object} The action response.
 */
function onMessage(event) {
  // Stores the Google Chat event as a variable.
  const chatMessage = event.chat.messagePayload.message;

  // Attach a card to the message for URLs of the subdomain "support".
  if (chatMessage.matchedUrl.url.includes("support.example.com")) {
    // A hard-coded card is used in this example. In a real-life scenario,
    // the case information would be fetched and used to build the card.
    return { hostAppDataAction: { chatDataAction: { updateInlinePreviewAction: { cardsV2: [{
      cardId: 'attachCard',
      card: {
        header: {
          title: 'Example Customer Service Case',
          subtitle: 'Case summary',
        },
        sections: [{ widgets: [
        { decoratedText: { topLabel: 'Case ID', text: 'case123'}},
        { decoratedText: { topLabel: 'Assignee', text: 'Charlie'}},
        { decoratedText: { topLabel: 'Status', text: 'Open'}},
        { decoratedText: { topLabel: 'Subject', text: 'It won\'t turn on...' }},
        { buttonList: { buttons: [{
          text: 'OPEN CASE',
          onClick: { openLink: {
            url: 'https://support.example.com/orders/case123'
          }},
        }, {
          text: 'RESOLVE CASE',
          onClick: { openLink: {
            url: 'https://support.example.com/orders/case123?resolved=y',
          }},
        }, {
          text: 'ASSIGN TO ME',
          // Clicking this button triggers the execution of the function
          // "assign" from the Apps Script project.
          onClick: { action: { function: 'assign'}}
        }]}}
        ]}]
      }
    }]}}}};
  }
}

当用户与链接预览卡片互动(例如点击卡片上的按钮)时,您的 Chat 应用可以更新该卡片。

如需更新卡片,您的 Chat 应用必须返回操作 DataActions 以及以下 ChatDataActionMarkup 对象之一:

如需确定消息的发件人,请使用事件载荷 (buttonClickedPayload) 检查发件人 (message.sender.type) 是否设置为 HUMAN(用户)或 BOT(Chat 应用)。

以下示例展示了 Chat 应用如何在用户点击分配给我按钮时更新链接预览,具体方法是更新卡片的负责人字段并停用该按钮。

聊天应用中,预览了附加到消息中的卡片更新版的链接

Node.js

/**
 * Google Cloud Function that handles messages that have links whose
 * URLs match URL patterns configured for link previewing.
 *
 *
 * @param {Object} req Request sent from Google Chat space
 * @param {Object} res Response to send back
 */
exports.previewLinks = function previewLinks(req, res) {
  const chatEvent = req.body.chat;

  // Handle MESSAGE events
  if(chatEvent.messagePayload) {
    return res.send(handlePreviewLink(chatEvent.messagePayload.message));
  // Handle button clicks
  } else if(chatEvent.buttonClickedPayload) {
    return res.send(handleCardClick(chatEvent.buttonClickedPayload.message));
  }
};

/**
 * Respond to clicks by assigning user and updating the card that was attached to a
 * message with a previewed link.
 *
 * @param {Object} chatMessage The chat message object from Google Workspace Add On event.
 * @return {Object} Action response depending on the original message.
 */
function handleCardClick(chatMessage) {
  // Creates the updated card that displays "You" for the assignee
  // and that disables the button.
  //
  // A hard-coded card is used in this example. In a real-life scenario,
  // an actual assign action would be performed before building the card.
  const message = { cardsV2: [{
    cardId: 'attachCard',
    card: {
      header: {
        title: 'Example Customer Service Case',
        subtitle: 'Case basics',
      },
      sections: [{ widgets: [
        { decoratedText: { topLabel: 'Case ID', text: 'case123'}},
        // The assignee is now "You"
        { decoratedText: { topLabel: 'Assignee', text: 'You'}},
        { decoratedText: { topLabel: 'Status', text: 'Open'}},
        { decoratedText: { topLabel: 'Subject', text: 'It won\'t turn on...' }},
        { buttonList: { buttons: [{
          text: 'OPEN CASE',
          onClick: { openLink: {
            url: 'https://support.example.com/orders/case123'
          }},
        }, {
          text: 'RESOLVE CASE',
          onClick: { openLink: {
            url: 'https://support.example.com/orders/case123?resolved=y',
          }},
        }, {
          text: 'ASSIGN TO ME',
          // The button is now disabled
          disabled: true,
          // Use runtime environment variable set with self URL
          onClick: { action: { function: process.env.BASE_URL }}
        }]}}
      ]}]
    }
  }]};

  // Checks whether the message event originated from a human or a Chat app
  // to return the adequate action response.
  if(chatMessage.sender.type === 'HUMAN') {
    return { hostAppDataAction: { chatDataAction: { updateInlinePreviewAction: message }}};
  } else {
    return { hostAppDataAction: { chatDataAction: { updateMessageAction: message }}};
  }
}

Apps 脚本

此示例通过返回卡片 JSON 来发送卡片消息。您还可以使用 Apps 脚本卡片服务

/**
 * Assigns and updates the card that's attached to a message with a
 * previewed link of the pattern "support.example.com".
 *
 * @param {Object} event The event object from the Google Workspace Add-on.
 *
 * @return {Object} Action response depending on the message author.
 */
function assign(event) {
  // Creates the updated card that displays "You" for the assignee
  // and that disables the button.
  //
  // A hard-coded card is used in this example. In a real-life scenario,
  // an actual assign action would be performed before building the card.
  const message = { cardsV2: [{
    cardId: 'attachCard',
    card: {
      header: {
        title: 'Example Customer Service Case',
        subtitle: 'Case summary',
      },
      sections: [{ widgets: [
        { decoratedText: { topLabel: 'Case ID', text: 'case123'}},
        // The assignee is now "You"
        { decoratedText: { topLabel: 'Assignee', text: 'You'}},
        { decoratedText: { topLabel: 'Status', text: 'Open'}},
        { decoratedText: { topLabel: 'Subject', text: 'It won\'t turn on...' }},
        { buttonList: { buttons: [{
          text: 'OPEN CASE',
          onClick: { openLink: {
            url: 'https://support.example.com/orders/case123'
          }},
        }, {
          text: 'RESOLVE CASE',
          onClick: { openLink: {
            url: 'https://support.example.com/orders/case123?resolved=y',
          }},
        }, {
          text: 'ASSIGN TO ME',
          // The button is now disabled
          disabled: true,
          onClick: { action: { function: 'assign'}}
        }]}}
      ]}]
    }
  }]};

  // Use the adequate action response type. It depends on whether the message
  // the preview link card is attached to was created by a human or a Chat app.
  if(event.chat.buttonClickedPayload.message.sender.type === 'HUMAN') {
    return { hostAppDataAction: { chatDataAction: { updateInlinePreviewAction: message }}};
  } else {
    return { hostAppDataAction: { chatDataAction: { updateMessageAction: message }}};
  }
}

限制和注意事项

为 Chat 应用配置链接预览时,请注意以下限制和注意事项:

  • 每个 Chat 应用最多支持 5 个网址格式的链接预览。
  • Chat 应用会针对每条消息预览一个链接。如果单个消息中包含多个可预览的链接,系统只会预览第一个可预览的链接。
  • 聊天应用仅预览以 https:// 开头的链接,因此 https://support.example.com/cases/ 会预览,但 support.example.com/cases/ 不会。
  • 除非消息包含要发送到 Chat 应用的其他信息(例如斜杠命令),否则只有链接网址会通过链接预览发送到 Chat 应用。
  • 如果用户发布链接,则只有在用户与链接预览卡片互动(例如点击按钮)时,Chat 应用才能更新链接预览卡片。您无法对 Message 资源调用 Chat API 的 update() 方法以异步更新用户的消息。
  • 聊天应用必须为聊天室中的所有人预览链接,因此消息必须省略 privateMessageViewer 字段。

在实现链接预览时,您可能需要通过读取应用的日志来调试 Chat 应用。如需读取日志,请访问 Google Cloud 控制台中的日志浏览器