Como quebrar linhas em mensagens do WhatsApp usando AdvPL/TL++

Hoje iremos continuar nossa série de mensagens para o WhatsApp, mostrando como enviar mensagens com quebra linhas, texto negrito, texto itálico e também com pontos de entrada para enviar uma mensagem de boas vindas quando um novo Cliente, Fornecedor ou Vendedor for cadastrado.

Primeiramente pessoal, tenha os fontes NETiZAP.prw e zZapSend.prw disponibilizados  na primeira semana da série – clique aqui para saber mais.

Agora, o que precisamos entender, é que o próprio WhatsApp trata situações para deixar o texto personalizado, como por exemplo, itálico ser com underscore/underline, negrito ser com asterisco, tachado ser com um til e monoespaçado com crases.

Para o nosso contexto, iremos usar apenas o itálico e o negrito. Além deles, também iremos usar a quebra de linha, que podemos usar simplesmente o \n para quebra. Mas para deixar nosso fonte de maneira mais fácil o entendimento, iremos adaptar para que ele entenda tags HTML, assim ao receber por exemplo <b>, ele já entenda que irá deixar aquele trecho negrito.

Então abaixo segue o zZapSend com as mudanças realizadas:

//Bibliotecas
#Include "TOTVS.ch"

/*/{Protheus.doc} User Function zZapSend
Função que dispara uma mensagem para um smartphone com o aplicativo do WhatsApp
@type  Function
@author Atilio
@since 05/08/2021
@version version
@param cTelDestin, Caractere, Telefone de destino com o país 55 e o ddd - por exemplo 5514999998888
@param cMensagem,  Caractere, Mensagem que será enviada para esse WhatsApp
@return aRet, Array, Posição 1 define se deu certo o envio com .T. ou .F. e a Posição 2 é o JSON obtido como resposta do envio com o protocolo
@obs É necessário baixar a classe NETiZAP desenvolvida pelo grande Júlio Wittwer
    disponível em https://github.com/siga0984/NETiZAP/blob/master/NETiZAP.prw
/*/

User Function zZapSend(cTelDestin, cMensagem)
    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 aRet   := {.F., ""}
    Local oZap
    Default cTelDestin := ""
    Default cMensagem  := ""

    //Retira caracteres em branco dos lados
    cTelDestin := Alltrim(cTelDestin)
    cMensagem  := Alltrim(cMensagem)

    //Transforma o texto em UTF-8
    cMensagem := EncodeUTF8(cMensagem)

    //Retira caracteres especiais do telefone por exemplo +55 (14) 9 9999-8888
    cTelDestin := StrTran(cTelDestin, " ", "")
    cTelDestin := StrTran(cTelDestin, "+", "")
    cTelDestin := StrTran(cTelDestin, "(", "")
    cTelDestin := StrTran(cTelDestin, ")", "")
    cTelDestin := StrTran(cTelDestin, "-", "")

    //Se houver telefone, mensagem, o número for menor que 12 caracteres (551400000000) e não iniciar com 55 não irá enviar a mensagem
    If Empty(cTelDestin) .And. Empty(cMensagem) .And. Len(cTelDestin) < 12 .And. SubStr(cTelDestin, 1, 2) != "55"
        aRet[1] := .F.
        aRet[2] := '[{"error":"Parametro(s) enviado(s) para zZapSend, invalido(s)"}]'
    Else
        //Se na mesma mensagem, tiver o -Enter- normal e tags br, retira os -Enter-
        If CRLF $ cMensagem .And. "<br>" $ cMensagem
            cMensagem := StrTran(cMensagem, CRLF, '')
        EndIf

        //Agora, irá converter o restante para o formato que o WhatsApp entenda
        cMensagem := StrTran(cMensagem, CRLF   , '\n')
        cMensagem := StrTran(cMensagem, '<br>' , '\n')
        cMensagem := StrTran(cMensagem, '<b>'  , '*')
        cMensagem := StrTran(cMensagem, '</b>' , '*')
        cMensagem := StrTran(cMensagem, '<i>'  , '_')
        cMensagem := StrTran(cMensagem, '</i>' , '_')
        
        //Instancia a classe, e passa os parametros da NETiZAP
        oZap := NETiZAP():New(cLinha, cChave, nPorta)

        //Define o destino e a mensagem de envio
        oZap:SetDestiny(cTelDestin)
        oZap:SetText(cMensagem)

        //Atualiza o retorno conforme se a mensagem foi enviada ou houve falha
        If oZap:MessageSend()
            aRet[1] := .T.
            aRet[2] := oZap:GetResponse()
        Else
            aRet[1] := .F.
            aRet[2] := oZap:GetLastError()
        EndIf
    EndIf

    RestArea(aArea)
Return aRet

Agora, iremos fazer 3 pontos de entrada, para que, ao ser criado um vendedor, um cliente ou um fornecedor, seja enviado uma mensagem de boas-vindas. No caso, os pontos de entrada que usaremos serão:

  • Cadastro de Vendedores: MA040TOK
  • Cadastro de Fornecedores: CUSTOMERVENDOR – MODELCOMMITNTTS
  • Cadastro de Clientes: CRMA980 – MODELCOMMITNTTS

Em cada um desses citados acima, iremos adicionar uma chamada para nosso zZapSend, para que seja enviado uma mensagem personalizada para cada caso. Iremos criar 1 fonte só com as 3 User Function, e iremos chamar de zZapNew.prw. Abaixo o código fonte desenvolvido:

//Bibliotecas
#Include "TOTVS.ch"

/*/{Protheus.doc} User Function MA040TOK
Função na validação do cadastro de vendedores
@type  Function
@author Atilio
@since 06/08/2021
@version version
@see https://tdn.totvs.com/pages/releaseview.action?pageId=6784256
/*/

User Function MA040TOK()
    Local aArea       := GetArea()
    Local lRet        := .T.    
    Local cNome       := ""
    Local cDDD        := ""
    Local cTelefone   := ""
    Local cApertoMao  := "\uD83E\uDD1D"
    Local cBraco      := "\uE14C"
    Local cMaosJuntas := "\uE41D"
    Local cMensagem   := ""
    Local aZap        := {}

    //Se for inclusão
    If INCLUI

        //Se tiver o nome reduzido, usa ele, do contrário, usa o nome
        If ! Empty(M->A3_NREDUZ)
            cNome := Alltrim(M->A3_NREDUZ)
        Else
            cNome := Alltrim(M->A3_NOME)
        EndIf
        cNome := Capital(cNome)

        //Pega o DDD, e se o usuário ter digitado 3 caracteres, retira o primeiro, por exemplo, 014 -> 14
        cDDD := Alltrim(M->A3_DDDTEL)
        If Len(cDDD) == 3
            cDDD := SubStr(cDDD, 2)
        EndIf

        //Pega o Telefone e retira os espaços
        cTelefone := Alltrim(M->A3_TEL)

        //Se tiver DDD e Telefone
        If ! Empty(cDDD) .And. ! Empty(cTelefone)

            //Monta a mensagem que será enviada ao vendedor
            cMensagem := 'Olá <b>' + cNome + '</b> ' + cApertoMao + '<br>' + CRLF
            cMensagem += '<br>' + CRLF
            cMensagem += 'Seja bem vindo ao nosso time de Vendedores e Representantes ' + cBraco + '<br>' + CRLF
            cMensagem += '<br>' + CRLF
            cMensagem += 'Esperamos que a parceria seja próspera e duradoura ' + cMaosJuntas + '<br>' + CRLF
            cMensagem += '<br>' + CRLF
            cMensagem += 'Atenciosamente,<br>' + CRLF
            cMensagem += '<br>' + CRLF
            cMensagem += '<i>Família Terminal de Informação</i>' + CRLF

            //Faz o envio da mensagem
            aZap := u_zZapSend("55" + cDDD + cTelefone, cMensagem)

            //Se houve falha, mostra a mensagem de erro
            If ! aZap[1]
                MsgStop(aZap[2], "Falha no envio")
            EndIf
        EndIf
    EndIf

    RestArea(aArea)
Return lRet

/*/{Protheus.doc} User Function CUSTOMERVENDOR
Ponto de Entrada no padrão MVC acionado no cadastro de Fornecedores
@type  Function
@author Atilio
@since 06/08/2021
@version version
@see https://centraldeatendimento.totvs.com/hc/pt-br/articles/360016405952-Cross-Segmento-TOTVS-Backoffice-Linha-Protheus-ADVPL-Ponto-de-entrada-MVC-para-as-rotinas-MATA010-MATA020-e-MATA030
/*/

User Function CUSTOMERVENDOR()
    Local aArea      := GetArea()
    Local aParam     := PARAMIXB
    Local xRet       := .T.
    Local oObj       := Nil
    Local cIdPonto   := ""
    Local cIdModel   := ""
    Local lIsGrid    := .F.
    Local cMensagem  := ""
    Local nOpc
    Local cAceno     := "\uE41E"
    Local cSorriso   := "\uE056"
    Local cPiscada   := "\uE405"

    //Se tiver parâmetros
    If (aParam != Nil)

        //Pega informações dos parâmetros
        oObj     := aParam[1]
        cIdPonto := aParam[2]
        cIdModel := aParam[3]
        lIsGrid  := (Len(aParam) > 3)
        nOpc     := oObj:GetOperation()

        //Chamada após a gravação total do modelo e fora da transação
        If (cIdPonto == "MODELCOMMITNTTS")

            //Se for inclusão
            If nOpc == 3
                //Se tiver o contato, usa ele, do contrário, usa o nome reduzido
                If ! Empty(SA2->A2_CONTATO)
                    cNome := Alltrim(SA2->A2_CONTATO)
                Else
                    cNome := Alltrim(SA2->A2_NREDUZ)
                EndIf
                cNome := Capital(cNome)

                //Pega o DDD, e se o usuário ter digitado 3 caracteres, retira o primeiro, por exemplo, 014 -> 14
                cDDD := Alltrim(SA2->A2_DDD)
                If Len(cDDD) == 3
                    cDDD := SubStr(cDDD, 2)
                EndIf

                //Pega o Telefone e retira os espaços
                cTelefone := Alltrim(SA2->A2_TEL)

                //Se tiver DDD e Telefone
                If ! Empty(cDDD) .And. ! Empty(cTelefone)

                    //Monta a mensagem que será enviada ao fornecedor
                    cMensagem := 'Olá <b>' + cNome + '</b> ' + cAceno + '<br>' + CRLF
                    cMensagem += '<br>' + CRLF
                    cMensagem += 'Estamos felizes em fazer negócios com você e sua empresa ' + cSorriso + '<br>' + CRLF
                    cMensagem += '<br>' + CRLF
                    cMensagem += 'Esperamos atender as expectativas e do que precisar conte com nossa equipe ' + cPiscada + '<br>' + CRLF
                    cMensagem += '<br>' + CRLF
                    cMensagem += 'Atenciosamente,<br>' + CRLF
                    cMensagem += '<br>' + CRLF
                    cMensagem += '<i>Família Terminal de Informação</i>' + CRLF

                    //Faz o envio da mensagem
                    aZap := u_zZapSend("55" + cDDD + cTelefone, cMensagem)

                    //Se houve falha, mostra a mensagem de erro
                    If ! aZap[1]
                        MsgStop(aZap[2], "Falha no envio")
                    EndIf
                EndIf
            EndIf

        EndIf
    EndIf

    RestArea(aArea)
Return xRet

/*/{Protheus.doc} User Function CRMA980
Ponto de Entrada no padrão MVC acionado no cadastro de Clientes
@type  Function
@author Atilio
@since 06/08/2021
@version version
@see https://centraldeatendimento.totvs.com/hc/pt-br/articles/360016405952-Cross-Segmento-TOTVS-Backoffice-Linha-Protheus-ADVPL-Ponto-de-entrada-MVC-para-as-rotinas-MATA010-MATA020-e-MATA030
@obs A função MATA030 será descontinuada em 04/04/2022, então foi usado esse CRMA980 no lugar, veja:
	https://tdn.totvs.com/pages/releaseview.action?pageId=604230458
/*/

User Function CRMA980()
    Local aArea       := GetArea()
    Local aParam      := PARAMIXB
    Local xRet        := .T.
    Local oObj        := Nil
    Local cIdPonto    := ""
    Local cIdModel    := ""
    Local lIsGrid     := .F.
    Local cMensagem   := ""
    Local nOpc
    Local cOculos     := "\uD83D\uDE0E"
    Local cComputador := "\uD83D\uDDA5"
    Local cSacola     := "\uD83D\uDECD"

    //Se tiver parâmetros
    If (aParam != Nil)

        //Pega informações dos parâmetros
        oObj     := aParam[1]
        cIdPonto := aParam[2]
        cIdModel := aParam[3]
        lIsGrid  := (Len(aParam) > 3)
        nOpc     := oObj:GetOperation()

        //Chamada após a gravação total do modelo e fora da transação
        If (cIdPonto == "MODELCOMMITNTTS")

            //Se for inclusão
            If nOpc == 3
                //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)

                //Pega o DDD, e se o usuário ter digitado 3 caracteres, retira o primeiro, por exemplo, 014 -> 14
                cDDD := Alltrim(SA1->A1_DDD)
                If Len(cDDD) == 3
                    cDDD := SubStr(cDDD, 2)
                EndIf

                //Pega o Telefone e retira os espaços
                cTelefone := Alltrim(SA1->A1_TEL)

                //Se tiver DDD e Telefone
                If ! Empty(cDDD) .And. ! Empty(cTelefone)

                    //Monta a mensagem que será enviada ao cliente
                    cMensagem := 'Olá <b>' + cNome + '</b> ' + cOculos + '<br>' + CRLF
                    cMensagem += '<br>' + CRLF
                    cMensagem += 'Lhe desejamos as boas vindas, e saiba que do que precisar, pode nos acionar pelo fone <b>(14) 9 9738-5495</b><br>' + CRLF
                    cMensagem += '<br>' + CRLF
                    cMensagem += 'Caso queira dar uma olhada no nosso portfolio, acesse ' + cComputador + ' https://atiliosistemas.com/projetos/<br>' + CRLF
                    cMensagem += '<br>' + CRLF
                    cMensagem += 'Boas compras ' + cSacola + '<br>' + CRLF
                    cMensagem += '<br>' + CRLF
                    cMensagem += 'Atenciosamente,<br>' + CRLF
                    cMensagem += '<br>' + CRLF
                    cMensagem += '<i>Família Terminal de Informação</i>' + CRLF

                    //Faz o envio da mensagem
                    aZap := u_zZapSend("55" + cDDD + cTelefone, cMensagem)

                    //Se houve falha, mostra a mensagem de erro
                    If ! aZap[1]
                        MsgStop(aZap[2], "Falha no envio")
                    EndIf
                EndIf
            EndIf

        EndIf
    EndIf

    RestArea(aArea)
Return xRet

Abaixo um print com as mensagens enviadas para vendedores (SA3), fornecedores (SA2) e clientes (SA1):

Exemplo das mensagens acionadas nos pontos de entrada

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.

Dan Atilio (Daniel Atilio)
Especialista em Engenharia de Software pela FIB. Entusiasta de soluções Open Source. E blogueiro nas horas vagas.

Deixe uma resposta