Skip to content

Commit fada57a

Browse files
committed
Add geo-bypass for video info
1 parent 20e815e commit fada57a

File tree

3 files changed

+198
-0
lines changed

3 files changed

+198
-0
lines changed

src/invidious/helpers/proxy.cr

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
# See https://github.com/crystal-lang/crystal/issues/2963
2+
class HTTPProxy
3+
getter proxy_host : String
4+
getter proxy_port : Int32
5+
getter options : Hash(Symbol, String)
6+
getter tls : OpenSSL::SSL::Context::Client?
7+
8+
def initialize(@proxy_host, @proxy_port = 80, @options = {} of Symbol => String)
9+
end
10+
11+
def open(host, port, tls = nil, connection_options = {} of Symbol => Float64 | Nil)
12+
dns_timeout = connection_options.fetch(:dns_timeout, nil)
13+
connect_timeout = connection_options.fetch(:connect_timeout, nil)
14+
read_timeout = connection_options.fetch(:read_timeout, nil)
15+
16+
socket = TCPSocket.new @proxy_host, @proxy_port, dns_timeout, connect_timeout
17+
socket.read_timeout = read_timeout if read_timeout
18+
socket.sync = true
19+
20+
socket << "CONNECT #{host}:#{port} HTTP/1.1\r\n"
21+
22+
if options[:user]?
23+
credentials = Base64.strict_encode("#{options[:user]}:#{options[:password]}")
24+
credentials = "#{credentials}\n".gsub(/\s/, "")
25+
socket << "Proxy-Authorization: Basic #{credentials}\r\n"
26+
end
27+
28+
socket << "\r\n"
29+
30+
resp = parse_response(socket)
31+
32+
if resp[:code]? == 200
33+
{% if !flag?(:without_openssl) %}
34+
if tls
35+
tls_socket = OpenSSL::SSL::Socket::Client.new(socket, context: tls, sync_close: true, hostname: host)
36+
socket = tls_socket
37+
end
38+
{% end %}
39+
40+
return socket
41+
else
42+
socket.close
43+
raise IO::Error.new(resp.inspect)
44+
end
45+
end
46+
47+
private def parse_response(socket)
48+
resp = {} of Symbol => Int32 | String | Hash(String, String)
49+
50+
begin
51+
version, code, reason = socket.gets.as(String).chomp.split(/ /, 3)
52+
53+
headers = {} of String => String
54+
55+
while (line = socket.gets.as(String)) && (line.chomp != "")
56+
name, value = line.split(/:/, 2)
57+
headers[name.strip] = value.strip
58+
end
59+
60+
resp[:version] = version
61+
resp[:code] = code.to_i
62+
resp[:reason] = reason
63+
resp[:headers] = headers
64+
rescue
65+
end
66+
67+
return resp
68+
end
69+
end
70+
71+
class HTTPClient < HTTP::Client
72+
def set_proxy(proxy : HTTPProxy)
73+
begin
74+
@socket = proxy.open(host: @host, port: @port, tls: @tls, connection_options: proxy_connection_options)
75+
rescue IO::Error
76+
@socket = nil
77+
end
78+
end
79+
80+
def proxy_connection_options
81+
opts = {} of Symbol => Float64 | Nil
82+
83+
opts[:dns_timeout] = @dns_timeout
84+
opts[:connect_timeout] = @connect_timeout
85+
opts[:read_timeout] = @read_timeout
86+
87+
return opts
88+
end
89+
end
90+
91+
def get_proxies(country_code = "US")
92+
client = HTTP::Client.new(URI.parse("http://spys.one"))
93+
client.read_timeout = 10.seconds
94+
client.connect_timeout = 10.seconds
95+
96+
headers = HTTP::Headers.new
97+
headers["User-Agent"] = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36"
98+
headers["Accept"] = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"
99+
headers["Accept-Language"] = "Accept-Language: en-US,en;q=0.9"
100+
headers["Host"] = "spys.one"
101+
headers["Content-Type"] = "application/x-www-form-urlencoded"
102+
body = {
103+
"xpp" => "2",
104+
"xf1" => "0",
105+
"xf2" => "2",
106+
"xf4" => "1",
107+
"xf5" => "1",
108+
}
109+
response = client.post("/free-proxy-list/#{country_code}/", headers, form: body)
110+
response = XML.parse_html(response.body)
111+
112+
proxies = [] of {ip: String, port: Int32, score: Float64}
113+
response = response.xpath_nodes(%q(//table))[1]
114+
response.xpath_nodes(%q(.//tr)).each do |node|
115+
if !node["onmouseover"]?
116+
next
117+
end
118+
119+
ip = node.xpath_node(%q(.//td[1]/font[2])).to_s.match(/<font class="spy14">(?<address>[^<]+)</).not_nil!["address"]
120+
port = 3128
121+
122+
latency = node.xpath_node(%q(.//td[6])).not_nil!.content.to_f
123+
speed = node.xpath_node(%q(.//td[7]/font/table)).not_nil!["width"].to_f
124+
uptime = node.xpath_node(%q(.//td[8]/font/acronym)).not_nil!
125+
126+
# Skip proxies that are down
127+
if uptime["title"].ends_with? "?"
128+
next
129+
end
130+
131+
if md = uptime.content.match(/^\d+/)
132+
uptime = md[0].to_f
133+
else
134+
next
135+
end
136+
137+
score = (uptime*4 + speed*2 + latency)/7
138+
139+
proxies << {ip: ip, port: port, score: score}
140+
end
141+
142+
return proxies
143+
end

src/invidious/videos.cr

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,9 @@ CAPTION_LANGUAGES = {
108108
"Zulu",
109109
}
110110

111+
REGIONS = {"AD", "AE", "AF", "AG", "AI", "AL", "AM", "AO", "AQ", "AR", "AS", "AT", "AU", "AW", "AX", "AZ", "BA", "BB", "BD", "BE", "BF", "BG", "BH", "BI", "BJ", "BL", "BM", "BN", "BO", "BQ", "BR", "BS", "BT", "BV", "BW", "BY", "BZ", "CA", "CC", "CD", "CF", "CG", "CH", "CI", "CK", "CL", "CM", "CN", "CO", "CR", "CU", "CV", "CW", "CX", "CY", "CZ", "DE", "DJ", "DK", "DM", "DO", "DZ", "EC", "EE", "EG", "EH", "ER", "ES", "ET", "FI", "FJ", "FK", "FM", "FO", "FR", "GA", "GB", "GD", "GE", "GF", "GG", "GH", "GI", "GL", "GM", "GN", "GP", "GQ", "GR", "GS", "GT", "GU", "GW", "GY", "HK", "HM", "HN", "HR", "HT", "HU", "ID", "IE", "IL", "IM", "IN", "IO", "IQ", "IR", "IS", "IT", "JE", "JM", "JO", "JP", "KE", "KG", "KH", "KI", "KM", "KN", "KP", "KR", "KW", "KY", "KZ", "LA", "LB", "LC", "LI", "LK", "LR", "LS", "LT", "LU", "LV", "LY", "MA", "MC", "MD", "ME", "MF", "MG", "MH", "MK", "ML", "MM", "MN", "MO", "MP", "MQ", "MR", "MS", "MT", "MU", "MV", "MW", "MX", "MY", "MZ", "NA", "NC", "NE", "NF", "NG", "NI", "NL", "NO", "NP", "NR", "NU", "NZ", "OM", "PA", "PE", "PF", "PG", "PH", "PK", "PL", "PM", "PN", "PR", "PS", "PT", "PW", "PY", "QA", "RE", "RO", "RS", "RU", "RW", "SA", "SB", "SC", "SD", "SE", "SG", "SH", "SI", "SJ", "SK", "SL", "SM", "SN", "SO", "SR", "SS", "ST", "SV", "SX", "SY", "SZ", "TC", "TD", "TF", "TG", "TH", "TJ", "TK", "TL", "TM", "TN", "TO", "TR", "TT", "TV", "TW", "TZ", "UA", "UG", "UM", "US", "UY", "UZ", "VA", "VC", "VE", "VG", "VI", "VN", "VU", "WF", "WS", "YE", "YT", "ZA", "ZM", "ZW"}
112+
BYPASS_REGIONS = {"CA", "DE", "FR", "JP", "RU", "UK"}
113+
111114
VIDEO_THUMBNAILS = {
112115
{name: "default", url: "default", height: 90, width: 120},
113116
{name: "medium", url: "mqdefault", height: 180, width: 320},
@@ -409,6 +412,49 @@ def fetch_video(id)
409412
html = html_channel.receive
410413
info = info_channel.receive
411414

415+
if info["reason"]? && info["reason"].includes? "your country"
416+
bypass_channel = Channel({HTTP::Params | Nil, XML::Node | Nil}).new
417+
418+
BYPASS_REGIONS.each do |country_code|
419+
spawn do
420+
begin
421+
proxies = get_proxies(country_code)
422+
423+
# Try not to overload single proxy
424+
proxy = proxies[0, 5].sample(1)[0]
425+
proxy = HTTPProxy.new(proxy_host: proxy[:ip], proxy_port: proxy[:port])
426+
427+
client = HTTPClient.new(URI.parse("https://www.youtube.com"))
428+
client.read_timeout = 10.seconds
429+
client.connect_timeout = 10.seconds
430+
client.set_proxy(proxy)
431+
432+
proxy_info = client.get("/get_video_info?video_id=#{id}&el=detailpage&ps=default&eurl=&gl=US&hl=en&disable_polymer=1")
433+
proxy_info = HTTP::Params.parse(proxy_info.body)
434+
435+
if !proxy_info["reason"]?
436+
proxy_html = client.get("/watch?v=#{id}&bpctr=#{Time.new.epoch + 2000}&gl=US&hl=en&disable_polymer=1")
437+
proxy_html = XML.parse_html(proxy_html.body)
438+
439+
bypass_channel.send({proxy_info, proxy_html})
440+
else
441+
bypass_channel.send({nil, nil})
442+
end
443+
rescue ex
444+
bypass_channel.send({nil, nil})
445+
end
446+
end
447+
end
448+
449+
BYPASS_REGIONS.size.times do
450+
response = bypass_channel.receive
451+
if response[0] || response[1]
452+
info = response[0].not_nil!
453+
html = response[1].not_nil!
454+
end
455+
end
456+
end
457+
412458
if info["reason"]?
413459
raise info["reason"]
414460
end

src/invidious/views/watch.ecr

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,15 @@ get_youtube_comments();
199199
<p id="Wilson">Wilson Score : <%= video.wilson_score.round(4) %></p>
200200
<p id="Rating">Rating : <%= rating.round(4) %> / 5</p>
201201
<p id="Engagement">Engagement : <%= engagement.round(2) %>%</p>
202+
<% if video.allowed_regions.size != REGIONS.size %>
203+
<p id="AllowedRegions">
204+
<% if video.allowed_regions.size < REGIONS.size / 2 %>
205+
Whitelisted regions: <%= video.allowed_regions.join(", ") %>
206+
<% else %>
207+
Blacklisted regions: <%= (REGIONS.to_a - video.allowed_regions).join(", ") %>
208+
<% end %>
209+
</p>
210+
<% end %>
202211
<% if ad_slots %>
203212
<p id="Slots">Ad Slots : <%= ad_slots %></p>
204213
<% end %>

0 commit comments

Comments
 (0)