预览 Google Chat 消息中的链接

为避免用户在 Google Chat 中分享链接时切换上下文,Chat 应用可以预览链接,方法是在用户消息中附加卡片,其中包含更多信息,并允许用户直接在 Google Chat 中采取行动。

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

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

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

表示某个 Chat 应用可能会预览链接的提示

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

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

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

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

前提条件

HTTP

可扩展 Google Chat 的 Google Workspace 加载项。如需构建一个,请完成 HTTP 快速入门

Apps 脚本

可扩展 Google Chat 的 Google Workspace 加载项。如需构建一个,请完成 Apps 脚本快速入门

在 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. 主机模式字段中,输入网址模式的网域。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 应用会收到包含 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
    }}}}};
  }
}

如需将卡片附加到预览链接,请返回操作 DataActions,其中包含 UpdateInlinePreviewAction 类型的 ChatDataActionMarkup 对象。

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

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) {
  // 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 脚本

此示例通过返回 card 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(聊天应用)。

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

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

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 脚本

此示例通过返回 card 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 种网址格式的链接预览。
  • 聊天应用会预览每条消息中的一个链接。如果一条消息中包含多个可预览的链接,系统只会预览第一个可预览的链接。
  • 聊天应用只会预览以 https:// 开头的链接,因此会预览 https://support.example.com/cases/,但不会预览 support.example.com/cases/
  • 除非消息包含其他会发送到 Chat 应用的信息(例如斜杠命令),否则链接预览只会将链接网址发送到 Chat 应用。
  • 如果用户发布了链接,只有在用户与链接预览卡片互动(例如点击按钮)后,Chat 应用才能更新该卡片。您无法在 Message 资源上调用 Chat API 的 update() 方法来异步更新用户的消息。
  • 聊天应用必须为聊天室中的所有人预览链接,因此消息必须省略 privateMessageViewer 字段。

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