En esta serie de posts veremos información paso a paso para crear un bot en Twitter. Este tipo de bots pueden ser muy útiles para programar publicaciones, para conocer información periódica de nuestra cuenta, para detectar menciones y tomar decisiones, y muchas cosas más. Todo ello lo quiero enfocar desde un punto de vista agnóstico del framework que vayamos a utilizar. Lo voy a basar en PHP porque la mayoría de los scripts los tengo hechos en este lenguaje y me va a resultar útil para un par de proyectos en el futuro.
También debo agradecer a Josep Maria por sugerirme hacer este post.
Autentificación con OAuth
Hace mucho tiempo, cuando Twitter empezó, y empezaron a surgir los servicios web, cada proveedor tenía su propia API, que funcionaba de una forma diferente. En principio me refiero a la autentificación o autorización de los servicios, aunque también me refiero a la forma de llamar los diferentes recursos. Poco a poco, todos los grandes servicios van utilizando una forma similar de trabajar, lo cual hace que sea muy parecida la forma de interactuar con la API de Twitter, Facebook, LinkedIn, Google, Dropbox, plataformas de pago como Paypal, sistemas de trading como CoinBase, incluso comercios online como Magento.
El problema de la autentificación, aunque ya estamos acostumbrados a ello es que una aplicación debe utilizar un servicio como si fuera el usuario. Y debemos garantizar la seguridad de todas las partes, especialmente la del usuario. Antiguamente (y todavía vemos alguna aplicación así), si en lugar de ir a la web del servicio queremos que un programa, u otra webapp utilicen el servicio por nosotros (o queremos automatizarlo en un script) debíamos introducir el nombre de usuario y contraseña (nuestro) en dicho programa. Pero claro, las contraseñas, por definición deberían ser intransferibles. Es como si para hacer un pago en una tienda online tuviéramos que dar nuestra clave del banco (y con eso diéramos a la tienda control total sobre nuestros ahorros).
Pero es que si damos la contraseña a las aplicaciones que utilizamos podemos tener varios problemas:
- La aplicación tendrá total acceso al servicio como si fuéramos nosotros. Puede que confiemos en el desarrollador (o seamos nosotros), pero éste puede tener malas intenciones (y no sería la primera vez). En materia de seguridad nunca debemos confiar en nadie.
- Si decidimos cambiar la contraseña, lo cual es una buena práctica y deberíamos hacer periódicamente, deberíamos actualizar la contraseña en todos los servicios para que ninguno falle.
- Si no confiamos más en una aplicación. Al tener ésta nuestra contraseña, deberíamos cambiarla inmediatamente. Pero también actualizarla en todas las aplicaciones en las que sí seguimos confiando para que sigan funcionando.
- Las aplicaciones no podrán almacenar un hash de nuestra contraseña. Es decir, la contraseña podrá ser descifrada si se filtran los datos del proveedor de la aplicación.
- Hay muchos más peligros, seguro que no tenemos que pensar demasiado para encontrar algunos más.
Entonces surgió OAuth, como una forma de autorizar a las aplicaciones a realizar acciones en nuestras cuentas de usuario de diferentes servicios. No necesitamos enviar nuestra contraseña a nadie. Sólo tenemos que estar identificados en el servicio con nuestro usuario y la aplicación podrá hacer el resto.
Para ello (y para el ejemplo utilizo Twitter como servicio):
- A la aplicación le decimos que queremos que use Twitter por nosotros.
- La aplicación habla con Twitter y éste le da una URL para que el usuario confirme.
- Nos vamos a la nueva URL (que es de Twitter), y cuando el usuario entra, una vez identificado con su nombre de usuario y contraseña (en Twitter) le dice que una aplicación quiere realizar ciertas acciones en su nombre. La aplicación tiene que pedir permiso para todo lo que quiere hacer, no es lo mismo un servicio que sólo lea tweets que uno que tenga que publicar también. Ya depende de nosotros y de la confianza que queramos otorgar a la aplicación.
- Cuando, como usuarios, le hemos dicho a Twitter que sí autorizamos a la aplicación a realizar ciertas acciones en nuestro nombre, Twitter se lo dice a la aplicación y le da un token, es como una contraseña temporal para realizar acciones en nuestro nombre y que tendrá que utilizar cada vez que quiera hacer algo.
- A partir de ahí, la aplicación podrá hacer sólo lo que hemos permitido que haga.
Una vez hecho esto, se solucionan algunos problemas:
- Al no dar nuestra contraseña, ésta no estará almacenada en ningún sitio extraño, no será objeto de datos filtrados y si la cambiamos no tiene por qué afectar a las aplicaciones que estén conectadas con nuestra cuenta.
- Si dejamos de confiar en una aplicación. Podemos ir a Twitter y revocar el acceso a una aplicación. No tendremos que cambiar contraseñas ni hacer nada, sólo decir que ya no queremos que dicha app funcione. Otros servicios (no Twitter) pueden hacer que el token caduque por lo que podríamos otorgar permisos de forma temporal.
- A las aplicaciones no se les da acceso total. Es dedcir, las aplicaciones podrán hacer sólo lo que nosotros especifiquemos. Por lo que si la aplicación es para crear una estadística de tweets, no tiene por qué publicar tweets en nuestro nombre, así que no debemos darle permiso para ello y Twitter se encargará de que no pueda hacerlo.
- Los tokens sólo pueden ser utilizados por la aplicación que los solicita, así que, si el token se filtra, por un lado, el ladrón no podrá hacer nada por sí solo (necesitará las claves de autorización de la aplicación, que se suelen guardar en otro lado). Y, por otro lado, si el ladrón (o un empleado que trabaja en la apliciación, coge también las claves de autorización de la aplicación, como desarrolladores, o empresa desarrolladora, podremos solicitar a Twitter nuevas claves, y no se podrá hacer nada con ningún token de acceso de los usuarios.
API de Twitter
Todo esto, al ser un estándar abierto y, además, al utilizar Twitter una API REST para comunicarnos con él hace que el lenguaje de programación en el que hagamos las aplicaciones sea lo de menos. No dependemos de ninguna característica especial, ni función propia del lenguaje, sólo necesitamos que sea capaz de hacer peticiones HTTP, lo cual, desde hace mucho tiempo se puede hacer independientemente del lenguaje utilizado. Sólo necesitaremos la documentación de Twitter para saber qué podemos pedir y qué petición debemos hacer para pedirlo.
Es más, gracias a esta independencia del lenguaje, ellos se libran de tener que hacer y mantener bibliotecas en muchos lenguajes de programación y plataformas. Ellos tienen algunas publicadas, pero si alguna no existe no es muy difícil crearla. Lo cual deja la puerta abierta a muchos desarrolladores que publican sus bibliotecas y su visión de cómo hacer las cosas.
Para los ejemplos, yo he utilizado la biblioteca TwitterOAuth de Abraham Williams para PHP. Pero podríamos utilizar cualquier otra en nuestros proyectos.
Dar de alta la aplicación
Lo que haremos aquí será decirle a Twitter que vamos a crear una aplicación. Para ello vamos a apps.twitter.com, donde veremos un listado de todas nuestras aplicaciones y pulsaremos sobre Create New App:
Tras esto nos preguntará algunos datos de nuestra aplicación:
- Nombre
- Descripción
- Página web. Donde los usuarios se pueden descargar nuestra aplicación, o web sobre la que funciona. O cualquier cosa, aunque es un dato obligatorio nos la podemos inventar.
- URL Callback. Es la URL con la que Twitter hablará con nuestra aplicación. Aunque puede ser que, como en este caso, la aplicación no esté publicada en una web y será un dato que no queremos que Twitter utilice. En este caso, lo dejamos vacío.
- Más tarde podremos introducir información sobre nuestra política de privacidad, permisos que necesitamos y el icono identificativo.
Lo vemos todo en esta pantalla.
. Tras esto, nos mostrará una pantalla más con información:
Esta pantalla muestra datos sobre nuestra recién creada aplicación, siendo lo más importante nuestra Consumer Key o API Key. Será la primera parte del identificador de nuestra aplicación en Twitter. Y que nuestro sistema pueda interactuar con la plataforma. Aunque todavía nos falta un trozo, la Consumer Secret o API Secret. Esta segunda clave debe ser privada y jamás, bajo ningún concepto enseñársela a nadie. La Consumer Secret o API Secret podemos obtenerla si vamos a la pestaña Keys And Access Tokens. Vale, yo os estoy enseñando la mía, pero para cuando se ha lanzado el post, dichas claves ya no estarán vigentes. Podemos ver esta información en la siguiente captura:
Estas claves tendrán que ser enviadas por nuestra aplicación y, en los ejemplos podremos ver que las claves están en el código, aunque podemos almacenarlas de modo más elegante en un archivo YAML, Json, Ini, base de datos, etc. Son claves que nuestra aplicación deberá utilizar sea cual sea el usuario que venga.
Enviando peticiones con cURL a pelo
Algo que me encanta es que todas estas APIs pueden ser utilizadas desde cualquier sitio. Incluso podemos ejecutar comandos en la terminal que envíen un tweet o reciban cierta información. Y es lo que vamos a hacer a modo de prueba de concepto.
Para ello, seguimos en la pestaña Keys and Access Tokens y pulsamos el botón Create my access token. Lo podemos ver en la imagen abajo del todo. Con esto, vamos a obtener un nuevo token de acceso (access token) que autorice a nuestra aplicación a utilizar los servicios de Twitter con nuestro usuario actual. Este token de acceso será una ristra de números y letras lo suficientemente larga como para que dé pereza escribirla a mano. Con esto, podemos deducir que si queremos que otro usuario utilice nuestra aplicación, el nuevo usuario deberá autorizarnos a utilizar su cuenta de usuario (ya veremos cómo hacer todo esto).
Ahora que hemos generado el token de acceso, una vez utilizado, éste puede ser revocado. Es decir, le diremos a Twitter que la aplicación ya no tiene permiso para acceder a nuestro Twitter. O puede ser regenerado, o lo que es lo mismo, la aplicación seguirá teniendo permiso, pero hemos cambiado las claves:
Bueno, vamos al lío. Enviar las peticiones a cURL puede ser complicado, aunque tenemos bibliotecas que nos ayudarán enormemente en nuestra tarea. Y, para este ejemplo, vamos a eliminar la parte de autentificación de la aplicación y autorización del usuario de Twitter. Vamos a utilizar directamente el access token de usuario y access token secret que ya tenemos.
No me voy a extender mucho en la explicación, pero para que el protocolo sea seguro y que nadie con una petición a la API de Twitter sea capaz de realizar acciones en nombre de un usuario (por ejemplo, robar tweets, leer mensajes privados, enviar tweets desde su cuenta, etc). La API requiere una autorización cada vez que hacemos una petición a la misma (la autorización la tenemos gracias al consumer key de la aplicación y al access token de usuario), y dicha autorización va firmada. Dichas firmas se realizan con el algoritmo de hash HMAC SHA1 a través de una clave generada por el consumer secret de la aplicación y el access token secret del usuario (y estos datos no se enviarán nunca, sólo las firmas generadas con ellos). Es decir, antes de enviar una petición, la firmamos y le añadimos a la petición la firma que hemos hecho. De esta forma, si alguien es capaz de ver la petición que hemos hecho al sistema, no será capaz de realizar una petición en nuestro nombre.
Para realizar la petición, además de Bash, necesitaremos cURL. para hacer la petición web, openSSL para generar el hash, base64 (que seguramente lo tengamos, es parte de las coreutils), perl con el módulo MURI::Escape para hacer parámetros amigables para transmisión por HTTP (urlencode) y jq para que la salida en JSON sea más bonita.
Descargando los últimos tweets
Para descargar los últimos tweets de mi timeline tengo que hacer una petición GET a https://api.twitter.com/1.1/statuses/home_timeline.json para ello, primero voy a montar los datos de autorización:
oauth_signature_method=\"HMAC-SHA1\",
oauth_timestamp=\"$(date +%s)\",
En este caso, meteremos en la variable $OAUTH toda esa información. Siendo:
- oauth_nonce: Una cadena de caracteres única que generamos. Un payload arbitrario que complicará la petición haciéndola más segura. Supuestamente Twitter verifica este valor evitando que se repita, pero no es así. Pero debemos cambiarlo a cada petición porque hará que los datos firmados sean más seguros.
- oauth_signature_method: Es el método utilizado para crear la firma de los datos. Esta firma verificará la información que estamos mandando, y que la estamos mandando nosotros. Por ahora el método es HMAC-SHA1.
- oauth_timestamp: Es la marca de tiempo en formato Unix del momento en el que enviamos la petición. No deberíamos poder enviar peticiones si la diferencia de tiempo es muy grande pero Twitter tampoco se lo toma muy en serio si le enviamos una timestamp de ayer, aunque si pasan varios días sí que se queja un poco. Es más, sería problema nuestro no actualizar este parámetro, ya que cambiarlo a menudo colabora en la fortaleza de la firma.
- oauth_token: Es el Access Token del usuario.
- oauth_consumer_key: Es la Consumer Key de nuestra aplicación.
- oauth_version: Es la versión del protocolo. Por ahora podemos utilizar la 1.0
Ahora, para descargar los tweets debemos poner el siguiente comando:
Este comando, que puede resultar muy largo envía la petición a Twitter incluyendo la cabecera de Authorization con los datos del protocolo OAuth que hemos visto en la variable anterior. Además, adjunta la firma (que es lo más largo. Una vez hecha la petición, como Twitter nos envia la información en formato JSON, se la pasamos a jq para poder visualizarla bien en pantalla.
Algunas órdenes útiles que podemos extraer de la línea anterior son:
Hace un urlencode en el texto complicado. Con esto, extraeremos una cadena de texto derivada del texto complicado convirtiendo algunos caracteres conflictivos a su código hexadecimal (%20, %3D, %26...). Según especifica Twitter en su documentación, tenemos que hacer esto a ciertas cadenas de texto antes de aplicar el algoritmo de firmado.
Es algo parecido a lo anterior, un poco rebuscado y propio de esta cadena en particular. Como estamos en Bash he reducido algunas cosas. Los elementos de la cadena $OAUTH están separados por comas, pero si los colocásemos como datos HTTP estarían separados por & y los valores no tendrían comillas. Eso hacemos, eliminamos las comillas dobles, y cambiamos los ", " por "%26" y los "=" por %3D. En la práctica lo haremos de forma más elegante, esto es sólo una prueba.
Nos devuelve el texto para firmar firmado con el algoritmo hmac-sha1 y la clave para firmar.
La documentación de Twitter, nos dice que la clave para firmar es el Consumer Secret, un ampersand (&) y el Access Token Secret, todo seguido. También nos habla del contenido de la signature antes de ser cifrada. Ésta debe ser:
PROTOCOLO&URL&argumentos
Por lo que, PROTOCOLO es GET, la URL la urlencodeamos y hacemos lo mismo con los argumentos. Todo lo separamos con ampersands (&).
Enviando un tweet desde consola
Esto será un poco más largo, pero la explicación será prácticamente la misma que en el caso anterior. Sólo que tenemos un nuevo campo, el status y será una petición de tipo POST:
oauth_signature_method=\"HMAC-SHA1\",
oauth_timestamp=\"$(date +%s)\",
Vale, sí hay que liarla para enviar un tweet desde consola (a pelo), pero esto está muy bien para saber qué necesitamos hacer, o si queremos desarrollar una aplicación que lo haga y conocer un poco cómo funciona todo. El problema es que el tweet tenemos que escribirlo dos veces, una para enviar el tweet y otra para crear la firma. Recordemos que pequeñas variaciones en el contenido a firmar crean cadenas de firma totalmente distintas.
El tweet que envié arriba es el siguiente:
Every time when I look in the mirror... All these lines on my face getting clearer
- Gaspar Fernández (@Gaspar_FM) January 27, 2018
Empezamos nuestro proyecto
No voy a hacer una aplicación web. Aunque será fácil convertirlo en web, mi propósito es hacer una aplicación que se ejecute en segundo plano y esté todo el tiempo corriendo, por lo que será una aplicación de consola. Podríamos haber implementado la primera parte de la autorización como web para que sea más sencillo, pero también de esta forma podemos ver y controlar el proceso de envío de información Aplicación/Twitter/Usuario.
Lo primero que necesitamos es tener Composer instalado en nuestro ordenador. Con esto será muy fácil instalar la biblioteca y actualizarla posteriormente. Una vez tenemos composer, crearemos un directorio para nuestra aplicación y ejecutaremos:
Con ello, se crearán varios archivos de composer y tendremos un directorio llamado vendor dentro del cual tendremos la biblioteca TwitterOAuth instalada. Y si en algún momento queremos actualizar, tanto TwitterOAuth como el resto de bibliotecas que hayamos incluido con composer en nuestro proyecto, basta con hacer:
Vamos al código en PHP
Los ejemplos anteriores los hemos hecho gracias a que conocíamos el Access Token y el Access Token Secret del usuario, que éramos nosotros. Pero normalmente un usuario de la aplicación usará nuestra interfaz y tendrá que ser nuestra aplicación la que ayudándose de Twitter obtenga la autorización del usuario para trabajar en su nombre. Así que vamos a trabajar a partir de ahora en el código.
Como he dicho antes, quiero hacerlo lo más agnóstico posible, de framework, de bases de datos y de todo, para poder incorporarlo en nuestros proyectos, y para entender bien cómo funciona. Además, lo que me interesa en principio para este post es la obtención de autorización y el envío de tweets desde nuestro script. Así que, vamos a dividirlo en dos scripts, para tener las cosas claras y separadas.
Lo primero, será para preparar nuestro proyecto con composer, para que se carguen automáticamente los archivos PHP al proyecto, haciendo que el autoload de composer busque los archivos del espacio de nombres Poesia dentro de lib. Para ello editamos composer.json dejándolo así:
GeSHi Error: GeSHi could not find the language json (using path /home/gaspy/www/poesiabinaria.net/www/wp-content/plugins/codecolorer/lib/geshi/) (code 2)
Ahora ejecutando
El sistema será capaz de buscar elementos dentro del espacio de nombres Poesia dentro de lib/ sin tener que incluirlos (utilizando los namespaces y el autoload).
El proyecto lo voy a dividir en varias partes. La primera de ellas valdrá para que el usuario autorice a la aplicación a trabajar con su Twitter. En este caso, la aplicación conectará con Twitter, y obtendrá unos tokens de acceso temporales con los que generaremos una URL con la que pediremos permiso a Twitter. Éste, cuando nos otorga el permiso le dará un PIN al usuario que tendrá que introducir en la aplicación para así poder pedirle a Twitter el token de acceso definitivo del usuario.
Si esto fuera una aplicación web no tendríamos que hacer que el usuario entrara en el navegador para autorizar, simplemente redirigiríamos a una URL de Twitter, y luego Twitter, llamaría a nuestra callback_url con la que volveríamos a la apliación con un código de verificación (el PIN) y con éste pediríamos el token de acceso y el token secreto definitivos para el usuario.
Pequeñas clases de ayuda
Dentro del directorio del proyecto tendremos un directorio llamado lib que contendrá algunos archivos útiles para mí. En este caso, con el objetivo de hacer el proyecto independiente de bibliotecas ni sistemas de base de datos y, para que vosotros podáis utilizar el que más os guste. He incluido dentro de lib/Config.php un Singleton que obtiene el consumer key y el consumer secret.
Ya que, en una aplicación real podremos tener varios, y cada uno los puede gestionar como quiera, sólo me interesa que alguien llame a Config::consumerKey() y Config::consumerSecret(), por dentro podrá funcionar como deseéis. Es más, al final, los valores están puestos a pelo, como vemos en lib/Config.php:
El segundo archivo corresponde con el almacén de datos del usuario, lib/User.php. En un sistema real seguro que preferís hacerlo con bases de datos. Pero para este ejemplo, me interesa que User::getUserData($nombre) y User::updateUserData($datos) funcionen. Al primero tendremos que pasarle el nombre del usuario, lo que va detrás de la @ en Twitter. El segundo necesita un array devuelto por la autorización de Twitter que contendrá:
- OAuth Token (oauth_token)
- OAuth Secret (oauth secret)
- User ID (user_id)
- Nombre en pantalla (screen_name). El que va detrás de la @
- Fecha de caducidad (x_auth_expires)
Aunque la clase no verifica los valores, cuando necesitas volver a los datos de usuario es importante que los tenga.
Por último, incluimos el archivo lib/Util.php con utilidades varias que necesitaremos. Aunque a priori esté un poco vacío, siempre me gusta dejar un archivo así abierto para meter cosas que no encajan en ningún lado:
Aunque podemos hacerlo todo en un solo archivo PHP, como aquí mi objetivo es que todo quede lo más claro posible, he querido abrir varios archivos. El primero de ellos será auth.php y su misión será pedir un Access Token a Twitter en nombre del usuario para que así nuestra aplicación pueda utilizar la cuenta de Twitter.
Ahora, si desde consola, ejecutamos php auth.php nos dará una URL que debemos visitar. La URL será de Twitter y nos presentará algo como esto:
Una vez pulsamos el botón para autorizar la aplicación, nos entregará un PIN:
Luego, volveremos a nuestra aplicación e introduciremos el PIN facilitado por Twitter. La aplicación le dará este código a Twitter. Es ahora cuando Twitter nos dará un access token y un access token secret nuevo y que podremos utilizar de aquí en adelante.
El sistema, tal y como está planteado, serviría para que la misma aplicación pueda manejar las cuentas de varios usuarios, por lo que podemos almacenar Access Tokens de todos y utilizarlos según nos vaya interesando tanto para leer tweets como para enviarlos. En este ejemplo todo se almacena en un fichero dentro de etc/users.dat (como dice en lib/User.php)que en realidad es un json que contiene un array con todos los datos de los usuarios. Más adelante, en los siguientes ejemplos, manejaremos esos tokens para realizar acciones en Twitter.
Por último, el script visualiza el contenido enviado por Twitter donde podemos ver el ID de usuario, el nombre en pantalla, y los tokens. Además, algo que está comentado es que podemos paasrle al objeto $tw los tokens de acceso del usuario y empezar a trabajar. Como vemos, llamamos a account/verify_credentials que devolverá información del usuario actual como el nombre, locaclización, descripción, web, último tweet, fecha de creación de la cuenta, número de tweets y muchas cosas más.
Últimos tweets
Ahora vamos con un ejemplo práctico en el que descargamos la lista de los últimos tweets del usuario actual.
A partir de aquí podemos investigar las posibilidades que tiene todo esto. Podríamos hacer un var_dump($tweet) para ver la información que nos envía Twitter. Es un ejercicio interesante que nos puede abrir un mundo de posibilidades.
Enviar un tweet
Igual que hemos hecho hasta ahora, llamamos a la API con una petición de actualización de estado. Donde podemos incluir enlaces si queremos.
Más consideraciones
Aunque el post se está alargando mucho, debo decir que las peticiones que nuestra aplicación puede hacer sobre un recurso de un usuario están limitadas. Es decir, por supuesto podríamos desarrollar una aplicación que se ponga a leer tweets de todo el mundo buscando, almacenándolos y recopilando información. Pero el número de peticiones para lectura de tweets que podemos hacer está limitado.
Es normal, esto es un servicio gratuito que ofrece twitter y algo que consume muchos recursos en sus servidores. Además, podemos ver que la información que nos entrega de cada tweet es muy grande, así que debemos comprender que para generar una salida hace falta una gran maquinaria detrás.
Por eso debemos tener controlado el número de peticiones que hacemos a Twitter y, al menos, controlado que de vez en cuando nos devolverá un error. Es más, las peticiones a Twitter, a veces fallan, puede que por culpa de Twitter, puede que por culpa de la red, o puede que porque no hemos hecho la petición correcta así que debemos utilizar mucho el try { } catch para capturar los posibles problemas que nos devuelva la aplicación.
Posibilidades a nuestro alcance
¡Es vuestro turno! Me gustaría saber lo que programaríais con esta sencilla guía. Además, podéis ver el listado de cosas que podéis hacer en la documentación de la API de Twitter y algún ejemplo más en la web de TwitterOauth.
.
Foto principal: