From 076eaa7635cc320518a2e96775ff50e3712a7e77 Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Sat, 24 Mar 2018 22:38:35 -0500 Subject: [PATCH] Add subscriptions --- src/helpers.cr | 50 ++++++++++++++++++++++++++++ src/invidious.cr | 65 +++++++++++++++++++++++++++++++++++-- src/views/layout.ecr | 13 ++++++-- src/views/subscriptions.ecr | 38 ++++++++++++++++++++++ 4 files changed, 161 insertions(+), 5 deletions(-) create mode 100644 src/views/subscriptions.ecr diff --git a/src/helpers.cr b/src/helpers.cr index a8de6b0ad..cd11baabc 100644 --- a/src/helpers.cr +++ b/src/helpers.cr @@ -54,6 +54,26 @@ class Video }) end +class InvidiousChannel + module XMLConverter + def self.from_rs(rs) + XML.parse_html(rs.read(String)) + end + end + + add_mapping({ + id: String, + rss: { + type: XML::Node, + default: XML.parse_html(""), + converter: InvidiousChannel::XMLConverter, + + }, + updated: Time, + author: String, + }) +end + class RedditSubmit JSON.mapping({ data: RedditSubmitData, @@ -464,3 +484,33 @@ def login_req(login_form, f_req) return HTTP::Params.encode(data) end + +def get_channel(id, client, db) + if db.query_one?("SELECT EXISTS (SELECT true FROM channels WHERE id = $1)", id, as: Bool) + channel = db.query_one("SELECT * FROM channels WHERE id = $1", id, as: InvidiousChannel) + + if Time.now - channel.updated > 1.hours + db.exec("DELETE FROM channels * WHERE id = $1", id) + channel = fetch_channel(id, client) + args = arg_array(channel.to_a) + db.exec("INSERT INTO channels VALUES (#{args})", channel.to_a) + end + else + channel = fetch_channel(id, client) + args = arg_array(channel.to_a) + db.exec("INSERT INTO channels VALUES (#{args})", channel.to_a) + end + + return channel +end + +def fetch_channel(id, client) + rss = client.get("/feeds/videos.xml?channel_id=#{id}").body + rss = XML.parse_html(rss) + + author = rss.xpath_node("//feed/author/name").not_nil!.content + + channel = InvidiousChannel.new(id, rss, Time.now, author) + + return channel +end diff --git a/src/invidious.cr b/src/invidious.cr index 969d22473..c9486e63a 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -419,21 +419,24 @@ post "/login" do |env| headers = login.cookies.add_request_headers(headers) # We are now logged in + host = URI.parse(env.request.headers["Host"]).host + login.cookies.each do |cookie| - host = URI.parse(env.request.headers["Host"]).host + cookie.secure = false cookie.extension = cookie.extension.not_nil!.gsub(".youtube.com", host) + cookie.extension = cookie.extension.not_nil!.gsub("Secure; ", "") end login.cookies.add_response_headers(env.response.headers) - env.redirect "/" + env.redirect "/feed/subscriptions" rescue ex error_message = "Login failed" next templated "error" end end -get "/logout" do |env| +get "/signout" do |env| env.request.cookies.each do |cookie| cookie.expires = Time.new(1990, 1, 1) end @@ -546,6 +549,62 @@ get "/api/manifest/dash/id/:id" do |env| manifest end +# Get subscriptions for authorized user +get "/feed/subscriptions" do |env| + authorized = env.get "authorized" + + if authorized + max_results = env.params.query["maxResults"]?.try &.to_i + max_results ||= 40 + + page = env.params.query["page"]?.try &.to_i + page ||= 1 + + client = get_client(youtube_pool) + + headers = HTTP::Headers.new + headers["Cookie"] = env.request.headers["Cookie"] + + feed = client.get("/subscription_manager?action_takeout=1", headers).body + + videos = Array(Hash(String, String | Time)).new + + feed = XML.parse_html(feed) + feed.xpath_nodes("//opml/outline/outline").each do |channel| + id = channel["xmlurl"][-24..-1] + rss = get_channel(id, client, PG_DB).rss + + rss.xpath_nodes("//feed/entry").each do |entry| + video = {} of String => String | Time + + video["id"] = entry.xpath_node("videoid").not_nil!.content + video["title"] = entry.xpath_node("title").not_nil!.content + video["published"] = Time.parse(entry.xpath_node("published").not_nil!.content, "%FT%X%z") + video["author"] = entry.xpath_node("author/name").not_nil!.content + video["ucid"] = entry.xpath_node("channelid").not_nil!.content + video["thumbnail"] = entry.xpath_node("group/thumbnail").not_nil!["url"].gsub(/hqdefault\.jpg$/, "mqdefault.jpg") + # video["thumbnail"] = video["thumbnail"].rstrip("hqdefault.jpg") + # video["thumbnail"] += "mqdefault.jpg" + + videos << video + end + end + + youtube_pool << client + + videos.sort_by! { |video| video["published"].as(Time).epoch } + videos.reverse! + + start = (page - 1)*max_results + stop = start + max_results - 1 + videos = videos[start..stop] + + templated "subscriptions" + else + env.redirect "/" + end +end + error 404 do |env| error_message = "404 Page not found" templated "error" diff --git a/src/views/layout.ecr b/src/views/layout.ecr index f0ce5a3fe..872f9a05a 100644 --- a/src/views/layout.ecr +++ b/src/views/layout.ecr @@ -26,9 +26,18 @@
<% if env.get "authorized" %> - Logout +
+
+ +
+
+
+
+ Sign out +
+
<% else %> - Login +
Login
<% end %>
diff --git a/src/views/subscriptions.ecr b/src/views/subscriptions.ecr new file mode 100644 index 000000000..3d97a8f44 --- /dev/null +++ b/src/views/subscriptions.ecr @@ -0,0 +1,38 @@ +<% content_for "header" do %> +Subscriptions - Invidious +<% end %> + +<% videos.each_slice(4) do |slice| %> +
+ <% slice.each do |video| %> +
+
+ "> + "/> +

<%= video["title"] %>

+
+

+ "><%= video["author"] %> +

+

+

Shared <%= video["published"].as(Time).to_s("%B %-d, %Y at %r") %>
+

+
+
+ <% end %> +
+<% end %> + +
+
+ <% if page > 1 %> + Previous page + <% else %> + Previous page + <% end %> +
+
+
+ Next page +
+
\ No newline at end of file