Cómo construí un backfill de sindicación del blog
He estado construyendo una app Rails, blog-manager, para rastrear y sindicar publicaciones entre vdaluz.com, Medium y LinkedIn. Después de lograr que funcionara la integración con Medium, me topé con un problema práctico: ya existían 72 publicaciones en Medium de las que la base de datos local no sabía nada. Todo tenía medium_status: not_imported. Antes de escribir código nuevo de publicación, necesitaba sincronizar el estado local.
El callejón sin salida del RSS
El primer intento usó el feed RSS en medium.com/feed/@vdaluz. El servicio Medium::PublishedBackfill lo consultaba y emparejaba publicaciones por URL o por título. Funcionaba, pero solo devolvía 10 publicaciones. Medium limita el RSS público a 10 elementos. Había 72.
Yendo más profundo con GraphQL
Medium tiene una API GraphQL no documentada en medium.com/_/graphql. Las sesiones con inicio de sesión pueden consultar latestPostsConnection(type: POST_TYPE_PUBLIC), paginada mediante cursor. Usé una superficie de navegador ya con sesión iniciada en Medium para correr consultas autenticadas y recorrer las 72 publicaciones en tres solicitudes (25+25+22).
La respuesta devolvía el id, title, firstPublishedAt y previewImage.id de cada publicación, exactamente lo necesario para el backfill.
Coincidencia de títulos y trampas de Unicode
Emparejé publicaciones con registros locales en dos niveles: primero por medium_url (publicaciones ya vinculadas), después por título normalizado. El segundo nivel topó con un problema inesperado: Medium guarda los apóstrofes como comillas curvas Unicode (U+2019), mientras que el frontmatter en markdown usa apóstrofes ASCII rectos (U+0027). Títulos como “What’s Next” con comilla curva no coincidían con “What’s Next” con una recta.
La solución fue una función de normalización que convierte las comillas curvas en rectas antes de comparar. Después de eso, 69 de las 72 publicaciones coincidieron sin problema. Los 3 casos fallidos eran publicaciones muy antiguas con títulos sustancialmente distintos entre plataformas.
La trampa del stdin de kamal
Escribí un script de Rails runner para hacer las actualizaciones e intenté correrlo con bin/kamal app exec --reuse "bin/rails runner -" < script.rb. Salió con código 0, tardó unos 6 segundos, y no hizo absolutamente nada. El script corrió pero con stdin vacío, el app exec de kamal no reenvía el stdin del shell que lo invoca hacia el contenedor.
La solución: hacer scp del script al host, docker cp hacia el contenedor, y después docker exec directamente. Obvio en retrospectiva, molesto de descubrir.
Imágenes destacadas en vdaluz.com
Con hero_image_url completo para 69 publicaciones (URLs del CDN de Medium), el siguiente paso era llevar esas imágenes al sitio Astro de vdaluz.com. Descargué las 82 imágenes (72 publicadas + 14 programadas, menos 4 superposiciones), generé variantes .webp, agregué el frontmatter heroImage a los archivos markdown correspondientes, y actualicé tres plantillas de Astro para mostrarlas: banner a todo lo ancho en las páginas de publicación, miniatura en las tarjetas de listado, miniatura en las tarjetas de lectura relacionada.
El campo heroImage ya estaba conectado a las etiquetas meta de OG. Simplemente no se estaba mostrando visualmente en ningún lado. Esa fue una observación de una línea que terminó en una actualización de tres plantillas.
Lo que hay en la base de datos ahora
Después del backfill: 69 publicaciones con medium_status: published, 14 con medium_status: scheduled (extraídas de la interfaz de Medium), 2 sin coincidencia (discrepancias de título antiguas). El servicio DraftCreator ya lee hero_image_url al construir borradores de Medium, así que las publicaciones nuevas incluirán su imagen destacada automáticamente en cuanto esa columna esté completa.
Lecturas relacionadas
Construyendo la publicación cruzada a Medium para blog-manager
Un botón para publicar en Medium sobre una API obsoleta pero funcional: Net::HTTP inyectable, renderizado de markdown bajo demanda, y una máquina de estados dividida entre el job y el servicio.
Rastrear publicaciones programadas de Medium sin una extensión de navegador
La API de Medium no tiene el concepto de una publicación programada. Fase 1: modelar el estado manualmente con un enum de solo agregar, y ver si la fricción justifica la automatización.
El último publicador nativo: terminando la consolidación de Postiz
La integración nativa de Medium era una pieza de museo. Un puente de navegador, un artículo cuyo cuerpo es una URL, y menos 1.603 líneas.