From c700d81426328c2aa2c1587c235c3eac8328833c Mon Sep 17 00:00:00 2001 From: Samantaz Fox Date: Tue, 16 Nov 2021 18:21:19 +0100 Subject: [PATCH 1/6] 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 From add7f6f8fa3538baa82039d00abc5b8b2b5e0216 Mon Sep 17 00:00:00 2001 From: Samantaz Fox Date: Tue, 16 Nov 2021 21:23:13 +0100 Subject: [PATCH 2/6] Use 'atom_feed_builder()' to build Atom feeds --- src/invidious/helpers/serialized_yt_data.cr | 17 ++-- src/invidious/routes/feeds.cr | 100 +++++++++----------- 2 files changed, 51 insertions(+), 66 deletions(-) diff --git a/src/invidious/helpers/serialized_yt_data.cr b/src/invidious/helpers/serialized_yt_data.cr index 3918bd13..0dde91fd 100644 --- a/src/invidious/helpers/serialized_yt_data.cr +++ b/src/invidious/helpers/serialized_yt_data.cr @@ -18,20 +18,13 @@ struct SearchVideo query_params["v"] = self.id xml.element("entry") do - xml.element("id") { xml.text "yt:video:#{self.id}" } - xml.element("yt:videoId") { xml.text self.id } - xml.element("yt:channelId") { xml.text self.ucid } + xml.element("id") { xml.text self.id } xml.element("title") { xml.text self.title } xml.element("link", rel: "alternate", href: "#{HOST_URL}/watch?#{query_params}") xml.element("author") do - if auto_generated - xml.element("name") { xml.text self.author } - xml.element("uri") { xml.text "#{HOST_URL}/channel/#{self.ucid}" } - else - xml.element("name") { xml.text author } - xml.element("uri") { xml.text "#{HOST_URL}/channel/#{ucid}" } - end + xml.element("name") { xml.text self.author } + xml.element("uri") { xml.text "#{HOST_URL}/channel/#{self.ucid}" } end xml.element("content", type: "xhtml") do @@ -40,7 +33,9 @@ struct SearchVideo xml.element("img", src: "#{HOST_URL}/vi/#{self.id}/mqdefault.jpg") end - xml.element("p", style: "word-break:break-word;white-space:pre-wrap") { xml.text html_to_content(self.description_html) } + xml.element("p", style: "white-space:pre-wrap") do + xml.text html_to_content(self.description_html) + end end end diff --git a/src/invidious/routes/feeds.cr b/src/invidious/routes/feeds.cr index b601db94..0ff18029 100644 --- a/src/invidious/routes/feeds.cr +++ b/src/invidious/routes/feeds.cr @@ -144,8 +144,6 @@ module Invidious::Routes::Feeds ucid = env.params.url["ucid"] - params = HTTP::Params.parse(env.params.query["params"]? || "") - begin channel = get_about_info(ucid, locale) rescue ex : ChannelRedirect @@ -159,12 +157,13 @@ module Invidious::Routes::Feeds response = YT_POOL.client &.get("/feeds/videos.xml?channel_id=#{channel.ucid}") rss = XML.parse_html(response.body) + updated = [] of String videos = rss.xpath_nodes("//feed/entry").map do |entry| video_id = entry.xpath_node("videoid").not_nil!.content title = entry.xpath_node("title").not_nil!.content published = Time.parse_rfc3339(entry.xpath_node("published").not_nil!.content) - updated = Time.parse_rfc3339(entry.xpath_node("updated").not_nil!.content) + updated << entry.xpath_node("updated").not_nil!.content author = entry.xpath_node("author/name").not_nil!.content ucid = entry.xpath_node("channelid").not_nil!.content @@ -188,33 +187,27 @@ module Invidious::Routes::Feeds }) end - XML.build(indent: " ", encoding: "UTF-8") do |xml| - xml.element("feed", "xmlns:yt": "http://www.youtube.com/xml/schemas/2015", - "xmlns:media": "http://search.yahoo.com/mrss/", xmlns: "http://www.w3.org/2005/Atom", - "xml:lang": "en-US") do - xml.element("link", rel: "self", href: "#{HOST_URL}#{env.request.resource}") - xml.element("id") { xml.text "yt:channel:#{channel.ucid}" } - xml.element("yt:channelId") { xml.text channel.ucid } - xml.element("icon") { xml.text channel.author_thumbnail } - xml.element("title") { xml.text channel.author } - xml.element("link", rel: "alternate", href: "#{HOST_URL}/channel/#{channel.ucid}") + feed_created = rss.xpath_node("//feed/published").not_nil!.content + feed_updated = updated.size > 0 ? updated[0] : feed_created - xml.element("author") do - xml.element("name") { xml.text channel.author } - xml.element("uri") { xml.text "#{HOST_URL}/channel/#{channel.ucid}" } - end + # Generate Atom feed - xml.element("image") do - xml.element("url") { xml.text channel.author_thumbnail } - xml.element("title") { xml.text channel.author } - xml.element("link", rel: "self", href: "#{HOST_URL}#{env.request.resource}") - end + properties = Invidious::RssAtom::AtomProperties.new( + title: "", # Use channel author name + icon_url: channel.author_thumbnail, + author: channel.author, + author_url: "#{HOST_URL}/channel/#{channel.ucid}", + date_updated: feed_updated, + date_published: feed_created, + alt_links: [{ + type: "text/html", + url: "#{HOST_URL}/channel/#{channel.ucid}", + }] + ) - videos.each do |video| - video.to_xml(channel.auto_generated, params, xml) - end - end - end + return Invidious::RssAtom.atom_feed_builder( + env, videos, "channel/#{channel.ucid}", properties + ) end def self.rss_private(env) @@ -245,20 +238,20 @@ module Invidious::Routes::Feeds videos, notifications = get_subscription_feed(user, max_results, page) - XML.build(indent: " ", encoding: "UTF-8") do |xml| - xml.element("feed", "xmlns:yt": "http://www.youtube.com/xml/schemas/2015", - "xmlns:media": "http://search.yahoo.com/mrss/", xmlns: "http://www.w3.org/2005/Atom", - "xml:lang": "en-US") do - xml.element("link", "type": "text/html", rel: "alternate", href: "#{HOST_URL}/feed/subscriptions") - xml.element("link", "type": "application/atom+xml", rel: "self", - href: "#{HOST_URL}#{env.request.resource}") - xml.element("title") { xml.text translate(locale, "Invidious Private Feed for `x`", user.email) } + # Generate Atom feed - (notifications + videos).each do |video| - video.to_xml(locale, params, xml) - end - end - end + properties = Invidious::RssAtom::AtomProperties.new( + title: translate(locale, "Invidious Private Feed for `x`", user.email), + author: user.email, + alt_links: [{ + type: "text/html", + url: "#{HOST_URL}/feed/subscriptions", + }] + ) + + return Invidious::RssAtom.atom_feed_builder( + env, (notifications + videos), "subscriptions/#{user.email}", properties + ) end def self.rss_playlist(env) @@ -276,23 +269,20 @@ module Invidious::Routes::Feeds if playlist = Invidious::Database::Playlists.select(id: plid) videos = get_playlist_videos(playlist, offset: 0) - return XML.build(indent: " ", encoding: "UTF-8") do |xml| - xml.element("feed", "xmlns:yt": "http://www.youtube.com/xml/schemas/2015", - "xmlns:media": "http://search.yahoo.com/mrss/", xmlns: "http://www.w3.org/2005/Atom", - "xml:lang": "en-US") do - xml.element("link", rel: "self", href: "#{HOST_URL}#{env.request.resource}") - xml.element("id") { xml.text "iv:playlist:#{plid}" } - xml.element("iv:playlistId") { xml.text plid } - xml.element("title") { xml.text playlist.title } - xml.element("link", rel: "alternate", href: "#{HOST_URL}/playlist?list=#{plid}") + # Generate Atom feed - xml.element("author") do - xml.element("name") { xml.text playlist.author } - end + properties = Invidious::RssAtom::AtomProperties.new( + title: playlist.title, + author: playlist.author, + alt_links: [{ + type: "text/html", + url: "#{HOST_URL}/playlist?list=#{plid}", + }] + ) - videos.each &.to_xml(xml) - end - end + return Invidious::RssAtom.atom_feed_builder( + env, videos, "playlist/#{plid}", properties + ) else haltf env, status_code: 404 end From 7c3c0564f5797e6c92777dd28dec44f79016b352 Mon Sep 17 00:00:00 2001 From: Samantaz Fox Date: Sun, 17 Apr 2022 15:41:13 +0200 Subject: [PATCH 3/6] Remove youtube IDs from ChannelVideo/PlaylistVideo #to_xml methods --- src/invidious/channels/channels.cr | 4 +--- src/invidious/helpers/serialized_yt_data.cr | 2 +- src/invidious/playlists.cr | 4 +--- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/invidious/channels/channels.cr b/src/invidious/channels/channels.cr index e0459cc3..fb104779 100644 --- a/src/invidious/channels/channels.cr +++ b/src/invidious/channels/channels.cr @@ -54,9 +54,7 @@ struct ChannelVideo query_params["v"] = self.id xml.element("entry") do - xml.element("id") { xml.text "yt:video:#{self.id}" } - xml.element("yt:videoId") { xml.text self.id } - xml.element("yt:channelId") { xml.text self.ucid } + xml.element("id") { xml.text "ni://invidious/sha-256;" + sha256("video/#{self.id}") } xml.element("title") { xml.text self.title } xml.element("link", rel: "alternate", href: "#{HOST_URL}/watch?#{query_params}") diff --git a/src/invidious/helpers/serialized_yt_data.cr b/src/invidious/helpers/serialized_yt_data.cr index 0dde91fd..082fe97c 100644 --- a/src/invidious/helpers/serialized_yt_data.cr +++ b/src/invidious/helpers/serialized_yt_data.cr @@ -18,7 +18,7 @@ struct SearchVideo query_params["v"] = self.id xml.element("entry") do - xml.element("id") { xml.text self.id } + xml.element("id") { xml.text "ni://invidious/sha-256;" + sha256("video/#{self.id}") } xml.element("title") { xml.text self.title } xml.element("link", rel: "alternate", href: "#{HOST_URL}/watch?#{query_params}") diff --git a/src/invidious/playlists.cr b/src/invidious/playlists.cr index c4eb7507..a4a9d1b6 100644 --- a/src/invidious/playlists.cr +++ b/src/invidious/playlists.cr @@ -13,9 +13,7 @@ struct PlaylistVideo def to_xml(xml : XML::Builder) xml.element("entry") do - xml.element("id") { xml.text "yt:video:#{self.id}" } - xml.element("yt:videoId") { xml.text self.id } - xml.element("yt:channelId") { xml.text self.ucid } + xml.element("id") { xml.text "ni://invidious/sha-256;" + sha256("video/#{self.id}") } xml.element("title") { xml.text self.title } xml.element("link", rel: "alternate", href: "#{HOST_URL}/watch?v=#{self.id}") From 6b8189b46561c0614a2db87a0a722e268b4eca31 Mon Sep 17 00:00:00 2001 From: Samantaz Fox Date: Sun, 17 Apr 2022 15:53:10 +0200 Subject: [PATCH 4/6] Unify parameters of the different #to_xml methods --- src/invidious/channels/channels.cr | 12 ++---------- src/invidious/helpers/serialized_yt_data.cr | 12 ++---------- src/invidious/playlists.cr | 8 ++------ src/invidious/rss_atom.cr | 9 +-------- 4 files changed, 7 insertions(+), 34 deletions(-) diff --git a/src/invidious/channels/channels.cr b/src/invidious/channels/channels.cr index fb104779..9315d165 100644 --- a/src/invidious/channels/channels.cr +++ b/src/invidious/channels/channels.cr @@ -50,9 +50,7 @@ struct ChannelVideo end end - def to_xml(locale, query_params, xml : XML::Builder) - query_params["v"] = self.id - + def to_xml(xml : XML::Builder, query_params : HTTP::Params) xml.element("entry") do xml.element("id") { xml.text "ni://invidious/sha-256;" + sha256("video/#{self.id}") } xml.element("title") { xml.text self.title } @@ -65,7 +63,7 @@ struct ChannelVideo xml.element("content", type: "xhtml") do xml.element("div", xmlns: "http://www.w3.org/1999/xhtml") do - xml.element("a", href: "#{HOST_URL}/watch?#{query_params}") do + xml.element("a", href: "#{HOST_URL}/watch?v=#{self.id}&#{query_params}") do xml.element("img", src: "#{HOST_URL}/vi/#{self.id}/mqdefault.jpg") end end @@ -82,12 +80,6 @@ struct ChannelVideo end end - def to_xml(locale, _xml : Nil = nil) - XML.build do |xml| - to_xml(locale, xml) - end - end - def to_tuple {% begin %} { diff --git a/src/invidious/helpers/serialized_yt_data.cr b/src/invidious/helpers/serialized_yt_data.cr index 082fe97c..fc91031f 100644 --- a/src/invidious/helpers/serialized_yt_data.cr +++ b/src/invidious/helpers/serialized_yt_data.cr @@ -14,9 +14,7 @@ struct SearchVideo property premiere_timestamp : Time? property author_verified : Bool - def to_xml(auto_generated, query_params, xml : XML::Builder) - query_params["v"] = self.id - + def to_xml(xml : XML::Builder, query_params : HTTP::Params) xml.element("entry") do xml.element("id") { xml.text "ni://invidious/sha-256;" + sha256("video/#{self.id}") } xml.element("title") { xml.text self.title } @@ -29,7 +27,7 @@ struct SearchVideo xml.element("content", type: "xhtml") do xml.element("div", xmlns: "http://www.w3.org/1999/xhtml") do - xml.element("a", href: "#{HOST_URL}/watch?#{query_params}") do + xml.element("a", href: "#{HOST_URL}/watch?v=#{self.id}&#{query_params}") do xml.element("img", src: "#{HOST_URL}/vi/#{self.id}/mqdefault.jpg") end @@ -54,12 +52,6 @@ struct SearchVideo end end - def to_xml(auto_generated, query_params, _xml : Nil) - XML.build do |xml| - to_xml(auto_generated, query_params, xml) - end - end - def to_json(locale : String?, json : JSON::Builder) json.object do json.field "type", "video" diff --git a/src/invidious/playlists.cr b/src/invidious/playlists.cr index a4a9d1b6..839ba41e 100644 --- a/src/invidious/playlists.cr +++ b/src/invidious/playlists.cr @@ -11,7 +11,7 @@ struct PlaylistVideo property index : Int64 property live_now : Bool - def to_xml(xml : XML::Builder) + def to_xml(xml : XML::Builder, query_params : HTTP::Params) xml.element("entry") do xml.element("id") { xml.text "ni://invidious/sha-256;" + sha256("video/#{self.id}") } xml.element("title") { xml.text self.title } @@ -24,7 +24,7 @@ struct PlaylistVideo xml.element("content", type: "xhtml") do xml.element("div", xmlns: "http://www.w3.org/1999/xhtml") do - xml.element("a", href: "#{HOST_URL}/watch?v=#{self.id}") do + xml.element("a", href: "#{HOST_URL}/watch?v=#{self.id}&#{query_params}") do xml.element("img", src: "#{HOST_URL}/vi/#{self.id}/mqdefault.jpg") end end @@ -40,10 +40,6 @@ struct PlaylistVideo end end - def to_xml(_xml : Nil = nil) - XML.build { |xml| to_xml(xml) } - end - def to_json(json : JSON::Builder, index : Int32? = nil) json.object do json.field "title", self.title diff --git a/src/invidious/rss_atom.cr b/src/invidious/rss_atom.cr index 305238e1..35f0c90f 100644 --- a/src/invidious/rss_atom.cr +++ b/src/invidious/rss_atom.cr @@ -110,14 +110,7 @@ module Invidious::RssAtom 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 + videos.each { |video| video.to_xml(xml, params) } end end end From 0e40b23d46b18ef6967c5fd538bae4e1c506f568 Mon Sep 17 00:00:00 2001 From: Samantaz Fox Date: Sun, 17 Apr 2022 17:34:54 +0200 Subject: [PATCH 5/6] Unify the multiple to_xml methods in a single function --- src/invidious/channels/channels.cr | 30 ----------- src/invidious/helpers/serialized_yt_data.cr | 38 -------------- src/invidious/playlists.cr | 29 ----------- src/invidious/rss_atom.cr | 57 ++++++++++++++++++++- 4 files changed, 56 insertions(+), 98 deletions(-) diff --git a/src/invidious/channels/channels.cr b/src/invidious/channels/channels.cr index 9315d165..1c12cf19 100644 --- a/src/invidious/channels/channels.cr +++ b/src/invidious/channels/channels.cr @@ -50,36 +50,6 @@ struct ChannelVideo end end - def to_xml(xml : XML::Builder, query_params : HTTP::Params) - xml.element("entry") do - xml.element("id") { xml.text "ni://invidious/sha-256;" + sha256("video/#{self.id}") } - xml.element("title") { xml.text self.title } - xml.element("link", rel: "alternate", href: "#{HOST_URL}/watch?#{query_params}") - - xml.element("author") do - xml.element("name") { xml.text self.author } - xml.element("uri") { xml.text "#{HOST_URL}/channel/#{self.ucid}" } - end - - xml.element("content", type: "xhtml") do - xml.element("div", xmlns: "http://www.w3.org/1999/xhtml") do - xml.element("a", href: "#{HOST_URL}/watch?v=#{self.id}&#{query_params}") do - xml.element("img", src: "#{HOST_URL}/vi/#{self.id}/mqdefault.jpg") - end - end - end - - xml.element("published") { xml.text self.published.to_s("%Y-%m-%dT%H:%M:%S%:z") } - xml.element("updated") { xml.text self.updated.to_s("%Y-%m-%dT%H:%M:%S%:z") } - - xml.element("media:group") do - xml.element("media:title") { xml.text self.title } - xml.element("media:thumbnail", url: "#{HOST_URL}/vi/#{self.id}/mqdefault.jpg", - width: "320", height: "180") - end - end - end - def to_tuple {% begin %} { diff --git a/src/invidious/helpers/serialized_yt_data.cr b/src/invidious/helpers/serialized_yt_data.cr index fc91031f..89ff5dad 100644 --- a/src/invidious/helpers/serialized_yt_data.cr +++ b/src/invidious/helpers/serialized_yt_data.cr @@ -14,44 +14,6 @@ struct SearchVideo property premiere_timestamp : Time? property author_verified : Bool - def to_xml(xml : XML::Builder, query_params : HTTP::Params) - xml.element("entry") do - xml.element("id") { xml.text "ni://invidious/sha-256;" + sha256("video/#{self.id}") } - xml.element("title") { xml.text self.title } - xml.element("link", rel: "alternate", href: "#{HOST_URL}/watch?#{query_params}") - - xml.element("author") do - xml.element("name") { xml.text self.author } - xml.element("uri") { xml.text "#{HOST_URL}/channel/#{self.ucid}" } - end - - xml.element("content", type: "xhtml") do - xml.element("div", xmlns: "http://www.w3.org/1999/xhtml") do - xml.element("a", href: "#{HOST_URL}/watch?v=#{self.id}&#{query_params}") do - xml.element("img", src: "#{HOST_URL}/vi/#{self.id}/mqdefault.jpg") - end - - xml.element("p", style: "white-space:pre-wrap") do - xml.text html_to_content(self.description_html) - end - end - end - - xml.element("published") { xml.text self.published.to_s("%Y-%m-%dT%H:%M:%S%:z") } - - xml.element("media:group") do - xml.element("media:title") { xml.text self.title } - xml.element("media:thumbnail", url: "#{HOST_URL}/vi/#{self.id}/mqdefault.jpg", - width: "320", height: "180") - xml.element("media:description") { xml.text html_to_content(self.description_html) } - end - - xml.element("media:community") do - xml.element("media:statistics", views: self.views) - end - end - end - def to_json(locale : String?, json : JSON::Builder) json.object do json.field "type", "video" diff --git a/src/invidious/playlists.cr b/src/invidious/playlists.cr index 839ba41e..3d7cb5a6 100644 --- a/src/invidious/playlists.cr +++ b/src/invidious/playlists.cr @@ -11,35 +11,6 @@ struct PlaylistVideo property index : Int64 property live_now : Bool - def to_xml(xml : XML::Builder, query_params : HTTP::Params) - xml.element("entry") do - xml.element("id") { xml.text "ni://invidious/sha-256;" + sha256("video/#{self.id}") } - xml.element("title") { xml.text self.title } - xml.element("link", rel: "alternate", href: "#{HOST_URL}/watch?v=#{self.id}") - - xml.element("author") do - xml.element("name") { xml.text self.author } - xml.element("uri") { xml.text "#{HOST_URL}/channel/#{self.ucid}" } - end - - xml.element("content", type: "xhtml") do - xml.element("div", xmlns: "http://www.w3.org/1999/xhtml") do - xml.element("a", href: "#{HOST_URL}/watch?v=#{self.id}&#{query_params}") do - xml.element("img", src: "#{HOST_URL}/vi/#{self.id}/mqdefault.jpg") - end - end - end - - xml.element("published") { xml.text self.published.to_s("%Y-%m-%dT%H:%M:%S%:z") } - - xml.element("media:group") do - xml.element("media:title") { xml.text self.title } - xml.element("media:thumbnail", url: "#{HOST_URL}/vi/#{self.id}/mqdefault.jpg", - width: "320", height: "180") - end - end - end - def to_json(json : JSON::Builder, index : Int32? = nil) json.object do json.field "title", self.title diff --git a/src/invidious/rss_atom.cr b/src/invidious/rss_atom.cr index 35f0c90f..2b69d8a8 100644 --- a/src/invidious/rss_atom.cr +++ b/src/invidious/rss_atom.cr @@ -110,7 +110,62 @@ module Invidious::RssAtom end # Video entries - videos.each { |video| video.to_xml(xml, params) } + videos.each do |video| + xml.element("entry") { atom_video(xml, video, params) } + end + end + end + end + + def atom_video(xml : XML::Builder, video : AnyVideo, query_params : HTTP::Params) + # URLs that are reused below + video_url = "#{HOST_URL}/watch?v=#{video.id}&#{query_params}" + video_thumb = "#{HOST_URL}/vi/#{video.id}/mqdefault.jpg" + + description = video.is_a?(SearchVideo) ? video.description_html : "" + + xml.element("id") { xml.text "ni://invidious/sha-256;" + sha256("video/#{video.id}") } + xml.element("title") { xml.text video.title } + xml.element("link", rel: "alternate", href: video_url) + + xml.element("author") do + xml.element("name") { xml.text video.author } + xml.element("uri") { xml.text "#{HOST_URL}/channel/#{video.ucid}" } + end + + xml.element("content", type: "xhtml") do + xml.element("div", xmlns: "http://www.w3.org/1999/xhtml") do + # Link to video + xml.element("a", href: video_url) do + xml.element("img", src: video_thumb) + end + + # Video sescription (SearchVideo only) + if video.is_a?(SearchVideo) + xml.element("p", style: "white-space:pre-wrap") { xml.text description } + end + end + end + + # Feed creation (if available) and update (ChannelVideo only) dates + xml.element("published") { xml.text video.published.to_rfc3339 } + xml.element("updated") { xml.text video.updated.to_rfc3339 } if video.is_a?(ChannelVideo) + + # Media properties + xml.element("media:group") do + xml.element("media:title") { xml.text video.title } + xml.element("media:thumbnail", url: video_thumb, width: "320", height: "180") + + # Video sescription (SearchVideo only) + if video.is_a?(SearchVideo) + xml.element("media:description") { xml.text description } + end + end + + # Views count (all except PlaylistVideo) + if !video.is_a?(PlaylistVideo) + xml.element("media:community") do + xml.element("media:statistics", views: video.views) end end end From e6bc502104956437bb8f706aa14e0197db282ac3 Mon Sep 17 00:00:00 2001 From: Samantaz Fox Date: Tue, 7 Jun 2022 19:08:48 +0200 Subject: [PATCH 6/6] Minor fixes --- src/invidious/rss_atom.cr | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/invidious/rss_atom.cr b/src/invidious/rss_atom.cr index 2b69d8a8..2f1e44ea 100644 --- a/src/invidious/rss_atom.cr +++ b/src/invidious/rss_atom.cr @@ -59,9 +59,6 @@ module Invidious::RssAtom 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", @@ -111,7 +108,7 @@ module Invidious::RssAtom # Video entries videos.each do |video| - xml.element("entry") { atom_video(xml, video, params) } + xml.element("entry") { atom_video(xml, video, env.params.query) } end end end @@ -122,7 +119,7 @@ module Invidious::RssAtom video_url = "#{HOST_URL}/watch?v=#{video.id}&#{query_params}" video_thumb = "#{HOST_URL}/vi/#{video.id}/mqdefault.jpg" - description = video.is_a?(SearchVideo) ? video.description_html : "" + description = video.is_a?(SearchVideo) ? video.description_html.gsub('\n', "
\n") : "" xml.element("id") { xml.text "ni://invidious/sha-256;" + sha256("video/#{video.id}") } xml.element("title") { xml.text video.title }