OPML File Generator

Description

Script to take information stored in a JSON file and converts it into OPML format. This works for RSS readers as well as podcast clients that support OPML import.

Usage

dotnet fsi opml-generator.fsx "<feed-title>" "<author-url>" "<data-file-path>"
dotnet fsi opml-generator.fsx "My Blogroll" "http://lqdev.me" "blogroll.json"

Snippet

opml-generator.fsx

open System.IO
open System.Linq
open System.Text.Json
open System.Xml.Linq

type OpmlMetadata = 
    {
        Title: string
        OwnerId: string
    }

type Outline = 
    {
        Title: string
        Type: string
        HtmlUrl: string
        XmlUrl: string
    }

let opmlFeed (head:XElement) = 
    XElement(XName.Get "opml",
        XAttribute(XName.Get "version", "2.0"),
            head,
            XElement(XName.Get "body"))

let headElement (metadata:OpmlMetadata) = 
        XElement(XName.Get "head",
            XElement(XName.Get "title", metadata.Title),
            XElement(XName.Get "ownerId", metadata.OwnerId))

let outlineElement (data:Outline) = 
    XElement(XName.Get "outline",
        XAttribute(XName.Get "title", data.Title),
        XAttribute(XName.Get "text", data.Title),        
        XAttribute(XName.Get "type", data.Type),
        XAttribute(XName.Get "htmlUrl", data.HtmlUrl),
        XAttribute(XName.Get "xmlUrl", data.XmlUrl))

let loadLinks (filePath:string) = 
    File.ReadAllText(filePath)
    |> fun x -> x |> JsonSerializer.Deserialize<Outline array>

let buildOpmlFeed (title:string) (ownerId:string) (filePath:string) = 
    let fileName = Path.GetFileNameWithoutExtension(filePath)
    
    let head = 
        {
            Title=title
            OwnerId=ownerId
        }
        |> headElement
    
    let links = filePath |> loadLinks |> Array.map(outlineElement) 
    
    let feed =  opmlFeed head 
    feed.Descendants(XName.Get "body").First().Add(links)
    File.WriteAllText($"{fileName}.opml", feed.ToString())

let args = fsi.CommandLineArgs
let title = args[1]
let ownerId = args.[2]
let dataPath = args.[3]

buildOpmlFeed title ownerId dataPath

blogroll.json

[
    {
        "Title": "Blogroll.org",
        "Type": "rss",
        "HtmlUrl": "https://blogroll.org/",
        "XmlUrl": "https://blogroll.org/feed/"
    },
    {
        "Title": "Cheapskate's Guide",
        "Type": "rss",
        "HtmlUrl": "https://cheapskatesguide.org/",
        "XmlUrl": "https://cheapskatesguide.org/cheapskates-guide-rss-feed.xml"
    },
    {
        "Title": "JWZ",
        "Type": "rss",
        "HtmlUrl": "https://www.jwz.org/blog/",
        "XmlUrl": "https://cdn.jwz.org/blog/feed/"
    }    
]