module Feed open System.Xml open System.ServiceModel.Syndication open Shared.Feed open System.Net open System open System.IO open Microsoft.FSharp.Control.CommonExtensions // Parses a RSS feed from an XMLReader, into an array of items // The .NET Framework SyndicationFeed class does most of the work, except it has a non-F#y class-based model, annoyingly feed-type-specific stuff, and is generally inconvenient to actually use let parseFromReader (xmlReader : XmlReader) : Item array = let feed = SyndicationFeed.Load(xmlReader) feed.Items |> Seq.toArray |> Array.map (fun item -> // Try and find some kind of description/content/summary/whatever to display - what sort it is varies based on what type the feed we parse is let desc = match item.Content with | (:? TextSyndicationContent as txt) -> txt.Text | _ -> match item.Summary with | null -> "" | txt -> txt.Text let date = match item.PublishDate.Ticks with // Due to format differences between Atom and RSS, we need to check whether PublishDate has an actual value and if not use something else | 0L -> item.LastUpdatedTime | _ -> item.PublishDate { title = item.Title.Text; description = desc; link = item.Links.[0].Uri.ToString(); time = date; source = feed.Title.Text } ) let fetchAsync url = async { let req = WebRequest.Create(Uri(url)) let! resp = req.AsyncGetResponse() return resp.GetResponseStream() } // Asynchronously loads an RSS feed from a URL let loadFromUrl url = async { use! stream = fetchAsync url use xml = XmlReader.Create(stream) return parseFromReader xml } // Given several feed URLs, loads them, concatenates them, and sorts them. let loadMany feeds = async { let! loadedFeeds = Seq.map loadFromUrl feeds |> Async.Parallel return Array.concat loadedFeeds |> sort }