diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/invidious.cr | 39 | ||||
| -rw-r--r-- | src/invidious/channels.cr | 270 | ||||
| -rw-r--r-- | src/invidious/comments.cr | 175 | ||||
| -rw-r--r-- | src/invidious/helpers/handlers.cr | 4 | ||||
| -rw-r--r-- | src/invidious/helpers/utils.cr | 8 | ||||
| -rw-r--r-- | src/invidious/playlists.cr | 64 | ||||
| -rw-r--r-- | src/invidious/search.cr | 164 | ||||
| -rw-r--r-- | src/invidious/views/login.ecr | 18 |
8 files changed, 285 insertions, 457 deletions
diff --git a/src/invidious.cr b/src/invidious.cr index 43d2ce72..d5be4ba1 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -24,6 +24,7 @@ require "sqlite3" require "xml" require "yaml" require "zip" +require "protodec/utils" require "./invidious/helpers/*" require "./invidious/*" @@ -1391,8 +1392,7 @@ get "/login" do |env| captcha_type ||= "image" tfa = env.params.query["tfa"]? - tfa ||= false - prompt = "" + prompt = nil templated "login" end @@ -1445,7 +1445,7 @@ post "/login" do |env| headers["Content-Type"] = "application/x-www-form-urlencoded;charset=utf-8" headers["Google-Accounts-XSRF"] = "1" - headers["User-Agent"] = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.75 Safari/537.36" + headers["User-Agent"] = random_user_agent response = client.post("/_/signin/sl/lookup", headers, login_req(lookup_req)) lookup_results = JSON.parse(response.body[5..-1]) @@ -1454,10 +1454,17 @@ post "/login" do |env| user_hash = lookup_results[0][2] + if token = env.params.body["token"]? + answer = env.params.body["answer"]? + captcha = {token, answer} + else + captcha = nil + end + challenge_req = { user_hash, nil, 1, nil, {1, nil, nil, nil, - {password, nil, true}, + {password, captcha, true}, }, {nil, nil, {2, 1, nil, 1, @@ -1485,11 +1492,14 @@ post "/login" do |env| next templated "error" end - # TODO: Handle Google's CAPTCHA - if captcha = challenge_results[0][-1]?.try &.[-1]?.try &.as_h?.try &.["5001"]?.try &.[-1].as_a? - error_message = "Unhandled CAPTCHA. Please try again later." - env.response.status_code = 401 - next templated "error" + if token = challenge_results[0][-1]?.try &.[-1]?.try &.as_h?.try &.["5001"]?.try &.[-1].as_a?.try &.[-1].as_s + account_type = "google" + captcha_type = "image" + prompt = nil + tfa = tfa_code + captcha = {tokens: [token], question: ""} + + next templated "login" end if challenge_results[0][-1]?.try &.[5] == "INCORRECT_ANSWER_ENTERED" @@ -1548,7 +1558,7 @@ post "/login" do |env| prompt = "Google verification code" end - tfa = true + tfa = nil captcha = nil next templated "login" end @@ -5447,7 +5457,7 @@ get "/videoplayback" do |env| client = make_client(URI.parse(host), region) - response = HTTP::Client::Response.new(403) + response = HTTP::Client::Response.new(500) 5.times do begin response = client.head(url, headers) @@ -5771,6 +5781,13 @@ get "/vi/:id/:name" do |env| end end +get "/Captcha" do |env| + client = make_client(LOGIN_URL) + response = client.get(env.request.resource) + env.response.headers["Content-Type"] = response.headers["Content-Type"] + response.body +end + # Undocumented, creates anonymous playlist with specified 'video_ids', max 50 videos get "/watch_videos" do |env| response = YT_POOL.client &.get(env.request.resource) diff --git a/src/invidious/channels.cr b/src/invidious/channels.cr index c79be352..4cf322b0 100644 --- a/src/invidious/channels.cr +++ b/src/invidious/channels.cr @@ -432,188 +432,108 @@ def fetch_channel_playlists(ucid, author, auto_generated, continuation, sort_by) end def produce_channel_videos_url(ucid, page = 1, auto_generated = nil, sort_by = "newest") + object = { + "80226972:embedded" => { + "2:string" => ucid, + "3:base64" => { + "2:string" => "videos", + "6:varint": 2_i64, + "7:varint": 1_i64, + "12:varint": 1_i64, + "13:string": "", + "23:varint": 0_i64, + }, + }, + } + if auto_generated seed = Time.unix(1525757349) - until seed >= Time.utc seed += 1.month end timestamp = seed - (page - 1).months - page = "#{timestamp.to_unix}" - switch = 0x36 + object["80226972:embedded"]["3:base64"].as(Hash)["4:varint"] = 0x36_i64 + object["80226972:embedded"]["3:base64"].as(Hash)["15:string"] = "#{timestamp.to_unix}" else - page = "#{page}" - switch = 0x00 + object["80226972:embedded"]["3:base64"].as(Hash)["4:varint"] = 0_i64 + object["80226972:embedded"]["3:base64"].as(Hash)["15:string"] = "#{page}" end - data = IO::Memory.new - data.write_byte 0x12 - data.write_byte 0x06 - data.print "videos" - - data.write Bytes[0x30, 0x02] - data.write Bytes[0x38, 0x01] - data.write Bytes[0x60, 0x01] - data.write Bytes[0x6a, 0x00] - data.write Bytes[0xb8, 0x01, 0x00] - - data.write Bytes[0x20, switch] - data.write_byte 0x7a - VarInt.to_io(data, page.bytesize) - data.print page - case sort_by when "newest" - # Empty tags can be omitted - # data.write(Bytes[0x18,0x00]) when "popular" - data.write Bytes[0x18, 0x01] + object["80226972:embedded"]["3:base64"].as(Hash)["3:varint"] = 0x01_i64 when "oldest" - data.write Bytes[0x18, 0x02] + object["80226972:embedded"]["3:base64"].as(Hash)["3:varint"] = 0x02_i64 end - data = Base64.urlsafe_encode(data) - cursor = URI.encode_www_form(data) - - data = IO::Memory.new - - data.write_byte 0x12 - VarInt.to_io(data, ucid.bytesize) - data.print ucid + object["80226972:embedded"]["3:string"] = Base64.urlsafe_encode(Protodec::Any.from_json(Protodec::Any.cast_json(object["80226972:embedded"]["3:base64"]))) + object["80226972:embedded"].delete("3:base64") - data.write_byte 0x1a - VarInt.to_io(data, cursor.bytesize) - data.print cursor + continuation = object.try { |i| Protodec::Any.cast_json(object) } + .try { |i| Protodec::Any.from_json(i) } + .try { |i| Base64.urlsafe_encode(i) } + .try { |i| URI.encode_www_form(i) } - data.rewind - - buffer = IO::Memory.new - buffer.write Bytes[0xe2, 0xa9, 0x85, 0xb2, 0x02] - VarInt.to_io(buffer, data.bytesize) - - IO.copy data, buffer - - continuation = Base64.urlsafe_encode(buffer) - continuation = URI.encode_www_form(continuation) - - url = "/browse_ajax?continuation=#{continuation}&gl=US&hl=en" - - return url + return "/browse_ajax?continuation=#{continuation}&gl=US&hl=en" end def produce_channel_playlists_url(ucid, cursor, sort = "newest", auto_generated = false) + object = { + "80226972:embedded" => { + "2:string" => ucid, + "3:base64" => { + "2:string" => "playlist", + "6:varint": 2_i64, + "7:varint": 1_i64, + "12:varint": 1_i64, + "13:string": "", + "23:varint": 0_i64, + }, + }, + } + if !auto_generated cursor = Base64.urlsafe_encode(cursor, false) end - - data = IO::Memory.new + object["80226972:embedded"]["3:base64"].as(Hash)["15:string"] = cursor if auto_generated - data.write Bytes[0x08, 0x0a] - end - - data.write Bytes[0x12, 0x09] - data.print "playlists" - - if auto_generated - data.write Bytes[0x20, 0x32] + object["80226972:embedded"]["3:base64"].as(Hash)["4:varint"] = 0x32_i64 else - # TODO: Look at 0x01, 0x00 + object["80226972:embedded"]["3:base64"].as(Hash)["4:varint"] = 1_i64 case sort when "oldest", "oldest_created" - data.write Bytes[0x18, 0x02] + object["80226972:embedded"]["3:base64"].as(Hash)["3:varint"] = 2_i64 when "newest", "newest_created" - data.write Bytes[0x18, 0x03] + object["80226972:embedded"]["3:base64"].as(Hash)["3:varint"] = 3_i64 when "last", "last_added" - data.write Bytes[0x18, 0x04] + object["80226972:embedded"]["3:base64"].as(Hash)["3:varint"] = 4_i64 end - - data.write Bytes[0x20, 0x01] end - data.write Bytes[0x30, 0x02] - data.write Bytes[0x38, 0x01] - data.write Bytes[0x60, 0x01] - data.write Bytes[0x6a, 0x00] - - data.write_byte 0x7a - VarInt.to_io(data, cursor.bytesize) - data.print cursor - - data.write Bytes[0xb8, 0x01, 0x00] - - data.rewind - data = Base64.urlsafe_encode(data) - continuation = URI.encode_www_form(data) - - data = IO::Memory.new - - data.write_byte 0x12 - VarInt.to_io(data, ucid.bytesize) - data.print ucid - - data.write_byte 0x1a - VarInt.to_io(data, continuation.bytesize) - data.print continuation - - data.rewind - - buffer = IO::Memory.new - buffer.write Bytes[0xe2, 0xa9, 0x85, 0xb2, 0x02] - VarInt.to_io(buffer, data.bytesize) + object["80226972:embedded"]["3:string"] = Base64.urlsafe_encode(Protodec::Any.from_json(Protodec::Any.cast_json(object["80226972:embedded"]["3:base64"]))) + object["80226972:embedded"].delete("3:base64") - IO.copy data, buffer + continuation = object.try { |i| Protodec::Any.cast_json(object) } + .try { |i| Protodec::Any.from_json(i) } + .try { |i| Base64.urlsafe_encode(i) } + .try { |i| URI.encode_www_form(i) } - continuation = Base64.urlsafe_encode(buffer) - continuation = URI.encode_www_form(continuation) - - url = "/browse_ajax?continuation=#{continuation}&gl=US&hl=en" - - return url + return "/browse_ajax?continuation=#{continuation}&gl=US&hl=en" end def extract_channel_playlists_cursor(url, auto_generated) - continuation = HTTP::Params.parse(URI.parse(url).query.not_nil!)["continuation"] - - continuation = URI.decode_www_form(continuation) - data = IO::Memory.new(Base64.decode(continuation)) - - # 0xe2 0xa9 0x85 0xb2 0x02 - data.pos += 5 - - continuation = Bytes.new(data.read_bytes(VarInt)) - data.read continuation - data = IO::Memory.new(continuation) - - data.read_byte # => 0x12 - ucid = Bytes.new(data.read_bytes(VarInt)) - data.read ucid - - data.read_byte # => 0x1a - inner_continuation = Bytes.new(data.read_bytes(VarInt)) - data.read inner_continuation - - continuation = String.new(inner_continuation) - continuation = URI.decode_www_form(continuation) - data = IO::Memory.new(Base64.decode(continuation)) - - # 0x12 0x09 playlists - data.pos += 11 - - until data.peek[0] == 0x7a - key = data.read_bytes(VarInt) - value = data.read_bytes(VarInt) - end - - data.pos += 1 # => 0x7a - cursor = Bytes.new(data.read_bytes(VarInt)) - data.read cursor - cursor = String.new(cursor) + cursor = URI.parse(url).query_params + .try { |i| Base64.decode(i["continuation"]) } + .try { |i| IO::Memory.new(i) } + .try { |i| Protodec::Any.parse(i) } + .try { |i| i["80226972:0:embedded"]["3:1:base64"]["15:7:string"].as_s } if !auto_generated cursor = URI.decode_www_form(cursor) - cursor = Base64.decode_string(cursor) + .try { |i| Base64.decode_string(i) } end return cursor @@ -621,12 +541,9 @@ end # TODO: Add "sort_by" def fetch_channel_community(ucid, continuation, locale, config, kemal_config, format, thin_mode) - headers = HTTP::Headers.new - headers["User-Agent"] = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36" - - response = YT_POOL.client &.get("/channel/#{ucid}/community?gl=US&hl=en", headers) + response = YT_POOL.client &.get("/channel/#{ucid}/community?gl=US&hl=en") if response.status_code == 404 - response = YT_POOL.client &.get("/user/#{ucid}/community?gl=US&hl=en", headers) + response = YT_POOL.client &.get("/user/#{ucid}/community?gl=US&hl=en") end if response.status_code == 404 @@ -648,6 +565,7 @@ def fetch_channel_community(ucid, continuation, locale, config, kemal_config, fo else continuation = produce_channel_community_continuation(ucid, continuation) + headers = HTTP::Headers.new headers["cookie"] = response.cookies.add_request_headers(headers)["cookie"] headers["content-type"] = "application/x-www-form-urlencoded" @@ -874,53 +792,31 @@ def fetch_channel_community(ucid, continuation, locale, config, kemal_config, fo end def produce_channel_community_continuation(ucid, cursor) - cursor = URI.encode_www_form(cursor) - - data = IO::Memory.new - - data.write_byte 0x12 - VarInt.to_io(data, ucid.bytesize) - data.print ucid - - data.write_byte 0x1a - VarInt.to_io(data, cursor.bytesize) - data.print cursor - - data.rewind - - buffer = IO::Memory.new - buffer.write Bytes[0xe2, 0xa9, 0x85, 0xb2, 0x02] - VarInt.to_io(buffer, data.size) - - IO.copy data, buffer - - continuation = Base64.urlsafe_encode(buffer) - continuation = URI.encode_www_form(continuation) + object = { + "80226972:embedded" => { + "2:string" => ucid, + "3:string" => cursor, + }, + } + + continuation = object.try { |i| Protodec::Any.cast_json(object) } + .try { |i| Protodec::Any.from_json(i) } + .try { |i| Base64.urlsafe_encode(i) } + .try { |i| URI.encode_www_form(i) } return continuation end def extract_channel_community_cursor(continuation) - continuation = URI.decode_www_form(continuation) - data = IO::Memory.new(Base64.decode(continuation)) - - # 0xe2 0xa9 0x85 0xb2 0x02 - data.pos += 5 - - continuation = Bytes.new(data.read_bytes(VarInt)) - data.read continuation - data = IO::Memory.new(continuation) - - data.read_byte # => 0x12 - ucid = Bytes.new(data.read_bytes(VarInt)) - data.read ucid - - data.read_byte # => 0x1a - until data.peek[0] == 'E'.ord - data.read_byte - end - - return URI.decode_www_form(data.gets_to_end) + cursor = URI.decode_www_form(continuation) + .try { |i| Base64.decode(i) } + .try { |i| IO::Memory.new(i) } + .try { |i| Protodec::Any.parse(i) } + .try { |i| Protodec::Any.cast_json(i["80226972:0:embedded"]["3:1:base64"].as_h) } + .try { |i| Protodec::Any.from_json(i) } + .try { |i| Base64.urlsafe_encode(i) } + + cursor end def get_about_info(ucid, locale) diff --git a/src/invidious/comments.cr b/src/invidious/comments.cr index d4fc2c6f..2d7bc1cf 100644 --- a/src/invidious/comments.cr +++ b/src/invidious/comments.cr @@ -572,129 +572,84 @@ def content_to_comment_html(content) end def extract_comment_cursor(continuation) - continuation = URI.decode_www_form(continuation) - data = IO::Memory.new(Base64.decode(continuation)) + cursor = URI.decode_www_form(continuation) + .try { |i| Base64.decode(i) } + .try { |i| IO::Memory.new(i) } + .try { |i| Protodec::Any.parse(i) } + .try { |i| i["6:2:embedded"]["1:0:string"].as_s } - # 0x12 0x26 - data.pos += 2 - - data.read_byte # => 0x12 - video_id = Bytes.new(data.read_bytes(VarInt)) - data.read video_id - - until data.peek[0] == 0x0a - data.read_byte - end - data.read_byte # 0x0a - data.read_byte if data.peek[0] == 0x0a - - cursor = Bytes.new(data.read_bytes(VarInt)) - data.read cursor - - String.new(cursor) + return cursor end def produce_comment_continuation(video_id, cursor = "", sort_by = "top") - data = IO::Memory.new - - data.write Bytes[0x12, 0x26] - - data.write_byte 0x12 - VarInt.to_io(data, video_id.bytesize) - data.print video_id - - data.write Bytes[0xc0, 0x01, 0x01] - data.write Bytes[0xc8, 0x01, 0x01] - data.write Bytes[0xe0, 0x01, 0x01] - - data.write Bytes[0xa2, 0x02, 0x0d] - data.write Bytes[0x28, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01] - - data.write Bytes[0x40, 0x00] - data.write Bytes[0x18, 0x06] - - if cursor.empty? - data.write Bytes[0x32] - VarInt.to_io(data, cursor.bytesize + video_id.bytesize + 8) - - data.write Bytes[0x22, video_id.bytesize + 4] - data.write Bytes[0x22, video_id.bytesize] - data.print video_id - - case sort_by - when "top" - data.write Bytes[0x30, 0x00] - when "new", "newest" - data.write Bytes[0x30, 0x01] - end - - data.write(Bytes[0x78, 0x02]) - else - data.write Bytes[0x32] - VarInt.to_io(data, cursor.bytesize + video_id.bytesize + 11) - - data.write_byte 0x0a - VarInt.to_io(data, cursor.bytesize) - data.print cursor - - data.write Bytes[0x22, video_id.bytesize + 4] - data.write Bytes[0x22, video_id.bytesize] - data.print video_id - - case sort_by - when "top" - data.write Bytes[0x30, 0x00] - when "new", "newest" - data.write Bytes[0x30, 0x01] - end + object = { + "2:embedded" => { + "2:string" => video_id, + "24:varint" => 1_i64, + "25:varint" => 1_i64, + "28:varint" => 1_i64, + "36:embedded" => { + "5:varint" => -1_i64, + "8:varint" => 0_i64, + }, + }, + "3:varint" => 6_i64, + "6:embedded" => { + "1:string" => cursor, + "4:embedded" => { + "4:string" => video_id, + "6:varint" => 0_i64, + }, + "5:varint" => 20_i64, + }, + } - data.write Bytes[0x28, 0x14] + case sort_by + when "top" + object["6:embedded"].as(Hash)["4:embedded"].as(Hash)["6:varint"] = 0_i64 + when "new", "newest" + object["6:embedded"].as(Hash)["4:embedded"].as(Hash)["6:varint"] = 1_i64 end - continuation = Base64.urlsafe_encode(data) - continuation = URI.encode_www_form(continuation) + continuation = object.try { |i| Protodec::Any.cast_json(object) } + .try { |i| Protodec::Any.from_json(i) } + .try { |i| Base64.urlsafe_encode(i) } + .try { |i| URI.encode_www_form(i) } return continuation end def produce_comment_reply_continuation(video_id, ucid, comment_id) - data = IO::Memory.new - - data.write Bytes[0x12, 0x26] - - data.write_byte 0x12 - VarInt.to_io(data, video_id.size) - data.print video_id - - data.write Bytes[0xc0, 0x01, 0x01] - data.write Bytes[0xc8, 0x01, 0x01] - data.write Bytes[0xe0, 0x01, 0x01] - - data.write Bytes[0xa2, 0x02, 0x0d] - data.write Bytes[0x28, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01] - - data.write Bytes[0x40, 0x00] - data.write Bytes[0x18, 0x06] - - data.write(Bytes[0x32, ucid.size + video_id.size + comment_id.size + 16]) - data.write(Bytes[0x1a, ucid.size + video_id.size + comment_id.size + 14]) - - data.write_byte 0x12 - VarInt.to_io(data, comment_id.size) - data.print comment_id - - data.write(Bytes[0x22, 0x02, 0x08, 0x00]) # ? - - data.write(Bytes[ucid.size + video_id.size + 7]) - data.write(Bytes[ucid.size]) - data.print(ucid) - data.write(Bytes[0x32, video_id.size]) - data.print(video_id) - data.write(Bytes[0x40, 0x01]) - data.write(Bytes[0x48, 0x0a]) + object = { + "2:embedded" => { + "2:string" => video_id, + "24:varint" => 1_i64, + "25:varint" => 1_i64, + "28:varint" => 1_i64, + "36:embedded" => { + "5:varint" => -1_i64, + "8:varint" => 0_i64, + }, + }, + "3:varint" => 6_i64, + "6:embedded" => { + "3:embedded" => { + "2:string" => comment_id, + "4:embedded" => { + "1:varint" => 0_i64, + }, + "5:string" => ucid, + "6:string" => video_id, + "8:varint" => 1_i64, + "9:varint" => 10_i64, + }, + }, + } - continuation = Base64.urlsafe_encode(data.to_slice) - continuation = URI.encode_www_form(continuation) + continuation = object.try { |i| Protodec::Any.cast_json(object) } + .try { |i| Protodec::Any.from_json(i) } + .try { |i| Base64.urlsafe_encode(i) } + .try { |i| URI.encode_www_form(i) } return continuation end diff --git a/src/invidious/helpers/handlers.cr b/src/invidious/helpers/handlers.cr index a3dfd062..456618cf 100644 --- a/src/invidious/helpers/handlers.cr +++ b/src/invidious/helpers/handlers.cr @@ -226,15 +226,15 @@ end class HTTP::Client private def handle_response(response) - if @socket.is_a?(OpenSSL::SSL::Socket::Client) + if @socket.is_a?(OpenSSL::SSL::Socket::Client) && @host.ends_with?("googlevideo.com") close unless response.keep_alive? || @socket.as(OpenSSL::SSL::Socket::Client).@in_buffer_rem.empty? + if @socket.as(OpenSSL::SSL::Socket::Client).@in_buffer_rem.empty? @socket = nil end else close unless response.keep_alive? end - response end end diff --git a/src/invidious/helpers/utils.cr b/src/invidious/helpers/utils.cr index 5ddc8375..7dd1adf3 100644 --- a/src/invidious/helpers/utils.cr +++ b/src/invidious/helpers/utils.cr @@ -1610,7 +1610,11 @@ struct HTTPPool end response = yield conn - conn.unset_proxy + + if region + conn.unset_proxy + end + response rescue ex conn = HTTPClient.new(url) @@ -1675,7 +1679,7 @@ def make_client(url : URI, region = nil) end def decode_length_seconds(string) - length_seconds = string.split(":").map { |a| a.to_i } + length_seconds = string.gsub(/[^0-9:]/, "").split(":").map &.to_i length_seconds = [0] * (3 - length_seconds.size) + length_seconds length_seconds = Time::Span.new(length_seconds[0], length_seconds[1], length_seconds[2]) length_seconds = length_seconds.total_seconds.to_i diff --git a/src/invidious/playlists.cr b/src/invidious/playlists.cr index 8d071acc..9c8afd3c 100644 --- a/src/invidious/playlists.cr +++ b/src/invidious/playlists.cr @@ -327,48 +327,28 @@ def produce_playlist_url(id, index) if id.starts_with? "UC" id = "UU" + id.lchop("UC") end - ucid = "VL" + id - - data = IO::Memory.new - data.write_byte 0x08 - VarInt.to_io(data, index) - - data.rewind - data = Base64.urlsafe_encode(data, false) - data = "PT:#{data}" - - continuation = IO::Memory.new - continuation.write_byte 0x7a - VarInt.to_io(continuation, data.bytesize) - continuation.print data - - data = Base64.urlsafe_encode(continuation) - cursor = URI.encode_www_form(data) - - data = IO::Memory.new - - data.write_byte 0x12 - VarInt.to_io(data, ucid.bytesize) - data.print ucid - - data.write_byte 0x1a - VarInt.to_io(data, cursor.bytesize) - data.print cursor - - data.rewind - - buffer = IO::Memory.new - buffer.write Bytes[0xe2, 0xa9, 0x85, 0xb2, 0x02] - VarInt.to_io(buffer, data.bytesize) - - IO.copy data, buffer - - continuation = Base64.urlsafe_encode(buffer) - continuation = URI.encode_www_form(continuation) - - url = "/browse_ajax?continuation=#{continuation}&gl=US&hl=en" - - return url + plid = "VL" + id + + data = {"1:varint" => index.to_i64} + .try { |i| Protodec::Any.cast_json(i) } + .try { |i| Protodec::Any.from_json(i) } + .try { |i| Base64.urlsafe_encode(i, padding: false) } + + object = { + "80226972:embedded" => { + "2:string" => plid, + "3:base64" => { + "15:string" => "PT:#{data}", + }, + }, + } + + continuation = object.try { |i| Protodec::Any.cast_json(object) } + .try { |i| Protodec::Any.from_json(i) } + .try { |i| Base64.urlsafe_encode(i) } + .try { |i| URI.encode_www_form(i) } + + return "/browse_ajax?continuation=#{continuation}&gl=US&hl=en" end def get_playlist(db, plid, locale, refresh = true, force_refresh = false) diff --git a/src/invidious/search.cr b/src/invidious/search.cr index 2acb4057..cc9c9634 100644 --- a/src/invidious/search.cr +++ b/src/invidious/search.cr @@ -281,152 +281,116 @@ end def produce_search_params(sort : String = "relevance", date : String = "", content_type : String = "", duration : String = "", features : Array(String) = [] of String) - header = IO::Memory.new - header.write Bytes[0x08] - header.write case sort + object = { + "1:varint" => 0_i64, + "2:embedded" => {} of String => Int64, + } + + case sort when "relevance" - Bytes[0x00] + object["1:varint"] = 0_i64 when "rating" - Bytes[0x01] + object["1:varint"] = 1_i64 when "upload_date", "date" - Bytes[0x02] + object["1:varint"] = 2_i64 when "view_count", "views" - Bytes[0x03] + object["1:varint"] = 3_i64 else raise "No sort #{sort}" end - body = IO::Memory.new - body.write case date + case date when "hour" - Bytes[0x08, 0x01] + object["2:embedded"].as(Hash)["1:varint"] = 1_i64 when "today" - Bytes[0x08, 0x02] + object["2:embedded"].as(Hash)["1:varint"] = 2_i64 when "week" - Bytes[0x08, 0x03] + object["2:embedded"].as(Hash)["1:varint"] = 3_i64 when "month" - Bytes[0x08, 0x04] + object["2:embedded"].as(Hash)["1:varint"] = 4_i64 when "year" - Bytes[0x08, 0x05] - else - Bytes.new(0) + object["2:embedded"].as(Hash)["1:varint"] = 5_i64 end - body.write case content_type + case content_type when "video" - Bytes[0x10, 0x01] + object["2:embedded"].as(Hash)["2:varint"] = 1_i64 when "channel" - Bytes[0x10, 0x02] + object["2:embedded"].as(Hash)["2:varint"] = 2_i64 when "playlist" - Bytes[0x10, 0x03] + object["2:embedded"].as(Hash)["2:varint"] = 3_i64 when "movie" - Bytes[0x10, 0x04] + object["2:embedded"].as(Hash)["2:varint"] = 4_i64 when "show" - Bytes[0x10, 0x05] + object["2:embedded"].as(Hash)["2:varint"] = 5_i64 when "all" - Bytes.new(0) + # else - Bytes[0x10, 0x01] + object["2:embedded"].as(Hash)["2:varint"] = 1_i64 end - body.write case duration + case duration when "short" - Bytes[0x18, 0x01] + object["2:embedded"].as(Hash)["3:varint"] = 1_i64 when "long" - Bytes[0x18, 0x12] - else - Bytes.new(0) + object["2:embedded"].as(Hash)["3:varint"] = 18_i64 end features.each do |feature| - body.write case feature + case feature when "hd" - Bytes[0x20, 0x01] + object["2:embedded"].as(Hash)["4:varint"] = 1_i64 when "subtitles" - Bytes[0x28, 0x01] + object["2:embedded"].as(Hash)["5:varint"] = 1_i64 when "creative_commons", "cc" - Bytes[0x30, 0x01] + object["2:embedded"].as(Hash)["6:varint"] = 1_i64 when "3d" - Bytes[0x38, 0x01] + object["2:embedded"].as(Hash)["7:varint"] = 1_i64 when "live", "livestream" - Bytes[0x40, 0x01] + object["2:embedded"].as(Hash)["8:varint"] = 1_i64 when "purchased" - Bytes[0x48, 0x01] + object["2:embedded"].as(Hash)["9:varint"] = 1_i64 when "4k" - Bytes[0x70, 0x01] + object["2:embedded"].as(Hash)["14:varint"] = 1_i64 when "360" - Bytes[0x78, 0x01] + object["2:embedded"].as(Hash)["15:varint"] = 1_i64 when "location" - Bytes[0xb8, 0x01, 0x01] + object["2:embedded"].as(Hash)["23:varint"] = 1_i64 when "hdr" - Bytes[0xc8, 0x01, 0x01] - else - Bytes.new(0) + object["2:embedded"].as(Hash)["25:varint"] = 1_i64 end end - token = header - if !body.empty? - token.write Bytes[0x12, body.bytesize] - token.write body.to_slice - end - - token = Base64.urlsafe_encode(token.to_slice) - token = URI.encode_www_form(token) + params = object.try { |i| Protodec::Any.cast_json(object) } + .try { |i| Protodec::Any.from_json(i) } + .try { |i| Base64.urlsafe_encode(i, padding: false) } - return token + return params end def produce_channel_search_url(ucid, query, page) - page = "#{page}" - - data = IO::Memory.new - data.write_byte 0x12 - data.write_byte 0x06 - data.print "search" - - data.write Bytes[0x30, 0x02] - data.write Bytes[0x38, 0x01] - data.write Bytes[0x60, 0x01] - data.write Bytes[0x6a, 0x00] - data.write Bytes[0xb8, 0x01, 0x00] - - data.write_byte 0x7a - VarInt.to_io(data, page.bytesize) - data.print page - - data.rewind - data = Base64.urlsafe_encode(data) - continuation = URI.encode_www_form(data) - - data = IO::Memory.new - - data.write_byte 0x12 - VarInt.to_io(data, ucid.bytesize) - data.print ucid - - data.write_byte 0x1a - VarInt.to_io(data, continuation.bytesize) - data.print continuation - - data.write_byte 0x5a - VarInt.to_io(data, query.bytesize) - data.print query - - data.rewind - - buffer = IO::Memory.new - buffer.write Bytes[0xe2, 0xa9, 0x85, 0xb2, 0x02] - VarInt.to_io(buffer, data.bytesize) - - IO.copy data, buffer - - continuation = Base64.urlsafe_encode(buffer) - continuation = URI.encode_www_form(continuation) - - url = "/browse_ajax?continuation=#{continuation}&gl=US&hl=en" - - return url + object = { + "80226972:embedded" => { + "2:string" => ucid, + "3:base64" => { + "2:string" => "search", + "6:varint" => 2_i64, + "7:varint" => 1_i64, + "12:varint" => 1_i64, + "13:string" => "", + "23:varint" => 0_i64, + "15:string" => "#{page}", + }, + "11:string" => query, + }, + } + + continuation = object.try { |i| Protodec::Any.cast_json(object) } + .try { |i| Protodec::Any.from_json(i) } + .try { |i| Base64.urlsafe_encode(i) } + .try { |i| URI.encode_www_form(i) } + + return "/browse_ajax?continuation=#{continuation}&gl=US&hl=en" end def process_search_query(query, page, user, region) diff --git a/src/invidious/views/login.ecr b/src/invidious/views/login.ecr index 8518ca81..4b5c7e6d 100644 --- a/src/invidious/views/login.ecr +++ b/src/invidious/views/login.ecr @@ -21,7 +21,8 @@ <hr> - <% if account_type == "invidious" %> + <% case account_type when %> + <% when "invidious" %> <form class="pure-form pure-form-stacked" action="/login?referer=<%= URI.encode_www_form(referer) %>&type=invidious" method="post"> <fieldset> <% if email %> @@ -84,7 +85,7 @@ <% end %> </fieldset> </form> - <% elsif account_type == "google" %> + <% when "google" %> <form class="pure-form pure-form-stacked" action="/login?referer=<%= URI.encode_www_form(referer) %>&type=google" method="post"> <fieldset> <% if email %> @@ -101,11 +102,22 @@ <input required class="pure-input-1" name="password" type="password" placeholder="<%= translate(locale, "Password") %>"> <% end %> - <% if tfa %> + <% if prompt %> <label for="tfa"><%= translate(locale, prompt) %> :</label> <input required class="pure-input-1" name="tfa" type="text" placeholder="<%= translate(locale, prompt) %>"> <% end %> + <% if tfa %> + <input type="hidden" name="tfa" value="<%= tfa %>"> + <% end %> + + <% if captcha %> + <img style="width:50%" src="/Captcha?v=2&ctoken=<%= captcha[:tokens][0] %>"/> + <input type="hidden" name="token" value="<%= captcha[:tokens][0] %>"> + <label for="answer"><%= translate(locale, "Answer") %> :</label> + <input type="text" name="answer" type="text" placeholder="<%= translate(locale, "Answer") %>"> + <% end %> + <button type="submit" class="pure-button pure-button-primary"><%= translate(locale, "Sign In") %></button> </fieldset> </form> |
