mirror of https://github.com/iv-org/invidious.git
Add support for CONNECT proxy
This commit is contained in:
parent
ceb252986e
commit
50bab26a3a
|
@ -5568,7 +5568,7 @@ get "/videoplayback" do |env|
|
||||||
next env.redirect location
|
next env.redirect location
|
||||||
end
|
end
|
||||||
|
|
||||||
IO.copy(response.body_io, env.response)
|
IO.copy response.body_io, env.response
|
||||||
end
|
end
|
||||||
rescue ex
|
rescue ex
|
||||||
end
|
end
|
||||||
|
@ -5865,6 +5865,69 @@ get "/Captcha" do |env|
|
||||||
response.body
|
response.body
|
||||||
end
|
end
|
||||||
|
|
||||||
|
connect "*" do |env|
|
||||||
|
if CONFIG.proxy_address.empty?
|
||||||
|
env.response.status_code = 400
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
|
url = env.request.headers["Host"]?.try { |u| u.split(":") }
|
||||||
|
host = url.try &.[0]?
|
||||||
|
port = url.try &.[1]?
|
||||||
|
|
||||||
|
host = "www.google.com" if !host || host.empty?
|
||||||
|
port = "443" if !port || port.empty?
|
||||||
|
|
||||||
|
# if env.request.internal_uri
|
||||||
|
# env.request.internal_uri.not_nil!.path = "#{host}:#{port}"
|
||||||
|
# end
|
||||||
|
|
||||||
|
user, pass = env.request.headers["Proxy-Authorization"]?
|
||||||
|
.try { |i| i.lchop("Basic ") }
|
||||||
|
.try { |i| Base64.decode_string(i) }
|
||||||
|
.try &.split(":", 2) || {nil, nil}
|
||||||
|
|
||||||
|
if CONFIG.proxy_user != user || CONFIG.proxy_pass != pass
|
||||||
|
env.response.status_code = 403
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
|
begin
|
||||||
|
upstream = TCPSocket.new(host, port)
|
||||||
|
rescue ex
|
||||||
|
logger.puts("Exception: #{ex.message}")
|
||||||
|
env.response.status_code = 400
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
|
env.response.reset
|
||||||
|
env.response.upgrade do |downstream|
|
||||||
|
downstream = downstream.as(TCPSocket)
|
||||||
|
downstream.sync = true
|
||||||
|
|
||||||
|
spawn do
|
||||||
|
begin
|
||||||
|
bytes = 1
|
||||||
|
while bytes != 0
|
||||||
|
bytes = IO.copy upstream, downstream
|
||||||
|
end
|
||||||
|
rescue ex
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
begin
|
||||||
|
bytes = 1
|
||||||
|
while bytes != 0
|
||||||
|
bytes = IO.copy downstream, upstream
|
||||||
|
end
|
||||||
|
rescue ex
|
||||||
|
ensure
|
||||||
|
upstream.close
|
||||||
|
downstream.close
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Undocumented, creates anonymous playlist with specified 'video_ids', max 50 videos
|
# Undocumented, creates anonymous playlist with specified 'video_ids', max 50 videos
|
||||||
get "/watch_videos" do |env|
|
get "/watch_videos" do |env|
|
||||||
response = YT_POOL.client &.get(env.request.resource)
|
response = YT_POOL.client &.get(env.request.resource)
|
||||||
|
@ -5939,6 +6002,7 @@ end
|
||||||
public_folder "assets"
|
public_folder "assets"
|
||||||
|
|
||||||
Kemal.config.powered_by_header = false
|
Kemal.config.powered_by_header = false
|
||||||
|
add_handler ProxyHandler.new
|
||||||
add_handler FilteredCompressHandler.new
|
add_handler FilteredCompressHandler.new
|
||||||
add_handler APIHandler.new
|
add_handler APIHandler.new
|
||||||
add_handler AuthHandler.new
|
add_handler AuthHandler.new
|
||||||
|
|
|
@ -212,3 +212,32 @@ class DenyFrame < Kemal::Handler
|
||||||
call_next env
|
call_next env
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class ProxyHandler < Kemal::Handler
|
||||||
|
def call(env)
|
||||||
|
if env.request.headers["Proxy-Authorization"]? && env.request.method != "CONNECT"
|
||||||
|
user, pass = env.request.headers["Proxy-Authorization"]?
|
||||||
|
.try { |i| i.lchop("Basic ") }
|
||||||
|
.try { |i| Base64.decode_string(i) }
|
||||||
|
.try &.split(":", 2) || {nil, nil}
|
||||||
|
|
||||||
|
if CONFIG.proxy_user != user || CONFIG.proxy_pass != pass
|
||||||
|
env.response.status_code = 403
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
HTTP::Client.exec(env.request.method, "#{env.request.headers["Host"]?}#{env.request.resource}", env.request.headers, env.request.body) do |response|
|
||||||
|
response.headers.each do |key, value|
|
||||||
|
if !RESPONSE_HEADERS_BLACKLIST.includes?(key.downcase) && key.downcase != "transfer-encoding"
|
||||||
|
env.response.headers[key] = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
IO.copy response.body_io, env.response
|
||||||
|
end
|
||||||
|
env.response.close
|
||||||
|
return
|
||||||
|
else
|
||||||
|
call_next env
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
|
@ -263,6 +263,10 @@ struct Config
|
||||||
admin_email: {type: String, default: "omarroth@protonmail.com"}, # Email for bug reports
|
admin_email: {type: String, default: "omarroth@protonmail.com"}, # Email for bug reports
|
||||||
cookies: {type: HTTP::Cookies, default: HTTP::Cookies.new, converter: StringToCookies}, # Saved cookies in "name1=value1; name2=value2..." format
|
cookies: {type: HTTP::Cookies, default: HTTP::Cookies.new, converter: StringToCookies}, # Saved cookies in "name1=value1; name2=value2..." format
|
||||||
captcha_key: {type: String?, default: nil}, # Key for Anti-Captcha
|
captcha_key: {type: String?, default: nil}, # Key for Anti-Captcha
|
||||||
|
proxy_address: {type: String, default: ""},
|
||||||
|
proxy_port: {type: Int32, default: 8080},
|
||||||
|
proxy_user: {type: String, default: ""},
|
||||||
|
proxy_pass: {type: String, default: ""},
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -249,15 +249,34 @@ def bypass_captcha(captcha_key, logger)
|
||||||
end
|
end
|
||||||
|
|
||||||
headers = response.cookies.add_request_headers(HTTP::Headers.new)
|
headers = response.cookies.add_request_headers(HTTP::Headers.new)
|
||||||
|
captcha_client = HTTPClient.new(URI.parse("https://api.anti-captcha.com"))
|
||||||
response = JSON.parse(HTTP::Client.post("https://api.anti-captcha.com/createTask", body: {
|
captcha_client.family = CONFIG.force_resolve || Socket::Family::INET
|
||||||
|
if !CONFIG.proxy_address.empty?
|
||||||
|
response = JSON.parse(captcha_client.post("/createTask", body: {
|
||||||
|
"clientKey" => CONFIG.captcha_key,
|
||||||
|
"task" => {
|
||||||
|
"type" => "NoCaptchaTask",
|
||||||
|
"websiteURL" => "https://www.youtube.com#{path}",
|
||||||
|
"websiteKey" => site_key,
|
||||||
|
"proxyType" => "http",
|
||||||
|
"proxyAddress" => CONFIG.proxy_address,
|
||||||
|
"proxyPort" => CONFIG.proxy_port,
|
||||||
|
"proxyLogin" => CONFIG.proxy_user,
|
||||||
|
"proxyPassword" => CONFIG.proxy_pass,
|
||||||
|
"userAgent" => headers["user-agent"],
|
||||||
|
},
|
||||||
|
}.to_json).body)
|
||||||
|
else
|
||||||
|
response = JSON.parse(captcha_client.post("/createTask", body: {
|
||||||
"clientKey" => CONFIG.captcha_key,
|
"clientKey" => CONFIG.captcha_key,
|
||||||
"task" => {
|
"task" => {
|
||||||
"type" => "NoCaptchaTaskProxyless",
|
"type" => "NoCaptchaTaskProxyless",
|
||||||
"websiteURL" => "https://www.youtube.com/watch?v=CvFH_6DNRCY&gl=US&hl=en&disable_polymer=1&has_verified=1&bpctr=9999999999",
|
"websiteURL" => "https://www.youtube.com#{path}",
|
||||||
"websiteKey" => site_key,
|
"websiteKey" => site_key,
|
||||||
|
"userAgent" => headers["user-agent"],
|
||||||
},
|
},
|
||||||
}.to_json).body)
|
}.to_json).body)
|
||||||
|
end
|
||||||
|
|
||||||
raise response["error"].as_s if response["error"]?
|
raise response["error"].as_s if response["error"]?
|
||||||
task_id = response["taskId"].as_i
|
task_id = response["taskId"].as_i
|
||||||
|
@ -265,7 +284,7 @@ def bypass_captcha(captcha_key, logger)
|
||||||
loop do
|
loop do
|
||||||
sleep 10.seconds
|
sleep 10.seconds
|
||||||
|
|
||||||
response = JSON.parse(HTTP::Client.post("https://api.anti-captcha.com/getTaskResult", body: {
|
response = JSON.parse(captcha_client.post("/getTaskResult", body: {
|
||||||
"clientKey" => CONFIG.captcha_key,
|
"clientKey" => CONFIG.captcha_key,
|
||||||
"taskId" => task_id,
|
"taskId" => task_id,
|
||||||
}.to_json).body)
|
}.to_json).body)
|
||||||
|
@ -283,7 +302,11 @@ def bypass_captcha(captcha_key, logger)
|
||||||
yield response.cookies.select { |cookie| cookie.name != "PREF" }
|
yield response.cookies.select { |cookie| cookie.name != "PREF" }
|
||||||
elsif response.headers["Location"]?.try &.includes?("/sorry/index")
|
elsif response.headers["Location"]?.try &.includes?("/sorry/index")
|
||||||
location = response.headers["Location"].try { |u| URI.parse(u) }
|
location = response.headers["Location"].try { |u| URI.parse(u) }
|
||||||
headers = HTTP::Headers{":authority" => location.host.not_nil!}
|
headers = HTTP::Headers{
|
||||||
|
":authority" => location.host.not_nil!,
|
||||||
|
"origin" => "https://www.google.com",
|
||||||
|
"user-agent" => "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36",
|
||||||
|
}
|
||||||
response = YT_POOL.client &.get(location.full_path, headers)
|
response = YT_POOL.client &.get(location.full_path, headers)
|
||||||
|
|
||||||
html = XML.parse_html(response.body)
|
html = XML.parse_html(response.body)
|
||||||
|
@ -297,14 +320,32 @@ def bypass_captcha(captcha_key, logger)
|
||||||
|
|
||||||
captcha_client = HTTPClient.new(URI.parse("https://api.anti-captcha.com"))
|
captcha_client = HTTPClient.new(URI.parse("https://api.anti-captcha.com"))
|
||||||
captcha_client.family = CONFIG.force_resolve || Socket::Family::INET
|
captcha_client.family = CONFIG.force_resolve || Socket::Family::INET
|
||||||
|
if !CONFIG.proxy_address.empty?
|
||||||
|
response = JSON.parse(captcha_client.post("/createTask", body: {
|
||||||
|
"clientKey" => CONFIG.captcha_key,
|
||||||
|
"task" => {
|
||||||
|
"type" => "NoCaptchaTask",
|
||||||
|
"websiteURL" => location.to_s,
|
||||||
|
"websiteKey" => site_key,
|
||||||
|
"proxyType" => "http",
|
||||||
|
"proxyAddress" => CONFIG.proxy_address,
|
||||||
|
"proxyPort" => CONFIG.proxy_port,
|
||||||
|
"proxyLogin" => CONFIG.proxy_user,
|
||||||
|
"proxyPassword" => CONFIG.proxy_pass,
|
||||||
|
"userAgent" => headers["user-agent"],
|
||||||
|
},
|
||||||
|
}.to_json).body)
|
||||||
|
else
|
||||||
response = JSON.parse(captcha_client.post("/createTask", body: {
|
response = JSON.parse(captcha_client.post("/createTask", body: {
|
||||||
"clientKey" => CONFIG.captcha_key,
|
"clientKey" => CONFIG.captcha_key,
|
||||||
"task" => {
|
"task" => {
|
||||||
"type" => "NoCaptchaTaskProxyless",
|
"type" => "NoCaptchaTaskProxyless",
|
||||||
"websiteURL" => location.to_s,
|
"websiteURL" => location.to_s,
|
||||||
"websiteKey" => site_key,
|
"websiteKey" => site_key,
|
||||||
|
"userAgent" => headers["user-agent"],
|
||||||
},
|
},
|
||||||
}.to_json).body)
|
}.to_json).body)
|
||||||
|
end
|
||||||
|
|
||||||
raise response["error"].as_s if response["error"]?
|
raise response["error"].as_s if response["error"]?
|
||||||
task_id = response["taskId"].as_i
|
task_id = response["taskId"].as_i
|
||||||
|
@ -326,8 +367,7 @@ def bypass_captcha(captcha_key, logger)
|
||||||
|
|
||||||
inputs["g-recaptcha-response"] = response["solution"]["gRecaptchaResponse"].as_s
|
inputs["g-recaptcha-response"] = response["solution"]["gRecaptchaResponse"].as_s
|
||||||
headers["content-type"] = "application/x-www-form-urlencoded"
|
headers["content-type"] = "application/x-www-form-urlencoded"
|
||||||
headers["origin"] = "https://www.google.com"
|
headers["referer"] = location.to_s
|
||||||
headers["user-agent"] = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
|
|
||||||
|
|
||||||
response = YT_POOL.client &.post("/sorry/index", headers: headers, form: inputs)
|
response = YT_POOL.client &.post("/sorry/index", headers: headers, form: inputs)
|
||||||
headers = HTTP::Headers{
|
headers = HTTP::Headers{
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
def connect(path : String, &block : HTTP::Server::Context -> _)
|
||||||
|
Kemal::RouteHandler::INSTANCE.add_route("CONNECT", path, &block)
|
||||||
|
end
|
||||||
|
|
||||||
# See https://github.com/crystal-lang/crystal/issues/2963
|
# See https://github.com/crystal-lang/crystal/issues/2963
|
||||||
class HTTPProxy
|
class HTTPProxy
|
||||||
getter proxy_host : String
|
getter proxy_host : String
|
||||||
|
@ -124,7 +128,7 @@ def get_nova_proxies(country_code = "US")
|
||||||
client.connect_timeout = 10.seconds
|
client.connect_timeout = 10.seconds
|
||||||
|
|
||||||
headers = HTTP::Headers.new
|
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"
|
headers["User-Agent"] = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
|
||||||
headers["Accept"] = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"
|
headers["Accept"] = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"
|
||||||
headers["Accept-Language"] = "Accept-Language: en-US,en;q=0.9"
|
headers["Accept-Language"] = "Accept-Language: en-US,en;q=0.9"
|
||||||
headers["Host"] = "www.proxynova.com"
|
headers["Host"] = "www.proxynova.com"
|
||||||
|
@ -161,7 +165,7 @@ def get_spys_proxies(country_code = "US")
|
||||||
client.connect_timeout = 10.seconds
|
client.connect_timeout = 10.seconds
|
||||||
|
|
||||||
headers = HTTP::Headers.new
|
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"
|
headers["User-Agent"] = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
|
||||||
headers["Accept"] = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"
|
headers["Accept"] = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"
|
||||||
headers["Accept-Language"] = "Accept-Language: en-US,en;q=0.9"
|
headers["Accept-Language"] = "Accept-Language: en-US,en;q=0.9"
|
||||||
headers["Host"] = "spys.one"
|
headers["Host"] = "spys.one"
|
||||||
|
|
|
@ -2,11 +2,12 @@ require "lsquic"
|
||||||
require "pool/connection"
|
require "pool/connection"
|
||||||
|
|
||||||
def add_yt_headers(request)
|
def add_yt_headers(request)
|
||||||
request.headers["user-agent"] ||= "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.97 Safari/537.36"
|
return if request.resource.starts_with? "/sorry/index"
|
||||||
|
|
||||||
|
request.headers["user-agent"] ||= "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
|
||||||
request.headers["accept-charset"] ||= "ISO-8859-1,utf-8;q=0.7,*;q=0.7"
|
request.headers["accept-charset"] ||= "ISO-8859-1,utf-8;q=0.7,*;q=0.7"
|
||||||
request.headers["accept"] ||= "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
|
request.headers["accept"] ||= "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
|
||||||
request.headers["accept-language"] ||= "en-us,en;q=0.5"
|
request.headers["accept-language"] ||= "en-us,en;q=0.5"
|
||||||
return if request.resource.starts_with? "/sorry/index"
|
|
||||||
request.headers["x-youtube-client-name"] ||= "1"
|
request.headers["x-youtube-client-name"] ||= "1"
|
||||||
request.headers["x-youtube-client-version"] ||= "1.20180719"
|
request.headers["x-youtube-client-version"] ||= "1.20180719"
|
||||||
if !CONFIG.cookies.empty?
|
if !CONFIG.cookies.empty?
|
||||||
|
|
|
@ -20,7 +20,7 @@ end
|
||||||
|
|
||||||
def fetch_mix(rdid, video_id, cookies = nil, locale = nil)
|
def fetch_mix(rdid, video_id, cookies = nil, locale = nil)
|
||||||
headers = HTTP::Headers.new
|
headers = HTTP::Headers.new
|
||||||
headers["User-Agent"] = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36"
|
headers["User-Agent"] = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
|
||||||
|
|
||||||
if cookies
|
if cookies
|
||||||
headers = cookies.add_request_headers(headers)
|
headers = cookies.add_request_headers(headers)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
def fetch_trending(trending_type, region, locale)
|
def fetch_trending(trending_type, region, locale)
|
||||||
headers = HTTP::Headers.new
|
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"
|
headers["User-Agent"] = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
|
||||||
|
|
||||||
region ||= "US"
|
region ||= "US"
|
||||||
region = region.upcase
|
region = region.upcase
|
||||||
|
|
Loading…
Reference in New Issue