Skip to content

Instantly share code, notes, and snippets.

@stevedylandev
Created September 24, 2025 03:02
Show Gist options
  • Save stevedylandev/65d354fd65e502a8f0739de5f4e7b0a0 to your computer and use it in GitHub Desktop.
Save stevedylandev/65d354fd65e502a8f0739de5f4e7b0a0 to your computer and use it in GitHub Desktop.
Converts OPML lists to a simple markdown bulleted list with links, ideal for sharing RSS feeds
import { XMLParser } from 'fast-xml-parser';
interface OutlineItem {
text: string;
title?: string;
htmlUrl?: string;
xmlUrl?: string;
}
function parseOpml(opmlContent: string): OutlineItem[] {
const parser = new XMLParser({
ignoreAttributes: false,
attributeNamePrefix: "@_"
});
const jsonObj = parser.parse(opmlContent);
const outlines = jsonObj.opml?.body?.outline || [];
// Handle both single outline and array of outlines
const outlineArray = Array.isArray(outlines) ? outlines : [outlines];
const items: OutlineItem[] = outlineArray
.filter(outline => outline["@_type"] === "rss")
.map(outline => ({
text: outline["@_text"] || outline["@_title"] || 'Untitled',
title: outline["@_title"],
htmlUrl: outline["@_htmlUrl"],
xmlUrl: outline["@_xmlUrl"]
}));
return items;
}
function convertToMarkdown(items: OutlineItem[]): string {
const lines = items.map(item => {
const name = item.text || item.title || 'Untitled';
// Prefer htmlUrl (website) over xmlUrl (RSS feed), fallback to xmlUrl if no htmlUrl
const url = (item.htmlUrl && item.htmlUrl.trim() !== '') ? item.htmlUrl : item.xmlUrl || '#';
return `- [${name}](${url})`;
});
return lines.join('\n');
}
async function main() {
const args = process.argv.slice(2);
if (args.length === 0) {
console.error('Usage: bun convert-opml.ts <opml-file> [output-file]');
console.error('Example: bun convert-opml.ts subscriptions.opml links.md');
process.exit(1);
}
const inputFile = args[0];
const outputFile = args[1] || inputFile?.replace(/\.opml$/i, '.md');
try {
const opmlContent = await Bun.file(inputFile as string).text();
const items = parseOpml(opmlContent);
const markdown = convertToMarkdown(items);
await Bun.write(outputFile as string, markdown);
console.log(`✅ Converted ${items.length} items from ${inputFile} to ${outputFile}`);
} catch (error) {
console.error('❌ Error:', error);
process.exit(1);
}
}
if (import.meta.main) {
main();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment