summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/invidious.cr1
-rw-r--r--src/invidious/helpers/helpers.cr25
-rw-r--r--src/invidious/routes/account.cr7
-rw-r--r--src/invidious/routes/api/v1/authenticated.cr4
-rw-r--r--src/invidious/routes/before_all.cr60
-rw-r--r--src/invidious/routes/feeds.cr4
-rw-r--r--src/invidious/routes/login.cr281
-rw-r--r--src/invidious/routes/notifications.cr44
-rw-r--r--src/invidious/routes/playlists.cr4
-rw-r--r--src/invidious/routes/subscriptions.cr13
-rw-r--r--src/invidious/routing.cr1
-rw-r--r--src/invidious/users.cr101
-rw-r--r--src/invidious/videos.cr4
-rw-r--r--src/invidious/videos/parser.cr6
-rw-r--r--src/invidious/views/privacy.ecr3
-rw-r--r--src/invidious/views/user/login.ecr36
-rw-r--r--src/invidious/yt_backend/connection_pool.cr3
17 files changed, 31 insertions, 566 deletions
diff --git a/src/invidious.cr b/src/invidious.cr
index 27c4775e..636e28a6 100644
--- a/src/invidious.cr
+++ b/src/invidious.cr
@@ -63,7 +63,6 @@ HMAC_KEY = CONFIG.hmac_key || Random::Secure.hex(32)
PG_DB = DB.open CONFIG.database_url
ARCHIVE_URL = URI.parse("https://archive.org")
-LOGIN_URL = URI.parse("https://accounts.google.com")
PUBSUB_URL = URI.parse("https://pubsubhubbub.appspot.com")
REDDIT_URL = URI.parse("https://www.reddit.com")
YT_URL = URI.parse("https://www.youtube.com")
diff --git a/src/invidious/helpers/helpers.cr b/src/invidious/helpers/helpers.cr
index c3b53339..23ff0da9 100644
--- a/src/invidious/helpers/helpers.cr
+++ b/src/invidious/helpers/helpers.cr
@@ -22,31 +22,6 @@ struct Annotation
property annotations : String
end
-def login_req(f_req)
- data = {
- # Unfortunately there's not much information available on `bgRequest`; part of Google's BotGuard
- # Generally this is much longer (>1250 characters), see also
- # https://github.com/ytdl-org/youtube-dl/commit/baf67a604d912722b0fe03a40e9dc5349a2208cb .
- # For now this can be empty.
- "bgRequest" => %|["identifier",""]|,
- "pstMsg" => "1",
- "checkConnection" => "youtube",
- "checkedDomains" => "youtube",
- "hl" => "en",
- "deviceinfo" => %|[null,null,null,[],null,"US",null,null,[],"GlifWebSignIn",null,[null,null,[]]]|,
- "f.req" => f_req,
- "flowName" => "GlifWebSignIn",
- "flowEntry" => "ServiceLogin",
- # "cookiesDisabled" => "false",
- # "gmscoreversion" => "undefined",
- # "continue" => "https://accounts.google.com/ManageAccount",
- # "azt" => "",
- # "bgHash" => "",
- }
-
- return HTTP::Params.encode(data)
-end
-
def html_to_content(description_html : String)
description = description_html.gsub(/(<br>)|(<br\/>)/, {
"<br>": "\n",
diff --git a/src/invidious/routes/account.cr b/src/invidious/routes/account.cr
index 5aa4452c..9d930841 100644
--- a/src/invidious/routes/account.cr
+++ b/src/invidious/routes/account.cr
@@ -42,11 +42,6 @@ module Invidious::Routes::Account
sid = sid.as(String)
token = env.params.body["csrf_token"]?
- # We don't store passwords for Google accounts
- if !user.password
- return error_template(400, "Cannot change password for Google accounts")
- end
-
begin
validate_request(token, sid, env.request, HMAC_KEY, locale)
rescue ex
@@ -54,7 +49,7 @@ module Invidious::Routes::Account
end
password = env.params.body["password"]?
- if !password
+ if password.nil? || password.empty?
return error_template(401, "Password is a required field")
end
diff --git a/src/invidious/routes/api/v1/authenticated.cr b/src/invidious/routes/api/v1/authenticated.cr
index ce2ee812..a35d2f2b 100644
--- a/src/invidious/routes/api/v1/authenticated.cr
+++ b/src/invidious/routes/api/v1/authenticated.cr
@@ -178,10 +178,6 @@ module Invidious::Routes::API::V1::Authenticated
Invidious::Database::Users.subscribe_channel(user, ucid)
end
- # For Google accounts, access tokens don't have enough information to
- # make a request on the user's behalf, which is why we don't sync with
- # YouTube.
-
env.response.status_code = 204
end
diff --git a/src/invidious/routes/before_all.cr b/src/invidious/routes/before_all.cr
index 8e2a253f..396840a4 100644
--- a/src/invidious/routes/before_all.cr
+++ b/src/invidious/routes/before_all.cr
@@ -80,49 +80,23 @@ module Invidious::Routes::BeforeAll
raise "Cannot use token as SID"
end
- # Invidious users only have SID
- if !env.request.cookies.has_key? "SSID"
- if email = Invidious::Database::SessionIDs.select_email(sid)
- user = Invidious::Database::Users.select!(email: email)
- csrf_token = generate_response(sid, {
- ":authorize_token",
- ":playlist_ajax",
- ":signout",
- ":subscription_ajax",
- ":token_ajax",
- ":watch_ajax",
- }, HMAC_KEY, 1.week)
-
- preferences = user.preferences
- env.set "preferences", preferences
-
- env.set "sid", sid
- env.set "csrf_token", csrf_token
- env.set "user", user
- end
- else
- headers = HTTP::Headers.new
- headers["Cookie"] = env.request.headers["Cookie"]
-
- begin
- user, sid = get_user(sid, headers, false)
- csrf_token = generate_response(sid, {
- ":authorize_token",
- ":playlist_ajax",
- ":signout",
- ":subscription_ajax",
- ":token_ajax",
- ":watch_ajax",
- }, HMAC_KEY, 1.week)
-
- preferences = user.preferences
- env.set "preferences", preferences
-
- env.set "sid", sid
- env.set "csrf_token", csrf_token
- env.set "user", user
- rescue ex
- end
+ if email = Database::SessionIDs.select_email(sid)
+ user = Database::Users.select!(email: email)
+ csrf_token = generate_response(sid, {
+ ":authorize_token",
+ ":playlist_ajax",
+ ":signout",
+ ":subscription_ajax",
+ ":token_ajax",
+ ":watch_ajax",
+ }, HMAC_KEY, 1.week)
+
+ preferences = user.preferences
+ env.set "preferences", preferences
+
+ env.set "sid", sid
+ env.set "csrf_token", csrf_token
+ env.set "user", user
end
end
diff --git a/src/invidious/routes/feeds.cr b/src/invidious/routes/feeds.cr
index fb482e33..fc62c5a3 100644
--- a/src/invidious/routes/feeds.cr
+++ b/src/invidious/routes/feeds.cr
@@ -83,10 +83,6 @@ module Invidious::Routes::Feeds
headers = HTTP::Headers.new
headers["Cookie"] = env.request.headers["Cookie"]
- if !user.password
- user, sid = get_user(sid, headers)
- end
-
max_results = env.params.query["max_results"]?.try &.to_i?.try &.clamp(0, MAX_ITEMS_PER_PAGE)
max_results ||= user.preferences.max_results
max_results ||= CONFIG.default_user_preferences.max_results
diff --git a/src/invidious/routes/login.cr b/src/invidious/routes/login.cr
index 6454131a..d0f7ac22 100644
--- a/src/invidious/routes/login.cr
+++ b/src/invidious/routes/login.cr
@@ -24,9 +24,6 @@ module Invidious::Routes::Login
captcha_type = env.params.query["captcha"]?
captcha_type ||= "image"
- tfa = env.params.query["tfa"]?
- prompt = nil
-
templated "user/login"
end
@@ -47,283 +44,18 @@ module Invidious::Routes::Login
account_type ||= "invidious"
case account_type
- when "google"
- tfa_code = env.params.body["tfa"]?.try &.lchop("G-")
- traceback = IO::Memory.new
-
- # See https://github.com/ytdl-org/youtube-dl/blob/2019.04.07/youtube_dl/extractor/youtube.py#L82
- begin
- client = nil # Declare variable
- {% unless flag?(:disable_quic) %}
- client = CONFIG.use_quic ? QUIC::Client.new(LOGIN_URL) : HTTP::Client.new(LOGIN_URL)
- {% else %}
- client = HTTP::Client.new(LOGIN_URL)
- {% end %}
-
- headers = HTTP::Headers.new
-
- login_page = client.get("/ServiceLogin")
- headers = login_page.cookies.add_request_headers(headers)
-
- lookup_req = {
- email, nil, [] of String, nil, "US", nil, nil, 2, false, true,
- {nil, nil,
- {2, 1, nil, 1,
- "https://accounts.google.com/ServiceLogin?passive=true&continue=https%3A%2F%2Fwww.youtube.com%2Fsignin%3Fnext%3D%252F%26action_handle_signin%3Dtrue%26hl%3Den%26app%3Ddesktop%26feature%3Dsign_in_button&hl=en&service=youtube&uilel=3&requestPath=%2FServiceLogin&Page=PasswordSeparationSignIn",
- nil, [] of String, 4},
- 1,
- {nil, nil, [] of String},
- nil, nil, nil, true,
- },
- email,
- }.to_json
-
- traceback << "Getting lookup..."
-
- headers["Content-Type"] = "application/x-www-form-urlencoded;charset=utf-8"
- headers["Google-Accounts-XSRF"] = "1"
-
- response = client.post("/_/signin/sl/lookup", headers, login_req(lookup_req))
- lookup_results = JSON.parse(response.body[5..-1])
-
- traceback << "done, returned #{response.status_code}.<br/>"
-
- 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, captcha, true},
- },
- {nil, nil,
- {2, 1, nil, 1,
- "https://accounts.google.com/ServiceLogin?passive=true&continue=https%3A%2F%2Fwww.youtube.com%2Fsignin%3Fnext%3D%252F%26action_handle_signin%3Dtrue%26hl%3Den%26app%3Ddesktop%26feature%3Dsign_in_button&hl=en&service=youtube&uilel=3&requestPath=%2FServiceLogin&Page=PasswordSeparationSignIn",
- nil, [] of String, 4},
- 1,
- {nil, nil, [] of String},
- nil, nil, nil, true,
- },
- }.to_json
-
- traceback << "Getting challenge..."
-
- response = client.post("/_/signin/sl/challenge", headers, login_req(challenge_req))
- headers = response.cookies.add_request_headers(headers)
- challenge_results = JSON.parse(response.body[5..-1])
-
- traceback << "done, returned #{response.status_code}.<br/>"
-
- headers["Cookie"] = URI.decode_www_form(headers["Cookie"])
-
- if challenge_results[0][3]?.try &.== 7
- return error_template(423, "Account has temporarily been disabled")
- end
-
- 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: ""}
-
- return templated "user/login"
- end
-
- if challenge_results[0][-1]?.try &.[5] == "INCORRECT_ANSWER_ENTERED"
- return error_template(401, "Incorrect password")
- end
-
- prompt_type = challenge_results[0][-1]?.try &.[0].as_a?.try &.[0][2]?
- if {"TWO_STEP_VERIFICATION", "LOGIN_CHALLENGE"}.includes? prompt_type
- traceback << "Handling prompt #{prompt_type}.<br/>"
- case prompt_type
- when "TWO_STEP_VERIFICATION"
- prompt_type = 2
- else # "LOGIN_CHALLENGE"
- prompt_type = 4
- end
-
- # Prefer Authenticator app and SMS over unsupported protocols
- if !{6, 9, 12, 15}.includes?(challenge_results[0][-1][0][0][8].as_i) && prompt_type == 2
- tfa = challenge_results[0][-1][0].as_a.select { |auth_type| {6, 9, 12, 15}.includes? auth_type[8] }[0]
-
- traceback << "Selecting challenge #{tfa[8]}..."
- select_challenge = {prompt_type, nil, nil, nil, {tfa[8]}}.to_json
-
- tl = challenge_results[1][2]
-
- tfa = client.post("/_/signin/selectchallenge?TL=#{tl}", headers, login_req(select_challenge)).body
- tfa = tfa[5..-1]
- tfa = JSON.parse(tfa)[0][-1]
-
- traceback << "done.<br/>"
- else
- traceback << "Using challenge #{challenge_results[0][-1][0][0][8]}.<br/>"
- tfa = challenge_results[0][-1][0][0]
- end
-
- if tfa[5] == "QUOTA_EXCEEDED"
- return error_template(423, "Quota exceeded, try again in a few hours")
- end
-
- if !tfa_code
- account_type = "google"
- captcha_type = "image"
-
- case tfa[8]
- when 6, 9
- prompt = "Google verification code"
- when 12
- prompt = "Login verification, recovery email: #{tfa[-1][tfa[-1].as_h.keys[0]][0]}"
- when 15
- prompt = "Login verification, security question: #{tfa[-1][tfa[-1].as_h.keys[0]][0]}"
- else
- prompt = "Google verification code"
- end
-
- tfa = nil
- captcha = nil
- return templated "user/login"
- end
-
- tl = challenge_results[1][2]
-
- request_type = tfa[8]
- case request_type
- when 6 # Authenticator app
- tfa_req = {
- user_hash, nil, 2, nil,
- {6, nil, nil, nil, nil,
- {tfa_code, false},
- },
- }.to_json
- when 9 # Voice or text message
- tfa_req = {
- user_hash, nil, 2, nil,
- {9, nil, nil, nil, nil, nil, nil, nil,
- {nil, tfa_code, false, 2},
- },
- }.to_json
- when 12 # Recovery email
- tfa_req = {
- user_hash, nil, 4, nil,
- {12, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
- {tfa_code},
- },
- }.to_json
- when 15 # Security question
- tfa_req = {
- user_hash, nil, 5, nil,
- {15, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
- {tfa_code},
- },
- }.to_json
- else
- return error_template(500, "Unable to log in, make sure two-factor authentication (Authenticator or SMS) is turned on.")
- end
-
- traceback << "Submitting challenge..."
-
- response = client.post("/_/signin/challenge?hl=en&TL=#{tl}", headers, login_req(tfa_req))
- headers = response.cookies.add_request_headers(headers)
- challenge_results = JSON.parse(response.body[5..-1])
-
- if (challenge_results[0][-1]?.try &.[5] == "INCORRECT_ANSWER_ENTERED") ||
- (challenge_results[0][-1]?.try &.[5] == "INVALID_INPUT")
- return error_template(401, "Invalid TFA code")
- end
-
- traceback << "done.<br/>"
- end
-
- traceback << "Logging in..."
-
- location = URI.parse(challenge_results[0][-1][2].to_s)
- cookies = HTTP::Cookies.from_client_headers(headers)
-
- headers.delete("Content-Type")
- headers.delete("Google-Accounts-XSRF")
-
- loop do
- if !location || location.path == "/ManageAccount"
- break
- end
-
- # Occasionally there will be a second page after login confirming
- # the user's phone number ("/b/0/SmsAuthInterstitial"), which we currently don't handle.
-
- if location.path.starts_with? "/b/0/SmsAuthInterstitial"
- traceback << "Unhandled dialog /b/0/SmsAuthInterstitial."
- end
-
- login = client.get(location.request_target, headers)
-
- headers = login.cookies.add_request_headers(headers)
- location = login.headers["Location"]?.try { |u| URI.parse(u) }
- end
-
- cookies = HTTP::Cookies.from_client_headers(headers)
- sid = cookies["SID"]?.try &.value
- if !sid
- raise "Couldn't get SID."
- end
-
- user, sid = get_user(sid, headers)
-
- # We are now logged in
- traceback << "done.<br/>"
-
- host = URI.parse(env.request.headers["Host"]).host
-
- cookies.each do |cookie|
- cookie.secure = Invidious::User::Cookies::SECURE
-
- if cookie.extension
- cookie.extension = cookie.extension.not_nil!.gsub(".youtube.com", host)
- cookie.extension = cookie.extension.not_nil!.gsub("Secure; ", "")
- end
- env.response.cookies << cookie
- end
-
- if env.request.cookies["PREFS"]?
- user.preferences = env.get("preferences").as(Preferences)
- Invidious::Database::Users.update_preferences(user)
-
- cookie = env.request.cookies["PREFS"]
- cookie.expires = Time.utc(1990, 1, 1)
- env.response.cookies << cookie
- end
-
- env.redirect referer
- rescue ex
- traceback.rewind
- # error_message = translate(locale, "Login failed. This may be because two-factor authentication is not turned on for your account.")
- error_message = %(#{ex.message}<br/>Traceback:<br/><div style="padding-left:2em" id="traceback">#{traceback.gets_to_end}</div>)
- return error_template(500, error_message)
- end
when "invidious"
- if !email
+ if email.nil? || email.empty?
return error_template(401, "User ID is a required field")
end
- if !password
+ if password.nil? || password.empty?
return error_template(401, "Password is a required field")
end
user = Invidious::Database::Users.select(email: email)
if user
- if !user.password
- return error_template(400, "Please sign in using 'Log in with Google'")
- end
-
if Crypto::Bcrypt::Password.new(user.password.not_nil!).verify(password.byte_slice(0, 55))
sid = Base64.urlsafe_encode(Random::Secure.random_bytes(32))
Invidious::Database::SessionIDs.insert(sid, email)
@@ -367,8 +99,6 @@ module Invidious::Routes::Login
captcha_type ||= "image"
account_type = "invidious"
- tfa = false
- prompt = ""
if captcha_type == "image"
captcha = Invidious::User::Captcha.generate_image(HMAC_KEY)
@@ -481,11 +211,4 @@ module Invidious::Routes::Login
env.redirect referer
end
-
- def self.captcha(env)
- headers = HTTP::Headers{":authority" => "accounts.google.com"}
- response = YT_POOL.client &.get(env.request.resource, headers)
- env.response.headers["Content-Type"] = response.headers["Content-Type"]
- response.body
- end
end
diff --git a/src/invidious/routes/notifications.cr b/src/invidious/routes/notifications.cr
index 272a3dc7..8922b740 100644
--- a/src/invidious/routes/notifications.cr
+++ b/src/invidious/routes/notifications.cr
@@ -24,50 +24,6 @@ module Invidious::Routes::Notifications
user = user.as(User)
- if !user.password
- channel_req = {} of String => String
-
- channel_req["receive_all_updates"] = env.params.query["receive_all_updates"]? || "true"
- channel_req["receive_no_updates"] = env.params.query["receive_no_updates"]? || ""
- channel_req["receive_post_updates"] = env.params.query["receive_post_updates"]? || "true"
-
- channel_req.reject! { |k, v| v != "true" && v != "false" }
-
- headers = HTTP::Headers.new
- headers["Cookie"] = env.request.headers["Cookie"]
-
- html = YT_POOL.client &.get("/subscription_manager?disable_polymer=1", headers)
-
- cookies = HTTP::Cookies.from_client_headers(headers)
- html.cookies.each do |cookie|
- if {"VISITOR_INFO1_LIVE", "YSC", "SIDCC"}.includes? cookie.name
- if cookies[cookie.name]?
- cookies[cookie.name] = cookie
- else
- cookies << cookie
- end
- end
- end
- headers = cookies.add_request_headers(headers)
-
- if match = html.body.match(/'XSRF_TOKEN': "(?<session_token>[^"]+)"/)
- session_token = match["session_token"]
- else
- return env.redirect referer
- end
-
- headers["content-type"] = "application/x-www-form-urlencoded"
- channel_req["session_token"] = session_token
-
- subs = XML.parse_html(html.body)
- subs.xpath_nodes(%q(//a[@class="subscription-title yt-uix-sessionlink"]/@href)).each do |channel|
- channel_id = channel.content.lstrip("/channel/").not_nil!
- channel_req["channel_id"] = channel_id
-
- YT_POOL.client &.post("/subscription_ajax?action_update_subscription_preferences=1", headers, form: channel_req)
- end
- end
-
if redirect
env.redirect referer
else
diff --git a/src/invidious/routes/playlists.cr b/src/invidious/routes/playlists.cr
index 8675fa45..1dd3f32e 100644
--- a/src/invidious/routes/playlists.cr
+++ b/src/invidious/routes/playlists.cr
@@ -320,10 +320,6 @@ module Invidious::Routes::Playlists
end
end
- if !user.password
- # TODO: Playlist stub, sync with YouTube for Google accounts
- # playlist_ajax(playlist_id, action, env.request.headers)
- end
email = user.email
case action
diff --git a/src/invidious/routes/subscriptions.cr b/src/invidious/routes/subscriptions.cr
index 0704c05e..7f9ec592 100644
--- a/src/invidious/routes/subscriptions.cr
+++ b/src/invidious/routes/subscriptions.cr
@@ -43,11 +43,6 @@ module Invidious::Routes::Subscriptions
channel_id = env.params.query["c"]?
channel_id ||= ""
- if !user.password
- # Sync subscriptions with YouTube
- subscribe_ajax(channel_id, action, env.request.headers)
- end
-
case action
when "action_create_subscription_to_channel"
if !user.subscriptions.includes? channel_id
@@ -82,14 +77,6 @@ module Invidious::Routes::Subscriptions
user = user.as(User)
sid = sid.as(String)
- if !user.password
- # Refresh account
- headers = HTTP::Headers.new
- headers["Cookie"] = env.request.headers["Cookie"]
-
- user, sid = get_user(sid, headers)
- end
-
action_takeout = env.params.query["action_takeout"]?.try &.to_i?
action_takeout ||= 0
action_takeout = action_takeout == 1
diff --git a/src/invidious/routing.cr b/src/invidious/routing.cr
index 72ee9194..daaf4d88 100644
--- a/src/invidious/routing.cr
+++ b/src/invidious/routing.cr
@@ -57,7 +57,6 @@ module Invidious::Routing
get "/login", Routes::Login, :login_page
post "/login", Routes::Login, :login
post "/signout", Routes::Login, :signout
- get "/Captcha", Routes::Login, :captcha
# User preferences
get "/preferences", Routes::PreferencesRoute, :show
diff --git a/src/invidious/users.cr b/src/invidious/users.cr
index b763596b..65566d20 100644
--- a/src/invidious/users.cr
+++ b/src/invidious/users.cr
@@ -3,75 +3,6 @@ require "crypto/bcrypt/password"
# Materialized views may not be defined using bound parameters (`$1` as used elsewhere)
MATERIALIZED_VIEW_SQL = ->(email : String) { "SELECT cv.* FROM channel_videos cv WHERE EXISTS (SELECT subscriptions FROM users u WHERE cv.ucid = ANY (u.subscriptions) AND u.email = E'#{email.gsub({'\'' => "\\'", '\\' => "\\\\"})}') ORDER BY published DESC" }
-def get_user(sid, headers, refresh = true)
- if email = Invidious::Database::SessionIDs.select_email(sid)
- user = Invidious::Database::Users.select!(email: email)
-
- if refresh && Time.utc - user.updated > 1.minute
- user, sid = fetch_user(sid, headers)
-
- Invidious::Database::Users.insert(user, update_on_conflict: true)
- Invidious::Database::SessionIDs.insert(sid, user.email, handle_conflicts: true)
-
- begin
- view_name = "subscriptions_#{sha256(user.email)}"
- PG_DB.exec("CREATE MATERIALIZED VIEW #{view_name} AS #{MATERIALIZED_VIEW_SQL.call(user.email)}")
- rescue ex
- end
- end
- else
- user, sid = fetch_user(sid, headers)
-
- Invidious::Database::Users.insert(user, update_on_conflict: true)
- Invidious::Database::SessionIDs.insert(sid, user.email, handle_conflicts: true)
-
- begin
- view_name = "subscriptions_#{sha256(user.email)}"
- PG_DB.exec("CREATE MATERIALIZED VIEW #{view_name} AS #{MATERIALIZED_VIEW_SQL.call(user.email)}")
- rescue ex
- end
- end
-
- return user, sid
-end
-
-def fetch_user(sid, headers)
- feed = YT_POOL.client &.get("/subscription_manager?disable_polymer=1", headers)
- feed = XML.parse_html(feed.body)
-
- channels = feed.xpath_nodes(%q(//ul[@id="guide-channels"]/li/a)).compact_map do |channel|
- if {"Popular on YouTube", "Music", "Sports", "Gaming"}.includes? channel["title"]
- nil
- else
- channel["href"].lstrip("/channel/")
- end
- end
-
- channels = get_batch_channels(channels)
-
- email = feed.xpath_node(%q(//a[@class="yt-masthead-picker-header yt-masthead-picker-active-account"]))
- if email
- email = email.content.strip
- else
- email = ""
- end
-
- token = Base64.urlsafe_encode(Random::Secure.random_bytes(32))
-
- user = Invidious::User.new({
- updated: Time.utc,
- notifications: [] of String,
- subscriptions: channels,
- email: email,
- preferences: Preferences.new(CONFIG.default_user_preferences.to_tuple),
- password: nil,
- token: token,
- watched: [] of String,
- feed_needs_update: true,
- })
- return user, sid
-end
-
def create_user(sid, email, password)
password = Crypto::Bcrypt::Password.create(password, cost: 10)
token = Base64.urlsafe_encode(Random::Secure.random_bytes(32))
@@ -91,38 +22,6 @@ def create_user(sid, email, password)
return user, sid
end
-def subscribe_ajax(channel_id, action, env_headers)
- headers = HTTP::Headers.new
- headers["Cookie"] = env_headers["Cookie"]
-
- html = YT_POOL.client &.get("/subscription_manager?disable_polymer=1", headers)
-
- cookies = HTTP::Cookies.from_client_headers(headers)
- html.cookies.each do |cookie|
- if {"VISITOR_INFO1_LIVE", "YSC", "SIDCC"}.includes? cookie.name
- if cookies[cookie.name]?
- cookies[cookie.name] = cookie
- else
- cookies << cookie
- end
- end
- end
- headers = cookies.add_request_headers(headers)
-
- if match = html.body.match(/'XSRF_TOKEN': "(?<session_token>[^"]+)"/)
- session_token = match["session_token"]
-
- headers["content-type"] = "application/x-www-form-urlencoded"
-
- post_req = {
- session_token: session_token,
- }
- post_url = "/subscription_ajax?#{action}=1&c=#{channel_id}"
-
- YT_POOL.client &.post(post_url, headers, form: post_req)
- end
-end
-
def get_subscription_feed(user, max_results = 40, page = 1)
limit = max_results.clamp(0, MAX_ITEMS_PER_PAGE)
offset = (page - 1) * limit
diff --git a/src/invidious/videos.cr b/src/invidious/videos.cr
index 0038a97a..f38b33e5 100644
--- a/src/invidious/videos.cr
+++ b/src/invidious/videos.cr
@@ -394,7 +394,9 @@ def fetch_video(id, region)
if reason = info["reason"]?
if reason == "Video unavailable"
raise NotFoundException.new(reason.as_s || "")
- else
+ elsif !reason.as_s.starts_with? "Premieres"
+ # dont error when it's a premiere.
+ # we already parsed most of the data and display the premiere date
raise InfoException.new(reason.as_s || "")
end
end
diff --git a/src/invidious/videos/parser.cr b/src/invidious/videos/parser.cr
index 2e8eecc3..9cc0ffdc 100644
--- a/src/invidious/videos/parser.cr
+++ b/src/invidious/videos/parser.cr
@@ -78,7 +78,11 @@ def extract_video_info(video_id : String, proxy_region : String? = nil)
elsif video_id != player_response.dig("videoDetails", "videoId")
# YouTube may return a different video player response than expected.
# See: https://github.com/TeamNewPipe/NewPipe/issues/8713
- raise VideoNotAvailableException.new("The video returned by YouTube isn't the requested one. (WEB client)")
+ # Line to be reverted if one day we solve the video not available issue.
+ return {
+ "version" => JSON::Any.new(Video::SCHEMA_VERSION.to_i64),
+ "reason" => JSON::Any.new("Can't load the video on this Invidious instance. YouTube is currently trying to block Invidious instances. <a href=\"https://github.com/iv-org/invidious/issues/3822\">Click here for more info about the issue.</a>"),
+ }
else
reason = nil
end
diff --git a/src/invidious/views/privacy.ecr b/src/invidious/views/privacy.ecr
index 643f880b..bc5ff40b 100644
--- a/src/invidious/views/privacy.ecr
+++ b/src/invidious/views/privacy.ecr
@@ -16,12 +16,11 @@
<li>a list of channel UCIDs the user is subscribed to</li>
<li>a user ID (for persistent storage of subscriptions and preferences)</li>
<li>a json object containing user preferences</li>
- <li>a hashed password if applicable (not present on google accounts)</li>
+ <li>a hashed password</li>
<li>a randomly generated token for providing an RSS feed of a user's subscriptions</li>
<li>a list of video IDs identifying watched videos</li>
</ul>
<p>Users can clear their watch history using the <a href="/clear_watch_history">clear watch history</a> page.</p>
- <p>If a user is logged in with a Google account, no password will ever be stored. This website uses the session token provided by Google to identify a user, but does not store the information required to make requests on a user's behalf without their knowledge or consent.</p>
<h3>Data you passively provide</h3>
<p>When you request any resource from this website (for example: a page, a font, an image, or an API endpoint) information about the request may be logged.</p>
diff --git a/src/invidious/views/user/login.ecr b/src/invidious/views/user/login.ecr
index 01d7a210..2b03d280 100644
--- a/src/invidious/views/user/login.ecr
+++ b/src/invidious/views/user/login.ecr
@@ -7,42 +7,6 @@
<div class="pure-u-1 pure-u-lg-3-5">
<div class="h-box">
<% case account_type when %>
- <% when "google" %>
- <form class="pure-form pure-form-stacked" action="/login?referer=<%= URI.encode_www_form(referer) %>&type=google" method="post">
- <fieldset>
- <% if email %>
- <input name="email" type="hidden" value="<%= HTML.escape(email) %>">
- <% else %>
- <label for="email"><%= translate(locale, "E-mail") %> :</label>
- <input required class="pure-input-1" name="email" type="email" placeholder="<%= translate(locale, "E-mail") %>">
- <% end %>
-
- <% if password %>
- <input name="password" type="hidden" value="<%= HTML.escape(password) %>">
- <% else %>
- <label for="password"><%= translate(locale, "Password") %> :</label>
- <input required class="pure-input-1" name="password" type="password" placeholder="<%= translate(locale, "Password") %>">
- <% end %>
-
- <% 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>
<% else # "invidious" %>
<form class="pure-form pure-form-stacked" action="/login?referer=<%= URI.encode_www_form(referer) %>&type=invidious" method="post">
<fieldset>
diff --git a/src/invidious/yt_backend/connection_pool.cr b/src/invidious/yt_backend/connection_pool.cr
index b4c1878c..658731cf 100644
--- a/src/invidious/yt_backend/connection_pool.cr
+++ b/src/invidious/yt_backend/connection_pool.cr
@@ -14,8 +14,9 @@ def add_yt_headers(request)
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-Language"] ||= "en-us,en;q=0.5"
+
# Preserve original cookies and add new YT consent cookie for EU servers
- request.headers["Cookie"] = "#{request.headers["cookie"]?}; CONSENT=YES+"
+ request.headers["Cookie"] = "#{request.headers["cookie"]?}; CONSENT=PENDING+#{Random.rand(100..999)}"
if !CONFIG.cookies.empty?
request.headers["Cookie"] = "#{(CONFIG.cookies.map { |c| "#{c.name}=#{c.value}" }).join("; ")}; #{request.headers["cookie"]?}"
end