Archive for the ‘Devlogs’ Category

Mi investigación sobre cómo se programa un feed de Bluesky

jueves, noviembre 28th, 2024

Esto no es un sustituto para la «maravillosa» experiencia que resulta de leer la documentación oficial de ATProto y Bluesky, solamente quiero dejar anotado lo que he aprendido para poder revisarlo en el futuro


¿Cómo sabe Bluesky cuál es el algoritmo que proporciona la inteligencia de un feed?

Bueno, la respuesta parece ser que un feed de Bluesky no es más que un servicio HTTP con el que Bluesky interactúa usando la API RPC con los endpoints definidos en el protocolo ATProto.

Entonces, «crear» un feed es exponer a través de un servicio web esos endpoints y prepararlos para que Bluesky pueda lanzarle peticiones cuando una persona consulta el feed.


¿Y cómo sabe Bluesky cuál es el servicio web que debe usar?

Cuando «creas» un feed, es decir, cuando lo haces público para que salga en tu perfil y para que «exista» para Bluesky, hay que darle un parámetro que codifica el hostname. De modo que cuando alguien intenta consultar tu feed, Bluesky usa el hostname para saber a qué servidor hacer las peticiones HTTP que devuelven los datos del feed y así «usar tu algoritmo».

Ejemplo: el feed Linux está creado por @mackuba.eu. El DID de esta cuenta es did:plc:oio4hkxaop4ao4wz2pp3f4cr. Si uso el endpoint RPC com.atproto.repo.listRecords para pedirle todos los app.bsky.feed.generator creados por esta cuenta, el array JSON incluye el feed Linux.

Petición RPC al endpoint com.atproto.repo.listRecords en el PDS bsky.social
https://bsky.social/xrpc/com.atproto.repo.listRecords?repo=did:plc:oio4hkxaop4ao4wz2pp3f4cr&collection=app.bsky.feed.generator

El campo did de este record tiene como valor did:web:blue.mackuba.eu. Eso significa que el feed está asociado con el hostname blue.mackuba.eu y que cuando quieras consultar el feed Linux, Bluesky le tiene que tirar las peticiones a https://blue.mackuba.eu.

La API de un feed

Leyendo la documentación del generador que he consultado, un servicio web que sirva feeds de Bluesky debe implementar tres métodos:

  • /.well-known/did.json: este tiene que validar que, efectivamente, ese servicio web es el correcto, para evitar impersonaciones.
  • /xrpc/app.bsky.feed.describeFeedGenerator: este es el que devuelve información de los feeds que se sirven desde ese servicio.
  • /xrpc/app.bsky.feed.getFeedSkeleton: este es el que devuelve el contenido del feed, para que los posts se puedan ver. Va paginado.

ericvolp12/go-bsky-feed-generator es un generador de feeds hecho por @jaz.bsky.social que está programado en Go. De aquí es de donde he sacado esta información. Todavía no lo he clonado para ver si es tan sencillo como tomar la plantilla, programar los algoritmos, y dejar que el proyecto levante el servidor web.

did.json

El endpoint de DID parece confirmar que estamos en la ubicación correcta. Imagino que validar el feed quiere decir verificar que no estás usando otro hostname diferente o que alguien se ha equivocado al poner la URL del servidor. No tengo ni idea del contexto de este endpoint.

Petición al endpoint did.json de blue.mackuba.eu.
https://blue.mackuba.eu/.well-known/did.json

app.bsky.feed.describeFeedGenerator

El endpoint app.bsky.feed.describeFeedGenerator devuelve la lista de feeds servidos desde ese servicio web. La documentación con los tipos del objeto devuelto están en la API de Bluesky.

Petición RPC al endpoint app.bsky.feed.describeFeedGenerator de blue.mackuba.eu.
https://blue.mackuba.eu/xrpc/app.bsky.feed.describeFeedGenerator

app.bsky.feed.getFeedSkeleton

El endpoint app.bsky.feed.getFeedSkeleton es el que devuelve los datos de un feed. Si le pido sin más que me hable, da un error HTTP 500, aunque supongo que esto depende del servidor.

Petición RPC al endpoint app.bsky.feed.getFeedSkeleton de blue.mackuba.eu.
https://blue.mackuba.eu/xrpc/app.bsky.feed.getFeedSkeleton

Eso es porque según la documentación de ese endpoint, le tengo que poner como queryparam feed para indicarle el record del feed que quiero que me devuelva. El feed tiene que estar en formato URL de protocolo ATProto, es decir, at://[did]/app.bsky.feed.generator/[slug], que para el feed «Linux» es at://did:plc:oio4hkxaop4ao4wz2pp3f4cr/app.bsky.feed.generator/linux, siendo el DID de la cuenta de mackuba. En cuanto hago eso, el feed empieza a hablar.

Petición RPC al endpoint app.bsky.feed.getFeedSkeleton de blue.mackuba.eu con un feed como parámetro.
https://blue.mackuba.eu/xrpc/app.bsky.feed.getFeedSkeleton?feed=at://did:plc:oio4hkxaop4ao4wz2pp3f4cr/app.bsky.feed.generator/linux

El formato de un feed tiene el siguiente tipo (voy a usar una interfaz de TypeScript por simplicidad):

interface Feed {
  cursor: string;
  feed: Array<{
    post: string;
  }>;
}

Uno de los campos de retorno es cursor, que es el que permite paginar el feed. Es un paginador un poco complejo porque como el feed se puede actualizar en tiempo real a medida que se va paginando si se agregan o se borran posts, se recomienda que lleve también un timestamp para asegurarse que se sincroniza bien.

El otro es feed, que devuelve un array de objetos con los IDs de cada post. El feed no porta el contenido de los posts, solamente sus IDs externos. Tú devuelves la URL en protocolo ATProto de un post, y ya se ocupa Bluesky de recuperar por separado el contenido de cada post a partir de la propia API del PDS.

Voy a hacer la prueba tratando de resolver desde el PDS https://bsky.social uno de los posts del feed, en este caso, at://did:plc:gxt7mot2ujgovitv6j4eo7n4/app.bsky.feed.post/3lbyn6inlds22. Puedo sacarlo mediante el endpoint RPC app.bsky.feed.getPosts. Como este endpoint requiere autenticación, voy a usar https://public.api.bsky.app, que permite acceso anónimo, para no tener que aprender ahora a crear tokens.

Petición RPC a app.bsky.feed.getPosts para este endpoint y este PDS.
https://public.api.bsky.app/xrpc/app.bsky.feed.getPosts?uris=at://did:plc:gxt7mot2ujgovitv6j4eo7n4/app.bsky.feed.post/3lbyn6inlds22

Estrategias para fabricar un algoritmo

Si quiero programar un feed estático, puedo devolver hardcodeado el array con las URLs ATProto de los posts que quiero que porte. Sin embargo, imagino que la gracia está en prestar atención a JetStream o al firehose de Bluesky directamente, que es el websocket que te trae en tiempo real los posts a medida que se publican. Para mirar a la cara a este websocket sin usar comandos de consola se pueden usar las siguientes herramientas web:

Cuando encuentre un post en el websocket que concuerde con el criterio de mi algoritmo, puedo anotar su ID para poder devolverlo más adelante cuando se pidan datos del feed. Se recomienda también descartar los posts más antiguos para no devolver un feed muy grande.

Siguientes puntos

La siguiente pregunta que me queda por hacer es cómo funciona la autenticación, porque algunos feeds son anónimos ya que solo tienen que devolver datos del firehose público. Sin embargo, si quiero filtrar para que muestre información relacionada con mi cuenta (por ejemplo, posts de la gente que sigo o posts de la gente que me sigue), supongo que el feed necesita una forma de saber quién soy. Esto es algo que dejo para investigar otro día.

Modelos experimentales de microblog

viernes, octubre 25th, 2024

Últimamente le ando dando vueltas a la idea de si la forma en la que se mantienen los blogs de notas es la óptima. Me refiero a cuando publicas en tu blog o en tu propia web un mensaje de texto corto y plano que cabría perfectamente en un tweet, pero que publicas en tu blog para poseer el control de él y no regalárselo a un multimillonario cabrón. (También me vale en un post de Mastodon o en un skeet de Bluesky).

(más…)

El widget de cabeceras HTTP por fin está hecho

miércoles, abril 10th, 2024

Como dicen que una imagen vale más que mil palabras, os quiero enseñar una cosa.

Efectivamente, el widget de cabeceras HTTP por fin está programado, lo que significa que por fin se puede completar toda la información necesaria para tirar una petición HTTP mínima con Cartero. Dejad que os cuente cómo funciona por dentro.

(más…)

Qué ha pasado estos días en Cartero

miércoles, abril 3rd, 2024

Me disculpen que no haya anotado por aquí lo que ha pasado en Cartero en los últimos días. Estuve un poco de vacaciones. Pero bueno, este es un resumen de lo que se ha incorporado al repositorio y ha pasado en el streaming en los últimos días.

  • El viernes en stream se empezó a implementar el cliente HTTP como tal. He decidido utilizar la biblioteca isahc. No tengo ninguna preferencia ni odio reqwest. Sin embargo, me parecía más idiomática y además me parecía buena idea probar cosas nuevas para experimentar. (Reqwest la tengo más tocada.)
  • Entró un pull request de @claufedacosta que arregla la interfaz de usuario. Estoy muy contento con este cambio y muy agradecido. Pone la barra de URL donde estaba antes, para que no sea difícil mover la ventana, y además la hace responsiva, o sea que si ahora la ventana se hace más estrecha, se apilan las partes de la aplicación una encima de la otra.
  • Anoche empecé a trabajar en el soporte para Flatpak y metí un manifest básico que por el momento funciona y compila la aplicación. He agregado comandos al README para explicar cómo generar este Flatpak con las herramientas de desarrollo instaladas.

Y esto sería todo, mucha calma. Dejo un patallazo de cómo está la aplicación en este momento.

Un pantallazo de cómo se está viendo en este momento la aplicación.

Compilación en Windows

viernes, marzo 29th, 2024
Un pantallazo de Cartero ejecutandose en Windows.

No es bonito el proceso todavía, pero compila en Windows. He utilizado rustup-init.exe y la toolchain stable-x86_64-pc-windows-gnu y estoy instalando las bibliotecas de GTK a través de MSYS2. La gracia está en que un usuario de Windows pueda simplemente descargar y ejecutar, para hacerlo atractivo como alternativa. Sobre que el ejecutable pese 124 MB, supongo que habrá que hacerse preguntas algún día.

Cartero ya está hecha en Meson

jueves, marzo 28th, 2024

Este es un resumen de lo ocurrido en el tercer stream de desarrollo de Cartero, así como los commits que le he tirado hoy aprovechando que es festivo y que Meson al fin y al cabo sabía de antemano que iba a ser algo aburrido de integrar que no valía la pena hacer en vivo. Las cuatro horas de stream de ayer (yo prometí que el stream duraría hora y media, pero ciertamente volví a fallar) se pueden ver aquí.

  • Se ha migrado el código de la ventana y la aplicación a una clase propia.
  • Primeros pull requests integrados, este proyecto ya tiene más contributors.
  • El proyecto ya tiene icono, aunque sea provisional.
  • Ahora se usa meson para compilar el programa, y eso incluye más cosas.
(más…)

Cartero va tomando forma

lunes, marzo 25th, 2024

Resumen del stream del viernes para quien se lo perdiese. En el stream del viernes se continuó con el desarrollo del clon de Postman que he empezado a escribir en Rust. Estos son en resumen los cambios:

  • El programa ya tiene nombre. En el chat el otro día se propusieron varias palabras, y he de decir que me gustó bastante «Cartero», por lo que va a adoptar este nombre.
  • El programa ya tiene repositorio online. Debido a la naturaleza de este proyecto y visto que la gente quería contribuir a él, lo he publicado en GitHub.com. En los últimos días ya he visto varios forks y sorprendentemente hay gente tirando código.
  • El prototipo de la interfaz de usuario ya está casi completo con las cosas que querría proponer para la primera iteración. En el stream del viernes casi todo el tiempo se fue en preparar lo que podría ser un widget para poner las cabeceras HTTP.
(más…)

Mi primer prototipo con gtk-rs (ahora sí)

jueves, marzo 21st, 2024

En el stream de ayer hice la primera compilación del cURL gráfico que he empezado a desarrollar. Por ahora no quiero que sea muy sofisticado y vamos a empezar suavemente. La aplicación por ahora debería mostrar un campo de texto para poner la URL, un dropdown para elegir el verbo HTTP de la petición (por ejemplo, POST o GET), una tabla para introducir las cabeceras HTTP de la petición, un campo de texto para el cuerpo de la petición HTTP, un botón para tirar la petición HTTP y un campo de texto donde ver la respuesta de la petición HTTP.

Aunque acabará ocurriendo, el reto por ahora va a ser ver hasta cuánto puedo avanzar en el desarrollo sin instalar GNOME Builder ni crear un proyecto auténtico al estilo GNOME moderno, con su meson.build y su parafernalia. Por el momento he creado un proyecto a mano usando cargo new y luego he agregado gtk4 como dependencia usando cargo add gtk4.

Para meter el cuerpo de la petición, me interesa usar un GtkSourceView, porque quiero que se pueda colorear en caso de que se utilice XML o JSON, así que también lo meteré.

(más…)

Primeros pasos creando blueprints con GNOME Workbench (resumen del stream de ayer)

jueves, marzo 21st, 2024

Este post forma parte de la saga dedicada a la creación de una alternativa verdaderamente libre (o sea, GNU GPL) a Postman, Insomnia y Bruno. A su vez, esto es un resumen de texto de lo que hice en un stream de livecoding anterior. Así si te lo perdiste, es fácil de leer. Principalmente, lo que voy a contar aquí es cómo utilizar Blueprint y el lenguaje de diseño. Es un post que sirve como referencia y que estaré enlazando más adelante.

En el stream de ayer, después de contar la razón por la que quiero empezar a crear una aplicación de este estilo, empecé a fabricar el prototipo de una interfaz de usuario con GNOME Workbench. Esta aplicación permite diseñar una ventana y verla en tiempo real, para poder iterar más rápido y sin tener que recompilar código.

(más…)

Nice to haves y funciones aptas para un MVP

jueves, marzo 21st, 2024

Por último, voy a describir algunas funciones que estaría bien ponerle a mi cliente HTTP, y cuáles vale la pena implementar al principio y cuáles para más adelante. O incluso cuales puede que nunca implemente.

Por el momento me interesa que mi cliente haga lo mínimo esencial para por lo menos empujar el proyecto para adelante. Es decir, me interesa:

  • Que se pueda introducir la URL a la que tirar la petición.
  • Que se pueda seleccionar el método HTTP a utilizar (GET, POST…)
  • Que se pueda ponerle un body a las peticiones web (para enviar datos).
  • Que se puedan elegir las cabeceras HTTP a utilizar.
  • Que se pueda visualizar la respuesta y las cabeceras de la misma.
(más…)