Discord a FreshRSS

Nacho - - Tiempo de lectura 6 mins

Discord es una plaga horrible. Está por todos lados, y no entiendo por qué lo usan tanto. Si pudiese no volver a usarlo sería feliz. Bueno, menos infeliz, al menos. El tema es que, lamentablemente, hay cosas que están en Discord que son interesantes.

Una de esas son los puzzles GAPP (“genuinely approachable pencil puzzles”) del Discord de Cracking The Cryptic. No tengo muy claro qué es CTC, si es un podcast o un canal de YouTube, o qué es, pero tienen una comunidad bastante activa en Discord, y entre otras cosas tienen un canal de “daily puzzles” donde postean esos “GAPP”: puzzles de lápiz y papel variados. En general el nivel de dificultad es bajo, pero para hacer uno por día e ir saltando entre distintos géneros está bueno.

El problema, claro, es Discord. Si quiero ver esos puzzles todos los días no sólo tengo que estar en el server y seguir el canal, sino que tengo que entrar a Discord todos los días a mirar. Y no quiero.

FreshRSS al rescate

Como mencioné hace poco, FreshRSS tiene la opción de crear un feed a partir de una página web. Lamentablemente, la web de Discord es uno de esos adefesios imposibles de leer sin javascript, y el parser HTML básico de FreshRSS no ejecuta javascript.

Pero también está la otra opción: leer desde un JSON. Mirando la página de Discord, vi que los mensajes más recientes de un canal los lee de un endpoint https://discord.com/api/v9/channels/{channel_id}/messages?limit=11 que devuelve un JSON. Probé con curl, y pasándole mi token de autenticación ya funciona.

El JSON que devuelve es un array de objetos, con la siguiente estructura:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
[
// ...
  {
    "type": 0,
    "content": "Texto del mensaje",
    "mentions": [],
    "mention_roles": [],
    "attachments": [
      {
        "id": "...",
        "filename": "image_-_2026-06-07T081630.317.png",
        "size": 52619,
        "url": "https://cdn.discordapp.com/...",
	     // ...
      }
    ],
    "embeds": [],
    "timestamp": "2026-06-07T15:20:11.268000+00:00",
    "edited_timestamp": null,
    "flags": 0,
    "components": [],
    "id": "9999999999",
    "channel_id": "888888888888888888",
    "author": {
      "id": "428585786120601600",
      "username": "autor",
      // ...
    },
	"reactions": []
  },
// ...
]

Hay un montón de información que no me importa ahí, y la mayoría la saqué, pero con lo demás puedo armar un feed.

Algo importante a tener en cuenta si vas a hacer algo similar: no lo dice en el formulario, ni en ningún lado, pero es obligatorio poner algo en el campo de link. No tiene que ser una URL válida, pero no puede estar vacío. Si dejás el campo vacío, o si lo que ponés resuelve a un valor vacío, FreshRSS ignora ese item silenciosamente. Así que poné algo.

Eso funciona, hasta ahí nomás. Pero tiene algunos problemas:

  • El timestamp como título es horrible
  • El link no existe, obviamente
  • FreshRSS interpreta el texto del mensaje como HTML, o sea que se pierden todos los saltos de línea y queda un bloque de texto re feo.
  • Otra consecuencia es que los links no son clickeables.
  • A veces (y no sé de qué depende), los links vienen entre <> y entonces FreshRSS no los muestra, ni clickeables ni no clickeables.
Screenshot de un post, con los problemas

Casi funciona, pero no es lindo

Puedo vivir con los cuatro primeros, pero el último es un problema. El objetivo de esto es poder hacer los puzzles sin tener que abrir Discord, y para eso necesito el link.

Lo que necesito es modificar los datos que vienen en el JSON, pero FreshRSS no puede hacer eso, lo único que hace es extraer los datos. Si el dato que necesitás no está, o necesitás manipularlo, bueno, mala suerte.

Entonces, lo que hay que hacer es modificar el JSON antes de que le llegue a FreshRSS.

Esto es un trabajo para Node RED

Node RED es un “entorno de programación low code”. ¿Qué significa? Que podés hacer scripts y aplicaciones sencillas con un par de clicks. Lo que hacés es armar un flujo con bloques visuales, y vas pasando la información de uno a otro, y en cada bloque la podés manipular o guardar en distintos lugares. Tenés bloques para leer datos de bases de datos, o escribir, o montones de cosas. Para cosas sencillas, puede llegar a rendir, pero no le pidas mucho porque se empieza a complicar el diagrama.

En general no es mucho mejor que un poco de bash o python, pero hay un caso en el que me resulta: crear un endpoint HTTP que sirva de “proxy” con otro endpoint y aplique un poco de transformación. Ideal para este caso. Si lo quisiera hacer con bash y/o python tendría que meter un servidor web o algo así, y bueno, Node RED ya lo tengo corriendo.

Screenshot de Node RED

Este es el script que hice

El proceso es bastante lineal. Primero, los puntos de entrada: tiene un endpoint HTTP, que si llega un request empieza a procesar, y también un “inject” para largar un proceso de prueba. De ahí pasa por los siguientes bloques:

  1. Hacer un request HTTP a la API de Discord
  2. Interpretar el texto recibido como JSON
  3. Separar el array JSON, de forma que cada elemento sea procesado como un mensaje separado
  4. Aplicar transformaciones. Acá pasa todo lo importante.
  5. Volver a unir las partes en un array.
  6. Salida HTTP: responde el request inicial con los datos procesados.

Además de eso, hay un bloque de “debug” que muestra lo que recibe, para poder ver qué está pasando.

Screenshot de las reglas en Node RED

Los cambios que aplico son bien simples:

  1. Agrego un campo permalink, con un link al mensaje (es concatenar la URL del canal con el id de mensaje)
  2. Un campo title con un título legible
  3. Reemplazo todos los < por &lt;, para que se lean.
  4. Hago los links clickeables
  5. Inserto elementos <br> en donde hay saltos de línea.

Luego de eso, cambié los campos de la configuración de FreshRSS, y pronto.

Screenshot de FreshRSS con un puzzle de GAPP

Y listo, ya puedo ver los links a los puzzles en FreshRSS.

Pero al final es casi al pedo todo

Resulta que después de haber hecho esto, pero con este post a medio escribir, la gente del GAPP hizo un sitio web donde van a publicar todos los puzzles. Por supuesto, con RSS. La única contra es que parece que los van a publicar con una semana de demora, pero bueno. Igual, el método se puede usar para otros canales, así que puede servirle a alguien.


Idealmente, estaría bueno tener esas capacidades de transformar el JSON de entrada en FreshRSS en lugar de tener que usar una herramienta externa. Node RED usa JSONata para aplicar las transformaciones. Si FreshRSS soportase eso nativamente, sería genial, mucho menos problema. Pensé en hacer un pull request, pero resulta que no parece haber una librería de JSONata en PHP (que esté activamente mantenida, hay un par que no), y no “hablo” suficiente PHP como para implementarlo yo. Y bueno, otra vez será.