From c700d81426328c2aa2c1587c235c3eac8328833c Mon Sep 17 00:00:00 2001 From: Samantaz Fox Date: Tue, 16 Nov 2021 18:21:19 +0100 Subject: [PATCH] Add an Atom feed builder helper --- src/invidious/rss_atom.cr | 124 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 src/invidious/rss_atom.cr diff --git a/src/invidious/rss_atom.cr b/src/invidious/rss_atom.cr new file mode 100644 index 00000000..305238e1 --- /dev/null +++ b/src/invidious/rss_atom.cr @@ -0,0 +1,124 @@ +require "xml" +require "http/server" + +module Invidious::RssAtom + extend self + + # TODO: Merge all of those in a single type + alias AnyVideo = SearchVideo | ChannelVideo | PlaylistVideo + + # + # Feed properties structure + # + + alias AltLink = NamedTuple(type: String, url: String) + + struct AtomProperties + getter title : String + getter icon_url : String + getter author : String + getter author_url : String + + getter date_published : String + getter date_updated : String + + getter alt_links : Array(AltLink) + + def initialize( + *, # All parameters must be named + @title = "", @icon_url = "", + @author = "", @author_url = "", + date_updated : Time | String = Time.utc, + date_published : Time | String = "", + @alt_links = [] of AltLink + ) + # Convert publication date if needed + if date_published.is_a?(Time) + @date_published = date_published.to_rfc3339 + else + @date_published = date_published + end + + # Convert update date if needed + if date_updated.is_a?(Time) + @date_updated = date_updated.to_rfc3339 + else + @date_updated = date_updated + end + end + end + + # + # Atom Feed builder + # + + def atom_feed_builder( + # Mandatory parameters + env : HTTP::Server::Context, + videos : Array(AnyVideo), + id : String, + properties : AtomProperties + ) + locale = env.get("preferences").as(Preferences).locale + params = HTTP::Params.parse(env.params.query["params"]? || "") + + return XML.build(indent: " ", encoding: "UTF-8") do |xml| + xml.element("feed", + xmlns: "http://www.w3.org/2005/Atom", + "xmlns:media": "http://search.yahoo.com/mrss/", + "xml:lang": "en-US" + ) do + # The id must be unique, and an IANA-approved IRI, so use "ni://" + # Relevant RFC documents: + # - https://datatracker.ietf.org/doc/html/rfc4287#section-4.2.6 + # - https://datatracker.ietf.org/doc/html/rfc6920 + # + xml.element("id") { xml.text "ni://invidious/sha-256;" + sha256(id) } + + # Feed title. Use author name if no title was provided + xml.element("title") do + xml.text(properties.title.empty? ? properties.author : properties.title) + end + + if !properties.icon_url.empty? + icon_url = "#{HOST_URL}/gghpt/#{URI.parse(properties.icon_url).request_target}" + xml.element("icon") { xml.text icon_url } + xml.element("logo") { xml.text icon_url } + end + + # Feed creation (if available) and update (mandatory) dates + if !properties.date_published.empty? + xml.element("published") { xml.text properties.date_published } + end + + xml.element("updated") { xml.text properties.date_updated } + + # Links + xml.element("link", rel: "self", + type: "application/atom+xml", + href: "#{HOST_URL}#{env.request.resource}" + ) + + properties.alt_links.each do |link| + xml.element("link", rel: "alternate", type: link[:type], href: link[:url]) + end + + # Author infos + xml.element("author") do + xml.element("name") { xml.text properties.author } + xml.element("uri") { xml.text properties.author_url } if !properties.author_url.empty? + end + + # Video entries + # TODO: Unify `.to_xml` methods + videos.each do |video| + case video + when .is_a?(PlaylistVideo) then video.to_xml(xml) + when .is_a?(ChannelVideo) then video.to_xml(locale, params, xml) + when .is_a?(SearchVideo) then video.to_xml(false, params, xml) + end + end + end + end + end +end