https://a.storyblok.com/f/270183/74256/6be5f9a3a9/blog_code-of-conduct-incident-line_1200x600.png

Criar uma linha de atendimento para incidentes relacionados ao Código de Conduta com Node.js

Publicado em May 5, 2021

Tempo de leitura: 5 minutos

Ter um Código de Conduta como organizador comunitário é apenas uma parte da história — contar com mecanismos bem elaborados para denunciar e responder a comportamentos inadequados também é fundamental. Nos eventos que organizei no passado, um número de telefone era uma das formas disponibilizadas aos participantes — eles podiam ligar ou enviar uma mensagem de texto para esse número, que era encaminhado a vários organizadores responsáveis por estarem disponíveis para lidar com quaisquer problemas.

Hoje vou mostrar a vocês como montar o seu próprio com a Vonage Voice e Messages da Vonage, incluindo um painel simples para baixar gravações de chamadas e registrar mensagens recebidas.

Você pode encontrar o código final do projeto em https://github.com/nexmo-community/node-code-of-conduct-conference-call

Pré-requisitos

  • Node.js instalado no seu computador

  • node-cli, que você pode instalar executando npm install nexmo-cli@beta -g

Crie um novo diretório e abra-o no terminal. Execute npm init -y para criar um package.json arquivo e instale as dependências com npm install express body-parser nunjucks uuid nedb-promises nexmo@beta.

Configurar dependências

Crie um index.js arquivo e configure as dependências:

index.js

const uuid = require('uuid')
const app = require('express')()
const bodyParser = require('body-parser')
const nedb = require('nedb-promises')
const Nexmo = require('nexmo')
const nunjucks = require('nunjucks')

app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: false }))

// Future code goes here

app.listen(3000)

Depois de fazer isso, execute npx ngrok http 3000 em um novo terminal e anote a URL temporária do ngrok. Ela é usada para tornar localhost:3000 disponibilizar na web pública.

Compre um número virtual e configure o cliente Nexmo

Abra outro terminal no diretório do seu projeto e crie um novo aplicativo usando a interface de linha de comando (CLI):

nexmo app:create
  -> Select Capabilities: voice, messages
  -> Use the default HTTP methods? Y
  -> Voice Answer URL: https://NGROK_URL/answer
  -> Voice Event URL: https://NGROK_URL/event
  -> Messages Inbound URL: https://NGROK_URL/inbound
  -> Messages Status URL: https://NGROK_URL/event
  -> Private Key path: private.key

Anote o ID do aplicativo exibido no seu terminal e, em seguida, procure um número (você pode substituir GB pelo código do seu país):

nexmo number:search GB --sms --voice

Copie um dos números para a área de transferência, compre-o e vincule-o ao seu aplicativo:

nexmo number:buy NUMBER
nexmo link:app NUMBER APP_ID
nexmo numbers:update NUMBER --mo_http_url https://NGROK_URL/sms

Em index.js, inicialize o cliente Nexmo:

const nexmo = new Nexmo({ 
  apiKey: 'API_KEY', 
  apiSecret: 'API_SECRET',
  applicationId: 'APPLICATION_ID',
  privateKey: './private.key'
})

Atender uma chamada recebida com comando de voz

Crie o GET /answer ponto de extremidade e retorne um Objeto de Controle de Chamadas da Nexmo (NCCO) com uma única talk ação:

app.get('/answer', async (req, res) => {
  res.json([
    { action: 'talk', voiceName: 'Amy', text: 'This is the Code of Conduct Incident Response Line' }
  ])
})

app.post('/event', (req, res) => {
  res.status(200).end()
})

O POST /event endpoint receberá, posteriormente, dados de chamadas, e, por enquanto, deve apenas responder com um HTTP 200 OK status.

Verificação: Inicie seu servidor executando node index.js e, em seguida, ligue para o número que você comprou usando a CLI — a mensagem deve ser lida em voz alta e, em seguida, a ligação deve ser encerrada. Se houver problemas, você sempre pode verificar as configurações do número e do aplicativo no painel de controle.

Atender uma chamada recebida discando para os organizadores

Em vez de apenas ler a mensagem em voz alta, adicione a pessoa que ligou a uma conversa totalmente nova. Podemos controlar as conversas por meio de código, inclusive adicionando vários participantes à chamada — basta saber o nome da conversa para fazer isso. Substitua o conteúdo do /answer ponto de extremidade por:

const conferenceId = uuid.v4()

res.json([
  { action: 'talk', voiceName: 'Amy', text: 'This is the Code of Conduct Incident Response Line' },
  { action: 'conversation', name: conferenceId, record: true }
])

Esse código gera um novo ID exclusivo e, em seguida, adiciona a pessoa que está ligando a uma conversa que usa um nome como identificador (nesse contexto, conversas são chamadas com um ou mais participantes). No entanto, teleconferências com apenas uma pessoa são tristes. Antes de res.json(), ligue para cada organizador e adicione-os à teleconferência:

for(let organizerNumber of ['NUMBER ONE', 'NUMBER TWO']) {
  nexmo.calls.create({
    to: [{ type: 'phone', number: organizerNumber }],
    from: { type: 'phone', number: 'NEXMO NUMBER' },
    ncco: [
      { action: 'conversation', name: conferenceId }
    ]
  })
}

Cada número deve estar no formato E.164, e você deve substituir NEXMO NUMBER pelo número vinculado ao seu aplicativo. Durante o teste, certifique-se de que os números na matriz não sejam os mesmos que você usará para ligar.

Verificação: Reinicie o servidor e ligue para o seu número Nexmo. O aplicativo deve tocar em todos os números fornecidos na matriz do loop for().

Gravar a ligação

Ao adicionar o chamador à teleconferência, record: true foi passado como uma opção e, como resultado, toda a chamada foi gravada. Assim que a chamada é concluída, o POST /event terminal recebe uma mensagem contendo o ID da conversa e uma URL da gravação.

Antes que os endpoints existentes criem um novo banco de dados nedb:

const recordingsDb = nedb.create({ filename: 'data/recordings.db', autoload: true })

Depois de reiniciar o servidor, um arquivo será criado dentro do diretório data diretório. Atualize o endpoint do evento para que fique assim:

app.post('/event', async (req, res) => { 
  if(req.body.recording_url) {
    await recordingsDb.insert(req.body)
  }
  res.status(200).end()
})

Verificação: Reinicie seu servidor e ligue para o seu número da Nexmo. Assim que todos os participantes desligarem, você deverá ver uma nova entrada no arquivo arquivo data/recordings.db .

Criar um painel de gravações

Agora que os dados de registro estão salvos em um banco de dados, é hora de criar um painel. Configure o nunjucks antes do primeiro endpoint:

nunjucks.configure('views', { express: app })

Isso configura o nunjucks para renderizar qualquer arquivo no views diretório e cria um link para o aplicativo Express armazenado na app variável. Crie um views diretório e um index.html arquivo dentro dele:

<h1>Recordings</h1>

{% for recording in recordings %}
  <p>
    <a href="/details/{{recording.conversation_uuid}}">{{recording.start_time}}</a>
  </p>
{% endfor %}

Crie também um details.html arquivo no views diretório:

<ul>
  <li>{{caller}}</li>
  <li>{{recording.timestamp}}</li>
  <li><a href="/details/{{recording.conversation_uuid}}/download">Download</a></li>
</ul>

São necessários três endpoints para index.js para que essas visualizações funcionem. O primeiro carrega todas as gravações do banco de dados e exibe a página de índice:

app.get('/', async (req, res) => {
  const recordings = await recordingsDb.find().sort({ timestamp: -1 })
  res.render('index.html', { recordings })
})

Agora, a página tem a seguinte aparência, com as gravações mais recentes em primeiro lugar:

Web page showing one recording timestamp with a blue underlineWeb page showing one recording timestamp with a blue underline

O próximo endpoint carrega a página de detalhes após obter informações da Conversation API, incluindo o número de telefone de quem ligou:

app.get('/details/:conversation', (req, res) => {
  nexmo.conversations.get(req.params.conversation, async (error, result) => {
    const caller = result.members.find(member => member.channel.from != process.env.NEXMO_NUMBER)
    const number = caller.channel.from.number
    const recording = await recordingsDb.findOne({ conversation_uuid: req.params.conversation })
    res.render('detail.html', { caller: number, recording })
  })
})

Por fim, um endpoint que obtém o arquivo de áudio bruto da API e o envia como um MP3 para download:

app.get('/details/:conversation/download', async (req, res) => {
  const recording = await recordingsDb.findOne({ conversation_uuid: req.params.conversation })
  nexmo.files.get(recording.recording_url, (error, result) => {
    res.writeHead(200, {
      'Content-Disposition': 'attachment; filename="recording.mp3"',
      'Content-Type': 'audio/mpeg',
    })
    res.end(Buffer.from(result, 'base64'))
  })
})

A page showing a phone number, timestamp, and download linkA page showing a phone number, timestamp, and download link

Verificação: Reinicie o servidor e ligue para o seu número da Nexmo. Assim que a chamada for concluída, você deverá ver a nova entrada no painel. Acesse a página de detalhes e faça o download.

Aceitar e salvar SMS

Por se tratar de um número de telefone, algumas pessoas que utilizam esse serviço também podem enviar mensagens SMS para ele. Seguindo um padrão semelhante, essas mensagens serão armazenadas e exibidas no painel. Abaixo da criação do banco de dados existente, adicione um novo para as mensagens:

const messagesDb = nedb.create({ filename: 'data/messages.db', autoload: true })

Salve as novas mensagens à medida que forem recebidas, criando um endpoint para o qual já havíamos definido o direcionamento ao configurar nosso número virtual:

app.post('/sms', async (req, res) => {
  await messagesDb.insert(req.body)
  res.status(200).end()
})

Atualize o endpoint do painel para que ele também recupere e exiba mensagens:

app.get('/', async (req, res) => {
  const recordings = await recordingsDb.find().sort({ timestamp: -1 })
  const messages = await messagesDb.find().sort({ 'message-timestamp': -1 })
  res.render('index.html', { recordings, messages })
})

Adicione esta seção ao final de index.html:

{% for message in messages %}
  <p>{{message.msisdn}} ({{message['message-timestamp']}}): {{message.text}}</p>
{% endfor %}

Web page showing both recordings and two example messagesWeb page showing both recordings and two example messages

Ponto de verificação: Reinicie seu servidor e envie um SMS para o seu número da Nexmo. Você deverá vê-lo aparecer no seu painel de controle assim que atualizar a página.

Encaminhar SMS e enviar uma resposta

Por fim, atualize o endpoint de SMS para que ele encaminhe a mensagem aos organizadores e responda ao remetente:

app.post('/sms', async (req, res) => {
  await messagesDb.insert(req.body)

  for(let organizerNumber of ['NUMBER ONE', 'NUMBER TWO']) {
    nexmo.channel.send(
      { type: 'sms', number: organizerNumber },
      { type: 'sms', number: 'NEXMO NUMBER' },
      { content: { type: 'text', text: `From ${req.body.msisdn}\n\n${req.body.text}` } }
    )
  }

  nexmo.channel.send(
    { type: 'sms', number: req.body.msisdn },
    { type: 'sms', number: 'NEXMO NUMBER' },
    { content: { type: 'text', text: 'Thank you for sending us a message. Organizers have been made aware and may be in touch for more information.' } }
  )

  res.status(200).end()
})

Verificação: Reinicie o servidor e envie um SMS para o seu número da Nexmo. Você deve receber uma resposta, e todos os organizadores listados também devem receber a mensagem.

Próximos passos

Parabéns! Agora você conta com uma Linha de Resposta a Incidentes do Código de Conduta que funciona tanto para chamadas telefônicas quanto para mensagens SMS. Se tiver mais tempo, talvez queira explorar:

Você pode encontrar o código final do projeto em https://github.com/nexmo-community/node-code-of-conduct-conference-call

Como sempre, se precisar de ajuda, sinta-se à vontade para entrar em contato na Slack da Comunidade de Desenvolvedores da Vonage. Esperamos vê-lo por lá.

Compartilhar:

https://a.storyblok.com/f/270183/400x400/c822f15b89/kevinlewis.png
Kevin LewisEx-funcionários da Vonage

Ex-representante de desenvolvedores da Vonage, onde sua função era apoiar a comunidade tecnológica local em Londres. Ele é um experiente organizador de eventos, jogador de jogos de tabuleiro e pai de um cachorrinho fofo chamado Moo. Ele também é o principal organizador do You Got This — uma rede de eventos sobre as habilidades essenciais necessárias para uma vida profissional feliz e saudável.