https://a.storyblok.com/f/270183/102394/7a59fb21d8/e_santas-helper_1200x600.jpg

Série do Advento do Papai Noel com o Nexmo em C#

Publicado em May 18, 2021

Tempo de leitura: 8 minutos

É a época do Advento, um período tão agitado para tantos, mas para ninguém mais do que o bom Papai Noel, cuja festa é celebrada hoje pelo Rito Oriental. Em homenagem à época do Advento do C# que está chegando, vamos dar uma mãozinha ao Papai Noel automatizando parte de sua correspondência e oferecendo a ele meios mais modernos de responder às perguntas do que o serviço postal.

Para definir claramente nosso objetivo aqui, vamos criar um bot de perguntas frequentes para o Papai Noel que possa ser acessado e responder por meio do Facebook Messenger, do WhatsApp e de SMS.

Vamos criar isso com a ajuda do QnAMaker e, é claro, da Messages API da Nexmo

Pré-requisitos

  • Visual Studio 2019, versão 16.3 ou superior

  • Um Account do Azure

  • Opcional: Ngrok para implantação de teste

  • Opcional: Para o Facebook Messenger, precisaremos vincular uma Página do Facebook ao nosso Account da Nexmo — você pode ver as instruções passo a passo em Nexmo Developer. Concluindo a a parte 2 do guia criará um aplicativo Nexmo com uma página do Facebook vinculada; certifique-se de salvar o arquivo da chave privada gerado para esse aplicativo.

Observação: O código desta demonstração funcionará com mensagens do WhatsApp assim que o WhatsApp Business estiver configurado. Dito isso, o WhatsApp é mais voltado para o ambiente empresarial — para saber mais detalhes sobre como configurar um aplicativo para o WhatsApp, consulte o guia em Nexmo Developer

Criando nosso bot

Configuração

Para criar nosso bot, vamos acessar o site QnAMaker e faremos login usando um Account do Azure.

Clique em “Criar uma Base de Conhecimento”.

Siga as instruções da etapa 1 para criar um serviço QnA no Azure.

Na Etapa 2, defina o seguinte:

  • ID do diretório do Microsoft Azure

  • Nome da assinatura do Account do Azure

  • O serviço de perguntas e respostas que vamos usar (esse nome corresponderá ao nome do serviço que acabamos de criar no Portal do Azure)

  • O idioma do nosso bot

Build QnAMaker gif

Na Etapa 3, vamos nomear nossa base de conhecimento como “Ajudante do Papai Noel no Nexmo”.

É no Passo 4 que o QnA Maker se destaca — alimentar a base de conhecimento desse bot é tão fácil quanto vinculá-lo a uma página de perguntas frequentes ou fazer o upload de um arquivo com essas perguntas. É claro que, para esta demonstração, vamos usar a página de perguntas frequentes.

Também vamos adicionar algumas conversas casuais ao nosso bot — já que ele vai ser um duende honorário, vamos usar a seleção de conversas espirituosas

Com tudo isso configurado, clique em “Criar Base de Conhecimento”.

Isso importará as perguntas frequentes (FAQs) que indicamos ao QnA Maker e nos levará a uma página com a seguinte aparência:

QnA Maker Knowledgebase edit screen

Edição, publicação e teste da nossa base de conhecimento

Esta é a tela de edição da nossa Base de Conhecimento. Daqui, podemos ver como ela se apresenta. Também podemos editá-la livremente, caso queiramos alterar algumas respostas; por exemplo, talvez encurtemos a resposta da pergunta “Quem é o Papai Noel?”.

Depois de editar a Base de Conhecimento como for necessário, clicar em Save and Train salvará e treinará o bot.

Para testar, clique no Test botão no canto superior direito. Isso abrirá a caixa de diálogo de teste; você pode enviar uma pergunta, por exemplo: how do reindeer fly? e o bot vai responder!

Test Question

É até possível verificar como o bot chegou a essa conclusão. Clique no link “Inspecionar” e a janela de inspeção será exibida. Ela mostrará o nível de confiança do bot em sua resposta e algumas alternativas que ele sugeriu.

Inspect Drill down

Quando o bot estiver pronto, clique em “Publicar” na parte superior da página e, em seguida, clique em “Publicar” na caixa de diálogo que aparecerá. Quando o processo for concluído, será exibida uma tela com algumas estruturas de solicitação úteis que podem ser usadas para gerar uma resposta do bot. Ela terá mais ou menos a seguinte aparência:

POST /knowledgebases/YOUR_KNOWLEDGE_BASE_ID/generateAnswer
Host: https://nexmofaqbot.azurewebsites.net/qnamaker
Authorization: EndpointKey YOUR_KNOWLEDGE_BASE_ENDPOINT_KEY
Content-Type: application/json
{"question":"YOUR_QUESTION"}

Salve essa string — ela será usada para criar o WebService que controlará nosso bot por meio da Messages API.

Criando nosso aplicativo

Comece abrindo o Visual Studio e selecionando “Criar um novo projeto”. Na caixa de diálogo que se abre, selecione um Aplicativo Web ASP.NET Core. Nomeie-o com algo como “QnAMakerMessagesDemo”. Selecione ASP.NET Core 3.0, “Aplicativo Web (Model-View-Control)” como tipo e clique em “Criar”.

Instalar pacotes do NuGet

No Visual Studio, vá para Ferramentas -> Gerenciador de Pacotes NuGet -> Gerenciar Pacotes NuGet para a Solução.

Instale os seguintes pacotes do NuGet:

  • Newtonsoft.Json

  • Nexmo.Csharp.Client

  • Castelo Inflável

  • jose-jwt

Criando nosso gerador de tokens

Crie uma classe chamada TokenGenerator e adicione o seguinte código a ela:

public static string GenerateToken(IConfiguration config)
{
    // retrieve appID and privateKey from configuration
    var appId = config["Authentication:appId"];
    var priavteKeyPath = config["Authentication:privateKey"];
    string privateKey = "";
    using (var reader = File.OpenText(priavteKeyPath)) // file containing RSA PKCS1 private key
        privateKey = reader.ReadToEnd();

    //generate claims list
    const int SECONDS_EXPIRY = 3600;
    var t = DateTime.UtcNow - new DateTime(1970, 1, 1);
    var iat = new Claim("iat", ((Int32)t.TotalSeconds).ToString(), ClaimValueTypes.Integer32); // Unix Timestamp for right now
    var application_id = new Claim("application_id", appId); // Current app ID
    var exp = new Claim("exp", ((Int32)(t.TotalSeconds + SECONDS_EXPIRY)).ToString(), ClaimValueTypes.Integer32); // Unix timestamp for when the token expires
    var jti = new Claim("jti", Guid.NewGuid().ToString()); // Unique Token ID
    var claims = new List<Claim>() { iat, application_id, exp, jti };

    //create rsa parameters
    RSAParameters rsaParams;
    using (var tr = new StringReader(privateKey))
    {
        var pemReader = new PemReader(tr);
        var kp = pemReader.ReadObject();
        var privateRsaParams = kp as RsaPrivateCrtKeyParameters;
        rsaParams = DotNetUtilities.ToRSAParameters(privateRsaParams);
    }

    //generate and return JWT
    using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
    {
        rsa.ImportParameters(rsaParams);
        Dictionary<string, object> payload = claims.ToDictionary(k => k.Type, v => (object)v.Value);
        return Jose.JWT.Encode(payload, rsa, Jose.JwsAlgorithm.RS256);
    }
}

Isso irá gerar o JWT necessário para a autenticação no aplicativo Messages, criado como parte dos pré-requisitos.

Adicione o appId e o caminho da chave privada à configuração

O gerador de JWT requer que um appId e o caminho para a chave privada, salvos anteriormente, estejam no arquivo appsettings.json.

Abra este arquivo e adicione o seguinte ao objeto de configuração:

"Authentication": {
    "appId": "NEXMO_APPLICATION_ID",
    "privateKey": "C:\\Path\\to\\Private\\key.key"
  }

Criar estruturas de dados para receber e enviar dados

São necessários alguns POCOs para esta demonstração; eles são um pouco prolixos e não fazem nada além de definir os objetos de mensagem de acordo com o especificação, por isso a estrutura completa foi omitida desta postagem. Basta adicionar as seguintes classes ao projeto:

InboundMessage.cs MessageRequest.cs

Enviar mensagens

Com as estruturas definidas, o próximo passo é enviar mensagens por meio da Messages API da Nexmo. Crie uma classe chamada MessageSender; ela terá um único método estático SendMessage que simplesmente criará uma solicitação de mensagem, gerará um JWT, criará uma solicitação e a enviará para a Messages API — deve ficar mais ou menos assim:

public static void SendMessage(string message, string fromId, string toId, IConfiguration config, string type)
{
    const string MESSAGING_URL = @"https://api.nexmo.com/v0.1/messages";
    try
    {
        var jwt = TokenGenerator.GenerateToken(config);

        //construct message Request
        var requestObject = new MessageRequest()
        {
            to = new MessageRequest.To()
            {
                type = type
            },
            from = new MessageRequest.From()
            {
                type = type
            },
            message = new MessageRequest.Message()
            {
                content = new MessageRequest.Message.Content()
                {
                    type = "text",
                    text = message
                }
            }
        };

        //special messenger request formatting (use to/from id rather than number, set category to RESPONSE)
        if (type == "messenger")
        {
            requestObject.message.messenger = new MessageRequest.Message.Messenger()
            {
                category = "RESPONSE"
            };
            requestObject.to.id = toId;
            requestObject.from.id = fromId;
        }
        else
        {
            requestObject.to.number = toId;
            requestObject.from.number = fromId;
        }

        //Generate Request payload from requestObject
        var requestPayload = JsonConvert.SerializeObject(requestObject, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore });

        //build request
        var httpWebRequest = (HttpWebRequest)WebRequest.Create(MESSAGING_URL);
        httpWebRequest.ContentType = "application/json";
        httpWebRequest.Accept = "application/json";
        httpWebRequest.Method = "POST";
        httpWebRequest.PreAuthenticate = true;
        httpWebRequest.Headers.Add("Authorization", "Bearer " + jwt);
        using (var streamWriter = new StreamWriter(httpWebRequest.GetRequestStream()))
        {
            streamWriter.Write(requestPayload);
        }

        //handle response
        using (var httpResponse = (HttpWebResponse)httpWebRequest.GetResponse())
        {
            using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
            {
                var result = streamReader.ReadToEnd();
                Console.WriteLine(result);
                Console.WriteLine("Message Sent");
            }
        }
    }
    catch (Exception e)
    {
        Debug.WriteLine(e.ToString());
    }
}

Faça uma pergunta ao bot e envie uma resposta

Agora é hora de interagir com o bot de perguntas frequentes do aplicativo. É aqui que entram em cena as chamadas REST de exemplo que o QnAMaker apresentou anteriormente. Lembre-se desta string que vimos antes.

POST /knowledgebases/YOUR_KNOWLEDGE_BASE_ID/generateAnswer
Host: https://AZURE_APP_NAME.azurewebsites.net/qnamaker
Authorization: EndpointKey YOUR_KNOWLEDGE_BASE_ENDPOINT_KEY
Content-Type: application/json
{"question":"YOUR_QUESTION"}

Use essa string para criar algumas constantes úteis/variáveis somente leitura para o Questioner — preencha com os valores apropriados da string acima:

//TODO: fill in with Knowledgebase ID
const string kb_id = "YOUR_KNOWLEDGE_BASE_ID";

//TODO: fill in with Knowledgebase Endpoint key
const string ENDPOINT_KEY = "YOUR_KNOWLEDGE_BASE_ENDPOINT_KEY";

const string QUESTION_FORMAT = @"{{'question': '{0}'}}";

//TODO fill in base url
static readonly string URI = $"https://AZURE_APP_NAME.azurewebsites.net/qnamaker/knowledgebases/{kb_id}/generateAnswer";

Em seguida, crie uma tarefa para fazer a seguinte pergunta:

public static async Task<string> RequestAnswer(string question)
{
    using (var client = new HttpClient())
    using (var request = new HttpRequestMessage())
    {
        request.Method = HttpMethod.Post;
        request.RequestUri = new Uri(URI);
        var formatted_question = string.Format(QUESTION_FORMAT, question);
        request.Content = new StringContent(formatted_question, Encoding.UTF8, "application/json");
        request.Headers.Add("Authorization", "EndpointKey " + ENDPOINT_KEY);
        var response = await client.SendAsync(request);
        var jsonResponse = await response.Content.ReadAsStringAsync();
        JObject obj = JObject.Parse(jsonResponse);
        var answer = ((JArray)obj["answers"])[0]["answer"];
        return answer.ToString();
    }
}

Isso simplesmente formata a pergunta a partir da Messages API e envia a solicitação como uma solicitação POST do tipo “generateAnswer” para o bot QnAMaker.

Por fim, crie um método para enviar a solicitação e retornar uma resposta.

public static async Task AskQuestion(string to, string from, string type, string question, IConfiguration config)
{
    question = HttpUtility.JavaScriptStringEncode(question);
    var response = await RequestAnswer(question);
    MessageSender.SendMessage(response, from, to, config, type);
}

Criar um controlador para receber mensagens recebidas

A peça final do quebra-cabeça é o controlador que irá lidar com o fluxo de mensagens provenientes da Messages API. Crie um controlador MVC vazio chamado MessagesController.

Injeção de dependências e configuração

Adicione um campo IConfiguration chamado _config a isso e configure a injeção de dependência de configuração criando um construtor do controlador que receba um objeto IConfiguration:

private IConfiguration _config;

public MessagesController(IConfiguration config)
{
    _config = config;
}

Solicitação de status

Em seguida, crie uma solicitação de Status Post que simplesmente não retorne nenhum conteúdo:

[HttpPost]
public HttpStatusCode Status()
{
    return HttpStatusCode.NoContent;
}

Mensagens recebidas

Em seguida, adicione uma solicitação POST para mensagens recebidas da Messages API.

Este método extrairá a mensagem recebida do corpo da solicitação e, em seguida, encaminhará as tarefas do Questioner com base no conteúdo do corpo da solicitação.

[HttpPost]
public HttpStatusCode Inbound([FromBody]InboundMessage message)
{
    Debug.WriteLine(JsonConvert.SerializeObject(message));
    if (message.from.type == "messenger")
    {
        _ = Questioner.AskQuestion(message.from.id, message.to.id, message.from.type, message.message.content.text, _config);
    }
    else
    {
        _ = Questioner.AskQuestion(message.from.number, message.to.number, message.from.type, message.message.content.text, _config);
    }
    return HttpStatusCode.NoContent;
}

SMS recebidas

Por fim, adicione uma solicitação HttpGet para gerenciar as mensagens SMS recebidas. Da mesma forma, isso extrairá as informações necessárias da mensagem recebida e fará a pergunta ao usuário.

[HttpGet]
public HttpStatusCode InboundSms([FromQuery] SMS.SMSInbound inboundMessage)
{
    _ = Questioner.AskQuestion(inboundMessage.msisdn, inboundMessage.to, "sms", inboundMessage.text, _config);
    return HttpStatusCode.NoContent;
}

Com isso resolvido, o serviço está pronto para ser implantado.

Testes

O último passo é iniciar o serviço, torná-lo acessível pela internet e configurar o aplicativo Nexmo Messages para enviar webhooks para o serviço.

Configuração do IIS Express

Para simplificar, esta demonstração utiliza o IIS. Para facilitar a configuração do ngrok, desative o SSL no IIS Express acessando as propriedades de depuração do projeto e desmarcando a opção “Ativar SSL”:

Debug settings

Anote o número da porta no campo da URL do aplicativo; ele será usado na próxima etapa.

Configurando o Ngrok

O próximo passo é expor esse endpoint à internet. Para esta demonstração, algo como ngrok pode ser usado para criar um túnel de volta para a porta do IIS Express. Após instalar o ngrok, use um comando como:

ngrok http --host-header="localhost:PORT_NUMBER" http://localhost:PORT_NUMBER

Para configurar o túnel, substitua “PORT_NUMBER” pelo número da porta do IIS Express anotado anteriormente. Isso gerará uma saída semelhante a esta:

ngrok output

Anote aqui a URL base http — na imagem acima, a URL base é http://dc0feb1d.ngrok.io.

Configurando webhooks

A etapa final antes de ativar o serviço é configurar os webhooks para que enviem respostas de retorno ao serviço.

SMS recebidas

Vá acessar o Painel da Nexmo e acesse Settings. Defina a URL de mensagens recebidas para SMS como ngrok_baseurl/messages/InboundSms, conforme o exemplo acima.

http://dc0feb1d.ngroke.io/messages/InboundSms

Outras mensagens recebidas

No Painel da Nexmo , abra Mensagens e Envio -> Seus Applications. Abra o aplicativo associado às contas vinculadas e clique em “Editar”. Em “Recursos”, na seção “Mensagens”, configure a URL de entrada e a URL de status para corresponderem à URL base do ngrok /Messages/Inbound e /Messages/Status, respectivamente, e clique em “Salvar”.

Seguindo o exemplo do túnel do ngrok, ficará mais ou menos assim:

messages urls

NOTA: Os 8 caracteres que precedem “ngrok.io” não são fixos no plano gratuito. Isso significa que, toda vez que o comando ngrok for executado, será necessário alterar o destino dos webhooks. É possível criar um nome de host estático ao fazer o upgrade para um plano pago do ngrok.

Ligue o motor e faça um teste

E pronto! O Nexmo Helper do Papai Noel está pronto para ser implantado. Inicie o IIS Express e comece a enviar mensagens. Ele pode ser acessado por qualquer canal configurado para se conectar ao aplicativo de mensagens.

Aqui está um exemplo do Facebook:

Facebook Example

E uma mensagem de SMS:

SMS Example

Bem, aí está: o Nexmo Helper do Papai Noel já está em funcionamento.

Leituras complementares

  • O código-fonte completo desta demonstração pode ser encontrado em GitHub

  • Para mais informações sobre o QnAMaker, acesse o site deles aqui

  • Para bots totalmente interativos, confira Luis Ai

  • Para obter mais informações sobre a Messages API, consulte a documentação em Nexmo Developer

  • Para conhecer mais APIs da Nexmo, acesse nosso site para desenvolvedores

  • Para conhecer o SDK do Nexmo para .NET, dê uma olhada no nosso repositório no GitHub

Compartilhar:

https://a.storyblok.com/f/270183/384x384/73d57fd8eb/stevelorello.png
Steve LorelloEx-funcionários da Vonage

Steve é um ex-membro da equipe da Vonage. Ele atuou como Developer Advocate .NET na Vonage, engenheiro de software full-stack poliglota, especializado em IA/ML