Hoje iremos continuar nossa série de mensagens para o WhatsApp, mostrando como criar um menu interativo (chatbot), onde o usuário irá enviar mensagens pelo WhatsApp e pelo Protheus iremos interpretar e mandar respostas.
Primeiramente pessoal, tenha os fontes NETiZAP.prw e zZapSend.prw disponibilizados na primeira semana da série – clique aqui para saber mais.
Basicamente a lógica usada aqui, é pensarmos em ouvirmos sempre o que o usuário está dizendo, interpretar a resposta, e enviar uma nova mensagem. Então a lógica seria mais ou menos a seguinte:
- Criar um job que fique rodando no Protheus a cada x minutos (iremos chamar de zZapMenu)
- Nesse job, iremos consultar todas as requisições que usuários enviaram para o nosso número, através do método RequestsStart
- Então iremos percorrer os números de telefones que nos enviaram requisições
- Se não tivermos começado o atendimento ainda, iremos solicitar o CPF
- Caso o atendimento já tenha sido iniciado, iremos enviar ou a resposta do que o usuário solicitou ou o menu para esperar uma resposta
Ah Daniel, mas existe alguma forma de ficar mais online, sem ser por job que fique rodando a cada x minutos? Sim, é possível você configurar um appserver REST, para que sirva de webhook na comunicação, se for esse o caso, a lógica seria a seguinte:
- Caso não tenha um AppServer REST, veja esse link em como configurar – https://terminaldeinformacao.com/2021/07/19/como-configurar-um-appserver-rest-no-protheus/
- Crie um prw / tlpp com um método GET que irá receber um JSON
- Nesse método, copie o trecho das linhas 89 a 269, fazendo adaptações na lógica
- Adicione a URL desse método criado na API da NETiZAP
- Assim quando qualquer pessoa enviar uma mensagem, a API da NETiZAP automaticamente irá enviar o JSON para o seu método criado em AdvPL
Para que ambos os cenários acima sejam possíveis, temos que criar uma tabela de controle em nosso Protheus, nesse caso eu dei o nome para ela de ZAA. Abaixo os campos dela:
- ZAA_FONE, Caractere, 15, Telefone
- ZAA_PRIMSG, Caractere, 20, Primeira Mensagem
- ZAA_ULTMSG, Caractere, 20, Última Mensagem
- ZAA_CLICOD, Caractere, 6 (mesmo do A1_COD), Código do Cliente
- ZAA_CLILOJ, Caractere, 2 (mesmo do A1_LOJA), Loja do Cliente
- ZAA_FUNCAO, Caractere, 10, Função que disparou a pergunta
- ZAA_PERGUN, Caractere, 10, Código da Pergunta
- ZAA_PERDAT, Data, 8, Data da Pergunta
- ZAA_PERHOR, Caractere, 8, Hora da Pergunta
Obs.: Antes de demonstrar a função desenvolvida, o ideal é usar os métodos QuestionSend() e QuestionSearch(), acabei desenvolvendo manualmente.
Abaixo o código da função desenvolvida:
//Bibliotecas #Include "TOTVS.ch" #Include "TopConn.ch" /*/{Protheus.doc} User Function zZapMenu Função que verifica mensagens recebidas e envia um menu de perguntas @type Function @author Atilio @since 21/08/2021 @version version @obs O ideal é agendar a rotina via Scheduler do Protheus Será necessário criar uma tabela que fará controle das interações pelo Protheus, abaixo os campos e o índice Tabela: ZAA - Interações via NETiZAP - Totalmente Compartilhada Campos: Campo | Tipo | Tamanho | Descrição ZAA_FONE | Caractere | 15 | Telefone ZAA_PRIMSG | Caractere | 20 | Primeira Mensagem ZAA_ULTMSG | Caractere | 20 | Última Mensagem ZAA_CLICOD | Caractere | 6 (mesmo do A1_COD) | Código do Cliente ZAA_CLILOJ | Caractere | 2 (mesmo do A1_LOJA) | Loja do Cliente ZAA_FUNCAO | Caractere | 10 | Função que disparou a pergunta ZAA_PERGUN | Caractere | 10 | Código da Pergunta ZAA_PERDAT | Data | 8 | Data da Pergunta ZAA_PERHOR | Caractere | 8 | Hora da Pergunta Índices: 1. ZAA_FILIAL + ZAA_FONE /*/ User Function zZapMenu() Local aArea Local cUser := "Administrador" Local cPass := MemoRead("\x_temp\teste.txt") //Aqui você pode adaptar para a sua lógica, mas evite deixar senhas chumbadas no código fonte Local cEmpAux := "99" Local cFilAux := "01" Local lContinua := .F. Private lJobPvt := .F. //Se não tiver ambiente aberto, é job If Select("SX2") == 0 //Reseta o ambiente e abre ele novamente RpcClearEnv() RpcSetEnv(cEmpAux, cFilAux, cUser, cPass, "FAT") lJobPvt := .T. lContinua := .T. Else lContinua := MsgYesNo("Deseja executar o processamento da busca das mensagens no WhatsApp?", "Atenção") EndIf aArea := GetArea() //Se for continuar, irá chamar a rotina de processamento If lContinua Processa({|| fProcessar() }, "Processando...") EndIf RestArea(aArea) Return Static Function fProcessar() Local aArea := GetArea() Local cLinha := SuperGetMV("MV_X_ZAPLI", .F., "5521986855299") Local nPorta := SuperGetMV("MV_X_ZAPPO", .F., 13155) Local cChave := SuperGetMV("MV_X_ZAPCH", .F., "fUzanE5ncxlTAWBjMO30") Local cMensagens := "" Local oJSON := JsonObject():New() Local cRet Local nTelAtu := 0 Local cTelefone := "" Local cPriMsg := "" Local cUltMsg := "" Local cFuncao := "" Local cPergun := "" Local dData := sToD("") Local cHora := "" Local cUltPerg := "" Local cUltResp := "" Local cZapMsg := "" Local lErro := .F. Local cTelCli := "" //Instancia a classe, e passa os parametros da NETiZAP oZap := NETiZAP():New(cLinha, cChave, nPorta) oZap:RequestsStart() cMensagens := oZap:cResponse //Se houver mensagens If ! Empty(cMensagens) //Transforma o JSON em um Objeto cRet := oJSON:FromJson(cMensagens) If ValType(cRet) == "U" aTelefones := oJSON:GetJsonObject("root") //Percorre os números de telefones If ValType(aTelefones) == "A" For nTelAtu := 1 To Len(aTelefones) cTelefone := aTelefones[nTelAtu]:GetJsonObject("phone") cPriMsg := aTelefones[nTelAtu]:GetJsonObject("message_datehour_first") cUltMsg := aTelefones[nTelAtu]:GetJsonObject("message_datehour_last") DbSelectArea("ZAA") ZAA->(DbSetOrder(1)) // ZAA_FILIAL + ZAA_FONE //Se encontrou na tabela customizada (não vai fazer nada, apenas deixar o registro posicionado) If ZAA->(DbSeek(FWxFilial("ZAA") + cTelefone)) //Senão, irá incluir um novo registro Else RecLock("ZAA", .T.) ZAA->ZAA_FILIAL := FWxFilial("ZAA") ZAA->ZAA_FONE := cTelefone ZAA->ZAA_PRIMSG := cPriMsg ZAA->ZAA_ULTMSG := "" ZAA->ZAA_CLICOD := "" ZAA->ZAA_CLILOJ := "" ZAA->ZAA_FUNCAO := "" ZAA->ZAA_PERGUN := "" ZAA->ZAA_PERDAT := sToD("") ZAA->ZAA_PERHOR := "" ZAA->(MsUnlock()) EndIf //Se o horário de fim estiver diferente, quer dizer que veio novas mensagens If ZAA->ZAA_ULTMSG != cUltMsg cFuncao := "" cPergun := "" dData := sToD("") cHora := "" //Se não houve pergunta ainda ou se a data da última pergunta for diferente da data de hoje, perguntamos sobre o cpf da pessoa If Empty(ZAA->ZAA_PERGUN) .Or. ZAA->ZAA_PERDAT != Date() //Dispara a mensagem para buscar o cpf ou cnpj cPergun := "CPF" cZapMsg := fMensagem(cPergun) aZap := u_zZapSend(Alltrim(ZAA->ZAA_FONE), cZapMsg) //Se houve falha, pula o telefone If ! aZap[1] nTelAtu++ Loop EndIf //Senão Else aMensagens := aTelefones[nTelAtu]:GetJsonObject("messages") //Armazena a última pergunta em uma variável cUltPerg := Alltrim(ZAA->ZAA_PERGUN) //Armazena a resposta em outra variável cUltResp := aMensagens[Len(aMensagens)]:GetJsonObject("message") //Se a pergunta foi sobre o CPF If cUltPerg == "CPF" //Retira caracteres especiais digitados pelo usuário cUltResp := StrTran(cUltResp, ' ', '') cUltResp := StrTran(cUltResp, '.', '') cUltResp := StrTran(cUltResp, '/', '') cUltResp := StrTran(cUltResp, '-', '') DbSelectArea("SA1") SA1->(DbSetOrder(3)) // A1_FILIAL + A1_CGC //Busca na SA1 o CPF If SA1->(DbSeek(FWxFilial('SA1') + cUltResp)) cTelCli := "55" + SubStr(SA1->A1_DDD, 2) + Alltrim(SA1->A1_TEL) cTelCli := StrTran(cTelCli, "-", "") cTelCli := StrTran(cTelCli, " ", "") //Se o DDD e o telefone estiverem diferentes If cTelCli != Alltrim(ZAA->ZAA_FONE) lErro := .T. cPergun := "CPF" Else RecLock("ZAA", .F.) ZAA->ZAA_CLICOD := SA1->A1_COD ZAA->ZAA_CLILOJ := SA1->A1_LOJA ZAA->(MsUnlock()) //Dispara a mensagem com o menu lErro := .F. cPergun := "MENU" cZapMsg := fMensagem("MENU") aZap := u_zZapSend(Alltrim(ZAA->ZAA_FONE), cZapMsg) //Se houve falha, pula o telefone If ! aZap[1] nTelAtu++ Loop EndIf EndIf Else lErro := .T. cPergun := "CPF" EndIf //Se houve erro, manda mensagem, dizendo que não compreendeu a resposta If lErro cZapMsg := fMensagem("ERRO") aZap := u_zZapSend(Alltrim(ZAA->ZAA_FONE), cZapMsg) //Se houve falha, pula o telefone If ! aZap[1] nTelAtu++ Loop EndIf EndIf //Se a pergunta foi sobre o que ele gostaria de saber Else cUltResp := SubStr(cUltResp, 1, 1) cPergun := ZAA->ZAA_PERGUN //Se a resposta foi 1 = Consultar valor de títulos em aberto If cUltResp == "1" cZapMsg := fMensagem("ABERTO") + "<br>--<br>" + fMensagem("MENU") //Se a resposta foi 2 = Ver dados de compras ElseIf cUltResp == "2" cZapMsg := fMensagem("RESUMO") + "<br>--<br>" + fMensagem("MENU") //Se a resposta foi 3 = Para qual e-Mail esta enviando as DANFEs ElseIf cUltResp == "3" cZapMsg := fMensagem("EMAIL") + "<br>--<br>" + fMensagem("MENU") //Se a resposta foi 4 = Falar com atendente ElseIf cUltResp == "4" cZapMsg := fMensagem("ATENDENTE") + "<br>--<br>" + fMensagem("MENU") //Se a resposta foi 0 = Sair e cancelar (zera a pergunta para identificar que o usuário foi atendido) ElseIf cUltResp == "0" cPergun := "" cZapMsg := fMensagem("TCHAU") //Senão, diz que não compreendeu Else cZapMsg := fMensagem("ERRO") + "<br><br>" + fMensagem("MENU") EndIf //Dispara a mensagem aZap := u_zZapSend(Alltrim(ZAA->ZAA_FONE), cZapMsg) //Se houve falha, pula o telefone If ! aZap[1] nTelAtu++ Loop EndIf EndIf EndIf //Atualiza as variáveis cFuncao := "zZapMenu" dData := Date() cHora := Time() //Grava a informação na tabela RecLock("ZAA", .F.) ZAA->ZAA_ULTMSG := cUltMsg ZAA->ZAA_FUNCAO := cFuncao ZAA->ZAA_PERGUN := cPergun ZAA->ZAA_PERDAT := dData ZAA->ZAA_PERHOR := cHora ZAA->(MsUnlock()) EndIf Next EndIf EndIf EndIf RestArea(aArea) Return Static Function fMensagem(cPergunta) Local cMensagem := "" Local cPiscada := "\uE405" Local cSorriSuor := "\uD83D\uDE05" //Monta a mensagem para perguntar do CPF If cPergunta == "CPF" cMensagem := "Olá.<br>" cMensagem += "Obrigado por entrar em contato conosco " + cPiscada + ".<br>" cMensagem += "Por favor, nos diga, qual é o seu <b>CPF</b> ou seu <b>CNPJ</b>?" //Monta a mensagem para exibir o menu principal ElseIf cPergunta == "MENU" DbSelectArea("SA1") SA1->(DbSetOrder(1)) // A1_FILIAL + A1_COD + A1_LOJA SA1->(DbSeek(FWxFilial('SA1') + ZAA->ZAA_CLICOD + ZAA->ZAA_CLILOJ)) //Se tiver o contato, usa ele, do contrário, usa o nome reduzido If ! Empty(SA1->A1_CONTATO) cNome := Alltrim(SA1->A1_CONTATO) Else cNome := Alltrim(SA1->A1_NREDUZ) EndIf cNome := Capital(cNome) cMensagem := cNome + ", abaixo nossa lista de opções:<br>" cMensagem += "<b>1</b> - Consultar valor de títulos em aberto<br>" cMensagem += "<b>2</b> - Ver resumo de compras<br>" cMensagem += "<b>3</b> - Para qual e-Mail esta enviando as DANFEs<br>" cMensagem += "<b>4</b> - Falar com atendente<br><br>" cMensagem += "<b>0</b> - Sair e cancelar<br><br>" cMensagem += "Qual é sua resposta?" //Se a resposta foi 1 = Consultar valor de títulos em aberto ElseIf cPergunta == "ABERTO" DbSelectArea("SA1") SA1->(DbSetOrder(1)) // A1_FILIAL + A1_COD + A1_LOJA SA1->(DbSeek(FWxFilial('SA1') + ZAA->ZAA_CLICOD + ZAA->ZAA_CLILOJ)) If SA1->A1_SALDUP == 0 cMensagem := "Parabéns, não consta atrasos em nossa base!" Else cMensagem := "O saldo em aberto é de <b>R$ " + Alltrim(Transform(SA1->A1_SALDUP, PesqPict('SA1', 'A1_SALDUP'))) + "</b>." EndIf //Se a resposta foi 2 = Ver dados de compras ElseIf cPergunta == "RESUMO" DbSelectArea("SA1") SA1->(DbSetOrder(1)) // A1_FILIAL + A1_COD + A1_LOJA SA1->(DbSeek(FWxFilial('SA1') + ZAA->ZAA_CLICOD + ZAA->ZAA_CLILOJ)) cMensagem := "Valor da Maior Compra: <b>" + Alltrim(Transform(SA1->A1_MCOMPRA, PesqPict('SA1', 'A1_MCOMPRA'))) + "</b><br>" cMensagem += "Data da Primeira Compra: <b>" + dToC(SA1->A1_PRICOM) + "</b><br>" cMensagem += "Data da Última Compra: <b>" + dToC(SA1->A1_ULTCOM) + "</b><br>" cMensagem += "Quantas vezes já comprou: <b>" + Alltrim(Transform(SA1->A1_NROCOM, PesqPict('SA1', 'A1_NROCOM'))) + "</b><br>" //Se a resposta foi 3 = Para qual e-Mail esta enviando as DANFEs ElseIf cPergunta == "EMAIL" DbSelectArea("SA1") SA1->(DbSetOrder(1)) // A1_FILIAL + A1_COD + A1_LOJA SA1->(DbSeek(FWxFilial('SA1') + ZAA->ZAA_CLICOD + ZAA->ZAA_CLILOJ)) If ! Empty(SA1->A1_EMAIL) cMensagem := "As DANFEs estão indo atualmente para o e-Mail cadastrado: <b>" + SA1->A1_EMAIL + "</b>" Else cMensagem := "Atualmente não existe nenhum e-mail cadastrado no sistema." EndIf //Se a resposta foi 4 = Falar com atendente ElseIf cPergunta == "ATENDENTE" cMensagem := "Abaixo os meios de comunicação com um atendente:<br>" cMensagem += "e-Mail: <b>contato@atiliosistemas.com</b><br>" cMensagem += "Telefone: <b>(14) 9 9738-5495</b><br>" cMensagem += "Site: <b>https://atiliosistemas.com/</b>" //Se for a opção 0, irá se despedir ElseIf cPergunta == "TCHAU" cMensagem := "Obrigado por entrar em contato conosco.<br>" cMensagem += "Até logo." //Se houve alguma falha ElseIf cPergunta == "ERRO" cMensagem := "Desculpe, mas não consegui identificar sua resposta " + cSorriSuor + ", poderia repeti-la por favor?<br>" cMensagem += "<i>Se possível, não use pontuação ou caracteres especiais</i>" EndIf Return cMensagem
Por último, veja o print dos envios das mensagens:
Obs.: Os códigos desenvolvidos nessa série do WhatsApp, estão dentro do nosso GitHub, o link é https://github.com/dan-atilio/AdvPL.
Lembrando também pessoal, se tiverem interesse em adquirir uma licença da API, entrem em contato com o pessoal da NETiZAP clicando aqui, e digam que conhecem o Atilio do Terminal de Informação.
Bom pessoal, por hoje é só.
Abraços e até a próxima.
fala Daniel blz fiz o esquema do REST.
Criei um rdmake separado, mais nao entendi a parte de
* Adicione a URL desse método criado na API da NETiZAP
como ficaria lá no fonte?
Fala Bruno, tudo joia?
Então após você criar seu WS, você vai ter ele em uma URL, por exemplo, http://seuip:suaporta/SeuWSQueFoiCriadoEmAdvPL
Ai no painel administrativo da API da NETiZAP (após você contratar o serviço deles), existe uma configuração, que você fala para ele disparar em um WebHook, e nessa configuração, você coloca essa sua URL que foi criada.
Boa tarde, como vai?
Consegue postar em exemplo deste WS que vc comenta ?
Boa tarde Rogério, tudo joia graças a Deus e você?
Opa, siga o passo a passo nesse link – https://terminaldeinformacao.com/2022/09/15/como-criar-uma-aplicacao-rest-em-advpl-em-poucos-passos/
Só que faça assim
1. Escolha a opção get simples
2. Coloque uma tabela qualquer
3. Depois que o Autumn gerar o código, você pega o conteúdo dentro do método
3.1 Ai adapte com o trecho das linhas 89 a 269 desse link
Um grande abraço.