リンクをプレビュー

ユーザーが Google Chat でリンクを共有する際にコンテキストの切り替えが発生しないように、 Chat アプリは、メッセージに カードを添付してリンクを プレビューできます。カードには詳細情報が表示され、ユーザーは Google Chat から直接操作できます。

たとえば、会社のカスタマー サービス エージェント全員と Case-y という名前の Chat アプリが含まれる Google Chat スペースがあるとします。エージェントは Chat スペースでカスタマー サービスケースへのリンクを頻繁に共有しますが、そのたびに同僚はケースのリンクを開いて、担当者、ステータス、件名などの詳細を確認する必要があります。同様に、ケースの所有権を取得したり、ステータスを変更したりする場合は、リンクを開く必要があります。

リンクのプレビューを使用すると、スペースの常駐 Chat アプリである Case-y は、ケースのリンクが共有されるたびに、割り当て先、ステータス、件名を示すカードを添付できます。カードのボタンを使用すると、エージェントはチャット ストリームから直接ケースの所有権を取得し、ステータスを変更できます。

ユーザーがメッセージにリンクを追加すると、Chat アプリがリンクをプレビューする可能性があることを示すチップが表示されます。

Chat 用アプリがリンクをプレビューする可能性があることを示すチップ

メッセージを送信すると、リンクが Chat アプリに送信され、アプリが カードを生成してユーザーのメッセージに添付します。

メッセージにカードを添付してリンクをプレビューする Chat 用アプリ

カードには、リンクとともに、ボタンなどのインタラクティブな要素を含むリンクに関する追加情報が表示されます。Chat アプリは、ボタンのクリックなどのユーザー操作に応じて、添付されたカードを更新できます。

Chat アプリがメッセージにカードを添付してリンクをプレビューしないようにするには、プレビュー チップの をクリックしてプレビューを無効にします。添付されたカードは、[プレビューを削除] をクリックしていつでも削除できます。

前提条件

Node.js

インタラクション イベントを受信して応答する Google Chat アプリ。HTTP サービスを使用してインタラクティブな Chat アプリを作成するには、この クイックスタートを完了してください。

Python

インタラクション イベントを受信して応答する Google Chat アプリ。HTTP サービスを使用してインタラクティブな Chat アプリを作成するには、この クイックスタートを完了してください。

Java

インタラクション イベントを受信して応答する Google Chat アプリ。HTTP サービスを使用してインタラクティブな Chat アプリを作成するには、この クイックスタートを完了してください。

Apps Script

インタラクション イベントを受信して応答する Google Chat アプリ。Apps Script でインタラクティブな Chat アプリを作成するには、このクイックスタートを完了してください。

Google Cloud コンソールの Chat アプリの構成ページで、example.comsupport.example.comsupport.example.com/cases/ などの特定のリンクを URL パターンとして登録すると、 Chat アプリでプレビューできます。

リンク プレビューの構成メニュー

  1. Google Cloud コンソールを開きます。
  2. [Google Cloud] の横にある下矢印 をクリックして、Chat アプリのプロジェクトを開きます。
  3. 検索フィールドに「Google Chat API」と入力し、[Google Chat API] をクリックします。
  4. [管理 > 構成] をクリックします。
  5. [リンクのプレビュー] で、URL パターンを追加または編集します。
    1. 新しい URL パターンのリンク プレビューを構成するには、[URL パターンを追加] をクリックします。
    2. 既存の URL パターンの構成を編集するには、下矢印 をクリックします。
  6. [ホストパターン] フィールドに、URL パターンのドメインを入力します。Chat アプリは、このドメインへのリンクをプレビューします。

    Chat アプリで subdomain.example.com などの特定のサブドメインのリンクをプレビューするには、サブドメインを含めます。

    Chat アプリでドメイン全体のリンクをプレビューするには、サブドメインとしてアスタリスク(*)のワイルドカード文字を指定します。 たとえば、*.example.comsubdomain.example.comany.number.of.subdomains.example.com に一致します。

  7. [パスの接頭辞] フィールドに、ホストパターン ドメインに追加するパスを入力します。

    ホストパターン ドメイン内のすべての URL に一致させるには、[パスの接頭辞] を空白のままにします。

    たとえば、ホストパターンが support.example.com の場合、 support.example.com/cases/ でホストされているケースの URL に一致させるには、「cases/」と入力します。

  8. [完了] をクリックします。

  9. [保存] をクリックします。

これで、Chat アプリを含む Chat スペースのメッセージに、リンク プレビュー URL パターンに一致するリンクが含まれると、アプリがリンクをプレビューします。

特定のリンクのリンク プレビューを構成すると、Chat アプリはリンクを認識してプレビューし、詳細情報を添付できます。

Chat アプリを含む Chat スペース内で、ユーザーのメッセージにリンク プレビュー URL パターンに一致するリンクが含まれている場合、Chat アプリは MESSAGE インタラクション イベントを受信します。インタラクション イベントの JSON ペイロードには、matchedUrl フィールドが含まれます。

JSON

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

Chat アプリは、MESSAGE イベント ペイロードで matchedUrl フィールドの有無を確認することで、プレビューされたリンクを含むメッセージに情報を追加できます。Chat アプリは、基本的なテキスト メッセージで返信するか、カードを添付できます。

テキスト メッセージで返信する

基本的なレスポンスの場合、Chat アプリはリンク にシンプルなテキスト メッセージ で返信することで、リンクをプレビューできます。この例では、リンク プレビュー URL パターンに一致するリンク URL を繰り返すメッセージを添付します。

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
  };
}

プレビューされたリンクにカードを添付するには、タイプ UPDATE_USER_MESSAGE_CARDSActionResponse を返します。この例では、基本的なカードを添付します。

メッセージにカードを添付してリンクをプレビューする Chat 用アプリ

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

この例では、 カードの 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.typeUPDATE_USER_MESSAGE_CARDS に設定します。
  • Chat アプリがメッセージを送信した場合は、actionResponse.typeUPDATE_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

この例では、 カードの 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 つの URL パターンのリンク プレビューをサポートしています。
  • Chat アプリは、メッセージごとに 1 つのリンクをプレビューします。1 つのメッセージに複数のプレビュー可能なリンクがある場合は、最初のプレビュー可能なリンクのみがプレビューされます。
  • Chat アプリは https:// で始まるリンクのみをプレビューします。したがって、 https://support.example.com/cases/ はプレビューされますが、 support.example.com/cases/ はプレビューされません。
  • メッセージにスラッシュ コマンドなど、Chat アプリに送信される他の情報が含まれていない限り、リンク プレビューによって Chat アプリに送信されるのはリンク URL のみです。
  • ユーザーがリンクを投稿した場合、Chat アプリは、ユーザーがカードを操作したとき(ボタンをクリックしたときなど)にのみ、リンク プレビュー カードを更新できます。Message リソースで Chat API の update() メソッドを呼び出して、ユーザーのメッセージを非同期で更新することはできません。
  • Chat アプリはスペース内のすべてのユーザーのリンクをプレビューする必要があるため、 メッセージには privateMessageViewer フィールドを含めないでください。

リンク プレビューを実装する際に、アプリのログを読み取って Chat アプリをデバッグする必要が生じることがあります。ログを読み取るには、 Google Cloud コンソールのログ エクスプローラにアクセスします。