From a53388883001fcde5e7316116decfbc34a31f5e6 Mon Sep 17 00:00:00 2001 From: putara <38089082+putara@users.noreply.github.com> Date: Mon, 28 Jun 2021 00:00:14 +1200 Subject: [PATCH] decrypt n --- shard.lock | 4 ++++ shard.yml | 3 +++ src/invidious/helpers/signatures.cr | 37 +++++++++++++++++++++++++++++ src/invidious/videos.cr | 2 ++ 4 files changed, 46 insertions(+) diff --git a/shard.lock b/shard.lock index 35d1aefd..ea0b0e97 100644 --- a/shard.lock +++ b/shard.lock @@ -4,6 +4,10 @@ shards: git: https://github.com/crystal-lang/crystal-db.git version: 0.10.1 + duktape: + git: https://github.com/jessedoyle/duktape.cr.git + version: 1.0.0 + exception_page: git: https://github.com/crystal-loot/exception_page.git version: 0.1.5 diff --git a/shard.yml b/shard.yml index fca1ce02..4b7c3cdf 100644 --- a/shard.yml +++ b/shard.yml @@ -25,6 +25,9 @@ dependencies: lsquic: github: iv-org/lsquic.cr version: ~> 2.18.1-2 + duktape: + github: jessedoyle/duktape.cr + version: ~> 1.0.0 crystal: 1.0.0 diff --git a/src/invidious/helpers/signatures.cr b/src/invidious/helpers/signatures.cr index d8b1de65..6decbc11 100644 --- a/src/invidious/helpers/signatures.cr +++ b/src/invidious/helpers/signatures.cr @@ -1,9 +1,14 @@ +require "duktape/runtime" + alias SigProc = Proc(Array(String), Int32, Array(String)) struct DecryptFunction @decrypt_function = [] of {SigProc, Int32} @decrypt_time = Time.monotonic + @playerjs_decrypt_n_function = {} of String => String + @last_decrypted_n = {id: "", n: "", dec_n: ""} + def initialize(@use_polling = true) end @@ -70,4 +75,36 @@ struct DecryptFunction return "&#{sp}=#{sig.join("")}" end + + def overwrite_n(id : String, fmt : Hash(String, JSON::Any)) + uri = URI.parse(fmt["url"].as_s) + params = HTTP::Params.parse(uri.query.not_nil!) + return fmt["url"].as_s unless params["n"]? + n = params["n"] + + function_name = "(cache #1)" + if (@last_decrypted_n[:id] == id && @last_decrypted_n[:n] == n) + dec_n = @last_decrypted_n[:dec_n] + else + document = YT_POOL.client &.get("/watch?v=#{id}&gl=US&hl=en").body + playerjs = document.match(/src="(?\/s\/player\/[^\/]+\/player_ias[^\/]+\/en_US\/base.js)"/).not_nil!["url"] + function_name = "(cache #2)" + function_body = @playerjs_decrypt_n_function[playerjs]? + if (!function_body) + player = YT_POOL.client &.get(playerjs).body + function_name = player.match(/a\.get\("n"\)\)&&\(b=(?[a-zA-Z0-9]+)\(b\)/m).not_nil!["nfunc"] + function_body = player.match(/^#{Regex.escape(function_name)}=(?function\(\w\)\{.*?"enhanced_except_[^\}]+\}[^\}]+\})/m).not_nil!["body"] + @playerjs_decrypt_n_function[playerjs] = function_body + end + rt = Duktape::Runtime.new do |sbx| + sbx.eval! "var dec=#{function_body}" + end + dec_n = rt.call("dec", n).to_s + @last_decrypted_n = {id: id, n: n, dec_n: dec_n} + end + + LOGGER.debug("decrypt_n: #{id} fn = #{function_name} n = #{n} -> #{dec_n}") + params["n"] = dec_n + return URI.new(uri.scheme, uri.host, uri.port, uri.path, params).to_s + end end diff --git a/src/invidious/videos.cr b/src/invidious/videos.cr index 116aafc7..a6b953f7 100644 --- a/src/invidious/videos.cr +++ b/src/invidious/videos.cr @@ -586,6 +586,7 @@ struct Video fmt["url"] = JSON::Any.new("#{fmt["url"]}#{DECRYPT_FUNCTION.decrypt_signature(fmt)}") end + fmt["url"] = JSON::Any.new(DECRYPT_FUNCTION.overwrite_n(self.id, fmt)) fmt["url"] = JSON::Any.new("#{fmt["url"]}&host=#{URI.parse(fmt["url"].as_s).host}") fmt["url"] = JSON::Any.new("#{fmt["url"]}®ion=#{self.info["region"]}") if self.info["region"]? end @@ -605,6 +606,7 @@ struct Video fmt["url"] = JSON::Any.new("#{fmt["url"]}#{DECRYPT_FUNCTION.decrypt_signature(fmt)}") end + fmt["url"] = JSON::Any.new(DECRYPT_FUNCTION.overwrite_n(self.id, fmt)) fmt["url"] = JSON::Any.new("#{fmt["url"]}&host=#{URI.parse(fmt["url"].as_s).host}") fmt["url"] = JSON::Any.new("#{fmt["url"]}®ion=#{self.info["region"]}") if self.info["region"]? end