APIs e Microsserviços

2 jun, 2014

Bots no Twitter: como automatizar interações

Publicidade

Desenvolvedores adoram APIs porque elas permitem que você interaja com um serviço via código, trazendo assim uma série de benefícios como incrementar um produto, automatizar uma tarefa ou capturar dados. A plataforma do Twitter oferece um conjunto de APIs para que os desenvolvedores e empresas possam usufruir dos dados gerados pelos seus usuários, e uma das aplicações possíveis é a criação de um bot (robô, em inglês), cuja função é interagir com usuários à medida que ele recebe tweets dirigidos a ele.

A função desse bot pode variar. Por exemplo, durante a Campus Party, foi disponibilizado um bot chamado @oquevernaCP (O que ver na Campus Party?) que, ao receber tweets dos usuários com a hashtag de um palco do evento, ele respondia com o próximo evento da programação – naquele palco ou na programação geral. É uma ótima maneira de ajudar participantes a se planejarem em um evento com vários acontecimentos em paralelo.

twitter-1

A Startbucks foi mais longe e criou um bot que, até dezembro de 2013, havia rendido mais de US$ 180 mil em vendas de café: o @Tweetacoffee. Esse bot ajuda os usuários a presentearem amigos com café da Starbucks, bastando que você crie um tweet como este abaixo:

twitter-2

O bot envia instruções de pagamento para você e, logo após a efetuação desse pagamento, ele envia as instruções de como pegar o vale-café no site. É uma solução simples, porém com um impacto grandioso.

Este artigo apresentará como criar um bot simples, que responde “Sim”, “Não” ou “Talvez” para qualquer pergunta de um usuário. Você pode trocar essa simples resposta por algo bem mais complexo. O objetivo é entender quais recursos da API você vai precisar e como identificar quando você deve responder a um usuário para poder interagir automaticamente. A implementação será em Ruby, mas desenvolvedores de qualquer outra linguagem serão capazes de entender como funciona.

Criando a aplicação no Twitter

Para obter os tokens de acesso para utilizar a API do Twitter, você precisa acessar apps.twitter.com e criar sua aplicação em “Create new app“. Você terá que preencher um formulário com os dados dela. Logo após sua aplicação ser criada, você deve criar os access tokens, que serão necessários para a autenticação na API. Para obtê-los, você deve entrar na sua aplicação e depois clicar na aba “API keys” e pedir para gerá-los. Depois desse procedimento, você deve possuir quatro tokens: consumer key, consumer secret, access token key, access token secret. Não compartilhe esses tokens com ninguém, pois com eles qualquer pessoa pode acessar sua conta. Certifique-se também, na aba “Permissions“, de que a sua aplicação possui acesso de leitura e escrita à API.

API de Streaming e a biblioteca Tweetstream

Normalmente, uma API é oferecida no modelo requisição e resposta, no qual para cada recurso que um desenvolvedor deseja recuperar ele realiza uma requisição HTTP e manipula a resposta obtida do servidor da API. Esse tipo de modelo é aplicado pela REST API no Twitter, porém, para o caso dos bots, desejamos saber em tempo real quando um tweet com uma pergunta é feito para que possamos responder o mais rápido possível. Sendo assim, o Twitter também oferece a API de Streaming, que trabalha com um modelo de conexão persistente, ou seja, a requisição é feita e a conexão fica aberta indefinidamente, e qualquer tweet criado que esteja dentro do critério estabelecido pela conexão é retornado na resposta com uma latência muito baixa entre sua criação e a entrega.

Para o nosso bot, utilizaremos a URL GET user da API de streaming. Essa URL faz parte do stream do usuário, ou seja, ela retorna eventos e dados relativos ao usuário que está autenticado, como quando alguém passa a seguir ou quando algum tweet é retweetado. No caso deste bot, estamos interessados em quando algum usuário cria um reply (resposta), ou seja, quando alguém cria um tweet que se inicia com arroba e o nome do usuário do bot.

A conexão será feita utilizando a biblioteca tweetstream, que é uma gem do Ruby própria para lidar com conexões à API de Streaming. Além disso, essa gem tem como dependência o EventMachine, que é uma biblioteca que suporta o desenvolvimento de aplicações orientadas a eventos. Esse tipo de arquitetura é importante para um bot no Twitter, pois ela permite que requisições à API não sejam bloqueantes, o que poderia deixar a resposta do bot lenta.

Configuração do Tweetstream

Basicamente, o que precisamos para configurar a biblioteca são os tokens obtidos quando se cria a aplicação em apps.twitter.com. Crie um arquivo chamado bot.rb e coloque o código abaixo:

bot.rb

require "rubygems"
require "tweetstream"
require "em-http-request"
require "simple_oauth"
require "json"
require "uri"

# config oauth
OAUTH = {
 :consumer_key => "EDIT_HERE",
 :consumer_secret => "EDIT_HERE",
 :token => "EDIT_HERE",
 :token_secret => "EDIT_HERE"
}
ACCOUNT_ID = OAUTH[:token].split("-").first.to_i

TweetStream.configure do |config|
 config.consumer_key       = OAUTH[:consumer_key]
 config.consumer_secret    = OAUTH[:consumer_secret]
 config.oauth_token        = OAUTH[:token]
 config.oauth_token_secret = OAUTH[:token_secret]
 config.auth_method = :oauth
end

O código acima apenas cria uma constante para configuração dos tokens de OAuth. Você deve trocar os valores “EDIT_HERE” pelos valores de consumer_key e consumer_secret, que são obtidos logo ao criar a aplicação, e token e token_secret, que são obtidos ao requisitar os tokens de acesso.

Logo depois, como precisamos do ID da conta de Twitter para saber se as perguntas são dirigidas a esse usuário, extraímos o ID do valor da variável OAUTH[:token], que se localiza no início da string.

Por fim, chamamos o método de configuração da biblioteca tweetstream, que simplesmente atribui os valores dos tokens para as variáveis equivalentes na biblioteca.

Realizando a conexão ao User Stream

Como já temos a biblioteca configurada, podemos escrever o código que realiza a conexão ao User Stream.

bot.rb (cont.)

@client  = TweetStream::Client.new

puts "[STARTING] bot..."
@client.userstream() do |status| 
  # Cada status recebido pelo User Stream será manipulado dentro desse bloco
  puts status.text  # print do texto do tweet
end

Nesse trecho de código, estamos criando uma instância do cliente de tweetstream que vai lidar com a conexão à Streaming API e logo após adicionando o método @client.userstream que realiza essa conexão. Esse método requer um bloco de código, que será executado toda vez que a conexão com o User Stream retornar um status. Esses status são representados por objetos JSON que são mapeados automaticamente para objetos Ruby, o que permite o fácil acesso e a manipulação dos eventos recebidos. Por exemplo, para saber qual é o texto do tweet, basta acessar status.text.

Decidindo se você deve responder ou não a um tweet

Vamos expandir um pouco o código anterior para incluir a decisão se vamos ou não responder a um tweet recebido via Streaming. Isso é importante, porque não queremos gerar respostas automáticas para retweets ou para simples menções ao nosso usuário, por exemplo.

bot.rb

@client  = TweetStream::Client.new

puts "[STARTING] bot..."
@client.userstream() do |status| 

if !status.retweet? && 
   status.in_reply_to_user_id? && status.in_reply_to_user_id == ACCOUNT_ID &&
   status.text[-1] == "?"

     tweet = {
       "status" => "@#{status.user.screen_name} " + %w(Sim Não Talvez).sample,
       "in_reply_to_status_id" => status.id.to_s
     }

     # Aqui vai o código que posta a resposta no twitter (apresentado abaixo)
 end
end

O comando if acima é dividido em três partes:

  1. a primeira linha do comando verifica se o status recebido não é um retweet, pois não queremos gerar respostas automáticas para perguntas que já foram respondidas; isso também pode gerar um uso abusivo da API;
  2. a segunda linha verifica se o tweet é em resposta ao usuário bot, ou seja, se o tweet se inicia com arroba (@) e o nome do usuário, e esse usuário possui ID igual ao ID do nosso bot, então esse tweet é dirigido apenas para o bot;
  3. a terceira linha apenas verifica se o último caractere do texto do tweet é um ponto de interrogação, ou seja, se o tweet é mesmo uma pergunta.

Se todas as condições forem satisfeitas, nós criamos o hash que vai conter a resposta e que possui dois campos: o status a ser retornado, que é simplesmente um sorteio entre “Sim”, “Não” e “Talvez”; e o ID do usuário que estamos respondendo, o que faz com que o Twitter notifique esse usuário e conecte esse tweet como uma resposta da pergunta que o usuário fez.

Criando o tweet de resposta

Por fim, só precisamos postar o tweet com a resposta. Para isso, utilizaremos a REST API, mais especificamente o endpoint POST statuses/update. Vamos atualizar o trecho de código anterior com a requisição HTTP que realiza o post do tweet.

bot.rb

@client  = TweetStream::Client.new

puts "[STARTING] bot..."
@client.userstream() do |status| 

if !status.retweet? && 
   status.in_reply_to_user_id? && status.in_reply_to_user_id == ACCOUNT_ID &&
   status.text[-1] == "?"

     tweet = {
       "status" => "@#{status.user.screen_name} " + %w(Sim Não Talvez).sample,
       "in_reply_to_status_id" => status.id.to_s
     }

     twurl = URI.parse("https://api.twitter.com/1.1/statuses/update.json")
     authorization = SimpleOAuth::Header.new(:post, twurl.to_s, tweet, OAUTH)

     http = EventMachine::HttpRequest.new(twurl.to_s).post({
       :head => {"Authorization" => authorization},
       :body => tweet
     })
     http.errback {
       puts "[CONN_ERROR] errback"
     }
     http.callback {
       if http.response_header.status.to_i == 200
         puts "[HTTP_OK] #{http.response_header.status}"
       else
         puts "[HTTP_ERROR] #{http.response_header.status}"
       end
     }

 end
end

Há dois pontos importantes nesse trecho que realiza a requisição. Primeiramente, a requisição deve ser autenticada, portanto deve possuir a assinatura OAuth. Para isso, utilizamos a biblioteca simple_oauth, cujo objetivo é receber como parâmetros os detalhes da requisição e os tokens OAuth configurados anteriormente e gerar a assinatura que deve ser atribuída ao header Authorization da requisição.

Outro ponto importante é que desejamos que essa requisição não seja bloqueante, pois sua latência pode fazer com que as respostas do nosso bot sejam entregues com atraso. Para implementarmos essa requisição, usamos a biblioteca em-http-rquest, que implementa a execução da requisição dentro da arquitetura do EventMachine e, portanto, é executada de forma assíncrona.

Perceba também que adicionamos o header Authorization na instanciação do request, bem como o tweet a ser feito no body. Após a instanciação, adicionamos o código que lida com os resultados da nossa requisição, em casos de falha ou de sucesso.

Executando o bot

Pronto! Agora basta entrar no terminal e executar o bot com o comando ruby bot.rb. Após a conexão, entre no Twitter e crie um tweet que inicia com o usuário do bot. O exemplo abaixo foi obtido rodando o código deste artigo (dê uma olhada nas datas de criação dos tweets para ter uma ideia da latência entre a pergunta e a resposta):

twitter-3

Conclusão

Este artigo mostrou com menos de 80 linhas de código como você pode criar um bot no Twitter que responde automaticamente a perguntas de outros usuários. Apesar de simples, essa implementação apresenta a linha geral a ser seguida quando você tiver alguma ideia interessante de um bot. O código final pode ser encontrado em um gist nesta URL.

Se você quiser ver uma implementação um pouco mais elaborada, que implementa fluxos alternativos da conexão ao User Stream, como casos nos quais acontecem erros ou tweets são perdidos devido ao volume alto de tweets, ou como implementar Rate Limit para evitar que um usuário faça perguntas demais, você pode consultar o código do bot @oquevernaCP, que foi executado na última Campus Party, em São Paulo. O código pode ser acessado aqui.

***

Artigo publicado originalmente da Revista iMasters de maio.