Lobstersgram, un cliente rápido de lobste.rs en Telegram

Lobsters (lobste.rs) es probablemente mi fuente principal1 de lecturas técnicas: es una curación hecha por humanos que prioriza la calidad de los contenidos, sin clickbaiting ladino, publicidad abusiva o comentarios hostiles. Si bien no soy miembro (requiere una invitación), comulgo fuertemente con su espíritu de la "vieja internet".

Ayer, mayormente desde ChatGPT en el teléfono, hice Lobstersgram, un bot que funciona como un cliente rápido de lobste.rs en Telegram.

Cada cierto tiempo, si el bot detecta que hay artículos nuevos publicados en el sitio, te manda un mensajito con una intro y un link del artículo copiado a telegra.ph, además del link a la fuente original y al hilo de discusión en el foro. ¿Por qué republicar en telegraph? Porque soporta el modo instant view, que permite precargar el contenido con un estilo simple y unificado, ideal para leer desde el móvil.

Si tenés Telegram, podés probarlo en @lobstersgram_bot

Comandos:

  • /start para suscribirte
  • /unsubscribe para dejar de recibir

(Atención, solo funciona cada 2 horas, así que eventualmente te va a responder, pero no esperes inmediatez)

Acá una demo que grabé capturando la pantalla del celu (con notificación de un mensaje al grupo familiar incluida)

Buscando una mejor experiencia de lectura

Lobsters ofrece un RSS de tipo "agregación" que contiene los links a los artículos que se comparten, pero no incluye el contenido de cada uno. Entonces no hay mucha diferencia entre un lector RSS tradicional y ver el sitio directamente en la web, que es muy sencilla, donde básicamente clickeás en los títulos de los artículos que te interesan.

Si bien la mayoría de las veces son artículos de blogs que no tienen publicidad y son muy livianos, igual la experiencia de usuario tiene el costo cognitivo de leer en distintos estilos y tener que ir y volver entre las fuentes y lobsters. Por otro lado, para las conexiones malas que aún se tienen por estos lares (por ejemplo, cuando estás de viaje por la Patagonia, como yo ahora), no viene mal que las lecturas ya estén disponibles, cualesquiera sean las condiciones.

Lobstersgram mejora esta experiencia de usuario principalmente para leer desde el teléfono.

El flujo de lo que hace es el siguiente:

  • lee el RSS de Lobsters
  • de cada link extrae el contenido principal, o sea un "scraping dinámico" 2.
  • Convierto el html a markdown
  • Con el markdown hace un DOM de telegra.ph para crear un post programáticamente, creando la copia del artículo original.
  • Con ese link, recortando un cachito del markdown y el resto de las urls (original + foro) sacadas del RSS, mando un mensaje a cada suscriptor del bot.

Mirá mamá, sin server y sin db (y gratis!)

Los bots de Telegram funcionan vía webhooks (el server hace un "push" del mensaje en tiempo real) o long polling, que es la que nos interesa: en vez de consultar constantemente si hay nuevos mensajes, el bot hace una petición al servidor que queda abierta un ratito esperando hasta que llegue un update o se acabe el tiempo, e inmediatamente o, cuando puede, vuelve a pedir otra.

GitHub Actions nos da un server básico (como dicen en Tiranos Temblad, ¡Que viva lo gratis!) que alcanza lo más bien para correr un programita sin muchas pretensiones por pocos minutos. Y nuestro bot es eso: un script Python que no demora demasiado.

La acción que dispara el workflow es un "cron" (schedule, en la jerga de github actions) y entonces cada 2 horas el bot se fija si tiene que actualizar la lista de susbcriptores (se agregaron / desagregaron) y si hay historias que enviar.

Para que la cosa funcione tambien hace falta una minima persistencia de datos. ¿De donde va a sacar el bot la lista de usuarios que quieren recibir las novedades? ¿Y cuando se lee Lobsters, como sabe cuales articulos ya envió en corridas anteriores?

Para eso hay un state.json y un subscribers.json cuyo fin, espero, no requieren explicación.

Telegram no te deja enviar mensajes a chats que nunca interactuaron con el bot. Por eso el flujo es:

  1. el usuario manda /start
  2. el workflow corre en modo --sync-subscribers
  3. se agregan los chat_id en subscribers.json
  4. en el modo normal se manda cada post a esos chats

Si alguien quiere dejar de recibir, manda /unsubscribe y en la proxima corrida se lo elimina.

Acá un diagramita que resume el asunto:

Tanto state.json como subscribers.json se autocommitean (si cambiaron) dentro del workflow desde GitHub Actions. Esta idea de usar git como storage de datos está un poco inspirada en la técnica de Git scraping.

Ejemplos de uso del CLI:

uv run python main.py --sync-subscribers
uv run python main.py --max-items 1
uv run python main.py --url https://mgaitan.github.io/posts/ampliando-el-universo-python-con-rust/

El modo --url es interesante porque procesa una o más urls arbitrarias por fuera del RSS. Cuando el workflow de GitHub Actions se lanza manualmente (o vía API), se puede pasar una lista de urls y correrá en este modo, que llega igual, pero obviamente sin el link al foro:

image

De paso, si querés forzar que los mensajes te lleguen solo a vos cuando estás desarrollando, exportá esta variable de entorno que tendrá prioridad sobre el json:

export TELEGRAM_DEV_CHAT_ID=123456789

¿Y qué sigue?

Probablemente nada, pero el repo está en mgaitan/lobstersgram y es todo tuyo para hacer un fork. El convertidor de "markdown to telegra.ph" necesita algunas mejoras y, como me pidió mi amigo Alvar Maciel, el archivo subscribers.json deberia estar encriptado (lo podemos hacer via sops) para no exponer ninguna data.

En cuanto a funcionalidades potenciales, fácilmente se podría permitir que los subscriptores definan la lista de feeds que quieren consumir, haciendolo un lector de RSS/Atom en Telegram general.

También podría funcionar como una app "read it later" minimalista y/o una comunidad de lectura: le enviamos una url al bot y este, cuando corre, procesa todo lo que le llegó en modo --url.

¿se te ocurre algo más?


  1. Otras partes de mi dieta informativa ñoña son el blog de Simon Willison y los "trending" de Github

  2. Probé trafilatura pero me quedé con el viejo readability porque daba mejor resultado. 

Comentarios

Comments powered by Disqus