HaskellでRSSを生成してみる

# 今さらRSS?

Webサイトの記事更新を効率よく知らせたり取得できたりするRSS。いくつかのRSSリーダーサービスが終了したりと、RSSについての盛り上がりは過去のもの、という話もありますが、「たいした仕込み要らずで、簡易通知機能を実装する」という観点でいうと、これはこれでありかもしれません。

というわけで、HaskellでRSS生成やってみたので、QuickStart的に書いてみました。

# RSSとは

ある意味マイナーになってきつつあるRSSなので、「RSSとはなんぞや」をごくごく簡単に書いてみます。

RSS自体は「更新記事のアイテムをXML形式で記述したテキストファイル」です。場合によってはCSSもセットにすることもあります。RSSを静的に生成しておくと、Webサーバにおいておくだけで、通知のブロードキャストを軽量に実現できます。もちろんユニキャストとして使うのも自由。動的にRSSを生成したりするのも、どうぞご自由に。本記事とは直接関係はないですが、RSSクライアントは、SNSのbotと違い、pull型でデータを取得することになります。

RSSにはいくつかのバージョンや種類がありますが、ここではRSS2.0を例として書きます。後述するfeedのREADMEでは、Atomの例が載っているので、比較するのも面白いかもしれません。

RSS2.0では「RSSタグ」の中に「channelタグ」があり、その中に複数の「itemタグ」があります。itemタグが記事1つ分のエントリに相当します。

# feedモジュール

RSSの中身はただのXMLファイルなので、例えばText.XMLを使ってしまえば、RSSの生成はできちゃいますが、やはり専用のモジュールを使った方が、わかりやすく型安全になるというものです。

Haskellでは、RSS向けに「feed」というモジュールがあります。ある程度のRSSの種類に対応しているようです。

# トップレベル（RSS -> Text）

まずは、RSS型の紹介です。

ちゃんとした型定義は[公式](https://hackage.haskell.org/package/feed-1.3.0.1/docs/Text-RSS-Syntax.html)をみてもらうとして、こんな感じで作ってやります。時刻はRFC822形式なので、Data.Timeのrfc822DateFormatを使ってあげるといいでしょう。

```Haskell
import Data.Text as T

myRSS :: UTCTime -> RSS
myRSS now =
  let ver = "2.0"
      attrs = []
      description = "My RSS description"
      title = "My RSS title"
      link = "http://example.com"
      channel = RSSChannel
        { rssTitle = title
        , rssLink = link
        , rssDescription = description
        , rssItems = []
        , rssLanguage = Nothing
        , rssCopyright = Nothing
        , rssEditor = Nothing
        , rssWebMaster = Nothing
        , rssPubDate = Nothing
        , rssLastUpdate = Just $ T.pack $ formatTime defaultTimeLocale rfc822DateFormat now
        , rssCategories = []
        , rssGenerator = Nothing
        , rssDocs = Nothing
        , rssCloud = Nothing
        , rssTTL = Nothing
        , rssImage = Nothing
        , rssRating = Nothing
        , rssTextInput = Nothing
        , rssSkipHours = Nothing
        , rssSkipDays = Nothing
        , rssChannelOther = []
        }
  in
    RSS ver attrs channel []
```

わざわざ全部書かなくても、```nullRSS```を使うとRSS型が手軽にできますが、最初はこのように「どのようなフィールドがあるのか」がわかったほうが理解は早いと思います。

この例では、まだアイテムは何もないですが、このようにして作ったRSS型は```textRSS```関数でXMLなテキストになります。

```Haskell
xmlText :: UTCTime -> Maybe Text
xmlText now = textRSS $ myRSS now
```

戻り値の型はLazyなTextなので、正格にするかは出力のインターフェイスに合わせてあげましょう。あと、戻り値の型がTextではなくMaybe Textになっているのは、Text.XMLのレンダリング関数を使っているためにそうなっているのではないかと思います。RSS型を食わせてやる限り、必ずJustになるのでないかなぁ、と（裏はとりきってないですが）。

# アイテム（RSSItem）

肝心のアイテムは、RSS型のrssItemsにセットします。型はRSSItemです。そのまんまですね。下記はあくまで例です。RSSItemのメンバはすべてMaybe aか[a]なので、「何も指定しないRSSItem」も作れます。意味はないですが。RSSItemを好きな数だけ作って、リストとしてRSSChannelのrssItemsにセットしてあげましょう。

```Haskell
myRSSItem :: Text -> URLString -> UTCTime -> Text -> RSSItem
myRSSItem title link timestamp desc =
    RSSItem
    { rssItemTitle = Just title
    , rssItemLink = Just link
    , rssItemDescription = Just desc
    , rssItemAuthor = Nothing
    , rssItemCategories = []
    , rssItemComments = Nothing
    , rssItemEnclosure = Nothing
    , rssItemGuid = Nothing
    , rssItemPubDate = Just $ T.pack $ formatTime defaultTimeLocale rfc822DateFormat timestamp
    , rssItemSource = Nothing
    , rssItemAttrs = []
    , rssItemOther = []
    }
```

# さいごに

ざっくり書いたので、コードにミスとかあるかも。
でも、ミスが有ってもHaskellerならどうにかできるでしょ（てきとう）

Atomとかでやりたい人がいたら、[公式のREADME](https://github.com/bergmark/feed)とかを見よう。きっとできる。

…といいつつ、個人的にはちょっとわかりづらかったので、RSS2.0をやって、さらに記事まで書いたんですが。