Consideraciones sobre el volcado de posts

Minientrada

A raíz del volcado de posts que cargué el otro día estoy considerando próximos datasets a importar. Exports procedentes de la GDPR, viejas entradas de blog sacadas de Wayback Archive, incluso tal vez puede que crosspost de cosas cargadas a YouTube o Twitter (algo que llevo años persiguiendo).

Algunas consideraciones técnicas que he aprendido para la próxima:

  • Es mejor usar un entorno de staging mientras se hacen pruebas para no ensuciar el sitio web, porque lo más posible es que falle al principio.
  • Por lo tanto, es mejor ir de poco en poco, y no intentar importar un dataset muy grande hasta que no se ensaye con algo más pequeño que se pueda borrar fácilmente si se hace mal.
  • Por si hay que borrar, es mejor ponerles a los posts importados una etiqueta nueva para poder filtrar fácilmente posts con esa etiqueta y borrar todo. En mi caso, esa etiqueta ha sido hn-import.
  • Si el blog lo alojas por tu cuenta, no tienes que dar parte del API Rate Limit a nadie, pero corres el riesgo de causarte un ataque de denegación de servicio a ti mismo. Como casi hago, de hecho, porque mi código JavaScript intentó lanzar las 49 peticiones HTTP POST a la vez.
  • Por asociación de ideas, mejor apagar los hooks externos que se llaman al crear posts mientras se estén importando cosas. No pingbacks, no trackbacks, no webmentions, no ActivityPub. (Aparte que muchos de estos posts tienen unos cuantos años y no tiene mucho sentido generar notificaciones por esto.)
  • El feed RSS va a sufrir.

El problema, como ya he dicho alguna vez, son los títulos. Muchas redes sociales no usan títulos en sus publicaciones, pero algo hay que poner para que la sindicación por RSS o al usar temas y widgets de WordPress que traten de mostrar el título de un post, tenga o no, puedan mostrar algo distinto a (sin título).

Importando de redes sociales con XML-RPC

Minientrada

Una de las cosas bonitas de la GDPR es que casi obliga a las redes sociales y sitios similares a tener un sistema de exportado de datos. Y casi siempre lo hacen con algún formato estructurado como CSV, JSON o XML, fácil de procesar por un ordenador.

Paralelamente, WordPress tiene el viejo confiable XML-RPC para crear posts de forma programática. Si no tienes mucha idea, recomiendan cerrar ese endpoint al exterior para evitar problemas. Pero en verdad, si sabes protegerlo, resulta muy práctico para crear posts a golpe de petición HTTP. (En teoría existe la API REST, pero bueno…)

Con la ayuda de un cliente XML-RPC para WordPress que hay en NPM y de la API de Hacker News, hice en Node.js un script de un solo uso que crease una entrada por cada comentario y enlace enviado para enlazar hacia el comentario o la historia, siguiendo el modelo de sindicación PESOS de IndieWeb (Publish Elsewhere, Syndicate to Own Site). Le he puesto la etiqueta hn-import a la colección. ¿Cuál será el próximo archivo que importe?

En sí la librería no es muy complicada de utilizar. Una vez tienes un cliente conectado es muy fácil chutarle un array de posts a crear (por ejemplo, procesar fila a fila un CSV o item a item un JSON o un XML). Primero se crea un cliente:

const { createClient } = require("wordpress")

const client = createClient({
  url: "https://example.com",
  username: "mi user",
  password: "mi password"
})

Luego definiendo el payload. Aquí es donde encuentro más cómodo crear un objeto JSON que declarar todo el chorizo XML de XML-RPC:

const payload = {
  title: "El título de mi entrada",
  content: `
    <p>El contenido de mi entrada.</p>
    <blockquote>Aceptamos HTML.</blockquote>
    <p><a href="https://www.example.com">Y enlaces</a></p>
  `.trim(),
  status: 'publish', // podría ser 'draft' o 'private'
  termNames: {
    category: ['Categoría'],
    post_tag: ['tag 1', 'tag 2', 'tag 3'],
  },
  date: '2006-01-02 15:04:05',
  format: 'link' // podría ser 'aside', 'status', 'photo', ...
}

Y ya postear usando newPost. La librería es previa a promesas así que su único punto malo es que está orientada a callbacks.

client.newPost(payload, (err, id) => {
  if (err) {
    console.error(err)
  } else {
    console.log(`ID del nuevo post: ${id}`)
  }
})