Partilhar via


Tutorial: Ativar o suporte a anexos de ficheiros na sua aplicação de Chat

O SDK do Chat funciona perfeitamente com o Microsoft Teams no contexto de uma reunião. Somente um usuário do Teams pode enviar anexos de arquivo para um usuário dos Serviços de Comunicação do Azure. Um usuário dos Serviços de Comunicação do Azure não pode enviar anexos de arquivo para um usuário do Teams. Para obter os recursos atuais, consulte Bate-papo de interoperabilidade do Teams.

Adicionar suporte a anexos de ficheiros

O SDK de bate-papo fornece a previewUrl propriedade para cada anexo de arquivo. Especificamente, previewUrl links para uma página da Web no SharePoint onde o usuário pode ver o conteúdo do arquivo, editá-lo e baixá-lo se a permissão permitir.

Algumas restrições estão associadas a esse recurso:

  • O administrador do Teams do locatário do remetente pode impor políticas que limitem ou desativem totalmente esse recurso. Por exemplo, o administrador do Teams pode desativar determinadas permissões (como Anyone) que podem fazer com que o URL do anexo do arquivo (previewUrl) fique inacessível.

  • Atualmente, suportamos apenas duas permissões de arquivo:

    • Anyone
    • People you choose (com endereço de e-mail)

    Informe os usuários do Teams de que todas as outras permissões (como People in your organization) não são suportadas. Os usuários do Teams devem verificar novamente se a permissão padrão é suportada depois de carregarem o arquivo no cliente do Teams.

  • O URL de descarregamento direto (url) não é suportado.

Além dos arquivos regulares (com AttachmentType de ), o SDK de filebate-papo também fornece a AttachmentType propriedade de image. Os usuários dos Serviços de Comunicação do Azure podem anexar imagens de uma forma que espelhe o comportamento de como o cliente Microsoft Teams converte o anexo de imagem em imagens embutidas na camada da interface do usuário. Para obter mais informações, consulte Manipular anexos de imagem.

Os utilizadores dos Serviços de Comunicação do Azure podem adicionar imagens através do Carregamento a partir deste dispositivo, que é renderizado no lado do Teams e o SDK de Chat devolve anexos como image. Para imagens carregadas através de ficheiros na nuvem Anexar, as imagens são tratadas como ficheiros normais no lado do Teams para que o SDK do Chat devolva anexos como file.

Observe também que os usuários dos Serviços de Comunicação do Azure só podem carregar arquivos usando arrastar e soltar ou por meio dos comandos do menu de anexos Carregar deste dispositivo e Anexar arquivos na nuvem. Certos tipos de mensagens com mídia incorporada (como clipes de vídeo, mensagens de áudio e cartões meteorológicos) não são suportados no momento.

Este tutorial descreve como habilitar o suporte a anexos de arquivo usando o SDK de Chat dos Serviços de Comunicação do Azure para JavaScript.

Código de exemplo

Encontre o código finalizado deste tutorial no GitHub.

Pré-requisitos

Objetivos

  • Renderize o anexo do arquivo no histórico de mensagens. Cada cartão de anexo de ficheiro tem um botão Abrir .
  • Renderize anexos de imagem como imagens embutidas.

Manipular anexos de arquivo

O SDK de bate-papo para JavaScript retorna uma ChatAttachmentType propriedade de para anexos de file arquivos regulares e image para imagens embutidas em mensagens.

export interface ChatMessageReceivedEvent extends BaseChatMessageEvent {
  /**
   * Content of the message.
   */
  message: string;

  /**
   * Chat message attachment.
   */
  attachments?: ChatAttachment[];
  
  ...
}

export interface ChatAttachment {
  /** Id of the attachment */
  id: string;
  /** The type of attachment. */
  attachmentType: AttachmentType;
  /** The name of the attachment content. */
  name?: string;
  /** The URL that is used to provide the original size of the inline images */
  url?: string;
  /** The URL that provides the preview of the attachment */
  previewUrl?: string;
}

/** Type of supported attachments. */
export type ChatAttachmentType = "image" | "file" | "unknown";

Por exemplo, o JSON a seguir mostra a ChatAttachment aparência de um anexo de imagem e um anexo de arquivo:

"attachments": [
    {
        "id": "08a182fe-0b29-443e-8d7f-8896bc1908a2",
        "attachmentType": "file",
        "name": "business report.pdf",
        "previewUrl": "https://contoso.sharepoint.com/:u:/g/user/h8jTwB0Zl1AY"
    },
    {
        "id": "9d89acb2-c4e4-4cab-b94a-7c12a61afe30",
        "attachmentType": "image", 
        "name": "Screenshot.png",
        "url": "https://contoso.communication.azure.com/chat/threads/19:9d89acb29d89acb2@thread.v2/messages/123/images/9d89acb2-c4e4-4cab-b94a-7c12a61afe30/views/original?api-version=2023-11-15-preview",
        "previewUrl": "https://contoso.communication.azure.com/chat/threads/19:9d89acb29d89acb2@thread.v2/messages/123/images/9d89acb2-c4e4-4cab-b94a-7c12a61afe30/views/small?api-version=2023-11-15-preview"
      }
]

Agora vamos voltar ao manipulador de eventos que você criou em Guia de início rápido: ingresse seu aplicativo de bate-papo em uma reunião do Teams e adicione alguma lógica extra para lidar com anexos com a attachmentType propriedade de file:

chatClient.on("chatMessageReceived", (e) => {
  console.log("Notification chatMessageReceived!");
  // Check whether the notification is intended for the current thread
  if (threadIdInput.value != e.threadId) {
     return;
  }
   
  if (e.sender.communicationUserId != userId) {
    renderReceivedMessage(e);
  } else {
    renderSentMessage(e.message);
  }
});

async function renderReceivedMessage(event) {
    messages += `<div class="container lighter"> ${event.message} </div>`;
    messagesContainer.innerHTML = messages;

    // Get the list of attachments and calls renderFileAttachments to construct a file attachment card
    var attachmentHtml = event.attachments
        .filter(attachment => attachment.attachmentType === "file")
        .map(attachment => renderFileAttachments(attachment))
        .join('');
    messagesContainer.innerHTML += attachmentHtml;
}

function renderFileAttachments(attachment) {
    var re = /(?:\.([^.]+))?$/;
    var fileExtension = re.exec(attachment.name)[1];  
    return '<div class="attachment-container">' +
        '<img class="attachment-icon" alt="attachment file icon" />' +
        '<div>' +
        '<p class="attachment-type">' + fileExtension + '</p>' +
        '<p>' + attachment.name + '</p>' +
        '<a href=' + attachment.previewUrl + ' target="_blank" rel="noreferrer">Open</a>' +
        '</div>' +
        '</div>';
}

Certifique-se de adicionar algum CSS para o cartão de anexo:

  /* Let's make the chat popup scrollable */
  .chat-popup {

     ...

     max-height: 650px;
     overflow-y: scroll;
}

 .attachment-container {
     overflow: hidden;
     background: #f3f2f1;
     padding: 20px;
     margin: 0;
     border-radius: 10px;
}
 .attachment-container img {
     width: 50px;
     height: 50px;
     float: left;
     margin: 0;
}
 .attachment-container p {
     font-weight: 700;
     margin: 0 5px 20px 0;
}
 .attachment-container {
     display: grid;
     grid-template-columns: 100px 1fr;
     margin-top: 5px;
}
 .attachment-icon {
     content: url("data:image/svg+xml;base64, ...");
}
 .attachment-container a {
     background-color: #dadada;
     color: black;
     font-size: 12px;
     padding: 10px;
     border: none;
     cursor: pointer;
     border-radius: 5px;
     text-align: center;
     margin-right: 10px;
     text-decoration: none;
     margin-top: 10px;
}
 .attachment-container a:hover {
     background-color: black;
     color: white;
}
 .attachment-type {
     position: absolute;
     color: black;
     border: 2px solid black;
     background-color: white;
     margin-top: 50px;
     font-family: sans-serif;
     font-weight: 400;
     padding: 2px;
     text-transform: uppercase;
     font-size: 8px;
}

Isso é tudo o que você precisa para lidar com anexos de arquivos. Em seguida, vamos executar o código.

Executar o código

Para Webpack, você pode usar a webpack-dev-server propriedade para criar e executar seu aplicativo. Execute o seguinte comando para agrupar o host do aplicativo em um servidor Web local:

npx webpack-dev-server --entry ./client.js --output bundle.js --debug --devtool inline-source-map

Ou:

npm start

Demonstração do anexo de arquivo

  1. Abra o seu browser e vá para http://localhost:8080/. Insira o URL da reunião e o ID do thread.

  2. Envie alguns anexos de arquivo do cliente Teams.

    Captura de tela que mostra o cliente do Teams com uma mensagem enviada com três anexos de arquivo.

  3. Você verá a nova mensagem sendo processada junto com os anexos de arquivo.

    Captura de ecrã que mostra uma aplicação de exemplo com uma mensagem recebida com três anexos de ficheiro.

Manipular anexos de imagem

Os anexos de imagem precisam ser tratados de forma diferente dos anexos padrão file . Os anexos de imagem têm a attachmentType propriedade de , que requer o token de imagecomunicação para recuperar as imagens de visualização ou em tamanho real.

Antes de continuar, conclua o tutorial que demonstra como habilitar o suporte a imagens embutidas em seu aplicativo de bate-papo. Este tutorial descreve como buscar imagens que exigem um token de comunicação no cabeçalho da solicitação. Depois de receber o blob de imagem, você precisa criar uma ObjectUrl propriedade que aponte para esse blob. Em seguida, você injeta esse URL no src atributo de cada imagem embutida.

Agora que você está familiarizado com o funcionamento das imagens embutidas, pode renderizar anexos de imagem como uma imagem embutida normal.

Primeiro, injete uma image tag no conteúdo da mensagem sempre que houver um anexo de imagem:

async function renderReceivedMessage(event) {
    messages += `<div class="container lighter"> ${event.message} </div>`;
    messagesContainer.innerHTML = messages;
    console.log(event);
    // Filter out inline images from attachments
    const imageAttachments = event.attachments?.filter(
        (attachment) =>
        attachment.attachmentType === "image" && !messages.includes(attachment.id)
    );
    // Inject image tag for all image attachments
    var imageAttachmentHtml =
        imageAttachments
        .map((attachment) => renderImageAttachments(attachment))
        .join("") ?? "";
    messagesContainer.innerHTML += imageAttachmentHtml;

    // Get list of attachments and calls renderFileAttachments to construct a file attachment card
    var attachmentHtml =
        event.attachments
        ?.filter((attachment) => attachment.attachmentType === "file")
        .map((attachment) => renderFileAttachments(attachment))
        .join("") ?? "";
    messagesContainer.innerHTML += attachmentHtml;

    // Fetch and render preview images
    fetchPreviewImages(imageAttachments);
}

function renderImageAttachments(attachment) {
    return `<img alt="image" src="" itemscope="png" id="${attachment.id}" style="max-width: 100px">`
}

Agora, vamos pegar emprestado fetchPreviewImages() do Tutorial: Ativar suporte a imagens embutidas e usá-lo como está, sem alterações:

function fetchPreviewImages(attachments) {
    if (!attachments.length > 0) {
        return;
    }
    Promise.all(
        attachments.map(async (attachment) => {
            const response = await fetch(attachment.previewUrl, {
                method: 'GET',
                headers: {
                    'Authorization': 'Bearer ' + tokenString,
                },
            });
            return {
                id: attachment.id,
                content: await response.blob(),
            };
        }),
    ).then((result) => {
        result.forEach((imageRef) => {
            const urlCreator = window.URL || window.webkitURL;
            const url = urlCreator.createObjectURL(imageRef.content);
            document.getElementById(imageRef.id).src = url;
        });
    }).catch((e) => {
        console.log('error fetching preview images');
    });
}

Essa função precisa de uma tokenString propriedade, portanto, você precisa de uma cópia global inicializada no init(), conforme mostrado no trecho de código a seguir:

var tokenString = '';

async function init() {
    ...
    const {
        token,
        expiresOn
    } = tokenResponse;
    
    tokenString = token;
    ...
}

Agora você tem suporte a anexos de imagem. Continue a executar o código e vê-lo em ação.

Demonstração do anexo de imagem

  1. Envie alguns anexos de imagem do cliente Teams.

    Captura de tela que mostra o cliente do Teams mostrando uma caixa de envio com um anexo de imagem carregado.

  2. Depois de enviar o anexo de imagem, observe que ele se torna uma imagem embutida no lado do cliente do Teams.

    Captura de tela que mostra o cliente do Teams mostrando uma mensagem com o anexo de imagem enviado ao outro participante.

  3. Retorne ao aplicativo de exemplo e verifique se a mesma imagem foi renderizada.

    Captura de tela que mostra um aplicativo de exemplo mostrando uma mensagem de entrada com uma imagem embutida renderizada.

Este tutorial descreve como habilitar o suporte a anexos de arquivo usando o SDK de Chat dos Serviços de Comunicação do Azure para C#.

Neste tutorial, irá aprender a:

  • Manipule anexos de arquivos.
  • Manipule anexos de imagem.

Pré-requisitos

  • Reveja o início rápido Junte-se à sua aplicação de Chat numa reunião do Teams.
  • Crie um recurso dos Serviços de Comunicação do Azure conforme descrito em Criar um recurso dos Serviços de Comunicação do Azure. Você precisa gravar sua cadeia de conexão para este tutorial.
  • Configure uma reunião do Teams usando sua conta comercial e tenha o URL da reunião pronto.
  • Baixe o SDK de bate-papo para C# (@azure/communication-chat) 1.3.0 ou o mais recente. Para obter mais informações, consulte Biblioteca de cliente do Chat de Comunicação do Azure.

Código de exemplo

Encontre o código finalizado para este tutorial no GitHub.

Manipular anexos de arquivo

O SDK de bate-papo para C# retorna uma ChatAttachmentType propriedade de para anexos de file arquivo regulares e image para imagens embutidas.

public readonly partial struct ChatAttachmentType : IEquatable<ChatAttachmentType>
{
        private const string ImageValue = "image";
        private const string FileValue = "file";
        /// <summary> image. </summary>
        public static ChatAttachmentType Image { get; } = new ChatAttachmentType(ImageValue);
        /// <summary> file. </summary>
        public static ChatAttachmentType File { get; } = new ChatAttachmentType(FileValue);
}


Por exemplo, o JSON a seguir mostra como ChatAttachment pode ser um anexo de imagem e um anexo de arquivo quando você recebe solicitações do lado do servidor:

"attachments": [
    {
        "id": "08a182fe-0b29-443e-8d7f-8896bc1908a2",
        "attachmentType": "file",
        "name": "business report.pdf",
        "previewUrl": "https://contoso.sharepoint.com/:u:/g/user/h8jTwB0Zl1AY"
    },
    {
        "id": "9d89acb2-c4e4-4cab-b94a-7c12a61afe30",
        "attachmentType": "image", 
        "name": "Screenshot.png",
        "url": "https://contoso.communication.azure.com/chat/threads/19:9d89acb29d89acb2@thread.v2/messages/123/images/9d89acb2-c4e4-4cab-b94a-7c12a61afe30/views/original?api-version=2023-11-15-preview",
        "previewUrl": "https://contoso.communication.azure.com/chat/threads/19:9d89acb29d89acb2@thread.v2/messages/123/images/9d89acb2-c4e4-4cab-b94a-7c12a61afe30/views/small?api-version=2023-11-15-preview"
      }
]

Agora volte para o manipulador de eventos que você criou no início rápido anterior e adicione alguma lógica extra para manipular anexos com a ChatAttachmentType propriedade defile:


await foreach (ChatMessage message in allMessages)
{
    // Get message attachments that are of type 'file'
    IEnumerable<ChatAttachment> fileAttachments = message.Content.Attachments.Where(x => x.AttachmentType == ChatAttachmentType.File);
    var chatAttachmentFileUris = new List<Uri>();
    foreach (var file in fileAttachments) 
    {
        chatAttachmentFileUris.Add(file.PreviewUri);
    }

    // Build message list
    if (message.Type == ChatMessageType.Html || message.Type == ChatMessageType.Text)
    {
        textMessages++;
        var userPrefix = message.Sender.Equals(currentUser) ? "[you]:" : "";
        var strippedMessage = StripHtml(message.Content.Message);
      


        var chatAttachments = fileAttachments.Count() > 0 ? "[Attachments]:\n" + string.Join(",\n", chatAttachmentFileUris) : "";
        messageList.Add(long.Parse(message.SequenceId), $"{userPrefix}{strippedMessage}\n{chatAttachments}");
    }
}

Especificamente, para cada anexo de arquivo, você obtém a previewUrl propriedade e constrói uma lista de URLs no for loop. Em seguida, você incorpora a cadeia de caracteres junto com o conteúdo da mensagem de bate-papo.

Manipular anexos de imagem

Você precisa lidar com anexos de imagem de forma diferente dos anexos padrão file . Os anexos de imagem têm a ChatAttachmentType propriedade de , que requer o token de imagecomunicação para recuperar as imagens de visualização ou em tamanho real.

Antes de continuar, conclua o tutorial Ativar suporte a imagens embutidas. Para identificar anexos de imagem, você precisa descobrir se o conteúdo da mensagem contém o mesmo ID de imagem dos anexos.

bool isImageAttachment = message.Content.Message.Contains(x.Id);

Se esse sinalizador for true, aplique a lógica de imagem embutida para renderizá-lo:

IEnumerable<ChatAttachment> imageAttachments = message.Content.Attachments.Where(x => x.AttachmentType == ChatAttachmentType.Image);
// Fetch image and render
var chatAttachmentImageUris = new List<Uri>();
foreach (ChatAttachment imageAttachment in imageAttachments)
{
    client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", communicationTokenCredential.GetToken().Token);
    var response = await client.GetAsync(imageAttachment.PreviewUri);
    var randomAccessStream = await response.Content.ReadAsStreamAsync();
    await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
    {
        var bitmapImage = new BitmapImage();
        await bitmapImage.SetSourceAsync(randomAccessStream.AsRandomAccessStream());
        InlineImage.Source = bitmapImage;
    });
    chatAttachmentImageUris.Add(imageAttachment.PreviewUri);
}

Agora seu aplicativo suporta anexos de imagem.

Próximos passos