summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/invidious.cr110
-rw-r--r--src/invidious/helpers.cr19
-rw-r--r--src/invidious/views/channel.ecr28
-rw-r--r--src/invidious/views/subscriptions.ecr2
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>