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 アプリは、ボタンのクリックなどのユーザー操作に応じて、添付されたカードを更新できます。

メッセージにカードを添付して Chat アプリでリンクをプレビューしたくない場合は、プレビュー チップの をクリックしてプレビューを防ぐことができます。ユーザーは [プレビューを削除] をクリックすることで、いつでも添付されたカードを削除できます。

前提条件

Node.js

Google Chat を拡張する Google Workspace アドオン。ビルドするには、HTTP クイックスタートを完了します。

Apps Script

Google Chat を拡張する Google Workspace アドオン。作成するには、Apps Script のクイックスタートを完了します。

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

リンク プレビューの設定メニュー

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

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

    チャットアプリでドメイン全体のリンクをプレビューするには、サブドメインとしてアスタリスク(*)を含むワイルドカード文字を指定します。たとえば、*.example.comsubdomain.example.com および any.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 アプリは MessagePayload を含むイベント オブジェクトを受け取ります。ペイロードの message.matchedUrl オブジェクトには、ユーザーがメッセージに含めたリンクが含まれます。

JSON

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

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

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

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

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 Script

/**
 * 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 アプリが URL パターン 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 Script

この例では、カード JSON を返すことでカード メッセージを送信します。Apps Script カード サービスを使用することもできます。

/**
 * 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 アプリが次のいずれかの ChatDataActionMarkup オブジェクトとともにアクション DataActions を返す必要があります。

  • ユーザーがメッセージを送信した場合は、UpdateMessageAction オブジェクトを返します。
  • Chat アプリがメッセージを送信した場合は、UpdateInlinePreviewAction オブジェクトを返します。

メッセージを送信したユーザーを特定するには、イベント ペイロード(buttonClickedPayload)を使用して、送信者(message.sender.type)が HUMAN(ユーザー)または BOT(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 Script

この例では、カード JSON を返すことでカード メッセージを送信します。Apps Script カード サービスを使用することもできます。

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

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