From aeaeacbf8da7d4d76e25f27d49b1141633dbf51e Mon Sep 17 00:00:00 2001
From: Omar Roth
Date: Tue, 20 Nov 2018 10:07:50 -0600
Subject: [PATCH 01/20] Refactor geo-bypass
---
src/invidious.cr | 54 +++++++++++++--------------------------
src/invidious/comments.cr | 44 ++++++++++---------------------
src/invidious/videos.cr | 47 ++++++++--------------------------
3 files changed, 43 insertions(+), 102 deletions(-)
diff --git a/src/invidious.cr b/src/invidious.cr
index e7f28927..50b3092d 100644
--- a/src/invidious.cr
+++ b/src/invidious.cr
@@ -694,7 +694,9 @@ post "/login" do |env|
challenge_req = {
user_hash, nil, 1, nil,
- {1, nil, nil, nil, {password, nil, true}},
+ {1, nil, nil, nil,
+ {password, nil, true},
+ },
{nil, nil,
{2, 1, nil, 1, "https://accounts.google.com/ServiceLogin?passive=1209600&continue=https%3A%2F%2Faccounts.google.com%2FManageAccount&followup=https%3A%2F%2Faccounts.google.com%2FManageAccount", nil, [] of String, 4, [] of String},
1,
@@ -3342,45 +3344,24 @@ get "/videoplayback" do |env|
host = "https://r#{fvip}---#{mn}.googlevideo.com"
url = "/videoplayback?#{query_params.to_s}"
- if query_params["region"]?
- client = make_client(URI.parse(host))
- response = HTTP::Client::Response.new(status_code: 403)
-
- if !proxies[query_params["region"]]?
- halt env, status_code: 403
- end
-
- proxies[query_params["region"]].each do |proxy|
- begin
- client = HTTPClient.new(URI.parse(host))
- client.read_timeout = 10.seconds
- client.connect_timeout = 10.seconds
-
- proxy = HTTPProxy.new(proxy_host: proxy[:ip], proxy_port: proxy[:port])
- client.set_proxy(proxy)
-
- response = client.head(url)
- if response.status_code == 200
- # For whatever reason the proxy needs to be set again
- client.set_proxy(proxy)
- break
- end
- rescue ex
- end
- end
- else
- client = make_client(URI.parse(host))
- response = client.head(url)
- end
-
- if response.status_code != 200
- halt env, status_code: 403
- end
+ region = query_params["region"]?
+ client = make_client(URI.parse(host), proxies, region)
+ response = client.head(url)
if response.headers["Location"]?
url = URI.parse(response.headers["Location"])
env.response.headers["Access-Control-Allow-Origin"] = "*"
- next env.redirect url.full_path
+
+ url = url.full_path
+ if region
+ url += "®ion=#{region}"
+ end
+
+ next env.redirect url
+ end
+
+ if response.status_code >= 400
+ halt env, status_code: 403
end
headers = env.request.headers
@@ -3389,6 +3370,7 @@ get "/videoplayback" do |env|
headers.delete("User-Agent")
headers.delete("Referer")
+ client = make_client(URI.parse(host), proxies, region)
client.get(url, headers) do |response|
env.response.status_code = response.status_code
diff --git a/src/invidious/comments.cr b/src/invidious/comments.cr
index 70d365a1..3735aa44 100644
--- a/src/invidious/comments.cr
+++ b/src/invidious/comments.cr
@@ -70,34 +70,18 @@ def fetch_youtube_comments(id, continuation, proxies, format)
if body.match(//)
bypass_channel = Channel({String, HTTPClient, HTTP::Headers} | Nil).new
- proxies.each do |region, list|
+ proxies.each do |proxy_region, list|
spawn do
- proxy_html = %()
+ proxy_client = make_client(YT_URL, proxies, proxy_region)
- list.each do |proxy|
- begin
- proxy_client = HTTPClient.new(YT_URL)
- proxy_client.read_timeout = 10.seconds
- proxy_client.connect_timeout = 10.seconds
+ 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
- proxy = HTTPProxy.new(proxy_host: proxy[:ip], proxy_port: proxy[:port])
- proxy_client.set_proxy(proxy)
-
- 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
-
- if !proxy_html.match(//)
- bypass_channel.send({proxy_html, proxy_client, proxy_headers})
- break
- end
- rescue ex
- end
- end
-
- # If none of the proxies we tried returned a valid response
- if proxy_html.match(//)
+ if !proxy_html.match(//)
+ bypass_channel.send({proxy_html, proxy_client, proxy_headers})
+ else
bypass_channel.send(nil)
end
end
@@ -106,12 +90,12 @@ def fetch_youtube_comments(id, continuation, proxies, format)
proxies.size.times do
response = bypass_channel.receive
if response
- session_token = response[0].match(/'XSRF_TOKEN': "(?[A-Za-z0-9\_\-\=]+)"/).not_nil!["session_token"]
- itct = response[0].match(/itct=(?[^"]+)"/).not_nil!["itct"]
- ctoken = response[0].match(/'COMMENTS_TOKEN': "(?[^"]+)"/)
+ html, client, headers = response
+
+ session_token = html.match(/'XSRF_TOKEN': "(?[A-Za-z0-9\_\-\=]+)"/).not_nil!["session_token"]
+ itct = html.match(/itct=(?[^"]+)"/).not_nil!["itct"]
+ ctoken = html.match(/'COMMENTS_TOKEN': "(?[^"]+)"/)
- client = response[1]
- headers = response[2]
break
end
end
diff --git a/src/invidious/videos.cr b/src/invidious/videos.cr
index 89d4cfb1..3934a66f 100644
--- a/src/invidious/videos.cr
+++ b/src/invidious/videos.cr
@@ -578,47 +578,26 @@ def fetch_video(id, proxies, region)
info = info_channel.receive
if info["reason"]? && info["reason"].includes? "your country"
- bypass_channel = Channel(HTTPProxy | Nil).new
+ bypass_channel = Channel({HTTPClient, String} | Nil).new
- proxies.each do |region, list|
+ proxies.each do |proxy_region, list|
spawn do
- info = HTTP::Params.new({
- "reason" => [info["reason"]],
- })
+ client = make_client(YT_URL, proxies, proxy_region)
- list.each do |proxy|
- begin
- client = HTTPClient.new(YT_URL)
- client.read_timeout = 10.seconds
- client.connect_timeout = 10.seconds
-
- proxy = HTTPProxy.new(proxy_host: proxy[:ip], proxy_port: proxy[:port])
- client.set_proxy(proxy)
-
- info = HTTP::Params.parse(client.get("/get_video_info?video_id=#{id}&ps=default&eurl=&gl=US&hl=en&disable_polymer=1").body)
- if !info["reason"]?
- bypass_channel.send(proxy)
- break
- end
- rescue ex
- end
- end
-
- # If none of the proxies we tried returned a valid response
- if info["reason"]?
+ info = HTTP::Params.parse(client.get("/get_video_info?video_id=#{id}&ps=default&eurl=&gl=US&hl=en&disable_polymer=1").body)
+ if !info["reason"]?
+ bypass_channel.send({client, proxy_region})
+ else
bypass_channel.send(nil)
end
end
end
proxies.size.times do
- proxy = bypass_channel.receive
- if proxy
+ response = bypass_channel.receive
+ if response
begin
- client = HTTPClient.new(YT_URL)
- client.read_timeout = 10.seconds
- client.connect_timeout = 10.seconds
- client.set_proxy(proxy)
+ client, proxy_region = response
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)
@@ -627,11 +606,7 @@ def fetch_video(id, proxies, region)
info = HTTP::Params.parse(client.get("/get_video_info?video_id=#{id}&ps=default&eurl=&gl=US&hl=en&disable_polymer=1").body)
end
- proxy = {ip: proxy.proxy_host, port: proxy.proxy_port}
- region_proxies = proxies.select { |region, list| list.includes? proxy }
- if !region_proxies.empty?
- info["region"] = region_proxies.keys[0]
- end
+ info["region"] = proxy_region
break
rescue ex
From 2e996421731c6024b6d3308ccfb0e0071bc4407b Mon Sep 17 00:00:00 2001
From: Omar Roth
Date: Tue, 20 Nov 2018 11:18:12 -0600
Subject: [PATCH 02/20] Add /feed/trending
---
src/invidious.cr | 32 +++++++++++++++++++++----
src/invidious/trending.cr | 41 ++++++++++++++++++++++++++++++++
src/invidious/views/trending.ecr | 11 +++++++++
3 files changed, 79 insertions(+), 5 deletions(-)
create mode 100644 src/invidious/trending.cr
create mode 100644 src/invidious/views/trending.ecr
diff --git a/src/invidious.cr b/src/invidious.cr
index 50b3092d..b08cbc9e 100644
--- a/src/invidious.cr
+++ b/src/invidious.cr
@@ -1590,6 +1590,20 @@ end
# Feeds
+get "/feed/trending" do |env|
+ trending_type = env.params.query["type"]?
+ region = env.params.query["region"]?
+
+ begin
+ trending = fetch_trending(trending_type, proxies, region)
+ rescue ex
+ error_message = "#{ex.message}"
+ next templated "error"
+ end
+
+ templated "trending"
+end
+
get "/feed/subscriptions" do |env|
user = env.get? "user"
referer = get_referer(env)
@@ -2467,14 +2481,19 @@ get "/api/v1/videos/:id" do |env|
end
get "/api/v1/trending" do |env|
- client = make_client(YT_URL)
- trending = client.get("/feed/trending?disable_polymer=1").body
+ region = env.params.query["region"]?
+ trending_type = env.params.query["type"]?
+
+ begin
+ trending = fetch_trending(trending_type, proxies, region)
+ rescue ex
+ error_message = {"error" => ex.message}.to_json
+ halt env, status_code: 500, response: error_message
+ end
- trending = XML.parse_html(trending)
videos = JSON.build do |json|
json.array do
- nodeset = trending.xpath_nodes(%q(//ul/li[@class="expanded-shelf-content-item-wrapper"]))
- extract_videos(nodeset).each do |video|
+ trending.each do |video|
json.object do
json.field "title", video.title
json.field "videoId", video.id
@@ -2493,6 +2512,9 @@ get "/api/v1/trending" do |env|
json.field "publishedText", "#{recode_date(video.published)} ago"
json.field "description", video.description
json.field "descriptionHtml", video.description_html
+ json.field "liveNow", video.live_now
+ json.field "paid", video.paid
+ json.field "premium", video.premium
end
end
end
diff --git a/src/invidious/trending.cr b/src/invidious/trending.cr
new file mode 100644
index 00000000..b8ef8186
--- /dev/null
+++ b/src/invidious/trending.cr
@@ -0,0 +1,41 @@
+def fetch_trending(trending_type, proxies, region)
+ client = make_client(YT_URL)
+ 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"
+
+ region ||= "US"
+ region = region.upcase
+
+ trending = ""
+ if trending_type
+ trending_type = trending_type.downcase.capitalize
+
+ response = client.get("/feed/trending?gl=#{region}&hl=en", headers).body
+
+ yt_data = response.match(/window\["ytInitialData"\] = (?.*);/)
+ if yt_data
+ yt_data = JSON.parse(yt_data["data"].rchop(";"))
+ else
+ raise "Could not pull trending pages."
+ end
+
+ tabs = yt_data["contents"]["twoColumnBrowseResultsRenderer"]["tabs"][0]["tabRenderer"]["content"]["sectionListRenderer"]["subMenu"]["channelListSubMenuRenderer"]["contents"].as_a
+ url = tabs.select { |tab| tab["channelListSubMenuAvatarRenderer"]["title"]["simpleText"] == trending_type }[0]?
+
+ if url
+ url = url["channelListSubMenuAvatarRenderer"]["navigationEndpoint"]["commandMetadata"]["webCommandMetadata"]["url"].as_s
+ url += "&disable_polymer=1&gl=#{region}&hl=en"
+ trending = client.get(url).body
+ else
+ trending = client.get("/feed/trending?gl=#{region}&hl=en&disable_polymer=1").body
+ end
+ else
+ trending = client.get("/feed/trending?gl=#{region}&hl=en&disable_polymer=1").body
+ end
+
+ trending = XML.parse_html(trending)
+ nodeset = trending.xpath_nodes(%q(//ul/li[@class="expanded-shelf-content-item-wrapper"]))
+ trending = extract_videos(nodeset)
+
+ return trending
+end
diff --git a/src/invidious/views/trending.ecr b/src/invidious/views/trending.ecr
new file mode 100644
index 00000000..b3de793c
--- /dev/null
+++ b/src/invidious/views/trending.ecr
@@ -0,0 +1,11 @@
+<% content_for "header" do %>
+Trending - Invidious
+<% end %>
+
+
+<% trending.each_slice(4) do |slice| %>
+ <% slice.each do |item| %>
+ <%= rendered "components/item" %>
+ <% end %>
+<% end %>
+
From 2be43c17aba4d1bbc2cd88f9ad5adadebe7bb309 Mon Sep 17 00:00:00 2001
From: Omar Roth
Date: Tue, 20 Nov 2018 11:18:48 -0600
Subject: [PATCH 03/20] Sample proxies to avoid overloading single proxy
---
src/invidious/helpers/utils.cr | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/invidious/helpers/utils.cr b/src/invidious/helpers/utils.cr
index f1339749..496c3e0d 100644
--- a/src/invidious/helpers/utils.cr
+++ b/src/invidious/helpers/utils.cr
@@ -30,7 +30,7 @@ def make_client(url, proxies = {} of String => Array({ip: String, port: Int32}),
client.connect_timeout = 10.seconds
if region
- proxies[region]?.try &.each do |proxy|
+ proxies[region]?.try &.sample(40).each do |proxy|
begin
proxy = HTTPProxy.new(proxy_host: proxy[:ip], proxy_port: proxy[:port])
client.set_proxy(proxy)
From e5730f4cbcb72d0a51c2dc48a6981318f1cc6195 Mon Sep 17 00:00:00 2001
From: Omar Roth
Date: Tue, 20 Nov 2018 11:19:04 -0600
Subject: [PATCH 04/20] Use 'ion-ios-trash' for /feed/history
---
src/invidious/views/history.ecr | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/src/invidious/views/history.ecr b/src/invidious/views/history.ecr
index 34ba3289..07fbb326 100644
--- a/src/invidious/views/history.ecr
+++ b/src/invidious/views/history.ecr
@@ -18,11 +18,7 @@
onmouseenter='this["href"]="javascript:void(0)"'
href="/mark_unwatched?id=<%= item %>"'
>
-
-
+
From a242390fc189f2268ee284eb25bda370e1c437e1 Mon Sep 17 00:00:00 2001
From: Omar Roth
Date: Tue, 20 Nov 2018 13:14:13 -0600
Subject: [PATCH 05/20] Fix typo in nonces.sql
---
config/sql/nonces.sql | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/config/sql/nonces.sql b/config/sql/nonces.sql
index 9693b936..d2ec5be6 100644
--- a/config/sql/nonces.sql
+++ b/config/sql/nonces.sql
@@ -5,7 +5,7 @@
CREATE TABLE public.nonces
(
nonce text,
- expire timestamp with time zone,
+ expire timestamp with time zone
)
WITH (
OIDS=FALSE
From 60c6778344c647ef0ebec58624f03926fdaf25fc Mon Sep 17 00:00:00 2001
From: Omar Roth
Date: Tue, 20 Nov 2018 22:57:51 -0600
Subject: [PATCH 06/20] Make 'watched' icon smaller
---
assets/css/default.css | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/assets/css/default.css b/assets/css/default.css
index 4ec94a68..26558383 100644
--- a/assets/css/default.css
+++ b/assets/css/default.css
@@ -62,10 +62,10 @@ img.thumbnail {
color: #fff;
border-radius: 2px;
padding: 4px 8px 4px 8px;
- font-size: 25px;
+ font-size: 16px;
font-family: sans-serif;
- left: 0.25em;
- top: -0.75em;
+ left: 0.20em;
+ top: -0.70em;
}
/*
From f01cfd0226d31db548c9438db0800e14132b86d0 Mon Sep 17 00:00:00 2001
From: Omar Roth
Date: Tue, 20 Nov 2018 22:58:04 -0600
Subject: [PATCH 07/20] Use material style for trash icon
---
src/invidious/views/history.ecr | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/src/invidious/views/history.ecr b/src/invidious/views/history.ecr
index 07fbb326..90bea14c 100644
--- a/src/invidious/views/history.ecr
+++ b/src/invidious/views/history.ecr
@@ -16,9 +16,8 @@
-
+ href="/mark_unwatched?id=<%= item %>">
+
From d6d73bd33630add1ff0d114fd468685ce88c3795 Mon Sep 17 00:00:00 2001
From: Omar Roth
Date: Tue, 20 Nov 2018 22:58:30 -0600
Subject: [PATCH 08/20] Fix clickable titles in subscription feed
---
src/invidious/views/components/item.ecr | 14 ++++++--------
1 file changed, 6 insertions(+), 8 deletions(-)
diff --git a/src/invidious/views/components/item.ecr b/src/invidious/views/components/item.ecr
index 5940ae8a..b38e7a5d 100644
--- a/src/invidious/views/components/item.ecr
+++ b/src/invidious/views/components/item.ecr
@@ -69,9 +69,9 @@
Shared <%= recode_date(item.published) %> ago
<% end %>
<% else %>
+ <% if env.get?("user") && env.get("user").as(User).preferences.thin_mode %>
+ <% else %>
- <% if env.get?("user") && env.get("user").as(User).preferences.thin_mode %>
- <% else %>
- <% end %>
- <%= item.title %>
+ <% end %>
+ <%= item.title %>
<% if item.responds_to?(:live_now) && item.live_now %>
LIVE
<% end %>
From 588f9b9bd68c1bde28324918f6123d4b5254213c Mon Sep 17 00:00:00 2001
From: Omar Roth
Date: Wed, 21 Nov 2018 08:25:21 -0600
Subject: [PATCH 09/20] Fix 'order' expression
---
src/invidious.cr | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/invidious.cr b/src/invidious.cr
index b08cbc9e..520e4ebf 100644
--- a/src/invidious.cr
+++ b/src/invidious.cr
@@ -1680,7 +1680,7 @@ get "/feed/subscriptions" do |env|
user.watched, as: ChannelVideo)
else
videos = PG_DB.query_all("SELECT DISTINCT ON (ucid) * FROM #{view_name} \
- ORDER BY published, ucid #{sort}", as: ChannelVideo)
+ ORDER BY ucid, published #{sort}", as: ChannelVideo)
end
videos.sort_by! { |video| video.published }.reverse!
From 2ce038fb7aa0b089a16fc5dc430d7ac4ea2f63b1 Mon Sep 17 00:00:00 2001
From: Omar Roth
Date: Wed, 21 Nov 2018 13:06:29 -0600
Subject: [PATCH 10/20] Only show toggle watched button when relevant
---
src/invidious.cr | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/invidious.cr b/src/invidious.cr
index 520e4ebf..d0ef43c1 100644
--- a/src/invidious.cr
+++ b/src/invidious.cr
@@ -1611,7 +1611,10 @@ get "/feed/subscriptions" do |env|
if user
user = user.as(User)
preferences = user.preferences
- env.set "show_watched", true
+
+ if preferences.unseen_only
+ env.set "show_watched", true
+ end
# Refresh account
headers = HTTP::Headers.new
From a15463cf377d55ee14269acd64db8fd2b242289a Mon Sep 17 00:00:00 2001
From: Omar Roth
Date: Wed, 21 Nov 2018 13:10:09 -0600
Subject: [PATCH 11/20] Clarify options in preferences
---
src/invidious/views/preferences.ecr | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/invidious/views/preferences.ecr b/src/invidious/views/preferences.ecr
index f34c40aa..cad858c7 100644
--- a/src/invidious/views/preferences.ecr
+++ b/src/invidious/views/preferences.ecr
@@ -24,7 +24,7 @@ function update_value(element) {
-
+
checked<% end %>>
@@ -143,7 +143,7 @@ function update_value(element) {
-
+
checked<% end %>>
From c656a7cb9e12c6169aec5f58901f47d9b00d2c4a Mon Sep 17 00:00:00 2001
From: Omar Roth
Date: Wed, 21 Nov 2018 13:10:56 -0600
Subject: [PATCH 12/20] Add link to watch history in preferences
---
src/invidious/views/preferences.ecr | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/src/invidious/views/preferences.ecr b/src/invidious/views/preferences.ecr
index cad858c7..3557f31f 100644
--- a/src/invidious/views/preferences.ecr
+++ b/src/invidious/views/preferences.ecr
@@ -166,6 +166,10 @@ function update_value(element) {
Manage subscriptions
+
+
From e80884cfce3ec21d6e3bb08a682607d3952eb954 Mon Sep 17 00:00:00 2001
From: Omar Roth
Date: Wed, 21 Nov 2018 13:18:33 -0600
Subject: [PATCH 13/20] Remove unnecessary request header
---
src/invidious/views/history.ecr | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/invidious/views/history.ecr b/src/invidious/views/history.ecr
index 90bea14c..691201cd 100644
--- a/src/invidious/views/history.ecr
+++ b/src/invidious/views/history.ecr
@@ -40,7 +40,6 @@ function mark_unwatched(target) {
xhr.responseType = "json";
xhr.timeout = 20000;
xhr.open("GET", url, true);
- xhr.setRequestHeader("Redirect", "false");
xhr.send();
xhr.onreadystatechange = function() {
From cdd916f51d68e9982ab3e98cad20c998504146cb Mon Sep 17 00:00:00 2001
From: Omar Roth
Date: Wed, 21 Nov 2018 13:35:37 -0600
Subject: [PATCH 14/20] Add async for manage_subscriptions
---
src/invidious.cr | 17 +++++++++-
src/invidious/views/subscription_manager.ecr | 33 ++++++++++++++++++--
2 files changed, 47 insertions(+), 3 deletions(-)
diff --git a/src/invidious.cr b/src/invidious.cr
index d0ef43c1..34cf318b 100644
--- a/src/invidious.cr
+++ b/src/invidious.cr
@@ -1438,6 +1438,12 @@ get "/subscription_ajax" do |env|
user = env.get? "user"
referer = get_referer(env)
+ redirect = env.params.query["redirect"]?
+ redirect ||= "false"
+ redirect = redirect == "true"
+
+ count_text = ""
+
if user
user = user.as(User)
@@ -1500,9 +1506,18 @@ get "/subscription_ajax" do |env|
PG_DB.exec("UPDATE users SET subscriptions = array_remove(subscriptions,$1) WHERE id = $2", channel_id, sid)
end
end
+
+ count_text = PG_DB.query_one?("SELECT cardinality(subscriptions) FROM users WHERE id = $1", [sid], as: Int64)
+ count_text ||= 0
+ count_text = "#{number_with_separator(count_text)} subscriptions"
end
- env.redirect referer
+ if redirect
+ env.redirect referer
+ else
+ env.response.content_type = "application/json"
+ {"countText" => count_text}.to_json
+ end
end
get "/delete_account" do |env|
diff --git a/src/invidious/views/subscription_manager.ecr b/src/invidious/views/subscription_manager.ecr
index b942e686..d420fbf8 100644
--- a/src/invidious/views/subscription_manager.ecr
+++ b/src/invidious/views/subscription_manager.ecr
@@ -4,7 +4,7 @@
-
<%= subscriptions.size %> subscriptions
+ <%= subscriptions.size %> subscriptions
@@ -34,3 +39,27 @@
<% end %>
<% end %>
+
+
\ No newline at end of file
From fd7aa59e0fdc50ca886c2b3f243e444dd80fdeb6 Mon Sep 17 00:00:00 2001
From: Omar Roth
Date: Wed, 21 Nov 2018 17:12:13 -0600
Subject: [PATCH 15/20] Properly parse NewPipe imports
---
shard.yml | 4 +++-
src/invidious.cr | 60 +++++++++++++++++++++++++-----------------------
2 files changed, 34 insertions(+), 30 deletions(-)
diff --git a/shard.yml b/shard.yml
index 02777233..f848b239 100644
--- a/shard.yml
+++ b/shard.yml
@@ -16,7 +16,9 @@ dependencies:
commit: afd17fc
pg:
github: will/crystal-pg
+ sqlite3:
+ github: crystal-lang/crystal-sqlite3
-crystal: 0.26.1
+crystal: 0.27.0
license: AGPLv3
diff --git a/src/invidious.cr b/src/invidious.cr
index 34cf318b..dd134a72 100644
--- a/src/invidious.cr
+++ b/src/invidious.cr
@@ -20,6 +20,7 @@ require "kemal"
require "openssl/hmac"
require "option_parser"
require "pg"
+require "sqlite3"
require "xml"
require "yaml"
require "zip"
@@ -1142,7 +1143,7 @@ get "/mark_unwatched" do |env|
if user
user = user.as(User)
- PG_DB.exec("UPDATE users SET watched = array_remove(watched, $1) WHERE id = $2", id, user.id)
+ PG_DB.exec("UPDATE users SET watched = array_remove(watched, $1) WHERE email = $2", id, user.email)
end
if redirect
@@ -1320,12 +1321,13 @@ post "/data_control" do |env|
user.subscriptions += body["subscriptions"].as_a.map { |a| a.as_s }
user.subscriptions.uniq!
- user.subscriptions.each do |ucid|
+ user.subscriptions.select! do |ucid|
begin
client = make_client(YT_URL)
get_channel(ucid, client, PG_DB, false, false)
+ true
rescue ex
- next
+ false
end
end
@@ -1349,12 +1351,13 @@ post "/data_control" do |env|
end
user.subscriptions.uniq!
- user.subscriptions.each do |ucid|
+ user.subscriptions.select! do |ucid|
begin
client = make_client(YT_URL)
get_channel(ucid, client, PG_DB, false, false)
+ true
rescue ex
- next
+ false
end
end
@@ -1365,12 +1368,13 @@ post "/data_control" do |env|
end
user.subscriptions.uniq!
- user.subscriptions.each do |ucid|
+ user.subscriptions.select! do |ucid|
begin
client = make_client(YT_URL)
get_channel(ucid, client, PG_DB, false, false)
+ true
rescue ex
- next
+ false
end
end
@@ -1396,34 +1400,32 @@ post "/data_control" do |env|
Zip::Reader.open(IO::Memory.new(body)) do |file|
file.each_entry do |entry|
if entry.filename == "newpipe.db"
- # We do this because the SQLite driver cannot parse a database from an IO
- # Currently: channel URLs can **only** be subscriptions, and
- # video URLs can **only** be watch history, so this works okay for now.
+ tempfile = File.tempfile(".db")
+ File.write(tempfile.path, entry.io.gets_to_end)
+ db = DB.open("sqlite3://" + tempfile.path)
- db = entry.io.gets_to_end
-
- user.watched += db.scan(/youtube\.com\/watch\?v\=(?[a-zA-Z0-9_-]{11})/).map do |md|
- md["id"]
- end
+ user.watched += db.query_all("SELECT url FROM streams", as: String).map { |url| url.lchop("https://www.youtube.com/watch?v=") }
user.watched.uniq!
PG_DB.exec("UPDATE users SET watched = $1 WHERE email = $2", user.watched, user.email)
- user.subscriptions += db.scan(/youtube\.com\/channel\/(?[a-zA-Z0-9_-]{22})/).map do |md|
- md["ucid"]
- end
+ user.subscriptions += db.query_all("SELECT url FROM subscriptions", as: String).map { |url| url.lchop("https://www.youtube.com/channel/") }
user.subscriptions.uniq!
- user.subscriptions.each do |ucid|
+ user.subscriptions.select! do |ucid|
begin
client = make_client(YT_URL)
get_channel(ucid, client, PG_DB, false, false)
+ true
rescue ex
- next
+ false
end
end
PG_DB.exec("UPDATE users SET subscriptions = $1 WHERE email = $2", user.subscriptions, user.email)
+
+ db.close
+ tempfile.delete
end
end
end
@@ -1482,32 +1484,32 @@ get "/subscription_ajax" do |env|
# Update user
if client.post(post_url, headers, post_req).status_code == 200
- sid = user.id
+ email = user.email
case action
when .starts_with? "action_create"
- PG_DB.exec("UPDATE users SET subscriptions = array_append(subscriptions,$1) WHERE id = $2", channel_id, sid)
+ PG_DB.exec("UPDATE users SET subscriptions = array_append(subscriptions,$1) WHERE email = $2", channel_id, email)
when .starts_with? "action_remove"
- PG_DB.exec("UPDATE users SET subscriptions = array_remove(subscriptions,$1) WHERE id = $2", channel_id, sid)
+ PG_DB.exec("UPDATE users SET subscriptions = array_remove(subscriptions,$1) WHERE email = $2", channel_id, email)
end
end
else
- sid = user.id
+ email = user.email
case action
when .starts_with? "action_create"
if !user.subscriptions.includes? channel_id
- PG_DB.exec("UPDATE users SET subscriptions = array_append(subscriptions,$1) WHERE id = $2", channel_id, sid)
+ PG_DB.exec("UPDATE users SET subscriptions = array_append(subscriptions,$1) WHERE email = $2", channel_id, email)
client = make_client(YT_URL)
get_channel(channel_id, client, PG_DB, false, false)
end
when .starts_with? "action_remove"
- PG_DB.exec("UPDATE users SET subscriptions = array_remove(subscriptions,$1) WHERE id = $2", channel_id, sid)
+ PG_DB.exec("UPDATE users SET subscriptions = array_remove(subscriptions,$1) WHERE email = $2", channel_id, email)
end
end
- count_text = PG_DB.query_one?("SELECT cardinality(subscriptions) FROM users WHERE id = $1", [sid], as: Int64)
+ count_text = PG_DB.query_one?("SELECT cardinality(subscriptions) FROM users WHERE email = $1", email, as: Int32)
count_text ||= 0
count_text = "#{number_with_separator(count_text)} subscriptions"
end
@@ -1742,8 +1744,8 @@ get "/feed/subscriptions" do |env|
videos = videos[0..max_results]
end
- PG_DB.exec("UPDATE users SET notifications = $1, updated = $2 WHERE id = $3", [] of String, Time.now,
- user.id)
+ PG_DB.exec("UPDATE users SET notifications = $1, updated = $2 WHERE email = $3", [] of String, Time.now,
+ user.email)
user.notifications = [] of String
env.set "user", user
From 95ebfd34c5b5484e9fc3d9932e3e8b61c0985603 Mon Sep 17 00:00:00 2001
From: Omar Roth
Date: Wed, 21 Nov 2018 19:26:55 -0600
Subject: [PATCH 16/20] Don't wait on server for subscription count
---
src/invidious.cr | 8 +-------
src/invidious/views/subscription_manager.ecr | 8 ++++----
2 files changed, 5 insertions(+), 11 deletions(-)
diff --git a/src/invidious.cr b/src/invidious.cr
index dd134a72..74ae35db 100644
--- a/src/invidious.cr
+++ b/src/invidious.cr
@@ -1444,8 +1444,6 @@ get "/subscription_ajax" do |env|
redirect ||= "false"
redirect = redirect == "true"
- count_text = ""
-
if user
user = user.as(User)
@@ -1508,17 +1506,13 @@ get "/subscription_ajax" do |env|
PG_DB.exec("UPDATE users SET subscriptions = array_remove(subscriptions,$1) WHERE email = $2", channel_id, email)
end
end
-
- count_text = PG_DB.query_one?("SELECT cardinality(subscriptions) FROM users WHERE email = $1", email, as: Int32)
- count_text ||= 0
- count_text = "#{number_with_separator(count_text)} subscriptions"
end
if redirect
env.redirect referer
else
env.response.content_type = "application/json"
- {"countText" => count_text}.to_json
+ "{}"
end
end
diff --git a/src/invidious/views/subscription_manager.ecr b/src/invidious/views/subscription_manager.ecr
index d420fbf8..ac1c0cdd 100644
--- a/src/invidious/views/subscription_manager.ecr
+++ b/src/invidious/views/subscription_manager.ecr
@@ -4,7 +4,7 @@
-
<%= subscriptions.size %> subscriptions
+ <%= subscriptions.size %> subscriptions
@@ -44,6 +44,8 @@
function remove_subscription(target) {
var row = target.parentNode.parentNode.parentNode.parentNode;
row.style.display = "none";
+ var count = document.getElementById("count")
+ count.innerText = count.innerText - 1;
var url = "/subscription_ajax?action_remove_subscriptions=1&redirect=false&c=" + target.getAttribute("data-id");
var xhr = new XMLHttpRequest();
@@ -54,9 +56,7 @@ function remove_subscription(target) {
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
- if (xhr.status == 200) {
- document.getElementById("count").innerHTML = xhr.response.countText;
- } else {
+ if (xhr.status != 200) {
row.style.display = "";
}
}
From 941a773b7d931ce097db11de8d3a09ade0c2071c Mon Sep 17 00:00:00 2001
From: Omar Roth
Date: Wed, 21 Nov 2018 20:00:17 -0600
Subject: [PATCH 17/20] Add opensearch.xml
---
src/invidious.cr | 15 +++++++++++++++
src/invidious/views/template.ecr | 1 +
2 files changed, 16 insertions(+)
diff --git a/src/invidious.cr b/src/invidious.cr
index 74ae35db..de40698f 100644
--- a/src/invidious.cr
+++ b/src/invidious.cr
@@ -512,6 +512,21 @@ end
# Search
+get "/opensearch.xml" do |env|
+ env.response.content_type = "application/opensearchdescription+xml"
+
+ XML.build(indent: " ", encoding: "UTF-8") do |xml|
+ xml.element("OpenSearchDescription", xmlns: "http://a9.com/-/spec/opensearch/1.1/") do
+ xml.element("ShortName") { xml.text "Invidious" }
+ xml.element("LongName") { xml.text "Invidious Search" }
+ xml.element("Description") { xml.text "Search for videos, channels, and playlists on Invidious" }
+ xml.element("InputEncoding") { xml.text "UTF-8" }
+ xml.element("Image", width: 48, height: 48, type: "image/x-icon") { xml.text "/favicon.ico" }
+ xml.element("Url", type: "text/html", method: "get", template: "/search?q={searchTerms}")
+ end
+ end
+end
+
get "/results" do |env|
query = env.params.query["search_query"]?
query ||= env.params.query["q"]?
diff --git a/src/invidious/views/template.ecr b/src/invidious/views/template.ecr
index 748a691f..4cfb41df 100644
--- a/src/invidious/views/template.ecr
+++ b/src/invidious/views/template.ecr
@@ -13,6 +13,7 @@
+
From 568e55dfa6af08880fa445fc8cac0fa45c0f029e Mon Sep 17 00:00:00 2001
From: Omar Roth
Date: Wed, 21 Nov 2018 20:00:33 -0600
Subject: [PATCH 18/20] Add description for home page
---
src/invidious/views/index.ecr | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/invidious/views/index.ecr b/src/invidious/views/index.ecr
index 46d07a9a..cd88d540 100644
--- a/src/invidious/views/index.ecr
+++ b/src/invidious/views/index.ecr
@@ -1,4 +1,5 @@
<% content_for "header" do %>
+
Invidious
<% end %>
From ca4e8b800c62e4747a61c7358322e2fbf1fcb5cb Mon Sep 17 00:00:00 2001
From: Omar Roth
Date: Wed, 21 Nov 2018 20:49:14 -0600
Subject: [PATCH 19/20] Use absolute paths in /opensearch.xml
---
src/invidious.cr | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/invidious.cr b/src/invidious.cr
index de40698f..76d772dc 100644
--- a/src/invidious.cr
+++ b/src/invidious.cr
@@ -521,8 +521,8 @@ get "/opensearch.xml" do |env|
xml.element("LongName") { xml.text "Invidious Search" }
xml.element("Description") { xml.text "Search for videos, channels, and playlists on Invidious" }
xml.element("InputEncoding") { xml.text "UTF-8" }
- xml.element("Image", width: 48, height: 48, type: "image/x-icon") { xml.text "/favicon.ico" }
- xml.element("Url", type: "text/html", method: "get", template: "/search?q={searchTerms}")
+ xml.element("Image", width: 48, height: 48, type: "image/x-icon") { xml.text "https://invidio.us/favicon.ico" }
+ xml.element("Url", type: "text/html", method: "get", template: "https://invidio.us/search?q={searchTerms}")
end
end
end
From 26eb59e00d9596adf69e3a3d5f8cb899f8a1da0a Mon Sep 17 00:00:00 2001
From: Omar Roth
Date: Thu, 22 Nov 2018 13:26:08 -0600
Subject: [PATCH 20/20] Add text CAPTCHA
---
src/invidious.cr | 87 +++++++++++++++++++++++++++--------
src/invidious/views/login.ecr | 20 +++++++-
2 files changed, 85 insertions(+), 22 deletions(-)
diff --git a/src/invidious.cr b/src/invidious.cr
index 76d772dc..cbba17c1 100644
--- a/src/invidious.cr
+++ b/src/invidious.cr
@@ -16,6 +16,7 @@
require "crypto/bcrypt/password"
require "detect_language"
+require "digest/md5"
require "kemal"
require "openssl/hmac"
require "option_parser"
@@ -82,10 +83,11 @@ PG_URL = URI.new(
path: CONFIG.db[:dbname],
)
-PG_DB = DB.open PG_URL
-YT_URL = URI.parse("https://www.youtube.com")
-REDDIT_URL = URI.parse("https://www.reddit.com")
-LOGIN_URL = URI.parse("https://accounts.google.com")
+PG_DB = DB.open PG_URL
+YT_URL = URI.parse("https://www.youtube.com")
+REDDIT_URL = URI.parse("https://www.reddit.com")
+LOGIN_URL = URI.parse("https://accounts.google.com")
+TEXTCAPTCHA_URL = URI.parse("http://textcaptcha.com/omarroth@hotmail.com.json")
crawl_threads.times do
spawn do
@@ -632,8 +634,25 @@ get "/login" do |env|
account_type = env.params.query["type"]?
account_type ||= "invidious"
+ captcha_type = env.params.query["captcha"]?
+ captcha_type ||= "image"
+
if account_type == "invidious"
- captcha = generate_captcha(HMAC_KEY, PG_DB)
+ if captcha_type == "image"
+ captcha = generate_captcha(HMAC_KEY, PG_DB)
+ else
+ response = HTTP::Client.get(TEXTCAPTCHA_URL).body
+ response = JSON.parse(response)
+
+ tokens = response["a"].as_a.map do |answer|
+ create_response(answer.as_s, "sign_in", HMAC_KEY, PG_DB)
+ end
+
+ text_captcha = {
+ question: response["q"].as_s,
+ tokens: tokens,
+ }
+ end
end
tfa = env.params.query["tfa"]?
@@ -827,27 +846,55 @@ post "/login" do |env|
end
elsif account_type == "invidious"
answer = env.params.body["answer"]?
+ text_answer = env.params.body["text_answer"]?
- if !answer
- error_message = "CAPTCHA is a required field"
- next templated "error"
- end
+ if answer
+ answer = answer.lstrip('0')
+ answer = OpenSSL::HMAC.hexdigest(:sha256, HMAC_KEY, answer)
- answer = answer.lstrip('0')
- answer = OpenSSL::HMAC.hexdigest(:sha256, HMAC_KEY, answer)
+ challenge = env.params.body["challenge"]?
+ token = env.params.body["token"]?
- challenge = env.params.body["challenge"]?
- token = env.params.body["token"]?
+ begin
+ validate_response(challenge, token, answer, "sign_in", HMAC_KEY, PG_DB)
+ rescue ex
+ if ex.message == "Invalid user"
+ error_message = "Invalid answer"
+ else
+ error_message = ex.message
+ end
- begin
- validate_response(challenge, token, answer, "sign_in", HMAC_KEY, PG_DB)
- rescue ex
- if ex.message && ex.message == "Invalid user"
- error_message = "Invalid CAPTCHA response"
- else
- error_message = ex.message
+ next templated "error"
+ end
+ elsif text_answer
+ text_answer = Digest::MD5.hexdigest(text_answer.downcase.strip)
+
+ challenges = env.params.body.select { |k, v| k.match(/text_challenge\d+/) }
+ tokens = env.params.body.select { |k, v| k.match(/text_token\d+/) }
+
+ found_valid_captcha = false
+
+ error_message = "Invalid CAPTCHA"
+ challenges.each_with_index do |challenge, i|
+ begin
+ challenge = challenge[1]
+ token = tokens[i][1]
+ validate_response(challenge, token, text_answer, "sign_in", HMAC_KEY, PG_DB)
+ found_valid_captcha = true
+ rescue ex
+ if ex.message == "Invalid user"
+ error_message = "Invalid answer"
+ else
+ error_message = ex.message
+ end
+ end
end
+ if !found_valid_captcha
+ next templated "error"
+ end
+ else
+ error_message = "CAPTCHA is a required field"
next templated "error"
end
diff --git a/src/invidious/views/login.ecr b/src/invidious/views/login.ecr
index 0243d900..69f04ed2 100644
--- a/src/invidious/views/login.ecr
+++ b/src/invidious/views/login.ecr
@@ -24,11 +24,27 @@
+ <% if captcha_type == "image" %>
-
-
+
+
+
+ <% else %>
+ <% text_captcha.not_nil![:tokens].each_with_index do |token, i| %>
+
+
+ <% end %>
+
+
+
+
+ <% end %>