Como adicionar bots de chamadas telefônicas ao Slack com Python

May 24, 2016
Escrito por
Matt Makai
Twilion

Faça chamadas telefônicas no Slack usando um bot em Python

O Slack é uma ferramenta incrível para você conversar por meio de texto e emojis com seus colegas. Mas, às vezes, é muito mais fácil e rápido responder pelo telefone. Como o Slack está apenas começando a adicionar chamada de voz entre usuários, não é possível fazer contato pelo seu bom e velho número de telefone. Vamos adicionar chamadas telefônicas ao Slack criando um bot com a linguagem de programação Python, a plataforma Twilio e a API de mensagens em tempo real do Slack.

Ferramentas de que precisamos

Nosso bot, que chamaremos de callbot, requer algumas bibliotecas e APIs. Para criar nosso bot, precisaremos de:

Aqui vai um guia prático passo a passo para configuração do Python, pip e virtualenv.

Os requisitos da plataforma Twilio são:

As dependências do Slack são:

Verifique se o Python versão 2 ou 3 está instalado. Configuraremos tudo ao longo deste tutorial.

Você pode acompanhar escrevendo o código neste post ou pular para o projeto concluído clonando o repositório GitHub associado.

Configurar o ambiente

Agora que sabemos de quais ferramentas precisamos, vá até o terminal (ou prompt de comando no Windows) e mude para um diretório onde deseja armazenar este projeto. Nesse diretório, crie um virtualenv para isolar as dependências do nosso aplicativo de outros projetos Python em que você esteja trabalhando.

virtualenv callbot

Ative o virtualenv:

source callbot/bin/activate

Dependendo de como o virtualenv e o shell estão configurados, seu prompt deve ser semelhante a esta captura de tela.

Tela do terminal com a configuração de ambiente

Usaremos a Biblioteca auxiliar da API do slackclient oficial para acessar a API para enviar e receber mensagens em um canal do Slack. Instale as bibliotecas auxiliares do slackclient e da Twilio junto com os números de telefone em seu virtualenv com o comando pip:

pip install slackclient twilio phonenumbers

Em seguida, precisamos obter um token de acesso do Slack Bot e nossas credenciais da API da Twilio.

API de mensagens em tempo real do Slack

O Slack fornece acesso programático ao aplicativo de chat por meio de uma API da Web. Abra a página inicial da API da Web do Slack e inscreva-se para criar uma equipe do Slack ou fazer login em sua conta já existente. Você poderá criar uma equipe gratuitamente se não tiver privilégios de administrador em uma equipe já existente.

Botão de login da tela de configuração do slack

Depois de fazer login, vá para a página Bot Users (Usuários de bots).

Tela do slack para criar um novo bot

Dê ao seu bot o nome "callbot" e clique no botão "Add bot integration" (Adicionar integração de bots).

Tela de criação de novo bot.

A página será recarregada e você verá um novo token de acesso gerado. Você também pode alterar o logotipo para um design personalizado, como eu fiz com este bot, dando-lhe o logotipo da Twilio.

Tela do slack verificar o token do bot.

Role para baixo e clique no botão "Save Integration" (Salvar integração). Agora, seu bot está pronto para acessar a API do Slack.

Uma prática comum dos desenvolvedores do Python é exportar tokens secretos, como o token do Slack, como variáveis de ambiente. Exporte o token com o nome SLACK_BOT_TOKEN:

export SLACK_BOT_TOKEN='your slack token pasted here'

Incrível! Estamos autorizados a usar a API do Slack como um bot. Agora, precisamos apenas de uma conta e de credenciais da Twilio para começar a lidar com chamadas telefônicas.

Números de telefone da Twilio

Precisamos acessar a API da plataforma Twilio para fazer chamadas telefônicas usando nosso aplicativo. Cadastre uma conta gratuita da Twilio ou faça login em sua conta já existente, caso já tenha uma. Nosso Slack bot só realizará chamadas telefônicas de saída. Portanto, nada precisa ser alterado na tela de configuração de números.

Tela do console da Twilio com a informação do número de telefone

Com nosso número de telefone em mãos, vá para a tela Console Dashboard (Dashboard do console) e procure o Account SID (SID da conta) da Twilio e o Auth Token (Token de autenticação):

Tela do console da Twilio com as configurações de SDI da conta e Token de autenticação.

Como fizemos anteriormente com o SLACK_BOT_TOKEN, usaremos as variáveis de ambiente recém-exportadas em nosso script do Python.

Na linha de comando, exporte as credenciais da Twilio como variáveis de ambiente:

(callbot)$ export TWILIO_ACCOUNT_SID='your twilio account sid'
(callbot)$ export TWILIO_AUTH_TOKEN='your twilio auth token'
(callbot)$ export TWILIO_NUMBER='your twilio phone number, for example  12025551234'

Há mais uma informação de que precisamos: o ID do nosso bot no Slack. Em seguida, escreveremos um script curto para capturar isso na API do Slack.

Obter o ID do nosso bot

É hora de escrever um código Python! Vamos fazer um aquecimento escrevendo um rápido script Python auxiliar para obter o ID do callbot, porque ele varia de acordo com a equipe do Slack. Precisamos do ID do callbot, porque ele permitirá que o código do nosso aplicativo determine se as mensagens analisadas na API de mensagens em tempo real do Slack são direcionadas para nosso bot.

Esse script também ajudará a testar se nossa SLACK_BOT_TOKEN está definida corretamente. Crie um arquivo chamado get_bot_id.py com o código a seguir.

import os
from slackclient import SlackClient


BOT_NAME = 'callbot'

slack_client = SlackClient(os.environ.get('SLACK_BOT_TOKEN'))


if __name__ == "__main__":
    api_call = slack_client.api_call("users.list")
    if api_call.get('ok'):
        # retrieve all users so we can find our bot
        users = api_call.get('members')
        for user in users:
            if 'name' in user and user.get('name') == BOT_NAME:
                print("Bot ID for '" + user['name'] + "' is " + user.get('id'))
    else:
        print("could not find bot user with the name " + BOT_NAME)

O código acima importa o SlackClient e o instancia com nosso SLACK_BOT_TOKEN. Quando o script é executado pelo comando python, vamos à API para obter uma lista de usuários do Slack e o ID daquele que corresponde ao nome callbot.

Só precisamos executar esse script uma vez para obter o ID do nosso bot.

python get_bot_id.py

Quando executarmos o script, obteremos uma única linha de saída com o ID de nosso bot.

tela do terminal com o ID do bot.

Copie o ID e exporte-o como uma variável de ambiente chamada BOT_ID.

(callbot)$ export BOT_ID='bot id returned by script'

Novamente, o script só precisa ser executado uma vez para garantir que tenhamos o ID de bot apropriado para nossa equipe do Slack. Agora, estamos prontos para codificar nosso aplicativo Python que executará nosso callbot.

Codificar nosso CallBot

Temos todas as variáveis de ambiente apropriadas definidas para nosso código Python para usarmos adequadamente as APIs da Twilio e do Slack. Crie um arquivo chamado callbot.py e adicione estas importações.

import os
import phonenumbers
import time
import uuid
from slackclient import SlackClient
from twilio.rest import TwilioRestClient

As importações os e SlackClient devem parecer familiares porque as usamos anteriormente no script get_bot_id.py.

Com as dependências importadas, podemos usá-las para obter os valores das variáveis de ambiente e instanciar o Slack e o Twilio Client.

# environment variables
BOT_ID = os.environ.get("BOT_ID")
TWILIO_NUMBER = os.environ.get("TWILIO_NUMBER")

# constants
AT_BOT = "<@" + BOT_ID + ">:"
CALL_COMMAND = "call"
TWIMLET = "https://twimlets.com/echo?Twiml=%3CResponse%3E%0A%20%20%3CDial%3E%3CConference%3E{{name}}%3C%2FConference%3E%3C%2FDial%3E%0A%3C%2FResponse%3E&"

# instantiate Slack & Twilio clients
slack_client = SlackClient(os.environ.get('SLACK_BOT_TOKEN'))
twilio_client = TwilioRestClient()

Nosso código instancia o SlackClient com o SLACK_BOT_TOKEN em uma variável de ambiente. O TwilioRestClient extrai automaticamente o TWILIO_ACCOUNT_SID e o TWILIO_AUTH_TOKEN de variáveis de ambiente com esses nomes exatos durante sua declaração. Continue o script do Python com as seguintes linhas de código que lidarão com a inicialização do bot.

if __name__ == "__main__":
    READ_WEBSOCKET_DELAY = 1 # 1 second delay between reading from firehose
    if slack_client.rtm_connect():
        print("CallBot connected and running!")
        while True:
            command, channel = parse_slack_output(slack_client.rtm_read())
            if command and channel:
                handle_command(command, channel)
            time.sleep(READ_WEBSOCKET_DELAY)
    else:
        print("Connection failed. Invalid Slack token or bot ID?")

O SlackClient conecta-se ao WebSocket da API de mensagens em tempo real do Slack e, em seguida, faz loops contínuos e analisa mensagens do firehose Messaging. Se qualquer uma dessas mensagens for direcionada ao nosso bot, uma função chamada handle_command determinará o que fazer com o comando.

Acima do código Python que acabamos de escrever, adicione duas novas funções para analisar a saída do Slack e manipular comandos.

def handle_command(command, channel):
    """
        Receives commands directed at the bot and determines if they
        are valid commands. If so, then acts on the commands. If not,
        returns back what it needs for clarification.
    """
    response = "Not sure what you mean. Use the *" + \ 
               CALL_COMMAND + "* command with numbers, delimited by spaces."
    if command.startswith(CALL_COMMAND):
        response = "calling stub..."
    slack_client.api_call("chat.postMessage", channel=channel,
                          text=response, as_user=True)


def parse_slack_output(slack_rtm_output):
    """
        The Slack Real Time Messaging API is a firehose of data, so
        this parsing function returns None unless a message is
        directed at the Bot, based on its ID.
    """
    output_list = slack_rtm_output
    if output_list and len(output_list) > 0:
        for output in output_list:
            if output and 'text' in output and AT_BOT in output['text']:
                # return text after the @ mention, whitespace removed
                return output['text'].split(AT_BOT)[1].strip(), 
                       output['channel']
    return None, None

A função parse_slack_output recebe mensagens do Slack e determina se elas são direcionadas ao Slack CallBot. Se uma mensagem começar com uma mensagem direta para o ID do nosso bot, saberemos que nosso bot precisa lidar com um comando. Atualmente, handle_command é uma função stub que transmite uma mensagem de ajuda genérica ou terá um stub dentro de uma condição se o comando iniciar com "call" (chamada).

Com a maior parte do nosso código em vigor, vamos testar o CallBot usando o comando Python callbot.py.

Tela do terminal com o log do bot conectado.

Vá para o canal do Slack com CallBot e digite "@callbot: call 14045551234 14155550909" (ou substitua esses dois números por seus próprios números de telefone de teste). O CallBot responderá, mas não discará números.

O CallBot pode responder aos comandos, mas ainda não faz chamadas. Podemos corrigir isso adicionando duas novas funções chamadas call_command e validate_phone_numbershandle_command pode chamar call_command em vez de servir apenas como stub. Altere seu código para corresponder a todo o aplicativo callbot.py abaixo. As alterações de código de nossa versão anterior são destacadas.

import os
import phonenumbers
import time
import uuid
from slackclient import SlackClient
from twilio.rest import TwilioRestClient


# environment variables
BOT_ID = os.environ.get("BOT_ID")
TWILIO_NUMBER = os.environ.get("TWILIO_NUMBER")

# constants
AT_BOT = "<@" + BOT_ID + ">:"
CALL_COMMAND = "call"
TWIMLET = "https://twimlets.com/echo?Twiml=%3CResponse%3E%0A%20%20%3CDial%3E%3CConference%3E{{name}}%3C%2FConference%3E%3C%2FDial%3E%0A%3C%2FResponse%3E&"

# instantiate Slack & Twilio clients
slack_client = SlackClient(os.environ.get('SLACK_BOT_TOKEN'))
twilio_client = TwilioRestClient()


def handle_command(command, channel):
    """
        Receives commands directed at the bot and determines if they
        are valid commands. If so, then acts on the commands. If not,
        returns back what it needs for clarification.
    """
    response = "Not sure what you mean. Use the *" + \
               CALL_COMMAND + "* command with numbers, delimited by spaces."
    if command.startswith(CALL_COMMAND):
        response = call_command(command[len(CALL_COMMAND):].strip())
    slack_client.api_call("chat.postMessage", channel=channel,
                          text=response, as_user=True)


def call_command(phone_numbers_list_as_string):
    """
        Validates a string of phone numbers, delimited by spaces, then
        dials everyone into a single call if they are all valid.
    """
    # generate random ID for this conference call
    conference_name = str(uuid.uuid4())
    # split phone numbers by spaces
    phone_numbers = phone_numbers_list_as_string.split(" ")
    # make sure at least 2 phone numbers are specified
    if len(phone_numbers) > 1:
        # check that phone numbers are in a valid format
        are_numbers_valid, response = validate_phone_numbers(phone_numbers)
        if are_numbers_valid:
            # all phone numbers are valid, so dial them together
            for phone_number in phone_numbers:
                twilio_client.calls.create(to=phone_number,
                                           from_=TWILIO_NUMBER,
                                           url=TWIMLET.replace('{{name}}',
                                           conference_name))
            response = "calling: " + phone_numbers_list_as_string
    else:
        response = "the *call* command requires at least 2 phone numbers"
    return response


def validate_phone_numbers(phone_numbers):
    """
        Uses the python-phonenumbers library to make sure each phone number
        is in a valid format.
    """
    invalid_response = " is not a valid phone number format. Please " + \
                       "correct the number and retry. No calls have yet " + \   
                       "been dialed."
    for phone_number in phone_numbers:
        try:
            validate_phone_number = phonenumbers.parse(phone_number)
            if not phonenumbers.is_valid_number(validate_phone_number):
                return False, phone_number   invalid_response
        except:
            return False, phone_number   invalid_response
    return True, None


def parse_slack_output(slack_rtm_output):
    """
        The Slack Real Time Messaging API is a firehose of data, so
        this parsing function returns None unless a message is
        directed at the Bot, based on its ID.
    """
    output_list = slack_rtm_output
    if output_list and len(output_list) > 0:
        for output in output_list:
            if output and 'text' in output and AT_BOT in output['text']:
                # return text after the @ mention, whitespace removed
                return output['text'].split(AT_BOT)[1].strip(), 
                       output['channel']
    return None, None


if __name__ == "__main__":
    READ_WEBSOCKET_DELAY = 1 # 1 second delay between reading from firehose
    if slack_client.rtm_connect():
        print("CallBot connected and running!")
        while True:
            command, channel = parse_slack_output(slack_client.rtm_read())
            if command and channel:
                handle_command(command, channel)
            time.sleep(READ_WEBSOCKET_DELAY)
    else:
        print("Connection failed. Invalid Slack token or bot ID?")

As duas novas funções acima, call_command e validate_phone_numbers, fazem a maior parte do trabalho para CallBot. validate_phone_numbers usa a biblioteca do Python de números de telefone para garantir que cada número seja analisável e esteja em conformidade com, pelo menos, um tipo de número de telefone de todo o mundo. call_command garante que, pelo menos, dois números de telefone sejam especificados e chama validate_phone_numbers para fazer algumas verificações adicionais. Se todos os números de telefone forem válidos, call_command invocará a API do Twilio Voice para fazer cada chamada telefônica de saída.

Agora, é hora de executar o bot já que todo o nosso código está em vigor. Na linha de comando, execute python callbot.py.

Tela do terminal com o log do bot conectado.

No Slack, comece a dar comandos CallBot. Você pode começar a testá-lo com números de telefone inválidos. Se especificarmos um formato de número de telefone inválido para um ou mais números, receberemos uma mensagem de erro útil.

Tela do slack com o resultado inválido do bot.

Agora, tente com dois números de telefone legítimos.

Tela do slack com o bot em funcionamento.

Aguarde um segundo para a chamada recebida…
Tela do iPhone com aviso de uma nova chamada aguardando ser atendida.

Agora, estamos em uma chamada de conferência com uma ou mais pessoas para quem discamos pelo Slack. É hora de colocar essas perguntas em hash por telefone para que possamos voltar à codificação.

Conclusão

Oba! Nosso novo callbot está pronto! Na verdade, há muito mais a se fazer com as APIs do Slack e da Twilio. Aqui estão mais algumas ideias para você testar, agora que tem as noções básicas:

  1. Implemente um back-end persistente como PostgreSQL e use-o para armazenar um número de telefone para cada nome de usuário
  2. Adicione o recurso de SMS ao bot
  3. Use o Twilio Lookup para determinar se um número é realmente válido em vez de apenas analisável
  4. Impulsione o analisador com melhor análise e processamento de linguagem natural, para que haja mais naturalidade na interação dos usuários do Slack.

Este artigo foi traduzido do original "How to Add Phone Calling Bots to Slack with Python". Enquanto melhoramos nossos processos de tradução, adoraríamos receber seus comentários em help@twilio.com - contribuições valiosas podem render brindes da Twilio.