預覽連結

為避免使用者在 Google Chat 中分享連結時切換環境,Chat 應用程式可以將資訊卡附加至訊息,預覽連結,提供更多資訊,讓使用者直接在 Google Chat 中採取行動。

舉例來說,假設 Google Chat 聊天室包含公司所有客服專員,以及名為 Case-y 的 Chat 應用程式。服務專員經常在 Chat 聊天室中分享客戶服務案件的連結,但每次同事都必須開啟案件連結,才能查看指派對象、狀態和主旨等詳細資料。同樣地,如果有人想接管案件或變更狀態,也需要開啟連結。

連結預覽功能可讓空間的常駐 Chat 應用程式 Case-y,在有人分享案件連結時,附上顯示指派對象、狀態和主旨的資訊卡。服務專員可以透過資訊卡上的按鈕接管案件,並直接在對話串中變更狀態。

如果有人在訊息中加入連結,系統會顯示資訊方塊,提醒對方 Chat 應用程式可能會預覽連結。

晶片:表示 Chat 應用程式可能會預覽連結

傳送訊息後,連結會傳送到 Chat 應用程式,然後系統會產生並將資訊卡附加到使用者的訊息。

即時通訊應用程式將資訊卡附加至訊息,預覽連結

除了連結外,資訊卡還會提供連結的額外資訊,包括按鈕等互動式元素。Chat 應用程式可以根據使用者互動 (例如點選按鈕) 更新隨附資訊卡。

如果使用者不想讓 Chat 應用程式在訊息中附加資訊卡來預覽連結,可以點選預覽資訊方塊上的 ,禁止預覽連結。使用者隨時可以點選「移除預覽」,移除附加的資訊卡。

必要條件

Node.js

接收並回應互動事件的 Google Chat 應用程式。如要使用 HTTP 服務建立互動式即時通訊應用程式,請完成這份快速入門導覽課程

Python

接收並回應互動事件的 Google Chat 應用程式。如要使用 HTTP 服務建立互動式即時通訊應用程式,請完成這份快速入門導覽課程

Java

接收並回應互動事件的 Google Chat 應用程式。如要使用 HTTP 服務建立互動式即時通訊應用程式,請完成這份快速入門導覽課程

Apps Script

接收並回應互動事件的 Google Chat 應用程式。如要在 Apps Script 中建立互動式 Chat 應用程式,請完成這項快速入門導覽課程

在 Google Cloud 控制台的 Chat 應用程式設定頁面中,將特定連結 (例如 example.comsupport.example.comsupport.example.com/cases/) 註冊為網址模式,Chat 應用程式就能預覽這些連結。

連結預覽設定選單

  1. 開啟 Google Cloud 控制台
  2. 按一下「Google Cloud」旁邊的向下箭頭 ,然後開啟 Chat 應用程式的專案。
  3. 在搜尋欄位中輸入 Google Chat API,然後點選「Google Chat API」
  4. 依序點選「管理」>「設定」
  5. 在「連結預覽」下方,新增或編輯網址模式。
    1. 如要為新的網址模式設定連結預覽,請按一下「新增網址模式」
    2. 如要編輯現有網址模式的設定,請按一下向下箭頭
  6. 在「主機模式」欄位中,輸入網址模式的網域。Chat 應用程式會預覽這個網域的連結。

    如要讓 Chat 應用程式預覽特定子網域 (例如 subdomain.example.com) 的連結,請加入該子網域。

    如要讓 Chat 應用程式預覽整個網域的連結,請指定萬用字元,並以星號 (*) 做為子網域。舉例來說,*.example.com 符合 subdomain.example.comany.number.of.subdomains.example.com

  7. 在「路徑前置字串」欄位中,輸入要附加至主機模式網域的路徑。

    如要比對主機模式網域中的所有網址,請將「路徑前置字串」留空。

    舉例來說,如果主機模式為 support.example.com,如要比對 support.example.com/cases/ 中託管的案件網址,請輸入 cases/

  8. 按一下 [完成]。

  9. 按一下 [儲存]

現在,只要有人在包含您 Chat 應用程式的 Chat 聊天室訊息中加入符合連結預覽網址模式的連結,您的應用程式就會預覽該連結。

為特定連結設定連結預覽功能後,Chat 應用程式就能辨識並預覽連結,方法是在連結中附加更多資訊。

在包含 Chat 應用程式的 Chat 空間中,如果有人傳送的訊息含有符合連結預覽網址模式的連結,Chat 應用程式就會收到MESSAGE互動事件。互動事件的 JSON 酬載包含 matchedUrl 欄位:

JSON

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

只要檢查 MESSAGE 事件酬載中是否有 matchedUrl 欄位,Chat 應用程式就能在訊息中加入預覽連結的相關資訊。Chat 應用程式可以回覆基本文字訊息或附加資訊卡。

使用簡訊回覆

對於基本回覆,Chat 應用程式可以透過回覆連結的簡單文字訊息來預覽連結。這個範例會附加訊息,重複符合連結預覽網址模式的連結網址。

Node.js

node/preview-link/index.js
// Reply with a text message for URLs of the subdomain "text"
if (event.message.matchedUrl.url.includes("text.example.com")) {
  return {
    text: 'event.message.matchedUrl.url: ' + event.message.matchedUrl.url
  };
}

Python

python/preview-link/main.py
# Reply with a text message for URLs of the subdomain "text"
if 'text.example.com' in event.get('message').get('matchedUrl').get('url'):
  return {
    'text': 'event.message.matchedUrl.url: ' +
            event.get('message').get('matchedUrl').get('url')
  }

Java

java/preview-link/src/main/java/com/google/chat/preview/App.java
// Reply with a text message for URLs of the subdomain "text"
if (event.at("/message/matchedUrl/url").asText().contains("text.example.com")) {
  return new Message().setText("event.message.matchedUrl.url: " +
    event.at("/message/matchedUrl/url").asText());
}

Apps Script

apps-script/preview-link/preview-link.gs
// Reply with a text message for URLs of the subdomain "text"
if (event.message.matchedUrl.url.includes("text.example.com")) {
  return {
    text: 'event.message.matchedUrl.url: ' + event.message.matchedUrl.url
  };
}

如要將資訊卡附加至預覽連結,請傳回 ActionResponse 類型的 UPDATE_USER_MESSAGE_CARDS。這個範例會附加基本資訊卡。

即時通訊應用程式將資訊卡附加至訊息,預覽連結

Node.js

node/preview-link/index.js
// Attach a card to the message for URLs of the subdomain "support"
if (event.message.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 {
    actionResponse: { type: 'UPDATE_USER_MESSAGE_CARDS' },
    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',
            onClick: { action: { function: 'assign'}}
          }]}}
        ]}]
      }
    }]
  };
}

Python

python/preview-link/main.py
# Attach a card to the message for URLs of the subdomain "support"
if 'support.example.com' in event.get('message').get('matchedUrl').get('url'):
  # 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 {
    'actionResponse': { 'type': 'UPDATE_USER_MESSAGE_CARDS' },
    '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',
            'onClick': { 'action': { 'function': 'assign'}}
          }]}}
        ]}]
      }
    }]
  }

Java

java/preview-link/src/main/java/com/google/chat/preview/App.java
// Attach a card to the message for URLs of the subdomain "support"
if (event.at("/message/matchedUrl/url").asText().contains("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 new Message()
    .setActionResponse(new ActionResponse()
      .setType("UPDATE_USER_MESSAGE_CARDS"))
    .setCardsV2(List.of(new CardWithId()
      .setCardId("attachCard")
      .setCard(new GoogleAppsCardV1Card()
        .setHeader(new GoogleAppsCardV1CardHeader()
          .setTitle("Example Customer Service Case")
          .setSubtitle("Case basics"))
        .setSections(List.of(new GoogleAppsCardV1Section().setWidgets(List.of(
          new GoogleAppsCardV1Widget().setDecoratedText(new GoogleAppsCardV1DecoratedText()
            .setTopLabel("Case ID")
            .setText("case123")),
          new GoogleAppsCardV1Widget().setDecoratedText(new GoogleAppsCardV1DecoratedText()
            .setTopLabel("Assignee")
            .setText("Charlie")),
          new GoogleAppsCardV1Widget().setDecoratedText(new GoogleAppsCardV1DecoratedText()
            .setTopLabel("Status")
            .setText("Open")),
          new GoogleAppsCardV1Widget().setDecoratedText(new GoogleAppsCardV1DecoratedText()
            .setTopLabel("Subject")
            .setText("It won't turn on...")),
          new GoogleAppsCardV1Widget()
            .setButtonList(new GoogleAppsCardV1ButtonList().setButtons(List.of(
              new GoogleAppsCardV1Button()
                .setText("OPEN CASE")
                .setOnClick(new GoogleAppsCardV1OnClick()
                  .setOpenLink(new GoogleAppsCardV1OpenLink()
                    .setUrl("https://support.example.com/orders/case123"))),
              new GoogleAppsCardV1Button()
                .setText("RESOLVE CASE")
                .setOnClick(new GoogleAppsCardV1OnClick()
                  .setOpenLink(new GoogleAppsCardV1OpenLink()
                    .setUrl("https://support.example.com/orders/case123?resolved=y"))),
              new GoogleAppsCardV1Button()
                .setText("ASSIGN TO ME")
                .setOnClick(new GoogleAppsCardV1OnClick()
                  .setAction(new GoogleAppsCardV1Action().setFunction("assign")))))))))))));
}

Apps Script

這個範例會傳回 card JSON,藉此傳送訊息卡片。 您也可以使用 Apps Script 卡片服務

apps-script/preview-link/preview-link.gs
// Attach a card to the message for URLs of the subdomain "support"
if (event.message.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 {
    actionResponse: { type: 'UPDATE_USER_MESSAGE_CARDS' },
    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',
            onClick: { action: { function: 'assign'}}
          }]}}
        ]}]
      }
    }]
  };
}

當使用者與連結預覽資訊卡互動時 (例如點選資訊卡上的按鈕),Chat 應用程式可以更新資訊卡。

如要更新資訊卡,Chat 應用程式必須處理 CARD_CLICKED 互動事件,並根據傳送含有連結預覽訊息的使用者,傳回 actionResponse

  • 如果訊息是由使用者傳送,請將 actionResponse.type 設為 UPDATE_USER_MESSAGE_CARDS
  • 如果訊息是由 Chat 應用程式傳送,請將 actionResponse.type 設為 UPDATE_MESSAGE

如要判斷郵件傳送者,可以使用互動事件的 message.sender.type 欄位,查看傳送者是 HUMAN 使用者還是 BOT

以下範例說明 Chat 應用程式如何更新連結預覽畫面:每當使用者點選「指派給我」按鈕,應用程式就會更新資訊卡的「受讓人」欄位,並停用該按鈕。

即時通訊應用程式預覽連結,訊息隨附的資訊卡已更新

Node.js

node/preview-link/index.js
/**
 * Updates a card that was attached to a message with a previewed link.
 *
 * @param {Object} event The event object from Chat.
 *
 * @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) {
  // To respond to the correct button, checks the button's actionMethodName.
  if (event.action.actionMethodName === 'assign') {
    // 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.

    // 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 = event.message.sender.type === 'HUMAN' ?
      'UPDATE_USER_MESSAGE_CARDS' :
      'UPDATE_MESSAGE';

    // Returns the updated card that displays "You" for the assignee
    // and that disables the button.
    return {
      actionResponse: { type: actionResponseType },
      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,
              onClick: { action: { function: 'assign'}}
            }]}}
          ]}]
        }
      }]
    };
  }
}

Python

python/preview-link/main.py
def on_card_click(event: dict) -> dict:
  """Updates a card that was attached to a message with a previewed link."""
  # To respond to the correct button, checks the button's actionMethodName.
  if 'assign' == event.get('action').get('actionMethodName'):
    # 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.

    # 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.
    actionResponseType = 'UPDATE_USER_MESSAGE_CARDS' if \
      event.get('message').get('sender').get('type') == 'HUMAN' else \
      'UPDATE_MESSAGE'

    # Returns the updated card that displays "You" for the assignee
    # and that disables the button.
    return {
      'actionResponse': { 'type': actionResponseType },
      '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,
              'onClick': { 'action': { 'function': 'assign'}}
            }]}}
          ]}]
        }
      }]
    }

Java

java/preview-link/src/main/java/com/google/chat/preview/App.java
// Updates a card that was attached to a message with a previewed link.
Message onCardClick(JsonNode event) {
  // To respond to the correct button, checks the button's actionMethodName.
  if (event.at("/action/actionMethodName").asText().equals("assign")) {
    // 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.

    // 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.
    String actionResponseType =
      event.at("/message/sender/type").asText().equals("HUMAN")
      ? "UPDATE_USER_MESSAGE_CARDS" : "UPDATE_MESSAGE";

    // Returns the updated card that displays "You" for the assignee
    // and that disables the button.
    return new Message()
    .setActionResponse(new ActionResponse()
      .setType(actionResponseType))
    .setCardsV2(List.of(new CardWithId()
      .setCardId("attachCard")
      .setCard(new GoogleAppsCardV1Card()
        .setHeader(new GoogleAppsCardV1CardHeader()
          .setTitle("Example Customer Service Case")
          .setSubtitle("Case basics"))
        .setSections(List.of(new GoogleAppsCardV1Section().setWidgets(List.of(
          new GoogleAppsCardV1Widget().setDecoratedText(new GoogleAppsCardV1DecoratedText()
            .setTopLabel("Case ID")
            .setText("case123")),
          new GoogleAppsCardV1Widget().setDecoratedText(new GoogleAppsCardV1DecoratedText()
            .setTopLabel("Assignee")
            // The assignee is now "You"
            .setText("You")),
          new GoogleAppsCardV1Widget().setDecoratedText(new GoogleAppsCardV1DecoratedText()
            .setTopLabel("Status")
            .setText("Open")),
          new GoogleAppsCardV1Widget().setDecoratedText(new GoogleAppsCardV1DecoratedText()
            .setTopLabel("Subject")
            .setText("It won't turn on...")),
          new GoogleAppsCardV1Widget()
            .setButtonList(new GoogleAppsCardV1ButtonList().setButtons(List.of(
              new GoogleAppsCardV1Button()
                .setText("OPEN CASE")
                .setOnClick(new GoogleAppsCardV1OnClick()
                  .setOpenLink(new GoogleAppsCardV1OpenLink()
                    .setUrl("https://support.example.com/orders/case123"))),
              new GoogleAppsCardV1Button()
                .setText("RESOLVE CASE")
                .setOnClick(new GoogleAppsCardV1OnClick()
                  .setOpenLink(new GoogleAppsCardV1OpenLink()
                    .setUrl("https://support.example.com/orders/case123?resolved=y"))),
              new GoogleAppsCardV1Button()
                .setText("ASSIGN TO ME")
                // The button is now disabled
                .setDisabled(true)
                .setOnClick(new GoogleAppsCardV1OnClick()
                  .setAction(new GoogleAppsCardV1Action().setFunction("assign")))))))))))));
  }
  return null;
}

Apps Script

這個範例會傳回 card JSON,藉此傳送訊息卡片。 您也可以使用 Apps Script 卡片服務

apps-script/preview-link/preview-link.gs
/**
 * Updates a card that was attached to a message with a previewed link.
 *
 * @param {Object} event The event object from Chat.
 *
 * @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) {
  // To respond to the correct button, checks the button's actionMethodName.
  if (event.action.actionMethodName === 'assign') {
    // 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.

    // 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 = event.message.sender.type === 'HUMAN' ?
      'UPDATE_USER_MESSAGE_CARDS' :
      'UPDATE_MESSAGE';

    // Returns the updated card that displays "You" for the assignee
    // and that disables the button.
    return {
      actionResponse: { type: actionResponseType },
      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,
              onClick: { action: { function: 'assign'}}
            }]}}
          ]}]
        }
      }]
    };
  }
}

限制和注意事項

為 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 控制台的「記錄檔探索工具」