swaldman / audiofluidity-rss   0.1.3

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>

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