summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/invidious.cr42
-rw-r--r--src/invidious/comments.cr25
-rw-r--r--src/invidious/helpers/helpers.cr52
-rw-r--r--src/invidious/mixes.cr2
-rw-r--r--src/invidious/playlists.cr2
-rw-r--r--src/invidious/users.cr58
-rw-r--r--src/invidious/videos.cr26
-rw-r--r--src/invidious/views/components/player.ecr1
-rw-r--r--src/invidious/views/licenses.ecr153
-rw-r--r--src/invidious/views/preferences.ecr5
-rw-r--r--src/invidious/views/template.ecr1
-rw-r--r--src/invidious/views/watch.ecr49
12 files changed, 323 insertions, 93 deletions
diff --git a/src/invidious.cr b/src/invidious.cr
index c7de22fb..96af04fa 100644
--- a/src/invidious.cr
+++ b/src/invidious.cr
@@ -188,6 +188,10 @@ get "/" do |env|
templated "index"
end
+get "/licenses" do |env|
+ rendered "licenses"
+end
+
# Videos
get "/:id" do |env|
@@ -278,9 +282,7 @@ get "/watch" do |env|
if source == "youtube"
begin
- comments = fetch_youtube_comments(id, "", proxies, "html")
- comments = JSON.parse(comments)
- comment_html = template_youtube_comments(comments)
+ comment_html = JSON.parse(fetch_youtube_comments(id, "", proxies, "html"))["contentHtml"]
rescue ex
if preferences.comments[1] == "reddit"
comments, reddit_thread = fetch_reddit_comments(id)
@@ -299,16 +301,12 @@ get "/watch" do |env|
comment_html = replace_links(comment_html)
rescue ex
if preferences.comments[1] == "youtube"
- comments = fetch_youtube_comments(id, "", proxies, "html")
- comments = JSON.parse(comments)
- comment_html = template_youtube_comments(comments)
+ comment_html = JSON.parse(fetch_youtube_comments(id, "", proxies, "html"))["contentHtml"]
end
end
end
else
- comments = fetch_youtube_comments(id, "", proxies, "html")
- comments = JSON.parse(comments)
- comment_html = template_youtube_comments(comments)
+ comment_html = JSON.parse(fetch_youtube_comments(id, "", proxies, "html"))["contentHtml"]
end
comment_html ||= ""
@@ -961,6 +959,10 @@ post "/preferences" do |env|
autoplay ||= "off"
autoplay = autoplay == "on"
+ continue = env.params.body["continue"]?.try &.as(String)
+ continue ||= "off"
+ continue = continue == "on"
+
listen = env.params.body["listen"]?.try &.as(String)
listen ||= "off"
listen = listen == "on"
@@ -1020,6 +1022,7 @@ post "/preferences" do |env|
preferences = {
"video_loop" => video_loop,
"autoplay" => autoplay,
+ "continue" => continue,
"listen" => listen,
"speed" => speed,
"quality" => quality,
@@ -2041,26 +2044,7 @@ get "/api/v1/comments/:id" do |env|
halt env, status_code: 500, response: error_message
end
- if format == "json"
- next comments
- else
- comments = JSON.parse(comments)
- content_html = template_youtube_comments(comments)
-
- response = JSON.build do |json|
- json.object do
- json.field "contentHtml", content_html
-
- if comments["commentCount"]?
- json.field "commentCount", comments["commentCount"]
- else
- json.field "commentCount", 0
- end
- end
- end
-
- next response
- end
+ next comments
elsif source == "reddit"
begin
comments, reddit_thread = fetch_reddit_comments(id)
diff --git a/src/invidious/comments.cr b/src/invidious/comments.cr
index a699aaac..f4398b58 100644
--- a/src/invidious/comments.cr
+++ b/src/invidious/comments.cr
@@ -58,7 +58,7 @@ end
def fetch_youtube_comments(id, continuation, proxies, format)
client = make_client(YT_URL)
- html = client.get("/watch?v=#{id}&bpctr=#{Time.new.to_unix + 2000}&gl=US&hl=en&disable_polymer=1")
+ html = client.get("/watch?v=#{id}&gl=US&hl=en&disable_polymer=1&has_verified=1&bpctr=9999999999")
headers = HTTP::Headers.new
headers["cookie"] = html.cookies.add_request_headers(headers)["cookie"]
body = html.body
@@ -83,7 +83,7 @@ def fetch_youtube_comments(id, continuation, proxies, format)
proxy = HTTPProxy.new(proxy_host: proxy[:ip], proxy_port: proxy[:port])
proxy_client.set_proxy(proxy)
- response = proxy_client.get("/watch?v=#{id}&bpctr=#{Time.new.to_unix + 2000}&gl=US&hl=en&disable_polymer=1")
+ response = proxy_client.get("/watch?v=#{id}&gl=US&hl=en&disable_polymer=1&has_verified=1&bpctr=9999999999")
proxy_headers = HTTP::Headers.new
proxy_headers["cookie"] = response.cookies.add_request_headers(headers)["cookie"]
proxy_html = response.body
@@ -140,8 +140,8 @@ def fetch_youtube_comments(id, continuation, proxies, format)
headers["content-type"] = "application/x-www-form-urlencoded"
headers["x-client-data"] = "CIi2yQEIpbbJAQipncoBCNedygEIqKPKAQ=="
- headers["x-spf-previous"] = "https://www.youtube.com/watch?v=#{id}&bpctr=#{Time.new.to_unix + 2000}&gl=US&hl=en&disable_polymer=1"
- headers["x-spf-referer"] = "https://www.youtube.com/watch?v=#{id}&bpctr=#{Time.new.to_unix + 2000}&gl=US&hl=en&disable_polymer=1"
+ headers["x-spf-previous"] = "https://www.youtube.com/watch?v=#{id}&gl=US&hl=en&disable_polymer=1&has_verified=1&bpctr=9999999999"
+ headers["x-spf-referer"] = "https://www.youtube.com/watch?v=#{id}&gl=US&hl=en&disable_polymer=1&has_verified=1&bpctr=9999999999"
headers["x-youtube-client-name"] = "1"
headers["x-youtube-client-version"] = "2.20180719"
@@ -264,6 +264,23 @@ def fetch_youtube_comments(id, continuation, proxies, format)
end
end
+ if format == "html"
+ comments = JSON.parse(comments)
+ content_html = template_youtube_comments(comments)
+
+ comments = JSON.build do |json|
+ json.object do
+ json.field "contentHtml", content_html
+
+ if comments["commentCount"]?
+ json.field "commentCount", comments["commentCount"]
+ else
+ json.field "commentCount", 0
+ end
+ end
+ end
+ end
+
return comments
end
diff --git a/src/invidious/helpers/helpers.cr b/src/invidious/helpers/helpers.cr
index 877a9d32..92a2e1b1 100644
--- a/src/invidious/helpers/helpers.cr
+++ b/src/invidious/helpers/helpers.cr
@@ -389,55 +389,3 @@ def extract_items(nodeset, ucid = nil)
return items
end
-
-def create_response(user_id, operation, key, expire = 6.hours)
- expire = Time.now + expire
- nonce = Random::Secure.hex(4)
-
- challenge = "#{expire.to_unix}-#{nonce}-#{user_id}-#{operation}"
- token = OpenSSL::HMAC.digest(:sha256, key, challenge)
-
- challenge = Base64.urlsafe_encode(challenge)
- token = Base64.urlsafe_encode(token)
-
- return challenge, token
-end
-
-def validate_response(challenge, token, user_id, operation, key)
- if !challenge
- raise "Hidden field \"challenge\" is a required field"
- end
-
- if !token
- raise "Hidden field \"token\" is a required field"
- end
-
- challenge = Base64.decode_string(challenge)
- if challenge.split("-").size == 4
- expire, nonce, challenge_user_id, challenge_operation = challenge.split("-")
-
- expire = expire.to_i?
- expire ||= 0
- else
- raise "Invalid challenge"
- end
-
- challenge = OpenSSL::HMAC.digest(:sha256, HMAC_KEY, challenge)
- challenge = Base64.urlsafe_encode(challenge)
-
- if challenge != token
- raise "Invalid token"
- end
-
- if challenge_operation != operation
- raise "Invalid token"
- end
-
- if challenge_user_id != user_id
- raise "Invalid token"
- end
-
- if expire < Time.now.to_unix
- raise "Token is expired, please try again"
- end
-end
diff --git a/src/invidious/mixes.cr b/src/invidious/mixes.cr
index 66f7371d..688a8622 100644
--- a/src/invidious/mixes.cr
+++ b/src/invidious/mixes.cr
@@ -26,7 +26,7 @@ def fetch_mix(rdid, video_id, cookies = nil)
if cookies
headers = cookies.add_request_headers(headers)
end
- response = client.get("/watch?v=#{video_id}&list=#{rdid}&bpctr=#{Time.new.to_unix + 2000}&gl=US&hl=en", headers)
+ response = client.get("/watch?v=#{video_id}&list=#{rdid}&gl=US&hl=en&has_verified=1&bpctr=9999999999", headers)
yt_data = response.body.match(/window\["ytInitialData"\] = (?<data>.*);/)
if yt_data
diff --git a/src/invidious/playlists.cr b/src/invidious/playlists.cr
index cc149ea8..c1adb1d9 100644
--- a/src/invidious/playlists.cr
+++ b/src/invidious/playlists.cr
@@ -30,7 +30,7 @@ def fetch_playlist_videos(plid, page, video_count, continuation = nil)
client = make_client(YT_URL)
if continuation
- html = client.get("/watch?v=#{continuation}&list=#{plid}&bpctr=#{Time.new.to_unix + 2000}&gl=US&hl=en&disable_polymer=1")
+ html = client.get("/watch?v=#{continuation}&list=#{plid}&gl=US&hl=en&disable_polymer=1&has_verified=1&bpctr=9999999999")
html = XML.parse_html(html.body)
index = html.xpath_node(%q(//span[@id="playlist-current-index"])).try &.content.to_i?
diff --git a/src/invidious/users.cr b/src/invidious/users.cr
index b354306f..113fa1c2 100644
--- a/src/invidious/users.cr
+++ b/src/invidious/users.cr
@@ -70,7 +70,11 @@ class Preferences
JSON.mapping({
video_loop: Bool,
autoplay: Bool,
- listen: {
+ continue: {
+ type: Bool,
+ default: false,
+ },
+ listen: {
type: Bool,
default: false,
},
@@ -195,3 +199,55 @@ def create_user(sid, email, password)
return user
end
+
+def create_response(user_id, operation, key, expire = 6.hours)
+ expire = Time.now + expire
+ nonce = Random::Secure.hex(4)
+
+ challenge = "#{expire.to_unix}-#{nonce}-#{user_id}-#{operation}"
+ token = OpenSSL::HMAC.digest(:sha256, key, challenge)
+
+ challenge = Base64.urlsafe_encode(challenge)
+ token = Base64.urlsafe_encode(token)
+
+ return challenge, token
+end
+
+def validate_response(challenge, token, user_id, operation, key)
+ if !challenge
+ raise "Hidden field \"challenge\" is a required field"
+ end
+
+ if !token
+ raise "Hidden field \"token\" is a required field"
+ end
+
+ challenge = Base64.decode_string(challenge)
+ if challenge.split("-").size == 4
+ expire, nonce, challenge_user_id, challenge_operation = challenge.split("-")
+
+ expire = expire.to_i?
+ expire ||= 0
+ else
+ raise "Invalid challenge"
+ end
+
+ challenge = OpenSSL::HMAC.digest(:sha256, HMAC_KEY, challenge)
+ challenge = Base64.urlsafe_encode(challenge)
+
+ if challenge != token
+ raise "Invalid token"
+ end
+
+ if challenge_operation != operation
+ raise "Invalid token"
+ end
+
+ if challenge_user_id != user_id
+ raise "Invalid token"
+ end
+
+ if expire < Time.now.to_unix
+ raise "Token is expired, please try again"
+ end
+end
diff --git a/src/invidious/videos.cr b/src/invidious/videos.cr
index eaf2e17a..64e817dd 100644
--- a/src/invidious/videos.cr
+++ b/src/invidious/videos.cr
@@ -546,7 +546,7 @@ def fetch_video(id, proxies)
spawn do
client = make_client(YT_URL)
- html = client.get("/watch?v=#{id}&bpctr=#{Time.new.to_unix + 2000}&gl=US&hl=en&disable_polymer=1")
+ html = client.get("/watch?v=#{id}&gl=US&hl=en&disable_polymer=1&has_verified=1&bpctr=9999999999")
if md = html.headers["location"]?.try &.match(/v=(?<id>[a-zA-Z0-9_-]{11})/)
next html_channel.send(md["id"])
@@ -620,7 +620,7 @@ def fetch_video(id, proxies)
client.connect_timeout = 10.seconds
client.set_proxy(proxy)
- html = XML.parse_html(client.get("/watch?v=#{id}&bpctr=#{Time.new.to_unix + 2000}&gl=US&hl=en&disable_polymer=1").body)
+ html = XML.parse_html(client.get("/watch?v=#{id}&gl=US&hl=en&disable_polymer=1&has_verified=1&bpctr=9999999999").body)
info = HTTP::Params.parse(client.get("/get_video_info?video_id=#{id}&el=detailpage&ps=default&eurl=&gl=US&hl=en&disable_polymer=1").body)
if info["reason"]?
@@ -641,7 +641,19 @@ def fetch_video(id, proxies)
end
if info["reason"]?
- raise info["reason"]
+ html_info = html.to_s.match(/ytplayer\.config = (?<info>.*?);ytplayer\.load/).try &.["info"]
+ if html_info
+ html_info = JSON.parse(html_info)["args"].as_h
+ info.delete("reason")
+
+ html_info.each do |k, v|
+ info[k] = v.to_s
+ end
+ end
+
+ if info["reason"]?
+ raise info["reason"]
+ end
end
title = info["title"]
@@ -680,8 +692,7 @@ def fetch_video(id, proxies)
genre_url = "/channel/UClgRkhTL3_hImCAmdLfDE4g"
when "Education"
# Education channel is linked but does not exist
- # genre_url = "/channel/UC3yA8nDwraeOfnYfBWun83g"
- genre_url = ""
+ genre_url = "/channel/UC3yA8nDwraeOfnYfBWun83g"
end
genre_url ||= ""
@@ -718,6 +729,7 @@ end
def process_video_params(query, preferences)
autoplay = query["autoplay"]?.try &.to_i?
+ continue = query["continue"]?.try &.to_i?
listen = query["listen"]? && (query["listen"] == "true" || query["listen"] == "1").to_unsafe
preferred_captions = query["subtitles"]?.try &.split(",").map { |a| a.downcase }
quality = query["quality"]?
@@ -727,6 +739,7 @@ def process_video_params(query, preferences)
if preferences
autoplay ||= preferences.autoplay.to_unsafe
+ continue ||= preferences.continue.to_unsafe
listen ||= preferences.listen.to_unsafe
preferred_captions ||= preferences.captions
quality ||= preferences.quality
@@ -736,6 +749,7 @@ def process_video_params(query, preferences)
end
autoplay ||= 0
+ continue ||= 0
listen ||= 0
preferred_captions ||= [] of String
quality ||= "hd720"
@@ -744,6 +758,7 @@ def process_video_params(query, preferences)
volume ||= 100
autoplay = autoplay == 1
+ continue = continue == 1
listen = listen == 1
video_loop = video_loop == 1
@@ -774,6 +789,7 @@ def process_video_params(query, preferences)
params = {
autoplay: autoplay,
+ continue: continue,
controls: controls,
listen: listen,
preferred_captions: preferred_captions,
diff --git a/src/invidious/views/components/player.ecr b/src/invidious/views/components/player.ecr
index 0dbdb05f..51f7182a 100644
--- a/src/invidious/views/components/player.ecr
+++ b/src/invidious/views/components/player.ecr
@@ -1,5 +1,6 @@
<video style="width:100%" playsinline poster="<%= thumbnail %>" title="<%= HTML.escape(video.title) %>"
id="player" class="video-js"
+ onmouseenter='this["title"]=""'
<% if params[:autoplay] %>autoplay<% end %>
<% if params[:video_loop] %>loop<% end %>
<% if params[:controls] %>controls<% end %>>
diff --git a/src/invidious/views/licenses.ecr b/src/invidious/views/licenses.ecr
new file mode 100644
index 00000000..4ceff3f4
--- /dev/null
+++ b/src/invidious/views/licenses.ecr
@@ -0,0 +1,153 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+</head>
+
+<body>
+ <h1>JavaScript license information</h1>
+ <table id="jslicense-labels1">
+ <tr>
+ <td>
+ <a href="/js/dash.mediaplayer.min.js">dash.mediaplayer.min.js</a>
+ </td>
+
+ <td>
+ <a href="http://directory.fsf.org/wiki/License:BSD_3Clause">Modified-BSD</a>
+ </td>
+
+ <td>
+ <a href="https://unpkg.com/dashjs@2.9.0/dist/dash.mediaplayer.debug.js">source</a>
+ </td>
+ </tr>
+
+ <tr>
+ <td>
+ <a href="/js/silvermine-videojs-quality-selector.min.js">silvermine-videojs-quality-selector.min.js</a>
+ </td>
+
+ <td>
+ <a href="http://www.jclark.com/xml/copying.txt">Expat</a>
+ </td>
+
+ <td>
+ <a href="/js/silvermine-videojs-quality-selector.js">source</a>
+ </td>
+ </tr>
+
+ <tr>
+ <td>
+ <a href="/js/video.min.js">video.min.js</a>
+ </td>
+
+ <td>
+ <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache-2.0-only</a>
+ </td>
+
+ <td>
+ <a href="https://unpkg.com/video.js@6.12.1/dist/video.js">source</a>
+ </td>
+ </tr>
+
+ <tr>
+ <td>
+ <a href="/js/videojs-contrib-quality-levels.min.js">videojs-contrib-quality-levels.min.js</a>
+ </td>
+
+ <td>
+ <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache-2.0-only</a>
+ </td>
+
+ <td>
+ <a href="https://unpkg.com/videojs-contrib-quality-levels@2.0.7/dist/videojs-contrib-quality-levels.js">source</a>
+ </td>
+ </tr>
+
+ <tr>
+ <td>
+ <a href="/js/videojs-dash.min.js">videojs-dash.min.js</a>
+ </td>
+
+ <td>
+ <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache-2.0-only</a>
+ </td>
+
+ <td>
+ <a href="https://unpkg.com/videojs-contrib-dash@2.8.2/dist/videojs-dash.js">source</a>
+ </td>
+ </tr>
+
+ <tr>
+ <td>
+ <a href="/js/videojs-http-streaming.min.js">videojs-http-streaming.min.js</a>
+ </td>
+
+ <td>
+ <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache-2.0-only</a>
+ </td>
+
+ <td>
+ <a href="https://unpkg.com/@videojs/http-streaming@1.2.2/dist/videojs-http-streaming.js">source</a>
+ </td>
+ </tr>
+
+ <tr>
+ <td>
+ <a href="/js/videojs-markers.min.js">videojs-markers.min.js</a>
+ </td>
+
+ <td>
+ <a href="http://www.jclark.com/xml/copying.txt">Expat</a>
+ </td>
+
+ <td>
+ <a href="https://unpkg.com/videojs-markers@1.0.1/dist/videojs-markers.js">source</a>
+ </td>
+ </tr>
+
+ <tr>
+ <td>
+ <a href="/js/videojs-share.min.js">videojs-share.min.js</a>
+ </td>
+
+ <td>
+ <a href="http://www.jclark.com/xml/copying.txt">Expat</a>
+ </td>
+
+ <td>
+ <a href="https://unpkg.com/videojs-share@2.0.1/dist/videojs-share.js">source</a>
+ </td>
+ </tr>
+
+ <tr>
+ <td>
+ <a href="/js/videojs.hotkeys.min.js">videojs.hotkeys.min.js</a>
+ </td>
+
+ <td>
+ <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache-2.0-only</a>
+ </td>
+
+ <td>
+ <a href="/js/videojs.hotkeys.js">source</a>
+ </td>
+ </tr>
+
+ <tr>
+ <td>
+ <a href="/js/watch.js">watch.js</a>
+ </td>
+
+ <td>
+ <a href="https://www.gnu.org/licenses/agpl-3.0.html">GNU-AGPL-3.0-or-later</a>
+ </td>
+
+ <td>
+ <a href="/js/watch.js">source</a>
+ </td>
+ </tr>
+ </table>
+</body>
+</html> \ No newline at end of file
diff --git a/src/invidious/views/preferences.ecr b/src/invidious/views/preferences.ecr
index 72b1d609..f7da7540 100644
--- a/src/invidious/views/preferences.ecr
+++ b/src/invidious/views/preferences.ecr
@@ -24,6 +24,11 @@ function update_value(element) {
</div>
<div class="pure-control-group">
+ <label for="continue">Automatically play next video: </label>
+ <input name="continue" id="continue" type="checkbox" <% if user.preferences.continue %>checked<% end %>>
+ </div>
+
+ <div class="pure-control-group">
<label for="listen">Listen by default: </label>
<input name="listen" id="listen" type="checkbox" <% if user.preferences.listen %>checked<% end %>>
</div>
diff --git a/src/invidious/views/template.ecr b/src/invidious/views/template.ecr
index a6191d36..748a691f 100644
--- a/src/invidious/views/template.ecr
+++ b/src/invidious/views/template.ecr
@@ -92,6 +92,7 @@
</p>
<p>BTC: 356DpZyMXu6rYd55Yqzjs29n79kGKWcYrY</p>
<p>BCH: qq4ptclkzej5eza6a50et5ggc58hxsq5aylqut2npk</p>
+ <p>View <a rel="jslicense" href="/licenses">JavaScript license information</a>.</p>
</div>
</div>
<div class="pure-u-1 pure-u-md-2-24"></div>
diff --git a/src/invidious/views/watch.ecr b/src/invidious/views/watch.ecr
index f9d98681..ba3692e2 100644
--- a/src/invidious/views/watch.ecr
+++ b/src/invidious/views/watch.ecr
@@ -140,6 +140,15 @@
<% if !preferences || preferences && preferences.related_videos %>
<div class="h-box">
+
+ <% if !plid && !rvs.empty? %>
+ <div class="pure-control-group">
+ <label for="continue">Autoplay next video: </label>
+ <input name="continue" onclick="continue_autoplay(this)" id="continue" type="checkbox" <% if params[:continue] %>checked<% end %>>
+ </div>
+ <hr>
+ <% end %>
+
<% rvs.each do |rv| %>
<% if rv.has_key?("id") %>
<a href="/watch?v=<%= rv["id"] %>">
@@ -163,6 +172,46 @@
</div>
<script>
+<% if !rvs.empty? && !plid && params[:continue] %>
+player.on('ended', function() {
+ window.location.replace("/watch?v="
+ + "<%= rvs[0]?.try &.["id"] %>"
+ + "&continue=1"
+ <% if params[:listen] %>
+ + "&listen=1"
+ <% end %>
+ <% if params[:autoplay] %>
+ + "&autoplay=1"
+ <% end %>
+ <% if params[:speed] %>
+ + "&speed=<%= params[:speed] %>"
+ <% end %>
+ );
+});
+<% end %>
+
+function continue_autoplay(target) {
+ if (target.checked) {
+ player.on('ended', function() {
+ window.location.replace("/watch?v="
+ + "<%= rvs[0]?.try &.["id"] %>"
+ + "&continue=1"
+ <% if params[:listen] %>
+ + "&listen=1"
+ <% end %>
+ <% if params[:autoplay] %>
+ + "&autoplay=1"
+ <% end %>
+ <% if params[:speed] %>
+ + "&speed=<%= params[:speed] %>"
+ <% end %>
+ );
+ });
+ } else {
+ player.off('ended');
+ }
+}
+
function number_with_separator(val) {
while (/(\d+)(\d{3})/.test(val.toString())) {
val = val.toString().replace(/(\d+)(\d{3})/, "$1" + "," + "$2");