Created
          September 24, 2025 03:02 
        
      - 
      
- 
        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 
  
        
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | 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