Visualizar links

Para evitar a mudança de contexto quando os usuários compartilham um link no Google Chat, seu app do Chat pode visualizar o link anexando um card à mensagem, que oferece mais informações e permite que as pessoas realizem ações diretamente no Google Chat.

Por exemplo, imagine um espaço do Google Chat que inclui todos os agentes de atendimento ao cliente de uma empresa e um app de chat chamado Case-y. Os agentes compartilham links de casos de atendimento ao cliente no espaço de chat com frequência. E sempre que fazem isso, os colegas precisam abrir o link do caso para conferir detalhes como responsável, status e assunto. Da mesma forma, se alguém quiser assumir a propriedade de um caso ou mudar o status, ele precisará abrir o link.

A prévia do link permite que o app do Chat do espaço, Case-y, anexe um cartão que mostre o cessionário, o status e o assunto sempre que alguém compartilhar um link de caso. Os botões no cartão permitem que os agentes assumam a propriedade do caso e mudem o status diretamente no stream do chat.

Quando alguém adiciona um link à mensagem, um ícone aparece para informar que um app de chat pode mostrar uma prévia do link.

Ícone indicando que um app de chat pode mostrar uma prévia de um link

Depois de enviar a mensagem, o link é enviado para o app do Chat, que gera e anexa o cartão à mensagem do usuário.

App de chat com a visualização de um link anexando um card à mensagem

Além do link, o card oferece mais informações sobre ele, incluindo elementos interativos, como botões. O app Chat pode atualizar o card anexado em resposta a interações do usuário, como cliques em botões.

Se uma pessoa não quiser que o app do Chat visualize o link anexando um card à mensagem, ela poderá impedir a visualização clicando em no ícone de visualização. Os usuários podem remover o cartão anexado a qualquer momento clicando em Remover visualização.

Pré-requisitos

Node.js

Um app do Google Chat com recursos interativos ativados. Para criar um app de chat interativo usando um serviço HTTP, conclua este guia de início rápido.

Python

Um app do Google Chat que tenha recursos interativos ativados. Para criar um app de chat interativo usando um serviço HTTP, conclua este guia de início rápido.

Java

Um app do Google Chat que tenha recursos interativos ativados. Para criar um app de chat interativo usando um serviço HTTP, conclua este guia de início rápido.

Apps Script

Um app do Google Chat que tenha recursos interativos ativados. Para criar um app do Chat interativo no Apps Script, conclua este guia de início rápido.

Registre links específicos, como example.com, support.example.com e support.example.com/cases/, como padrões de URL na página de configuração do seu app de chat no console do Google Cloud para que ele possa mostrar uma prévia deles.

Menu de configuração das visualizações de link

  1. Abra o Console do Google Cloud.
  2. Ao lado de "Google Cloud", clique na seta para baixo e abra o projeto do app de chat.
  3. No campo de pesquisa, digite Google Chat API e clique em API Google Chat.
  4. Clique em Gerenciar > Configuração.
  5. Em "Visualizações de link", adicione ou edite um padrão de URL.
    1. Para configurar as visualizações de link de um novo padrão de URL, clique em Adicionar padrão de URL.
    2. Para editar a configuração de um padrão de URL atual, clique na seta para baixo .
  6. No campo Padrão de host, insira o domínio do padrão de URL. O app de chat vai mostrar uma prévia dos links para esse domínio.

    Se quiser que os links de visualização do app do Chat para um subdomínio específico, como subdomain.example.com, inclua o subdomínio.

    Para que o app de chat mostre links de visualização para todo o domínio, especifique um caractere curinga com um asterisco (*) como subdomínio. Por exemplo, *.example.com corresponde a subdomain.example.com e any.number.of.subdomains.example.com.

  7. No campo Prefixo do caminho, insira um caminho para anexar ao domínio do padrão do host.

    Para corresponder a todos os URLs no domínio do padrão do host, deixe o Prefixo do caminho em branco.

    Por exemplo, se o padrão do host for support.example.com, para corresponder aos URLs dos casos hospedados em support.example.com/cases/, insira cases/.

  8. Clique em Concluído.

  9. Clique em Salvar.

Agora, sempre que alguém incluir um link que corresponda a um padrão de URL de visualização de link em uma mensagem em um espaço do Chat que inclua seu app, o link será mostrado na prévia.

Depois de configurar a visualização de links para um determinado link, o app de chat poderá reconhecer e visualizar o link anexando mais informações a ele.

Nos espaços do Chat que incluem seu app do Chat, quando a mensagem de alguém contém um link que corresponde a um padrão de URL de visualização de link, o app recebe um evento de interação MESSAGE. O payload JSON do evento de interação contém o campo matchedUrl:

JSON

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

Ao verificar a presença do campo matchedUrl no payload do evento MESSAGE, seu app do Chat pode adicionar informações à mensagem com o link visualizado. O app do Chat pode responder com uma mensagem de texto básica ou anexar um card.

Responder com uma mensagem de texto

Para respostas básicas, o app de chat pode mostrar uma prévia de um link respondendo com uma mensagem de texto simples para um link. Este exemplo anexa uma mensagem que repete o URL do link que corresponde a um padrão de URL de visualização de link.

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

Para anexar um card a um link visualizado, retorne um ActionResponse do tipo UPDATE_USER_MESSAGE_CARDS. Este exemplo anexa um card básico.

App de chat com a visualização de um link anexando um card à mensagem

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?hl=pt-BR
# 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

Neste exemplo, uma mensagem de cartão é enviada retornando o JSON do cartão. Também é possível usar o serviço de cards do 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'}}
          }]}}
        ]}]
      }
    }]
  };
}

O app de chat pode atualizar um card de visualização de link quando os usuários interagem com ele, como clicar em um botão no card.

Para atualizar o card, o app do Chat precisa processar o evento de interação CARD_CLICKED e retornar um actionResponse com base em quem enviou a mensagem que contém a visualização do link:

  • Se um usuário enviou a mensagem, defina actionResponse.type como UPDATE_USER_MESSAGE_CARDS.
  • Se o app do Chat enviou a mensagem, defina o actionResponse.type como UPDATE_MESSAGE.

Para determinar quem enviou a mensagem, use o campo message.sender.type do evento de interação para saber se o remetente era um usuário HUMAN ou BOT.

O exemplo a seguir mostra como um app de chat atualiza uma visualização de link sempre que um usuário clica no botão Atribuir a mim, atualiza o campo Assignee do card e desativa o botão.

App de chat com a prévia de um link com uma versão atualizada de um card anexado a uma mensagem

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

Neste exemplo, uma mensagem de cartão é enviada retornando o JSON do cartão. Também é possível usar o serviço de cards do 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'}}
            }]}}
          ]}]
        }
      }]
    };
  }
}

Limites e considerações

Ao configurar as prévias de links no seu app Chat, observe estes limites e considerações:

  • Cada app do Chat é compatível com visualizações de links de até cinco padrões de URL.
  • Os apps de chat mostram uma prévia de um link por mensagem. Se vários links visualizáveis estiverem presentes em uma única mensagem, somente o primeiro será visualizado.
  • Os apps de chat só mostram links que começam com https://, então https://support.example.com/cases/ mostra uma prévia, mas support.example.com/cases/ não.
  • A menos que a mensagem inclua outras informações enviadas ao app Chat, como um comando de barra, apenas o URL do link é enviado ao app Chat pelas visualizações de link.
  • Se um usuário postar o link, um app do Chat só poderá atualizar o card de visualização de link se os usuários interagirem com ele, como clicando em um botão. Não é possível chamar o método update() da API Chat no recurso Message para atualizar a mensagem de um usuário de forma assíncrona.
  • Os apps de chat precisam mostrar prévias de links para todos no espaço. Portanto, a mensagem precisa omitir o campo privateMessageViewer.

Ao implementar as visualizações de links, talvez seja necessário depurar o app do Chat lendo os registros dele. Para ler os registros, acesse o Explorador de registros no console do Google Cloud.