diff --git a/src/invidious.cr b/src/invidious.cr index 6b408dc6..958f95f7 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -50,6 +50,7 @@ PUBSUB_URL = URI.parse("https://pubsubhubbub.appspot.com") REDDIT_URL = URI.parse("https://www.reddit.com") TEXTCAPTCHA_URL = URI.parse("https://textcaptcha.com") YT_URL = URI.parse("https://www.youtube.com") +HOST_URL = make_host_url(CONFIG, Kemal.config) CHARS_SAFE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" TEST_IDS = {"AgbeGFYluEA", "BaW_jenozKc", "a9LDPn-MO4I", "ddFvjfvPnqk", "iqKdEhx-dD4"} @@ -202,10 +203,11 @@ spawn do end end -decrypt_function = [] of {SigProc, Int32} +DECRYPT_FUNCTION = [] of {SigProc, Int32} spawn do update_decrypt_function do |function| - decrypt_function = function + DECRYPT_FUNCTION.clear + function.each { |i| DECRYPT_FUNCTION << i } end end @@ -1351,16 +1353,14 @@ get "/opensearch.xml" do |env| locale = LOCALES[env.get("preferences").as(Preferences).locale]? env.response.content_type = "application/opensearchdescription+xml" - host = make_host_url(config, Kemal.config) - XML.build(indent: " ", encoding: "UTF-8") do |xml| xml.element("OpenSearchDescription", xmlns: "http://a9.com/-/spec/opensearch/1.1/") do xml.element("ShortName") { xml.text "Invidious" } xml.element("LongName") { xml.text "Invidious Search" } xml.element("Description") { xml.text "Search for videos, channels, and playlists on Invidious" } xml.element("InputEncoding") { xml.text "UTF-8" } - xml.element("Image", width: 48, height: 48, type: "image/x-icon") { xml.text "#{host}/favicon.ico" } - xml.element("Url", type: "text/html", method: "get", template: "#{host}/search?q={searchTerms}") + xml.element("Image", width: 48, height: 48, type: "image/x-icon") { xml.text "#{HOST_URL}/favicon.ico" } + xml.element("Url", type: "text/html", method: "get", template: "#{HOST_URL}/search?q={searchTerms}") end end end @@ -2473,8 +2473,6 @@ get "/subscription_manager" do |env| subscriptions.sort_by! { |channel| channel.author.downcase } if action_takeout - host_url = make_host_url(config, Kemal.config) - if format == "json" env.response.content_type = "application/json" env.response.headers["content-disposition"] = "attachment" @@ -2500,7 +2498,7 @@ get "/subscription_manager" do |env| if format == "newpipe" xmlUrl = "https://www.youtube.com/feeds/videos.xml?channel_id=#{channel.id}" else - xmlUrl = "#{host_url}/feed/channel/#{channel.id}" + xmlUrl = "#{HOST_URL}/feed/channel/#{channel.id}" end xml.element("outline", text: channel.author, title: channel.author, @@ -3179,25 +3177,23 @@ get "/feed/channel/:ucid" do |env| ) end - host_url = make_host_url(config, Kemal.config) - 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("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("title") { xml.text channel.author } - xml.element("link", rel: "alternate", href: "#{host_url}/channel/#{channel.ucid}") + xml.element("link", rel: "alternate", href: "#{HOST_URL}/channel/#{channel.ucid}") xml.element("author") do xml.element("name") { xml.text channel.author } - xml.element("uri") { xml.text "#{host_url}/channel/#{channel.ucid}" } + xml.element("uri") { xml.text "#{HOST_URL}/channel/#{channel.ucid}" } end videos.each do |video| - video.to_xml(host_url, channel.auto_generated, params, xml) + video.to_xml(channel.auto_generated, params, xml) end end end @@ -3231,19 +3227,18 @@ get "/feed/private" do |env| params = HTTP::Params.parse(env.params.query["params"]? || "") videos, notifications = get_subscription_feed(PG_DB, user, max_results, page) - host_url = make_host_url(config, Kemal.config) 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": "text/html", rel: "alternate", href: "#{HOST_URL}/feed/subscriptions") xml.element("link", "type": "application/atom+xml", rel: "self", - href: "#{host_url}#{env.request.resource}") + href: "#{HOST_URL}#{env.request.resource}") xml.element("title") { xml.text translate(locale, "Invidious Private Feed for `x`", user.email) } (notifications + videos).each do |video| - video.to_xml(locale, host_url, params, xml) + video.to_xml(locale, params, xml) end end end @@ -3257,8 +3252,6 @@ get "/feed/playlist/:plid" do |env| plid = env.params.url["plid"] params = HTTP::Params.parse(env.params.query["params"]? || "") - - host_url = make_host_url(config, Kemal.config) path = env.request.path if plid.starts_with? "IV" @@ -3269,18 +3262,18 @@ get "/feed/playlist/:plid" do |env| 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("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}") + xml.element("link", rel: "alternate", href: "#{HOST_URL}/playlist?list=#{plid}") xml.element("author") do xml.element("name") { xml.text playlist.author } end videos.each do |video| - video.to_xml(host_url, false, xml) + video.to_xml(false, xml) end end end @@ -3299,7 +3292,7 @@ get "/feed/playlist/:plid" do |env| when "url", "href" full_path = URI.parse(node[attribute.name]).full_path query_string_opt = full_path.starts_with?("/watch?v=") ? "&#{params}" : "" - node[attribute.name] = "#{host_url}#{full_path}#{query_string_opt}" + node[attribute.name] = "#{HOST_URL}#{full_path}#{query_string_opt}" else nil # Skip end end @@ -3308,7 +3301,7 @@ get "/feed/playlist/:plid" do |env| document = document.to_xml(options: XML::SaveOptions::NO_DECL) document.scan(/(?[^<]+)<\/uri>/).each do |match| - content = "#{host_url}#{URI.parse(match["url"]).full_path}" + content = "#{HOST_URL}#{URI.parse(match["url"]).full_path}" document = document.gsub(match[0], "#{content}") end @@ -3684,7 +3677,7 @@ get "/channel/:ucid/community" do |env| end begin - items = JSON.parse(fetch_channel_community(ucid, continuation, locale, config, Kemal.config, "json", thin_mode)) + items = JSON.parse(fetch_channel_community(ucid, continuation, locale, "json", thin_mode)) rescue ex env.response.status_code = 500 error_message = ex.message @@ -3737,7 +3730,6 @@ get "/api/v1/storyboards/:id" do |env| end storyboards = video.storyboards - width = env.params.query["width"]? height = env.params.query["height"]? @@ -3745,7 +3737,7 @@ get "/api/v1/storyboards/:id" do |env| response = JSON.build do |json| json.object do json.field "storyboards" do - generate_storyboards(json, id, storyboards, config, Kemal.config) + generate_storyboards(json, id, storyboards) end end end @@ -3775,8 +3767,7 @@ get "/api/v1/storyboards/:id" do |env| end_time = storyboard[:interval].milliseconds storyboard[:storyboard_count].times do |i| - host_url = make_host_url(config, Kemal.config) - url = storyboard[:url].gsub("$M", i).gsub("https://i9.ytimg.com", host_url) + url = storyboard[:url].gsub("$M", i).gsub("https://i9.ytimg.com", HOST_URL) storyboard[:storyboard_height].times do |j| storyboard[:storyboard_width].times do |k| @@ -4099,7 +4090,7 @@ get "/api/v1/videos/:id" do |env| next error_message end - video.to_json(locale, config, Kemal.config, decrypt_function) + video.to_json(locale) end get "/api/v1/trending" do |env| @@ -4121,7 +4112,7 @@ get "/api/v1/trending" do |env| videos = JSON.build do |json| json.array do trending.each do |video| - video.to_json(locale, config, Kemal.config, json) + video.to_json(locale, json) end end end @@ -4137,7 +4128,7 @@ get "/api/v1/popular" do |env| JSON.build do |json| json.array do popular_videos.each do |video| - video.to_json(locale, config, Kemal.config, json) + video.to_json(locale, json) end end end @@ -4178,7 +4169,7 @@ get "/api/v1/channels/:ucid" do |env| count = 0 else begin - videos, count = get_60_videos(channel.ucid, channel.author, page, channel.auto_generated, sort_by) + count, videos = get_60_videos(channel.ucid, channel.author, page, channel.auto_generated, sort_by) rescue ex error_message = {"error" => ex.message}.to_json env.response.status_code = 500 @@ -4247,7 +4238,7 @@ get "/api/v1/channels/:ucid" do |env| json.field "latestVideos" do json.array do videos.each do |video| - video.to_json(locale, config, Kemal.config, json) + video.to_json(locale, json) end end end @@ -4308,7 +4299,7 @@ end end begin - videos, count = get_60_videos(channel.ucid, channel.author, page, channel.auto_generated, sort_by) + count, videos = get_60_videos(channel.ucid, channel.author, page, channel.auto_generated, sort_by) rescue ex error_message = {"error" => ex.message}.to_json env.response.status_code = 500 @@ -4318,7 +4309,7 @@ end JSON.build do |json| json.array do videos.each do |video| - video.to_json(locale, config, Kemal.config, json) + video.to_json(locale, json) end end end @@ -4344,7 +4335,7 @@ end JSON.build do |json| json.array do videos.each do |video| - video.to_json(locale, config, Kemal.config, json) + video.to_json(locale, json) end end end @@ -4359,9 +4350,9 @@ end ucid = env.params.url["ucid"] continuation = env.params.query["continuation"]? - sort_by = env.params.query["sort"]?.try &.downcase - sort_by ||= env.params.query["sort_by"]?.try &.downcase - sort_by ||= "last" + sort_by = env.params.query["sort"]?.try &.downcase || + env.params.query["sort_by"]?.try &.downcase || + "last" begin channel = get_about_info(ucid, locale) @@ -4383,9 +4374,7 @@ end json.field "playlists" do json.array do items.each do |item| - if item.is_a?(SearchPlaylist) - item.to_json(locale, config, Kemal.config, json) - end + item.to_json(locale, json) if item.is_a?(SearchPlaylist) end end end @@ -4414,7 +4403,7 @@ end # sort_by = env.params.query["sort_by"]?.try &.downcase begin - fetch_channel_community(ucid, continuation, locale, config, Kemal.config, format, thin_mode) + fetch_channel_community(ucid, continuation, locale, format, thin_mode) rescue ex env.response.status_code = 400 error_message = {"error" => ex.message}.to_json @@ -4440,7 +4429,7 @@ get "/api/v1/channels/search/:ucid" do |env| JSON.build do |json| json.array do search_results.each do |item| - item.to_json(locale, config, Kemal.config, json) + item.to_json(locale, json) end end end @@ -4485,7 +4474,7 @@ get "/api/v1/search" do |env| JSON.build do |json| json.array do search_results.each do |item| - item.to_json(locale, config, Kemal.config, json) + item.to_json(locale, json) end end end @@ -4562,7 +4551,7 @@ end next error_message end - response = playlist.to_json(offset, locale, config, Kemal.config, continuation: continuation) + response = playlist.to_json(offset, locale, continuation: continuation) if format == "html" response = JSON.parse(response) @@ -4626,7 +4615,7 @@ get "/api/v1/mixes/:rdid" do |env| json.field "videoThumbnails" do json.array do - generate_thumbnails(json, video.id, config, Kemal.config) + generate_thumbnails(json, video.id) end end @@ -4661,7 +4650,7 @@ get "/api/v1/auth/notifications" do |env| topics = env.params.query["topics"]?.try &.split(",").uniq.first(1000) topics ||= [] of String - create_notification_stream(env, config, Kemal.config, decrypt_function, topics, connection_channel) + create_notification_stream(env, topics, connection_channel) end post "/api/v1/auth/notifications" do |env| @@ -4670,7 +4659,7 @@ post "/api/v1/auth/notifications" do |env| topics = env.params.body["topics"]?.try &.split(",").uniq.first(1000) topics ||= [] of String - create_notification_stream(env, config, Kemal.config, decrypt_function, topics, connection_channel) + create_notification_stream(env, topics, connection_channel) end get "/api/v1/auth/preferences" do |env| @@ -4714,7 +4703,7 @@ get "/api/v1/auth/feed" do |env| json.field "notifications" do json.array do notifications.each do |video| - video.to_json(locale, config, Kemal.config, json) + video.to_json(locale, json) end end end @@ -4722,7 +4711,7 @@ get "/api/v1/auth/feed" do |env| json.field "videos" do json.array do videos.each do |video| - video.to_json(locale, config, Kemal.config, json) + video.to_json(locale, json) end end end @@ -4794,7 +4783,7 @@ get "/api/v1/auth/playlists" do |env| JSON.build do |json| json.array do playlists.each do |playlist| - playlist.to_json(0, locale, config, Kemal.config, json) + playlist.to_json(0, locale, json) end end end @@ -4825,10 +4814,8 @@ post "/api/v1/auth/playlists" do |env| next error_message end - host_url = make_host_url(config, Kemal.config) - playlist = create_playlist(PG_DB, title, privacy, user) - env.response.headers["Location"] = "#{host_url}/api/v1/auth/playlists/#{playlist.id}" + env.response.headers["Location"] = "#{HOST_URL}/api/v1/auth/playlists/#{playlist.id}" env.response.status_code = 201 { "title" => title, @@ -4958,11 +4945,9 @@ post "/api/v1/auth/playlists/:plid/videos" do |env| PG_DB.exec("INSERT INTO playlist_videos VALUES (#{args})", args: video_array) PG_DB.exec("UPDATE playlists SET index = array_append(index, $1), video_count = video_count + 1, updated = $2 WHERE id = $3", playlist_video.index, Time.utc, plid) - host_url = make_host_url(config, Kemal.config) - - env.response.headers["Location"] = "#{host_url}/api/v1/auth/playlists/#{plid}/videos/#{playlist_video.index.to_u64.to_s(16).upcase}" + env.response.headers["Location"] = "#{HOST_URL}/api/v1/auth/playlists/#{plid}/videos/#{playlist_video.index.to_u64.to_s(16).upcase}" env.response.status_code = 201 - playlist_video.to_json(locale, config, Kemal.config, index: playlist.index.size) + playlist_video.to_json(locale, index: playlist.index.size) end delete "/api/v1/auth/playlists/:plid/videos/:index" do |env| @@ -5251,12 +5236,10 @@ get "/api/manifest/hls_variant/*" do |env| env.response.content_type = "application/x-mpegURL" env.response.headers.add("Access-Control-Allow-Origin", "*") - host_url = make_host_url(config, Kemal.config) - - manifest = manifest.body + manifest = response.body if local - manifest = manifest.gsub("https://www.youtube.com", host_url) + manifest = manifest.gsub("https://www.youtube.com", HOST_URL) manifest = manifest.gsub("index.m3u8", "index.m3u8?local=true") end @@ -5276,9 +5259,7 @@ get "/api/manifest/hls_playlist/*" do |env| env.response.content_type = "application/x-mpegURL" env.response.headers.add("Access-Control-Allow-Origin", "*") - host_url = make_host_url(config, Kemal.config) - - manifest = manifest.body + manifest = response.body if local manifest = manifest.gsub(/^https:\/\/r\d---.{11}\.c\.youtube\.com[^\n]*/m) do |match| @@ -5313,7 +5294,7 @@ get "/api/manifest/hls_playlist/*" do |env| raw_params["local"] = "true" - "#{host_url}/videoplayback?#{raw_params}" + "#{HOST_URL}/videoplayback?#{raw_params}" end end @@ -5784,7 +5765,7 @@ get "/vi/:id/:name" do |env| headers = HTTP::Headers{":authority" => "i.ytimg.com"} if name == "maxres.jpg" - build_thumbnails(id, config, Kemal.config).each do |thumb| + build_thumbnails(id).each do |thumb| if YT_POOL.client &.head("/vi/#{id}/#{thumb[:url]}.jpg", headers).status_code == 200 name = thumb[:url] + ".jpg" break diff --git a/src/invidious/channels.cr b/src/invidious/channels.cr index afc1528e..f1a57eee 100644 --- a/src/invidious/channels.cr +++ b/src/invidious/channels.cr @@ -9,14 +9,14 @@ struct InvidiousChannel end struct ChannelVideo - def to_json(locale, config, kemal_config, json : JSON::Builder) + def to_json(locale, json : JSON::Builder) json.object do json.field "type", "shortVideo" json.field "title", self.title json.field "videoId", self.id json.field "videoThumbnails" do - generate_thumbnails(json, self.id, config, Kemal.config) + generate_thumbnails(json, self.id) end json.field "lengthSeconds", self.length_seconds @@ -31,17 +31,17 @@ struct ChannelVideo end end - def to_json(locale, config, kemal_config, json : JSON::Builder | Nil = nil) + def to_json(locale, json : JSON::Builder | Nil = nil) if json - to_json(locale, config, kemal_config, json) + to_json(locale, json) else JSON.build do |json| - to_json(locale, config, kemal_config, json) + to_json(locale, json) end end end - def to_xml(locale, host_url, query_params, xml : XML::Builder) + def to_xml(locale, query_params, xml : XML::Builder) query_params["v"] = self.id xml.element("entry") do @@ -49,17 +49,17 @@ struct ChannelVideo xml.element("yt:videoId") { xml.text self.id } xml.element("yt:channelId") { xml.text self.ucid } xml.element("title") { xml.text self.title } - xml.element("link", rel: "alternate", href: "#{host_url}/watch?#{query_params}") + 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}" } + 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?#{query_params}") do - xml.element("img", src: "#{host_url}/vi/#{self.id}/mqdefault.jpg") + xml.element("a", href: "#{HOST_URL}/watch?#{query_params}") do + xml.element("img", src: "#{HOST_URL}/vi/#{self.id}/mqdefault.jpg") end end end @@ -69,18 +69,18 @@ struct ChannelVideo 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", + xml.element("media:thumbnail", url: "#{HOST_URL}/vi/#{self.id}/mqdefault.jpg", width: "320", height: "180") end end end - def to_xml(locale, config, kemal_config, xml : XML::Builder | Nil = nil) + def to_xml(locale, xml : XML::Builder | Nil = nil) if xml - to_xml(locale, config, kemal_config, xml) + to_xml(locale, xml) else XML.build do |xml| - to_xml(locale, config, kemal_config, xml) + to_xml(locale, xml) end end end @@ -557,7 +557,7 @@ def extract_channel_playlists_cursor(url, auto_generated) end # TODO: Add "sort_by" -def fetch_channel_community(ucid, continuation, locale, config, kemal_config, format, thin_mode) +def fetch_channel_community(ucid, continuation, locale, format, thin_mode) response = YT_POOL.client &.get("/channel/#{ucid}/community?gl=US&hl=en") if response.status_code != 200 response = YT_POOL.client &.get("/user/#{ucid}/community?gl=US&hl=en") @@ -708,7 +708,7 @@ def fetch_channel_community(ucid, continuation, locale, config, kemal_config, fo json.field "title", attachment["title"]["simpleText"].as_s json.field "videoId", video_id json.field "videoThumbnails" do - generate_thumbnails(json, video_id, config, kemal_config) + generate_thumbnails(json, video_id) end json.field "lengthSeconds", decode_length_seconds(attachment["lengthText"]["simpleText"].as_s) @@ -956,33 +956,17 @@ def get_about_info(ucid, locale) end def get_60_videos(ucid, author, page, auto_generated, sort_by = "newest") - count = 0 videos = [] of SearchVideo 2.times do |i| url = produce_channel_videos_url(ucid, page * 2 + (i - 1), auto_generated: auto_generated, sort_by: sort_by) - response = YT_POOL.client &.get(url) - json = JSON.parse(response.body) - - if json["content_html"]? && !json["content_html"].as_s.empty? - document = XML.parse_html(json["content_html"].as_s) - nodeset = document.xpath_nodes(%q(//li[contains(@class, "feed-item-container")])) - - if !json["load_more_widget_html"]?.try &.as_s.empty? - count += 30 - end - - if auto_generated - videos += extract_videos(nodeset) - else - videos += extract_videos(nodeset, ucid, author) - end - else - break - end + response = YT_POOL.client &.get(url, headers) + initial_data = JSON.parse(response.body).as_a.find &.["response"]? + break if !initial_data + videos.concat extract_videos(initial_data.as_h) end - return videos, count + return videos.size, videos end def get_latest_videos(ucid) diff --git a/src/invidious/helpers/helpers.cr b/src/invidious/helpers/helpers.cr index f6ba33cd..b572ee1c 100644 --- a/src/invidious/helpers/helpers.cr +++ b/src/invidious/helpers/helpers.cr @@ -727,13 +727,10 @@ def cache_annotation(db, id, annotations) end end - if has_legacy_annotations - # TODO: Update on conflict? - db.exec("INSERT INTO annotations VALUES ($1, $2) ON CONFLICT DO NOTHING", id, annotations) - end + db.exec("INSERT INTO annotations VALUES ($1, $2) ON CONFLICT DO NOTHING", id, annotations) if has_legacy_annotations end -def create_notification_stream(env, config, kemal_config, decrypt_function, topics, connection_channel) +def create_notification_stream(env, topics, connection_channel) connection = Channel(PQ::Notification).new(8) connection_channel.send({true, connection}) @@ -753,7 +750,7 @@ def create_notification_stream(env, config, kemal_config, decrypt_function, topi video = get_video(video_id, PG_DB) video.published = published - response = JSON.parse(video.to_json(locale, config, kemal_config, decrypt_function)) + response = JSON.parse(video.to_json(locale)) if fields_text = env.params.query["fields"]? begin @@ -787,7 +784,7 @@ def create_notification_stream(env, config, kemal_config, decrypt_function, topi when .match(/UC[A-Za-z0-9_-]{22}/) PG_DB.query_all("SELECT * FROM channel_videos WHERE ucid = $1 AND published > $2 ORDER BY published DESC LIMIT 15", topic, Time.unix(since.not_nil!), as: ChannelVideo).each do |video| - response = JSON.parse(video.to_json(locale, config, Kemal.config)) + response = JSON.parse(video.to_json(locale)) if fields_text = env.params.query["fields"]? begin @@ -829,7 +826,7 @@ def create_notification_stream(env, config, kemal_config, decrypt_function, topi video = get_video(video_id, PG_DB) video.published = Time.unix(published) - response = JSON.parse(video.to_json(locale, config, Kemal.config, decrypt_function)) + response = JSON.parse(video.to_json(locale)) if fields_text = env.params.query["fields"]? begin diff --git a/src/invidious/helpers/signatures.cr b/src/invidious/helpers/signatures.cr index f82cc8dd..0aaacd04 100644 --- a/src/invidious/helpers/signatures.cr +++ b/src/invidious/helpers/signatures.cr @@ -40,12 +40,12 @@ def fetch_decrypt_function(id = "CvFH_6DNRCY") return decrypt_function end -def decrypt_signature(fmt, op) +def decrypt_signature(fmt : Hash(String, JSON::Any)) return "" if !fmt["s"]? || !fmt["sp"]? - sp = fmt["sp"] - sig = fmt["s"].split("") - op.each do |proc, value| + sp = fmt["sp"].as_s + sig = fmt["s"].as_s.split("") + DECRYPT_FUNCTION.each do |proc, value| sig = proc.call(sig, value) end diff --git a/src/invidious/helpers/utils.cr b/src/invidious/helpers/utils.cr index a4bd1d54..a39a0b16 100644 --- a/src/invidious/helpers/utils.cr +++ b/src/invidious/helpers/utils.cr @@ -351,10 +351,8 @@ def subscribe_pubsub(topic, key, config) nonce = Random::Secure.hex(4) signature = "#{time}:#{nonce}" - host_url = make_host_url(config, Kemal.config) - body = { - "hub.callback" => "#{host_url}/feed/webhook/v1:#{time}:#{nonce}:#{OpenSSL::HMAC.hexdigest(:sha1, key, signature)}", + "hub.callback" => "#{HOST_URL}/feed/webhook/v1:#{time}:#{nonce}:#{OpenSSL::HMAC.hexdigest(:sha1, key, signature)}", "hub.topic" => "https://www.youtube.com/xml/feeds/videos.xml?#{topic}", "hub.verify" => "async", "hub.mode" => "subscribe", diff --git a/src/invidious/playlists.cr b/src/invidious/playlists.cr index 184329dc..fcf73dad 100644 --- a/src/invidious/playlists.cr +++ b/src/invidious/playlists.cr @@ -1,26 +1,26 @@ struct PlaylistVideo - def to_xml(host_url, auto_generated, xml : XML::Builder) + def to_xml(auto_generated, 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("title") { xml.text self.title } - xml.element("link", rel: "alternate", href: "#{host_url}/watch?v=#{self.id}") + xml.element("link", rel: "alternate", href: "#{HOST_URL}/watch?v=#{self.id}") xml.element("author") do if auto_generated xml.element("name") { xml.text self.author } - xml.element("uri") { xml.text "#{host_url}/channel/#{self.ucid}" } + 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}" } + xml.element("uri") { xml.text "#{HOST_URL}/channel/#{ucid}" } end 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}") do - xml.element("img", src: "#{host_url}/vi/#{self.id}/mqdefault.jpg") + xml.element("a", href: "#{HOST_URL}/watch?v=#{self.id}") do + xml.element("img", src: "#{HOST_URL}/vi/#{self.id}/mqdefault.jpg") end end end @@ -29,23 +29,23 @@ struct PlaylistVideo 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", + xml.element("media:thumbnail", url: "#{HOST_URL}/vi/#{self.id}/mqdefault.jpg", width: "320", height: "180") end end end - def to_xml(host_url, auto_generated, xml : XML::Builder? = nil) + def to_xml(auto_generated, xml : XML::Builder? = nil) if xml - to_xml(host_url, auto_generated, xml) + to_xml(auto_generated, xml) else XML.build do |json| - to_xml(host_url, auto_generated, xml) + to_xml(auto_generated, xml) end end end - def to_json(locale, config, kemal_config, json : JSON::Builder, index : Int32?) + def to_json(locale, json : JSON::Builder, index : Int32?) json.object do json.field "title", self.title json.field "videoId", self.id @@ -55,7 +55,7 @@ struct PlaylistVideo json.field "authorUrl", "/channel/#{self.ucid}" json.field "videoThumbnails" do - generate_thumbnails(json, self.id, config, kemal_config) + generate_thumbnails(json, self.id) end if index @@ -69,12 +69,12 @@ struct PlaylistVideo end end - def to_json(locale, config, kemal_config, json : JSON::Builder? = nil, index : Int32? = nil) + def to_json(locale, json : JSON::Builder? = nil, index : Int32? = nil) if json - to_json(locale, config, kemal_config, json, index: index) + to_json(locale, json, index: index) else JSON.build do |json| - to_json(locale, config, kemal_config, json, index: index) + to_json(locale, json, index: index) end end end @@ -93,7 +93,7 @@ struct PlaylistVideo end struct Playlist - def to_json(offset, locale, config, kemal_config, json : JSON::Builder, continuation : String? = nil) + def to_json(offset, locale, json : JSON::Builder, continuation : String? = nil) json.object do json.field "type", "playlist" json.field "title", self.title @@ -130,19 +130,19 @@ struct Playlist json.array do videos = get_playlist_videos(PG_DB, self, offset: offset, locale: locale, continuation: continuation) videos.each_with_index do |video, index| - video.to_json(locale, config, Kemal.config, json) + video.to_json(locale, json) end end end end end - def to_json(offset, locale, config, kemal_config, json : JSON::Builder? = nil, continuation : String? = nil) + def to_json(offset, locale, json : JSON::Builder? = nil, continuation : String? = nil) if json - to_json(offset, locale, config, kemal_config, json, continuation: continuation) + to_json(offset, locale, json, continuation: continuation) else JSON.build do |json| - to_json(offset, locale, config, kemal_config, json, continuation: continuation) + to_json(offset, locale, json, continuation: continuation) end end end @@ -172,7 +172,7 @@ enum PlaylistPrivacy end struct InvidiousPlaylist - def to_json(offset, locale, config, kemal_config, json : JSON::Builder, continuation : String? = nil) + def to_json(offset, locale, json : JSON::Builder, continuation : String? = nil) json.object do json.field "type", "invidiousPlaylist" json.field "title", self.title @@ -195,19 +195,19 @@ struct InvidiousPlaylist json.array do videos = get_playlist_videos(PG_DB, self, offset: offset, locale: locale, continuation: continuation) videos.each_with_index do |video, index| - video.to_json(locale, config, Kemal.config, json, offset + index) + video.to_json(locale, json, offset + index) end end end end end - def to_json(offset, locale, config, kemal_config, json : JSON::Builder? = nil, continuation : String? = nil) + def to_json(offset, locale, json : JSON::Builder? = nil, continuation : String? = nil) if json - to_json(offset, locale, config, kemal_config, json, continuation: continuation) + to_json(offset, locale, json, continuation: continuation) else JSON.build do |json| - to_json(offset, locale, config, kemal_config, json, continuation: continuation) + to_json(offset, locale, json, continuation: continuation) end end end diff --git a/src/invidious/search.cr b/src/invidious/search.cr index e8521629..7a88f316 100644 --- a/src/invidious/search.cr +++ b/src/invidious/search.cr @@ -1,5 +1,5 @@ struct SearchVideo - def to_xml(host_url, auto_generated, query_params, xml : XML::Builder) + def to_xml(auto_generated, query_params, xml : XML::Builder) query_params["v"] = self.id xml.element("entry") do @@ -7,22 +7,22 @@ struct SearchVideo xml.element("yt:videoId") { xml.text self.id } xml.element("yt:channelId") { xml.text self.ucid } xml.element("title") { xml.text self.title } - xml.element("link", rel: "alternate", href: "#{host_url}/watch?#{query_params}") + 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}" } + 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}" } + xml.element("uri") { xml.text "#{HOST_URL}/channel/#{ucid}" } end 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?#{query_params}") do - xml.element("img", src: "#{host_url}/vi/#{self.id}/mqdefault.jpg") + xml.element("a", href: "#{HOST_URL}/watch?#{query_params}") do + 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) } @@ -33,7 +33,7 @@ struct SearchVideo 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", + 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 @@ -44,17 +44,17 @@ struct SearchVideo end end - def to_xml(host_url, auto_generated, query_params, xml : XML::Builder | Nil = nil) + def to_xml(auto_generated, query_params, xml : XML::Builder | Nil = nil) if xml - to_xml(host_url, auto_generated, query_params, xml) + to_xml(HOST_URL, auto_generated, query_params, xml) else XML.build do |json| - to_xml(host_url, auto_generated, query_params, xml) + to_xml(HOST_URL, auto_generated, query_params, xml) end end end - def to_json(locale, config, kemal_config, json : JSON::Builder) + def to_json(locale, json : JSON::Builder) json.object do json.field "type", "video" json.field "title", self.title @@ -65,7 +65,7 @@ struct SearchVideo json.field "authorUrl", "/channel/#{self.ucid}" json.field "videoThumbnails" do - generate_thumbnails(json, self.id, config, kemal_config) + generate_thumbnails(json, self.id) end json.field "description", html_to_content(self.description_html) @@ -78,15 +78,20 @@ struct SearchVideo json.field "liveNow", self.live_now json.field "paid", self.paid json.field "premium", self.premium + json.field "isUpcoming", self.is_upcoming + + if self.premiere_timestamp + json.field "premiereTimestamp", self.premiere_timestamp.try &.to_unix + end end end - def to_json(locale, config, kemal_config, json : JSON::Builder | Nil = nil) + def to_json(locale, json : JSON::Builder | Nil = nil) if json - to_json(locale, config, kemal_config, json) + to_json(locale, json) else JSON.build do |json| - to_json(locale, config, kemal_config, json) + to_json(locale, json) end end end @@ -116,7 +121,7 @@ struct SearchPlaylistVideo end struct SearchPlaylist - def to_json(locale, config, kemal_config, json : JSON::Builder) + def to_json(locale, json : JSON::Builder) json.object do json.field "type", "playlist" json.field "title", self.title @@ -137,7 +142,7 @@ struct SearchPlaylist json.field "lengthSeconds", video.length_seconds json.field "videoThumbnails" do - generate_thumbnails(json, video.id, config, Kemal.config) + generate_thumbnails(json, video.id) end end end @@ -146,12 +151,12 @@ struct SearchPlaylist end end - def to_json(locale, config, kemal_config, json : JSON::Builder | Nil = nil) + def to_json(locale, json : JSON::Builder | Nil = nil) if json - to_json(locale, config, kemal_config, json) + to_json(locale, json) else JSON.build do |json| - to_json(locale, config, kemal_config, json) + to_json(locale, json) end end end @@ -168,7 +173,7 @@ struct SearchPlaylist end struct SearchChannel - def to_json(locale, config, kemal_config, json : JSON::Builder) + def to_json(locale, json : JSON::Builder) json.object do json.field "type", "channel" json.field "author", self.author @@ -198,12 +203,12 @@ struct SearchChannel end end - def to_json(locale, config, kemal_config, json : JSON::Builder | Nil = nil) + def to_json(locale, json : JSON::Builder | Nil = nil) if json - to_json(locale, config, kemal_config, json) + to_json(locale, json) else JSON.build do |json| - to_json(locale, config, kemal_config, json) + to_json(locale, json) end end end diff --git a/src/invidious/videos.cr b/src/invidious/videos.cr index 7e815ca1..ed5847e4 100644 --- a/src/invidious/videos.cr +++ b/src/invidious/videos.cr @@ -255,17 +255,20 @@ struct Video end end - def to_json(locale, config, kemal_config, decrypt_function, json : JSON::Builder) + def to_json(locale, json : JSON::Builder) json.object do json.field "type", "video" json.field "title", self.title json.field "videoId", self.id + + json.field "error", info["reason"] if info["reason"]? + json.field "videoThumbnails" do - generate_thumbnails(json, self.id, config, kemal_config) + generate_thumbnails(json, self.id) end json.field "storyboards" do - generate_storyboards(json, self.id, self.storyboards, config, kemal_config) + generate_storyboards(json, self.id, self.storyboards) end json.field "description", html_to_content(self.description_html) @@ -316,16 +319,12 @@ struct Video json.field "premiereTimestamp", self.premiere_timestamp.not_nil!.to_unix end - if player_response["streamingData"]?.try &.["hlsManifestUrl"]? - host_url = make_host_url(config, kemal_config) - - hlsvp = player_response["streamingData"]["hlsManifestUrl"].as_s - hlsvp = hlsvp.gsub("https://manifest.googlevideo.com", host_url) - + if hlsvp = self.hls_manifest_url + hlsvp = hlsvp.gsub("https://manifest.googlevideo.com", HOST_URL) json.field "hlsUrl", hlsvp end - json.field "dashUrl", "#{make_host_url(config, kemal_config)}/api/manifest/dash/id/#{id}" + json.field "dashUrl", "#{HOST_URL}/api/manifest/dash/id/#{id}" json.field "adaptiveFormats" do json.array do @@ -424,7 +423,7 @@ struct Video json.field "videoId", rv["id"] json.field "title", rv["title"] json.field "videoThumbnails" do - generate_thumbnails(json, rv["id"], config, kemal_config) + generate_thumbnails(json, rv["id"]) end json.field "author", rv["author"] @@ -457,12 +456,12 @@ struct Video end end - def to_json(locale, config, kemal_config, decrypt_function, json : JSON::Builder | Nil = nil) + def to_json(locale, json : JSON::Builder | Nil = nil) if json - to_json(locale, config, kemal_config, decrypt_function, json) + to_json(locale, json) else JSON.build do |json| - to_json(locale, config, kemal_config, decrypt_function, json) + to_json(locale, json) end end end @@ -1391,9 +1390,9 @@ def process_video_params(query, preferences) return params end -def build_thumbnails(id, config, kemal_config) +def build_thumbnails(id) return { - {name: "maxres", host: "#{make_host_url(config, kemal_config)}", url: "maxres", height: 720, width: 1280}, + {name: "maxres", host: "#{HOST_URL}", url: "maxres", height: 720, width: 1280}, {name: "maxresdefault", host: "https://i.ytimg.com", url: "maxresdefault", height: 720, width: 1280}, {name: "sddefault", host: "https://i.ytimg.com", url: "sddefault", height: 480, width: 640}, {name: "high", host: "https://i.ytimg.com", url: "hqdefault", height: 360, width: 480}, @@ -1405,9 +1404,9 @@ def build_thumbnails(id, config, kemal_config) } end -def generate_thumbnails(json, id, config, kemal_config) +def generate_thumbnails(json, id) json.array do - build_thumbnails(id, config, kemal_config).each do |thumbnail| + build_thumbnails(id).each do |thumbnail| json.object do json.field "quality", thumbnail[:name] json.field "url", "#{thumbnail[:host]}/vi/#{id}/#{thumbnail["url"]}.jpg" @@ -1418,7 +1417,7 @@ def generate_thumbnails(json, id, config, kemal_config) end end -def generate_storyboards(json, id, storyboards, config, kemal_config) +def generate_storyboards(json, id, storyboards) json.array do storyboards.each do |storyboard| json.object do diff --git a/src/invidious/views/watch.ecr b/src/invidious/views/watch.ecr index 708456f9..ae6341e0 100644 --- a/src/invidious/views/watch.ecr +++ b/src/invidious/views/watch.ecr @@ -3,23 +3,23 @@ "> - + - + - - + + - + - - - + + + <%= rendered "components/player_sources" %>