Partilhar via


Integrar o seu próprio canal personalizado através do Direct Line

Com o Dynamics 365 Contact Center, você pode implementar um conector para integrar canais de mensagens personalizados usando a API de Linha Direta 3.0, que faz parte do SDK do .NET. O código de exemplo completo ilustra como você pode criar seu próprio conector. Para saber mais sobre a API Direct Line 3.0, consulte Principais conceitos na API Direct Line 3.0.

Este artigo explica como um canal é conectado ao Microsoft Direct Line Bot Framework, que está conectado internamente ao Dynamics 365 Contact Center. A seção a seguir inclui trechos de código que usam a API de Linha Direta 3.0 para criar um cliente de Linha Direta e a IChannelAdapter interface para criar um conector de exemplo.

Observação

O código-fonte e a documentação descrevem o fluxo geral de como o canal pode se conectar ao Dynamics 365 Contact Center por meio do Direct Line e não se concentram em aspetos de confiabilidade e escalabilidade.

Componentes

Serviço da API Webhook do Adaptador

Quando o usuário insere uma mensagem, a API do adaptador é invocada a partir do canal. Ele processa a solicitação de entrada e envia um status de sucesso ou falha como resposta. O serviço de API do adaptador deve implementar a IChannelAdapter interface e envia a solicitação de entrada para o respetivo adaptador de canal para processar a solicitação.

/// <summary>
/// Accept an incoming web-hook request from MessageBird Channel
/// </summary>
/// <param name="requestPayload">Inbound request Object</param>
/// <returns>Executes the result operation of the action method asynchronously.</returns>
    [HttpPost("postactivityasync")]
    public async Task<IActionResult> PostActivityAsync(JToken requestPayload)
    {
        if (requestPayload == null)
        {
            return BadRequest("Request payload is invalid.");
        }

        try
        {
            await _messageBirdAdapter.ProcessInboundActivitiesAsync(requestPayload, Request).ConfigureAwait(false);
        }
        catch (Exception ex)
        {
            _logger.LogError($"postactivityasync: {ex}");
            return StatusCode(500, "An error occured while handling your request.");
        }

        return StatusCode(200);
    }

Adaptadores de canais

O adaptador de canal processa as atividades de entrada e saída e deve implementar a IAdapterBuilder interface.

Processar atividades de entrada

O adaptador de canal executa as seguintes atividades de entrada:

  1. Valide a assinatura da solicitação de mensagem de entrada.

A solicitação de entrada do canal é validada com base na chave de assinatura. Se a solicitação for inválida, uma mensagem de exceção de "assinatura inválida" será lançada. Se o pedido for válido, procede da seguinte forma:

  /// <summary>
  /// Validate Message Bird Request
  /// </summary>
  /// <param name="content">Request Content</param>
  /// <param name="request">HTTP Request</param>
  /// <param name="messageBirdSigningKey">Message Bird Signing Key</param>
  /// <returns>True if there request is valid, false if there aren't.</returns>
  public static bool ValidateMessageBirdRequest(string content, HttpRequest request, string messageBirdSigningKey)
  {
      if (string.IsNullOrWhiteSpace(messageBirdSigningKey))
      {
          throw new ArgumentNullException(nameof(messageBirdSigningKey));
      }
      if (request == null)
      {
          throw new ArgumentNullException(nameof(request));
      }
      if (string.IsNullOrWhiteSpace(content))
      {
          throw new ArgumentNullException(nameof(content));
      }
      var messageBirdRequest = new MessageBirdRequest(
          request.Headers?["Messagebird-Request-Timestamp"],
          request.QueryString.Value?.Equals("?",
              StringComparison.CurrentCulture) != null
              ? string.Empty
              : request.QueryString.Value,
          GetBytes(content));

      var messageBirdRequestSigner = new MessageBirdRequestSigner(GetBytes(messageBirdSigningKey));
      string expectedSignature = request.Headers?["Messagebird-Signature"];
      return messageBirdRequestSigner.IsMatch(expectedSignature, messageBirdRequest);
  }
  1. Converter o pedido de entrada para uma atividade bot.

A carga útil da solicitação de entrada é convertida em uma atividade que o Bot Framework pode entender.

Observação

A carga útil da atividade não deve exceder o limite de tamanho da mensagem de 28 KB.

Este objeto Activity inclui os seguintes atributos:

Atributo Descrição
de Armazena as informações da conta do canal que consiste no identificador exclusivo do usuário e do nome (combinação de nome e sobrenome, separados por um delimitador de espaço).
channelId Indica o identificador do canal. Para solicitações de entrada, o ID do canal é directline.
serviceUrl Indica a URL do serviço. Para solicitações de entrada, a URL do serviço é https://directline.botframework.com/.
tipo Indica o tipo de atividade. Para atividades de mensagens, o tipo é message.
texto Armazena o conteúdo da mensagem.
ID Indica o identificador que o adaptador usa para responder a mensagens de saída.
channelData Indica os dados do canal que consistem em channelType, conversationcontexte customercontext.
channelType Indica o nome do canal através do qual o cliente está enviando mensagens. Por exemplo, MessageBird, KakaoTalk, Snapchat
conversationcontext Refere-se a um objeto de dicionário que contém as variáveis de contexto definidas no fluxo de trabalho. O Dynamics 365 Contact Center utiliza esta informação para encaminhar a conversação para o representante de suporte ao cliente correto (representante de suporte ou representante). Por exemplo:
"contextodeconversa ":{ "NomeDoProduto": "Xbox", "Problema":"Instalação" }
Neste exemplo, o contexto encaminha a conversa para o representante de serviço que lida com a instalação do Xbox.
customercontext Refere-se a um objeto de dicionário que contém os detalhes do cliente, como número de telefone e endereço de e-mail. O Dynamics 365 Contact Center usa essas informações para identificar o registro de contato do usuário.
"contextocliente":{ "email":email@email.com, "numerodetelefone":"1234567890" }
  /// <summary>
  /// Build Bot Activity type from the inbound MessageBird request payload<see cref="Activity"/>
  /// </summary>
  /// <param name = "messagePayload"> Message Bird Activity Payload</param>
  /// <returns>Direct Line Activity</returns>
  public static Activity PayloadToActivity(MessageBirdRequestModel messagePayload)
  {
  if (messagePayload == null)
  {
      throw new ArgumentNullException(nameof(messagePayload));
  }
  if (messagePayload.Message?.Direction == ConversationMessageDirection.Sent ||
  messagePayload.Type == ConversationWebhookMessageType.MessageUpdated)
  {
      return null;
  }
  var channelData = new ActivityExtension
  {
      ChannelType = ChannelType.MessageBird,
      // Add Conversation Context in below dictionary object. Please refer the document for more information.
      ConversationContext = new Dictionary<string, string>(),
      // Add Customer Context in below dictionary object. Please refer the document for more information.
      CustomerContext = new Dictionary<string, string>()
  };
  var activity = new Activity
      {
          From = new ChannelAccount(messagePayload.Message?.From, messagePayload.Contact?.DisplayName),
          Text = messagePayload.Message?.Content?.Text,
          Type = ActivityTypes.Message,
          Id = messagePayload.Message?.ChannelId,
          ServiceUrl = Constant.DirectLineBotServiceUrl,
          ChannelData = channelData
      };

      return activity;
  }

A carga útil JSON de exemplo é a seguinte:

{
    "type": "message",
    "id": "bf3cc9a2f5de...",    
    "serviceUrl": https://directline.botframework.com/,
    "channelId": "directline",
    "from": {
        "id": "1234abcd",// userid which uniquely identify the user
        "name": "customer name" // customer name as First Name <space> Last Name
    },
    "text": "Hi,how are you today.",
    "channeldata":{
        "channeltype":"messageBird",
        "conversationcontext ":{ // this holds context variables defined in Workstream
            "ProductName" : "XBox",
            "Issue":"Installation"
        },
        "customercontext":{            
            "email":email@email.com,
            "phonenumber":"1234567890"           
        }
    }
}

  1. Envie a atividade para o processador de retransmissão de mensagens.

Depois de criar o payload da atividade, ele chama o método PostActivityAsync do processador de retransmissão de mensagens para enviar a atividade para o Direct Line. O adaptador de canal também deve passar o processador de eventos, que o processador de reencaminhamento invoca quando recebe uma mensagem de saída do Dynamics 365 Contact Center através do Direct Line.

Processar atividades de saída

O processador de retransmissão invoca o manipulador de eventos para enviar atividades de saída para o respetivo adaptador de canal e, em seguida, o adaptador processa as atividades de saída. O adaptador de canal executa as seguintes atividades de saída:

  1. Converter atividades de saída para o modelo de resposta do canal.

As atividades da Linha Direta são convertidas para o modelo de resposta específico de cada canal.

  /// <summary>
  /// Creates MessageBird response object from a Bot Framework <see cref="Activity"/>.
  /// </summary>
  /// <param name="activities">The outbound activities.</param>
  /// <param name="replyToId">Reply Id of Message Bird user.</param>
  /// <returns>List of MessageBird Responses.</returns>
  public static List<MessageBirdResponseModel> ActivityToMessageBird(IList<Activity> activities, string replyToId)
  {
      if (string.IsNullOrWhiteSpace(replyToId))
      {
          throw new ArgumentNullException(nameof(replyToId));
      }

      if (activities == null)
      {
          throw new ArgumentNullException(nameof(activities));
      }

      return activities.Select(activity => new MessageBirdResponseModel
      {
          To = replyToId,
          From = activity.ChannelId,
          Type = "text",
          Content = new Content
          {
              Text = activity.Text
          }
      }).ToList();
  }
  1. Envie respostas através da API REST do canal.

O adaptador de canal chama a API REST para enviar uma resposta de saída para o canal, que é enviada ao usuário.

  /// <summary>
  /// Send Outbound Messages to Message Bird
  /// </summary>
  /// <param name="messageBirdResponses">Message Bird Response object</param>
  /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
  public async Task SendMessagesToMessageBird(IList<MessageBirdResponseModel> messageBirdResponses)
  {
      if (messageBirdResponses == null)
      {
          throw new ArgumentNullException(nameof(messageBirdResponses));
      }

      foreach (var messageBirdResponse in messageBirdResponses)
      {
          using (var request = new HttpRequestMessage(HttpMethod.Post, $"{MessageBirdDefaultApi}/send"))
          {
              var content = JsonConvert.SerializeObject(messageBirdResponse);
              request.Content = new StringContent(content, Encoding.UTF8, "application/json");
              await _httpClient.SendAsync(request).ConfigureAwait(false);
          }
      }
  }

Processador de retransmissão de mensagens

O processador de retransmissão de mensagens recebe a atividade de entrada do adaptador de canal e faz a validação do modelo de atividade. O processador de reencaminhamento verifica se a conversação está ativa para a atividade em particular, antes de enviar esta atividade para a Direct Line

Para procurar se a conversa está ativa, o processador de retransmissão mantém uma coleção de conversas ativas em um dicionário. Este dicionário contém chave como ID de usuário, que identifica exclusivamente o usuário e Value como um objeto da seguinte classe:

 /// <summary>
/// Direct Line Conversation to store as an Active Conversation
/// </summary>
public class DirectLineConversation
{
    /// <summary>
    /// .NET SDK Client to connect to Direct Line Bot
    /// </summary>
    public DirectLineClient DirectLineClient { get; set; }

    /// <summary>
    /// Direct Line response after start a new conversation
    /// </summary>
    public Conversation Conversation { get; set; }

    /// <summary>
    /// Watermark to guarantee that no messages are lost
    /// </summary>
    public string WaterMark { get; set; }
}

Se a conversação não estiver ativa para a atividade recebida pelo processador de reencaminhamento, efetua os seguintes passos:

  1. Inicia uma conversação com a Direct Line e guarda o objeto de conversação enviado pela Direct Line com o ID de utilizador no dicionário.
 /// <summary>
 /// Initiate Conversation with Direct Line Bot
 /// </summary>
 /// <param name="inboundActivity">Inbound message from Aggregator/Channel</param>
 /// <param name="adapterCallBackHandler">Call Back to send activities to Messaging API</param>
 /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
 private async Task InitiateConversation(Activity inboundActivity, EventHandler<IList<Activity>> adapterCallBackHandler)
 {
     var directLineConversation = new DirectLineConversation
     {
         DirectLineClient = new DirectLineClient(_relayProcessorConfiguration.Value.DirectLineSecret)
     };
     // Start a conversation with Direct Line Bot
     directLineConversation.Conversation = await directLineConversation.DirectLineClient.Conversations.
         StartConversationAsync().ConfigureAwait(false);

     await directLineConversation.DirectLineClient.Conversations.
         StartConversationAsync().ConfigureAwait(false);
     if (directLineConversation.Conversation == null)
     {
         throw new Exception(
             "An error occurred while starting the Conversation with direct line. Please validate the direct line secret in the configuration file.");
     }

     // Adding the Direct Line Conversation object to the lookup dictionary and starting a thread to poll the activities from the direct line bot.
     if (ActiveConversationCache.ActiveConversations.TryAdd(inboundActivity.From.Id, directLineConversation))
     {
         // Starts a new thread to poll the activities from Direct Line Bot
         new Thread(async () => await PollActivitiesFromBotAsync(
             directLineConversation.Conversation.ConversationId, inboundActivity, adapterCallBackHandler).ConfigureAwait(false))
         .Start();
     }
 }
  1. Inicia um novo thread para sondar as atividades de saída do bot Direct Line com base no intervalo de sondagem configurado no arquivo de configuração. O tópico da consulta está ativo até que o fim da atividade de conversação seja recebido da Direct Line.
/// <summary>
/// Polling the activities from BOT for the active conversation
/// </summary>
/// <param name="conversationId">Direct Line Conversation Id</param>
/// <param name="inboundActivity">Inbound Activity from Channel/Aggregator</param>
/// <param name="lineActivitiesReceived">Call Back to send activities to Messaging API</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
private async Task PollActivitiesFromBotAsync(string conversationId, Activity inboundActivity, EventHandler<IList<Activity>> lineActivitiesReceived)
{
    if (!int.TryParse(_relayProcessorConfiguration.Value.PollingIntervalInMilliseconds, out var pollingInterval))
    {
        throw new FormatException($"Invalid Configuration value of PollingIntervalInMilliseconds: {_relayProcessorConfiguration.Value.PollingIntervalInMilliseconds}");
    }
    if (!ActiveConversationCache.ActiveConversations.TryGetValue(inboundActivity.From.Id,
        out var conversationContext))
    {
        throw new KeyNotFoundException($"No active conversation found for {inboundActivity.From.Id}");
    }
    while (true)
    {
        var watermark = conversationContext.WaterMark;
        // Retrieve the activity set from the bot.
        var activitySet = await conversationContext.DirectLineClient.Conversations.
            GetActivitiesAsync(conversationId, watermark).ConfigureAwait(false);
        // Set the watermark to the message received
        watermark = activitySet?.Watermark;

        // Extract the activities sent from our bot.
        if (activitySet != null)
        {
            var activities = (from activity in activitySet.Activities
                              where activity.From.Id == _relayProcessorConfiguration.Value.BotHandle
                              select activity).ToList();
            if (activities.Count > 0)
            {
                SendReplyActivity(activities, inboundActivity, lineActivitiesReceived);
            }
            // Update Watermark
            ActiveConversationCache.ActiveConversations[inboundActivity.From.Id].WaterMark = watermark;
            if (activities.Exists(a => a.Type.Equals("endOfConversation", StringComparison.InvariantCulture)))
            {
                if (ActiveConversationCache.ActiveConversations.TryRemove(inboundActivity.From.Id, out _))
                {
                    Thread.CurrentThread.Abort();
                }
            }
        }
        await Task.Delay(TimeSpan.FromMilliseconds(pollingInterval)).ConfigureAwait(false);
    }
}

Observação

No centro do código que recebe a mensagem está o método GetActivitiesAsync que usa ConversationId e watermark como parâmetros. O objetivo do parâmetro watermark é obter as mensagens que não são entregues pela Direct Line. Se o parâmetro da marca de água for especificado, a conversa é reproduzida a partir da marca de água, para que não se percam mensagens.

Envie a atividade para Linha Direta

 /// <summary>
 /// Send the activity to the bot using Direct Line client
 /// </summary>
 /// <param name="inboundActivity">Inbound message from Aggregator/Channel</param>
 /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
 private static async Task SendActivityToBotAsync(Activity inboundActivity)
 {
     if (!ActiveConversationCache.ActiveConversations.TryGetValue(inboundActivity.From.Id,
         out var conversationContext))
     {
         throw new KeyNotFoundException($"No active conversation found for {inboundActivity.From.Id}");
     }
     await conversationContext.DirectLineClient.Conversations.PostActivityAsync(
         conversationContext.Conversation.ConversationId, inboundActivity).ConfigureAwait(false);
 }

Se a conversa estiver ativa para a atividade recebida pelo processador de retransmissão, ela enviará a atividade para o processador de retransmissão de mensagens.

Terminar uma conversação

Para terminar a conversa, consulte Terminar uma conversa em Linha Direta.

Formatos de markdown em canais personalizados

Pode enviar e receber mensagens formatadas com Markdown em canais de mensagens personalizados utilizando a Direct Line API 3.0. Compreender como o formato de Markdown é transmitido através do canal e conhecer os detalhes do formato ajuda a atualizar as etiquetas e o estilo HTML na sua própria interface de utilizador.

No canal Direct Line, quando um representante de suporte ao cliente (representante ou representante de suporte) envia (saída) uma mensagem formatada com Markdown para um bot de Direct Line, o bot recebe a mensagem num determinado formato. Agora, se um bot receber (entrada) uma mensagem formatada de um cliente, tem de ser capaz de interpretar corretamente a mensagem que é formatada com Markdown. Como programador, terá de utilizar o Markdown adequadamente para que a mensagem seja formatada corretamente para o seu representante de suporte e os seus clientes.

Saiba mais sobre formatos de markdown em Formatos markdown para mensagens de chat.

Observação

  • Atualmente, não suportamos a combinação de teclas <Shift + Enter> para adicionar várias quebras de linha.
  • Para mensagens de entrada, defina o texto de Markdown na propriedade do objeto da text.
  • Para mensagens de saída, o texto de Markdown é recebido na propriedade do objeto da text (semelhante a uma mensagem normal).

Próximos passos

Suporte para chat em direto e canais assíncronos

Configurar canal de mensagens personalizado
Referência da API do MessageBird
Melhores práticas para configurar bots