Acerca de Jose Antonio

Yo soy el que manda aqui... ¿Que pasa?

Hacer un bot de telegram sin usar ninguna librería de telegram

Entiendo que conoces la aplicación telegram, lleva con nosotros mucho tiempo y, aunque en nuestro país es mucho más usada whatsapp, telegram tiene también sus adeptos, sobre todo para canales de distribución o para uso secundario con aquellos que ya usan esta herramienta.

Una de las características más importantes de esta plataforma es la separación entre usuarios y bots, los usuarios tienen que tener un número de teléfono registrado y no es tan sencillo hacerse con una cuenta adicional si no tienes otro teléfono, la plataforma distingue perfectamente entre personas y bots, que serían los programas a los que solo se permite interacturar con personas que hayan hablado con ellos antes o con canales de distribución a los que se hayan añadido como administradores (antes podían añadirse como miembros, pero esa posibilidad ya no existe). El caso es que llevo un tiempo desarrollando bots para esta plataforma, usando diversas librerías y después de tantear todos los límites de telegram y de las librerías me he visto obligado a ir directo al api de telegram para bots… Y resulta que no solo es sencillo, sino muy sencillo iniciarse en la creación de bots sin ninguna librería adicional… En este caso vamos a crear un bot de cero y sin librerías de por medio.

Pero, como decía Jack el destripador: vayamos por partes…

Lo primero es crear un bot, telegram pone a nuestra disposición el bot llamado botfather que nos permite crear bots asociados a nuestra cuenta (un máximo de 20 por si os sentís tentados de crear armadas de bots) y se crean simplemente con el comando /newbot y responder unas pocas preguntas… Como podemos ver en la imagen:

Lo importante, y fundamental, es el token que nos genera botfather, esto será todo lo que necesitamos para acceder al api, que como bien nos indican está en https://core.telegram.org/bots/api

Lo segundo será decidir en qué lenguaje y para qué plataforma vamos a crear nuestro bot, en nuestro caso y para simplificar las cosas vamos a hacerlo en python que puede correr en cualquier sitio (linux, mac, windows o incluso serverless), en mi caso vamos a crear un bot que, cuando se lo pedimos, nos genere una nueva contraseña (luego ya veremos si queremos mejorarlo). Por cierto, todo el código que generemos aquí estará en el repositorio https://github.com/yoprogramo/nomorepassbot para que podáis usarlo y modificarlo a voluntad.

Hemos dicho que no tendría dependencias de ninguna librería telegram, pero no hemos dicho que no la tuviese de alguna otra, así que es momento de decidir si queremos usar alguna librería http, en mi caso, por simplicidad he elegido la librería httpx, como tampoco quiero ir instalando cosas de más voy a utilizar pipenv para crear mi entorno de trabajo (vosotros podéis hacerlo como queráis)

La lógica del programa será sencilla, simplemente preguntaremos periódicamente a telegram si tenemos updates, y si los tenemos, miraremos a ver si alguna petición contiene la cadena «password» y si la contiene devolveremos una nueva contraseña generada por nosotros en ese momento. Para recuperar los updates haremos un bucle infinito en donde llamaremos a la función getUpdates y si recibimos elementos los recorreremos uno a uno para tratarlos.

La función para generar una contraseña aleatoria será esta:

def generatePassword(num=10):
    # Generamos una contraseña aleatoria de num caracteres
    letras = string.ascii_letters + string.digits
    password = ''.join(random.choice(letras) for i in range(num))
    return password

Lo primero que tenemos que hacer es dar un valor a nuestro token y establecer la url del api de telegram:

token = 'TOKEN'
url_base = f'https://api.telegram.org/bot{token}/'

Y la función que recupera las actualizaciones será, por tanto esta:

r = httpx.get(url_base + 'getUpdates')
    updates = r.json()['result']

La función para enviar un mensaje a un usuario (o a un chat) se llama sendMessage y recibe como parámetros (entre otros) chat_id y text, así que poniendo todo junto nos quedaría esta función:

while True:
    r = httpx.get(url_base + 'getUpdates')
    updates = r.json()['result']
    for update in updates:
        if 'message' in update:
            message = update['message']
            if 'text' in message:
                text = message['text']
                if 'from' in update['message']:
                    user = update['message']['from']
                    if 'password' in text:
                        respuesta = 'La contraseña es ' + generatePassword(10)
                        r = httpx.post(url_base + 'sendMessage', data={'chat_id': user['id'], 'text': respuesta})
                else:
                    continue 
    time.sleep(1) 

Si ejecutamos esto (y tenemos el token que nos ha dado godfather) ya podremos pedir la contraseña y recibiremos respuesta (al hablar con nuestro bot, claro):

Claro que si dejamos el código así el bot se empeñará en darnos contraseñas una detrás de otra sin parar, y es que telegram no sabe si hemos tratado o no el update que nos ha enviado, para ello hay que usar el parámetro offset para indicar cual será la siguiente actualización a tratar y cambiando el código un poco nos quedaría:

offset = 0
while True:
    r = httpx.get(url_base + 'getUpdates', params={'offset': offset+1})
    updates = r.json()['result']
    for update in updates:
        offset = update['update_id']
        if 'message' in update:
            message = update['message']
            if 'text' in message:
                text = message['text']
                if 'from' in update['message']:
                    user = update['message']['from']
                    if 'password' in text:
                        respuesta = 'La contraseña es ' + generatePassword(10)
                        r = httpx.post(url_base + 'sendMessage', data={'chat_id': user['id'], 'text': respuesta})
                else:
                    continue
    time.sleep(1)

Obviamente nos falta tratar las respuestas incorrectas y los timeouts y demás (y alguna respuesta adicional que nos puede dar telegram si hacemos demasiadas peticiones), pero eso lo dejo para que lo mejoréis en el repositorio https://github.com/yoprogramo/nomorepassbot

El caso Rubiales

Si, se supone que este es un blog de tecnología y no de política y menos de deporte, pero como en agosto hemos estado un poco amodorrados lo más interesante que tengo para hablar es esto… Así que dejaremos aquí mi opinión y ya.

Empecemos por lo más básico: dar un beso no consentido es delito. Lo es en España y lo es en mucho más países donde se considera una agresión sexual, aunque leve. Lo cierto es que muchas veces se trivializa con el tema, o se le quita importancia por herencias culturales que nos dicen que las mujeres están para eso.

Lo que algunos no se explican es cómo la carrera de alguien que gana casi 700.000 Euros al año se puede ir al garete por comportamientos de gañán redomado. Pero no es una cuestión de feminismo o de legalidad, es una cuestión de principios.

A fecha de hoy todavía no se conoce cual será el destino de Rubiales, atrincherado en su cargo, rodeado por su familia y defendido por la recua de desinformadores de la extrema derecha. Lo que está claro, y lo dejó claro en sus declaraciones, es que él no va a dimitir. Lo más normal sería que esperase a que le destituyesen para cobrar las indemnizaciones correspondientes, y mientras, prepara el camino para las inevitables querellas que le van a caer. Lo que si se sabe es que una amplia mayoría de la población no le quiere ver representando a nuestro país en ningún sitio y que la FIFA ya le ha sancionado en ese aspecto. Por lo menos la polarización actual parece que aquí es un poco menos, pero seguimos a la espera de qué harán las «fuerzas vivas» y si dejarán caer a este golfo o le mantendrán como representación de su poder.

La verdad es que este personaje tenía que haber sido cesado hace tiempo por sus corruptelas económicas, pero parece que tenía sostenes muy importantes en el mundo del futbol (no quiero decir quién) y ya se creía como el jefe indiscutible que podía hacer y deshacer a su antojo. Desconozco si es su forma de ser o es que ha tenido alguna alteración hormonal reciente, pero no veo muy normal que se agarre el paquete delante de millones de espectadores y junto a la Reina y la infanta para después plantarle un morreo televisado a una de las jugadoras (que por su orientación sexual tampoco es que lo vaya a apreciar mucho). Imaginaos esto en otros ámbitos fuera del deporte… Que tu jefe te pida un piquito y te lo plante por lo buenos que han sido los resultados trimestrales, que el representante de España en unas negociaciones se toque el paquete celebrando la firma del mismo y que, además, en la emoción del momento agarre a su secretaria y la plante un beso… Inaceptable, ¿no? Pues lo mismo con este sujeto. El futbol puede ser especial, pero no las actitudes de los que representan a un país o de los que tienen un puesto de poder sobre sus subordinados.

Lo que ya hizo este esperpento de persona después de que le pidiesen responsabilidades es de lo más despreciable que se puede imaginar. Presionar a la jugadora y a su familia para que saliesen a defenderle, sacar comunicados falsos haciendo creer que han sido redactados por la víctima, poner su huevos sobre la mesa en un discurso del «no dimito» digno del manual del machista ibérico y, sobre todo, negándose a reconocer que haya hecho algo mal son actos que no deberían quedar impunes.

Varias formas de hacer pruebas de stress a una url

Una vez que hemos programado algo, una web, un api o cualquier otra cosa que van a utilizar muchas personas a la vez nos entra la necesidad inmediata de, después de las pruebas unitarias, poder pasarle unas pruebas de stress para ver cuanta gente puede entrar a la vez antes de que el sistema reviente o, en el peor de los casos, ver si hay algún problema de programación que nos impide tener peticiones concurrentes con seguridad.

Servidor bajo asedio... Imagen generada por AI

Solo os voy a presentar los más sencillos, aunque luego hay herramientas mucho más completas que ya se deben usar en el momento de QA para comprobar si los requisitos del sistema se cumplen (máximo número de peticiones por minuto / curva de carga / evolución del tiempo de respuesta, etc.).. Pero vamos, que ahora mismo solo queremos ver si se puede llamar a nuestro api o web concurrentemente…

siege

Este es el comando más sencillo que he encontrado, pero a la vez es muy eficiente en lo que se puede hacer con él.

siege -c10 -r1 https://blaba.com

Tras un tiempo de ejecución (depende de los parámetros c: concurrencia y r: repeticiones) se recibirá una salida como esta:

{ "transactions": 140,
"availability": 100.00,
"elapsed_time": 3.12,
"data_transferred": 3.48,
"response_time": 0.22,
"transaction_rate": 44.87,
"throughput": 1.11,
"concurrency": 9.68,
"successful_transactions": 140,
"failed_transactions": 0,
"longest_transaction": 0.33,
"shortest_transaction": 0.04
}

Ahí ya podemos ver cual es el ratio de fallo para esa concurrencia y los tiempos máximos y mínimos para cada transacción. Guay…

Ahora bien, el problema viene de que solo podemos hacer peticiones GET (podemos poner las cabeceras que queramos, pero no hay manera de hacer una petición POST, por ejemplo). Así pues si tenemos que probar un API que tiene algo más que peticiones GET tendremos que usar otra cosa…

Sigue habiendo una infinidad de herramientas muy buenas para hacer estas cosas, como Jmeter y otras, pero para unas pruebas de carga básicas vamos a continuar con el camino «barato»

curl + bash

Si estamos en linux, tenemos suerte (el siege también es de linux, pero seguro que hay un port para tu sistema operativo). Apuntad esto:

Primero hay que instalar paralell que no está en la distribución por defecto:

sudo apt install parallel

Luego podremos escribir algo como esto:

seq 100 | parallel --max-args 0 --jobs 10 "./curl_to.sh"

Y en curl_to.sh poned el comando curl con el que vais a probar, por ejemplo este:

curl --location 'https://blabla.com/api/v1/micomando' --header 'Content-Type: application/json' --header 'Accept: application/json' --header 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIU...CvuTuF30k8XEFg_XL-gJyK-8rT4htC7HTHxbtDSGN8' --data '{ "userid": "418043893","message": "Hola payo…..", "uuid": "kk"}'

Este comando de antes lo que hace, básicamente, es lanzar una petición POST con un cuerpo JSON y una cabecera de autenticación (con un token JWT por ejemplo). Este código lo podemos sacar de postman, por ejemplo diciendole que queremos generar código para curl.

Si lanzamos el comando seq, lo que hace es generar una secuencia de números (en este caso de 1 a 100, y paralell lo que hace es lanzarnos de manera paralela (10 cada vez) el comando que hemos especificado… Con esto conseguiremos lanzar 100 veces 10 peticiones paralelas.

Obviamente no es perfecto, pero habremos podido simular una carga paralela sin tener que instalar nada en nuestro ordenador. Si eso lo combinamos con el comando time (os lo dejo como ejercicio) también podríamos sacar estadísticas de las ejecuciones.

En próximas entradas veremos como usar algunas herramientas muy potentes para pruebas de carga, pero por ahora, ya podéis empezar a estresar a vuestro servidor.

La pinza

Uno de los grandes logros en la estrategia militar de los alemanes en la segunda guerra mundial fue utilizar el movimiento de pinza con embolsamiento (no había forma de salir de la misma) para acabar de manera rápida con un enemigo que se creía muy bien guarnecido en su territorio.

Este es un movimiento que ya se describe desde tiempos inmemoriales, Tsun Zu ya lo recomienda, pero sin embolsamiento, dejando una salida al enemigo para que no se sienta acorralado y se vea forzado a una lucha más feroz. Y, diréis vosotros, ¿porque nos habla este ahora de un movimiento militar? Bien, porque este movimiento se utiliza en muchos otros campos, uno de ellos, y de actualidad tras las últimas elecciones municipales, es el campo político.

Yo ya os he dicho que soy más progresista que otra cosa, no me importa decirlo aunque no creo en eso de que haya que odiar al que piensa distinto y odio la polarización en la política y en cualquier otro aspecto de la vida. Estamos asistiendo, no obstante, a un espectáculo vergonzoso de prostitución de la democracia en la que los ciudadanos, por el momento, no podemos más que escandalizarnos y desanimarnos. Perdonadme por poner un post de corte político en medio de mi blog, pero es que es mi blog y es el sitio donde dejo lo que me pasa por la cabeza (sea técnico o no).

Estas elecciones municipales han servido para varias cosas, que se han visto desde la campaña y desde antes, a saber:

  • Los medios de comunicación ya no tienen ningún pudor en mostrar a quién apoyan y a quien quieren silenciar. Eso es bastante normal cuando hablamos de una tertulia política, pero es vergonzoso cuando convierten los programas de entretenimiento en adoctrinamiento. Solo hay que ver el programa de Pablo Motos y sus invitados a quién menos gracioso o el hecho de que el horario de la telebasura del corazón la hayan sustituido por telebasura política con Ana Rosa, por poner dos ejemplos del lavado sistemático de cerebros débiles.
  • No existen medios de izquierda ni que se le parezcan. La falsa sensación de que la sexta, por ejemplo, era una televisión «friendly» con la izquierda se ha tornado, como no podía ser de otra manera, en la constatación de que tienen patrones a los que contentar y sus directivos no tienen ningún escrúpulo en participar de las cloacas o de invitar a «hostiles» y silenciar a los «amigos» de la izquierda más irreductible.
  • La derecha no tiene escrúpulos y no hace prisioneros. Lo hemos visto en la defenestración de casado, en el uso de «medios» mentirosos y difunde bulos (okdiario, EDA, etc.) siendo pagados sin rubor con dinero público y compitiendo en quien decía la mentira más gorda (ya sabes, si alguien dice que es mentira es que es un zurdo intentando coartar mi libertad de expresión) y siempre es un buen momento para reirse de los progres.
  • No hay aliados entre los que piensan igual que tu. Sumar, que podría parecer una buena idea a priori, se ha esforzado muy mucho en desligarse de Podemos desde el primer momento. Yolanda Díaz, que está en el gobierno gracias a Podemos, que ha disfrutado de libertad y apoyo constante para su labor de gobierno ha decidido que no quiere cargar con el peso de un partido ya formado, que prefiere alimentarse de estructuras más tradicionales, desmembrarlas y someterlas a un único objetivo: volver a sentarse en un gobierno.
  • Nadie se lee los programas… Votan lo mismo al defensor de los toros y azote de los menas que a la que va de bar en bar sirviendo cervezas y sin encontrarse con el ex-novio. ¿Programa? ¿Para qué? Ya se que me roban, pero yo nunca permitiría que me robase un comunista (Álvaro Ojeda dixit)

Esto ha generado una terrible pinza donde, si te unes a Sumar renuncias a cualquier posibilidad de apoyar políticas más sociales (nada que no haría el PSOE) y, además, sabes que te van a mirar mal como el radical que ya no sabe donde meterse. Si no te unes eres un traidor a la izquierda y solo piensas en tu orgullo y les vas a hacer perder votos. Por otro lado tienes a la derecha lobotomizadora que te está diciendo que no merece seguir votando, total, ellos ya han ganado y «la resistencia es futil», mejor dejar de resistirse y, en todo caso, votar al PSOE (que es lo que Pedro Sanchez ha dado por descontado que hará la mayor parte de la gente como voto útil visto lo que provoca la fragmentación del voto).

Lo que más me duele es que gente aparentemente culta e inteligente como Pedro Vallin van por ahí insultado, ninguneando y mofándose de los que han construido el modo de política que ahora pretenden aprovechar para hacerse un Carmena. Lo que ellos no saben es que, al igual que matar la sanidad pública hará que la privada lo pase muy mal (no está preparada para tener que hacerse caso de las cosas costosas), matar a Podemos hará que la izquierda vuelva a lo de siempre, pero de mal humor. Sumar será la nueva Izquierda Unida (¿Similitudes en la intención del nombre?) y PSOE volverá a aglutinar el voto no de derechas.

Internacionalizando javascript en symfony

En este nuevo desafío que he asumido recientemente me encuentro con tener que mantener y mejorar aplicaciones web que están desarrolladas sobre symfony 5. Este es un framework PHP (si, php, no me matéis) que tiene sus cosas buenas y sus cosas malas. Una de las buenas es que permite internacionalizar las aplicaciones sin demasiados problemas… Al menos la parte de html.

Symfony utiliza un sistema de plantillas twig que es donde se pueden realizar las traducciones poniendo {%trans%}cadena{%endtrans%} y luego simplemente tenemos que mantener los archivos de traducción de cada lenguaje y ya toda la magia sucede internamente y sin problemas.

Imagen generada por AI cuando le pedí que me hiciese una imagen para un post de programación

Pero, ¿Qué pasa con las cadenas que generamos en javascript? Los archivos .js se sirven estáticamente sin que pasen por el proceso de sustitución que se hace en los twig, no nos vale poner {%trans%}cadena{%endtrans%} en el js porque ese archivo nunca va a ser tratado por symfony ni por php ya puestos.

La solución que he terminado por implementar se basa en generar un archivo .js con las traducciones en base a una plantilla twig y cargarla cada vez que necesite traducir algo en un js. Para ello solo tenemos que dar estos pasos:

  1. Crear la ruta en routes.yml (o usar un decorador) para /jstrans.js o similar
  2. Crear un controlador para servir ese archivo, por ejemplo JavascriptController.php
  3. Crear un twig para el javascript, por ejemplo jstrans.js.twig
  4. Meter en el twig todas las cadenas y sus traducciones correspondientes
  5. Hacer que jstrans.js se cargue en todas las páginas que necesiten traducciones
  6. usar la función que traduce en los js que lo necesiten.

Aquí os dejo un ejemplo de cada uno de los archivos:

JavascriptController.php

class JavascriptController extends AbstractController
{
    public function __construct()
    {
        $this->config = new Config();
    }

    public function index(): Response
    {
        $response =  $this->render('javascript/jstrans.js.twig', [
            'controller_name' => 'JavascriptController',
        ])->setSharedMaxAge(3600)->setPublic();
        $response->headers->set('Content-Type','text/javascript');
        return $response;
    }
}

jstrans.js.twig

if (typeof jstrans == 'undefined') {

    var jstrans = {
        traducciones: {
            'Seleccionar': '{%trans%}Seleccionar{%endtrans%}',
            'Registros': '{%trans%}Registros{%endtrans%}',
            'No se han encontrado resultados': '{%trans%}No se han encontrado resultados{%endtrans%}',
        },
        // Función que traduce un texto
        translate: function (text) {
            if (jstrans.traducciones[text] != undefined) {
                return jstrans.traducciones[text];
            } else {
                return text;
            }
        }
    };
} else {
    console.log ('jstrans ya está definida');
}

Luego ya se meten las cadenas en, messages.en.yaml,, por ejemplo

Seleccionar: Select
Registros: Registers
No se han encontrado resultados: No results found

Y luego solo hay que usarlo en los archivos js de esta manera:

jstrans.translate("Seleccionar")

Y eso te dará la cadena traducida al idioma en el que se esté navegando.

¿Conoces algún método mejor para conseguir traducciones en los js de un proyecto symfony? déjame un comentario si es así…