swaldman / audiofluidity-rss   0.1.0

Apache License 2.0 GitHub

A simple Scala 3 library for generating RSS.

Scala versions: 3.x

audiofluidity-rss

A simple Scala 3 API for generating RSS, for general websites as well as for podcasts.

Most common RSS extensions are supported, including Apple Podcast "itunes" elements.

scaladoc

Quickstart

Suppose we have a "blog" defined like this:

//> using scala "3.2.1"
//> using dep "com.mchange::audiofluidity-rss:0.0.2"

import audiofluidity.rss.*
import java.time.*
import scala.collection.*

object MyBlog {
  val myName = "Arthur Q. Author"
  val myEmail = s"[email protected] (${myName})"
  val mainUrl = "https://myblog.dev.null/"

  case class Post(title: String, desc: String, text : String, published : Long):
    def permalink = s"${mainUrl}/entry/${published}.html"
}

// reverse chronological
given Ordering[MyBlog.Post]
  = Ordering.by((p : MyBlog.Post) => (p.published, p.title, p.desc, p.text)).reverse

val posts = immutable.SortedSet (
  MyBlog.Post("Hello!", "First post!", "Some words I write.", 1674449923644),
  MyBlog.Post("Is this on?", "In which I worry.", "Why was my post not greeted with adulation?", 1674795664978),
  MyBlog.Post("Pulitzer!", "In which I gloat.", "Finally 'Hello !' received the recognition it deserves.", 1675054938281),
  MyBlog.Post("", "This is an untitled post.", "I've got nothing to say but it's okay.", 1676054938281),
)

def zonedDateTime( epochMilli : Long ) =
  Instant.ofEpochMilli(epochMilli).atZone(ZoneId.systemDefault())

You can generate simple XML for it like this:

import audiofluidity.rss.*
import java.time.*
import scala.collection.*

object SimpleExample:
  given Itemable[MyBlog.Post] with
    extension (post : MyBlog.Post)
      def toItem : Element.Item =
        val pubDate : Option[ZonedDateTime] = Some(zonedDateTime(post.published))
        Element.Item.create (
          title = post.title,
          linkUrl = post.permalink,
          description = post.desc,
          author = MyBlog.myEmail,
          pubDate = pubDate
        )

  val channel = Element.Channel.create (
    title = "My blog's RSS feed!",
    linkUrl = MyBlog.mainUrl,
    description = "This blog will blow your mind. Or your chance.",
    items = posts
  )

  val rssFeed = Element.Rss(channel)

  @main def simple_go() : Unit =
    println(rssFeed.asXmlText)

You can run this from the example directory of this repository:

$ scala-cli . --interactive        

And whee!

<?xml version='1.0' encoding='UTF-8'?>
<rss version="2.0">
  <channel>
    <title>My blog's RSS feed!</title>
    <link>https://myblog.dev.null/</link>
    <description><![CDATA[This blog will blow your mind. Or your chance.]]></description>
    <docs>https://cyber.harvard.edu/rss/rss.html</docs>
    <item>
      <pubDate>Fri, 10 Feb 2023 13:48:58 -0500</pubDate>
      <author>[email protected] (Arthur Q. Author)</author>
      <description><![CDATA[This is an untitled post.]]></description>
      <link>https://myblog.dev.null//entry/1676054938281.html</link>
      <title></title>
    </item>
    <item>
      <pubDate>Mon, 30 Jan 2023 00:02:18 -0500</pubDate>
      <author>[email protected] (Arthur Q. Author)</author>
      <description><![CDATA[In which I gloat.]]></description>
      <link>https://myblog.dev.null//entry/1675054938281.html</link>
      <title>Pulitzer!</title>
    </item>
    <item>
      <pubDate>Fri, 27 Jan 2023 00:01:04 -0500</pubDate>
      <author>[email protected] (Arthur Q. Author)</author>
      <description><![CDATA[In which I worry.]]></description>
      <link>https://myblog.dev.null//entry/1674795664978.html</link>
      <title>Is this on?</title>
    </item>
    <item>
      <pubDate>Sun, 22 Jan 2023 23:58:43 -0500</pubDate>
      <author>[email protected] (Arthur Q. Author)</author>
      <description><![CDATA[First post!]]></description>
      <link>https://myblog.dev.null//entry/1674449923644.html</link>
      <title>Hello!</title>
    </item>
  </channel>
</rss>

Features

audiofluidity-rss defines lots of not-standard-RSS elements that are commonly mixed into RSS feeds.

For example...

  • You might wish to mix-in non-RSS-standard tags
    • ...to use <dc:creator> elements for an author's name, rather than the e-mail address required in the <author> tag by the RSS standard
    • ...to include full-text content, rather than just the RSS-standard <description>, in your feed items. A common way to do this is with RDF-defined <content:encoded> tags.
    • ...to add an <atom:link> element to your channel item indicating the home page of the blog tht the feed represents
    • If you do any or all of these things, your RSS tag should properly bind the right XML namespaces to those dc/content/atom prefixes.
  • Defying the RSS standard, you might wish to drop required <author> tags (because you don't want to emit e-mails, real or fake), or to drop the required <title> element for untitled posts (rather than including an empty title).

audiofluidity_rss supports

  • mixing-in nonstandard elements (and offers definitions of lots of common choices)
  • defining RSS-tag namespaces
  • inserting post-processing into the XML-generation process to drop or rewrite elements.

For an example with all the above, please see example/FancierExample.scala.

You can run it in the examples dir with

$ scala-cli . --interactive        

Here's what the output looks like:

<?xml version='1.0' encoding='UTF-8'?>
<rss 
version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>My blog's RSS feed!</title>
    <link>https://myblog.dev.null/</link>
    <description><![CDATA[This blog will blow your mind. Or your chance.]]></description>
    <docs>https://cyber.harvard.edu/rss/rss.html</docs>
    <item>
      <pubDate>Fri, 10 Feb 2023 13:48:58 -0500</pubDate>
      <description><![CDATA[This is an untitled post.]]></description>
      <link>https://myblog.dev.null//entry/1676054938281.html</link>
      <dc:creator><![CDATA[Arthur Q. Author]]></dc:creator>
      <content:encoded><![CDATA[I've got nothing to say but it's okay.]]></content:encoded>
    </item>
    <item>
      <pubDate>Mon, 30 Jan 2023 00:02:18 -0500</pubDate>
      <description><![CDATA[In which I gloat.]]></description>
      <link>https://myblog.dev.null//entry/1675054938281.html</link>
      <title>Pulitzer!</title>
      <dc:creator><![CDATA[Arthur Q. Author]]></dc:creator>
      <content:encoded><![CDATA[Finally 'Hello !' received the recognition it deserves.]]></content:encoded>
    </item>
    <item>
      <pubDate>Fri, 27 Jan 2023 00:01:04 -0500</pubDate>
      <description><![CDATA[In which I worry.]]></description>
      <link>https://myblog.dev.null//entry/1674795664978.html</link>
      <title>Is this on?</title>
      <dc:creator><![CDATA[Arthur Q. Author]]></dc:creator>
      <content:encoded><![CDATA[Why was my post not greeted with adulation?]]></content:encoded>
    </item>
    <item>
      <pubDate>Sun, 22 Jan 2023 23:58:43 -0500</pubDate>
      <description><![CDATA[First post!]]></description>
      <link>https://myblog.dev.null//entry/1674449923644.html</link>
      <title>Hello!</title>
      <dc:creator><![CDATA[Arthur Q. Author]]></dc:creator>
      <content:encoded><![CDATA[Some words I write.]]></content:encoded>
    </item>
    <atom:link type="application/rss+xml" rel="self" href="https://myblog.dev.null/"/>
  </channel>
</rss>

Limitations

Although this library defines an informal AST for RSS, for now it only generates XML, it does not consume and parse it back.

Maybe someday if there's interest.

License

audiofluidity-rss was revised from a library internal to audiofluidity, a podcast-specific static-site generator.

However, this library is now offered independently, under Apache 2.0 terms. Please see LICENSE.

(The main audiofluidity application is a GPLv3 project.)

Some useful RSS resources

More docs soon, I hope. But for now, I want to bookmark some useful RSS resources:

See also the podcast-centric RSS resource list in the main audiofluidity README.md