From 4ca3c83d447fbf86a4f21d841a107cb28a64658e Mon Sep 17 00:00:00 2001 From: Vika Date: Thu, 13 Mar 2025 11:06:23 +0300 Subject: WIP: RSS feed generator Some people are old-fashioned, and RSS feeds can be consumed even by laypeople unfamiliar with microformats2. This does violate DRY at first glance, but since the feeds are dynamically generated it's not repetition but rather format conversion, and as such does not violate DRY per se. --- src/frontend/rss_conversion.rs | 65 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 src/frontend/rss_conversion.rs (limited to 'src/frontend/rss_conversion.rs') diff --git a/src/frontend/rss_conversion.rs b/src/frontend/rss_conversion.rs new file mode 100644 index 0000000..3448bf0 --- /dev/null +++ b/src/frontend/rss_conversion.rs @@ -0,0 +1,65 @@ +//! Conversion of MF2-JSON posts into RSS items. + +/// TTL of the RSS feeds generated by Kittybox. Compliant RSS +/// consumers must avoid refetching the feed before its TTL expires. +const RSS_TTL: std::time::Duration = std::time::Duration::from_secs(600); + +/// Convert a single MF2 post to an RSS item. +/// +/// Posts must have a UID. +pub fn post_to_rss(mut post: serde_json::Value) -> Option { + let mut builder = rss::ItemBuilder::default(); + + let date = match post["properties"]["published"][0].take() { + serde_json::Value::String(d) => chrono::DateTime::::parse_from_rfc3339(&d).ok(), + _ => None + }; + if let Some(date) = date { + builder.pub_date(date.to_rfc2822()); + } + if let serde_json::Value::String(title) = post["properties"]["title"][0].take() { + builder.title(title); + } + if let serde_json::Value::String(summary) = post["properties"]["summary"][0].take() { + builder.description(summary); + } + // Do not emit posts that do not have GUIDs. + match post["properties"]["uid"][0].take() { + serde_json::Value::String(uid) => { + builder.guid(rss::GuidBuilder::default().value(uid).permalink(true).build()); + }, + _ => { return None; }, + } + // TODO: enclosures for u-photo, u-video, u-audio + // requires knowing MIME type for the included media file + // can enrich from media endpoint + + let item = builder.build(); + if item.title.is_some() || item.description.is_some() { + Some(item) + } else { + None + } +} + +/// Convert an entire MF2 h-feed into an RSS channel. +/// Fairly opinionated. Provided MF2-JSON object must be a feed. +pub fn feed_to_rss(mut post: serde_json::Value) -> rss::Channel { + let children: Vec = match post["children"].take() { + serde_json::Value::Array(children) => children, + _ => Vec::default(), + }; + rss::ChannelBuilder::default() + .title(post["properties"]["name"][0].as_str().unwrap()) + .link(post["properties"]["uid"][0].as_str().unwrap()) + .generator(Some(concat!( + env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION") + ).to_string())) + .ttl((RSS_TTL.as_secs() / 60).to_string()) + .items(children + .into_iter() + .filter_map(post_to_rss) + .collect::>() + ) + .build() +} -- cgit 1.4.1