diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/invidious.cr | 42 | ||||
| -rw-r--r-- | src/invidious/comments.cr | 25 | ||||
| -rw-r--r-- | src/invidious/helpers/helpers.cr | 52 | ||||
| -rw-r--r-- | src/invidious/mixes.cr | 2 | ||||
| -rw-r--r-- | src/invidious/playlists.cr | 2 | ||||
| -rw-r--r-- | src/invidious/users.cr | 58 | ||||
| -rw-r--r-- | src/invidious/videos.cr | 26 | ||||
| -rw-r--r-- | src/invidious/views/components/player.ecr | 1 | ||||
| -rw-r--r-- | src/invidious/views/licenses.ecr | 153 | ||||
| -rw-r--r-- | src/invidious/views/preferences.ecr | 5 | ||||
| -rw-r--r-- | src/invidious/views/template.ecr | 1 | ||||
| -rw-r--r-- | src/invidious/views/watch.ecr | 49 |
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"); |
