Add support for translations
This commit is contained in:
parent
5b2b026468
commit
a160c645c9
28 changed files with 502 additions and 272 deletions
219
src/invidious.cr
219
src/invidious.cr
|
@ -88,6 +88,15 @@ REDDIT_URL = URI.parse("https://www.reddit.com")
|
||||||
LOGIN_URL = URI.parse("https://accounts.google.com")
|
LOGIN_URL = URI.parse("https://accounts.google.com")
|
||||||
TEXTCAPTCHA_URL = URI.parse("http://textcaptcha.com/omarroth@hotmail.com.json")
|
TEXTCAPTCHA_URL = URI.parse("http://textcaptcha.com/omarroth@hotmail.com.json")
|
||||||
|
|
||||||
|
LOCALES = {
|
||||||
|
"ar" => load_locale("ar"),
|
||||||
|
"de" => load_locale("de"),
|
||||||
|
"en-US" => load_locale("en-US"),
|
||||||
|
"nl" => load_locale("nl"),
|
||||||
|
"pl" => load_locale("pl"),
|
||||||
|
"ru" => load_locale("ru"),
|
||||||
|
}
|
||||||
|
|
||||||
crawl_threads.times do
|
crawl_threads.times do
|
||||||
spawn do
|
spawn do
|
||||||
crawl_videos(PG_DB)
|
crawl_videos(PG_DB)
|
||||||
|
@ -147,6 +156,7 @@ before_all do |env|
|
||||||
env.set "challenge", challenge
|
env.set "challenge", challenge
|
||||||
env.set "token", token
|
env.set "token", token
|
||||||
|
|
||||||
|
locale = user.preferences.locale
|
||||||
env.set "user", user
|
env.set "user", user
|
||||||
env.set "sid", sid
|
env.set "sid", sid
|
||||||
end
|
end
|
||||||
|
@ -158,6 +168,7 @@ before_all do |env|
|
||||||
env.set "challenge", challenge
|
env.set "challenge", challenge
|
||||||
env.set "token", token
|
env.set "token", token
|
||||||
|
|
||||||
|
locale = user.preferences.locale
|
||||||
env.set "user", user
|
env.set "user", user
|
||||||
env.set "sid", sid
|
env.set "sid", sid
|
||||||
rescue ex
|
rescue ex
|
||||||
|
@ -165,6 +176,10 @@ before_all do |env|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
locale = env.params.query["hl"]? || locale
|
||||||
|
locale ||= "en-US"
|
||||||
|
env.set "locale", locale
|
||||||
|
|
||||||
current_page = env.request.path
|
current_page = env.request.path
|
||||||
if env.request.query
|
if env.request.query
|
||||||
query = HTTP::Params.parse(env.request.query.not_nil!)
|
query = HTTP::Params.parse(env.request.query.not_nil!)
|
||||||
|
@ -180,7 +195,9 @@ before_all do |env|
|
||||||
end
|
end
|
||||||
|
|
||||||
get "/" do |env|
|
get "/" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
user = env.get? "user"
|
user = env.get? "user"
|
||||||
|
|
||||||
if user
|
if user
|
||||||
user = user.as(User)
|
user = user.as(User)
|
||||||
if user.preferences.redirect_feed
|
if user.preferences.redirect_feed
|
||||||
|
@ -192,12 +209,14 @@ get "/" do |env|
|
||||||
end
|
end
|
||||||
|
|
||||||
get "/licenses" do |env|
|
get "/licenses" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
rendered "licenses"
|
rendered "licenses"
|
||||||
end
|
end
|
||||||
|
|
||||||
# Videos
|
# Videos
|
||||||
|
|
||||||
get "/:id" do |env|
|
get "/:id" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
id = env.params.url["id"]
|
id = env.params.url["id"]
|
||||||
|
|
||||||
if md = id.match(/[a-zA-Z0-9_-]{11}/)
|
if md = id.match(/[a-zA-Z0-9_-]{11}/)
|
||||||
|
@ -219,6 +238,8 @@ get "/:id" do |env|
|
||||||
end
|
end
|
||||||
|
|
||||||
get "/watch" do |env|
|
get "/watch" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
|
|
||||||
if env.params.query.to_s.includes?("%20") || env.params.query.to_s.includes?("+")
|
if env.params.query.to_s.includes?("%20") || env.params.query.to_s.includes?("+")
|
||||||
url = "/watch?" + env.params.query.to_s.gsub("%20", "").delete("+")
|
url = "/watch?" + env.params.query.to_s.gsub("%20", "").delete("+")
|
||||||
next env.redirect url
|
next env.redirect url
|
||||||
|
@ -287,11 +308,11 @@ get "/watch" do |env|
|
||||||
|
|
||||||
if source == "youtube"
|
if source == "youtube"
|
||||||
begin
|
begin
|
||||||
comment_html = JSON.parse(fetch_youtube_comments(id, "", proxies, "html"))["contentHtml"]
|
comment_html = JSON.parse(fetch_youtube_comments(id, "", proxies, "html", locale))["contentHtml"]
|
||||||
rescue ex
|
rescue ex
|
||||||
if preferences.comments[1] == "reddit"
|
if preferences.comments[1] == "reddit"
|
||||||
comments, reddit_thread = fetch_reddit_comments(id)
|
comments, reddit_thread = fetch_reddit_comments(id)
|
||||||
comment_html = template_reddit_comments(comments)
|
comment_html = template_reddit_comments(comments, locale)
|
||||||
|
|
||||||
comment_html = fill_links(comment_html, "https", "www.reddit.com")
|
comment_html = fill_links(comment_html, "https", "www.reddit.com")
|
||||||
comment_html = replace_links(comment_html)
|
comment_html = replace_links(comment_html)
|
||||||
|
@ -300,18 +321,18 @@ get "/watch" do |env|
|
||||||
elsif source == "reddit"
|
elsif source == "reddit"
|
||||||
begin
|
begin
|
||||||
comments, reddit_thread = fetch_reddit_comments(id)
|
comments, reddit_thread = fetch_reddit_comments(id)
|
||||||
comment_html = template_reddit_comments(comments)
|
comment_html = template_reddit_comments(comments, locale)
|
||||||
|
|
||||||
comment_html = fill_links(comment_html, "https", "www.reddit.com")
|
comment_html = fill_links(comment_html, "https", "www.reddit.com")
|
||||||
comment_html = replace_links(comment_html)
|
comment_html = replace_links(comment_html)
|
||||||
rescue ex
|
rescue ex
|
||||||
if preferences.comments[1] == "youtube"
|
if preferences.comments[1] == "youtube"
|
||||||
comment_html = JSON.parse(fetch_youtube_comments(id, "", proxies, "html"))["contentHtml"]
|
comment_html = JSON.parse(fetch_youtube_comments(id, "", proxies, "html", locale))["contentHtml"]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
comment_html = JSON.parse(fetch_youtube_comments(id, "", proxies, "html"))["contentHtml"]
|
comment_html = JSON.parse(fetch_youtube_comments(id, "", proxies, "html", locale))["contentHtml"]
|
||||||
end
|
end
|
||||||
|
|
||||||
comment_html ||= ""
|
comment_html ||= ""
|
||||||
|
@ -383,6 +404,7 @@ get "/watch" do |env|
|
||||||
end
|
end
|
||||||
|
|
||||||
get "/embed/:id" do |env|
|
get "/embed/:id" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
id = env.params.url["id"]
|
id = env.params.url["id"]
|
||||||
|
|
||||||
if id.includes?("%20") || id.includes?("+") || env.params.query.to_s.includes?("%20") || env.params.query.to_s.includes?("+")
|
if id.includes?("%20") || id.includes?("+") || env.params.query.to_s.includes?("%20") || env.params.query.to_s.includes?("+")
|
||||||
|
@ -470,6 +492,8 @@ end
|
||||||
# Playlists
|
# Playlists
|
||||||
|
|
||||||
get "/playlist" do |env|
|
get "/playlist" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
|
|
||||||
plid = env.params.query["list"]?
|
plid = env.params.query["list"]?
|
||||||
if !plid
|
if !plid
|
||||||
next env.redirect "/"
|
next env.redirect "/"
|
||||||
|
@ -483,14 +507,14 @@ get "/playlist" do |env|
|
||||||
end
|
end
|
||||||
|
|
||||||
begin
|
begin
|
||||||
playlist = fetch_playlist(plid)
|
playlist = fetch_playlist(plid, locale)
|
||||||
rescue ex
|
rescue ex
|
||||||
error_message = ex.message
|
error_message = ex.message
|
||||||
next templated "error"
|
next templated "error"
|
||||||
end
|
end
|
||||||
|
|
||||||
begin
|
begin
|
||||||
videos = fetch_playlist_videos(plid, page, playlist.video_count)
|
videos = fetch_playlist_videos(plid, page, playlist.video_count, locale)
|
||||||
rescue ex
|
rescue ex
|
||||||
videos = [] of PlaylistVideo
|
videos = [] of PlaylistVideo
|
||||||
end
|
end
|
||||||
|
@ -499,6 +523,8 @@ get "/playlist" do |env|
|
||||||
end
|
end
|
||||||
|
|
||||||
get "/mix" do |env|
|
get "/mix" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
|
|
||||||
rdid = env.params.query["list"]?
|
rdid = env.params.query["list"]?
|
||||||
if !rdid
|
if !rdid
|
||||||
next env.redirect "/"
|
next env.redirect "/"
|
||||||
|
@ -508,7 +534,7 @@ get "/mix" do |env|
|
||||||
continuation ||= rdid.lchop("RD")
|
continuation ||= rdid.lchop("RD")
|
||||||
|
|
||||||
begin
|
begin
|
||||||
mix = fetch_mix(rdid, continuation)
|
mix = fetch_mix(rdid, continuation, locale: locale)
|
||||||
rescue ex
|
rescue ex
|
||||||
error_message = ex.message
|
error_message = ex.message
|
||||||
next templated "error"
|
next templated "error"
|
||||||
|
@ -520,6 +546,7 @@ end
|
||||||
# Search
|
# Search
|
||||||
|
|
||||||
get "/opensearch.xml" do |env|
|
get "/opensearch.xml" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
env.response.content_type = "application/opensearchdescription+xml"
|
env.response.content_type = "application/opensearchdescription+xml"
|
||||||
|
|
||||||
XML.build(indent: " ", encoding: "UTF-8") do |xml|
|
XML.build(indent: " ", encoding: "UTF-8") do |xml|
|
||||||
|
@ -535,6 +562,8 @@ get "/opensearch.xml" do |env|
|
||||||
end
|
end
|
||||||
|
|
||||||
get "/results" do |env|
|
get "/results" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
|
|
||||||
query = env.params.query["search_query"]?
|
query = env.params.query["search_query"]?
|
||||||
query ||= env.params.query["q"]?
|
query ||= env.params.query["q"]?
|
||||||
query ||= ""
|
query ||= ""
|
||||||
|
@ -550,6 +579,8 @@ get "/results" do |env|
|
||||||
end
|
end
|
||||||
|
|
||||||
get "/search" do |env|
|
get "/search" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
|
|
||||||
query = env.params.query["search_query"]?
|
query = env.params.query["search_query"]?
|
||||||
query ||= env.params.query["q"]?
|
query ||= env.params.query["q"]?
|
||||||
query ||= ""
|
query ||= ""
|
||||||
|
@ -629,6 +660,8 @@ end
|
||||||
# Users
|
# Users
|
||||||
|
|
||||||
get "/login" do |env|
|
get "/login" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
|
|
||||||
user = env.get? "user"
|
user = env.get? "user"
|
||||||
if user
|
if user
|
||||||
next env.redirect "/feed/subscriptions"
|
next env.redirect "/feed/subscriptions"
|
||||||
|
@ -668,6 +701,8 @@ end
|
||||||
|
|
||||||
# See https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/youtube.py#L79
|
# See https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/youtube.py#L79
|
||||||
post "/login" do |env|
|
post "/login" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
|
|
||||||
referer = get_referer(env, "/feed/subscriptions")
|
referer = get_referer(env, "/feed/subscriptions")
|
||||||
|
|
||||||
email = env.params.body["email"]?
|
email = env.params.body["email"]?
|
||||||
|
@ -754,7 +789,7 @@ post "/login" do |env|
|
||||||
headers["Cookie"] = URI.unescape(headers["Cookie"])
|
headers["Cookie"] = URI.unescape(headers["Cookie"])
|
||||||
|
|
||||||
if challenge_results[0][-1]?.try &.[5] == "INCORRECT_ANSWER_ENTERED"
|
if challenge_results[0][-1]?.try &.[5] == "INCORRECT_ANSWER_ENTERED"
|
||||||
error_message = "Incorrect password"
|
error_message = translate(locale, "Incorrect password")
|
||||||
next templated "error"
|
next templated "error"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -775,7 +810,7 @@ post "/login" do |env|
|
||||||
|
|
||||||
if tfa[2] == "TWO_STEP_VERIFICATION"
|
if tfa[2] == "TWO_STEP_VERIFICATION"
|
||||||
if tfa[5] == "QUOTA_EXCEEDED"
|
if tfa[5] == "QUOTA_EXCEEDED"
|
||||||
error_message = "Quota exceeded, try again in a few hours"
|
error_message = translate(locale, "Quota exceeded, try again in a few hours")
|
||||||
next templated "error"
|
next templated "error"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -806,7 +841,7 @@ post "/login" do |env|
|
||||||
challenge_results = JSON.parse(challenge_results)
|
challenge_results = JSON.parse(challenge_results)
|
||||||
|
|
||||||
if challenge_results[0][-1]?.try &.[5] == "INCORRECT_ANSWER_ENTERED"
|
if challenge_results[0][-1]?.try &.[5] == "INCORRECT_ANSWER_ENTERED"
|
||||||
error_message = "Invalid TFA code"
|
error_message = translate(locale, "Invalid TFA code")
|
||||||
next templated "error"
|
next templated "error"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -845,7 +880,7 @@ post "/login" do |env|
|
||||||
|
|
||||||
env.redirect referer
|
env.redirect referer
|
||||||
rescue ex
|
rescue ex
|
||||||
error_message = "Login failed. This may be because two-factor authentication is not enabled on your account."
|
error_message = translate(locale, "Login failed. This may be because two-factor authentication is not enabled on your account.")
|
||||||
next templated "error"
|
next templated "error"
|
||||||
end
|
end
|
||||||
elsif account_type == "invidious"
|
elsif account_type == "invidious"
|
||||||
|
@ -860,10 +895,10 @@ post "/login" do |env|
|
||||||
token = env.params.body["token"]?
|
token = env.params.body["token"]?
|
||||||
|
|
||||||
begin
|
begin
|
||||||
validate_response(challenge, token, answer, "sign_in", HMAC_KEY, PG_DB)
|
validate_response(challenge, token, answer, "sign_in", HMAC_KEY, PG_DB, locale)
|
||||||
rescue ex
|
rescue ex
|
||||||
if ex.message == "Invalid user"
|
if ex.message == translate(locale, "Invalid user")
|
||||||
error_message = "Invalid answer"
|
error_message = translate(locale, "Invalid answer")
|
||||||
else
|
else
|
||||||
error_message = ex.message
|
error_message = ex.message
|
||||||
end
|
end
|
||||||
|
@ -878,16 +913,16 @@ post "/login" do |env|
|
||||||
|
|
||||||
found_valid_captcha = false
|
found_valid_captcha = false
|
||||||
|
|
||||||
error_message = "Invalid CAPTCHA"
|
error_message = translate(locale, "Invalid CAPTCHA")
|
||||||
challenges.each_with_index do |challenge, i|
|
challenges.each_with_index do |challenge, i|
|
||||||
begin
|
begin
|
||||||
challenge = challenge[1]
|
challenge = challenge[1]
|
||||||
token = tokens[i][1]
|
token = tokens[i][1]
|
||||||
validate_response(challenge, token, text_answer, "sign_in", HMAC_KEY, PG_DB)
|
validate_response(challenge, token, text_answer, "sign_in", HMAC_KEY, PG_DB, locale)
|
||||||
found_valid_captcha = true
|
found_valid_captcha = true
|
||||||
rescue ex
|
rescue ex
|
||||||
if ex.message == "Invalid user"
|
if ex.message == translate(locale, "Invalid user")
|
||||||
error_message = "Invalid answer"
|
error_message = translate(locale, "Invalid answer")
|
||||||
else
|
else
|
||||||
error_message = ex.message
|
error_message = ex.message
|
||||||
end
|
end
|
||||||
|
@ -898,7 +933,7 @@ post "/login" do |env|
|
||||||
next templated "error"
|
next templated "error"
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
error_message = "CAPTCHA is a required field"
|
error_message = translate(locale, "CAPTCHA is a required field")
|
||||||
next templated "error"
|
next templated "error"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -906,12 +941,12 @@ post "/login" do |env|
|
||||||
action ||= "signin"
|
action ||= "signin"
|
||||||
|
|
||||||
if !email
|
if !email
|
||||||
error_message = "User ID is a required field"
|
error_message = translate(locale, "User ID is a required field")
|
||||||
next templated "error"
|
next templated "error"
|
||||||
end
|
end
|
||||||
|
|
||||||
if !password
|
if !password
|
||||||
error_message = "Password is a required field"
|
error_message = translate(locale, "Password is a required field")
|
||||||
next templated "error"
|
next templated "error"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -919,12 +954,12 @@ post "/login" do |env|
|
||||||
user = PG_DB.query_one?("SELECT * FROM users WHERE LOWER(email) = LOWER($1) AND password IS NOT NULL", email, as: User)
|
user = PG_DB.query_one?("SELECT * FROM users WHERE LOWER(email) = LOWER($1) AND password IS NOT NULL", email, as: User)
|
||||||
|
|
||||||
if !user
|
if !user
|
||||||
error_message = "Invalid username or password"
|
error_message = translate(locale, "Invalid username or password")
|
||||||
next templated "error"
|
next templated "error"
|
||||||
end
|
end
|
||||||
|
|
||||||
if !user.password
|
if !user.password
|
||||||
error_message = "Please sign in using 'Sign in with Google'"
|
error_message = translate(locale, "Please sign in using 'Sign in with Google'")
|
||||||
next templated "error"
|
next templated "error"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -946,24 +981,24 @@ post "/login" do |env|
|
||||||
secure: secure, http_only: true)
|
secure: secure, http_only: true)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
error_message = "Invalid username or password"
|
error_message = translate(locale, "Invalid username or password")
|
||||||
next templated "error"
|
next templated "error"
|
||||||
end
|
end
|
||||||
elsif action == "register"
|
elsif action == "register"
|
||||||
if password.empty?
|
if password.empty?
|
||||||
error_message = "Password cannot be empty"
|
error_message = translate(locale, "Password cannot be empty")
|
||||||
next templated "error"
|
next templated "error"
|
||||||
end
|
end
|
||||||
|
|
||||||
# See https://security.stackexchange.com/a/39851
|
# See https://security.stackexchange.com/a/39851
|
||||||
if password.size > 55
|
if password.size > 55
|
||||||
error_message = "Password cannot be longer than 55 characters"
|
error_message = translate(locale, "Password cannot be longer than 55 characters")
|
||||||
next templated "error"
|
next templated "error"
|
||||||
end
|
end
|
||||||
|
|
||||||
user = PG_DB.query_one?("SELECT * FROM users WHERE LOWER(email) = LOWER($1) AND password IS NOT NULL", email, as: User)
|
user = PG_DB.query_one?("SELECT * FROM users WHERE LOWER(email) = LOWER($1) AND password IS NOT NULL", email, as: User)
|
||||||
if user
|
if user
|
||||||
error_message = "Please sign in"
|
error_message = translate(locale, "Please sign in")
|
||||||
next templated "error"
|
next templated "error"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1002,6 +1037,8 @@ post "/login" do |env|
|
||||||
end
|
end
|
||||||
|
|
||||||
get "/signout" do |env|
|
get "/signout" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
|
|
||||||
user = env.get? "user"
|
user = env.get? "user"
|
||||||
referer = get_referer(env)
|
referer = get_referer(env)
|
||||||
|
|
||||||
|
@ -1012,7 +1049,7 @@ get "/signout" do |env|
|
||||||
token = env.params.query["token"]?
|
token = env.params.query["token"]?
|
||||||
|
|
||||||
begin
|
begin
|
||||||
validate_response(challenge, token, user.email, "sign_out", HMAC_KEY, PG_DB)
|
validate_response(challenge, token, user.email, "sign_out", HMAC_KEY, PG_DB, locale)
|
||||||
rescue ex
|
rescue ex
|
||||||
error_message = ex.message
|
error_message = ex.message
|
||||||
next templated "error"
|
next templated "error"
|
||||||
|
@ -1033,6 +1070,8 @@ get "/signout" do |env|
|
||||||
end
|
end
|
||||||
|
|
||||||
get "/preferences" do |env|
|
get "/preferences" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
|
|
||||||
user = env.get? "user"
|
user = env.get? "user"
|
||||||
referer = get_referer(env)
|
referer = get_referer(env)
|
||||||
|
|
||||||
|
@ -1045,6 +1084,8 @@ get "/preferences" do |env|
|
||||||
end
|
end
|
||||||
|
|
||||||
post "/preferences" do |env|
|
post "/preferences" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
|
|
||||||
user = env.get? "user"
|
user = env.get? "user"
|
||||||
referer = get_referer(env)
|
referer = get_referer(env)
|
||||||
|
|
||||||
|
@ -1093,6 +1134,9 @@ post "/preferences" do |env|
|
||||||
redirect_feed ||= "off"
|
redirect_feed ||= "off"
|
||||||
redirect_feed = redirect_feed == "on"
|
redirect_feed = redirect_feed == "on"
|
||||||
|
|
||||||
|
locale = env.params.body["locale"]?.try &.as(String)
|
||||||
|
locale ||= "en-US"
|
||||||
|
|
||||||
dark_mode = env.params.body["dark_mode"]?.try &.as(String)
|
dark_mode = env.params.body["dark_mode"]?.try &.as(String)
|
||||||
dark_mode ||= "off"
|
dark_mode ||= "off"
|
||||||
dark_mode = dark_mode == "on"
|
dark_mode = dark_mode == "on"
|
||||||
|
@ -1131,6 +1175,7 @@ post "/preferences" do |env|
|
||||||
"captions" => captions,
|
"captions" => captions,
|
||||||
"related_videos" => related_videos,
|
"related_videos" => related_videos,
|
||||||
"redirect_feed" => redirect_feed,
|
"redirect_feed" => redirect_feed,
|
||||||
|
"locale" => locale,
|
||||||
"dark_mode" => dark_mode,
|
"dark_mode" => dark_mode,
|
||||||
"thin_mode" => thin_mode,
|
"thin_mode" => thin_mode,
|
||||||
"max_results" => max_results,
|
"max_results" => max_results,
|
||||||
|
@ -1147,6 +1192,8 @@ post "/preferences" do |env|
|
||||||
end
|
end
|
||||||
|
|
||||||
get "/toggle_theme" do |env|
|
get "/toggle_theme" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
|
|
||||||
user = env.get? "user"
|
user = env.get? "user"
|
||||||
referer = get_referer(env)
|
referer = get_referer(env)
|
||||||
|
|
||||||
|
@ -1167,6 +1214,8 @@ get "/toggle_theme" do |env|
|
||||||
end
|
end
|
||||||
|
|
||||||
get "/mark_watched" do |env|
|
get "/mark_watched" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
|
|
||||||
user = env.get? "user"
|
user = env.get? "user"
|
||||||
referer = get_referer(env, "/feed/subscriptions")
|
referer = get_referer(env, "/feed/subscriptions")
|
||||||
|
|
||||||
|
@ -1195,6 +1244,8 @@ get "/mark_watched" do |env|
|
||||||
end
|
end
|
||||||
|
|
||||||
get "/mark_unwatched" do |env|
|
get "/mark_unwatched" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
|
|
||||||
user = env.get? "user"
|
user = env.get? "user"
|
||||||
referer = get_referer(env, "/feed/history")
|
referer = get_referer(env, "/feed/history")
|
||||||
|
|
||||||
|
@ -1225,6 +1276,8 @@ end
|
||||||
# /modify_notifications?receive_all_updates=false&receive_no_updates=false
|
# /modify_notifications?receive_all_updates=false&receive_no_updates=false
|
||||||
# will "unding" all subscriptions.
|
# will "unding" all subscriptions.
|
||||||
get "/modify_notifications" do |env|
|
get "/modify_notifications" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
|
|
||||||
user = env.get? "user"
|
user = env.get? "user"
|
||||||
referer = get_referer(env)
|
referer = get_referer(env)
|
||||||
|
|
||||||
|
@ -1270,6 +1323,8 @@ get "/modify_notifications" do |env|
|
||||||
end
|
end
|
||||||
|
|
||||||
get "/subscription_manager" do |env|
|
get "/subscription_manager" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
|
|
||||||
user = env.get? "user"
|
user = env.get? "user"
|
||||||
referer = get_referer(env, "/")
|
referer = get_referer(env, "/")
|
||||||
|
|
||||||
|
@ -1351,6 +1406,8 @@ get "/subscription_manager" do |env|
|
||||||
end
|
end
|
||||||
|
|
||||||
get "/data_control" do |env|
|
get "/data_control" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
|
|
||||||
user = env.get? "user"
|
user = env.get? "user"
|
||||||
referer = get_referer(env)
|
referer = get_referer(env)
|
||||||
|
|
||||||
|
@ -1364,6 +1421,8 @@ get "/data_control" do |env|
|
||||||
end
|
end
|
||||||
|
|
||||||
post "/data_control" do |env|
|
post "/data_control" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
|
|
||||||
user = env.get? "user"
|
user = env.get? "user"
|
||||||
referer = get_referer(env)
|
referer = get_referer(env)
|
||||||
|
|
||||||
|
@ -1495,6 +1554,8 @@ post "/data_control" do |env|
|
||||||
end
|
end
|
||||||
|
|
||||||
get "/subscription_ajax" do |env|
|
get "/subscription_ajax" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
|
|
||||||
user = env.get? "user"
|
user = env.get? "user"
|
||||||
referer = get_referer(env)
|
referer = get_referer(env)
|
||||||
|
|
||||||
|
@ -1574,6 +1635,8 @@ get "/subscription_ajax" do |env|
|
||||||
end
|
end
|
||||||
|
|
||||||
get "/delete_account" do |env|
|
get "/delete_account" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
|
|
||||||
user = env.get? "user"
|
user = env.get? "user"
|
||||||
referer = get_referer(env)
|
referer = get_referer(env)
|
||||||
|
|
||||||
|
@ -1589,6 +1652,8 @@ get "/delete_account" do |env|
|
||||||
end
|
end
|
||||||
|
|
||||||
post "/delete_account" do |env|
|
post "/delete_account" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
|
|
||||||
user = env.get? "user"
|
user = env.get? "user"
|
||||||
referer = get_referer(env)
|
referer = get_referer(env)
|
||||||
|
|
||||||
|
@ -1599,7 +1664,7 @@ post "/delete_account" do |env|
|
||||||
token = env.params.body["token"]?
|
token = env.params.body["token"]?
|
||||||
|
|
||||||
begin
|
begin
|
||||||
validate_response(challenge, token, user.email, "delete_account", HMAC_KEY, PG_DB)
|
validate_response(challenge, token, user.email, "delete_account", HMAC_KEY, PG_DB, locale)
|
||||||
rescue ex
|
rescue ex
|
||||||
error_message = ex.message
|
error_message = ex.message
|
||||||
next templated "error"
|
next templated "error"
|
||||||
|
@ -1619,6 +1684,8 @@ post "/delete_account" do |env|
|
||||||
end
|
end
|
||||||
|
|
||||||
get "/clear_watch_history" do |env|
|
get "/clear_watch_history" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
|
|
||||||
user = env.get? "user"
|
user = env.get? "user"
|
||||||
referer = get_referer(env)
|
referer = get_referer(env)
|
||||||
|
|
||||||
|
@ -1634,6 +1701,8 @@ get "/clear_watch_history" do |env|
|
||||||
end
|
end
|
||||||
|
|
||||||
post "/clear_watch_history" do |env|
|
post "/clear_watch_history" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
|
|
||||||
user = env.get? "user"
|
user = env.get? "user"
|
||||||
referer = get_referer(env)
|
referer = get_referer(env)
|
||||||
|
|
||||||
|
@ -1644,7 +1713,7 @@ post "/clear_watch_history" do |env|
|
||||||
token = env.params.body["token"]?
|
token = env.params.body["token"]?
|
||||||
|
|
||||||
begin
|
begin
|
||||||
validate_response(challenge, token, user.email, "clear_watch_history", HMAC_KEY, PG_DB)
|
validate_response(challenge, token, user.email, "clear_watch_history", HMAC_KEY, PG_DB, locale)
|
||||||
rescue ex
|
rescue ex
|
||||||
error_message = ex.message
|
error_message = ex.message
|
||||||
next templated "error"
|
next templated "error"
|
||||||
|
@ -1659,19 +1728,25 @@ end
|
||||||
# Feeds
|
# Feeds
|
||||||
|
|
||||||
get "/feed/top" do |env|
|
get "/feed/top" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
|
|
||||||
templated "top"
|
templated "top"
|
||||||
end
|
end
|
||||||
|
|
||||||
get "/feed/popular" do |env|
|
get "/feed/popular" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
|
|
||||||
templated "popular"
|
templated "popular"
|
||||||
end
|
end
|
||||||
|
|
||||||
get "/feed/trending" do |env|
|
get "/feed/trending" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
|
|
||||||
trending_type = env.params.query["type"]?
|
trending_type = env.params.query["type"]?
|
||||||
region = env.params.query["region"]?
|
region = env.params.query["region"]?
|
||||||
|
|
||||||
begin
|
begin
|
||||||
trending = fetch_trending(trending_type, proxies, region)
|
trending = fetch_trending(trending_type, proxies, region, locale)
|
||||||
rescue ex
|
rescue ex
|
||||||
error_message = "#{ex.message}"
|
error_message = "#{ex.message}"
|
||||||
next templated "error"
|
next templated "error"
|
||||||
|
@ -1681,6 +1756,8 @@ get "/feed/trending" do |env|
|
||||||
end
|
end
|
||||||
|
|
||||||
get "/feed/subscriptions" do |env|
|
get "/feed/subscriptions" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
|
|
||||||
user = env.get? "user"
|
user = env.get? "user"
|
||||||
referer = get_referer(env)
|
referer = get_referer(env)
|
||||||
|
|
||||||
|
@ -1814,6 +1891,8 @@ get "/feed/subscriptions" do |env|
|
||||||
end
|
end
|
||||||
|
|
||||||
get "/feed/history" do |env|
|
get "/feed/history" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
|
|
||||||
user = env.get? "user"
|
user = env.get? "user"
|
||||||
referer = get_referer(env)
|
referer = get_referer(env)
|
||||||
|
|
||||||
|
@ -1837,11 +1916,13 @@ get "/feed/history" do |env|
|
||||||
end
|
end
|
||||||
|
|
||||||
get "/feed/channel/:ucid" do |env|
|
get "/feed/channel/:ucid" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
|
|
||||||
env.response.content_type = "text/xml"
|
env.response.content_type = "text/xml"
|
||||||
ucid = env.params.url["ucid"]
|
ucid = env.params.url["ucid"]
|
||||||
|
|
||||||
begin
|
begin
|
||||||
author, ucid, auto_generated = get_about_info(ucid)
|
author, ucid, auto_generated = get_about_info(ucid, locale)
|
||||||
rescue ex
|
rescue ex
|
||||||
error_message = ex.message
|
error_message = ex.message
|
||||||
halt env, status_code: 500, response: error_message
|
halt env, status_code: 500, response: error_message
|
||||||
|
@ -1906,6 +1987,8 @@ get "/feed/channel/:ucid" do |env|
|
||||||
end
|
end
|
||||||
|
|
||||||
get "/feed/private" do |env|
|
get "/feed/private" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
|
|
||||||
token = env.params.query["token"]?
|
token = env.params.query["token"]?
|
||||||
|
|
||||||
if !token
|
if !token
|
||||||
|
@ -1978,7 +2061,7 @@ get "/feed/private" do |env|
|
||||||
"xml:lang": "en-US") do
|
"xml:lang": "en-US") do
|
||||||
xml.element("link", "type": "text/html", rel: "alternate", href: "#{host_url}/feed/subscriptions")
|
xml.element("link", "type": "text/html", rel: "alternate", href: "#{host_url}/feed/subscriptions")
|
||||||
xml.element("link", "type": "application/atom+xml", rel: "self", href: "#{host_url}#{path}?#{query}")
|
xml.element("link", "type": "application/atom+xml", rel: "self", href: "#{host_url}#{path}?#{query}")
|
||||||
xml.element("title") { xml.text "Invidious Private Feed for #{user.email}" }
|
xml.element("title") { xml.text translate(locale, "Invidious Private Feed for `x`", user.email) }
|
||||||
|
|
||||||
videos.each do |video|
|
videos.each do |video|
|
||||||
xml.element("entry") do
|
xml.element("entry") do
|
||||||
|
@ -2011,6 +2094,8 @@ get "/feed/private" do |env|
|
||||||
end
|
end
|
||||||
|
|
||||||
get "/feed/playlist/:plid" do |env|
|
get "/feed/playlist/:plid" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
|
|
||||||
plid = env.params.url["plid"]
|
plid = env.params.url["plid"]
|
||||||
|
|
||||||
host_url = make_host_url(Kemal.config.ssl || CONFIG.https_only, env.request.headers["Host"]?)
|
host_url = make_host_url(Kemal.config.ssl || CONFIG.https_only, env.request.headers["Host"]?)
|
||||||
|
@ -2047,6 +2132,8 @@ end
|
||||||
# YouTube appears to let users set a "brand" URL that
|
# YouTube appears to let users set a "brand" URL that
|
||||||
# is different from their username, so we convert that here
|
# is different from their username, so we convert that here
|
||||||
get "/c/:user" do |env|
|
get "/c/:user" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
|
|
||||||
client = make_client(YT_URL)
|
client = make_client(YT_URL)
|
||||||
user = env.params.url["user"]
|
user = env.params.url["user"]
|
||||||
|
|
||||||
|
@ -2072,6 +2159,8 @@ get "/user/:user/videos" do |env|
|
||||||
end
|
end
|
||||||
|
|
||||||
get "/channel/:ucid" do |env|
|
get "/channel/:ucid" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
|
|
||||||
user = env.get? "user"
|
user = env.get? "user"
|
||||||
if user
|
if user
|
||||||
user = user.as(User)
|
user = user.as(User)
|
||||||
|
@ -2088,7 +2177,7 @@ get "/channel/:ucid" do |env|
|
||||||
sort_by ||= "newest"
|
sort_by ||= "newest"
|
||||||
|
|
||||||
begin
|
begin
|
||||||
author, ucid, auto_generated, sub_count = get_about_info(ucid)
|
author, ucid, auto_generated, sub_count = get_about_info(ucid, locale)
|
||||||
rescue ex
|
rescue ex
|
||||||
error_message = ex.message
|
error_message = ex.message
|
||||||
next templated "error"
|
next templated "error"
|
||||||
|
@ -2108,6 +2197,8 @@ get "/channel/:ucid" do |env|
|
||||||
end
|
end
|
||||||
|
|
||||||
get "/channel/:ucid/videos" do |env|
|
get "/channel/:ucid/videos" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
|
|
||||||
ucid = env.params.url["ucid"]
|
ucid = env.params.url["ucid"]
|
||||||
params = env.request.query
|
params = env.request.query
|
||||||
|
|
||||||
|
@ -2123,6 +2214,8 @@ end
|
||||||
# API Endpoints
|
# API Endpoints
|
||||||
|
|
||||||
get "/api/v1/captions/:id" do |env|
|
get "/api/v1/captions/:id" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
|
|
||||||
env.response.content_type = "application/json"
|
env.response.content_type = "application/json"
|
||||||
|
|
||||||
id = env.params.url["id"]
|
id = env.params.url["id"]
|
||||||
|
@ -2222,6 +2315,8 @@ get "/api/v1/captions/:id" do |env|
|
||||||
end
|
end
|
||||||
|
|
||||||
get "/api/v1/comments/:id" do |env|
|
get "/api/v1/comments/:id" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
|
|
||||||
env.response.content_type = "application/json"
|
env.response.content_type = "application/json"
|
||||||
|
|
||||||
id = env.params.url["id"]
|
id = env.params.url["id"]
|
||||||
|
@ -2237,7 +2332,7 @@ get "/api/v1/comments/:id" do |env|
|
||||||
|
|
||||||
if source == "youtube"
|
if source == "youtube"
|
||||||
begin
|
begin
|
||||||
comments = fetch_youtube_comments(id, continuation, proxies, format)
|
comments = fetch_youtube_comments(id, continuation, proxies, format, locale)
|
||||||
rescue ex
|
rescue ex
|
||||||
error_message = {"error" => ex.message}.to_json
|
error_message = {"error" => ex.message}.to_json
|
||||||
halt env, status_code: 500, response: error_message
|
halt env, status_code: 500, response: error_message
|
||||||
|
@ -2247,7 +2342,7 @@ get "/api/v1/comments/:id" do |env|
|
||||||
elsif source == "reddit"
|
elsif source == "reddit"
|
||||||
begin
|
begin
|
||||||
comments, reddit_thread = fetch_reddit_comments(id)
|
comments, reddit_thread = fetch_reddit_comments(id)
|
||||||
content_html = template_reddit_comments(comments)
|
content_html = template_reddit_comments(comments, locale)
|
||||||
|
|
||||||
content_html = fill_links(content_html, "https", "www.reddit.com")
|
content_html = fill_links(content_html, "https", "www.reddit.com")
|
||||||
content_html = replace_links(content_html)
|
content_html = replace_links(content_html)
|
||||||
|
@ -2276,6 +2371,8 @@ get "/api/v1/comments/:id" do |env|
|
||||||
end
|
end
|
||||||
|
|
||||||
get "/api/v1/insights/:id" do |env|
|
get "/api/v1/insights/:id" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
|
|
||||||
id = env.params.url["id"]
|
id = env.params.url["id"]
|
||||||
env.response.content_type = "application/json"
|
env.response.content_type = "application/json"
|
||||||
|
|
||||||
|
@ -2356,6 +2453,8 @@ get "/api/v1/insights/:id" do |env|
|
||||||
end
|
end
|
||||||
|
|
||||||
get "/api/v1/videos/:id" do |env|
|
get "/api/v1/videos/:id" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
|
|
||||||
env.response.content_type = "application/json"
|
env.response.content_type = "application/json"
|
||||||
|
|
||||||
id = env.params.url["id"]
|
id = env.params.url["id"]
|
||||||
|
@ -2388,7 +2487,7 @@ get "/api/v1/videos/:id" do |env|
|
||||||
json.field "description", description
|
json.field "description", description
|
||||||
json.field "descriptionHtml", video.description
|
json.field "descriptionHtml", video.description
|
||||||
json.field "published", video.published.to_unix
|
json.field "published", video.published.to_unix
|
||||||
json.field "publishedText", "#{recode_date(video.published)} ago"
|
json.field "publishedText", translate(locale, "`x` ago", recode_date(video.published))
|
||||||
json.field "keywords", video.keywords
|
json.field "keywords", video.keywords
|
||||||
|
|
||||||
json.field "viewCount", video.views
|
json.field "viewCount", video.views
|
||||||
|
@ -2559,11 +2658,13 @@ get "/api/v1/videos/:id" do |env|
|
||||||
end
|
end
|
||||||
|
|
||||||
get "/api/v1/trending" do |env|
|
get "/api/v1/trending" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
|
|
||||||
region = env.params.query["region"]?
|
region = env.params.query["region"]?
|
||||||
trending_type = env.params.query["type"]?
|
trending_type = env.params.query["type"]?
|
||||||
|
|
||||||
begin
|
begin
|
||||||
trending = fetch_trending(trending_type, proxies, region)
|
trending = fetch_trending(trending_type, proxies, region, locale)
|
||||||
rescue ex
|
rescue ex
|
||||||
error_message = {"error" => ex.message}.to_json
|
error_message = {"error" => ex.message}.to_json
|
||||||
halt env, status_code: 500, response: error_message
|
halt env, status_code: 500, response: error_message
|
||||||
|
@ -2587,7 +2688,7 @@ get "/api/v1/trending" do |env|
|
||||||
json.field "authorUrl", "/channel/#{video.ucid}"
|
json.field "authorUrl", "/channel/#{video.ucid}"
|
||||||
|
|
||||||
json.field "published", video.published.to_unix
|
json.field "published", video.published.to_unix
|
||||||
json.field "publishedText", "#{recode_date(video.published)} ago"
|
json.field "publishedText", translate(locale, "`x` ago", recode_date(video.published))
|
||||||
json.field "description", video.description
|
json.field "description", video.description
|
||||||
json.field "descriptionHtml", video.description_html
|
json.field "descriptionHtml", video.description_html
|
||||||
json.field "liveNow", video.live_now
|
json.field "liveNow", video.live_now
|
||||||
|
@ -2603,6 +2704,8 @@ get "/api/v1/trending" do |env|
|
||||||
end
|
end
|
||||||
|
|
||||||
get "/api/v1/popular" do |env|
|
get "/api/v1/popular" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
|
|
||||||
videos = JSON.build do |json|
|
videos = JSON.build do |json|
|
||||||
json.array do
|
json.array do
|
||||||
popular_videos.each do |video|
|
popular_videos.each do |video|
|
||||||
|
@ -2619,7 +2722,7 @@ get "/api/v1/popular" do |env|
|
||||||
json.field "authorId", video.ucid
|
json.field "authorId", video.ucid
|
||||||
json.field "authorUrl", "/channel/#{video.ucid}"
|
json.field "authorUrl", "/channel/#{video.ucid}"
|
||||||
json.field "published", video.published.to_unix
|
json.field "published", video.published.to_unix
|
||||||
json.field "publishedText", "#{recode_date(video.published)} ago"
|
json.field "publishedText", translate(locale, "`x` ago", recode_date(video.published))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -2630,6 +2733,8 @@ get "/api/v1/popular" do |env|
|
||||||
end
|
end
|
||||||
|
|
||||||
get "/api/v1/top" do |env|
|
get "/api/v1/top" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
|
|
||||||
videos = JSON.build do |json|
|
videos = JSON.build do |json|
|
||||||
json.array do
|
json.array do
|
||||||
top_videos.each do |video|
|
top_videos.each do |video|
|
||||||
|
@ -2647,7 +2752,7 @@ get "/api/v1/top" do |env|
|
||||||
json.field "authorId", video.ucid
|
json.field "authorId", video.ucid
|
||||||
json.field "authorUrl", "/channel/#{video.ucid}"
|
json.field "authorUrl", "/channel/#{video.ucid}"
|
||||||
json.field "published", video.published.to_unix
|
json.field "published", video.published.to_unix
|
||||||
json.field "publishedText", "#{recode_date(video.published)} ago"
|
json.field "publishedText", translate(locale, "`x` ago", recode_date(video.published))
|
||||||
|
|
||||||
description = video.description.gsub("<br>", "\n")
|
description = video.description.gsub("<br>", "\n")
|
||||||
description = description.gsub("<br/>", "\n")
|
description = description.gsub("<br/>", "\n")
|
||||||
|
@ -2664,6 +2769,8 @@ get "/api/v1/top" do |env|
|
||||||
end
|
end
|
||||||
|
|
||||||
get "/api/v1/channels/:ucid" do |env|
|
get "/api/v1/channels/:ucid" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
|
|
||||||
env.response.content_type = "application/json"
|
env.response.content_type = "application/json"
|
||||||
|
|
||||||
ucid = env.params.url["ucid"]
|
ucid = env.params.url["ucid"]
|
||||||
|
@ -2671,7 +2778,7 @@ get "/api/v1/channels/:ucid" do |env|
|
||||||
sort_by ||= "newest"
|
sort_by ||= "newest"
|
||||||
|
|
||||||
begin
|
begin
|
||||||
author, ucid, auto_generated = get_about_info(ucid)
|
author, ucid, auto_generated = get_about_info(ucid, locale)
|
||||||
rescue ex
|
rescue ex
|
||||||
error_message = {"error" => ex.message}.to_json
|
error_message = {"error" => ex.message}.to_json
|
||||||
halt env, status_code: 500, response: error_message
|
halt env, status_code: 500, response: error_message
|
||||||
|
@ -2817,7 +2924,7 @@ get "/api/v1/channels/:ucid" do |env|
|
||||||
|
|
||||||
json.field "viewCount", video.views
|
json.field "viewCount", video.views
|
||||||
json.field "published", video.published.to_unix
|
json.field "published", video.published.to_unix
|
||||||
json.field "publishedText", "#{recode_date(video.published)} ago"
|
json.field "publishedText", translate(locale, "`x` ago", recode_date(video.published))
|
||||||
json.field "lengthSeconds", video.length_seconds
|
json.field "lengthSeconds", video.length_seconds
|
||||||
json.field "liveNow", video.live_now
|
json.field "liveNow", video.live_now
|
||||||
json.field "paid", video.paid
|
json.field "paid", video.paid
|
||||||
|
@ -2860,6 +2967,8 @@ end
|
||||||
|
|
||||||
["/api/v1/channels/:ucid/videos", "/api/v1/channels/videos/:ucid"].each do |route|
|
["/api/v1/channels/:ucid/videos", "/api/v1/channels/videos/:ucid"].each do |route|
|
||||||
get route do |env|
|
get route do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
|
|
||||||
env.response.content_type = "application/json"
|
env.response.content_type = "application/json"
|
||||||
|
|
||||||
ucid = env.params.url["ucid"]
|
ucid = env.params.url["ucid"]
|
||||||
|
@ -2869,7 +2978,7 @@ end
|
||||||
sort_by ||= "newest"
|
sort_by ||= "newest"
|
||||||
|
|
||||||
begin
|
begin
|
||||||
author, ucid, auto_generated = get_about_info(ucid)
|
author, ucid, auto_generated = get_about_info(ucid, locale)
|
||||||
rescue ex
|
rescue ex
|
||||||
error_message = {"error" => ex.message}.to_json
|
error_message = {"error" => ex.message}.to_json
|
||||||
halt env, status_code: 500, response: error_message
|
halt env, status_code: 500, response: error_message
|
||||||
|
@ -2908,7 +3017,7 @@ end
|
||||||
|
|
||||||
json.field "viewCount", video.views
|
json.field "viewCount", video.views
|
||||||
json.field "published", video.published.to_unix
|
json.field "published", video.published.to_unix
|
||||||
json.field "publishedText", "#{recode_date(video.published)} ago"
|
json.field "publishedText", translate(locale, "`x` ago", recode_date(video.published))
|
||||||
json.field "lengthSeconds", video.length_seconds
|
json.field "lengthSeconds", video.length_seconds
|
||||||
json.field "liveNow", video.live_now
|
json.field "liveNow", video.live_now
|
||||||
json.field "paid", video.paid
|
json.field "paid", video.paid
|
||||||
|
@ -2923,6 +3032,8 @@ end
|
||||||
end
|
end
|
||||||
|
|
||||||
get "/api/v1/channels/search/:ucid" do |env|
|
get "/api/v1/channels/search/:ucid" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
|
|
||||||
env.response.content_type = "application/json"
|
env.response.content_type = "application/json"
|
||||||
|
|
||||||
ucid = env.params.url["ucid"]
|
ucid = env.params.url["ucid"]
|
||||||
|
@ -2957,7 +3068,7 @@ get "/api/v1/channels/search/:ucid" do |env|
|
||||||
|
|
||||||
json.field "viewCount", item.views
|
json.field "viewCount", item.views
|
||||||
json.field "published", item.published.to_unix
|
json.field "published", item.published.to_unix
|
||||||
json.field "publishedText", "#{recode_date(item.published)} ago"
|
json.field "publishedText", translate(locale, "`x` ago", recode_date(item.published))
|
||||||
json.field "lengthSeconds", item.length_seconds
|
json.field "lengthSeconds", item.length_seconds
|
||||||
json.field "liveNow", item.live_now
|
json.field "liveNow", item.live_now
|
||||||
json.field "paid", item.paid
|
json.field "paid", item.paid
|
||||||
|
@ -3021,6 +3132,8 @@ get "/api/v1/channels/search/:ucid" do |env|
|
||||||
end
|
end
|
||||||
|
|
||||||
get "/api/v1/search" do |env|
|
get "/api/v1/search" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
|
|
||||||
env.response.content_type = "application/json"
|
env.response.content_type = "application/json"
|
||||||
|
|
||||||
query = env.params.query["q"]?
|
query = env.params.query["q"]?
|
||||||
|
@ -3080,7 +3193,7 @@ get "/api/v1/search" do |env|
|
||||||
|
|
||||||
json.field "viewCount", item.views
|
json.field "viewCount", item.views
|
||||||
json.field "published", item.published.to_unix
|
json.field "published", item.published.to_unix
|
||||||
json.field "publishedText", "#{recode_date(item.published)} ago"
|
json.field "publishedText", translate(locale, "`x` ago", recode_date(item.published))
|
||||||
json.field "lengthSeconds", item.length_seconds
|
json.field "lengthSeconds", item.length_seconds
|
||||||
json.field "liveNow", item.live_now
|
json.field "liveNow", item.live_now
|
||||||
json.field "paid", item.paid
|
json.field "paid", item.paid
|
||||||
|
@ -3144,6 +3257,8 @@ get "/api/v1/search" do |env|
|
||||||
end
|
end
|
||||||
|
|
||||||
get "/api/v1/playlists/:plid" do |env|
|
get "/api/v1/playlists/:plid" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
|
|
||||||
env.response.content_type = "application/json"
|
env.response.content_type = "application/json"
|
||||||
plid = env.params.url["plid"]
|
plid = env.params.url["plid"]
|
||||||
|
|
||||||
|
@ -3160,14 +3275,14 @@ get "/api/v1/playlists/:plid" do |env|
|
||||||
end
|
end
|
||||||
|
|
||||||
begin
|
begin
|
||||||
playlist = fetch_playlist(plid)
|
playlist = fetch_playlist(plid, locale)
|
||||||
rescue ex
|
rescue ex
|
||||||
error_message = {"error" => "Playlist is empty"}.to_json
|
error_message = {"error" => "Playlist is empty"}.to_json
|
||||||
halt env, status_code: 500, response: error_message
|
halt env, status_code: 500, response: error_message
|
||||||
end
|
end
|
||||||
|
|
||||||
begin
|
begin
|
||||||
videos = fetch_playlist_videos(plid, page, playlist.video_count, continuation)
|
videos = fetch_playlist_videos(plid, page, playlist.video_count, continuation, locale)
|
||||||
rescue ex
|
rescue ex
|
||||||
videos = [] of PlaylistVideo
|
videos = [] of PlaylistVideo
|
||||||
end
|
end
|
||||||
|
@ -3241,6 +3356,8 @@ get "/api/v1/playlists/:plid" do |env|
|
||||||
end
|
end
|
||||||
|
|
||||||
get "/api/v1/mixes/:rdid" do |env|
|
get "/api/v1/mixes/:rdid" do |env|
|
||||||
|
locale = LOCALES[env.get("locale").as(String)]?
|
||||||
|
|
||||||
env.response.content_type = "application/json"
|
env.response.content_type = "application/json"
|
||||||
|
|
||||||
rdid = env.params.url["rdid"]
|
rdid = env.params.url["rdid"]
|
||||||
|
@ -3252,7 +3369,7 @@ get "/api/v1/mixes/:rdid" do |env|
|
||||||
format ||= "json"
|
format ||= "json"
|
||||||
|
|
||||||
begin
|
begin
|
||||||
mix = fetch_mix(rdid, continuation)
|
mix = fetch_mix(rdid, continuation, locale: locale)
|
||||||
|
|
||||||
if !rdid.ends_with? continuation
|
if !rdid.ends_with? continuation
|
||||||
mix = fetch_mix(rdid, mix.videos[1].id)
|
mix = fetch_mix(rdid, mix.videos[1].id)
|
||||||
|
|
|
@ -28,7 +28,7 @@ def get_channel(id, db, refresh = true, pull_all_videos = true)
|
||||||
channel = db.query_one("SELECT * FROM channels WHERE id = $1", id, as: InvidiousChannel)
|
channel = db.query_one("SELECT * FROM channels WHERE id = $1", id, as: InvidiousChannel)
|
||||||
|
|
||||||
if refresh && Time.now - channel.updated > 10.minutes
|
if refresh && Time.now - channel.updated > 10.minutes
|
||||||
channel = fetch_channel(id, client, db, pull_all_videos)
|
channel = fetch_channel(id, client, db, pull_all_videos: pull_all_videos)
|
||||||
channel_array = channel.to_a
|
channel_array = channel.to_a
|
||||||
args = arg_array(channel_array)
|
args = arg_array(channel_array)
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ def get_channel(id, db, refresh = true, pull_all_videos = true)
|
||||||
ON CONFLICT (id) DO UPDATE SET author = $2, updated = $3", channel_array)
|
ON CONFLICT (id) DO UPDATE SET author = $2, updated = $3", channel_array)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
channel = fetch_channel(id, client, db, pull_all_videos)
|
channel = fetch_channel(id, client, db, pull_all_videos: pull_all_videos)
|
||||||
channel_array = channel.to_a
|
channel_array = channel.to_a
|
||||||
args = arg_array(channel_array)
|
args = arg_array(channel_array)
|
||||||
|
|
||||||
|
@ -46,13 +46,13 @@ def get_channel(id, db, refresh = true, pull_all_videos = true)
|
||||||
return channel
|
return channel
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_channel(ucid, client, db, pull_all_videos = true)
|
def fetch_channel(ucid, client, db, pull_all_videos = true, locale = nil)
|
||||||
rss = client.get("/feeds/videos.xml?channel_id=#{ucid}").body
|
rss = client.get("/feeds/videos.xml?channel_id=#{ucid}").body
|
||||||
rss = XML.parse_html(rss)
|
rss = XML.parse_html(rss)
|
||||||
|
|
||||||
author = rss.xpath_node(%q(//feed/title))
|
author = rss.xpath_node(%q(//feed/title))
|
||||||
if !author
|
if !author
|
||||||
raise "Deleted or invalid channel"
|
raise translate(locale, "Deleted or invalid channel")
|
||||||
end
|
end
|
||||||
author = author.content
|
author = author.content
|
||||||
|
|
||||||
|
@ -223,7 +223,7 @@ def produce_channel_videos_url(ucid, page = 1, auto_generated = nil, sort_by = "
|
||||||
return url
|
return url
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_about_info(ucid)
|
def get_about_info(ucid, locale)
|
||||||
client = make_client(YT_URL)
|
client = make_client(YT_URL)
|
||||||
|
|
||||||
about = client.get("/channel/#{ucid}/about?disable_polymer=1&gl=US&hl=en")
|
about = client.get("/channel/#{ucid}/about?disable_polymer=1&gl=US&hl=en")
|
||||||
|
@ -234,14 +234,14 @@ def get_about_info(ucid)
|
||||||
about = XML.parse_html(about.body)
|
about = XML.parse_html(about.body)
|
||||||
|
|
||||||
if about.xpath_node(%q(//div[contains(@class, "channel-empty-message")]))
|
if about.xpath_node(%q(//div[contains(@class, "channel-empty-message")]))
|
||||||
error_message = "This channel does not exist."
|
error_message = translate(locale, "This channel does not exist.")
|
||||||
|
|
||||||
raise error_message
|
raise error_message
|
||||||
end
|
end
|
||||||
|
|
||||||
if about.xpath_node(%q(//span[contains(@class,"qualified-channel-title-text")]/a)).try &.content.empty?
|
if about.xpath_node(%q(//span[contains(@class,"qualified-channel-title-text")]/a)).try &.content.empty?
|
||||||
error_message = about.xpath_node(%q(//div[@class="yt-alert-content"])).try &.content.strip
|
error_message = about.xpath_node(%q(//div[@class="yt-alert-content"])).try &.content.strip
|
||||||
error_message ||= "Could not get channel info."
|
error_message ||= translate(locale, "Could not get channel info.")
|
||||||
|
|
||||||
raise error_message
|
raise error_message
|
||||||
end
|
end
|
||||||
|
|
|
@ -56,7 +56,7 @@ class RedditListing
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_youtube_comments(id, continuation, proxies, format)
|
def fetch_youtube_comments(id, continuation, proxies, format, locale)
|
||||||
client = make_client(YT_URL)
|
client = make_client(YT_URL)
|
||||||
html = client.get("/watch?v=#{id}&gl=US&hl=en&disable_polymer=1&has_verified=1&bpctr=9999999999")
|
html = client.get("/watch?v=#{id}&gl=US&hl=en&disable_polymer=1&has_verified=1&bpctr=9999999999")
|
||||||
headers = HTTP::Headers.new
|
headers = HTTP::Headers.new
|
||||||
|
@ -133,7 +133,7 @@ def fetch_youtube_comments(id, continuation, proxies, format)
|
||||||
response = JSON.parse(response.body)
|
response = JSON.parse(response.body)
|
||||||
|
|
||||||
if !response["response"]["continuationContents"]?
|
if !response["response"]["continuationContents"]?
|
||||||
raise "Could not fetch comments"
|
raise translate(locale, "Could not fetch comments")
|
||||||
end
|
end
|
||||||
|
|
||||||
response = response["response"]["continuationContents"]
|
response = response["response"]["continuationContents"]
|
||||||
|
@ -214,7 +214,7 @@ def fetch_youtube_comments(id, continuation, proxies, format)
|
||||||
json.field "content", content
|
json.field "content", content
|
||||||
json.field "contentHtml", content_html
|
json.field "contentHtml", content_html
|
||||||
json.field "published", published.to_unix
|
json.field "published", published.to_unix
|
||||||
json.field "publishedText", "#{recode_date(published)} ago"
|
json.field "publishedText", translate(locale, "`x` ago", recode_date(published))
|
||||||
json.field "likeCount", node_comment["likeCount"]
|
json.field "likeCount", node_comment["likeCount"]
|
||||||
json.field "commentId", node_comment["commentId"]
|
json.field "commentId", node_comment["commentId"]
|
||||||
|
|
||||||
|
@ -250,7 +250,7 @@ def fetch_youtube_comments(id, continuation, proxies, format)
|
||||||
|
|
||||||
if format == "html"
|
if format == "html"
|
||||||
comments = JSON.parse(comments)
|
comments = JSON.parse(comments)
|
||||||
content_html = template_youtube_comments(comments)
|
content_html = template_youtube_comments(comments, locale)
|
||||||
|
|
||||||
comments = JSON.build do |json|
|
comments = JSON.build do |json|
|
||||||
json.object do
|
json.object do
|
||||||
|
@ -296,7 +296,7 @@ def fetch_reddit_comments(id)
|
||||||
return comments, thread
|
return comments, thread
|
||||||
end
|
end
|
||||||
|
|
||||||
def template_youtube_comments(comments)
|
def template_youtube_comments(comments, locale)
|
||||||
html = ""
|
html = ""
|
||||||
|
|
||||||
root = comments["comments"].as_a
|
root = comments["comments"].as_a
|
||||||
|
@ -308,7 +308,7 @@ def template_youtube_comments(comments)
|
||||||
<div class="pure-u-23-24">
|
<div class="pure-u-23-24">
|
||||||
<p>
|
<p>
|
||||||
<a href="javascript:void(0)" data-continuation="#{child["replies"]["continuation"]}"
|
<a href="javascript:void(0)" data-continuation="#{child["replies"]["continuation"]}"
|
||||||
onclick="get_youtube_replies(this)">View #{child["replies"]["replyCount"]} replies</a>
|
onclick="get_youtube_replies(this)">#{translate(locale, "View `x` replies", child["replies"]["replyCount"].to_s)}</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -328,7 +328,7 @@ def template_youtube_comments(comments)
|
||||||
<a href="#{child["authorUrl"]}">#{child["author"]}</a>
|
<a href="#{child["authorUrl"]}">#{child["author"]}</a>
|
||||||
</b>
|
</b>
|
||||||
<p style="white-space:pre-wrap">#{child["contentHtml"]}</p>
|
<p style="white-space:pre-wrap">#{child["contentHtml"]}</p>
|
||||||
#{recode_date(Time.unix(child["published"].as_i64))} ago
|
#{translate(locale, "`x` ago", recode_date(Time.unix(child["published"].as_i64)))}
|
||||||
|
|
|
|
||||||
<i class="icon ion-ios-thumbs-up"></i> #{number_with_separator(child["likeCount"])}
|
<i class="icon ion-ios-thumbs-up"></i> #{number_with_separator(child["likeCount"])}
|
||||||
</p>
|
</p>
|
||||||
|
@ -344,7 +344,7 @@ def template_youtube_comments(comments)
|
||||||
<div class="pure-u-1">
|
<div class="pure-u-1">
|
||||||
<p>
|
<p>
|
||||||
<a href="javascript:void(0)" data-continuation="#{comments["continuation"]}"
|
<a href="javascript:void(0)" data-continuation="#{comments["continuation"]}"
|
||||||
onclick="get_youtube_replies(this, true)">Load more</a>
|
onclick="get_youtube_replies(this, true)">#{translate(locale, "Load more")}</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -354,7 +354,7 @@ def template_youtube_comments(comments)
|
||||||
return html
|
return html
|
||||||
end
|
end
|
||||||
|
|
||||||
def template_reddit_comments(root)
|
def template_reddit_comments(root, locale)
|
||||||
html = ""
|
html = ""
|
||||||
root.each do |child|
|
root.each do |child|
|
||||||
if child.data.is_a?(RedditComment)
|
if child.data.is_a?(RedditComment)
|
||||||
|
@ -366,15 +366,15 @@ def template_reddit_comments(root)
|
||||||
replies_html = ""
|
replies_html = ""
|
||||||
if child.replies.is_a?(RedditThing)
|
if child.replies.is_a?(RedditThing)
|
||||||
replies = child.replies.as(RedditThing)
|
replies = child.replies.as(RedditThing)
|
||||||
replies_html = template_reddit_comments(replies.data.as(RedditListing).children)
|
replies_html = template_reddit_comments(replies.data.as(RedditListing).children, locale)
|
||||||
end
|
end
|
||||||
|
|
||||||
content = <<-END_HTML
|
content = <<-END_HTML
|
||||||
<p>
|
<p>
|
||||||
<a href="javascript:void(0)" onclick="toggle_parent(this)">[ - ]</a>
|
<a href="javascript:void(0)" onclick="toggle_parent(this)">[ - ]</a>
|
||||||
<b><a href="https://www.reddit.com/user/#{author}">#{author}</a></b>
|
<b><a href="https://www.reddit.com/user/#{author}">#{author}</a></b>
|
||||||
#{number_with_separator(score)} points
|
#{translate(locale, "`x` points", number_with_separator(score))}
|
||||||
#{recode_date(child.created_utc)} ago
|
#{translate(locale, "`x` ago", recode_date(child.created_utc))}
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<div>
|
||||||
#{body_html}
|
#{body_html}
|
||||||
|
|
23
src/invidious/helpers/i18n.cr
Normal file
23
src/invidious/helpers/i18n.cr
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
def load_locale(name)
|
||||||
|
return JSON.parse(File.read("locales/#{name}.json")).as_h
|
||||||
|
end
|
||||||
|
|
||||||
|
def translate(locale : Hash(String, JSON::Any) | Nil, translation : String, text : String | Nil = nil)
|
||||||
|
if !locale
|
||||||
|
return translation
|
||||||
|
end
|
||||||
|
|
||||||
|
# if !locale[translation]?
|
||||||
|
# puts "Could not find translation for #{translation}"
|
||||||
|
# end
|
||||||
|
|
||||||
|
if locale[translation]? && !locale[translation].as_s.empty?
|
||||||
|
translation = locale[translation].as_s
|
||||||
|
end
|
||||||
|
|
||||||
|
if text
|
||||||
|
translation = translation.gsub("`x`", text)
|
||||||
|
end
|
||||||
|
|
||||||
|
return translation
|
||||||
|
end
|
|
@ -18,7 +18,7 @@ class Mix
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_mix(rdid, video_id, cookies = nil)
|
def fetch_mix(rdid, video_id, cookies = nil, locale = nil)
|
||||||
client = make_client(YT_URL)
|
client = make_client(YT_URL)
|
||||||
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/69.0.3497.100 Safari/537.36"
|
||||||
|
@ -32,11 +32,11 @@ def fetch_mix(rdid, video_id, cookies = nil)
|
||||||
if yt_data
|
if yt_data
|
||||||
yt_data = JSON.parse(yt_data["data"].rchop(";"))
|
yt_data = JSON.parse(yt_data["data"].rchop(";"))
|
||||||
else
|
else
|
||||||
raise "Could not create mix."
|
raise translate(locale, "Could not create mix.")
|
||||||
end
|
end
|
||||||
|
|
||||||
if !yt_data["contents"]["twoColumnWatchNextResults"]["playlist"]?
|
if !yt_data["contents"]["twoColumnWatchNextResults"]["playlist"]?
|
||||||
raise "Could not create mix."
|
raise translate(locale, "Could not create mix.")
|
||||||
end
|
end
|
||||||
|
|
||||||
playlist = yt_data["contents"]["twoColumnWatchNextResults"]["playlist"]["playlist"]
|
playlist = yt_data["contents"]["twoColumnWatchNextResults"]["playlist"]["playlist"]
|
||||||
|
@ -70,7 +70,7 @@ def fetch_mix(rdid, video_id, cookies = nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
if !cookies
|
if !cookies
|
||||||
next_page = fetch_mix(rdid, videos[-1].id, response.cookies)
|
next_page = fetch_mix(rdid, videos[-1].id, response.cookies, locale)
|
||||||
videos += next_page.videos
|
videos += next_page.videos
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ class Playlist
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_playlist_videos(plid, page, video_count, continuation = nil)
|
def fetch_playlist_videos(plid, page, video_count, continuation = nil, locale = nil)
|
||||||
client = make_client(YT_URL)
|
client = make_client(YT_URL)
|
||||||
|
|
||||||
if continuation
|
if continuation
|
||||||
|
@ -48,7 +48,7 @@ def fetch_playlist_videos(plid, page, video_count, continuation = nil)
|
||||||
response = client.get(url)
|
response = client.get(url)
|
||||||
response = JSON.parse(response.body)
|
response = JSON.parse(response.body)
|
||||||
if !response["content_html"]? || response["content_html"].as_s.empty?
|
if !response["content_html"]? || response["content_html"].as_s.empty?
|
||||||
raise "Playlist is empty"
|
raise translate(locale, "Playlist is empty")
|
||||||
end
|
end
|
||||||
|
|
||||||
document = XML.parse_html(response["content_html"].as_s)
|
document = XML.parse_html(response["content_html"].as_s)
|
||||||
|
@ -105,14 +105,14 @@ def extract_playlist(plid, nodeset, index)
|
||||||
end
|
end
|
||||||
|
|
||||||
videos << PlaylistVideo.new(
|
videos << PlaylistVideo.new(
|
||||||
title,
|
title: title,
|
||||||
id,
|
id: id,
|
||||||
author,
|
author: author,
|
||||||
ucid,
|
ucid: ucid,
|
||||||
length_seconds,
|
length_seconds: length_seconds,
|
||||||
Time.now,
|
published: Time.now,
|
||||||
[plid],
|
playlists: [plid],
|
||||||
index + offset,
|
index: index + offset,
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -155,7 +155,7 @@ def produce_playlist_url(id, index)
|
||||||
return url
|
return url
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_playlist(plid)
|
def fetch_playlist(plid, locale)
|
||||||
client = make_client(YT_URL)
|
client = make_client(YT_URL)
|
||||||
|
|
||||||
if plid.starts_with? "UC"
|
if plid.starts_with? "UC"
|
||||||
|
@ -164,7 +164,7 @@ def fetch_playlist(plid)
|
||||||
|
|
||||||
response = client.get("/playlist?list=#{plid}&hl=en&disable_polymer=1")
|
response = client.get("/playlist?list=#{plid}&hl=en&disable_polymer=1")
|
||||||
if response.status_code != 200
|
if response.status_code != 200
|
||||||
raise "Invalid playlist."
|
raise translate(locale, "Invalid playlist.")
|
||||||
end
|
end
|
||||||
|
|
||||||
body = response.body.gsub(%(
|
body = response.body.gsub(%(
|
||||||
|
@ -175,7 +175,7 @@ def fetch_playlist(plid)
|
||||||
|
|
||||||
title = document.xpath_node(%q(//h1[@class="pl-header-title"]))
|
title = document.xpath_node(%q(//h1[@class="pl-header-title"]))
|
||||||
if !title
|
if !title
|
||||||
raise "Playlist does not exist."
|
raise translate(locale, "Playlist does not exist.")
|
||||||
end
|
end
|
||||||
title = title.content.strip(" \n")
|
title = title.content.strip(" \n")
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
def fetch_trending(trending_type, proxies, region)
|
def fetch_trending(trending_type, proxies, region, locale)
|
||||||
client = make_client(YT_URL)
|
client = make_client(YT_URL)
|
||||||
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/68.0.3440.106 Safari/537.36"
|
||||||
|
@ -16,7 +16,7 @@ def fetch_trending(trending_type, proxies, region)
|
||||||
if yt_data
|
if yt_data
|
||||||
yt_data = JSON.parse(yt_data["data"].rchop(";"))
|
yt_data = JSON.parse(yt_data["data"].rchop(";"))
|
||||||
else
|
else
|
||||||
raise "Could not pull trending pages."
|
raise translate(locale, "Could not pull trending pages.")
|
||||||
end
|
end
|
||||||
|
|
||||||
tabs = yt_data["contents"]["twoColumnBrowseResultsRenderer"]["tabs"][0]["tabRenderer"]["content"]["sectionListRenderer"]["subMenu"]["channelListSubMenuRenderer"]["contents"].as_a
|
tabs = yt_data["contents"]["twoColumnBrowseResultsRenderer"]["tabs"][0]["tabRenderer"]["content"]["sectionListRenderer"]["subMenu"]["channelListSubMenuRenderer"]["contents"].as_a
|
||||||
|
|
|
@ -31,18 +31,23 @@ end
|
||||||
DEFAULT_USER_PREFERENCES = Preferences.from_json({
|
DEFAULT_USER_PREFERENCES = Preferences.from_json({
|
||||||
"video_loop" => false,
|
"video_loop" => false,
|
||||||
"autoplay" => false,
|
"autoplay" => false,
|
||||||
|
"continue" => false,
|
||||||
|
"listen" => false,
|
||||||
"speed" => 1.0,
|
"speed" => 1.0,
|
||||||
"quality" => "hd720",
|
"quality" => "hd720",
|
||||||
"volume" => 100,
|
"volume" => 100,
|
||||||
"comments" => ["youtube", ""],
|
"comments" => ["youtube", ""],
|
||||||
"captions" => ["", "", ""],
|
"captions" => ["", "", ""],
|
||||||
"related_videos" => true,
|
"related_videos" => true,
|
||||||
|
"redirect_feed" => false,
|
||||||
|
"locale" => "en-US",
|
||||||
"dark_mode" => false,
|
"dark_mode" => false,
|
||||||
"thin_mode" => false,
|
"thin_mode" => false,
|
||||||
"max_results" => 40,
|
"max_results" => 40,
|
||||||
"sort" => "published",
|
"sort" => "published",
|
||||||
"latest_only" => false,
|
"latest_only" => false,
|
||||||
"unseen_only" => false,
|
"unseen_only" => false,
|
||||||
|
"notifications_only" => false,
|
||||||
}.to_json)
|
}.to_json)
|
||||||
|
|
||||||
class Preferences
|
class Preferences
|
||||||
|
@ -113,6 +118,10 @@ class Preferences
|
||||||
type: Bool,
|
type: Bool,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
locale: {
|
||||||
|
type: String,
|
||||||
|
default: "en-US",
|
||||||
|
},
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -217,13 +226,13 @@ def create_response(user_id, operation, key, db, expire = 6.hours)
|
||||||
return challenge, token
|
return challenge, token
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_response(challenge, token, user_id, operation, key, db)
|
def validate_response(challenge, token, user_id, operation, key, db, locale)
|
||||||
if !challenge
|
if !challenge
|
||||||
raise "Hidden field \"challenge\" is a required field"
|
raise translate(locale, "Hidden field \"challenge\" is a required field")
|
||||||
end
|
end
|
||||||
|
|
||||||
if !token
|
if !token
|
||||||
raise "Hidden field \"token\" is a required field"
|
raise translate(locale, "Hidden field \"token\" is a required field")
|
||||||
end
|
end
|
||||||
|
|
||||||
challenge = Base64.decode_string(challenge)
|
challenge = Base64.decode_string(challenge)
|
||||||
|
@ -233,7 +242,7 @@ def validate_response(challenge, token, user_id, operation, key, db)
|
||||||
expire = expire.to_i?
|
expire = expire.to_i?
|
||||||
expire ||= 0
|
expire ||= 0
|
||||||
else
|
else
|
||||||
raise "Invalid challenge"
|
raise translate(locale, "Invalid challenge")
|
||||||
end
|
end
|
||||||
|
|
||||||
challenge = OpenSSL::HMAC.digest(:sha256, HMAC_KEY, challenge)
|
challenge = OpenSSL::HMAC.digest(:sha256, HMAC_KEY, challenge)
|
||||||
|
@ -242,23 +251,23 @@ def validate_response(challenge, token, user_id, operation, key, db)
|
||||||
if db.query_one?("SELECT EXISTS (SELECT true FROM nonces WHERE nonce = $1)", nonce, as: Bool)
|
if db.query_one?("SELECT EXISTS (SELECT true FROM nonces WHERE nonce = $1)", nonce, as: Bool)
|
||||||
db.exec("DELETE FROM nonces * WHERE nonce = $1", nonce)
|
db.exec("DELETE FROM nonces * WHERE nonce = $1", nonce)
|
||||||
else
|
else
|
||||||
raise "Invalid token"
|
raise translate(locale, "Invalid token")
|
||||||
end
|
end
|
||||||
|
|
||||||
if challenge != token
|
if challenge != token
|
||||||
raise "Invalid token"
|
raise translate(locale, "Invalid token")
|
||||||
end
|
end
|
||||||
|
|
||||||
if challenge_operation != operation
|
if challenge_operation != operation
|
||||||
raise "Invalid token"
|
raise translate(locale, "Invalid token")
|
||||||
end
|
end
|
||||||
|
|
||||||
if challenge_user_id != user_id
|
if challenge_user_id != user_id
|
||||||
raise "Invalid user"
|
raise translate(locale, "Invalid user")
|
||||||
end
|
end
|
||||||
|
|
||||||
if expire < Time.now.to_unix
|
if expire < Time.now.to_unix
|
||||||
raise "Token is expired, please try again"
|
raise translate(locale, "Token is expired, please try again")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -19,14 +19,14 @@
|
||||||
<p>
|
<p>
|
||||||
<a id="subscribe" onclick="unsubscribe()" class="pure-button pure-button-primary"
|
<a id="subscribe" onclick="unsubscribe()" class="pure-button pure-button-primary"
|
||||||
href="/subscription_ajax?action_remove_subscriptions=1&c=<%= ucid %>&referer=<%= env.get("current_page") %>">
|
href="/subscription_ajax?action_remove_subscriptions=1&c=<%= ucid %>&referer=<%= env.get("current_page") %>">
|
||||||
<b>Unsubscribe | <%= number_to_short_text(sub_count) %></b>
|
<b><%= translate(locale, "Unsubscribe") %> | <%= number_to_short_text(sub_count) %></b>
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<% else %>
|
<% else %>
|
||||||
<p>
|
<p>
|
||||||
<a id="subscribe" onclick="subscribe()" class="pure-button pure-button-primary"
|
<a id="subscribe" onclick="subscribe()" class="pure-button pure-button-primary"
|
||||||
href="/subscription_ajax?action_create_subscription_to_channel=1&c=<%= ucid %>&referer=<%= env.get("current_page") %>">
|
href="/subscription_ajax?action_create_subscription_to_channel=1&c=<%= ucid %>&referer=<%= env.get("current_page") %>">
|
||||||
<b>Subscribe | <%= number_to_short_text(sub_count) %></b>
|
<b><%= translate(locale, "Subscribe") %> | <%= number_to_short_text(sub_count) %></b>
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
@ -34,7 +34,7 @@
|
||||||
<p>
|
<p>
|
||||||
<a id="subscribe" class="pure-button pure-button-primary"
|
<a id="subscribe" class="pure-button pure-button-primary"
|
||||||
href="/login?referer=<%= env.get("current_page") %>">
|
href="/login?referer=<%= env.get("current_page") %>">
|
||||||
<b>Login to subscribe to <%= author %></b>
|
<b><%= translate(locale, "Login to subscribe to `x`", author) %></b>
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
@ -42,7 +42,7 @@
|
||||||
|
|
||||||
<div class="pure-g h-box">
|
<div class="pure-g h-box">
|
||||||
<div class="pure-u-1-3">
|
<div class="pure-u-1-3">
|
||||||
<a href="https://www.youtube.com/channel/<%= ucid %>">View channel on YouTube</a>
|
<a href="https://www.youtube.com/channel/<%= ucid %>"><%= translate(locale, "View channel on YouTube") %></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="pure-u-1-3">
|
<div class="pure-u-1-3">
|
||||||
</div>
|
</div>
|
||||||
|
@ -51,10 +51,10 @@
|
||||||
<% {"newest", "oldest", "popular"}.each do |sort| %>
|
<% {"newest", "oldest", "popular"}.each do |sort| %>
|
||||||
<div class="pure-u-1 pure-md-1-3">
|
<div class="pure-u-1 pure-md-1-3">
|
||||||
<% if sort_by == sort %>
|
<% if sort_by == sort %>
|
||||||
<b><%= sort %></b>
|
<b><%= translate(locale, sort) %></b>
|
||||||
<% else %>
|
<% else %>
|
||||||
<a href="/channel/<%= ucid %>?page=<%= page %>&sort_by=<%= sort %>">
|
<a href="/channel/<%= ucid %>?page=<%= page %>&sort_by=<%= sort %>">
|
||||||
<%= sort %>
|
<%= translate(locale, sort) %>
|
||||||
</a>
|
</a>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
@ -78,13 +78,17 @@
|
||||||
<div class="pure-g h-box">
|
<div class="pure-g h-box">
|
||||||
<div class="pure-u-1 pure-u-md-1-5">
|
<div class="pure-u-1 pure-u-md-1-5">
|
||||||
<% if page >= 2 %>
|
<% if page >= 2 %>
|
||||||
<a href="/channel/<%= ucid %>?page=<%= page - 1 %><% if sort_by != "newest" %>&sort_by=<%= sort_by %><% end %>">Previous page</a>
|
<a href="/channel/<%= ucid %>?page=<%= page - 1 %><% if sort_by != "newest" %>&sort_by=<%= sort_by %><% end %>">
|
||||||
|
<%= translate(locale, "Previous page") %>
|
||||||
|
</a>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<div class="pure-u-1 pure-u-md-3-5"></div>
|
<div class="pure-u-1 pure-u-md-3-5"></div>
|
||||||
<div style="text-align:right;" class="pure-u-1 pure-u-md-1-5">
|
<div style="text-align:right;" class="pure-u-1 pure-u-md-1-5">
|
||||||
<% if count == 60 %>
|
<% if count == 60 %>
|
||||||
<a href="/channel/<%= ucid %>?page=<%= page + 1 %><% if sort_by != "newest" %>&sort_by=<%= sort_by %><% end %>">Next page</a>
|
<a href="/channel/<%= ucid %>?page=<%= page + 1 %><% if sort_by != "newest" %>&sort_by=<%= sort_by %><% end %>">
|
||||||
|
<%= translate(locale, "Next page") %>
|
||||||
|
</a>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -105,7 +109,7 @@ function subscribe() {
|
||||||
if (xhr.status == 200) {
|
if (xhr.status == 200) {
|
||||||
subscribe_button = document.getElementById("subscribe");
|
subscribe_button = document.getElementById("subscribe");
|
||||||
subscribe_button.onclick = unsubscribe;
|
subscribe_button.onclick = unsubscribe;
|
||||||
subscribe_button.innerHTML = '<b>Unsubscribe | <%= number_to_short_text(sub_count) %></b>'
|
subscribe_button.innerHTML = '<b><%= translate(locale, "Unsubscribe") %> | <%= number_to_short_text(sub_count) %></b>'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -124,7 +128,7 @@ function unsubscribe() {
|
||||||
if (xhr.status == 200) {
|
if (xhr.status == 200) {
|
||||||
subscribe_button = document.getElementById("subscribe");
|
subscribe_button = document.getElementById("subscribe");
|
||||||
subscribe_button.onclick = subscribe;
|
subscribe_button.onclick = subscribe;
|
||||||
subscribe_button.innerHTML = '<b>Subscribe | <%= number_to_short_text(sub_count) %></b>'
|
subscribe_button.innerHTML = '<b><%= translate(locale, "Subscribe") %> | <%= number_to_short_text(sub_count) %></b>'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,21 @@
|
||||||
|
<% content_for "header" do %>
|
||||||
|
<title><%= translate(locale, "Clear watch history") %> - Invidious</title>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<div class="h-box">
|
<div class="h-box">
|
||||||
<form class="pure-form pure-form-aligned" action="/clear_watch_history?referer=<%= URI.escape(referer) %>" method="post">
|
<form class="pure-form pure-form-aligned" action="/clear_watch_history?referer=<%= URI.escape(referer) %>" method="post">
|
||||||
<legend>Clear watch history?</legend>
|
<legend><%= translate(locale, "Clear watch history?") %></legend>
|
||||||
|
|
||||||
<div class="pure-g">
|
<div class="pure-g">
|
||||||
<div class="pure-u-1-2">
|
<div class="pure-u-1-2">
|
||||||
<button type="submit" name="submit" value="clear_watch_history" class="pure-button pure-button-primary">Yes</button>
|
<button type="submit" name="submit" value="clear_watch_history" class="pure-button pure-button-primary">
|
||||||
|
<%= translate(locale, "Yes") %>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="pure-u-1-2">
|
<div class="pure-u-1-2">
|
||||||
<a class="pure-button" href="<%= referer %>">No</a>
|
<a class="pure-button" href="<%= referer %>">
|
||||||
|
<%= translate(locale, "No") %>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -11,8 +11,8 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
<p><%= item.author %></p>
|
<p><%= item.author %></p>
|
||||||
</a>
|
</a>
|
||||||
<p><%= number_with_separator(item.subscriber_count) %> subscribers</p>
|
<p><%= translate(locale, "`x` subscribers", number_with_separator(item.subscriber_count)) %></p>
|
||||||
<p><%= number_with_separator(item.video_count) %> videos</p>
|
<p><%= translate(locale, "`x` videos", number_with_separator(item.video_count)) %></p>
|
||||||
<h5><%= item.description_html %></h5>
|
<h5><%= item.description_html %></h5>
|
||||||
<% when SearchPlaylist %>
|
<% when SearchPlaylist %>
|
||||||
<% if item.id.starts_with? "RD" %>
|
<% if item.id.starts_with? "RD" %>
|
||||||
|
@ -59,14 +59,14 @@
|
||||||
<p><%= item.title %></p>
|
<p><%= item.title %></p>
|
||||||
</a>
|
</a>
|
||||||
<% if item.responds_to?(:live_now) && item.live_now %>
|
<% if item.responds_to?(:live_now) && item.live_now %>
|
||||||
<p>LIVE</p>
|
<p><%= translate(locale, "LIVE") %></p>
|
||||||
<% end %>
|
<% end %>
|
||||||
<p>
|
<p>
|
||||||
<b><a style="width:100%;" href="/channel/<%= item.ucid %>"><%= item.author %></a></b>
|
<b><a style="width:100%;" href="/channel/<%= item.ucid %>"><%= item.author %></a></b>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<% if Time.now - item.published > 1.minute %>
|
<% if Time.now - item.published > 1.minute %>
|
||||||
<h5>Shared <%= recode_date(item.published) %> ago</h5>
|
<h5><%= translate(locale, "Shared `x` ago", recode_date(item.published)) %></h5>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<% if env.get?("user") && env.get("user").as(User).preferences.thin_mode %>
|
<% if env.get?("user") && env.get("user").as(User).preferences.thin_mode %>
|
||||||
|
@ -93,14 +93,14 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
<p><a href="/watch?v=<%= item.id %>"><%= item.title %></a></p>
|
<p><a href="/watch?v=<%= item.id %>"><%= item.title %></a></p>
|
||||||
<% if item.responds_to?(:live_now) && item.live_now %>
|
<% if item.responds_to?(:live_now) && item.live_now %>
|
||||||
<p>LIVE</p>
|
<p><%= translate(locale, "LIVE") %></p>
|
||||||
<% end %>
|
<% end %>
|
||||||
<p>
|
<p>
|
||||||
<b><a style="width:100%;" href="/channel/<%= item.ucid %>"><%= item.author %></a></b>
|
<b><a style="width:100%;" href="/channel/<%= item.ucid %>"><%= item.author %></a></b>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<% if Time.now - item.published > 1.minute %>
|
<% if Time.now - item.published > 1.minute %>
|
||||||
<h5>Shared <%= recode_date(item.published) %> ago</h5>
|
<h5><%= translate(locale, "Shared `x` ago", recode_date(item.published)) %></h5>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -27,12 +27,12 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<% preferred_captions.each_with_index do |caption, i| %>
|
<% preferred_captions.each_with_index do |caption, i| %>
|
||||||
<track kind="captions" src="/api/v1/captions/<%= video.id %>?label=<%= caption.name.simpleText %>"
|
<track kind="captions" src="/api/v1/captions/<%= video.id %>?label=<%= caption.name.simpleText %>&hl=<%= env.get("locale").as(String) %>"
|
||||||
label="<%= caption.name.simpleText %>" <% if i == 0 %>default<% end %>>
|
label="<%= caption.name.simpleText %>" <% if i == 0 %>default<% end %>>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<% captions.each do |caption| %>
|
<% captions.each do |caption| %>
|
||||||
<track kind="captions" src="/api/v1/captions/<%= video.id %>?label=<%= caption.name.simpleText %>"
|
<track kind="captions" src="/api/v1/captions/<%= video.id %>?label=<%= caption.name.simpleText %>&hl=<%= env.get("locale").as(String) %>"
|
||||||
label="<%= caption.name.simpleText %>">
|
label="<%= caption.name.simpleText %>">
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -1,54 +1,57 @@
|
||||||
<% content_for "header" do %>
|
<% content_for "header" do %>
|
||||||
<title>Import and Export Data - Invidious</title>
|
<title><%= translate(locale, "Import and Export Data") %> - Invidious</title>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="h-box">
|
<div class="h-box">
|
||||||
<form class="pure-form pure-form-aligned" enctype="multipart/form-data" action="/data_control?referer=<%= referer %>" method="post">
|
<form class="pure-form pure-form-aligned" enctype="multipart/form-data" action="/data_control?referer=<%= referer %>" method="post">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Import</legend>
|
<legend><%= translate(locale, "Import") %></legend>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
<label for="import_youtube">Import Invidious data</label>
|
<label for="import_youtube"><%= translate(locale, "Import Invidious data") %></label>
|
||||||
<input type="file" id="import_invidious" name="import_invidious">
|
<input type="file" id="import_invidious" name="import_invidious">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
<label for="import_youtube">Import <a rel="noopener" target="_blank"
|
<label for="import_youtube">
|
||||||
href="https://support.google.com/youtube/answer/6224202?hl=en-GB">YouTube subscriptions</a></label>
|
<a rel="noopener" target="_blank" href="https://support.google.com/youtube/answer/6224202?hl=en">
|
||||||
|
<%= translate(locale, "Import YouTube subscriptions") %>
|
||||||
|
</a>
|
||||||
|
</label>
|
||||||
<input type="file" id="import_youtube" name="import_youtube">
|
<input type="file" id="import_youtube" name="import_youtube">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
<label for="import_freetube">Import Freetube subscriptions (.db)</label>
|
<label for="import_freetube"><%= translate(locale, "Import Freetube subscriptions (.db)") %></label>
|
||||||
<input type="file" id="import_freetube" name="import_freetube">
|
<input type="file" id="import_freetube" name="import_freetube">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
<label for="import_newpipe_subscriptions">Import NewPipe subscriptions (.json)</label>
|
<label for="import_newpipe_subscriptions"><%= translate(locale, "Import NewPipe subscriptions (.json)") %></label>
|
||||||
<input type="file" id="import_newpipe_subscriptions" name="import_newpipe_subscriptions">
|
<input type="file" id="import_newpipe_subscriptions" name="import_newpipe_subscriptions">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
<label for="import_newpipe">Import NewPipe data (.zip)</label>
|
<label for="import_newpipe"><%= translate(locale, "Import NewPipe data (.zip)") %></label>
|
||||||
<input type="file" id="import_newpipe" name="import_newpipe">
|
<input type="file" id="import_newpipe" name="import_newpipe">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-controls">
|
<div class="pure-controls">
|
||||||
<button type="submit" class="pure-button pure-button-primary">Import</button>
|
<button type="submit" class="pure-button pure-button-primary"><%= translate(locale, "Import") %></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<legend>Export</legend>
|
<legend><%= translate(locale, "Export") %></legend>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
<a href="/subscription_manager?action_takeout=1">Export subscriptions as OPML</a>
|
<a href="/subscription_manager?action_takeout=1"><%= translate(locale, "Export subscriptions as OPML") %></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
<a href="/subscription_manager?action_takeout=1&format=newpipe">Export subscriptions as OPML (for NewPipe & FreeTube)</a>
|
<a href="/subscription_manager?action_takeout=1&format=newpipe"><%= translate(locale, "Export subscriptions as OPML (for NewPipe & FreeTube)") %></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
<a href="/subscription_manager?action_takeout=1&format=json">Export data as JSON</a>
|
<a href="/subscription_manager?action_takeout=1&format=json"><%= translate(locale, "Export data as JSON") %></a>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -1,13 +1,21 @@
|
||||||
|
<% content_for "header" do %>
|
||||||
|
<title><%= translate(locale, "Delete account") %> - Invidious</title>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<div class="h-box">
|
<div class="h-box">
|
||||||
<form class="pure-form pure-form-aligned" action="/delete_account?referer=<%= URI.escape(referer) %>" method="post">
|
<form class="pure-form pure-form-aligned" action="/delete_account?referer=<%= URI.escape(referer) %>" method="post">
|
||||||
<legend>Delete account?</legend>
|
<legend><%= translate(locale, "Delete account?") %></legend>
|
||||||
|
|
||||||
<div class="pure-g">
|
<div class="pure-g">
|
||||||
<div class="pure-u-1-2">
|
<div class="pure-u-1-2">
|
||||||
<button type="submit" name="submit" value="delete_account" class="pure-button pure-button-primary">Yes</button>
|
<button type="submit" name="submit" value="delete_account" class="pure-button pure-button-primary">
|
||||||
|
<%= translate(locale, "Yes") %>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="pure-u-1-2">
|
<div class="pure-u-1-2">
|
||||||
<a class="pure-button" href="<%= referer %>">No</a>
|
<a class="pure-button" href="<%= referer %>">
|
||||||
|
<%= translate(locale, "No") %>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
<% content_for "header" do %>
|
<% content_for "header" do %>
|
||||||
<title>History - Invidious</title>
|
<title><%= translate(locale, "History") %> - Invidious</title>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="pure-g h-box">
|
<div class="pure-g h-box">
|
||||||
<div class="pure-u-2-3">
|
<div class="pure-u-2-3">
|
||||||
<h3><span id="count"><%= user.watched.size %></span> videos</h3>
|
<h3><%= translate(locale, "`x` videos", %(<span id="count">#{user.watched.size}</span>)) %></h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="pure-u-1-3" style="text-align:right;">
|
<div class="pure-u-1-3" style="text-align:right;">
|
||||||
<h3>
|
<h3>
|
||||||
<a href="/clear_watch_history">Clear watch history</a>
|
<a href="/clear_watch_history"><%= translate(locale, "Clear watch history") %></a>
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -69,13 +69,17 @@ function mark_unwatched(target) {
|
||||||
<div class="pure-g h-box">
|
<div class="pure-g h-box">
|
||||||
<div class="pure-u-1 pure-u-md-1-5">
|
<div class="pure-u-1 pure-u-md-1-5">
|
||||||
<% if page >= 2 %>
|
<% if page >= 2 %>
|
||||||
<a href="/feed/history?page=<%= page - 1 %>">Previous page</a>
|
<a href="/feed/history?page=<%= page - 1 %>">
|
||||||
|
<%= translate(locale, "Previous page") %>
|
||||||
|
</a>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<div class="pure-u-1 pure-u-md-3-5"></div>
|
<div class="pure-u-1 pure-u-md-3-5"></div>
|
||||||
<div style="text-align:right;" class="pure-u-1 pure-u-md-1-5">
|
<div style="text-align:right;" class="pure-u-1 pure-u-md-1-5">
|
||||||
<% if watched.size >= limit %>
|
<% if watched.size >= limit %>
|
||||||
<a href="/feed/history?page=<%= page + 1 %>">Next page</a>
|
<a href="/feed/history?page=<%= page + 1 %>">
|
||||||
|
<%= translate(locale, "Next page") %>
|
||||||
|
</a>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<% content_for "header" do %>
|
<% content_for "header" do %>
|
||||||
<meta name="description" content="An alternative front-end to YouTube">
|
<meta name="description" content="<%= translate(locale, "An alternative front-end to YouTube") %>">
|
||||||
<title>Invidious</title>
|
<title>Invidious</title>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<h1>JavaScript license information</h1>
|
<h1><%= translate(locale, "JavaScript license information") %></h1>
|
||||||
<table id="jslicense-labels1">
|
<table id="jslicense-labels1">
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
|
@ -19,7 +19,7 @@
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
<a href="https://unpkg.com/dashjs@2.9.0/dist/dash.mediaplayer.debug.js">source</a>
|
<a href="https://unpkg.com/dashjs@2.9.0/dist/dash.mediaplayer.debug.js"><%= translate(locale, "source") %></a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
<a href="/js/silvermine-videojs-quality-selector.js">source</a>
|
<a href="/js/silvermine-videojs-quality-selector.js"><%= translate(locale, "source") %></a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
<a href="https://unpkg.com/video.js@6.12.1/dist/video.js">source</a>
|
<a href="https://unpkg.com/video.js@6.12.1/dist/video.js"><%= translate(locale, "source") %></a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
<a href="https://unpkg.com/videojs-contrib-quality-levels@2.0.7/dist/videojs-contrib-quality-levels.js">source</a>
|
<a href="https://unpkg.com/videojs-contrib-quality-levels@2.0.7/dist/videojs-contrib-quality-levels.js"><%= translate(locale, "source") %></a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
@ -75,7 +75,7 @@
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
<a href="https://unpkg.com/videojs-contrib-dash@2.8.2/dist/videojs-dash.js">source</a>
|
<a href="https://unpkg.com/videojs-contrib-dash@2.8.2/dist/videojs-dash.js"><%= translate(locale, "source") %></a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
@ -89,7 +89,7 @@
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
<a href="https://unpkg.com/@videojs/http-streaming@1.2.2/dist/videojs-http-streaming.js">source</a>
|
<a href="https://unpkg.com/@videojs/http-streaming@1.2.2/dist/videojs-http-streaming.js"><%= translate(locale, "source") %></a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
@ -103,7 +103,7 @@
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
<a href="https://unpkg.com/videojs-markers@1.0.1/dist/videojs-markers.js">source</a>
|
<a href="https://unpkg.com/videojs-markers@1.0.1/dist/videojs-markers.js"><%= translate(locale, "source") %></a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
@ -117,7 +117,7 @@
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
<a href="https://unpkg.com/videojs-share@2.0.1/dist/videojs-share.js">source</a>
|
<a href="https://unpkg.com/videojs-share@2.0.1/dist/videojs-share.js"><%= translate(locale, "source") %></a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
@ -131,7 +131,7 @@
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
<a href="/js/videojs.hotkeys.js">source</a>
|
<a href="/js/videojs.hotkeys.js"><%= translate(locale, "source") %></a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
@ -145,7 +145,7 @@
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
<a href="/js/watch.js">source</a>
|
<a href="/js/watch.js"><%= translate(locale, "source") %></a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<% content_for "header" do %>
|
<% content_for "header" do %>
|
||||||
<title>Login - Invidious</title>
|
<title><%= translate(locale, "Login") %> - Invidious</title>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="pure-g">
|
<div class="pure-g">
|
||||||
|
@ -8,31 +8,37 @@
|
||||||
<div class="h-box">
|
<div class="h-box">
|
||||||
<div class="pure-g">
|
<div class="pure-g">
|
||||||
<div class="pure-u-1-2">
|
<div class="pure-u-1-2">
|
||||||
<a class="pure-button <% if account_type == "invidious" %>pure-button-disabled<% end %>" href="/login">Login/Register</a>
|
<a class="pure-button <% if account_type == "invidious" %>pure-button-disabled<% end %>" href="/login">
|
||||||
|
<%= translate(locale, "Login/Register") %>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="pure-u-1-2">
|
<div class="pure-u-1-2">
|
||||||
<a class="pure-button <% if account_type == "google" %>pure-button-disabled<% end %>" href="/login?type=google">Login to Google</a>
|
<a class="pure-button <% if account_type == "google" %>pure-button-disabled<% end %>" href="/login?type=google">
|
||||||
|
<%= translate(locale, "Login to Google") %>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
<% if account_type == "invidious" %>
|
<% if account_type == "invidious" %>
|
||||||
<form class="pure-form pure-form-stacked" action="/login?referer=<%= URI.escape(referer) %>&type=invidious" method="post">
|
<form class="pure-form pure-form-stacked" action="/login?referer=<%= URI.escape(referer) %>&type=invidious" method="post">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<label for="email">User ID:</label>
|
<label for="email"><%= translate(locale, "User ID:") %></label>
|
||||||
<input required class="pure-input-1" name="email" type="text" placeholder="User ID">
|
<input required class="pure-input-1" name="email" type="text" placeholder="User ID">
|
||||||
|
|
||||||
<label for="password">Password:</label>
|
<label for="password"><%= translate(locale, "Password:") %></label>
|
||||||
<input required class="pure-input-1" name="password" type="password" placeholder="Password">
|
<input required class="pure-input-1" name="password" type="password" placeholder="Password">
|
||||||
|
|
||||||
<% if captcha_type == "image" %>
|
<% if captcha_type == "image" %>
|
||||||
<img style="width:100%" src='<%= captcha.not_nil![:image] %>'/>
|
<img style="width:100%" src='<%= captcha.not_nil![:image] %>'/>
|
||||||
<input type="hidden" name="token" value="<%= captcha.not_nil![:token] %>">
|
<input type="hidden" name="token" value="<%= captcha.not_nil![:token] %>">
|
||||||
<input type="hidden" name="challenge" value="<%= captcha.not_nil![:challenge] %>">
|
<input type="hidden" name="challenge" value="<%= captcha.not_nil![:challenge] %>">
|
||||||
<label for="answer">Time (h:mm:ss):</label>
|
<label for="answer"><%= translate(locale, "Time (h:mm:ss):") %></label>
|
||||||
<input required type="text" name="answer" type="text" placeholder="h:mm:ss">
|
<input required type="text" name="answer" type="text" placeholder="h:mm:ss">
|
||||||
|
|
||||||
<label>
|
<label>
|
||||||
<a href="/login?referer=<%= URI.escape(referer) %>&type=invidious&captcha=text">Text CAPTCHA</a>
|
<a href="/login?referer=<%= URI.escape(referer) %>&type=invidious&captcha=text">
|
||||||
|
<%= translate(locale, "Text CAPTCHA") %>
|
||||||
|
</a>
|
||||||
</label>
|
</label>
|
||||||
<% else %>
|
<% else %>
|
||||||
<% text_captcha.not_nil![:tokens].each_with_index do |token, i| %>
|
<% text_captcha.not_nil![:tokens].each_with_index do |token, i| %>
|
||||||
|
@ -43,29 +49,31 @@
|
||||||
<input required type="text" name="text_answer" type="text" placeholder="Answer">
|
<input required type="text" name="text_answer" type="text" placeholder="Answer">
|
||||||
|
|
||||||
<label>
|
<label>
|
||||||
<a href="/login?referer=<%= URI.escape(referer) %>&type=invidious">Image CAPTCHA</a>
|
<a href="/login?referer=<%= URI.escape(referer) %>&type=invidious">
|
||||||
|
<%= translate(locale, "Image CAPTCHA") %>
|
||||||
|
</a>
|
||||||
</label>
|
</label>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<button type="submit" name="action" value="signin" class="pure-button pure-button-primary">Sign In</button>
|
<button type="submit" name="action" value="signin" class="pure-button pure-button-primary"><%= translate(locale, "Sign In") %></button>
|
||||||
<button type="submit" name="action" value="register" class="pure-button pure-button-primary">Register</button>
|
<button type="submit" name="action" value="register" class="pure-button pure-button-primary"><%= translate(locale, "Register") %></button>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</form>
|
</form>
|
||||||
<% elsif account_type == "google" %>
|
<% elsif account_type == "google" %>
|
||||||
<form class="pure-form pure-form-stacked" action="/login?referer=<%= URI.escape(referer) %>" method="post">
|
<form class="pure-form pure-form-stacked" action="/login?referer=<%= URI.escape(referer) %>" method="post">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<label for="email">Email:</label>
|
<label for="email"><%= translate(locale, "Email:") %></label>
|
||||||
<input required class="pure-input-1" name="email" type="email" placeholder="Email">
|
<input required class="pure-input-1" name="email" type="email" placeholder="Email">
|
||||||
|
|
||||||
<label for="password">Password:</label>
|
<label for="password"><%= translate(locale, "Password:") %></label>
|
||||||
<input required class="pure-input-1" name="password" type="password" placeholder="Password">
|
<input required class="pure-input-1" name="password" type="password" placeholder="Password">
|
||||||
|
|
||||||
<% if tfa %>
|
<% if tfa %>
|
||||||
<label for="tfa">Google verification code:</label>
|
<label for="tfa"><%= translate(locale, "Google verification code:") %></label>
|
||||||
<input required class="pure-input-1" name="tfa" type="text" placeholder="Google verification code">
|
<input required class="pure-input-1" name="tfa" type="text" placeholder="Google verification code">
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<button type="submit" class="pure-button pure-button-primary">Sign In</button>
|
<button type="submit" class="pure-button pure-button-primary"><%= translate(locale, "Sign In") %></button>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</form>
|
</form>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -35,13 +35,17 @@
|
||||||
<div class="pure-g h-box">
|
<div class="pure-g h-box">
|
||||||
<div class="pure-u-1 pure-u-md-1-5">
|
<div class="pure-u-1 pure-u-md-1-5">
|
||||||
<% if page >= 2 %>
|
<% if page >= 2 %>
|
||||||
<a href="/playlist?list=<%= playlist.id %>&page=<%= page - 1 %>">Previous page</a>
|
<a href="/playlist?list=<%= playlist.id %>&page=<%= page - 1 %>">
|
||||||
|
<%= translate(locale, "Previous page") %>
|
||||||
|
</a>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<div class="pure-u-1 pure-u-md-3-5"></div>
|
<div class="pure-u-1 pure-u-md-3-5"></div>
|
||||||
<div style="text-align:right;" class="pure-u-1 pure-u-md-1-5">
|
<div style="text-align:right;" class="pure-u-1 pure-u-md-1-5">
|
||||||
<% if videos.size == 100 %>
|
<% if videos.size == 100 %>
|
||||||
<a href="/playlist?list=<%= playlist.id %>&page=<%= page + 1 %>">Next page</a>
|
<a href="/playlist?list=<%= playlist.id %>&page=<%= page + 1 %>">
|
||||||
|
<%= translate(locale, "Next page") %>
|
||||||
|
</a>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
<% content_for "header" do %>
|
||||||
|
<title><%= translate(locale, "Popular") %> - Invidious</title>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<div class="pure-g">
|
<div class="pure-g">
|
||||||
<% popular_videos.each_slice(4) do |slice| %>
|
<% popular_videos.each_slice(4) do |slice| %>
|
||||||
<% slice.each do |item| %>
|
<% slice.each do |item| %>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<% content_for "header" do %>
|
<% content_for "header" do %>
|
||||||
<title>Preferences - Invidious</title>
|
<title><%= translate(locale, "Preferences") %> - Invidious</title>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -11,30 +11,30 @@ function update_value(element) {
|
||||||
<div class="h-box">
|
<div class="h-box">
|
||||||
<form class="pure-form pure-form-aligned" action="/preferences?referer=<%= referer %>" method="post">
|
<form class="pure-form pure-form-aligned" action="/preferences?referer=<%= referer %>" method="post">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Player preferences</legend>
|
<legend><%= translate(locale, "Player preferences") %></legend>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
<label for="video_loop">Always loop: </label>
|
<label for="video_loop"><%= translate(locale, "Always loop: ") %></label>
|
||||||
<input name="video_loop" id="video_loop" type="checkbox" <% if user.preferences.video_loop %>checked<% end %>>
|
<input name="video_loop" id="video_loop" type="checkbox" <% if user.preferences.video_loop %>checked<% end %>>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
<label for="autoplay">Autoplay: </label>
|
<label for="autoplay"><%= translate(locale, "Autoplay: ") %></label>
|
||||||
<input name="autoplay" id="autoplay" type="checkbox" <% if user.preferences.autoplay %>checked<% end %>>
|
<input name="autoplay" id="autoplay" type="checkbox" <% if user.preferences.autoplay %>checked<% end %>>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
<label for="continue">Autoplay next video: </label>
|
<label for="continue"><%= translate(locale, "Autoplay next video: ") %></label>
|
||||||
<input name="continue" id="continue" type="checkbox" <% if user.preferences.continue %>checked<% end %>>
|
<input name="continue" id="continue" type="checkbox" <% if user.preferences.continue %>checked<% end %>>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
<label for="listen">Listen by default: </label>
|
<label for="listen"><%= translate(locale, "Listen by default: ") %></label>
|
||||||
<input name="listen" id="listen" type="checkbox" <% if user.preferences.listen %>checked<% end %>>
|
<input name="listen" id="listen" type="checkbox" <% if user.preferences.listen %>checked<% end %>>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
<label for="speed">Default speed: </label>
|
<label for="speed"><%= translate(locale, "Default speed: ") %></label>
|
||||||
<select name="speed" id="speed">
|
<select name="speed" id="speed">
|
||||||
<% {2.0, 1.5, 1.0, 0.5}.each do |option| %>
|
<% {2.0, 1.5, 1.0, 0.5}.each do |option| %>
|
||||||
<option <% if user.preferences.speed == option %> selected <% end %>><%= option %></option>
|
<option <% if user.preferences.speed == option %> selected <% end %>><%= option %></option>
|
||||||
|
@ -43,96 +43,105 @@ function update_value(element) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
<label for="quality">Preferred video quality: </label>
|
<label for="quality"><%= translate(locale, "Preferred video quality: ") %></label>
|
||||||
<select name="quality" id="quality">
|
<select name="quality" id="quality">
|
||||||
<% {"dash", "hd720", "medium", "small"}.each do |option| %>
|
<% {"dash", "hd720", "medium", "small"}.each do |option| %>
|
||||||
<option <% if user.preferences.quality == option %> selected <% end %>><%= option %></option>
|
<option value="<%= option %>" <% if user.preferences.quality == option %> selected <% end %>><%= translate(locale, option) %></option>
|
||||||
<% end %>
|
<% end %>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
<label for="volume">Player volume: </label>
|
<label for="volume"><%= translate(locale, "Player volume: ") %></label>
|
||||||
<input name="volume" id="volume" oninput="update_value(this);" type="range" min="0" max="100" step="5" value="<%= user.preferences.volume %>">
|
<input name="volume" id="volume" oninput="update_value(this);" type="range" min="0" max="100" step="5" value="<%= user.preferences.volume %>">
|
||||||
<span class="pure-form-message-inline" id="volume-value"><%= user.preferences.volume %></span>
|
<span class="pure-form-message-inline" id="volume-value"><%= user.preferences.volume %></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
<label for="comments_0">Default comments: </label>
|
<label for="comments_0"><%= translate(locale, "Default comments: ") %></label>
|
||||||
<select name="comments_0" id="comments_0">
|
<select name="comments_0" id="comments_0">
|
||||||
<% {"", "youtube", "reddit"}.each do |option| %>
|
<% {"", "youtube", "reddit"}.each do |option| %>
|
||||||
<option <% if user.preferences.comments[0] == option %> selected <% end %>><%= option %></option>
|
<option value="<%= option %>" <% if user.preferences.comments[0] == option %> selected <% end %>><%= translate(locale, option) %></option>
|
||||||
<% end %>
|
<% end %>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
<label for="comments_1">Fallback comments: </label>
|
<label for="comments_1"><%= translate(locale, "Fallback comments: ") %></label>
|
||||||
<select name="comments_1" id="comments_1">
|
<select name="comments_1" id="comments_1">
|
||||||
<% {"", "youtube", "reddit"}.each do |option| %>
|
<% {"", "youtube", "reddit"}.each do |option| %>
|
||||||
<option <% if user.preferences.comments[1] == option %> selected <% end %>><%= option %></option>
|
<option value="<%= option %>" <% if user.preferences.comments[1] == option %> selected <% end %>><%= translate(locale, option) %></option>
|
||||||
<% end %>
|
<% end %>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
<label for="captions_0">Default captions: </label>
|
<label for="captions_0"><%= translate(locale, "Default captions: ") %></label>
|
||||||
<select class="pure-u-1-5" name="captions_0" id="captions_0">
|
<select class="pure-u-1-5" name="captions_0" id="captions_0">
|
||||||
<% CAPTION_LANGUAGES.each do |option| %>
|
<% CAPTION_LANGUAGES.each do |option| %>
|
||||||
<option <% if user.preferences.captions[0] == option %> selected <% end %>><%= option %></option>
|
<option value="<%= option %>" <% if user.preferences.captions[0] == option %> selected <% end %>><%= translate(locale, option) %></option>
|
||||||
<% end %>
|
<% end %>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
<label for="captions_fallback">Fallback captions: </label>
|
<label for="captions_fallback"><%= translate(locale, "Fallback captions: ") %></label>
|
||||||
<select class="pure-u-1-5" name="captions_1" id="captions_1">
|
<select class="pure-u-1-5" name="captions_1" id="captions_1">
|
||||||
<% CAPTION_LANGUAGES.each do |option| %>
|
<% CAPTION_LANGUAGES.each do |option| %>
|
||||||
<option <% if user.preferences.captions[1] == option %> selected <% end %>><%= option %></option>
|
<option value="<%= option %>" <% if user.preferences.captions[1] == option %> selected <% end %>><%= translate(locale, option) %></option>
|
||||||
<% end %>
|
<% end %>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select class="pure-u-1-5" name="captions_2" id="captions_2">
|
<select class="pure-u-1-5" name="captions_2" id="captions_2">
|
||||||
<% CAPTION_LANGUAGES.each do |option| %>
|
<% CAPTION_LANGUAGES.each do |option| %>
|
||||||
<option <% if user.preferences.captions[2] == option %> selected <% end %>><%= option %></option>
|
<option value="<%= option %>" <% if user.preferences.captions[2] == option %> selected <% end %>><%= translate(locale, option) %></option>
|
||||||
<% end %>
|
<% end %>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
<label for="related_videos">Show related videos? </label>
|
<label for="related_videos"><%= translate(locale, "Show related videos? ") %></label>
|
||||||
<input name="related_videos" id="related_videos" type="checkbox" <% if user.preferences.related_videos %>checked<% end %>>
|
<input name="related_videos" id="related_videos" type="checkbox" <% if user.preferences.related_videos %>checked<% end %>>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<legend>Visual preferences</legend>
|
<legend><%= translate(locale, "Visual preferences") %></legend>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
<label for="dark_mode">Dark mode: </label>
|
<label for="locale"><%= translate(locale, "Language: ") %></label>
|
||||||
|
<select name="locale" id="locale">
|
||||||
|
<% LOCALES.each_key do |option| %>
|
||||||
|
<option value="<%= option %>" <% if user.preferences.locale == option %> selected <% end %>><%= option %></option>
|
||||||
|
<% end %>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pure-control-group">
|
||||||
|
<label for="dark_mode"><%= translate(locale, "Dark mode: ") %></label>
|
||||||
<input name="dark_mode" id="dark_mode" type="checkbox" <% if user.preferences.dark_mode %>checked<% end %>>
|
<input name="dark_mode" id="dark_mode" type="checkbox" <% if user.preferences.dark_mode %>checked<% end %>>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
<label for="thin_mode">Thin mode: </label>
|
<label for="thin_mode"><%= translate(locale, "Thin mode: ") %></label>
|
||||||
<input name="thin_mode" id="thin_mode" type="checkbox" <% if user.preferences.thin_mode %>checked<% end %>>
|
<input name="thin_mode" id="thin_mode" type="checkbox" <% if user.preferences.thin_mode %>checked<% end %>>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<legend>Subscription preferences</legend>
|
<legend><%= translate(locale, "Subscription preferences") %></legend>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
<label for="redirect_feed">Redirect homepage to feed: </label>
|
<label for="redirect_feed"><%= translate(locale, "Redirect homepage to feed: ") %></label>
|
||||||
<input name="redirect_feed" id="redirect_feed" type="checkbox" <% if user.preferences.redirect_feed %>checked<% end %>>
|
<input name="redirect_feed" id="redirect_feed" type="checkbox" <% if user.preferences.redirect_feed %>checked<% end %>>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
<label for="max_results">Number of videos shown in feed: </label>
|
<label for="max_results"><%= translate(locale, "Number of videos shown in feed: ") %></label>
|
||||||
<input name="max_results" id="max_results" type="number" value="<%= user.preferences.max_results %>">
|
<input name="max_results" id="max_results" type="number" value="<%= user.preferences.max_results %>">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
<label for="sort">Sort videos by: </label>
|
<label for="sort"><%= translate(locale, "Sort videos by: ") %></label>
|
||||||
<select name="sort" id="sort">
|
<select name="sort" id="sort">
|
||||||
<% {"published", "published - reverse", "alphabetically", "alphabetically - reverse", "channel name", "channel name - reverse"}.each do |option| %>
|
<% {"published", "published - reverse", "alphabetically", "alphabetically - reverse", "channel name", "channel name - reverse"}.each do |option| %>
|
||||||
<option <% if user.preferences.sort == option %> selected <% end %>><%= option %></option>
|
<option value="<%= option %>" <% if user.preferences.sort == option %> selected <% end %>><%= translate(locale, option) %></option>
|
||||||
<% end %>
|
<% end %>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
@ -143,39 +152,39 @@ function update_value(element) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
<label for="unseen_only">Only show unwatched: </label>
|
<label for="unseen_only"><%= translate(locale, "Only show unwatched: ") %></label>
|
||||||
<input name="unseen_only" id="unseen_only" type="checkbox" <% if user.preferences.unseen_only %>checked<% end %>>
|
<input name="unseen_only" id="unseen_only" type="checkbox" <% if user.preferences.unseen_only %>checked<% end %>>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
<label for="notifications_only">Only show notifications (if there are any): </label>
|
<label for="notifications_only"><%= translate(locale, "Only show notifications (if there are any): ") %></label>
|
||||||
<input name="notifications_only" id="notifications_only" type="checkbox" <% if user.preferences.notifications_only %>checked<% end %>>
|
<input name="notifications_only" id="notifications_only" type="checkbox" <% if user.preferences.notifications_only %>checked<% end %>>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<legend>Data preferences</legend>
|
<legend><%= translate(locale, "Data preferences") %></legend>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
<a href="/clear_watch_history?referer=<%= URI.escape(referer) %>">Clear watch history</a>
|
<a href="/clear_watch_history?referer=<%= URI.escape(referer) %>"><%= translate(locale, "Clear watch history") %></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
<a href="/data_control?referer=<%= URI.escape(referer) %>">Import/Export data</a>
|
<a href="/data_control?referer=<%= URI.escape(referer) %>"><%= translate(locale, "Import/Export data") %></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
<a href="/subscription_manager">Manage subscriptions</a>
|
<a href="/subscription_manager"><%= translate(locale, "Manage subscriptions") %></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
<a href="/feed/history">Watch history</a>
|
<a href="/feed/history"><%= translate(locale, "Watch history") %></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
<a href="/delete_account?referer=<%= URI.escape(referer) %>">Delete account</a>
|
<a href="/delete_account?referer=<%= URI.escape(referer) %>"><%= translate(locale, "Delete account") %></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-controls">
|
<div class="pure-controls">
|
||||||
<button type="submit" class="pure-button pure-button-primary">Save preferences</button>
|
<button type="submit" class="pure-button pure-button-primary"><%= translate(locale, "Save preferences") %></button>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -13,13 +13,17 @@
|
||||||
<div class="pure-g h-box">
|
<div class="pure-g h-box">
|
||||||
<div class="pure-u-1 pure-u-md-1-5">
|
<div class="pure-u-1 pure-u-md-1-5">
|
||||||
<% if page >= 2 %>
|
<% if page >= 2 %>
|
||||||
<a href="/search?q=<%= HTML.escape(query.not_nil!) %>&page=<%= page - 1 %>">Previous page</a>
|
<a href="/search?q=<%= HTML.escape(query.not_nil!) %>&page=<%= page - 1 %>">
|
||||||
|
<%= translate(locale, "Previous page") %>
|
||||||
|
</a>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<div class="pure-u-1 pure-u-md-3-5"></div>
|
<div class="pure-u-1 pure-u-md-3-5"></div>
|
||||||
<div style="text-align:right;" class="pure-u-1 pure-u-md-1-5">
|
<div style="text-align:right;" class="pure-u-1 pure-u-md-1-5">
|
||||||
<% if count >= 20 %>
|
<% if count >= 20 %>
|
||||||
<a href="/search?q=<%= HTML.escape(query.not_nil!) %>&page=<%= page + 1 %>">Next page</a>
|
<a href="/search?q=<%= HTML.escape(query.not_nil!) %>&page=<%= page + 1 %>">
|
||||||
|
<%= translate(locale, "Next page") %>
|
||||||
|
</a>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
<% content_for "header" do %>
|
<% content_for "header" do %>
|
||||||
<title>Subscription manager - Invidious</title>
|
<title><%= translate(locale, "Subscription manager") %> - Invidious</title>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="pure-g h-box">
|
<div class="pure-g h-box">
|
||||||
<div class="pure-u-1-3">
|
<div class="pure-u-1-3">
|
||||||
<h3><span id="count"><%= subscriptions.size %></span> subscriptions</h3>
|
<h3><%= translate(locale, "`x` subscriptions", %(<span id="count">#{subscriptions.size}</span>)) %></h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="pure-u-1-3" style="text-align:center;">
|
<div class="pure-u-1-3" style="text-align:center;">
|
||||||
<h3>
|
<h3>
|
||||||
<a href="/feed/history">Watch history</a>
|
<a href="/feed/history"><%= translate(locale, "Watch history") %></a>
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="pure-u-1-3" style="text-align:right;">
|
<div class="pure-u-1-3" style="text-align:right;">
|
||||||
<h3>
|
<h3>
|
||||||
<a href="/data_control?referer=<%= referer %>">Import/Export</a>
|
<a href="/data_control?referer=<%= referer %>"><%= translate(locale, "Import/Export") %></a>
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -33,7 +33,7 @@
|
||||||
data-id="<%= channel.id %>"
|
data-id="<%= channel.id %>"
|
||||||
onmouseenter='this["href"]="javascript:void(0)"'
|
onmouseenter='this["href"]="javascript:void(0)"'
|
||||||
href="/subscription_ajax?action_remove_subscriptions=1&c=<%= channel.id %>">
|
href="/subscription_ajax?action_remove_subscriptions=1&c=<%= channel.id %>">
|
||||||
unsubscribe
|
<%= translate(locale, "unsubscribe") %>
|
||||||
</a>
|
</a>
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
<% content_for "header" do %>
|
<% content_for "header" do %>
|
||||||
<title>Subscriptions - Invidious</title>
|
<title><%= translate(locale, "Subscriptions") %> - Invidious</title>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="pure-g h-box">
|
<div class="pure-g h-box">
|
||||||
<div class="pure-u-1-3">
|
<div class="pure-u-1-3">
|
||||||
<h3>
|
<h3>
|
||||||
<a href="/subscription_manager">Manage subscriptions</a>
|
<a href="/subscription_manager"><%= translate(locale, "Manage subscriptions") %></a>
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="pure-u-1-3" style="text-align:center;">
|
<div class="pure-u-1-3" style="text-align:center;">
|
||||||
<h3>
|
<h3>
|
||||||
<a href="/feed/history">Watch history</a>
|
<a href="/feed/history"><%= translate(locale, "Watch history") %></a>
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="pure-u-1-3" style="text-align:right;">
|
<div class="pure-u-1-3" style="text-align:right;">
|
||||||
|
@ -20,7 +20,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<center><%= notifications.size %> unseen notifications</center>
|
<center><%= translate(locale, "`x` unseen notifications", "#{notifications.size}") %></center>
|
||||||
|
|
||||||
<% if !notifications.empty? %>
|
<% if !notifications.empty? %>
|
||||||
<div class="h-box">
|
<div class="h-box">
|
||||||
|
@ -73,13 +73,17 @@ function mark_watched(target) {
|
||||||
<div class="pure-g">
|
<div class="pure-g">
|
||||||
<div class="pure-u-1 pure-u-md-1-5">
|
<div class="pure-u-1 pure-u-md-1-5">
|
||||||
<% if page >= 2 %>
|
<% if page >= 2 %>
|
||||||
<a href="/feed/subscriptions?max_results=<%= max_results %>&page=<%= page - 1 %>">Previous page</a>
|
<a href="/feed/subscriptions?max_results=<%= max_results %>&page=<%= page - 1 %>">
|
||||||
|
<%= translate(locale, "Previous page") %>
|
||||||
|
</a>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<div class="pure-u-1 pure-u-md-3-5"></div>
|
<div class="pure-u-1 pure-u-md-3-5"></div>
|
||||||
<div style="text-align:right;" class="pure-u-1 pure-u-md-1-5">
|
<div style="text-align:right;" class="pure-u-1 pure-u-md-1-5">
|
||||||
<% if (videos.size + notifications.size) == max_results %>
|
<% if (videos.size + notifications.size) == max_results %>
|
||||||
<a href="/feed/subscriptions?max_results=<%= max_results %>&page=<%= page + 1 %>">Next page</a>
|
<a href="/feed/subscriptions?max_results=<%= max_results %>&page=<%= page + 1 %>">
|
||||||
|
<%= translate(locale, "Next page") %>
|
||||||
|
</a>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -25,6 +25,8 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
<% locale = LOCALES[env.get("locale").as(String)]? %>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="pure-g">
|
<div class="pure-g">
|
||||||
<div class="pure-u-1 pure-u-md-2-24"></div>
|
<div class="pure-u-1 pure-u-md-2-24"></div>
|
||||||
|
@ -68,32 +70,46 @@
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="pure-u-1-4">
|
<div class="pure-u-1-4">
|
||||||
<a href="/signout?referer=<%= env.get?("current_page") %>&token=<%= env.get?("token") %>&challenge=<%= env.get?("challenge") %>" class="pure-menu-heading">Sign out</a>
|
<a href="/signout?referer=<%= env.get?("current_page") %>&token=<%= env.get?("token") %>&challenge=<%= env.get?("challenge") %>" class="pure-menu-heading">
|
||||||
|
<%= translate(locale, "Sign out") %>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<a href="/login?referer=<%= env.get?("current_page") %>" class="pure-menu-heading">Login</a>
|
<a href="/login?referer=<%= env.get?("current_page") %>" class="pure-menu-heading">
|
||||||
|
<%= translate(locale, "Login") %>
|
||||||
|
</a>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<%= content %>
|
<%= content %>
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
Released under the AGPLv3 by <a href="https://github.com/omarroth">Omar
|
<p>
|
||||||
Roth</a>.
|
<a href="https://github.com/omarroth">
|
||||||
Source available <a
|
<%= translate(locale, "Released under the AGPLv3 by Omar Roth.") %>
|
||||||
href="https://github.com/omarroth/invidious">here</a>.
|
</a>
|
||||||
<p>Liberapay:
|
</p>
|
||||||
|
<p>
|
||||||
|
<a href="https://github.com/omarroth/invidious">
|
||||||
|
<%= translate(locale, "Source available here.") %>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p><%= translate(locale, "Liberapay: ") %>
|
||||||
<a href="https://liberapay.com/omarroth">
|
<a href="https://liberapay.com/omarroth">
|
||||||
https://liberapay.com/omarroth
|
https://liberapay.com/omarroth
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<p>Patreon:
|
<p><%= translate(locale, "Patreon: ") %>
|
||||||
<a href="https://patreon.com/omarroth">
|
<a href="https://patreon.com/omarroth">
|
||||||
https://patreon.com/omarroth
|
https://patreon.com/omarroth
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<p>BTC: 356DpZyMXu6rYd55Yqzjs29n79kGKWcYrY</p>
|
<p><%= translate(locale, "BTC: ") %>356DpZyMXu6rYd55Yqzjs29n79kGKWcYrY</p>
|
||||||
<p>BCH: qq4ptclkzej5eza6a50et5ggc58hxsq5aylqut2npk</p>
|
<p><%= translate(locale, "BCH: ") %>qq4ptclkzej5eza6a50et5ggc58hxsq5aylqut2npk</p>
|
||||||
<p>View <a rel="jslicense" href="/licenses">JavaScript license information</a>.</p>
|
<p>
|
||||||
|
<a rel="jslicense" href="/licenses">
|
||||||
|
<%= translate(locale, "View JavaScript license information.") %>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pure-u-1 pure-u-md-2-24"></div>
|
<div class="pure-u-1 pure-u-md-2-24"></div>
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
<% content_for "header" do %>
|
||||||
|
<title><%= translate(locale, "Top") %> - Invidious</title>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<div class="pure-g">
|
<div class="pure-g">
|
||||||
<% top_videos.each_slice(4) do |slice| %>
|
<% top_videos.each_slice(4) do |slice| %>
|
||||||
<% slice.each do |item| %>
|
<% slice.each do |item| %>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<% content_for "header" do %>
|
<% content_for "header" do %>
|
||||||
<title>Trending - Invidious</title>
|
<title><%= translate(locale, "Trending") %> - Invidious</title>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="pure-g">
|
<div class="pure-g">
|
||||||
|
|
|
@ -52,11 +52,11 @@
|
||||||
<div class="pure-g">
|
<div class="pure-g">
|
||||||
<div class="pure-u-1 pure-u-md-1-5">
|
<div class="pure-u-1 pure-u-md-1-5">
|
||||||
<div class="h-box">
|
<div class="h-box">
|
||||||
<p><a href="https://www.youtube.com/watch?v=<%= video.id %>">Watch video on YouTube</a></p>
|
<p><a href="https://www.youtube.com/watch?v=<%= video.id %>"><%= translate(locale, "Watch video on Youtube") %></a></p>
|
||||||
<p><i class="icon ion-ios-eye"></i> <%= number_with_separator(video.views) %></p>
|
<p><i class="icon ion-ios-eye"></i> <%= number_with_separator(video.views) %></p>
|
||||||
<p><i class="icon ion-ios-thumbs-up"></i> <%= number_with_separator(video.likes) %></p>
|
<p><i class="icon ion-ios-thumbs-up"></i> <%= number_with_separator(video.likes) %></p>
|
||||||
<p><i class="icon ion-ios-thumbs-down"></i> <%= number_with_separator(video.dislikes) %></p>
|
<p><i class="icon ion-ios-thumbs-down"></i> <%= number_with_separator(video.dislikes) %></p>
|
||||||
<p id="Genre">Genre:
|
<p id="Genre"><%= translate(locale, "Genre: ") %>
|
||||||
<% if video.genre_url.empty? %>
|
<% if video.genre_url.empty? %>
|
||||||
<%= video.genre %>
|
<%= video.genre %>
|
||||||
<% else %>
|
<% else %>
|
||||||
|
@ -64,18 +64,18 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
</p>
|
</p>
|
||||||
<% if !video.license.empty? %>
|
<% if !video.license.empty? %>
|
||||||
<p id="License">License: <%= video.license %></p>
|
<p id="License"><%= translate(locale, "License: ") %><%= video.license %></p>
|
||||||
<% end %>
|
<% end %>
|
||||||
<p id="FamilyFriendly">Family friendly? <%= video.is_family_friendly %></p>
|
<p id="FamilyFriendly"><%= translate(locale, "Family friendly? ") %><%= video.is_family_friendly %></p>
|
||||||
<p id="Wilson">Wilson score: <%= video.wilson_score.round(4) %></p>
|
<p id="Wilson"><%= translate(locale, "Wilson score: ") %><%= video.wilson_score.round(4) %></p>
|
||||||
<p id="Rating">Rating: <%= rating.round(4) %> / 5</p>
|
<p id="Rating"><%= translate(locale, "Rating: ") %><%= rating.round(4) %> / 5</p>
|
||||||
<p id="Engagement">Engagement: <%= engagement.round(2) %>%</p>
|
<p id="Engagement"><%= translate(locale, "Engagement: ") %><%= engagement.round(2) %>%</p>
|
||||||
<% if video.allowed_regions.size != REGIONS.size %>
|
<% if video.allowed_regions.size != REGIONS.size %>
|
||||||
<p id="AllowedRegions">
|
<p id="AllowedRegions">
|
||||||
<% if video.allowed_regions.size < REGIONS.size / 2 %>
|
<% if video.allowed_regions.size < REGIONS.size / 2 %>
|
||||||
Whitelisted regions: <%= video.allowed_regions.join(", ") %>
|
<%= translate(locale, "Whitelisted regions: ") %><%= video.allowed_regions.join(", ") %>
|
||||||
<% else %>
|
<% else %>
|
||||||
Blacklisted regions: <%= (REGIONS.to_a - video.allowed_regions).join(", ") %>
|
<%= translate(locale, "Blacklisted regions: ") %><%= (REGIONS.to_a - video.allowed_regions).join(", ") %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
@ -94,14 +94,14 @@
|
||||||
<p>
|
<p>
|
||||||
<a id="subscribe" onclick="unsubscribe()" class="pure-button pure-button-primary"
|
<a id="subscribe" onclick="unsubscribe()" class="pure-button pure-button-primary"
|
||||||
href="/subscription_ajax?action_remove_subscriptions=1&c=<%= video.ucid %>&referer=<%= env.get("current_page") %>">
|
href="/subscription_ajax?action_remove_subscriptions=1&c=<%= video.ucid %>&referer=<%= env.get("current_page") %>">
|
||||||
<b>Unsubscribe | <%= video.sub_count_text %></b>
|
<b><%= translate(locale, "Unsubscribe") %> | <%= video.sub_count_text %></b>
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<% else %>
|
<% else %>
|
||||||
<p>
|
<p>
|
||||||
<a id="subscribe" onclick="subscribe()" class="pure-button pure-button-primary"
|
<a id="subscribe" onclick="subscribe()" class="pure-button pure-button-primary"
|
||||||
href="/subscription_ajax?action_create_subscription_to_channel=1&c=<%= video.ucid %>&referer=<%= env.get("current_page") %>">
|
href="/subscription_ajax?action_create_subscription_to_channel=1&c=<%= video.ucid %>&referer=<%= env.get("current_page") %>">
|
||||||
<b>Subscribe | <%= video.sub_count_text %></b>
|
<b><%= translate(locale, "Subscribe") %> | <%= video.sub_count_text %></b>
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
@ -109,12 +109,12 @@
|
||||||
<p>
|
<p>
|
||||||
<a id="subscribe" class="pure-button pure-button-primary"
|
<a id="subscribe" class="pure-button pure-button-primary"
|
||||||
href="/login?referer=<%= env.get("current_page") %>">
|
href="/login?referer=<%= env.get("current_page") %>">
|
||||||
<b>Login to subscribe to <%= video.author %></b>
|
<b><%= translate(locale, "Login to subscribe to `x`", video.author) %></b>
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
<p>
|
<p>
|
||||||
<b>Shared <%= video.published.to_s("%B %-d, %Y") %></b>
|
<b><%= translate(locale, "Shared `x`", video.published.to_s("%B %-d, %Y")) %></b>
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<div>
|
||||||
<%= video.description %>
|
<%= video.description %>
|
||||||
|
@ -125,8 +125,9 @@
|
||||||
<%= comment_html %>
|
<%= comment_html %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<noscript>
|
<noscript>
|
||||||
Hi! Looks like you have JavaScript disabled. Click <a href="/watch?<%= env.params.query %>&nojs=1">here</a> to view
|
<a href="/watch?<%= env.params.query %>&nojs=1">
|
||||||
comments, keep in mind it may take a bit longer to load.
|
<%= translate(locale, "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.") %>
|
||||||
|
</a>
|
||||||
</noscript>
|
</noscript>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
@ -145,7 +146,7 @@
|
||||||
<% if !rvs.empty? %>
|
<% if !rvs.empty? %>
|
||||||
<div id="continue" <% if plid %>style="display:none"<% end %>>
|
<div id="continue" <% if plid %>style="display:none"<% end %>>
|
||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
<label for="continue">Autoplay next video: </label>
|
<label for="continue"><%= translate(locale, "Autoplay next video: ") %></label>
|
||||||
<input name="continue" onclick="continue_autoplay(this)" id="continue" type="checkbox" <% if params[:continue] %>checked<% end %>>
|
<input name="continue" onclick="continue_autoplay(this)" id="continue" type="checkbox" <% if params[:continue] %>checked<% end %>>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
|
@ -241,7 +242,7 @@ function subscribe() {
|
||||||
if (xhr.status == 200) {
|
if (xhr.status == 200) {
|
||||||
subscribe_button = document.getElementById("subscribe");
|
subscribe_button = document.getElementById("subscribe");
|
||||||
subscribe_button.onclick = unsubscribe;
|
subscribe_button.onclick = unsubscribe;
|
||||||
subscribe_button.innerHTML = '<b>Unsubscribe | <%= video.sub_count_text %></b>'
|
subscribe_button.innerHTML = '<b><%= translate(locale, "Unsubscribe") %> | <%= video.sub_count_text %></b>'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -260,7 +261,7 @@ function unsubscribe() {
|
||||||
if (xhr.status == 200) {
|
if (xhr.status == 200) {
|
||||||
subscribe_button = document.getElementById("subscribe");
|
subscribe_button = document.getElementById("subscribe");
|
||||||
subscribe_button.onclick = subscribe;
|
subscribe_button.onclick = subscribe;
|
||||||
subscribe_button.innerHTML = '<b>Subscribe | <%= video.sub_count_text %></b>'
|
subscribe_button.innerHTML = '<b><%= translate(locale, "Subscribe") %> | <%= video.sub_count_text %></b>'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -276,9 +277,9 @@ function get_playlist() {
|
||||||
var plid = "<%= plid %>"
|
var plid = "<%= plid %>"
|
||||||
|
|
||||||
if (plid.startsWith("RD")) {
|
if (plid.startsWith("RD")) {
|
||||||
var plid_url = "/api/v1/mixes/<%= plid %>?continuation=<%= video.id %>&format=html";
|
var plid_url = "/api/v1/mixes/<%= plid %>?continuation=<%= video.id %>&format=html&hl=<%= env.get("locale").as(String) %>";
|
||||||
} else {
|
} else {
|
||||||
var plid_url = "/api/v1/playlists/<%= plid %>?continuation=<%= video.id %>&format=html";
|
var plid_url = "/api/v1/playlists/<%= plid %>?continuation=<%= video.id %>&format=html&hl=<%= env.get("locale").as(String) %>";
|
||||||
}
|
}
|
||||||
|
|
||||||
var xhr = new XMLHttpRequest();
|
var xhr = new XMLHttpRequest();
|
||||||
|
@ -335,7 +336,7 @@ function get_reddit_comments() {
|
||||||
comments.innerHTML =
|
comments.innerHTML =
|
||||||
'<h3><center class="loading"><i class="icon ion-ios-refresh"></i></center></h3>';
|
'<h3><center class="loading"><i class="icon ion-ios-refresh"></i></center></h3>';
|
||||||
|
|
||||||
var url = "/api/v1/comments/<%= video.id %>?source=reddit&format=html";
|
var url = "/api/v1/comments/<%= video.id %>?source=reddit&format=html&hl=<%= env.get("locale").as(String) %>";
|
||||||
var xhr = new XMLHttpRequest();
|
var xhr = new XMLHttpRequest();
|
||||||
xhr.responseType = "json";
|
xhr.responseType = "json";
|
||||||
xhr.timeout = 20000;
|
xhr.timeout = 20000;
|
||||||
|
@ -354,12 +355,12 @@ function get_reddit_comments() {
|
||||||
<p> \
|
<p> \
|
||||||
<b> \
|
<b> \
|
||||||
<a href="javascript:void(0)" onclick="swap_comments(\'youtube\')"> \
|
<a href="javascript:void(0)" onclick="swap_comments(\'youtube\')"> \
|
||||||
View YouTube comments \
|
<%= translate(locale, "View YouTube comments") %> \
|
||||||
</a> \
|
</a> \
|
||||||
</b> \
|
</b> \
|
||||||
</p> \
|
</p> \
|
||||||
<b> \
|
<b> \
|
||||||
<a rel="noopener" target="_blank" href="https://reddit.com{permalink}">View more comments on Reddit</a> \
|
<a rel="noopener" target="_blank" href="https://reddit.com{permalink}"><%= translate(locale, "View more comments on Reddit") %></a> \
|
||||||
</b> \
|
</b> \
|
||||||
</div> \
|
</div> \
|
||||||
<div>{contentHtml}</div> \
|
<div>{contentHtml}</div> \
|
||||||
|
@ -391,7 +392,7 @@ function get_youtube_comments() {
|
||||||
comments.innerHTML =
|
comments.innerHTML =
|
||||||
'<h3><center class="loading"><i class="icon ion-ios-refresh"></i></center></h3>';
|
'<h3><center class="loading"><i class="icon ion-ios-refresh"></i></center></h3>';
|
||||||
|
|
||||||
var url = "/api/v1/comments/<%= video.id %>?format=html";
|
var url = "/api/v1/comments/<%= video.id %>?format=html&hl=<%= env.get("locale").as(String) %>";
|
||||||
var xhr = new XMLHttpRequest();
|
var xhr = new XMLHttpRequest();
|
||||||
xhr.responseType = "json";
|
xhr.responseType = "json";
|
||||||
xhr.timeout = 20000;
|
xhr.timeout = 20000;
|
||||||
|
@ -406,11 +407,11 @@ function get_youtube_comments() {
|
||||||
<div> \
|
<div> \
|
||||||
<h3> \
|
<h3> \
|
||||||
<a href="javascript:void(0)" onclick="toggle_comments(this)">[ - ]</a> \
|
<a href="javascript:void(0)" onclick="toggle_comments(this)">[ - ]</a> \
|
||||||
View {commentCount} comments \
|
<%= translate(locale, "View `x` comments", "{commentCount}") %> \
|
||||||
</h3> \
|
</h3> \
|
||||||
<b> \
|
<b> \
|
||||||
<a href="javascript:void(0)" onclick="swap_comments(\'reddit\')"> \
|
<a href="javascript:void(0)" onclick="swap_comments(\'reddit\')"> \
|
||||||
View Reddit comments \
|
<%= translate(locale, "View Reddit comments") %> \
|
||||||
</a> \
|
</a> \
|
||||||
</b> \
|
</b> \
|
||||||
</div> \
|
</div> \
|
||||||
|
@ -449,7 +450,7 @@ function get_youtube_replies(target, load_more) {
|
||||||
body.innerHTML =
|
body.innerHTML =
|
||||||
'<h3><center class="loading"><i class="icon ion-ios-refresh"></i></center></h3>';
|
'<h3><center class="loading"><i class="icon ion-ios-refresh"></i></center></h3>';
|
||||||
|
|
||||||
var url = '/api/v1/comments/<%= video.id %>?format=html&continuation=' +
|
var url = '/api/v1/comments/<%= video.id %>?format=html&hl=<%= env.get("locale").as(String) %>&continuation=' +
|
||||||
continuation;
|
continuation;
|
||||||
var xhr = new XMLHttpRequest();
|
var xhr = new XMLHttpRequest();
|
||||||
xhr.responseType = 'json';
|
xhr.responseType = 'json';
|
||||||
|
@ -467,7 +468,7 @@ function get_youtube_replies(target, load_more) {
|
||||||
} else {
|
} else {
|
||||||
body.innerHTML = ' \
|
body.innerHTML = ' \
|
||||||
<p><a href="javascript:void(0)" \
|
<p><a href="javascript:void(0)" \
|
||||||
onclick="hide_youtube_replies(this)">Hide replies \
|
onclick="hide_youtube_replies(this)"><%= translate(locale, "Hide replies") %> \
|
||||||
</a></p> \
|
</a></p> \
|
||||||
<div>{contentHtml}</div>'.supplant({
|
<div>{contentHtml}</div>'.supplant({
|
||||||
contentHtml: xhr.response.contentHtml,
|
contentHtml: xhr.response.contentHtml,
|
||||||
|
|
Loading…
Reference in a new issue