diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/invidious.cr | 110 | ||||
| -rw-r--r-- | src/invidious/helpers.cr | 19 | ||||
| -rw-r--r-- | src/invidious/views/channel.ecr | 28 | ||||
| -rw-r--r-- | src/invidious/views/subscriptions.ecr | 2 |
4 files changed, 110 insertions, 49 deletions
diff --git a/src/invidious.cr b/src/invidious.cr index 35169ce1..3e5cf4a9 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -221,8 +221,9 @@ end decrypt_function = [] of {name: String, value: Int32} spawn do loop do + client = make_client(YT_URL) + begin - client = make_client(YT_URL) decrypt_function = update_decrypt_function(client) rescue ex end @@ -327,6 +328,7 @@ get "/watch" do |env| video = get_video(id, client, PG_DB) rescue ex error_message = ex.message + env.response.status_code = 500 next templated "error" end @@ -488,7 +490,9 @@ get "/api/v1/captions/:id" do |env| track_xml.xpath_nodes("//transcript/text").each do |node| start_time = node["start"].to_f.seconds - end_time = start_time + node["dur"].to_f.seconds + duration = node["dur"]?.try &.to_f.seconds + duration ||= start_time + end_time = start_time + duration start_time = "#{start_time.hours.to_s.rjust(2, '0')}:#{start_time.minutes.to_s.rjust(2, '0')}:#{start_time.seconds.to_s.rjust(2, '0')}.#{start_time.milliseconds.to_s.rjust(3, '0')}" end_time = "#{end_time.hours.to_s.rjust(2, '0')}:#{end_time.minutes.to_s.rjust(2, '0')}:#{end_time.seconds.to_s.rjust(2, '0')}.#{end_time.milliseconds.to_s.rjust(3, '0')}" @@ -558,18 +562,31 @@ get "/api/v1/comments/:id" do |env| response = client.post("/comment_service_ajax?action_get_comments=1&pbj=1&ctoken=#{ctoken}&continuation=#{continuation}&itct=#{itct}", headers, post_req).body response = JSON.parse(response) + env.response.content_type = "application/json" + + if !response["response"]["continuationContents"]? + halt env, status_code: 401 + end + response = response["response"]["continuationContents"] if response["commentRepliesContinuation"]? body = response["commentRepliesContinuation"] else body = response["itemSectionContinuation"] end - contents = body["contents"] + contents = body["contents"]? + if !contents + if format == "json" + next {"comments" => [] of String}.to_json + else + next {"content_html" => ""}.to_json + end + end comments = JSON.build do |json| json.object do if body["header"]? - comment_count = body["header"]["commentsHeaderRenderer"]["countText"]["simpleText"].as_s.rchop(" Comments").delete(',').to_i + comment_count = body["header"]["commentsHeaderRenderer"]["countText"]["simpleText"].as_s.delete("Comments,").to_i json.field "commentCount", comment_count end @@ -592,7 +609,8 @@ get "/api/v1/comments/:id" do |env| end content_text = item_comment["contentText"]["simpleText"]?.try &.as_s.rchop('\ufeff') - content_text ||= item_comment["contentText"]["runs"][0]["text"].as_s.rchop('\ufeff') + content_text ||= item_comment["contentText"]["runs"].as_a.map { |comment| comment["text"] } + .join("").rchop('\ufeff') json.field "author", item_comment["authorText"]["simpleText"] json.field "authorThumbnails" do @@ -614,7 +632,8 @@ get "/api/v1/comments/:id" do |env| json.field "commentId", item_comment["commentId"] if item_replies && !response["commentRepliesContinuation"]? - reply_count = item_replies["moreText"]["simpleText"].as_s.match(/View all (?<count>\d+) replies/).try &.["count"].to_i + reply_count = item_replies["moreText"]["simpleText"].as_s.match(/View all (?<count>\d+) replies/) + .try &.["count"].to_i reply_count ||= 1 continuation = item_replies["continuations"].as_a[0]["nextContinuationData"]["continuation"].as_s @@ -638,7 +657,6 @@ get "/api/v1/comments/:id" do |env| end end - env.response.content_type = "application/json" if format == "json" next comments else @@ -665,7 +683,6 @@ get "/api/v1/comments/:id" do |env| halt env, status_code: 404 end - env.response.content_type = "application/json" {"title" => reddit_thread.title, "permalink" => reddit_thread.permalink, "content_html" => content_html}.to_json @@ -1037,7 +1054,8 @@ get "/api/v1/channels/:ucid" do |env| total_views = total_views.content.rchop(" views").lchop(" • ").delete(",").to_i64 joined = Time.parse(joined.content.lchop("Joined "), "%b %-d, %Y", Time::Location.local) - latest_videos = PG_DB.query_all("SELECT * FROM channel_videos WHERE ucid = $1 ORDER BY published DESC LIMIT 15", channel.id, as: ChannelVideo) + latest_videos = PG_DB.query_all("SELECT * FROM channel_videos WHERE ucid = $1 ORDER BY published DESC LIMIT 15", + channel.id, as: ChannelVideo) channel_info = JSON.build do |json| json.object do @@ -1127,7 +1145,7 @@ end get "/api/v1/channels/:ucid/videos" do |env| ucid = env.params.url["ucid"] - page = env.params.query["page"]? + page = env.params.query["page"]?.try &.to_i? page ||= 1 url = produce_videos_url(ucid, page) @@ -1135,9 +1153,15 @@ get "/api/v1/channels/:ucid/videos" do |env| response = client.get(url) json = JSON.parse(response.body) + if !json["content_html"]? + env.response.content_type = "application/json" + next {"error" => "No videos or nonexistent channel"}.to_json + end + content_html = json["content_html"].as_s if content_html.empty? - halt env, status_code: 403 + env.response.content_type = "application/json" + next Hash(String, String).new.to_json end document = XML.parse_html(content_html) @@ -1312,6 +1336,15 @@ get "/embed/:id" do |env| rendered "embed" end +get "/results" do |env| + search_query = env.params.query["search_query"]? + if search_query + env.redirect "/search?q=#{URI.escape(search_query)}" + else + env.redirect "/" + end +end + get "/search" do |env| if env.params.query["q"]? query = env.params.query["q"] @@ -1319,7 +1352,7 @@ get "/search" do |env| next env.redirect "/" end - page = env.params.query["page"]?.try &.to_i + page = env.params.query["page"]?.try &.to_i? page ||= 1 client = make_client(YT_URL) @@ -1608,7 +1641,8 @@ post "/login" do |env| secure = false end - env.response.cookies["SID"] = HTTP::Cookie.new(name: "SID", value: sid, expires: Time.now + 2.years, secure: secure, http_only: true) + env.response.cookies["SID"] = HTTP::Cookie.new(name: "SID", value: sid, expires: Time.now + 2.years, + secure: secure, http_only: true) else error_message = "Invalid username or password" next templated "error" @@ -1639,7 +1673,8 @@ post "/login" do |env| secure = false end - env.response.cookies["SID"] = HTTP::Cookie.new(name: "SID", value: sid, expires: Time.now + 2.years, secure: secure, http_only: true) + env.response.cookies["SID"] = HTTP::Cookie.new(name: "SID", value: sid, expires: Time.now + 2.years, + secure: secure, http_only: true) end env.redirect referer @@ -1774,7 +1809,7 @@ get "/feed/subscriptions" do |env| max_results ||= env.params.query["max_results"]?.try &.to_i max_results ||= 40 - page = env.params.query["page"]?.try &.to_i + page = env.params.query["page"]?.try &.to_i? page ||= 1 if max_results < 0 @@ -1835,7 +1870,8 @@ get "/feed/subscriptions" do |env| end # TODO: Add option to disable picking out notifications from regular feed - notifications = PG_DB.query_one("SELECT notifications FROM users WHERE email = $1", user.email, as: Array(String)) + notifications = PG_DB.query_one("SELECT notifications FROM users WHERE email = $1", user.email, + as: Array(String)) notifications = videos.select { |v| notifications.includes? v.id } videos = videos - notifications @@ -1844,7 +1880,8 @@ get "/feed/subscriptions" do |env| videos = videos[0..max_results] end - PG_DB.exec("UPDATE users SET notifications = $1, updated = $2 WHERE id = $3", [] of String, Time.now, user.id) + PG_DB.exec("UPDATE users SET notifications = $1, updated = $2 WHERE id = $3", [] of String, Time.now, + user.id) user.notifications = [] of String env.set "user", user @@ -1864,6 +1901,11 @@ get "/feed/channel/:ucid" do |env| channel = get_channel(ucid, client, PG_DB, pull_all_videos: false) json = JSON.parse(response.body) + if !json["content_html"]? + error_message = "This channel does not exist or has no videos." + next templated "error" + end + content_html = json["content_html"].as_s if content_html.empty? halt env, status_code: 403 @@ -1934,7 +1976,8 @@ get "/feed/channel/:ucid" do |env| xml.element("media:group") do xml.element("media:title") { xml.text title } - xml.element("media:thumbnail", url: "https://i.ytimg.com/vi/#{video_id}/hqdefault.jpg", width: "480", height: "360") + xml.element("media:thumbnail", url: "https://i.ytimg.com/vi/#{video_id}/hqdefault.jpg", + width: "480", height: "360") xml.element("media:description") { xml.text description } end @@ -1965,7 +2008,7 @@ get "/feed/private" do |env| max_results = env.params.query["max_results"]?.try &.to_i max_results ||= 40 - page = env.params.query["page"]?.try &.to_i + page = env.params.query["page"]?.try &.to_i? page ||= 1 if max_results < 0 @@ -2020,7 +2063,8 @@ get "/feed/private" do |env| query = env.request.query.not_nil! feed = 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 + xml.element("feed", xmlns: "http://www.w3.org/2005/Atom", "xmlns:media": "http://search.yahoo.com/mrss/", + "xml:lang": "en-US") do xml.element("link", "type": "text/html", rel: "alternate", href: "#{scheme}#{host}/feed/subscriptions") xml.element("link", "type": "application/atom+xml", rel: "self", href: "#{scheme}#{host}#{path}?#{query}") xml.element("title") { xml.text "Invidious Private Feed for #{user.email}" } @@ -2043,7 +2087,8 @@ get "/feed/private" do |env| xml.element("media:group") do xml.element("media:title") { xml.text video.title } - xml.element("media:thumbnail", url: "https://i.ytimg.com/vi/#{video.id}/hqdefault.jpg", width: "480", height: "360") + xml.element("media:thumbnail", url: "https://i.ytimg.com/vi/#{video.id}/hqdefault.jpg", + width: "480", height: "360") end end end @@ -2100,7 +2145,8 @@ get "/modify_notifications" do |env| channel_req["channel_id"] = channel_id - client.post("/subscription_ajax?action_update_subscription_preferences=1", headers, HTTP::Params.encode(channel_req)).body + client.post("/subscription_ajax?action_update_subscription_preferences=1", headers, + HTTP::Params.encode(channel_req)).body end end @@ -2236,7 +2282,7 @@ get "/channel/:ucid" do |env| ucid = env.params.url["ucid"] - page = env.params.query["page"]?.try &.to_i + page = env.params.query["page"]?.try &.to_i? page ||= 1 client = make_client(YT_URL) @@ -2253,6 +2299,21 @@ get "/channel/:ucid" do |env| response = client.get(url) json = JSON.parse(response.body) + if !json["content_html"]? + error_message = "This channel does not exist or has no videos." + next templated "error" + end + + if json["content_html"].as_s.strip(" \n").empty? + rss = client.get("/feeds/videos.xml?channel_id=#{ucid}").body + rss = XML.parse_html(rss) + author = rss.xpath_node("//feed/author/name").not_nil!.content + + videos = [] of ChannelVideo + + next templated "channel" + end + document = XML.parse_html(json["content_html"].as_s) author = document.xpath_node(%q(//div[@class="pl-video-owner"]/a)).not_nil!.content @@ -2366,7 +2427,8 @@ get "/api/manifest/dash/id/:id" do |env| url = url.gsub("=", "/") xml.element("Representation", id: fmt["itag"], codecs: codecs, bandwidth: bandwidth) do - xml.element("AudioChannelConfiguration", schemeIdUri: "urn:mpeg:dash:23003:3:audio_channel_configuration:2011", value: "2") + xml.element("AudioChannelConfiguration", schemeIdUri: "urn:mpeg:dash:23003:3:audio_channel_configuration:2011", + value: "2") xml.element("BaseURL") { xml.text url } xml.element("SegmentBase", indexRange: fmt["init"]) do xml.element("Initialization", range: fmt["index"]) diff --git a/src/invidious/helpers.cr b/src/invidious/helpers.cr index d978ec29..26a1aaa2 100644 --- a/src/invidious/helpers.cr +++ b/src/invidious/helpers.cr @@ -305,7 +305,8 @@ def fetch_video(id, client) is_family_friendly = html.xpath_node(%q(//meta[@itemprop="isFamilyFriendly"])).not_nil!["content"] == "True" genre = html.xpath_node(%q(//meta[@itemprop="genre"])).not_nil!["content"] - video = Video.new(id, info, Time.now, title, views, likes, dislikes, wilson_score, published, description, nil, author, ucid, allowed_regions, is_family_friendly, genre) + video = Video.new(id, info, Time.now, title, views, likes, dislikes, wilson_score, published, description, + nil, author, ucid, allowed_regions, is_family_friendly, genre) return video end @@ -525,12 +526,13 @@ def template_youtube_comments(comments) if child["replies"]? replies_html = <<-END_HTML <div id="replies" class="pure-g"> - <div class="pure-u-md-1-24"></div> - <div class="pure-u-md-23-24"> - <p> - <a href="javascript:void(0)" data-continuation="#{child["replies"]["continuation"]}" - onclick="load_comments(this)">View #{child["replies"]["replyCount"]} replies</a> - </p> + <div class="pure-u-md-1-24"></div> + <div class="pure-u-md-23-24"> + <p> + <a href="javascript:void(0)" data-continuation="#{child["replies"]["continuation"]}" + onclick="load_comments(this)">View #{child["replies"]["replyCount"]} replies</a> + </p> + </div> </div> END_HTML end @@ -1081,7 +1083,8 @@ def generate_captcha(key) END_SVG challenge = "" - convert = Process.run(%(convert -density 1200 -resize 400x400 -background none svg:- png:-), shell: true, input: IO::Memory.new(clock_svg), output: Process::Redirect::Pipe) do |proc| + convert = Process.run(%(convert -density 1200 -resize 400x400 -background none svg:- png:-), shell: true, + input: IO::Memory.new(clock_svg), output: Process::Redirect::Pipe) do |proc| challenge = proc.output.gets_to_end challenge = Base64.strict_encode(challenge) challenge = "data:image/png;base64,#{challenge}" diff --git a/src/invidious/views/channel.ecr b/src/invidious/views/channel.ecr index 886ecc93..d5bd3121 100644 --- a/src/invidious/views/channel.ecr +++ b/src/invidious/views/channel.ecr @@ -2,7 +2,7 @@ <title><%= author %> - Invidious</title> <% end %> -<div class="pure-g" style="padding:1em;"> +<div class="pure-g h-box"> <div class="pure-u-2-3"> <h3><%= author %></h3> </div> @@ -13,27 +13,23 @@ </div> </div> +<p class="h-box"> <% if user %> <% if subscriptions.includes? ucid %> - <p> - <a href="/subscription_ajax?action_remove_subscriptions=1&c=<%= ucid %>"> - <b>Unsubscribe from <%= author %></b> - </a> - </p> + <a href="/subscription_ajax?action_remove_subscriptions=1&c=<%= ucid %>"> + <b>Unsubscribe from <%= author %></b> + </a> <% else %> - <p> - <a href="/subscription_ajax?action_create_subscription_to_channel=1&c=<%= ucid %>"> - <b>Subscribe to <%= author %></b> - </a> - </p> + <a href="/subscription_ajax?action_create_subscription_to_channel=1&c=<%= ucid %>"> + <b>Subscribe to <%= author %></b> + </a> <% end %> <% else %> - <p> - <a href="/login"> - <b>Login to subscribe to <%= author %></b> - </a> - </p> + <a href="/login"> + <b>Login to subscribe to <%= author %></b> + </a> <% end %> +</p> <% videos.each_slice(4) do |slice| %> <div class="pure-g"> diff --git a/src/invidious/views/subscriptions.ecr b/src/invidious/views/subscriptions.ecr index 54d8be62..484603b9 100644 --- a/src/invidious/views/subscriptions.ecr +++ b/src/invidious/views/subscriptions.ecr @@ -2,7 +2,7 @@ <title>Subscriptions - Invidious</title> <% end %> -<div class="pure-g" style="padding:1em;"> +<div class="pure-g h-box"> <div class="pure-u-2-3"> <h3> <a href="/subscription_manager">Manage subscriptions</a> |
