Abe Estrada

Newsboat: urls → opml

Desde aquél triste día en el que cerraron Google Reader y descargue por primera vez mi archivo opml con todos los feeds que leía, no he dejado de hacer respaldos, tengo aproximadamente 20 archivos opml en los cuales se pueden ver mis intereses de en ese momento por los blogs y sitios que leía.

Ahora que utilizo newsboat el formato que utilizan para guardar los feeds es muy particular, por lo tanto que creado un pequeño script que corre en node para poder generar un archivo opml que es compatible con otros lectores de feeds, en caso de querer migrar.

package.json

{
  "dependencies": {
    "lodash": "^4.17.21",
    "xmlbuilder": "^15.1.1"
  }
}

index.js

const fs = require("fs");
const builder = require("xmlbuilder");
const escape = require("lodash/escape");

const urlRegex =
  /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[\-;:&=\+\$,\w]+@)?[A-Za-z0-9\.\-]+|(?:www\.|[\-;:&=\+\$,\w]+@)[A-Za-z0-9\.\-]+)((?:\/[\+~%\/\.\w\-_]*)?\??(?:[\-\+=&;%@\.\w_]*)#?(?:[\.\!\/\\\w]*))?)/;
const titleRegex = /"(.*?)"/;

try {
  // Load urls file
  const data = fs.readFileSync("./urls", "UTF-8");
  const lines = data.split(/\r?\n/);
  const tags = {};
  const notags = [];
  lines.forEach((line) => {
    if (line) {
      const cleanLine = line.replace("!", "").replace("~", "");
      // Find urls
      if (cleanLine.startsWith("http")) {
        const url = cleanLine.match(urlRegex);
        const title = cleanLine.match(titleRegex);

        // Split the line to get the tags
        const split = cleanLine.split('"');
        // Get the last tag
        const tag = !cleanLine.endsWith('"') ? `${split[split.length - 1]}`.trim() : "";
        if (!tags[tag]) {
          // Create tag
          tags[tag] = [];
        }
        if (url[1] && title[1]) {
          if (tag) {
            // Add feed to tag
            tags[tag].push({
              title: escape(`${title[1]}`.trim()),
              url: escape(`${url[1]}`.trim()),
            });
          } else {
            // Feeds without tag
            notags.push({
              title: escape(`${title[1]}`.trim()),
              url: escape(`${url[1]}`.trim()),
            });
          }
        }
      }
    }
  });
  const opmlObj = {
    opml: {
      "@version": "2.0",
      head: {},
      body: {
        outline: Object.keys(tags)
          .map((name) => ({
            "@text": name,
            outline: tags[name].map((feed) => ({
              "@title": feed.title,
              "@text": feed.title,
              "@xmlUrl": feed.url,
              "@type": "rss",
              "@version": "RSS",
            })),
          }))
          .concat(
            notags.map((feed) => ({
              "@title": feed.title,
              "@text": feed.title,
              "@xmlUrl": feed.url,
              "@type": "rss",
              "@version": "RSS",
            }))
          ),
      },
    },
  };
  const opml = builder.create(opmlObj, { encoding: "utf-8" });
  console.log(opml.end({ pretty: true }));
} catch (err) {
  console.error(err);
}

Lo que hace el script es leer linea por linea el archivo urls de newsboat y encontrar la url, el título que se encuentra dentro de "" y los tags que son las palabras separadas por espacios que van al final. Con estos datos genera el opml y guarda las urls de los feeds dentro de las “categorías” basadas en el último tag.

En esta ocasión todo se imprime en pantalla, pero es posible guardarlo en un archivo de la siguiente manera:

node index.js > 20221109.opml

Este es mi resultado: https://files.abeestrada.com/opml/20221109.xml

Se puede validar el archivo en: http://validator.opml.org

Referencias: