Integração do Blip com Google Drive: Como salvar histórico de atendimento humano


Reputação 5

Olá, Pessoal.


Estou aparecendo aqui pela primeira vez e gostaria de compartilhar um tutorial bem bacana com vocês.


O objetivo deste tutorial é permitir que ao final do atendimento humano, seja gerado no Google Drive, um arquivo .txt com a transcrição da conversa, mantendo um backup do lado de vocês, espero que vocês gostem.


Antes de iniciar, queria deixar um agradecimento ao Pedro Mourão, pois o seu tutorial Como Integrar o Blip Com o Google Sheets serviu de inspiração e base para este.


Criação do projeto


Acesse o Google Developers e em sua tela inicial crie um novo projeto.


1-Criacao de projeto


Obs: Se o projeto ainda não estiver selecionado, basta acessar o menu do gif acima e seleciona-lo.


Ativação da API do Google Drive


Nesta etapa, vamos acessar a opção de menu do lado esquerdo, APIs e Serviços → Biblioteca, pesquisar pelo Google Drive e por fim ativa-la.


Obs: Caso a API não seja ativada na primeira tentativa, basta clicar novamente no botão Ativar.


2-Ativacao de API


Tela de permissão OAuth


Com a API ativada, vamos configurar o permissionamento externo, para isso, no menu esquerdo selecione a opção Tela de permissão OAuth, agora você verá duas opções para User Type , basta selecionar Externo e clicar em CRIAR ! E dando seguimento, aqui também coloquei um GIF com o passo a passo.


3-Tela de permissionamento


Agora, para finalizar essa etapa você deverá definir um nome para a API. Insira o e-mail para suporte e dos desenvolvedores (lembrando, que não precisa preencher mais nada). Vá até o final da página e clique em SALVAR E CONTINUAR. Em seguida, nessa próxima página que irá ser exibida, clique no mesmo botão no final da página de SALVAR E CONTINUAR, até chegar ao final, onde deverá clicar no botão Voltar para o Painel e por fim é só Publicar Aplicativo.


Criação de credenciais


Chegou a hora de criar as credenciais de acesso ao Drive que vão ser utilizados pelo blip, para isso, no menu esquerdo clique em Credenciais → Criar Credenciais → ID do cliente OAuth.



Na nova tela que irá carregar, escolha o tipo de aplicativo como Aplicativo da Web, novas opções serão listadas, navegue até o fim da página e clique em Adicionar URI na opção URIs de redirecionamento autorizados e inclua OAuth 2.0 Playground e clique em Criar.


4-Criacao de credenciais


Será exibido uma pop up com o ID do cliente e a chave secreta, copie os valores, SALVE e clique em ok.


img1


Autorizar Credenciais


Para finalizar as configurações relativas ao Google Drive, vamos acessar OAuth 2.0 Playground, nessa parte, você deverá clicar na engrenagem no canto superior direito. Nesse novo pop-up habilite a opção Use your own OAuth credentials e preencha com os dados que coletou na etapa anterior. Dê uma olhadinha no .gif abaixo


5-Autorizaçao P1


Agora é só dar um CRTL+F para procurar pela palavra Drive. Selecione a opção para expandi-la. Em seguida, selecione a opção que possui https://www.googleapis.com/auth/drive, como a dessa figura:



Uma nova janela irá se abrir, selecione o email que irá utilziar para autorizar e clique em Permitir, como no .gif abaixo.


5-Autorizacao P2


Por fim, clique em Exchange authorization code for tokens para obter o refresh token, copie o seu valor salve com as credenciais já salvas nos passos anteriores.


Configuração de fluxo


A primeira etapa para salvar o histórico é obter o token da requisição dentro do fluxo do builder, então nas ações de entrada do bloco imediatamente seguinte ao de atendimento humano, deve-se realizar as seguintes configurações.


Crie uma ação de entrada do tipo Requisição HTTP.



O Método será POST , o URL será https://accounts.google.com/o/oauth2/token com os Cabeçalhos vazios. Por fim, o Corpo será:


{
"client_secret": "seu_id_secreto",
"grant_type": "refresh_token",
"refresh_token": "seu_refresh_token",
"client_id": "seu_id_de_cliente"
}


Salve as variáveis de saída da resposta em statusAuthGoogle e o corpo da resposta em oauth, ficando conforme imagem abaixo.



Obs: Não esqueca de alterar os campos client_secret, refresh_token e client_id pelos obtidos nas etapas anteriores, o grant_type deve permanecer com valor refresh_token.


Logo abaixo vamos utilizar uma API da Blip para retornar a transcrição da conversa, configure uma nova requisição HTTP, o Método será POST , o URL será https://http.msging.net/commands com os Cabeçalhos Content-Type com valor application/json e Authorization com a chave do bot de atendimento humano. Por fim, o Corpo será:


{
"id": "{{random.guid}}",
"to": "[email protected]",
"method": "get",
"uri": "/tickets/{{input.content@id}}/messages?attendanceTranscriptionOnly=true"
}


Salve as variáveis de saída da resposta em statusHistory e o corpo da resposta em responseHistory, ficando conforme imagem abaixo.



Abaixo dessa requisição, crie uma ação do tipo Executar script passando responseHistory como parâmetro de entrada e salvando o retorno em history, o corpo do script deverá ser:


function run(input) {

var messages = '';

try
{

// remove non-printable and other non-valid JSON chars
input = input.replace(/\\n/g, " ").replace(/\\'/g, "\\'").replace(/\"/g, '\"').replace(/\\&/g, "\\&").replace(/\\r/g, " ").replace(/\\t/g, " ").replace(/\\b/g, "\\b").replace(/\\f/g, "\\f");
input = input.replace(/[\u0000-\u0019]+/g,"");

obj = JSON.parse(input);

i = 0;
while(obj["resource"]["items"][i] != null && i < obj["resource"]["items"].length)
{
var o = obj["resource"]["items"][i];
if(o != null)
{
if(o["type"] == 'text/plain'){
if(o["direction"] == 'received')
{
messages = messages + '\n['+dateFormat(o["date"])+'][Usuario] ' + o["content"].replace("<b>","").replace("</b>","");
}
else
{
messages = messages + '\n['+dateFormat(o["date"])+'][Atendente] ' + o["content"].replace("<b>","").replace("</b>","");
}
}
else if(o["type"] == 'application/vnd.lime.select+json'){
t = '\n['+dateFormat(o["date"])+'][Atendente] ' + o["content"]["text"];
for(var j=0; j < o["content"]["options"].length; j++)
{
t = t + " - " + o["content"]["options"][j]["text"].replace("<b>","").replace("</b>","");
}
messages = messages + t;
}
else if(o["type"] == 'application/vnd.lime.media-link+json'){
if(o["direction"] == 'received')
{
messages = messages + '\n['+dateFormat(o["date"])+'][Usuario] <Mídia>';
}
else
{
messages = messages + '\n['+dateFormat(o["date"])+'][Atendente] <Mídia>';
}
}
else if(o["type"] != 'application/vnd.iris.ticket+json'){
if(o["direction"] == 'received')
{
messages = messages + '\n['+dateFormat(o["date"])+'][Usuario] Conteúdo indisponível';
}
else
{
messages = messages + '\n['+dateFormat(o["date"])+'][Atendente] Conteúdo indisponível';
}
}


}
i = i+1;
}

}
catch(e)
{
}
return messages.trim();
}


function dateFormat(input)
{
let d = new Date(input);

let dStr = ("0" + d.getDate()).slice(-2) + "-" + ("0" + (d.getMonth()+1)).slice(-2)+"-"+d.getFullYear();
dStr = dStr + " "+("0" + d.getHours()).slice(-2)+":"+("0" + d.getMinutes()).slice(-2);

return dStr;
}


Ficando conforme imagem abaixo:



Chegou a hora de enviar o arquivo para o Google Drive, finalmente.


Crie outra requisição HTTP, com Método será POST , o URL será https://www.googleapis.com/upload/drive/v3/files?uploadType=media com os Cabeçalhos Content-Type com valor application/json e Authorization com o valor Bearer {{oauth@access_token}} . Por fim, o Corpo será apenas {{history}}, ficando conforme imagem abaixo.



Salve as variáveis de saída da resposta em statusSend e o corpo da resposta em responseSend.


Prontinho, o histórico do atendimento que passou por este fluxo, já cairá na conta do Google Drive vinculada ao email autorizado, no entanto, ela está no formato JSON e sem nome definido, portanto, vamos para a última etapa, eu prometo.


Crie mais uma requisição HTTP, com Método será PATCH , o URL será https://www.googleapis.com/drive/v3/files/{{responseSend@id}} com o Cabeçalho Authorization com o valor Bearer {{oauth@access_token}}. Por fim, o Corpo será:


{
"name":"{{input.content@sequentialId}}.txt",
"mimeType":"text/plain"
}


Salve as variáveis de saída da resposta em statusUpdate e o corpo da resposta em responseUpdate, ficando conforme imagem abaixo.



Se você chegou até aqui e fez tudo certinho, o arquivo vai aparecer no seu Drive dá seguinte maneira:


Caso você precise ou seu cliente precise, é só ir no Drive, baixar o histórico e enviar.


Espero que tenham curtido e conto com o apoio de vocês para evoluirmos essa solução.


10 comentários

Reputação 7

Sensacional @Bruno_Luz :giveaway: :giveaway:


Obrigado por compartilhar com a gente

Reputação 7
Crachá +3

@Bruno_Luz parabéns ficou muito bom e muito claro como salvar o histórico no drive 🚀.

Reputação 7
Crachá +1

Caramba, lindo demais o tutorial!

Essa feature de subir arquivos pro Drive pode ser útil em inúmeros aspectos… acho que já falamos sobre essa possibilidade uma vez, né @Bruno_Gabriel ?


Parabéns demais @Bruno_Luz ! Vai ajudar muito!

:blipinlove:

Reputação 4
Crachá

Solução excelente, muito obrigado @Bruno_Luz 🚀

Reputação 3

segui o tutorial mas me restaram 2 dúvidas.


1 - a requisição vem em branco para capturar a mensagem. será que mudou algo ?


{
"type": "application/vnd.lime.collection+json",
"resource": {
"itemType": "application/vnd.iris.thread-message+json",
"items": []
},
"method": "get",
"status": "success",
"id": "3c0120a7-39b3-4513-bd12-34604110a5ee",
"from": "[email protected]/!iris-hosted-2",
"to": "[email protected]/!iris-hosted-2-43zksgep",
"metadata": {
"#command.uri": "lime://[email protected]/tickets/95390544-08a5-4199-8d2d-01838a5dd102/messages?attendanceTranscriptionOnly=true",
"uber-trace-id": "503343973bd62fd%3Ad32e5d54ea9aa956%3A503343973bd62fd%3A1"
}
}

2 - é possível indicar a pasta do googleDrive ? pois os arquivos caem direto na raiz e ficam bagunçados.


No resumo, está funcionando só que envia para a raiz e o arquivo vem em branco.


pode me dar um help ?


Consegui alterando o body da requisição com outro filtro.


{
"id": "{{random.guid}}",
"to": "[email protected]",
"method": "get",
"uri": "/tickets/{{input.content@id}}/messages?getFromOwnerIfTunnel=true"
}

Mas a parte de salvar em uma pasta específica ainda estou caminhando.

Reputação 5

Ei Davis.


Desculpa a demora, acabei não vendo sua pergunta.


Na mesma requisição que você atualiza o nome do arquivo e o tipo que é https://www.googleapis.com/drive/v3/files/{{responseSend@id}} você pode passar o ID da pasta que deseja, ficando da seguinte maneira: https://www.googleapis.com/drive/v3/files/{{responseSend@id}}?addParents={{idDaPastaDestino}}


Ficando por exemplo: https://www.googleapis.com/drive/v3/files/1TpJ26q4VOKabkp6F-Eq_cXB0MOmFumIp?addParents=16FN4oEsrIOHKCG7kIWbjbTJ0JIUQeV1P


Para capturar o ID da pasta abra a mesma e copie o valor posterior a folders/, exemplo:


image

Reputação 3

Bruno, obrigado.

Funcionou perfeitamente.

só uma outra dica.

para usar os drives compartilhados é necessário tb usar


supportsAllDrives=true

Agora encontrei um comportamento esquisito.

No arquivo TXT só envia as 20 últimas mensagens.

Fiz um ticket com 30 de cada lado e no POSTMAN o JSON vem com todas as trocas 60 no total, porém no drive só salvam as últimas 20


olhei no script e não encontrei nada referente a esse limite. o seu loop está certinho


  while(obj["resource"]["items"][i] != null && i < obj["resource"]["items"].length) 

enquanto não for nulo e for menor que o tamanho do objeto…


estranho

Reputação 5

Ei, Davis.


Por nada e mto obg pela dica 😀


Eu testei aqui o script e foi salvo corretamente 30 registros, inclusive deixando um print abaixo.



Por gentileza, valida a variável de saída do script que trata os dados para ver como está a saída dele, caso não consiga, me passa o ID do bot e nome do bloco que você faz a integração que dou uma olhada.


Repassando o script


function run(input) {

var messages = '';

try
{

// remove non-printable and other non-valid JSON chars
input = input.replace(/\\n/g, " ").replace(/\\'/g, "\\'").replace(/\"/g, '\"').replace(/\\&/g, "\\&").replace(/\\r/g, " ").replace(/\\t/g, " ").replace(/\\b/g, "\\b").replace(/\\f/g, "\\f");
input = input.replace(/[\u0000-\u0019]+/g,"");

obj = JSON.parse(input);

i = 0;
while(obj["resource"]["items"][i] != null && i < obj["resource"]["items"].length)
{
var o = obj["resource"]["items"][i];
if(o != null)
{
if(o["type"] == 'text/plain'){
if(o["direction"] == 'received')
{
messages = messages + '\n['+dateFormat(o["date"])+'][Usuario] ' + o["content"].replace("<b>","").replace("</b>","");
}
else
{
messages = messages + '\n['+dateFormat(o["date"])+'][Atendente] ' + o["content"].replace("<b>","").replace("</b>","");
}
}
else if(o["type"] == 'application/vnd.lime.select+json'){
t = '\n['+dateFormat(o["date"])+'][Atendente] ' + o["content"]["text"];
for(var j=0; j < o["content"]["options"].length; j++)
{
t = t + " - " + o["content"]["options"][j]["text"].replace("<b>","").replace("</b>","");
}
messages = messages + t;
}
else if(o["type"] == 'application/vnd.lime.media-link+json'){
if(o["direction"] == 'received')
{
messages = messages + '\n['+dateFormat(o["date"])+'][Usuario] <Mídia>';
}
else
{
messages = messages + '\n['+dateFormat(o["date"])+'][Atendente] <Mídia>';
}
}
else if(o["type"] != 'application/vnd.iris.ticket+json'){
if(o["direction"] == 'received')
{
messages = messages + '\n['+dateFormat(o["date"])+'][Usuario] Conteúdo indisponível';
}
else
{
messages = messages + '\n['+dateFormat(o["date"])+'][Atendente] Conteúdo indisponível';
}
}


}
i = i+1;
}

}
catch(e)
{
}
return messages.trim();
}


function dateFormat(input)
{
let d = new Date(input);

let dStr = ("0" + d.getDate()).slice(-2) + "-" + ("0" + (d.getMonth()+1)).slice(-2)+"-"+d.getFullYear();
dStr = dStr + " "+("0" + d.getHours()).slice(-2)+":"+("0" + d.getMinutes()).slice(-2);

return dStr;
}
Reputação 5

Hey Pessoal!


Eu refiz o código e estou deixando logo abaixo caso tenham interesse em testa lo.


function run(responseHistory) {
responseHistory = JSON.parse(responseHistory)
return handleHistory(responseHistory.resource.items);
}

function handleHistory(history) {
try {
let auxHistory = '';
history.forEach((item, index) => {
if (index != 0) {
auxHistory += `[${messageTime(item)}][${verifyOrigin(item)}] - ${verifyContent(item)}\n`
}
})
return auxHistory;
} catch (error) {
return `Error on process history - ${error}`
}

};

const verifyOrigin = item => (item.direction === 'received') ? 'Cliente' : 'Atendente';
const verifyContent = item => (item.type === 'text/plain') ? item.content : `Tipo: ${item.content.type} | Link ${item.content.uri}`
const messageTime = item => timeoOfInteraction = new Date(item.date).toLocaleString('pt-BR');

Agora, caso tenha mídia, retorna o tipo da mídia e o link dele.


Obs: Lembrando que os links expiram depois de um certo tempo e caso haja interesse em renova-lo para acessar a midia, é possível utilizar a APIRefresh a media expired link


Segue a saída de exemplo


@Bruno_Luz fiz todo o procedimento aqui, porem meu arquivo salva sem nome, apenas .txt e em branco, sem nenhuma mensagem. Consegue me dar um help?

Comente