Abe Estrada

OPML → Newsboat

Llevo tiempo utilizando Newsboat como cliente de feeds local desde la terminal.

Newsboat tienen una opción para importar archivos OPML y convertirlos en su formato para poder cargar los feeds, pero no me gusta la forma o la falta de organización por categorías, así que escribí un pequeño script para convertir el OPML con categorías en el formato para Newsboat.

package.json

{
  "type": "module",
  "dependencies": {
    "fast-xml-parser": "^4.0.8",
    "lodash-es": "^4.17.21"
  }
}

index.js

import { readFile, writeFile } from "fs/promises";
import { XMLParser } from "fast-xml-parser";
import { isObject } from "lodash-es";

const parser = new XMLParser({ ignoreAttributes: false });

let urls = `"query:★ Unread:unread = \\"yes\\""\n`;

try {
  // Load file
  const opml = await readFile("./20221109.xml", "utf8");
  try {
    const json = parser.parse(opml);
    json?.opml?.body?.outline?.map((category) => {
      if (Array.isArray(category.outline) && category.outline?.length > 0) {
        // Category
        urls += `\n\n# ${category["@_title"] || category["@_text"]}\n`;
        urls += `"query:${category["@_title"] || category["@_text"]}:tags # \\"${
          category["@_title"] || category["@_text"]
        }\\""\n`;
        category.outline.map((item) => {
          urls += `${item["@_xmlUrl"]} ! "~${item["@_title"]}" ${category["@_title"] || category["@_text"]}\n`;
        });
      } else if (isObject(category.outline)) {
        // Single feed category
        const item = category.outline;
        urls += `\n\n# ${category["@_title"] || category["@_text"]}\n`;
        urls += `"query:${category["@_title"] || category["@_text"]}:tags # \\"${
          category["@_title"] || category["@_text"]
        }\\""\n`;
        urls += `${item["@_xmlUrl"]} ! "~${item["@_title"]}" ${category["@_title"] || category["@_text"]}\n`;
      } else if (category["@_xmlUrl"]) {
        // Feeds without category
        urls += `\n${category["@_xmlUrl"]} "~${category["@_title"] || category["@_text"]}"`;
      }
    });
    try {
      // Copy this file to ~/.newsboat
      writeFile("./urls", urls);
      console.log("> ./urls");
    } catch (err) {
      console.log(err);
    }
  } catch (err) {
    console.log(err);
  }
} catch (err) {
  console.log(err);
}

Para empezar se agrega la linea "query:★ Unread:unread = \"yes\"" para crear una categoría con todas los artículos sin leer, luego se crean las categorías con los feeds ocultos (!) para que solo se puedan visualizar al entrar a la categoría y todo eso se guarda en el archivo urls en el formato para Newsboat.

El resultado es algo así:

urls

"query:★ Unread:unread = \"yes\""

https://feed.abeestrada.com/all "~Abe Estrada"
https://rpilocator.com/feed.rss "~RPi Locator"

# Android
"query:Android:tags # \"Android\""
https://blog.google/products/android/rss/ ! "~Android" Android
http://feeds.feedburner.com/blogspot/hsDu ! "~Android Developers Blog" Android

# Tech
"query:Tech:tags # \"Tech\""
http://www.engadget.com/rss.xml ! "~Engadget" Tech
http://www.theverge.com/rss/index.xml ! "~The Verge" Tech
http://feeds.arstechnica.com/arstechnica/technology-lab ! "~Ars Technica" Tech

Cada categoría es definida con un query y donde cada url consiste de cuatro partes:

url ! "~Titulo" Categoría

Screenshots